mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-04-22 10:46:38 +00:00
471 lines
12 KiB
Markdown
471 lines
12 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
docker-compose up --build
|
|
```
|
|
|
|
The API will be available at `http://localhost:5100`
|
|
|
|
Swagger documentation: `http://localhost:5100/swagger`
|
|
|
|
### Run with Tests
|
|
|
|
```bash
|
|
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
|
|
|
|
```http
|
|
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 latitude
|
|
- `Longitude` (double): Center longitude
|
|
- `ZoomLevel` (int): Zoom level (higher = more detail, max ~20)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```http
|
|
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 region
|
|
- `latitude` (double): Center latitude
|
|
- `longitude` (double): Center longitude
|
|
- `sizeMeters` (double): Square side length (100-10000 meters)
|
|
- `zoomLevel` (int): Zoom level (default: 18)
|
|
- `stitchTiles` (bool): Create single stitched image (default: false)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```http
|
|
GET /api/satellite/region/{id}
|
|
```
|
|
|
|
Check processing status and get file paths when complete.
|
|
|
|
**Status Values:**
|
|
- `pending`: Queued for processing
|
|
- `processing`: Currently downloading tiles
|
|
- `completed`: All tiles ready, files created
|
|
- `failed`: Processing failed
|
|
|
|
**Response (completed):**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```http
|
|
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 identifier
|
|
- `name` (string): Route name
|
|
- `description` (string): Optional description
|
|
- `regionSizeMeters` (double): Size of region at each point
|
|
- `zoomLevel` (int): Zoom level for tiles
|
|
- `points` (array): Original route points
|
|
- `geofences` (object): Optional polygon boundaries
|
|
- `polygons`: Array of polygon definitions
|
|
- `direction`: "inside" or "outside" (filter regions)
|
|
- `requestMaps` (bool): Automatically request tiles for route regions
|
|
- `createTilesZip` (bool): Create ZIP file of all tiles
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```http
|
|
GET /api/satellite/route/{id}
|
|
```
|
|
|
|
Retrieve route details including all interpolated points.
|
|
|
|
## Configuration
|
|
|
|
Configuration is managed through `appsettings.json`:
|
|
|
|
```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`:
|
|
|
|
```yaml
|
|
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:
|
|
```csv
|
|
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
|
|
|
|
```bash
|
|
cd SatelliteProvider.Api
|
|
dotnet build
|
|
dotnet run
|
|
```
|
|
|
|
### Running Tests
|
|
|
|
**Unit Tests:**
|
|
```bash
|
|
cd SatelliteProvider.Tests
|
|
dotnet test
|
|
```
|
|
|
|
**Integration Tests:**
|
|
```bash
|
|
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:
|
|
1. Create `NNN_DescriptiveName.sql` (increment number)
|
|
2. Set Build Action to `EmbeddedResource`
|
|
3. 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.
|
|
|