fix(cctv): leave Iceland to its own pack, credit World and Canada packs

Main now ships an Iceland pack that loads the full live Vegagerðin list
(about 500 views) with its CC BY 4.0 credit. The World catalog's 166
Vegagerðin rows point at the same frames under different ids, so every one
of those cameras would be drawn twice. Drop them from the World export and
its loader policy; World is now 2,230 rows and the default catalog limit
7,350 (4,000 shared slots plus 3,350 reserved).

Register Data attribution entries for Ottawa, Québec, TripCheck and MLIT,
as every other CCTV pack has. MLIT's terms ask for the source in the form
"出典:国土交通省 川の防災情報" with the page link, and ODOT asks for
"Camera courtesy of ODOT"; TripCheck partner cameras keep their owner's
name in the label.

The Houston guard test from main checked the old 4,000 default ceiling;
check the 4,000 shared slots instead, like the other pack tests.
This commit is contained in:
Said Turksever committed 2026-11-07 17:53:55 +00:00
1 parent cc3d390911
commit 6af14f7f13
15 files changed
+115 -2725

No files matched your search

+2 -2
View File
@@ -106,7 +106,7 @@ test('duplicate ids resolve last-pack-wins and count against the winning pack',
const reservedFixture = () => [
...Array.from({ length: 20 }, (_, i) => pack(`shared${i}`, 250)),
{ ...pack('quebec', 1120), reservedCount: 1120 },
{ ...pack('world', 2396), reservedCount: 2396 },
{ ...pack('world', 2230), reservedCount: 2230 },
];
const keptByName = (packs) =>
Object.fromEntries(packs.map(({ name, kept }) => [name, kept]));
@@ -128,7 +128,7 @@ test('at the default cap reserved packs are whole and shared packs keep the floo
const kept = keptByName(packs);
assert.equal(sources.length, DEFAULT_CCTV_MAX_SOURCES);
assert.equal(kept.quebec, 1120);
assert.equal(kept.world, 2396);
assert.equal(kept.world, 2230);
for (let i = 0; i < 20; i++) assert.equal(kept[`shared${i}`], 200);
assert.match(
sources[0].id,
+3 -3
View File
@@ -7,7 +7,7 @@ import {
loadHoustonSourcesFromOpenData,
} from '../../server/providers/cctv/sources.js';
import {
DEFAULT_CCTV_MAX_SOURCES,
CCTV_SHARED_SOURCE_SLOTS,
DEFAULT_HOUSTON_MAX_SOURCES,
HOUSTON_CAMERAS_URL,
HOUSTON_GROUND_ELEVATION_M,
@@ -387,8 +387,8 @@ test('Houston stays unloaded unless CCTV_HOUSTON_ENABLED=1', async (t) => {
}
});
test('the shipped catalog ceiling is not raised to make room for this pack', () => {
assert.equal(DEFAULT_CCTV_MAX_SOURCES, 4000);
test('the shared catalog slots are not raised to make room for this pack', () => {
assert.equal(CCTV_SHARED_SOURCE_SLOTS, 4000);
assert.equal(DEFAULT_HOUSTON_MAX_SOURCES, 250);
});
+39 -29
View File
@@ -58,16 +58,15 @@ function sourceRoot(t, { world = [], quebec = null } = {}) {
return root;
}
const icelandRow = (overrides = {}) => ({
id: 'world-is-7075',
name: 'Almannaskarð - norður',
provider: 'Vegagerðin road cameras',
lat: 64.291877,
lon: -15.028035,
sourceKind: 'vegagerdin-road',
url: 'https://www.vegagerdin.is/vgdata/vefmyndavelar/almannaskard_1.jpg',
snapshotUrl:
'https://www.vegagerdin.is/vgdata/vefmyndavelar/almannaskard_1.jpg',
const oregonRow = (overrides = {}) => ({
id: 'world-or-764',
name: '3rd St at Harwood MP18.35',
provider: 'TripCheck (ODOT)',
lat: 44.3029,
lon: -120.8549,
sourceKind: 'tripcheck-odot',
url: 'https://www.tripcheck.com/roadcams/cams/Harwood_pid3527.jpg',
snapshotUrl: 'https://www.tripcheck.com/roadcams/cams/Harwood_pid3527.jpg',
...overrides,
});
@@ -76,7 +75,8 @@ test('the shipped World catalog loads every curated row', (t) => {
const sources = loadWorldCameraSources({ sourceRoot: REPO_ROOT });
assert.equal(sources.length, WORLD_CCTV_SOURCE_COUNT);
const byKind = Object.groupBy(sources, (source) => source.sourceKind);
assert.equal(byKind['vegagerdin-road'].length, 166);
// Iceland comes from the live Iceland pack, never a second World copy.
assert.equal(byKind['vegagerdin-road'], undefined);
assert.equal(byKind['tripcheck-odot'].length, 1176);
assert.equal(byKind['mlit-river'].length, 1054);
for (const source of byKind['mlit-river'])
@@ -87,31 +87,41 @@ test('the World loader accepts a pinned row and rejects anything outside its pol
quiet(t);
const rejected = [
['unknown kind', { sourceKind: 'nzta-journeys' }],
['wrong id prefix', { id: 'world-or-7075' }],
[
'Iceland duplicates the Iceland pack',
{
sourceKind: 'vegagerdin-road',
lat: 64.291877,
lon: -15.028035,
url: 'https://www.vegagerdin.is/vgdata/vefmyndavelar/almannaskard_1.jpg',
snapshotUrl: 'https://www.vegagerdin.is/vgdata/vefmyndavelar/almannaskard_1.jpg',
},
],
['wrong id prefix', { id: 'world-jp-river-7075' }],
['outside bounds', { lat: 40, lon: -15 }],
['other host', { url: 'https://evil.test/vgdata/vefmyndavelar/a.jpg' }],
['other directory', { url: 'https://www.vegagerdin.is/other/a.jpg' }],
['query string', { url: icelandRow().url + '?x=1' }],
['not a jpg', { url: 'https://www.vegagerdin.is/vgdata/vefmyndavelar/a.png' }],
['plain http', { url: icelandRow().url.replace('https:', 'http:') }],
['other host', { url: 'https://evil.test/roadcams/cams/a.jpg' }],
['other directory', { url: 'https://www.tripcheck.com/other/a.jpg' }],
['query string', { url: oregonRow().url + '?x=1' }],
['not a jpg', { url: 'https://www.tripcheck.com/roadcams/cams/a.png' }],
['plain http', { url: oregonRow().url.replace('https:', 'http:') }],
[
'url and snapshot differ',
{ snapshotUrl: 'https://www.vegagerdin.is/vgdata/vefmyndavelar/b.jpg' },
{ snapshotUrl: 'https://www.tripcheck.com/roadcams/cams/b.jpg' },
],
];
const root = sourceRoot(t, {
world: [
icelandRow(),
icelandRow(), // duplicate id
oregonRow(),
oregonRow(), // duplicate id
...rejected.map(([, overrides], i) =>
icelandRow({ id: `world-is-${9000 + i}`, ...overrides }),
oregonRow({ id: `world-or-${9000 + i}`, ...overrides }),
),
],
});
const sources = loadWorldCameraSources({ sourceRoot: root });
assert.deepEqual(
sources.map((source) => source.id),
['world-is-7075'],
['world-or-764'],
);
assert.equal(sources[0].frameRefreshMs, 5 * 60 * 1000);
assert.equal(sources[0].feedType, 'image');
@@ -238,14 +248,14 @@ test('/sources lists Québec browser media only when the operator opts in', asyn
test('a World frame is fetched once per refresh floor and the cooldown never spends Street View', async (t) => {
quiet(t);
const root = sourceRoot(t, { world: [icelandRow()] });
const root = sourceRoot(t, { world: [oregonRow()] });
let answer = () => new Response(JPEG, { headers: { 'content-type': 'image/jpeg' } });
const app = mount(t, root, (url) =>
url === icelandRow().url ? answer() : null,
url === oregonRow().url ? answer() : null,
);
const frame = '/frame/world-is-7075';
const frame = '/frame/world-or-764';
const upstreamHits = () =>
app.requests.filter((url) => url === icelandRow().url).length;
app.requests.filter((url) => url === oregonRow().url).length;
const first = await app.call(frame);
assert.equal(first.headers['X-CCTV-Source'], 'upstream-image');
@@ -255,17 +265,17 @@ test('a World frame is fetched once per refresh floor and the cooldown never spe
// A camera whose first fetch fails is inside its failure backoff: it gets
// the placeholder, not Google Street View, and no second upstream request.
const failRoot = sourceRoot(t, { world: [icelandRow()] });
const failRoot = sourceRoot(t, { world: [oregonRow()] });
answer = () => new Response('down', { status: 503 });
const failing = mount(t, failRoot, (url) =>
url === icelandRow().url ? answer() : null,
url === oregonRow().url ? answer() : null,
);
const failed = await failing.call(frame);
assert.notEqual(failed.headers['X-CCTV-Source'], 'upstream-image');
const retry = await failing.call(frame);
assert.equal(retry.headers['X-CCTV-Source'], 'synthetic');
assert.equal(
failing.requests.filter((url) => url === icelandRow().url).length,
failing.requests.filter((url) => url === oregonRow().url).length,
1,
'the backoff holds the provider cadence',
);
+32
View File
@@ -299,6 +299,38 @@ export const DATA_CREDITS = [
'CCTV static data (MOTC 2.0), released under the ' +
'<a href="https://data.gov.tw/license" target="_blank" rel="noopener">Open Government Data License, Taiwan (Version 1.0)</a>',
},
{
key: 'ottawa-cctv',
html:
'Traffic cameras (Ottawa, with an access certificate): ' +
'<a href="https://traffic.ottawa.ca/en/traffic-map-data-lists-and-resources/about-the-data" target="_blank" rel="noopener">City of Ottawa Traffic Camera Images API</a>',
},
{
key: 'quebec-cctv',
html:
'Camera locations (Québec): Ministère des Transports et de la Mobilité durable via ' +
'<a href="https://www.donneesquebec.ca/" target="_blank" rel="noopener">Données Québec</a> ' +
'(<a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">CC BY 4.0</a>); ' +
'Montréal camera locations © OpenStreetMap contributors (ODbL). ' +
'Video (when enabled) and linked webcam pages stay with ' +
'<a href="https://www.quebec511.info/" target="_blank" rel="noopener">Québec 511</a> and each operator, under their own terms',
},
{
key: 'tripcheck-cctv',
html:
'Road camera stills (Oregon): camera courtesy of ODOT, via ' +
'<a href="https://www.tripcheck.com/" target="_blank" rel="noopener">TripCheck</a>; ' +
'partner cameras (counties, cities, Caltrans, WSDOT) are named in the camera label',
},
{
key: 'mlit-river-cctv',
html:
'River camera stills (Japan): 出典:国土交通省 ' +
'<a href="https://www.river.go.jp/" target="_blank" rel="noopener">川の防災情報</a> ' +
'(Ministry of Land, Infrastructure, Transport and Tourism; ' +
'<a href="https://www.river.go.jp/kawabou/kwb_apend/html/caution.html" target="_blank" rel="noopener">terms</a>), ' +
'fetched on view, at most every ten minutes per camera',
},
{
key: 'calgary-cctv',
html:
+14
View File
@@ -153,3 +153,17 @@ test('every Street Level imagery source is in the attribution lightbox', () => {
`${key} has no credit`,
);
});
test('the PR #1506 camera packs are credited with their sources and terms', () => {
const html = (key) => DATA_CREDITS.find((entry) => entry.key === key)?.html;
assert.match(html('ottawa-cctv') || '', /traffic\.ottawa\.ca/);
assert.match(html('quebec-cctv') || '', /CC BY 4\.0/);
assert.match(html('quebec-cctv') || '', /OpenStreetMap contributors/);
assert.match(html('tripcheck-cctv') || '', /courtesy of ODOT/);
// MLIT's terms ask for the source in this form, with the page link.
assert.match(html('mlit-river-cctv') || '', /出典:国土交通省/);
assert.match(
html('mlit-river-cctv') || '',
/href="https:\/\/www\.river\.go\.jp\//,
);
});