Files
gods-eye-view/src/layers/cctv/headingConfidence.js
T
Sameh Khamis 0e90a03de7 Round every heading readout through one shared helper
The transit, ALPR, vessel, CCTV and flight/military context fixes each
wrapped 359.5° to north with an inline Math.round(x) % 360. They now call
roundHeadingDegrees in src/data/headingDegrees.js, which also wraps negative
and multi-turn bearings. normalizeHeading moves there with it, and
cockpitMath re-exports it so existing imports keep working. It now adds a
turn only to a negative remainder, so a bearing just under half a degree
keeps its precision.

The awareness, Local ADS-B, cockpit wind, cockpit heading and Street Level
readouts use the same helper. The Cockpit's TRACK ACQUIRED signal rounded
without wrapping and could read COURSE 360°; it now uses
formatHeadingReadout, which shows --- for a track that is not finite.
2026-11-01 21:41:49 -08:00

53 lines
2.0 KiB
JavaScript

import { t } from '../../i18n/index.js';
import { roundHeadingDegrees } from '../../data/headingDegrees.js';
/**
* @module headingConfidence
*
* Consumers for the catalog's per-camera `headingConfidence` flag (#639).
*
* Packs whose feed publishes no facing get a synthetic bearing from
* `fallbackHeadingFromId` — a hash of the id string, with no geographic
* input — and are marked `headingConfidence: 'low'`. Until now nothing read
* the flag, so a guessed bearing rendered exactly like a surveyed one. These
* helpers make the guess visible: the HUD heading token gains an
* `(ESTIMATED)` tag and the coverage wireframe draws dashed instead of solid.
*
* A human-vouched pose always wins over the pack flag: a manually saved
* calibration (`calSource:'manual'`) or a hand-authored catalog entry
* (`poseSource:'curated'`) is never presented as estimated, mirroring the
* CAL badge states in calibration.js.
*/
/**
* Whether a camera's bearing is a synthetic guess that should present as
* provisional.
* @param {{headingConfidence?: string|null, calSource?: string|null,
* poseSource?: string|null}} camera - Catalog camera (or public state).
* @returns {boolean}
*/
export function isHeadingEstimated(camera) {
if (!camera) return false;
if (camera.calSource === 'manual') return false;
if (camera.poseSource === 'curated') return false;
return (
String(camera.headingConfidence || '')
.trim()
.toLowerCase() === 'low'
);
}
/**
* HUD heading token: `HDG 194°`, tagged `(ESTIMATED)` when the bearing is
* synthetic — so a hash can never read as a surveyed facing.
* @param {{headingDeg?: number, headingConfidence?: string|null,
* calSource?: string|null, poseSource?: string|null}} camera
* @returns {string}
*/
export function headingHudToken(camera) {
const hdg = roundHeadingDegrees(Number(camera?.headingDeg)) ?? 0;
return isHeadingEstimated(camera)
? t('HDG {value}° (ESTIMATED)', { value: hdg })
: t('HDG {value}°', { value: hdg });
}