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.