Oleksandr Bezdieniezhnykh b28581d945
ci/woodpecker/push/build-arm Pipeline was successful
Merge branch 'stage' into main
2026-04-22 01:40:42 +03:00
2025-10-26 09:15:06 +02:00
2025-11-19 17:40:12 +01:00
2025-11-19 17:26:23 +01:00
2025-11-01 16:54:46 +01:00
2025-11-20 12:41:07 +01:00
2025-11-18 19:14:12 +01:00
2025-10-25 21:29:33 +03:00
2025-11-19 12:17:27 +01:00
2025-11-20 12:41:07 +01:00

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 latitude
  • Longitude (double): Center longitude
  • ZoomLevel (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 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:

{
  "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 processing
  • processing: Currently downloading tiles
  • completed: All tiles ready, files created
  • failed: 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 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:

{
  "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:

  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.

S
Description
No description provided
Readme 513 KiB
Languages
C# 98.9%
Dockerfile 1.1%