mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(cctv): seven camera packs, semantic labels, terrain-clear monitor planes (#433)
Seven contributed CCTV camera packs (Ontario 511, Fintraffic Finland, DriveBC, TxDOT Texas, Estonia Tallinn + Tarktee, Warendorf, Live Traffic NSW) on a fair per-pack catalog cap; semantic camera labels; monitor planes lifted clear of the terrain from a precomputed, bundled ground-height dataset. Default catalog 800 → 3,446 cameras. Full context in PR #433. Co-authored-by: sendmebits <[email protected]> Co-authored-by: avocadoAgent <[email protected]> Co-authored-by: Alex Harvey <[email protected]> Co-authored-by: Jason Meredith <[email protected]> Co-authored-by: Daniel Smith <[email protected]> Co-authored-by: Vladimir Sobolev <[email protected]> Co-authored-by: Matthias Schneider <[email protected]> Co-authored-by: Cyber Knight <[email protected]> Co-authored-by: NovaSoft Australia <[email protected]>
This commit is contained in:
51 files changed
+78682
-939
No files matched your search
@@ -0,0 +1,256 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { loadFintrafficSourcesFromOpenData } from '../../server/providers/cctv/sources.js';
|
||||
import {
|
||||
fintrafficCameraName,
|
||||
isLikelyFinlandCoordinate,
|
||||
} from '../../server/providers/cctv/normalize.js';
|
||||
import {
|
||||
FINTRAFFIC_GROUND_ELEVATION_M,
|
||||
FINTRAFFIC_IMAGE_ORIGIN,
|
||||
FINTRAFFIC_STATIONS_URL,
|
||||
} from '../../server/providers/cctv/constants.js';
|
||||
|
||||
/** One station feature in the shape tie.digitraffic.fi actually returns. */
|
||||
function station(
|
||||
id,
|
||||
{
|
||||
name = `vt4_${id}`,
|
||||
status = 'GATHERING',
|
||||
lon = 24.9384,
|
||||
lat = 60.1699,
|
||||
altitude = 0,
|
||||
presets = [],
|
||||
} = {},
|
||||
) {
|
||||
return {
|
||||
type: 'Feature',
|
||||
id,
|
||||
geometry: { type: 'Point', coordinates: [lon, lat, altitude] },
|
||||
properties: {
|
||||
id,
|
||||
name,
|
||||
collectionStatus: status,
|
||||
dataUpdatedTime: '2026-09-13T03:25:36Z',
|
||||
presets,
|
||||
state: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Run the loader against a canned station list, restoring global fetch after. */
|
||||
async function loadWith(features, { env = {}, capture = {} } = {}) {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const originalEnv = {};
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
originalEnv[key] = process.env[key];
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
globalThis.fetch = async (url, options) => {
|
||||
capture.url = String(url);
|
||||
capture.options = options;
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
type: 'FeatureCollection',
|
||||
dataUpdatedTime: '2026-09-13T05:31:58Z',
|
||||
features,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
},
|
||||
);
|
||||
};
|
||||
try {
|
||||
return await loadFintrafficSourcesFromOpenData();
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
for (const [key, value] of Object.entries(originalEnv)) {
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('Fintraffic loader turns every in-collection preset into one camera', async () => {
|
||||
const cameras = await loadWith([
|
||||
station('C01503', {
|
||||
name: 'kt51_Inkoo',
|
||||
lon: 23.99616,
|
||||
lat: 60.05374,
|
||||
presets: [
|
||||
{ id: 'C0150301', inCollection: true },
|
||||
{ id: 'C0150302', inCollection: true },
|
||||
],
|
||||
}),
|
||||
]);
|
||||
|
||||
assert.equal(cameras.length, 2);
|
||||
const [first, second] = cameras;
|
||||
assert.equal(first.id, 'fi-c0150301');
|
||||
assert.equal(first.name, 'kt51 Inkoo (view 01)');
|
||||
assert.equal(second.name, 'kt51 Inkoo (view 02)');
|
||||
assert.equal(first.provider, 'Fintraffic');
|
||||
assert.equal(first.city, 'Finland');
|
||||
assert.equal(first.cityId, 'finland');
|
||||
assert.equal(first.sourceKind, 'fintraffic-open-data');
|
||||
assert.equal(first.license, 'Fintraffic / digitraffic.fi (CC BY 4.0)');
|
||||
assert.equal(first.feedType, 'image');
|
||||
// Presets of one station share the station position...
|
||||
assert.equal(first.lat, 60.05374);
|
||||
assert.equal(second.lat, first.lat);
|
||||
assert.equal(second.lon, first.lon);
|
||||
// ...so the id-hash fallback heading is what keeps their gizmos apart.
|
||||
assert.notEqual(first.headingDeg, second.headingDeg);
|
||||
assert.equal(first.headingConfidence, 'low');
|
||||
});
|
||||
|
||||
test('Fintraffic frame URLs are built on the official image origin', async () => {
|
||||
const cameras = await loadWith([
|
||||
station('C01503', { presets: [{ id: 'C0150301', inCollection: true }] }),
|
||||
]);
|
||||
|
||||
assert.equal(cameras[0].url, `${FINTRAFFIC_IMAGE_ORIGIN}C0150301.jpg`);
|
||||
assert.equal(cameras[0].snapshotUrl, cameras[0].url);
|
||||
assert.ok(cameras[0].url.startsWith('https://weathercam.digitraffic.fi/'));
|
||||
});
|
||||
|
||||
test('Fintraffic loader drops dead stations, uncollected presets and bad ids', async () => {
|
||||
const cameras = await loadWith([
|
||||
// Station off collection: every preset goes, live or not.
|
||||
station('C01601', {
|
||||
status: 'REMOVED_TEMPORARILY',
|
||||
presets: [{ id: 'C0160101', inCollection: true }],
|
||||
}),
|
||||
station('C01503', {
|
||||
presets: [
|
||||
{ id: 'C0150301', inCollection: true },
|
||||
{ id: 'C0150302', inCollection: false },
|
||||
{ id: 'C0150303' },
|
||||
// Ids that would escape the synthesized frame path, or belong to
|
||||
// another station, never become a camera.
|
||||
{ id: '../../etc/passwd', inCollection: true },
|
||||
{ id: 'C9999901', inCollection: true },
|
||||
],
|
||||
}),
|
||||
// Coordinates nowhere near Finland (swapped lat/lon).
|
||||
station('C01504', {
|
||||
lon: 60.1699,
|
||||
lat: 24.9384,
|
||||
presets: [{ id: 'C0150401', inCollection: true }],
|
||||
}),
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
cameras.map((camera) => camera.id),
|
||||
['fi-c0150301'],
|
||||
);
|
||||
});
|
||||
|
||||
test('Fintraffic ground elevation uses a reported altitude and falls back otherwise', async () => {
|
||||
const cameras = await loadWith([
|
||||
station('C08508', {
|
||||
altitude: 136,
|
||||
presets: [{ id: 'C0850801', inCollection: true }],
|
||||
}),
|
||||
station('C01503', {
|
||||
altitude: 0,
|
||||
presets: [{ id: 'C0150301', inCollection: true }],
|
||||
}),
|
||||
station('C03506', {
|
||||
altitude: 99_000,
|
||||
presets: [{ id: 'C0350601', inCollection: true }],
|
||||
}),
|
||||
]);
|
||||
|
||||
const byId = new Map(cameras.map((camera) => [camera.id, camera]));
|
||||
assert.equal(byId.get('fi-c0850801').groundElevationM, 136);
|
||||
// 0 is "not reported", not sea level.
|
||||
assert.equal(
|
||||
byId.get('fi-c0150301').groundElevationM,
|
||||
FINTRAFFIC_GROUND_ELEVATION_M,
|
||||
);
|
||||
// A garbage upstream value cannot fling a camera kilometres up.
|
||||
assert.equal(byId.get('fi-c0350601').groundElevationM, 1400);
|
||||
});
|
||||
|
||||
test('Fintraffic loader identifies itself to Digitraffic and refuses redirects', async () => {
|
||||
const capture = {};
|
||||
await loadWith(
|
||||
[station('C01503', { presets: [{ id: 'C0150301', inCollection: true }] })],
|
||||
{ capture },
|
||||
);
|
||||
|
||||
assert.equal(capture.url, FINTRAFFIC_STATIONS_URL);
|
||||
assert.equal(capture.options.headers['Digitraffic-User'], 'gods-eye-view');
|
||||
assert.equal(capture.options.headers['Accept-Encoding'], 'gzip');
|
||||
assert.equal(capture.options.redirect, 'manual');
|
||||
assert.ok(capture.options.signal instanceof AbortSignal);
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () =>
|
||||
new Response(null, {
|
||||
status: 302,
|
||||
headers: { Location: 'https://example.com/stations' },
|
||||
});
|
||||
try {
|
||||
assert.deepEqual(await loadFintrafficSourcesFromOpenData(), []);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('Fintraffic pack caps itself and fails to an empty list, never a throw', async () => {
|
||||
const presets = Array.from({ length: 40 }, (_, index) => ({
|
||||
id: `C01503${String(index).padStart(2, '0')}`,
|
||||
inCollection: true,
|
||||
}));
|
||||
const capped = await loadWith([station('C01503', { presets })], {
|
||||
env: { CCTV_FINTRAFFIC_MAX_SOURCES: '8' },
|
||||
});
|
||||
assert.equal(capped.length, 8);
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => {
|
||||
throw new Error('network down');
|
||||
};
|
||||
try {
|
||||
assert.deepEqual(await loadFintrafficSourcesFromOpenData(), []);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
globalThis.fetch = async () => new Response('nope', { status: 503 });
|
||||
try {
|
||||
assert.deepEqual(await loadFintrafficSourcesFromOpenData(), []);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('fintrafficCameraName unpacks the machine station name and the view number', () => {
|
||||
assert.equal(
|
||||
fintrafficCameraName('vt3_Hyvinkää_Noppo', 'C01234', 'C0123402'),
|
||||
'vt3 Hyvinkää Noppo (view 02)',
|
||||
);
|
||||
// Nameless station still gets a usable label.
|
||||
assert.equal(
|
||||
fintrafficCameraName('', 'C01234', 'C0123409'),
|
||||
'Fintraffic C01234 (view 09)',
|
||||
);
|
||||
// A preset id that is just the station id has no view suffix to add.
|
||||
assert.equal(
|
||||
fintrafficCameraName('vt4_Mäntsälä', 'C01234', 'C01234'),
|
||||
'vt4 Mäntsälä',
|
||||
);
|
||||
});
|
||||
|
||||
test('isLikelyFinlandCoordinate spans the catalog extent and rejects the rest', () => {
|
||||
assert.equal(isLikelyFinlandCoordinate(60.1699, 24.9384), true); // Helsinki
|
||||
assert.equal(isLikelyFinlandCoordinate(70.088512, 27.0), true); // Utsjoki, the northern extreme
|
||||
assert.equal(isLikelyFinlandCoordinate(59.856454, 19.618722), true); // the southwestern extreme
|
||||
assert.equal(isLikelyFinlandCoordinate(51.5074, -0.1278), false); // London
|
||||
assert.equal(isLikelyFinlandCoordinate(0, 0), false); // null island
|
||||
assert.equal(isLikelyFinlandCoordinate(NaN, 24.9384), false);
|
||||
});
|
||||
Reference in new issue
Block a user