Oleksandr Bezdieniezhnykh fc10d5f120
ci/woodpecker/manual/build-arm Pipeline was successful
Update Woodpecker CI configuration for Docker registry authentication
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.
2026-04-21 20:37:38 +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 516 KiB
Languages
C# 98.9%
Dockerfile 1.1%