mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +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
+161
-40
@@ -8,12 +8,87 @@ import {
|
||||
COVERAGE_NEIGHBOR_RADIUS_KM,
|
||||
COVERAGE_NEIGHBOR_LIMIT,
|
||||
IDLE_COVERAGE_COLOR,
|
||||
PLANE_FOOTPRINT_LIFT_CAP_M,
|
||||
} from './policy.js';
|
||||
|
||||
import {
|
||||
planeDimensions,
|
||||
poseHash,
|
||||
requiredPlaneLift,
|
||||
} from '../../data/cctvFootprint.js';
|
||||
|
||||
export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
const { warmGroundFloor, cachedGroundFloor } = services.ground;
|
||||
const { sampleMeshFloorCells } = services.mesh;
|
||||
|
||||
/**
|
||||
* Ground measured under the plane's support points for the record's
|
||||
* CURRENT pose, or null. Shipped precompute samples (`camera.groundHeights`)
|
||||
* were taken for the nominal pose; they apply only while the pose hash still
|
||||
* matches, so a calibrated (edited) camera falls back to the mount ground
|
||||
* until its own footprint is resolved. Photoreal regime only: the samples
|
||||
* are Google mesh heights and would be wrong against a globe DEM.
|
||||
* @param {Object} record
|
||||
* @returns {Record<string, number>|null}
|
||||
*/
|
||||
function footprintGroundFor(record) {
|
||||
if (hasShippedFootprint(record)) {
|
||||
return record.camera.groundHeights.supports || null;
|
||||
}
|
||||
// DEM footprint resolved on demand (activation / calibration commit) —
|
||||
// valid in either regime, the DEM is what the globe stacks render anyway.
|
||||
const resolved = record?.footprintGround;
|
||||
if (resolved && resolved.poseHash === poseHash(footprintPose(record))) {
|
||||
return resolved.supports || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pose the monitor plane is actually rendered with: the calibrated
|
||||
* camera fields plus the activation probe's range clamp. Footprint samples
|
||||
* (shipped or DEM) are only valid for exactly this pose.
|
||||
* @param {Object} record
|
||||
* @returns {{lat:number, lon:number, headingDeg:number, pitchDeg:number,
|
||||
* fovDeg:number, rangeM:number, mountHeightM:number}}
|
||||
*/
|
||||
function footprintPose(record) {
|
||||
const camera = record.camera;
|
||||
const override = parts.model.safeNumber(record.probeClampRangeM, NaN);
|
||||
const rangeM =
|
||||
Number.isFinite(override) && override > 0
|
||||
? Math.min(camera.rangeM, override)
|
||||
: camera.rangeM;
|
||||
return {
|
||||
lat: camera.lat,
|
||||
lon: camera.lon,
|
||||
headingDeg: camera.headingDeg,
|
||||
pitchDeg: camera.pitchDeg,
|
||||
fovDeg: camera.fovDeg,
|
||||
rangeM,
|
||||
mountHeightM: camera.mountHeightM,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the record carries shipped precompute samples for its CURRENT
|
||||
* pose and the scene is rendering the photoreal mesh they were taken from.
|
||||
* @param {Object} record
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasShippedFootprint(record) {
|
||||
const shipped = record?.camera?.groundHeights;
|
||||
if (!shipped || typeof shipped !== 'object') return false;
|
||||
if (parts.ground.currentSurfaceRegime() !== 'google-3d') return false;
|
||||
// Samples describe the pose as served. Any calibration, client clamp or
|
||||
// activation range clamp changes the rendered pose and its hash, and the
|
||||
// camera then falls back to its own DEM footprint.
|
||||
return (
|
||||
Number.isFinite(shipped.mountGroundM) &&
|
||||
shipped.poseHash === poseHash(footprintPose(record))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 core geometry (design §2a): computes the pitched frustum pyramid — mount
|
||||
* point, far-cap (monitor plane) center, and the 4 far-plane corners — purely
|
||||
@@ -28,16 +103,20 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
* vFov = 2·atan(tan(hFov/2) / (16/9)) → halfH = R·tan(vFov/2)
|
||||
* upOffset = cos(pitch)·halfH vertical + (−sin(pitch))·halfH along heading
|
||||
* corners = (capCenter ∓ halfW toward heading∓90°) ± upOffset
|
||||
* The cap CENTER altitude clamps to ≥ groundAltM + 2 m (§6 risk: fabricated
|
||||
* pitch must never bury the plane's anchor); corners derive rigidly from the
|
||||
* clamped center so the wireframe rays always terminate on the plane's
|
||||
* corners — the bottom pair may dip below ground (tiles occlude it).
|
||||
* The whole rectangle is then lifted rigidly by the smallest amount that
|
||||
* puts every support point (a 3×3 grid over the plane) at least 2 m above
|
||||
* the ground measured under it — the ground at the mount where nothing
|
||||
* finer is known — so the plane never clips into the terrain and the
|
||||
* wireframe rays still terminate exactly on its corners.
|
||||
*
|
||||
* @param {Object} camera - Pose: lat, lon, headingDeg, pitchDeg, fovDeg,
|
||||
* rangeM, mountHeightM.
|
||||
* @param {number} groundAltM - Ground altitude at the mount (metres).
|
||||
* @param {number|null} [rangeOverrideM=null] - Obstruction-probe clamp: caps the
|
||||
* effective range (never lengthens it).
|
||||
* @param {Record<string, number>|null} [groundUnderPlane=null] - Ground
|
||||
* altitude measured under each plane support point (`bl`…`tr`, see
|
||||
* footprint.js), from the shipped precompute or a DEM lookup.
|
||||
* @returns {{ rangeM: number, vFovDeg: number, halfW: number, halfH: number,
|
||||
* mount: {lat:number,lon:number,alt:number},
|
||||
* capCenter: {lat:number,lon:number,alt:number},
|
||||
@@ -45,7 +124,12 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
* topCenter: {lat:number,lon:number,alt:number}, groundAltM: number }}
|
||||
*/
|
||||
|
||||
function computeFrustumGeometry(camera, groundAltM, rangeOverrideM = null) {
|
||||
function computeFrustumGeometry(
|
||||
camera,
|
||||
groundAltM,
|
||||
rangeOverrideM = null,
|
||||
groundUnderPlane = null,
|
||||
) {
|
||||
const ground = parts.model.safeNumber(groundAltM, 0);
|
||||
const poseRange = Math.max(1, parts.model.safeNumber(camera.rangeM, 700));
|
||||
const override = parts.model.safeNumber(rangeOverrideM, NaN);
|
||||
@@ -53,73 +137,92 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
Number.isFinite(override) && override > 0
|
||||
? Math.min(poseRange, override)
|
||||
: poseRange;
|
||||
const pitch = parts.model.toRad(
|
||||
parts.model.clamp(parts.model.safeNumber(camera.pitchDeg, -17), -89, 89),
|
||||
const pitchDeg = parts.model.clamp(
|
||||
parts.model.safeNumber(camera.pitchDeg, -17),
|
||||
-89,
|
||||
89,
|
||||
);
|
||||
const hFov = parts.model.toRad(
|
||||
parts.model.clamp(parts.model.safeNumber(camera.fovDeg, 74), 8, 160),
|
||||
const fovDeg = parts.model.clamp(
|
||||
parts.model.safeNumber(camera.fovDeg, 74),
|
||||
8,
|
||||
160,
|
||||
);
|
||||
const heading = parts.model.safeNumber(camera.headingDeg, 0);
|
||||
const mountAlt = ground + parts.model.safeNumber(camera.mountHeightM, 24);
|
||||
const dims = planeDimensions({ rangeM: R, pitchDeg, fovDeg });
|
||||
|
||||
const horiz = R * Math.cos(pitch);
|
||||
const vert = R * Math.sin(pitch);
|
||||
const capLL = parts.model.projectPoint(
|
||||
camera.lat,
|
||||
camera.lon,
|
||||
heading,
|
||||
horiz,
|
||||
dims.horiz,
|
||||
);
|
||||
const capAlt = mountAlt + vert;
|
||||
|
||||
const halfW = R * Math.tan(hFov / 2);
|
||||
const vFovRad = 2 * Math.atan(Math.tan(hFov / 2) / PROJECTION_VERT_ASPECT);
|
||||
const halfH = R * Math.tan(vFovRad / 2);
|
||||
|
||||
// In-plane "up" of the pitched cap, decomposed into a vertical part and a
|
||||
// horizontal part along the heading (pitch < 0 tilts the cap's top forward).
|
||||
const upVert = Math.cos(pitch) * halfH;
|
||||
const upHoriz = -Math.sin(pitch) * halfH;
|
||||
|
||||
const capAlt = mountAlt + dims.vert;
|
||||
const capL = parts.model.projectPoint(
|
||||
capLL.lat,
|
||||
capLL.lon,
|
||||
heading - 90,
|
||||
halfW,
|
||||
dims.halfW,
|
||||
);
|
||||
const capR = parts.model.projectPoint(
|
||||
capLL.lat,
|
||||
capLL.lon,
|
||||
heading + 90,
|
||||
halfW,
|
||||
dims.halfW,
|
||||
);
|
||||
// Ground clamp (§6 risk): lift the CAP CENTER once so a fabricated pitch
|
||||
// never buries the plane's anchor — then derive the corners RIGIDLY from the
|
||||
// lifted center. Clamping each corner independently flattened the wireframe
|
||||
// into a ground-hugging fan while the rigid plane kept its height (owner
|
||||
// field test 2026-07-04): the corner rays must always terminate exactly on
|
||||
// the monitor plane's corners. The bottom pair may dip below ground; the 3D
|
||||
// tiles occlude that portion, exactly as they do for the plane itself.
|
||||
const minAlt = ground + FRUSTUM_GROUND_CLEARANCE_M;
|
||||
const capAltClamped = Math.max(minAlt, capAlt);
|
||||
// Ground clearance: ONE rigid lift for the whole rectangle, sized by the
|
||||
// support point with the largest deficit against the ground measured
|
||||
// under it (a 3×3 grid over the plane; see footprint.js). Points without
|
||||
// a measurement fall back to the ground at the mount, so with no
|
||||
// footprint data the bottom edge still clears the mount's ground — the
|
||||
// old center-only clamp left the bottom edge tens of metres underground
|
||||
// (owner field test 2026-09-13). The rectangle stays rigid and the
|
||||
// wireframe rays still terminate exactly on its corners.
|
||||
// Two lifts: what the mount's own ground demands (always honoured), plus
|
||||
// whatever the measured footprint adds, capped so a building under the
|
||||
// far edge cannot send the plane into the sky.
|
||||
const base = requiredPlaneLift(
|
||||
capAlt,
|
||||
dims,
|
||||
null,
|
||||
ground,
|
||||
FRUSTUM_GROUND_CLEARANCE_M,
|
||||
);
|
||||
const measured = requiredPlaneLift(
|
||||
capAlt,
|
||||
dims,
|
||||
groundUnderPlane,
|
||||
ground,
|
||||
FRUSTUM_GROUND_CLEARANCE_M,
|
||||
);
|
||||
const footprintExtraM = Math.max(0, measured.liftM - base.liftM);
|
||||
const liftM =
|
||||
base.liftM + Math.min(PLANE_FOOTPRINT_LIFT_CAP_M, footprintExtraM);
|
||||
const limitingKey =
|
||||
footprintExtraM > 0 ? measured.limitingKey : base.limitingKey;
|
||||
const capAltLifted = capAlt + liftM;
|
||||
const corner = (base, sign) => {
|
||||
const ll = parts.model.projectPoint(
|
||||
base.lat,
|
||||
base.lon,
|
||||
heading,
|
||||
sign * upHoriz,
|
||||
sign * dims.upHoriz,
|
||||
);
|
||||
return { lat: ll.lat, lon: ll.lon, alt: capAltClamped + sign * upVert };
|
||||
return {
|
||||
lat: ll.lat,
|
||||
lon: ll.lon,
|
||||
alt: capAltLifted + sign * dims.upVert,
|
||||
};
|
||||
};
|
||||
|
||||
const topCenter = corner(capLL, 1);
|
||||
return {
|
||||
rangeM: R,
|
||||
vFovDeg: Cesium.Math.toDegrees(vFovRad),
|
||||
halfW,
|
||||
halfH,
|
||||
vFovDeg: dims.vFovDeg,
|
||||
halfW: dims.halfW,
|
||||
halfH: dims.halfH,
|
||||
mount: { lat: camera.lat, lon: camera.lon, alt: mountAlt },
|
||||
capCenter: { lat: capLL.lat, lon: capLL.lon, alt: capAltClamped },
|
||||
capCenter: { lat: capLL.lat, lon: capLL.lon, alt: capAltLifted },
|
||||
corners: {
|
||||
tl: corner(capL, 1),
|
||||
tr: corner(capR, 1),
|
||||
@@ -128,6 +231,10 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
},
|
||||
topCenter,
|
||||
groundAltM: ground,
|
||||
liftM,
|
||||
liftLimitedBy: limitingKey,
|
||||
liftCapped: footprintExtraM > PLANE_FOOTPRINT_LIFT_CAP_M,
|
||||
footprintMeasured: !!groundUnderPlane,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -230,6 +337,7 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
record.camera,
|
||||
groundAltM,
|
||||
record.probeClampRangeM,
|
||||
footprintGroundFor(record),
|
||||
);
|
||||
const positions = frustumCartesians(geometry);
|
||||
record.frustumGeometry = geometry;
|
||||
@@ -327,6 +435,17 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Photoreal regime with shipped precompute samples for exactly this pose:
|
||||
// the mount ground and the footprint come from the sidecar, no scene
|
||||
// query and no shared-cell write (aircraft floors stay untouched).
|
||||
if (hasShippedFootprint(record)) {
|
||||
const shippedGround = record.camera.groundHeights.mountGroundM;
|
||||
record.groundSamples['google-3d'] = shippedGround;
|
||||
record.groundResolved['google-3d'] = true;
|
||||
applyFrustumGeometry(record, shippedGround);
|
||||
return;
|
||||
}
|
||||
|
||||
// Photoreal regime. Sampling is delegated to the shared coarse-cell
|
||||
// sampler. It remains event-driven, one-shot per cell, and keeps its
|
||||
// existing acceptance window; CCTV adds no rooftop rejection policy.
|
||||
@@ -696,6 +815,8 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
||||
}
|
||||
return {
|
||||
computeFrustumGeometry,
|
||||
hasShippedFootprint,
|
||||
footprintPose,
|
||||
activationProbeClampRange,
|
||||
frustumCartesians,
|
||||
frustumFrameEcef,
|
||||
|
||||
Reference in new issue
Block a user