Backend (c2-core):
- app/internal/cameras/: provider interface, licence model, per-provider
catalog cache; one adapter per source in design-canvas/camera-sources.md.
Implemented: 511NY (legacy shape + tolerant XML inventory parser, 511x
endpoints UNVERIFIED), Caltrans, DelDOT, Ontario 511, DriveBC, Calgary,
TfL, NSW, Fintraffic. Stubs: NYC DOT, Westchester DPW, TRANSCOM, CTroads,
511NJ, Austin, TxDOT, MD CHART, WSDOT, Tallinn, Tarktee, Warendorf,
TrafficLand, Windy, EarthCam.
- Every provider defaults OFF (config/camera_providers). Gated licences
(agreement required / unverified / non-commercial / paid) need an explicit
acknowledgement stored with who/when and written to audit_log; the audit
write is a precondition. Stubs and prohibited sources cannot be enabled.
Ack is bound to the licence status it was given for.
- GET /cameras (signed-in), GET/PUT /admin/camera-providers (admin only).
- /traffic/511 follows the ny511 provider switch and returns the DAA credit
+ NYSDOT as-is disclaimer; 511NY event descriptions no longer truncated
("may not alter the substance of a report").
Frontend:
- LiveView becomes the ops wall: Region > Incident > Camera drill-down,
worst-first rail (MapView), KPIs, transcript ticker, scrubber incident
bands, auto-tour, deep links (#inc=&cam=&tour), Esc steps up.
- Incident panel: nearest cameras (path-weighted), fused situational context
(ADS-B, AIS, 511NY events, NEXRAD tile sample), units, call spine.
- MapView keeps every layer; adds Camera Viewsheds (approx.) overlay and a
nearest-camera layer for the selected incident.
- CameraFeed hygiene: hidden-tab/off-screen gating, 2 concurrent HLS cap,
last good frame + stale badge, frozen-frame signature, HLS stall watchdog,
provider attribution rendered on the imagery.
- Camera/event popups show source credit, update time and the disclaimer.
- Admin > Camera providers tab with licence status, health, gated confirm.
- Camera detection is not built (blocked on #199/#200); seam only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
165 lines
7.9 KiB
Python
165 lines
7.9 KiB
Python
"""Tri-state (Westchester-first) camera sources -- design-canvas/camera-sources.md §A.
|
|
|
|
Only 511NY has a working adapter. Every other regional source is listed so
|
|
Admin shows its licence status, and is a stub because DRB has no right to use
|
|
it yet (NYC DOT, Westchester DPW), it carries no cameras (CTroads), or no
|
|
developer program exists (511NJ). Do not "fix" a stub by scraping: CTroads,
|
|
511NJ's app and the new 511ny.org backend all ban robots/reverse engineering.
|
|
"""
|
|
from typing import List
|
|
|
|
from app.internal import ny511
|
|
from app.internal.cameras.base import (
|
|
Camera, CameraProvider, Licence, LicenceInfo, ProviderMeta, StubProvider, make_camera, require_parsed,
|
|
)
|
|
|
|
NY_BBOX = (40.4, -80.0, 45.1, -71.7)
|
|
TRISTATE_BBOX = (40.3, -75.6, 42.1, -71.7)
|
|
|
|
NYSDOT_DISCLAIMER = (
|
|
"NYSDOT compiles the data from the best available sources and makes a best effort to disseminate it. "
|
|
"Data availability and accuracy are subject to change, and may change as often as daily. Posted "
|
|
"restrictions in the field may differ from, and supersede, the information in this feed. NYSDOT and its "
|
|
"partner agencies make no warranty regarding the data, express or implied, and expressly disclaim all "
|
|
"warranties of merchantability and fitness for any particular purpose. NYSDOT makes no warranty that the "
|
|
"data will be provided without interruption or that it will be free of errors. Access is provided on an "
|
|
"\"as is\" and \"with all faults\" basis."
|
|
)
|
|
|
|
|
|
class Ny511Provider(CameraProvider):
|
|
meta = ProviderMeta(
|
|
id="ny511",
|
|
name="511NY (NYSDOT)",
|
|
region="New York State (aggregates NYSDOT, Thruway, NYC DOT, MTA B&T, PANYNJ, TRANSCOM partners)",
|
|
coverage=NY_BBOX,
|
|
licence=LicenceInfo(
|
|
status=Licence.AGREEMENT_REQUIRED,
|
|
label="Requires NYSDOT Developer Access Agreement -- access to the new 511x feed NOT granted yet",
|
|
terms=("Commercial redistribution is allowed under the DAA (\"You may redistribute, enhance, repackage, "
|
|
"and add value to the data\"; \"You may not alter the substance of a report\"; must display the "
|
|
"time the data was published or last updated). Access is granted per request via the 511NY "
|
|
"Data Feed Request Form; DRB's 2026-09-27 request went to the retired system. Overlays, "
|
|
"detection and frame archiving are not addressed (server-26#199)."),
|
|
terms_url="https://511ny.org/help/24",
|
|
# DAA permitted use: tag line + logo + link. The approved logo is not
|
|
# in the repo yet (owner action), so only the tag line + link render.
|
|
attribution="powered by 511NY",
|
|
attribution_url="https://511ny.org",
|
|
disclaimer=NYSDOT_DISCLAIMER,
|
|
issue="server-26#198",
|
|
),
|
|
formats="stills (60 s) + HLS on NYSDOT hosts (legacy shape)",
|
|
implemented=True,
|
|
notes=("Legacy request/record shape behind settings.ny511_api_base + ny511_api_key, plus a tolerant "
|
|
"parser for the 511x XML camera inventory. UNVERIFIED until the DAA grant documents the new "
|
|
"endpoints: base URL, query params, XML element names, still/HLS URL shape and CORS. The "
|
|
"legacy endpoint has returned HTML since the 511x cutover, so expect a feed error until then."),
|
|
homepage="https://511ny.org",
|
|
key_setting="ny511_api_key",
|
|
ttl_s=ny511.CAMERAS_TTL_S,
|
|
)
|
|
|
|
async def fetch_catalog(self) -> List[Camera]:
|
|
rows = await ny511._fetch("getcameras", ny511.normalize_camera)
|
|
out = [
|
|
make_camera("ny511", r["id"], r["lat"], r["lon"], name=r["name"], roadway=r["roadway"],
|
|
direction=r["direction"], image_url=r["image_url"], video_url=r["video_url"])
|
|
for r in rows
|
|
]
|
|
return require_parsed(len(rows), out, "511NY")
|
|
|
|
|
|
NYC_DOT = StubProvider(ProviderMeta(
|
|
id="nyc_dot",
|
|
name="NYC DOT TMC (webcams.nyctmc.org)",
|
|
region="New York City (~975 cams, ~80 in the Bronx)",
|
|
coverage=(40.45, -74.30, 40.95, -73.65),
|
|
licence=LicenceInfo(
|
|
status=Licence.AGREEMENT_REQUIRED,
|
|
label="Requires a signed NYC DOT data-sharing agreement -- NOT signed",
|
|
terms=("NYC DOT Data Feeds page: \"Developers must sign a data-sharing agreement\" (tmcdot@dot.nyc.gov). "
|
|
"The API answers keyless, but keyless use is outside those terms. Agreement terms unpublished."),
|
|
terms_url="https://www.nyc.gov/html/dot/html/about/datafeeds.shtml",
|
|
issue="server-26#201",
|
|
),
|
|
formats="JPEG stills (~15 s), no CORS",
|
|
implemented=False,
|
|
notes="Stub on purpose: do not integrate until the agreement is signed (server-26#201). Many of these cams "
|
|
"are likely already re-published by 511NY.",
|
|
homepage="https://webcams.nyctmc.org",
|
|
))
|
|
|
|
WESTCHESTER_DPW = StubProvider(ProviderMeta(
|
|
id="westchester_dpw",
|
|
name="Westchester County DPW traffic cameras",
|
|
region="Westchester County (~21 locations, mostly Bronx River Pkwy)",
|
|
coverage=(40.88, -73.99, 41.37, -73.48),
|
|
licence=LicenceInfo(
|
|
status=Licence.AGREEMENT_REQUIRED,
|
|
label="No reuse licence -- written permission from County DPW required, NOT obtained",
|
|
terms="Only a disclaimer: data \"provided 'as is' without warranty\". No API, no reuse licence.",
|
|
terms_url="https://experience.arcgis.com/experience/a59314e926e94301b9083edd6bb52a11",
|
|
issue="server-26#202",
|
|
),
|
|
formats="stills (5 s), viewer only",
|
|
implemented=False,
|
|
notes="Stub: there is no documented API or feed, and no permission. Ask DPW in writing (server-26#202).",
|
|
homepage="https://www.westchestergov.com/trafficcamera",
|
|
))
|
|
|
|
TRANSCOM = StubProvider(ProviderMeta(
|
|
id="transcom_xcm",
|
|
name="TRANSCOM XCM data exchange",
|
|
region="NY/NJ/CT tri-state",
|
|
coverage=TRISTATE_BBOX,
|
|
licence=LicenceInfo(
|
|
status=Licence.UNVERIFIED,
|
|
label="Events/travel times commercial OK; cameras UNVERIFIED",
|
|
terms="Free registration, open to \"commercial vendors ... for use in their applications\". Whether the "
|
|
"feed carries camera URLs is unverified.",
|
|
terms_url="https://data.xcmdata.org/DEWeb/Pages/aboutus",
|
|
),
|
|
formats="XML events + link travel times (cameras unverified)",
|
|
implemented=False,
|
|
notes="Stub: register first, then confirm whether any camera inventory exists. Its events feed would be a "
|
|
"separate (event) provider, not a camera one.",
|
|
homepage="https://data.xcmdata.org",
|
|
))
|
|
|
|
CTROADS = StubProvider(ProviderMeta(
|
|
id="ctroads",
|
|
name="CTDOT / CTroads",
|
|
region="Connecticut",
|
|
coverage=(40.95, -73.75, 42.06, -71.78),
|
|
licence=LicenceInfo(
|
|
status=Licence.PROHIBITED,
|
|
label="No sanctioned camera path -- site terms forbid camera reuse",
|
|
terms="The keyed developer API covers Message Signs, Events and Advisories only -- no cameras. The site "
|
|
"T&C forbid use \"for any personal, public or commercial purpose\" and robots.",
|
|
terms_url="https://ctroads.org/termsandconditions",
|
|
),
|
|
formats="events/signs only (no cameras)",
|
|
implemented=False,
|
|
notes="Cannot be enabled. CT cameras may surface via 511NY's \"Connecticut Statewide\" region once the DAA "
|
|
"grant arrives.",
|
|
homepage="https://ctroads.org/developers/doc",
|
|
))
|
|
|
|
NJ511 = StubProvider(ProviderMeta(
|
|
id="nj511",
|
|
name="511NJ / NJDOT / NJTA / PANYNJ",
|
|
region="New Jersey",
|
|
coverage=(38.9, -75.6, 41.4, -73.9),
|
|
licence=LicenceInfo(
|
|
status=Licence.UNVERIFIED,
|
|
label="No published developer program -- reuse UNVERIFIED",
|
|
terms="511NJ is a JS app with no published developer program; NJDOT only cites RSS/Twitter.",
|
|
),
|
|
formats="unknown",
|
|
implemented=False,
|
|
notes="Stub: ask NJDOT 511 if northern NJ matters. Some cams appear in 511NY.",
|
|
))
|
|
|
|
PROVIDERS = [Ny511Provider(), NYC_DOT, WESTCHESTER_DPW, TRANSCOM, CTROADS, NJ511]
|