fix(cctv): Ontario 511 cameras take the developer key the API now requires

The Ontario 511 camera list answered 400 "Invalid Key" on every catalog load
("[CCTV] Ontario 511 camera download failed: 400"), so the pack loaded
nothing. 511on.ca's developer docs now say the API "Requires a developer
key" (free, from 511on.ca/developers; 10 calls a minute). The loader reads
ONTARIO_511_API_KEY and sends it as `key`; without it the pack is skipped
with one log line instead of a failed request on every load. The key is
never logged. .env.example and DATA_SOURCES no longer call the endpoint
keyless.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
quanticsoul4772andClaude Opus 5.5 committed 2026-10-18 15:40:22 -07:00
1 parent 93ea088f45
commit 48c8a64c97
4 files changed
+95 -7

No files matched your search

+1
View File
@@ -201,6 +201,7 @@ AISSTREAM_API_KEY=
# CCTV_TFL_ENABLED=1 # set 0 to disable Transport for London JamCams
# CCTV_TFL_MAX_SOURCES=250 # cap cameras loaded from TfL
# CCTV_ONTARIO_ENABLED=1 # set 0 to disable Ontario 511 cameras
# ONTARIO_511_API_KEY= # free developer key from 511on.ca/developers; the camera list needs it
# CCTV_ONTARIO_MAX_SOURCES=1000 # cap cameras loaded from Ontario 511
# CCTV_FINTRAFFIC_ENABLED=1 # set 0 to drop the Fintraffic (Finland) pack
# CCTV_FINTRAFFIC_MAX_SOURCES=300 # cap cameras loaded from the Fintraffic catalog
+1 -1
View File
@@ -152,7 +152,7 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
- **adsbdb enrichment.** `/api/adsbdb` resolves an ICAO hex to aircraft type, model name and registration, and the tracked flight's callsign to an airline and an origin/destination pair. Fan-out is deliberately small because adsbdb is a free community API that publishes no quota: at most 4 requests in flight, dispatches dripped to roughly 5/s, and each key asked for at most once per session, with route lookups reserved for the tracked aircraft. The dev-server proxy caches each key for 24 hours in `.gev-cache/adsbdb.json`, negative answers included, so fresh entries survive server restarts. That gitignored cache remains local to the deployment and is not bundled with this repo. adsbdb publishes no general service license; its README says the route data may not be copied, published, or incorporated into other databases without the explicit permission of David J Taylor, Edinburgh. Deployers are responsible for obtaining any permission their use requires. Enrichment is best-effort and fail-silent — a flight that gets no answer keeps what the flight feed already gave it.
- **Launch Library 2.** `/api/launches` makes a server-side rolling-30-day query against the supported v2.3 detailed launch endpoint, caches successful responses for 15 minutes in memory and on disk, and serves the last successful response during a throttle or transient outage. Anonymous access is limited to 15 calls/hour; deployments can provide `LL2_API_TOKEN` for authenticated access. The Space Devs' published terms permit using and sharing the API data in any form, ask users not to forward it without adding value, disclaim complete accuracy, and encourage—but do not require—attribution. This app keeps a courtesy credit. Payload and stage/recovery records are shown only when supplied. Failed launches expose their source status and never receive fallback orbit geometry or a live/estimated marker. LL2 supplies launch context and event timing, not continuous ascent telemetry or live orbital state.
- **TfL JamCams.** The camera list comes from the keyless `api.tfl.gov.uk` endpoint (an optional `TFL_APP_KEY` raises its rate limit); frames come from TfL's public S3 bucket. The "Powered by TfL Open Data" attribution is required by TfL's terms and is registered in the Data attribution popover.
- **Ontario 511 CCTV.** The camera list comes from the keyless `511on.ca/api/v2/get/cameras` endpoint; frames are server-registered still images under `511on.ca/map/Cctv/`. The source is live-fetched at runtime and attributed under the Open Government Licence - Ontario.
- **Ontario 511 CCTV.** The camera list comes from the `511on.ca/api/v2/get/cameras` endpoint, which now requires a free developer key (`ONTARIO_511_API_KEY`, from 511on.ca/developers; throttled to 10 calls a minute). Without the key the pack is skipped. Frames are server-registered still images under `511on.ca/map/Cctv/`. The source is live-fetched at runtime and attributed under the Open Government Licence - Ontario.
- **DelDOT live video cameras.** The keyless `tmc.deldot.gov/json/videocamera.json` catalog supplies active cameras and official `https://video.deldot.gov/live/…/playlist.m3u8` links. The local server transiently buffers bounded MPEG-TS segments in memory and deletes them on session release or idle expiry. No recordings are retained. `CCTV_DELDOT_ENABLED=0` disables the pack. Public availability is not a blanket redistribution license; deployment beyond personal local viewing needs confirmation of applicable DelDOT terms.
- **Fintraffic weathercams.** The camera list is one keyless GeoJSON request to `tie.digitraffic.fi/api/weathercam/v1/stations` (809 stations / 2,275 presets), sent with the `Digitraffic-User: gods-eye-view` header the service asks clients to identify themselves with and with gzip; frames are stills on `weathercam.digitraffic.fi`, refreshed on each station's 600 s collection interval, which is also the client's ambient-still refresh cadence for this pack (the active camera keeps the usual 10-second refresh). One preset (a fixed camera view) is one camera; stations not `GATHERING` and presets not `inCollection` are dropped. Attribution is required by the CC BY 4.0 licence and is registered in the Data attribution popover. `CCTV_FINTRAFFIC_ENABLED=0` is the kill switch.
- **DriveBC highway cameras.** The camera list comes from the keyless `https://www.drivebc.ca/api/webcams/` endpoint served by the DriveBC.ca site ([bcgov/DriveBC.ca](https://github.com/bcgov/DriveBC.ca)); frames come from `https://www.drivebc.ca/images/<id>.jpg`, built from the numeric camera id. DataBC's [DriveBC HighwayCams](https://catalogue.data.gov.bc.ca/dataset/bc-highwaycams) catalogue entry publishes the same cameras under the Open Government Licence – British Columbia, but its CSV still lists the retired `images.drivebc.ca` frame URLs, which now return a placeholder. The licence's attribution statement is required and is registered in the Data attribution popover. By default the 250 cameras nearest Vancouver and Victoria load; `CCTV_DRIVEBC_MAX_SOURCES` changes the cap and `CCTV_DRIVEBC_ENABLED=0` turns the pack off. Some cameras in the feed are supplied by partners (TransLink, the City of Vancouver, the City of Surrey, Parks Canada / Alberta Motor Association and others); the feed's per-camera credit is carried onto the camera record and shown beside the provider in the CCTV panel.
+65
View File
@@ -0,0 +1,65 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { loadOntarioSourcesFromOpenData } from './sources.js';
const ROW = {
Id: '42',
Latitude: 43.6532,
Longitude: -79.3832,
Location: 'Hwy 401 near Yonge St',
Direction: 'Eastbound',
Views: [
{
Status: 'Enabled',
Url: 'https://511on.ca/map/Cctv/4201',
Description: 'Eastbound',
},
],
};
test('without ONTARIO_511_API_KEY the Ontario pack is skipped, logged once', async (t) => {
const warnings = [];
t.mock.method(console, 'warn', (...args) => warnings.push(args.join(' ')));
t.mock.method(globalThis, 'fetch', async () =>
assert.fail('no request without a key'),
);
assert.deepEqual(await loadOntarioSourcesFromOpenData({}, {}), []);
assert.deepEqual(await loadOntarioSourcesFromOpenData({}, {}), []);
assert.equal(warnings.length, 1);
assert.match(warnings[0], /ONTARIO_511_API_KEY/);
});
test('with a key the camera list is requested with it and parsed', async (t) => {
const requested = [];
t.mock.method(globalThis, 'fetch', async (url) => {
requested.push(String(url));
return Response.json([ROW]);
});
const cameras = await loadOntarioSourcesFromOpenData(
{ sourceRoot: '/ignored' },
{ ONTARIO_511_API_KEY: ' test-key ' },
);
const url = new URL(requested[0]);
assert.equal(url.host, '511on.ca');
assert.equal(url.searchParams.get('key'), 'test-key');
assert.equal(url.searchParams.get('format'), 'json');
assert.equal(cameras.length, 1);
assert.equal(cameras[0].id, 'on-42');
assert.equal(cameras[0].url, 'https://511on.ca/map/Cctv/4201');
});
test('a rejected key is reported by status, never by the key itself', async (t) => {
const warnings = [];
t.mock.method(console, 'warn', (...args) => warnings.push(args.join(' ')));
t.mock.method(globalThis, 'fetch', async () =>
Response.json({ Message: 'Invalid Key' }, { status: 400 }),
);
const cameras = await loadOntarioSourcesFromOpenData(
{},
{ ONTARIO_511_API_KEY: 'secret-key-value' },
);
assert.deepEqual(cameras, []);
assert.match(warnings.join('\n'), /400/);
assert.doesNotMatch(warnings.join('\n'), /secret-key-value/);
});
+28 -6
View File
@@ -502,17 +502,39 @@ function pickOntarioCctvView(views) {
);
}
let ontarioKeyMissingLogged = false;
/**
* Fetch Ontario 511 CCTV cameras. Keyless: the catalog is exposed by the
* public 511 API, while frame URLs are stable still-image endpoints under
* 511on.ca/map/Cctv/. Only rows with finite Ontario coords and at least one
* enabled official still view are kept.
* Fetch Ontario 511 CCTV cameras. The camera list comes from the 511 API,
* which now requires a free developer key (511on.ca/developers, "Requires a
* developer key"; unkeyed calls answer 400 "Invalid Key"), read from
* ONTARIO_511_API_KEY. Without one the pack is skipped with a single log line.
* Frame URLs are stable still-image endpoints under 511on.ca/map/Cctv/. Only
* rows with finite Ontario coords and at least one enabled official still
* view are kept.
*
* @param {object} [_options] The catalog's loader options (unused).
* @param {object} [env] Environment to read the key from.
* @returns {Promise<Array<object>>} Normalized camera source objects.
*/
export async function loadOntarioSourcesFromOpenData() {
export async function loadOntarioSourcesFromOpenData(
_options = {},
env = process.env,
) {
const key = String(env.ONTARIO_511_API_KEY ?? '').trim();
if (!key) {
if (!ontarioKeyMissingLogged) {
ontarioKeyMissingLogged = true;
console.warn(
'[CCTV] Ontario 511 cameras skipped: the 511on.ca API needs a free developer key (set ONTARIO_511_API_KEY)',
);
}
return [];
}
try {
const resp = await fetch(ONTARIO_511_CAMERAS_URL, {
const url = new URL(ONTARIO_511_CAMERAS_URL);
url.searchParams.set('key', key);
const resp = await fetch(url, {
headers: { Accept: 'application/json' },
signal: AbortSignal.timeout(CCTV_SOURCE_FETCH_TIMEOUT_MS),
});