feat: Kartverket ground heights for Statens vegvesen cameras

The Vegvesen camera feed is 2D, so every camera shared one flat 150 m
ground prior and mountain-pass cameras could sit hundreds of metres below
the terrain where neither the point-height prior nor the ground snap
resolves. Each camera now gets its terrain height from Kartverket's
keyless point-height API (CC BY 4.0): ~840 of ~845 cameras.

- 50 points per request, six in flight; points a full batch answers null
  for are retried once in batches of 10 (the API drops points from large
  batches).
- Sea-floor depths (dybdekurver / Havflate) under bridges and quays
  count as the water surface, 0 m.
- Heights are cached by position, so the 15-minute refresh only asks
  about new or moved cameras. A catalog load waits at most 12 s; a slower
  lookup keeps filling the cache for the next refresh.
- CCTV_VEGVESEN_HEIGHTS=0 turns it off; Kartverket is credited beside
  Statens vegvesen in the attribution popover.

Follow-up to #929.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Trygve HemminghyttandClaude Opus 5.5 committed 2026-10-05 21:46:20 +02:00
1 parent a08a53cf94
commit c43f58dc3f
8 files changed
+479 -11

No files matched your search

+24
View File
@@ -267,6 +267,30 @@ export const NORWAY_ANCHORS = [
{ lat: 69.6492, lon: 18.9553 }, // Tromsø
];
/** Kartverket's keyless point-height API (CC BY 4.0): terrain height above
* sea level (NN2000) for up to 50 points per request, used as the Vegvesen
* cameras' ground prior because the camera feed's points are 2D. */
export const KARTVERKET_HEIGHT_URL =
'https://ws.geonorge.no/hoydedata/v1/punkt';
export const KARTVERKET_MAX_POINTS = 50;
/** Batch size for the one retry of points a full batch answered null for. */
export const KARTVERKET_RETRY_POINTS = 10;
/** Batches in flight at once. The whole country is ~17 batches of ~2 s each
* (spread-out points are slow to resolve), so six keep a cold lookup ~6 s. */
export const KARTVERKET_CONCURRENCY = 6;
/** Deadline for one complete lookup, shared by every batch. */
export const KARTVERKET_LOOKUP_TIMEOUT_MS = 30 * 1000;
/** How long a catalog load waits for heights. Kept under the per-provider
* fetch timeout so this pack is never the catalog's slowest lane; a lookup
* still running after this keeps filling the cache, and the next refresh
* applies the rest. At app start, with every pack loading at once, a cold
* lookup takes ~10 s. */
export const KARTVERKET_CATALOG_WAIT_MS = 12 * 1000;
export const KARTVERKET_MAX_RESPONSE_BYTES = 256 * 1024;
/** Plausible Norwegian terrain: below sea level only at the shore, and no road
* camera sits above ~1,500 m. Anything outside is treated as no answer. */
export const KARTVERKET_HEIGHT_RANGE_M = Object.freeze({ min: -20, max: 2500 });
/** DelDOT CCTV: one keyless statewide JSON catalog; live video via RTMP-over-HTTP (rtmpt:80). */
export const DELDOT_CCTV_URL = 'https://tmc.deldot.gov/json/videocamera.json';
export const DEFAULT_DELDOT_MAX_SOURCES = 300;