"""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]