# 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?lat={lat}&lon={lon}&zoom={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 5433 are available (Postgres host-side; the container itself listens on 5432 inside the docker network)
- 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.

