# Module: Services/GoogleMapsDownloaderV2 ## Purpose Downloads satellite imagery tiles from Google Maps. Handles session token management, concurrent download throttling, retry logic with exponential backoff, and tile deduplication. **csproj**: `SatelliteProvider.Services.TileDownloader/GoogleMapsDownloaderV2.cs` ## Public Interface ### GoogleMapsDownloaderV2 - Constructor: `GoogleMapsDownloaderV2(ILogger, IOptions, IOptions, IOptions, IHttpClientFactory)` - `DownloadSingleTileAsync(double lat, double lon, int zoomLevel, CancellationToken) → Task`: downloads one tile at specified coordinates. Validates zoom level, creates session token, downloads image, saves to disk. - `GetTilesWithMetadataAsync(GeoPoint center, double radiusM, int zoomLevel, IEnumerable existingTiles, CancellationToken) → Task>`: downloads all tiles in a bounding box, skipping those already present in `existingTiles`. Manages session token rotation. ### DownloadedTileInfoV2 (record) - `X`, `Y` (int), `ZoomLevel` (int), `CenterLatitude`, `CenterLongitude` (double), `FilePath` (string), `TileSizeMeters` (double) ### RateLimitException (exception) Lives in `SatelliteProvider.Common.Exceptions` (relocated from this module in epic AZ-309 so RegionProcessing can catch it without acquiring a `ProjectReference` to TileDownloader). Thrown when Google Maps returns 429 Too Many Requests and retries are exhausted. ## Internal Logic - **Allowed zoom levels**: 15, 16, 17, 18, 19 — throws `ArgumentException` for others - **URL template**: `https://mt{server}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&token={token}` - **Session tokens**: obtained via `https://tile.googleapis.com/v1/createSession?key={apiKey}`, rotated every `SessionTokenReuseCount` tiles (default: 100) - **Concurrency control**: `SemaphoreSlim` limits parallel downloads to `MaxConcurrentDownloads` (default: 4) - **Deduplication**: `ConcurrentDictionary>` (`_activeDownloads`) prevents duplicate concurrent downloads of the same tile - **Retry logic**: exponential backoff (1s base, 30s max, 5 retries) for 429 and 5xx errors. Cancellation and auth errors (401, 403) propagate immediately. - **Server selection**: `(x + y) % 4` distributes requests across `mt0`–`mt3`; single-tile downloads always use `mt0` - **Delay between requests**: configurable via `ProcessingConfig.DelayBetweenRequestsMs` - **Tile size calculation**: `CalculateTileSizeInMeters` uses Earth circumference × cos(latitude) / (2^zoom × 256) ## Dependencies - `SatelliteProvider.Common.Configs` — MapConfig, StorageConfig, ProcessingConfig - `SatelliteProvider.Common.DTO` — GeoPoint - `SatelliteProvider.Common.Exceptions` — RateLimitException - `SatelliteProvider.Common.Utils` — GeoUtils - `SatelliteProvider.DataAccess.Models` — TileEntity (for existingTiles parameter) - NuGet: `Newtonsoft.Json`, `Microsoft.Extensions.Http`, `Microsoft.Extensions.Options` ## Consumers - `TileService` — `GetTilesWithMetadataAsync` and `DownloadSingleTileAsync` (the API endpoints reach this class only through `ITileService` post AZ-310 / AZ-311) ## Data Models Produces `DownloadedTileInfoV2` records; accepts `TileEntity` for cache checks. ## Configuration | Config | Key | Used For | |--------|-----|----------| | MapConfig | ApiKey | Session token requests | | StorageConfig | TilesDirectory | File save paths | | ProcessingConfig | MaxConcurrentDownloads | SemaphoreSlim capacity | | ProcessingConfig | DelayBetweenRequestsMs | Throttle delay | | ProcessingConfig | SessionTokenReuseCount | Token rotation threshold | ## External Integrations | Integration | Protocol | Details | |-------------|----------|---------| | Google Maps Tile API | HTTPS | `mt*.google.com/vt/lyrs=s` for tiles | | Google Maps Session API | HTTPS | `tile.googleapis.com/v1/createSession` | | File system | Local FS | Writes JPEG tiles to `StorageConfig.TilesDirectory` | ## Security - API key transmitted over HTTPS to Google endpoints - User-Agent spoofs a Chrome browser to match expected Google Maps client ## Tests No dedicated unit tests (the test file `GoogleMapsDownloaderTests.cs` contains only a dummy test).