kredd25 Claude Opus 4.6 (1M context) commited on
Commit
6638035
·
1 Parent(s): fb2ab55

✨ feat(flood): add theme-colored vector flood zones + FEMA proxy

Browse files

Replace raster tile overlay with GeoJSON vector polygons that adapt
colors to the active theme. Add FEMA proxy endpoint to backend
(bypasses CORS). Add loading indicator and zone count in flood panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. surgeink/api/fema.py +72 -0
  2. surgeink/api/router.py +2 -1
surgeink/api/fema.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import httpx
2
+ from fastapi import APIRouter, HTTPException, Query
3
+
4
+ router = APIRouter()
5
+
6
+ FEMA_QUERY_URL = (
7
+ "https://hazards.fema.gov/arcgis/rest/services/public/NFHL/MapServer/28/query"
8
+ )
9
+ MAX_RECORDS = 100
10
+
11
+
12
+ @router.get("/fema/zones")
13
+ async def get_fema_zones(
14
+ bbox: str = Query(..., description="west,south,east,north"),
15
+ ):
16
+ parts = bbox.split(",")
17
+ if len(parts) != 4:
18
+ raise HTTPException(422, detail="bbox must have 4 comma-separated values")
19
+
20
+ all_features = []
21
+ offset = 0
22
+ has_more = True
23
+
24
+ async with httpx.AsyncClient(timeout=30.0) as client:
25
+ while has_more:
26
+ params = {
27
+ "geometry": bbox,
28
+ "geometryType": "esriGeometryEnvelope",
29
+ "inSR": "4326",
30
+ "outSR": "4326",
31
+ "spatialRel": "esriSpatialRelIntersects",
32
+ "outFields": "FLD_ZONE,ZONE_SUBTY",
33
+ "returnGeometry": "true",
34
+ "resultOffset": str(offset),
35
+ "resultRecordCount": str(MAX_RECORDS),
36
+ "f": "json",
37
+ }
38
+
39
+ resp = await client.get(FEMA_QUERY_URL, params=params)
40
+ if resp.status_code != 200:
41
+ raise HTTPException(502, detail=f"FEMA returned {resp.status_code}")
42
+
43
+ data = resp.json()
44
+
45
+ if "error" in data:
46
+ raise HTTPException(502, detail=f"FEMA error: {data['error']}")
47
+
48
+ features = data.get("features", [])
49
+
50
+ for f in features:
51
+ geom = f.get("geometry", {})
52
+ rings = geom.get("rings")
53
+ if not rings:
54
+ continue
55
+ all_features.append({
56
+ "type": "Feature",
57
+ "properties": f.get("attributes", {}),
58
+ "geometry": {
59
+ "type": "Polygon",
60
+ "coordinates": rings,
61
+ },
62
+ })
63
+
64
+ if len(features) < MAX_RECORDS:
65
+ has_more = False
66
+ else:
67
+ offset += MAX_RECORDS
68
+
69
+ return {
70
+ "type": "FeatureCollection",
71
+ "features": all_features,
72
+ }
surgeink/api/router.py CHANGED
@@ -1,6 +1,6 @@
1
  from fastapi import APIRouter
2
 
3
- from surgeink.api import geocode, forecast, layers, risk, tiles, predict, interpret
4
 
5
  router = APIRouter(prefix="/api/v1")
6
 
@@ -11,3 +11,4 @@ router.include_router(risk.router, tags=["risk"])
11
  router.include_router(tiles.router, tags=["tiles"])
12
  router.include_router(predict.router, tags=["predict"])
13
  router.include_router(interpret.router, tags=["interpret"])
 
 
1
  from fastapi import APIRouter
2
 
3
+ from surgeink.api import geocode, forecast, layers, risk, tiles, predict, interpret, fema
4
 
5
  router = APIRouter(prefix="/api/v1")
6
 
 
11
  router.include_router(tiles.router, tags=["tiles"])
12
  router.include_router(predict.router, tags=["predict"])
13
  router.include_router(interpret.router, tags=["interpret"])
14
+ router.include_router(fema.router, tags=["fema"])