feat(sentinel1): Sentinel-1 SAR STAC -> self-hosted TiTiler tile template #20

Merged
sirius merged 2 commits from feat/sentinel1-sar-titiler into master 2026-08-29 08:24:32 -04:00
Owner

Summary

Backend half of the Sentinel-1 SAR overlay. GET /api/map/sentinel1?bbox=minlon,minlat,maxlon,maxlat queries Planetary Computer STAC (sentinel-1-grd, last 7 days, most recent), signs the vv/hh COG with a SAS token, and returns a same-origin /titiler/... XYZ tile template for Leaflet.

Endpoint contract (frontend child card consumes this)

GET /api/map/sentinel1?bbox=...

200:

{
  "id": "sentinel-1-sar",
  "kind": "raster",
  "tileUrl": "/titiler/cog/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?url=<signed_cog>&rescale=0,500&colormap_name=cfastie",
  "opacity": 0.8,
  "itemId": "<stac id>",
  "datetime": "<item datetime>",
  "polarization": "vv"|"hh",
  "attribution": "Copernicus Sentinel-1 / Microsoft Planetary Computer"
}
  • 422 missing/invalid bbox
  • 404 {"error":"no_imagery"} when no GRD covers the bbox in 7 days
  • 429 (with Retry-After if upstream sent it) on Planetary Computer rate limit
  • 502 on other upstream failures (no stack traces)

/api/map/layers gains overlays.sentinel1_sar (tileUrl: null, endpoint: "/api/map/sentinel1", title Sentinel-1 SAR (Cloud-Penetrating)).

TiTiler (self-hosted)

  • Added titiler service to the existing docker-compose.yml (ghcr.io/developmentseed/titiler:latest, platform: linux/arm64, WORKERS_PER_CORE=1, 1G memory cap, host 127.0.0.1:8001 -> container 8000). The app keeps host 8000.
  • New deploy/osint-titiler.nginx.conf: location /titiler/ -> http://127.0.0.1:8001/ (prefix stripped). Browser hits the osint.rpi.local vhost, never a raw loopback port or titiler.xyz.
  • TITILER_PUBLIC_BASE (default /titiler) and TITILER_INTERNAL_URL (default http://titiler:8000) are env-driven; no hardcoded hostnames.

Pi / quota

  • Cache keyed on quantized 0.25deg bbox + UTC day, 20 min TTL; repeat toggles/pans in a cell don't re-hit STAC.
  • OSINT_USER_AGENT on STAC + SAS requests; 8s httpx timeout.
  • No new daemon, no Redis, no table, no proxy of tiles through the app.

Tests

Mocked httpx (_post_json/_get_json): vv -> signed tile URL contains /titiler/cog/tiles/WebMercatorQuad/{z}/{x}/{y}@1x, rescale=0,500, colormap_name=cfastie; empty features -> None (404); missing vv falls back to hh; tile URL is same-origin relative (no scheme/host, no titiler.xyz). No real PC/TiTiler in CI. Full suite: 125 passed, 16 skipped.

## Summary Backend half of the Sentinel-1 SAR overlay. `GET /api/map/sentinel1?bbox=minlon,minlat,maxlon,maxlat` queries Planetary Computer STAC (`sentinel-1-grd`, last 7 days, most recent), signs the vv/hh COG with a SAS token, and returns a same-origin `/titiler/...` XYZ tile template for Leaflet. ## Endpoint contract (frontend child card consumes this) `GET /api/map/sentinel1?bbox=...` 200: ```json { "id": "sentinel-1-sar", "kind": "raster", "tileUrl": "/titiler/cog/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?url=<signed_cog>&rescale=0,500&colormap_name=cfastie", "opacity": 0.8, "itemId": "<stac id>", "datetime": "<item datetime>", "polarization": "vv"|"hh", "attribution": "Copernicus Sentinel-1 / Microsoft Planetary Computer" } ``` - 422 missing/invalid bbox - 404 `{"error":"no_imagery"}` when no GRD covers the bbox in 7 days - 429 (with `Retry-After` if upstream sent it) on Planetary Computer rate limit - 502 on other upstream failures (no stack traces) `/api/map/layers` gains `overlays.sentinel1_sar` (`tileUrl: null`, `endpoint: "/api/map/sentinel1"`, title **Sentinel-1 SAR (Cloud-Penetrating)**). ## TiTiler (self-hosted) - Added `titiler` service to the existing `docker-compose.yml` (`ghcr.io/developmentseed/titiler:latest`, `platform: linux/arm64`, `WORKERS_PER_CORE=1`, 1G memory cap, host `127.0.0.1:8001` -> container `8000`). The app keeps host `8000`. - New `deploy/osint-titiler.nginx.conf`: `location /titiler/` -> `http://127.0.0.1:8001/` (prefix stripped). Browser hits the osint.rpi.local vhost, never a raw loopback port or titiler.xyz. - `TITILER_PUBLIC_BASE` (default `/titiler`) and `TITILER_INTERNAL_URL` (default `http://titiler:8000`) are env-driven; no hardcoded hostnames. ## Pi / quota - Cache keyed on quantized 0.25deg bbox + UTC day, 20 min TTL; repeat toggles/pans in a cell don't re-hit STAC. - `OSINT_USER_AGENT` on STAC + SAS requests; 8s httpx timeout. - No new daemon, no Redis, no table, no proxy of tiles through the app. ## Tests Mocked httpx (`_post_json`/`_get_json`): vv -> signed tile URL contains `/titiler/cog/tiles/WebMercatorQuad/{z}/{x}/{y}@1x`, `rescale=0,500`, `colormap_name=cfastie`; empty features -> None (404); missing vv falls back to hh; tile URL is same-origin relative (no scheme/host, no titiler.xyz). No real PC/TiTiler in CI. Full suite: 125 passed, 16 skipped.
sirius added 1 commit 2026-08-29 01:00:32 -04:00
Add GET /api/map/sentinel1?bbox=... which queries Planetary Computer STAC
(sentinel-1-grd, last 7d, most recent), signs the vv/hh COG with a SAS token,
and returns a same-origin /titiler/... XYZ tile template. Cache keyed on
quantized bbox + UTC day (20 min TTL). 429 -> 429 (Retry-After), no imagery
-> 404, other upstream errors -> 502.

Self-host TiTiler on the Pi (ghcr.io/developmentseed/titiler, arm64, 1G cap,
host loopback 8001 -> container 8000) instead of titiler.xyz. nginx
/titiler/ proxy snippet routes browser tiles to it; TITILER_PUBLIC_BASE and
TITILER_INTERNAL_URL are env-driven (no hardcoded hostnames).
sirius added 1 commit 2026-08-29 08:23:51 -04:00
PR #20 conflicted on docker-compose.yml after #19 landed. App service now
passes both VESSELAPI_* and TITILER_* ; titiler stays on 127.0.0.1:8001.
sirius merged commit b0657cf3e4 into master 2026-08-29 08:24:32 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: sirius/osint-dashboard#20
No description provided.