Replaced HARBOR_USER and HARBOR_TOKEN with REGISTRY_USER and REGISTRY_TOKEN for improved clarity and consistency. Adjusted Docker login command and updated image tagging to reflect the new registry path under 'azaion'. This change enhances the pipeline's ability to authenticate and push images to the correct registry.
Satellite Provider API
A .NET 8.0 microservice for downloading, managing, and serving satellite imagery tiles from Google Maps. Supports region-based tile requests, route planning with automatic intermediate point generation, and geofencing capabilities.
Features
- Tile Management: Download and cache satellite imagery tiles from Google Maps
- Region Requests: Request tiles for specific geographic areas (100m - 10km squares)
- Route Planning: Create routes with automatic intermediate point interpolation (~200m intervals)
- Geofencing: Define polygon boundaries to filter regions inside/outside zones
- Image Stitching: Combine tiles into single images for regions
- Tile Packaging: Create ZIP archives of route tiles (max 50MB)
- Async Processing: Queue-based background processing for large requests
- Caching: Reuse previously downloaded tiles to minimize redundant downloads
- REST API: OpenAPI/Swagger documented endpoints
Prerequisites
- Docker and Docker Compose
- .NET 8.0 SDK (for local development)
- PostgreSQL (provided via Docker)
Quick Start
Run the Service
docker-compose up --build
The API will be available at http://localhost:5100
Swagger documentation: http://localhost:5100/swagger
Run with Tests
docker-compose -f docker-compose.yml -f docker-compose.tests.yml up --build --abort-on-container-exit
This command:
- Builds and starts all services
- Runs integration tests
- Exits when tests complete
Architecture
The service follows a layered architecture:
┌─────────────────────────────────────┐
│ API Layer (ASP.NET Core) │ HTTP endpoints
├─────────────────────────────────────┤
│ Services (Business Logic) │ Processing, validation
├─────────────────────────────────────┤
│ Data Access (Dapper + Repos) │ Database operations
├─────────────────────────────────────┤
│ PostgreSQL Database │ Persistence
└─────────────────────────────────────┘
Projects
- SatelliteProvider.Api: Web API and endpoints
- SatelliteProvider.Services: Business logic and processing
- SatelliteProvider.DataAccess: Database access and migrations
- SatelliteProvider.Common: Shared DTOs, interfaces, utilities
- SatelliteProvider.IntegrationTests: Integration test suite
- SatelliteProvider.Tests: Unit tests
API Endpoints
Download Single Tile
GET /api/satellite/tiles/latlon?Latitude={lat}&Longitude={lon}&ZoomLevel={zoom}
Downloads a single tile at specified coordinates and zoom level.
Parameters:
Latitude(double): Center latitudeLongitude(double): Center longitudeZoomLevel(int): Zoom level (higher = more detail, max ~20)
Response:
{
"id": "uuid",
"zoomLevel": 18,
"latitude": 37.7749,
"longitude": -122.4194,
"tileSizeMeters": 38.2,
"tileSizePixels": 256,
"imageType": "jpg",
"mapsVersion": "downloaded_2024-11-20",
"version": 2024,
"filePath": "18/158/90.jpg",
"createdAt": "2024-11-20T10:30:00Z",
"updatedAt": "2024-11-20T10:30:00Z"
}
Request Region Tiles
POST /api/satellite/request
Content-Type: application/json
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"latitude": 37.7749,
"longitude": -122.4194,
"sizeMeters": 500,
"zoomLevel": 18,
"stitchTiles": false
}
Requests tiles for a square region. Processing happens asynchronously.
Parameters:
id(guid): Unique identifier for the regionlatitude(double): Center latitudelongitude(double): Center longitudesizeMeters(double): Square side length (100-10000 meters)zoomLevel(int): Zoom level (default: 18)stitchTiles(bool): Create single stitched image (default: false)
Response:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "pending",
"csvFilePath": null,
"summaryFilePath": null,
"tilesDownloaded": 0,
"tilesReused": 0,
"createdAt": "2024-11-20T10:30:00Z",
"updatedAt": "2024-11-20T10:30:00Z"
}
Get Region Status
GET /api/satellite/region/{id}
Check processing status and get file paths when complete.
Status Values:
pending: Queued for processingprocessing: Currently downloading tilescompleted: All tiles ready, files createdfailed: Processing failed
Response (completed):
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "completed",
"csvFilePath": "./ready/region_3fa85f64-5717-4562-b3fc-2c963f66afa6_ready.csv",
"summaryFilePath": "./ready/region_3fa85f64-5717-4562-b3fc-2c963f66afa6_summary.txt",
"tilesDownloaded": 12,
"tilesReused": 4,
"createdAt": "2024-11-20T10:30:00Z",
"updatedAt": "2024-11-20T10:32:15Z"
}
Create Route
POST /api/satellite/route
Content-Type: application/json
{
"id": "7fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "City Tour Route",
"description": "Downtown to waterfront",
"regionSizeMeters": 200,
"zoomLevel": 18,
"points": [
{ "latitude": 37.7749, "longitude": -122.4194, "name": "Start" },
{ "latitude": 37.7849, "longitude": -122.4094, "name": "Middle" },
{ "latitude": 37.7949, "longitude": -122.3994, "name": "End" }
],
"geofences": {
"polygons": [
{
"name": "Restricted Area",
"points": [
{ "latitude": 37.775, "longitude": -122.420 },
{ "latitude": 37.776, "longitude": -122.420 },
{ "latitude": 37.776, "longitude": -122.418 },
{ "latitude": 37.775, "longitude": -122.418 }
]
}
],
"direction": "inside"
},
"requestMaps": true,
"createTilesZip": true
}
Creates a route with intermediate points calculated automatically.
Parameters:
id(guid): Route identifiername(string): Route namedescription(string): Optional descriptionregionSizeMeters(double): Size of region at each pointzoomLevel(int): Zoom level for tilespoints(array): Original route pointsgeofences(object): Optional polygon boundariespolygons: Array of polygon definitionsdirection: "inside" or "outside" (filter regions)
requestMaps(bool): Automatically request tiles for route regionscreateTilesZip(bool): Create ZIP file of all tiles
Response:
{
"id": "7fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "City Tour Route",
"description": "Downtown to waterfront",
"regionSizeMeters": 200,
"zoomLevel": 18,
"totalDistanceMeters": 2486.5,
"totalPoints": 15,
"points": [
{
"sequenceNumber": 0,
"latitude": 37.7749,
"longitude": -122.4194,
"pointType": "original",
"segmentIndex": 0,
"distanceFromPrevious": 0
},
{
"sequenceNumber": 1,
"latitude": 37.7765,
"longitude": -122.4177,
"pointType": "intermediate",
"segmentIndex": 0,
"distanceFromPrevious": 198.3
}
],
"regions": [
"region-id-1",
"region-id-2"
],
"tilesZipPath": "./ready/route_7fa85f64-5717-4562-b3fc-2c963f66afa6_tiles.zip",
"createdAt": "2024-11-20T10:30:00Z"
}
Get Route
GET /api/satellite/route/{id}
Retrieve route details including all interpolated points.
Configuration
Configuration is managed through appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=satelliteprovider;Username=postgres;Password=postgres"
},
"MapConfig": {
"Service": "GoogleMaps",
"ApiKey": "your-google-maps-api-key"
},
"StorageConfig": {
"TilesDirectory": "./tiles",
"ReadyDirectory": "./ready"
},
"ProcessingConfig": {
"MaxConcurrentDownloads": 4,
"MaxConcurrentRegions": 20,
"DefaultZoomLevel": 20,
"QueueCapacity": 1000,
"DelayBetweenRequestsMs": 50,
"SessionTokenReuseCount": 100
}
}
Environment Variables (Docker)
Override settings via environment variables in docker-compose.yml:
environment:
- ConnectionStrings__DefaultConnection=Host=db;Database=satelliteprovider;Username=postgres;Password=postgres
- MapConfig__ApiKey=your-api-key-here
File Storage
Tiles
Downloaded tiles are stored in:
./tiles/{zoomLevel}/{x}/{y}.jpg
Output Files
CSV File (region_{id}_ready.csv):
Comma-separated list of tiles covering the region, ordered top-left to bottom-right:
latitude,longitude,filepath
37.7750,-122.4195,18/158/90.jpg
37.7750,-122.4193,18/158/91.jpg
Summary File (region_{id}_summary.txt):
Processing statistics:
Region: 3fa85f64-5717-4562-b3fc-2c963f66afa6
Size: 500m x 500m
Center: 37.7749, -122.4194
Zoom Level: 18
Tiles Downloaded: 12
Tiles Reused: 4
Total Tiles: 16
Processing Time: 3.5s
Stitched Image (region_{id}_stitched.jpg):
Single combined image when stitchTiles: true
Route ZIP (route_{id}_tiles.zip):
ZIP archive of all tiles for route when createTilesZip: true (max 50MB)
Database Schema
Tables
tiles: Cached satellite tiles
- Unique per (latitude, longitude, tile_size_meters, zoom_level)
- Tracks maps version for cache invalidation
regions: Region requests and processing status
- Status tracking: pending → processing → completed/failed
- Links to output files when complete
routes: Route definitions
- Stores original configuration
- Links to calculated points and regions
route_points: All points (original + interpolated)
- Sequenced in route order
- Type: "original" or "intermediate"
- Includes distance calculations
route_regions: Junction table
- Links routes to regions for tile requests
- Tracks geofence status (inside/outside)
Development
Building Locally
cd SatelliteProvider.Api
dotnet build
dotnet run
Running Tests
Unit Tests:
cd SatelliteProvider.Tests
dotnet test
Integration Tests:
docker-compose -f docker-compose.yml -f docker-compose.tests.yml up --build --abort-on-container-exit
Database Migrations
Migrations run automatically on startup. SQL files are located in:
SatelliteProvider.DataAccess/Migrations/
To add a new migration:
- Create
NNN_DescriptiveName.sql(increment number) - Set Build Action to
EmbeddedResource - Restart application
Logging
Logs are written to:
- Console (stdout)
./logs/satellite-provider-YYYYMMDD.log
Log level can be adjusted in appsettings.json under Serilog:MinimumLevel.
Performance Considerations
Tile Caching
- Tiles are cached indefinitely (no expiration)
- Reused across multiple region/route requests
- Reduces redundant downloads significantly
Concurrent Processing
- Configurable concurrent downloads (default: 4)
- Configurable concurrent regions (default: 20)
- Queue capacity: 1000 requests
Rate Limiting
- Delay between requests: 50ms default
- Session token reuse: 100 tiles per token
- Helps avoid Google Maps rate limits
Geofencing
- Uses point-in-polygon algorithm
- Reduces unnecessary tile downloads
- Spatial index on polygon data
Troubleshooting
Service won't start
- Check Docker is running
- Verify ports 5100 and 5432 are available
- Check logs:
docker-compose logs api
Tiles not downloading
- Verify Google Maps API key is configured
- Check internet connectivity
- Review logs for HTTP errors
- Check rate limiting settings
Region stuck in "processing"
- Check background service is running:
docker-compose logs api - Verify database connection
- Check queue capacity not exceeded
- Review logs for exceptions
Tests failing
- Ensure all containers are healthy before tests run
- Check test container logs:
docker-compose logs tests - Verify database migrations completed
- Check network connectivity between containers
Out of disk space
- Tiles accumulate in
./tiles/directory - Implement cleanup strategy for old tiles
- Monitor disk usage
License
This project is proprietary software.
Support
For issues and questions, please contact the development team.