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
@@ -306,6 +306,109 @@ export function isLikelyAustinCoordinate(lat, lon) {
|
||||
return lat >= 30.02 && lat <= 30.58 && lon >= -98.12 && lon <= -97.4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bounding-box sanity check: is this coordinate plausibly on the Finnish road
|
||||
* network? Generous around the observed catalog extent (59.86..70.09 N,
|
||||
* 19.62..31.28 E) so a real new station is never dropped, tight enough that a
|
||||
* swapped lat/lon or a null island record is.
|
||||
*
|
||||
* @param {number} lat
|
||||
* @param {number} lon
|
||||
* @returns {boolean}
|
||||
*/
|
||||
/** Longest unselected camera label the HUD shows before it gets noisy. */
|
||||
export const CAMERA_CODE_MAX_CHARS = 28;
|
||||
|
||||
/**
|
||||
* Short display code for the unselected camera label ("CAM-<code>"): the
|
||||
* feed's own name for the camera ("5TH ST / CONGRESS AVE", "TRAFALGAR
|
||||
* SQUARE"), trimmed to CAMERA_CODE_MAX_CHARS. A pack may pass an explicit
|
||||
* `code` (TxDOT's device key, NSW's title); the id is the last resort.
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
export function cameraDisplayCode(text) {
|
||||
const clean = String(text || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
if (clean.length <= CAMERA_CODE_MAX_CHARS) return clean;
|
||||
return `${clean.slice(0, CAMERA_CODE_MAX_CHARS - 1).trimEnd()}…`;
|
||||
}
|
||||
|
||||
/** Finite, in range, and not the null island that Number(null) produces. */
|
||||
export function isPlausibleLatLon(lat, lon) {
|
||||
return (
|
||||
Number.isFinite(lat) &&
|
||||
Number.isFinite(lon) &&
|
||||
Math.abs(lat) <= 90 &&
|
||||
Math.abs(lon) <= 180 &&
|
||||
!(lat === 0 && lon === 0)
|
||||
);
|
||||
}
|
||||
|
||||
/** British Columbia bounding box (with the neighbouring border crossings). */
|
||||
export function isLikelyBcCoordinate(lat, lon) {
|
||||
return (
|
||||
isPlausibleLatLon(lat, lon) &&
|
||||
lat >= 48 &&
|
||||
lat <= 60.5 &&
|
||||
lon >= -139.5 &&
|
||||
lon <= -114
|
||||
);
|
||||
}
|
||||
|
||||
/** Texas bounding box. */
|
||||
export function isLikelyTexasCoordinate(lat, lon) {
|
||||
return (
|
||||
isPlausibleLatLon(lat, lon) &&
|
||||
lat >= 25.5 &&
|
||||
lat <= 36.7 &&
|
||||
lon >= -107 &&
|
||||
lon <= -93.4
|
||||
);
|
||||
}
|
||||
|
||||
/** New South Wales bounding box (incl. the ACT and Lord Howe Island). */
|
||||
export function isLikelyNswCoordinate(lat, lon) {
|
||||
return (
|
||||
isPlausibleLatLon(lat, lon) &&
|
||||
lat >= -38 &&
|
||||
lat <= -28 &&
|
||||
lon >= 140.9 &&
|
||||
lon <= 159.2
|
||||
);
|
||||
}
|
||||
|
||||
export function isLikelyFinlandCoordinate(lat, lon) {
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return false;
|
||||
return lat >= 59.5 && lat <= 70.5 && lon >= 19 && lon <= 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human label for one Fintraffic preset (camera view).
|
||||
*
|
||||
* Station names are machine-shaped road codes ("vt3_Hyvinkää_Noppo"); the
|
||||
* underscores become spaces. A preset id is always its station id plus a
|
||||
* two-digit view number, so the remainder distinguishes the several views that
|
||||
* share one station position. The station list endpoint carries no
|
||||
* presentationName ("Helsinkiin"); that lives only on the per-station detail
|
||||
* endpoint, which would cost one request per station.
|
||||
*
|
||||
* @param {string} stationName - Raw `properties.name`.
|
||||
* @param {string} stationId - Raw `properties.id` (e.g. "C01503").
|
||||
* @param {string} presetId - Raw preset id (e.g. "C0150301").
|
||||
* @returns {string}
|
||||
*/
|
||||
export function fintrafficCameraName(stationName, stationId, presetId) {
|
||||
const base =
|
||||
String(stationName || '')
|
||||
.replace(/_/g, ' ')
|
||||
.trim() || `Fintraffic ${stationId}`;
|
||||
const view = String(presetId || '').slice(String(stationId || '').length);
|
||||
return view ? `${base} (view ${view})` : base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a deterministic fallback heading from a camera ID hash.
|
||||
*
|
||||
@@ -353,14 +456,15 @@ export function prioritizeSources(cameras, maxCount, anchors) {
|
||||
const anchorList = (Array.isArray(anchors) ? anchors : []).filter(
|
||||
(a) => Number.isFinite(a?.lat) && Number.isFinite(a?.lon),
|
||||
);
|
||||
if (
|
||||
!Number.isFinite(maxCount) ||
|
||||
maxCount <= 0 ||
|
||||
list.length <= maxCount ||
|
||||
!anchorList.length
|
||||
) {
|
||||
return list;
|
||||
}
|
||||
if (!anchorList.length) return list;
|
||||
// Always sort when anchors exist, even when the pack fits its own cap: the
|
||||
// catalog-wide cap (cap.js) thins a pack from the END of this order, so
|
||||
// "nearest to an anchor first" has to hold whether or not the pack was
|
||||
// trimmed here.
|
||||
const cap =
|
||||
Number.isFinite(maxCount) && maxCount > 0
|
||||
? Math.min(maxCount, list.length)
|
||||
: list.length;
|
||||
|
||||
const scored = list.map((camera, idx) => {
|
||||
const lat = Number(camera?.lat);
|
||||
@@ -379,7 +483,7 @@ export function prioritizeSources(cameras, maxCount, anchors) {
|
||||
return a.idx - b.idx;
|
||||
});
|
||||
|
||||
return scored.slice(0, maxCount).map((entry) => entry.camera);
|
||||
return scored.slice(0, cap).map((entry) => entry.camera);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,6 +514,14 @@ export function normalizeSourceItem(item) {
|
||||
url: typeof item.url === 'string' ? item.url : '',
|
||||
snapshotUrl: typeof item.snapshotUrl === 'string' ? item.snapshotUrl : '',
|
||||
license: String(item.license || item.licenseNote || ''),
|
||||
// Per-camera attribution for feeds a partner supplies inside a pack
|
||||
// (DriveBC: TransLink, city cameras). Shown beside the provider.
|
||||
credit: String(item.credit || '').trim(),
|
||||
// Unselected-label code: the pack's explicit short name, else the feed's
|
||||
// name, else the id.
|
||||
code: cameraDisplayCode(
|
||||
item.code || String(item.name || '').toUpperCase() || item.id || '',
|
||||
),
|
||||
sourceKind: String(item.sourceKind || item.kind || 'configured'),
|
||||
// Optional CAL badge input (cctv-v2 design §3b/§9.2, additive-only per the
|
||||
// global constraints — nothing else in this file changes): hand-authored
|
||||
|
||||
Reference in new issue
Block a user