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
@@ -275,8 +275,8 @@ AISSTREAM_API_KEY=
# CCTV_OTTAWA_MAX_SOURCES=250 # cap Ottawa cameras (8-500, nearest downtown first)
# CCTV_QUEBEC_ENABLED=1 # set 0 to skip the Québec camera locations (MTQ, Montréal, scenic links)
# CCTV_QUEBEC_VIDEO_ENABLED= # off by default; set 1 to let viewers play Québec 511 clips in their browser after pressing Play (511 terms of use unconfirmed; see DATA_SOURCES.md)
# CCTV_WORLD_CAMERAS_ENABLED=1 # set 0 to skip the Iceland, Oregon and MLIT (Japan) still cameras
# CCTV_MAX_SOURCES=7516 # catalog-wide ceiling: 4,000 slots shared round-robin, the rest reserved for Québec and World
# CCTV_WORLD_CAMERAS_ENABLED=1 # set 0 to skip the Oregon and MLIT (Japan) still cameras
# CCTV_MAX_SOURCES=7350 # catalog-wide ceiling: 4,000 slots shared round-robin, the rest reserved for Québec and World
# CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry
# CCTV_FORCE_AUSTIN= # force live default sources when a source-pack JSON is configured
+2 -2
View File
@@ -173,7 +173,7 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
- **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.
- **City of Ottawa traffic cameras.** Camera images come from the city's [Traffic Camera Images API](https://traffic.ottawa.ca/en/traffic-map-data-lists-and-resources/about-the-data), which requires an access certificate requested through the city's registration form and allows one certificate and user id to fetch a camera at most once every 60 seconds. Set `OTTAWA_CAMERA_CERTIFICATE`; without it the pack is skipped and makes no request. The camera list is the documented keyless `traffic.ottawa.ca/map/service/camera` JSON (camera number, English and French names, coordinates, owner). Frames are `traffic.ottawa.ca/opendata/camera?c=<number>`; the server adds the certificate and the user id `gev` at fetch time, so no catalog response carries the key, and serves each camera from one shared in-memory frame per minute. MTO cameras carried on the city feed keep their owner as a per-camera credit. The feed publishes no bearing, so headings use the shared id-hash fallback at low confidence, and no status field, so a camera the city takes offline still registers. Frames are never stored on disk. The city states no redistribution licence for the images; the certificate request asks how they will be used, so describe the deployment there. `CCTV_OTTAWA_MAX_SOURCES` caps the pack (default 250, nearest downtown) and `CCTV_OTTAWA_ENABLED=0` turns it off.
- **World cams image subset.** The normal CCTV catalog adds 166 Icelandic Road and Coastal Administration cameras, 1,176 Oregon TripCheck stills, and 1,054 MLIT river cameras from the curated `config/cctv_sources.world-image-cameras.json` export; disable it with `CCTV_WORLD_CAMERAS_ENABLED=0`. The server validates and pins each image URL to its provider path. [Vegagerðin's data-service licence](https://www.vegagerdin.is/vegagerdin/gagnasafn/vefthjonustur/terms-and-conditions) permits reuse with attribution but excludes third-party rights; [ODOT allows TripCheck image use and requests credit](https://www.tripcheck.com/Pages/Frequently-Asked-Questions); MLIT's [site terms](https://www.river.go.jp/kawabou/kwb_apend/html/caution.html) are CC BY-compatible with attribution and warn against high-volume/periodic collection, so those frames refresh at ten minutes per camera. Images are not bundled or persisted; a bounded 32 MB in-memory cache reuses them until the per-source cadence. The 249 NZTA Journey Planner rows are excluded because its [website terms prohibit scraping](https://www.journeys.nzta.govt.nz/about-this-site/terms-of-use); use an authorized API feed or obtain permission before adding those rows.
- **World cams image subset.** The normal CCTV catalog adds 1,176 Oregon TripCheck stills and 1,054 MLIT river cameras from the curated `config/cctv_sources.world-image-cameras.json` export; disable it with `CCTV_WORLD_CAMERAS_ENABLED=0`. The export's Vegagerðin rows are left out because the Iceland pack already loads those cameras. The server validates and pins each image URL to its provider path, fetches a frame only when a camera is viewed, and never stores or redistributes it. [ODOT allows businesses, private citizens and organizations to use TripCheck images and asks for credit](https://www.tripcheck.com/Pages/Frequently-Asked-Questions) ("Camera courtesy of ODOT"); TripCheck also carries partner cameras run by Oregon counties and cities, Caltrans and WSDOT, which keep their owner's name in the camera label. MLIT's [site terms](https://www.river.go.jp/kawabou/kwb_apend/html/caution.html) follow the Government of Japan Standard Terms of Use 2.0 (CC BY 4.0 compatible, commercial use allowed, source credit required) and ask tools not to collect data periodically, pointing regular collection to the river information data service; frames are therefore fetched only on view and refresh at most every ten minutes per camera. Both credits are registered in the Data attribution popover.
- **TxDOT ITS cameras.** The catalog comes from the keyless `its.txdot.gov/its/DistrictIts/GetCctvStatusListByDistrict` endpoint, one call per district. Frames come from the per-camera `GetCctvSnapshotByIcdId` endpoint, which answers JSON carrying a base64 JPEG rather than an image body; the proxy decodes it only for responses from the official TxDOT origin and validates the JPEG header before serving. Only cameras reporting `Device Online` are registered. Districts default to Austin and San Antonio (`CCTV_TXDOT_DISTRICTS`; 25 codes exist statewide); `CCTV_TXDOT_MAX_SOURCES` caps the pack and `CCTV_TXDOT_ENABLED=0` turns it off. Frames are fetched live at request time and are never stored or redistributed.
- **Tallinn ristmikud.** Intersection camera stills are fetched live from `ristmikud.tallinn.ee` (`/last/camNNN.jpg`). The curated catalog (`config/cctv_sources.tallinn.json`) supplies coordinates and heading priors; only server-registered ristmikud HTTPS URLs are proxied. Frames are fetched at request time and never stored or redistributed. Disable with `CCTV_TALLINN_ENABLED=0`.
- **Tarktee / Transpordiamet.** Estonia road-weather camera locations and current JPEG URLs come from keyless DATEX2 endpoints on `tarktee.transpordiamet.ee` (`/api/v1/datex/roadCameraLocations` + `roadCameraImages`). Frame URLs rotate with each capture; the 15-minute CCTV source cache refreshes them. Only `https://tarktee.transpordiamet.ee/images/…` URLs are registered. Disable with `CCTV_TARKTEE_ENABLED=0`.
@@ -226,7 +226,7 @@ The [Bhote Koshi event pack](public/events/bhote-koshi-2026/README.md), under `p
| **Dams** (704) | `dams/` | **ODbL 1.0** (OpenInfraMap / OSM extract) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Open Infrastructure Map |
| **Military area names** (36,466) | `osm_military_names/` | **ODbL 1.0** (OpenStreetMap, via Overture Maps) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Overture Maps Foundation |
| **Québec CCTV source catalog** (1,120 locations) | `public/quebec-cameras.json` | Mixed: MTQ locations CC BY 4.0; Montréal camera nodes ODbL 1.0; scenic records retain per-record provenance. No video files are bundled or cached. | Location data is per-record; video remains hosted by Québec 511 and its platform terms apply; scenic source pages may have separate terms | MTQ / Données Québec CC BY 4.0; © OpenStreetMap contributors (ODbL); scenic operator and position source shown in the selected-camera panel |
| **World camera image subset** (2,396 locations) | `config/cctv_sources.world-image-cameras.json` | Mixed: Vegagerðin open-government licence, ODOT TripCheck public images, and MLIT site content compatible with CC BY 4.0. Frames are not bundled or persisted; the server holds a bounded in-memory cache. | Per-source terms apply; NZTA Journey Planner web-cache rows are excluded | Vegagerðin, Oregon Department of Transportation, and Japan Ministry of Land, Infrastructure, Transport and Tourism (MLIT) |
| **World camera image subset** (2,230 locations) | `config/cctv_sources.world-image-cameras.json` | Mixed: ODOT TripCheck public images (ODOT permits use with credit) and MLIT site content under terms compatible with CC BY 4.0. Frames are not bundled or persisted; the server holds a bounded in-memory cache. | Per-source terms apply; NZTA Journey Planner web-cache rows are excluded; Vegagerðin rows are served by the Iceland pack instead | Oregon Department of Transportation (Camera courtesy of ODOT) and Japan Ministry of Land, Infrastructure, Transport and Tourism (出典:国土交通省 川の防災情報) |
| **TeleGeography Submarine Cable Map** (712 cables + 1,917 landing points) | `telegeography_submarine_cables/` | **CC BY-NC-SA 3.0** | ❌ **NonCommercial — remove for commercial use** | "© TeleGeography — submarinecablemap.com" |
| **Natural Earth physical regions** (1,046 land + 292 marine named polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
| **DataSF Analysis Neighborhoods** (41 SF neighborhood polygons) | `neighborhoods/` | **PDDL 1.0** (public domain) | ✅ (no restrictions) | "City & County of San Francisco — DataSF" (courtesy — not legally required) |
+1 -1
View File
@@ -440,7 +440,7 @@ Twenty-two layers and map sources. **Twenty have a keyless path.** Some offer ad
| 🌍 **Earthquakes** | Global seismic activity, last 24h | USGS | 🟢 |
| ◍ **Air quality** | Live AQHI on the published 1–10+ health-risk scale, with a Weather-panel card showing the band legend and the highest readings in view. **Canada only** — ECCC is the first registered provider | ECCC MSC GeoMet | 🟢 |
| 🚗 **Traffic** | Simulated vehicles on OSM roads. With TomTom, live flow speeds drive the simulation and congestion colors below ~8 km; individual vehicle positions are not live observations | TomTom + OSM | 🟢 simulation · 🟡 live flow speeds |
| 📹 **CCTV Mesh** | up to 7,516 camera locations (4,000 shared round-robin slots, plus room for 1,120 Québec and 2,396 World image cameras) projected _into_ the 3D space — Austin · Texas (TxDOT) · California (Caltrans) · London (TfL) · Ontario (511, free developer key) · Ottawa (City of Ottawa, free access certificate) · Finland (Fintraffic) · British Columbia (DriveBC) · Estonia (Tallinn, Tarktee) · Delaware (DelDOT live video) · New South Wales (Live Traffic NSW) · Calgary · Norway (Statens vegvesen) · Toronto · King County (WA) · Washington (WSDOT) · Catalonia (Servei Català de Trànsit) · Iceland (Vegagerðin) · Hong Kong (Transport Department) · Taiwan (Freeway Bureau, MOTC) · Québec (MTQ locations with Québec 511 links, Montréal locations, regional webcams) · Oregon (TripCheck) · Japan (MLIT river cameras) · Italy (A22 Brennero motorway), plus opt-in New York City (NYC DOT TMC, needs your own NYC DOT agreement), opt-in Lithuania (Via Lietuva / Eismoinfo, photo licence unconfirmed), opt-in Québec 511 in-browser video (embedding terms unconfirmed), opt-in Florida (FL511, individual personal use only; other use needs FDOT's written consent) and opt-in Houston (TranStar, enable only with TxDOT's written permission per TranStar's Content Usage terms). Positions are published or attributed; poses are estimated priors **you calibrate by dragging a gizmo on the camera itself** | City APIs | 🟢 |
| 📹 **CCTV Mesh** | up to 7,350 camera locations (4,000 shared round-robin slots, plus room for 1,120 Québec and 2,230 World image cameras) projected _into_ the 3D space — Austin · Texas (TxDOT) · California (Caltrans) · London (TfL) · Ontario (511, free developer key) · Ottawa (City of Ottawa, free access certificate) · Finland (Fintraffic) · British Columbia (DriveBC) · Estonia (Tallinn, Tarktee) · Delaware (DelDOT live video) · New South Wales (Live Traffic NSW) · Calgary · Norway (Statens vegvesen) · Toronto · King County (WA) · Washington (WSDOT) · Catalonia (Servei Català de Trànsit) · Iceland (Vegagerðin) · Hong Kong (Transport Department) · Taiwan (Freeway Bureau, MOTC) · Québec (MTQ locations with Québec 511 links, Montréal locations, regional webcams) · Oregon (TripCheck) · Japan (MLIT river cameras) · Italy (A22 Brennero motorway), plus opt-in New York City (NYC DOT TMC, needs your own NYC DOT agreement), opt-in Lithuania (Via Lietuva / Eismoinfo, photo licence unconfirmed), opt-in Québec 511 in-browser video (embedding terms unconfirmed), opt-in Florida (FL511, individual personal use only; other use needs FDOT's written consent) and opt-in Houston (TranStar, enable only with TxDOT's written permission per TranStar's Content Usage terms). Positions are published or attributed; poses are estimated priors **you calibrate by dragging a gizmo on the camera itself** | City APIs | 🟢 |
| 📷 **Mapped ALPR Cameras** | License-plate-reader camera locations tagged by OpenStreetMap contributors, loaded one city-sized view at a time, with **SHOW NEAREST**. Locations and tags only: no plate data, no video | OpenStreetMap (incl. DeFlock mapping) | 🟢 |
| 📷 **Street Level** | Street-level photos behind one panel, one chip per provider: coverage on the streets, image cones, an embedded viewer with full-screen **EXPAND**. Mapillary, Google Street View, KartaView and Panoramax | Mapillary · Google · KartaView · Panoramax | 🟢 KartaView, Panoramax · 🟡 Mapillary token · 🔴 Google Maps key |
| 📻 **Radio** | Geolocated world radio with an **analog tuner** — drag the needle across up to 750 stations and the globe flies to each broadcaster | Radio Browser / broadcasters | 🟢 |
@@ -1,7 +1,7 @@
- Expand CCTV coverage with a keyless Taiwan Highway Bureau fallback, 1,120
Québec camera locations, City of Ottawa traffic cameras (needs a free city
access certificate, `OTTAWA_CAMERA_CERTIFICATE`), and 2,396 curated
image-backed cameras from Iceland, Oregon, and Japan (InoukT, #1506).
access certificate, `OTTAWA_CAMERA_CERTIFICATE`), and 2,230 curated
image-backed cameras from Oregon and Japan (InoukT, #1506).
- Québec 511 in-browser video is opt-in (`CCTV_QUEBEC_VIDEO_ENABLED=1`) and
loads only after the viewer presses Play; enforce provider-specific frame
refresh limits, and report recognized MLIT maintenance images as degraded
File diff suppressed because it is too large. Load diff
+11 -11
View File
@@ -1574,8 +1574,8 @@ id-hash fallback at low confidence.
Once enabled, the 250 cameras nearest the five borough cores load.
`CCTV_NYC_MAX_SOURCES` sets the pack cap (8–400). The default catalog limit is
7,516: 4,000 shared round-robin slots, plus room above them for the 1,120
Québec locations and 2,396 World image sources. Lower explicit
7,350: 4,000 shared round-robin slots, plus room above them for the 1,120
Québec locations and 2,230 World image sources. Lower explicit
`CCTV_MAX_SOURCES` values are honored; reservations only use the capacity
above the 4,000 shared slots.
@@ -1594,7 +1594,7 @@ cameras return a 200 "Image Unavailable" placeholder frame instead of an error.
By default all ~336 cameras load, nearest downtown first.
`CCTV_TORONTO_MAX_SOURCES` sets the pack cap (8–500) and
`CCTV_TORONTO_ENABLED=0` turns the pack off. The pack shares the default
7,516-source catalog limit with the other packs; Québec and the World image
7,350-source catalog limit with the other packs; Québec and the World image
pack have reserved slots and the remaining sources use round-robin allocation.
## Lithuania CCTV source pack
@@ -5409,8 +5409,8 @@ silently demoting every later lookup for the session.
kill switch `CCTV_FINTRAFFIC_ENABLED=0`) are the fourth pack: one keyless GeoJSON station list
covering the whole country, where one _preset_ (a station's fixed view) is one camera — 806
GATHERING stations carry 2,256 in-collection presets, prioritized to 300 against seven anchors
on the main road spine. `CCTV_MAX_SOURCES` defaults to 7,516: 4,000 shared
round-robin slots plus 3,516 reserved Québec and World image slots.
on the main road spine. `CCTV_MAX_SOURCES` defaults to 7,350: 4,000 shared
round-robin slots plus 3,350 reserved Québec and World image slots.
Reservations only use capacity above the 4,000 shared slots, so at 4,000 or
less every pack, Québec and World included, shares the cap round-robin.
~1,100 cameras total, all RAW PRIOR poses, stills-first. The catalog lives for 15 minutes; once
@@ -7095,8 +7095,8 @@ configure automatic restarts.
The regular CCTV source loader reads `public/quebec-cameras.json` by default
when `CCTV_QUEBEC_ENABLED` is not `0`. The pack contains 1,120 records: 680
MTQ highway cameras, 426 Montréal OSM camera locations, and 14 scenic webcams.
The default catalog limit is 7,516: it reserves all 1,120 Québec locations and
2,396 World image feeds, and keeps 4,000 slots for the existing round-robin
The default catalog limit is 7,350: it reserves all 1,120 Québec locations and
2,230 World image feeds, and keeps 4,000 slots for the existing round-robin
camera packs. Reservations only use capacity above those 4,000 slots: a lower
explicit `CCTV_MAX_SOURCES` thins every pack round-robin, Québec and World
included, instead of taking the shared slots.
@@ -7143,16 +7143,16 @@ id-hash fallback at low confidence. `CCTV_OTTAWA_MAX_SOURCES` caps the pack
## World camera image feeds
The default `world-cameras` pack reads only the checked-in, curated
`config/cctv_sources.world-image-cameras.json` file. It adds 166 Vegagerðin,
1,176 Oregon TripCheck, and 1,054 MLIT river cameras. Duplicate Canadian feeds
are excluded because DriveBC, Ottawa, and Québec already have app packs. NZTA's
`config/cctv_sources.world-image-cameras.json` file. It adds 1,176 Oregon
TripCheck and 1,054 MLIT river cameras. Duplicate feeds are excluded because
DriveBC, Ottawa, Québec and Iceland (Vegagerðin) already have app packs. NZTA's
249 rows are excluded because they were collected from a Journey Planner web
cache whose current terms prohibit automated scraping; an authorized API feed
or permission is required before adding them. The wider discovery export is
not shipped with the app.
The app validates and pins each still-image URL to its provider directory
before the server proxy uses it. Iceland and Oregon frames refresh no faster
before the server proxy uses it. Oregon frames refresh no faster
than every five minutes; MLIT river frames refresh every ten minutes to reduce
requests. Images are not persisted; the proxy reuses frames in a bounded 32 MB
in-memory cache until their provider cadence. Set
+1 -1
View File
@@ -44,7 +44,7 @@ CCTV_NSW_ENABLED="${CCTV_NSW_ENABLED:-1}"
CCTV_NSW_MAX_SOURCES="${CCTV_NSW_MAX_SOURCES:-250}"
CCTV_CALGARY_ENABLED="${CCTV_CALGARY_ENABLED:-1}"
CCTV_CALGARY_MAX_SOURCES="${CCTV_CALGARY_MAX_SOURCES:-220}"
CCTV_MAX_SOURCES="${CCTV_MAX_SOURCES:-7516}"
CCTV_MAX_SOURCES="${CCTV_MAX_SOURCES:-7350}"
# Capture which provider credentials genuinely came from the parent shell
# before this launcher resolves dotenv and Keychain fallbacks. Only names are
+3 -5
View File
@@ -12,9 +12,9 @@ export const DEFAULT_AUSTIN_MAX_SOURCES = 250;
export const CCTV_SHARED_SOURCE_SLOTS = 4000;
/**
* Catalog-wide default: the 4,000 shared slots plus room reserved for the
* 1,120 Québec and 2,396 World image locations.
* 1,120 Québec and 2,230 World image locations.
*/
export const DEFAULT_CCTV_MAX_SOURCES = 7516;
export const DEFAULT_CCTV_MAX_SOURCES = 7350;
/** Hard upper bound for CCTV_MAX_SOURCES; also sizes the health map. */
export const CCTV_MAX_SOURCES_CEILING = 8000;
/** Reference point for Austin camera prioritization (Congress & 6th). */
@@ -227,14 +227,12 @@ export const TAIWAN_HIGHWAY_IMAGE_ORIGINS = Object.freeze(
[1, 2, 3, 4, 5, 6, 7, 8].map((n) => `https://cctv-ss0${n}.thb.gov.tw/`),
);
/** Direct still-image directories from the World cams add-on's eligible packs. */
export const VEGAGERDIN_IMAGE_ORIGIN =
'https://www.vegagerdin.is/vgdata/vefmyndavelar/';
export const TRIPCHECK_IMAGE_ORIGIN =
'https://www.tripcheck.com/roadcams/cams/';
export const MLIT_RIVER_IMAGE_ORIGIN = 'https://cam.river.go.jp/cam/now/';
export const DEFAULT_WORLD_CCTV_SOURCE_FILE =
'config/cctv_sources.world-image-cameras.json';
export const WORLD_CCTV_SOURCE_COUNT = 2396;
export const WORLD_CCTV_SOURCE_COUNT = 2230;
/** Tallinn intersection cameras: curated catalog + public stills on ristmikud.tallinn.ee. */
export const DEFAULT_TALLINN_SOURCE_FILE = 'config/cctv_sources.tallinn.json';
export const DEFAULT_TALLINN_MAX_SOURCES = 255;
-2
View File
@@ -14,7 +14,6 @@ import {
TRIPCHECK_IMAGE_ORIGIN,
NSW_IMAGE_ORIGIN,
NSW_IMAGE_USER_AGENT,
VEGAGERDIN_IMAGE_ORIGIN,
OTTAWA_IMAGE_ORIGIN,
TALLINN_IMAGE_ORIGIN,
TARKTEE_IMAGE_ORIGIN,
@@ -538,7 +537,6 @@ for (const prefix of [
KING_COUNTY_IMAGE_ORIGIN,
WSDOT_IMAGE_ORIGIN,
A22_IMAGE_ORIGIN,
VEGAGERDIN_IMAGE_ORIGIN,
TRIPCHECK_IMAGE_ORIGIN,
MLIT_RIVER_IMAGE_ORIGIN,
...TAIWAN_HIGHWAY_IMAGE_ORIGINS,
+3 -9
View File
@@ -5,19 +5,11 @@ import {
DEFAULT_WORLD_CCTV_SOURCE_FILE,
MLIT_RIVER_IMAGE_ORIGIN,
TRIPCHECK_IMAGE_ORIGIN,
VEGAGERDIN_IMAGE_ORIGIN,
WORLD_CCTV_SOURCE_COUNT,
} from './constants.js';
import { pinnedCameraUrl } from './normalize.js';
const SOURCE_POLICIES = Object.freeze({
'vegagerdin-road': {
idPrefix: 'world-is-',
origin: VEGAGERDIN_IMAGE_ORIGIN,
bounds: [63, 68, -26, -12],
filename: /^\/vgdata\/vefmyndavelar\/[^/]+\.jpg$/i,
frameRefreshMs: 5 * 60 * 1000,
},
'tripcheck-odot': {
idPrefix: 'world-or-',
origin: TRIPCHECK_IMAGE_ORIGIN,
@@ -72,7 +64,9 @@ function inBounds(lat, lon, [minLat, maxLat, minLon, maxLon]) {
/**
* Load only the new image-backed networks from the add-on export. Existing
* Canadian networks, Québec entries, JMA placeholder images, and NZTA's
* scraped Journey Planner rows are deliberately excluded.
* scraped Journey Planner rows are deliberately excluded. Vegagerðin (Iceland)
* rows are excluded too: the Iceland pack loads the full live list of those
* cameras, so a World copy would draw each one twice.
*/
export function loadWorldCameraSources({ sourceRoot = process.cwd() } = {}) {
const filename = path.resolve(sourceRoot, DEFAULT_WORLD_CCTV_SOURCE_FILE);
+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\//,
);
});