Files
server-26/drb-c2-core/app/internal/cameras/regional.py
T

169 lines
8.1 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)
# Hosts for the 511x imagery are unverified, so pin the scheme at least:
# only https URLs reach the browser.
def https(u):
return u if isinstance(u, str) and u.startswith("https://") else None
out = [
make_camera("ny511", r["id"], r["lat"], r["lon"], name=r["name"], roadway=r["roadway"],
direction=r["direction"], image_url=https(r["image_url"]), video_url=https(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]