GET /traffic/511 serves a bbox slice of the statewide 511NY camera and event feeds from an in-memory cache (cameras 1h, events 2m TTL, fetched lazily) -- public data, so no Firestore writes. A failed refresh keeps the last good data and reports the error; a schema change (nothing parses) is an error, not an empty layer. Frontend: opt-in "DOT Cameras" and "Traffic Events" overlays that fetch only while shown, with an on-map notice when the feed is down or stale. NY511_API_KEY is optional in config (the API answers without one today; the terms require a registered key). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39 lines
1.4 KiB
Python
39 lines
1.4 KiB
Python
import asyncio
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
|
|
from app.internal import ny511
|
|
|
|
router = APIRouter(prefix="/traffic", tags=["traffic"])
|
|
|
|
# Per-layer cap per response. A statewide view is ~3k cameras; past this the
|
|
# map is unreadable anyway, and the client is told the list was cut.
|
|
MAX_ITEMS = 1500
|
|
|
|
|
|
def _feed_status(feed: "ny511._Feed") -> dict:
|
|
return {"fetched_at": feed.fetched_at, "error": feed.error}
|
|
|
|
|
|
@router.get("/511")
|
|
async def get_511(
|
|
south: float = Query(..., ge=-90, le=90),
|
|
west: float = Query(..., ge=-180, le=180),
|
|
north: float = Query(..., ge=-90, le=90),
|
|
east: float = Query(..., ge=-180, le=180),
|
|
layers: Optional[str] = Query("cameras,events", description="comma list: cameras, events"),
|
|
):
|
|
if south > north or west > east:
|
|
raise HTTPException(400, "bbox must satisfy south<=north and west<=east")
|
|
wanted = {s.strip() for s in (layers or "").split(",") if s.strip()}
|
|
feeds = {name: getattr(ny511, name) for name in ("cameras", "events") if name in wanted}
|
|
await asyncio.gather(*(f.get() for f in feeds.values()))
|
|
|
|
out: dict = {}
|
|
for name, feed in feeds.items():
|
|
hits = ny511.in_bbox(feed.items, south, west, north, east)
|
|
out[name] = hits[:MAX_ITEMS]
|
|
out[f"{name}_status"] = {**_feed_status(feed), "total_in_bbox": len(hits), "truncated": len(hits) > MAX_ITEMS}
|
|
return out
|