surgeink-api / surgeink /api /layers.py
kredd25's picture
✨ feat(flood): add SurgeInk backend + flood risk visualization
fb2ab55
from fastapi import APIRouter, HTTPException, Query
from surgeink.models.schemas import (
LayerInfo,
LayerLegend,
LayerLegendStop,
LayersResponse,
)
router = APIRouter()
# Hardcoded layer catalog β€” Phase 1.
# Layers are marked available/unavailable based on what's actually implemented.
# This will become dynamic in Phase 3 (check data availability per bbox).
LAYER_CATALOG = [
LayerInfo(
id="ml_risk",
name="ML Flood Risk Prediction",
type="raster",
source="SurgeInk ML v1.0",
available=False,
reason="Not yet implemented β€” Phase 4",
legend=LayerLegend(
type="gradient",
stops=[
LayerLegendStop(value=1, color="#2166ac", label="Low"),
LayerLegendStop(value=5, color="#f4a582", label="Moderate"),
LayerLegendStop(value=10, color="#b2182b", label="Critical"),
],
),
),
LayerInfo(
id="jrc_water_occurrence",
name="Historical Water Occurrence",
type="raster",
source="JRC Global Surface Water (1984–2021)",
available=False,
reason="Not yet implemented β€” Phase 2",
),
LayerInfo(
id="live_discharge",
name="River Discharge (Live)",
type="geojson",
source="Open-Meteo / GloFAS v4",
endpoint="/api/v1/forecast",
available=True,
),
LayerInfo(
id="fema_zones",
name="FEMA Flood Zones",
type="vector",
source="FEMA NFHL",
available=False,
region="US only",
reason="Not yet implemented β€” Phase 3",
),
LayerInfo(
id="wri_aqueduct",
name="Climate Flood Risk Scenarios",
type="raster",
source="WRI Aqueduct Floods (via GEE)",
available=False,
reason="Not yet implemented β€” Phase 3",
),
LayerInfo(
id="interpretability",
name="Model Interpretability Heatmap",
type="raster",
source="SurgeInk ML interpretability",
available=False,
reason="Not yet implemented β€” Phase 4",
),
]
@router.get("/layers", response_model=LayersResponse)
async def get_layers(
bbox: str = Query(
...,
description="Bounding box: min_lng,min_lat,max_lng,max_lat",
),
zoom: int = Query(10, ge=0, le=22),
):
# Parse bbox
parts = bbox.split(",")
if len(parts) != 4:
raise HTTPException(422, detail="bbox must have exactly 4 comma-separated values")
try:
[float(p.strip()) for p in parts]
except ValueError:
raise HTTPException(422, detail="bbox values must be numeric")
# For Phase 1, return the full catalog regardless of bbox.
# Phase 3 will make availability dynamic based on location.
return LayersResponse(layers=LAYER_CATALOG)