mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 06:51:13 +00:00
5d84d2839e
Step 12 (Test-Spec Sync, cycle-update mode): - blackbox-tests.md: append BT-23..BT-26 for AZ-505's new observable behaviors (inventory order/shape; leaflet most-recent via location_hash; HTTP/2 multiplex over TLS+ALPN; request validation). - performance-tests.md: append PT-09 (inventory p95 ≤ 1000ms / 2500 tiles); records cycle-6 measured p95=66ms; documents promotion path to scripts/run-performance-tests.sh if budget ever tightens. - traceability-matrix.md: resolve the 5 AZ-503 deferrals (AC-5/6/9/10/12) by pointing at AZ-505 test names + add 7 AZ-505 AC rows (AC-1..AC-7) + bump totals (90 -> 94 tests, 56/56 -> 63/63 in-scope) + add cycle-6 coverage shape notes (budget relaxation rationale, voting-filter deferral note, TLS+ALPN pivot, NFR propagation). Step 13 (Update Docs, task mode): - common_dtos.md: add 5 new TileInventory DTOs. - common_interfaces.md: add ITileService.GetInventoryAsync. - services_tile_service.md: document TileService.GetInventoryAsync steps + the XOR-validation-in-handler note. - dataaccess_migrator.md: bump migration count 14 -> 15; describe migration 015 (AZ-505 leaflet covering index, lock window, INCLUDE-list trade-off). - system-flows.md: add F7 (Leaflet Tile Serving, AZ-310 + AZ-505 location_hash rewire + TLS+ALPN) and F8 (Tile Inventory Bulk Lookup) with sequence diagrams, validation surface, and AC-4 perf evidence. Update Flow Inventory + Dependencies tables accordingly. - glossary.md: add "Tile Inventory" entry pointing at the v1.0.0 contract. - ripple_log_cycle6.md: new file — exhaustive reverse-dependency analysis confirms zero stale downstream module docs. Advance autodev state from step 11 -> 14 (skipping 12+13 as completed in this commit; auto-chain through Step 14 = Security Audit optional gate). Co-authored-by: Cursor <cursoragent@cursor.com>
5.8 KiB
5.8 KiB
Module: Services/TileService
Purpose
Orchestrates tile downloading and persistence. Bridges the downloader (Google Maps) with the tile repository (PostgreSQL), handling in-memory caching, entity creation, and metadata mapping. Single ownership point for all tile read/write business logic — both region-batch and single-tile API endpoints route through this service.
csproj: SatelliteProvider.Services.TileDownloader/TileService.cs
Public Interface
TileService (implements ITileService)
DownloadAndStoreTilesAsync(double lat, double lon, double sizeMeters, int zoomLevel, CancellationToken) → Task<List<TileMetadata>>:- Queries existing tiles in the region from the repository — most-recent across sources per
(latitude, longitude, tile_zoom, tile_size_meters)(AZ-484 selection rule applied byTileRepository.GetTilesByRegionAsyncviaDISTINCT ON) - Calls
ISatelliteDownloader.GetTilesWithMetadataAsyncwith existing tiles to skip - Creates
TileEntityfor each newly downloaded tile and inserts via repository (per-source UPSERT keyed on(latitude, longitude, tile_zoom, tile_size_meters, source)) - Returns combined list of existing + new tile metadata
- Queries existing tiles in the region from the repository — most-recent across sources per
GetTileAsync(Guid id) → Task<TileMetadata?>: single tile lookupGetTilesByRegionAsync(double lat, double lon, double sizeMeters, int zoomLevel) → Task<IEnumerable<TileMetadata>>: query tiles in a regionGetOrDownloadTileAsync(int z, int x, int y, CancellationToken) → Task<TileBytes>(AZ-310): cache → repository → downloader fallback for single Z/X/Y servingDownloadAndStoreSingleTileAsync(double latitude, double longitude, int zoomLevel, CancellationToken) → Task<TileMetadata>(AZ-311): download one tile by lat/lon, persist, return metadataGetInventoryAsync(TileInventoryRequest request, CancellationToken) → Task<TileInventoryResponse>(AZ-505): bulk per-cell metadata read forPOST /api/satellite/tiles/inventory. Steps:- Project the request to an ordered
Guid[]oflocation_hashvalues — either by computingUuidv5.LocationHashForTile(z, x, y)per entry (Form Arequest.Tiles) or by echoing the caller-supplied hashes (Form Brequest.LocationHashes). The request-order vector is retained so step 3 can shape the response in input order. - Call
ITileRepository.GetTilesByLocationHashesAsync(hashes, CancellationToken)once. The repository returns the most-recent row per hash ((captured_at DESC, updated_at DESC, id DESC) LIMIT 1perlocation_hash); this is the AZ-484 / AZ-503-foundation selection rule preserved at the bulk layer. - Build a
TileInventoryEntry[]of the same length as the input vector. For each request slot: emitPresent=false(onlyLocationHashpopulated) when no row was returned for that hash; otherwise emitPresent=truewithId/CapturedAt/Source/FlightId/ResolutionMPerPxpopulated from the row. Order matches the request — duplicate hashes in the request produce duplicate entries pointing at the same row (tile-inventory.mdv1.0.0 Inv-2 / Inv-3). - XOR validation (both populated / neither populated) and the 5000-entry cap are NOT enforced here — they live in the API handler (
GetTilesInventoryinProgram.cs) so the HTTP-layer error contract is single-sourced andITileService.GetInventoryAsynccan be called from non-HTTP contexts without re-implementing the gate.
- Project the request to an ordered
Internal Logic
- New rows write
Version = nullandMapsVersion = null(post-AZ-357 / AZ-373); theversionandmaps_versioncolumns are retained for backward compatibility with pre-existing rows - AZ-484:
BuildTileEntitystamps every newly downloaded row withSource = TileSourceConverter.ToWireValue(TileSource.GoogleMaps)(wire value"google_maps") andCapturedAt = DateTime.UtcNow. The Google Maps download path is the only producer of'google_maps'rows; UAV ingestion (separate task) is the only producer of'uav'rows. - AZ-503:
BuildTileEntitycomputes deterministic identity fields —Id = Uuidv5.Create(Uuidv5.TileNamespace, "{z}/{x}/{y}/google_maps/00000000-0000-0000-0000-000000000000"),LocationHash = Uuidv5.Create(Uuidv5.TileNamespace, "{z}/{x}/{y}"),ContentSha256 = SHA256.HashData(<jpeg bytes from disk>).FlightIdis alwaysnullfor Google Maps tiles. NoGuid.NewGuid()remains on this path. MapToMetadata(TileEntity) → TileMetadata: entity-to-DTO mapping (static helper);MapsVersionis no longer projected ontoTileMetadata/DownloadTileResponse.Source,CapturedAt,FlightId,LocationHash,ContentSha256are not currently projected to the public DTO (no API contract change observable for AZ-484 or AZ-503).TileSizePixelssourced fromMapConfig.TileSizePixels(default 256, post-AZ-371); image type fixed at"jpg"IMemoryCachekeyed by(z, x, y)with 1h absolute / 30min sliding expiration; populated on first hit and on downloader fallback
Dependencies
ISatelliteDownloader(resolved via DI; concrete isGoogleMapsDownloaderV2)ITileRepositoryIMemoryCache(registered byAddTileDownloader())SatelliteProvider.Common.DTO— GeoPoint, TileMetadata, TileBytesSatelliteProvider.Common.Enums—TileSource,TileSourceConverter(AZ-484)SatelliteProvider.Common.Utils.Uuidv5(AZ-503) — deterministic UUIDv5 generator +TileNamespaceconstantSystem.Security.Cryptography.SHA256(AZ-503) — content digestSatelliteProvider.DataAccess.Models— TileEntity
Consumers
RegionService.ProcessRegionAsync— downloads and retrieves tiles for a region
Data Models
Transforms between TileEntity (persistence) and TileMetadata (DTO).
Configuration
None directly; relies on GoogleMapsDownloaderV2's configuration.
External Integrations
Indirect: Google Maps (via downloader), PostgreSQL (via repository).
Security
None.
Tests
No dedicated tests.