# TileDownloader ## 1. High-Level Overview **Purpose**: Acquires satellite imagery tiles from Google Maps, stores them on disk, and persists metadata to the database. Handles session tokens, concurrent downloads, retry logic, and tile deduplication. **Architectural Pattern**: Service + Gateway (wraps external API with retry/throttling) **csproj**: `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj` (split out of the monolithic `SatelliteProvider.Services` project in epic AZ-309) **Upstream dependencies**: Common (DTOs, GeoUtils, configs, RateLimitException), DataAccess (TileEntity, ITileRepository) **Downstream consumers**: RegionProcessing and WebApi — both via `ITileService` from Common (no compile-time `ProjectReference` from any consumer to this project's concrete types). ## 2. Internal Interfaces ### Class: GoogleMapsDownloaderV2 | Method | Input | Output | Async | Error Types | |--------|-------|--------|-------|-------------| | `DownloadSingleTileAsync` | lat, lon, zoomLevel, CancellationToken | `DownloadedTileInfoV2` | Yes | ArgumentException, RateLimitException, HttpRequestException | | `GetTilesWithMetadataAsync` | center, radiusM, zoom, existingTiles, CancellationToken | `List` | Yes | ArgumentException, RateLimitException, HttpRequestException | ### Service: TileService (implements ITileService) | Method | Input | Output | Async | Error Types | |--------|-------|--------|-------|-------------| | `DownloadAndStoreTilesAsync` | lat, lon, sizeM, zoom, CancellationToken | `List` | Yes | propagated from downloader | | `GetTileAsync` | Guid | `TileMetadata?` | Yes | NpgsqlException | | `GetTilesByRegionAsync` | lat, lon, sizeM, zoom | `IEnumerable` | Yes | NpgsqlException | | `GetOrDownloadTileAsync` (AZ-310) | z, x, y, CancellationToken | `TileBytes` | Yes | propagated from downloader | | `DownloadAndStoreSingleTileAsync` (AZ-311) | lat, lon, zoom, CancellationToken | `TileMetadata` | Yes | propagated from downloader | ## 4. Data Access Patterns ### Caching Strategy | Data | Cache Type | TTL | Invalidation | |------|-----------|-----|-------------| | Tile bytes | In-memory (IMemoryCache, owned by TileService since AZ-310) | 1h absolute, 30min sliding | None (manual restart) | | Tile metadata | Database | Until year rollover | Version-based (current year) | | Active downloads | ConcurrentDictionary | Duration of download | Removed on completion | ## 5. Implementation Details **Algorithmic Complexity**: Tile grid calculation is O(w×h) where w×h is the number of tiles covering the bounding box. **State Management**: `_activeDownloads` (ConcurrentDictionary) prevents duplicate concurrent downloads. `_downloadSemaphore` limits parallelism. **Key Dependencies**: | Library | Version | Purpose | |---------|---------|---------| | Newtonsoft.Json | 13.0.4 | Serialize session creation request body | | IHttpClientFactory | built-in | Create HttpClient instances per request | **Error Handling**: - Exponential backoff retry for 429 (rate limit) and 5xx errors: 1s → 2s → 4s → 8s → 16s, max 30s, 5 retries - Immediate throw for 401/403 (auth errors) and cancellation - `RateLimitException` thrown after exhausting retries on 429 ## 7. Caveats & Edge Cases - `GoogleMapsDownloaderV2` is registered behind `ISatelliteDownloader` (resolved by AZ-310 cleanup); the previous concrete-type coupling is gone. - User-Agent header spoofs Chrome — could be rejected if Google changes detection - Allowed zoom levels hardcoded to [15,16,17,18,19] — throws for others - Session token rotation threshold (100 tiles) is an educated guess; Google's actual limit is not documented - Static `_activeDownloads` dictionary means deduplication is process-wide, surviving service scope boundaries ## 8. Dependency Graph **Must be implemented after**: Common, DataAccess **Can be implemented in parallel with**: nothing (needs both foundations) **Blocks**: RegionProcessing ## 9. Logging Strategy | Log Level | When | Example | |-----------|------|---------| | ERROR | Download failure, session token failure | `Tile download failed. Tile: (X, Y), Status: {StatusCode}` | | WARN | Rate limiting retry | `Rate limited (429). Waiting {Delay}s before retry` | | INFO | — | (no INFO-level logs in this component) |