feat(cctv): enrich identified cameras with datasheet FOV + specs

When a CCTV source pack (or a seed) names its hardware `model`, resolve it
against a camera-model spec table: the coverage frustum then uses the
manufacturer's horizontal FOV instead of the pose estimate, and the HUD meta
line gains the model's resolution, night-vision, and PTZ specs. Cameras with
no identified model are unchanged — unknown never implies anything, matching
the layer's existing honesty about RAW PRIOR poses.

Spec records are vendored verbatim from the CCTV Camera Database
(https://github.com/ch-bas/cctv-camera-database, CC0-1.0), an open dataset of
12,000+ camera models transcribed from manufacturer datasheets. This lands a
small starter table plus the join + parsing helpers; source packs that publish
their hardware can grow it. Records describe camera products (optics,
resolution, night vision) only — no endpoints or access data.

- src/layers/cctv/modelSpecs.js: spec table, case-insensitive/alias lookup,
  wide-end FOV parser, HUD summary formatter
- src/layers/cctv/modelSpecs.test.mjs: lookup/alias/unknown, FOV parsing
  across the real fov string shapes, summary rendering
- src/layers/cctv/catalog.js: apply enrichment in both catalog builders
- src/layers/cctv/presentation.js: label a datasheet FOV, append the spec
  summary, and pass fovSource + spec through the public camera state
This commit is contained in:
bassem chagra committed 2026-09-25 12:36:34 +02:00
1 parent b210ab0fe4
commit ccdf8f0dc2
4 files changed
+229 -1

No files matched your search

+10 -1
View File
@@ -1,6 +1,7 @@
import { CCTV_AMBIENT_CARD_MAX } from '../../data/cctvLod.js';
import { ACTIVE_FRAME_REFRESH_MS, IDLE_FRAME_REFRESH_MS } from './policy.js';
import { headingHudToken, isHeadingEstimated } from './headingConfidence.js';
import { specSummary } from './modelSpecs.js';
export function createPresentation({
state: layerState,
@@ -39,7 +40,8 @@ export function createPresentation({
// A synthetic bearing (headingConfidence 'low', no human calibration)
// is tagged so a hashed guess never reads as a surveyed facing (#639).
headingHudToken(active.camera),
`FOV ${Math.round(active.camera.fovDeg)}°`,
// Identified hardware labels its FOV as datasheet-sourced (catalog.js).
`FOV ${Math.round(active.camera.fovDeg)}°${active.camera.fovSource === 'datasheet' ? ' (DATASHEET)' : ''}`,
`COVERAGE ${area.toFixed(2)}km²`,
overlapCount > 0 ? `OVERLAP ${overlapCount} cams` : 'ISOLATED VIEW',
`PROJ ${layerState._showProjection ? 'MONITOR' : 'OFF'}`,
@@ -49,6 +51,10 @@ export function createPresentation({
? `SRC ${String(health.sourceKind).toUpperCase()}`
: `SRC ${String(active.camera.feedType || 'image').toUpperCase()}`,
`${viewBand.toUpperCase()} CONTEXT`,
// Identified hardware: append the model's datasheet specs + attribution.
active.camera.spec
? `${specSummary(active.camera.spec).toUpperCase()} · SPECS: CCTV-DATABASE.COM`
: null,
]
.filter(Boolean)
.join(' · ');
@@ -86,6 +92,9 @@ export function createPresentation({
headingEstimated: isHeadingEstimated(camera),
pitchDeg: camera.pitchDeg,
fovDeg: camera.fovDeg,
// Identified-hardware enrichment passthrough (catalog.js / modelSpecs.js).
fovSource: camera.fovSource || 'estimated',
spec: camera.spec || null,
rangeM: camera.rangeM,
elevationM: camera.absoluteHeightM,
mountHeightM: camera.mountHeightM,