Files
gods-eye-view/src/layers/flights/queries.js
T

994 lines
40 KiB
JavaScript

import { approxDistanceKm as _approxDistanceKm } from './recordPolicy.js';
import { displayedKinematics } from '../../data/motionModel.js';
import * as Cesium from 'cesium';
import { isExplicitLayerStateOrigin } from '../../data/layerState.js';
import { modelVisualAnchor } from '../../data/modelVisualAnchor.js';
import {
rankContactMatch,
contactMatchWins,
CONTACT_MATCH_TIER,
} from '../../data/contactMatch.js';
import { aircraftIncludedInNearby } from '../../data/aircraftNearbyPolicy.js';
import {
LANDED_ALT_MAX_M,
LANDED_SPEED_MAX_MPS,
FOCUS_EVIDENCE_DEV,
FLEET_DR_INTERVAL_MS,
} from './policy.js';
import { emergencyBroadcastPhrase } from '../../data/aircraftEmergency.js';
export function createQueries({
flightState,
services,
parts,
layer,
resolveAsset,
}) {
const { tr3bTypeLabel, tr3bAircraftClass } = services.aircraftPresentation;
const { formatFlightLevel } = services.labels;
const { isMilitaryIcao } = services.militaryRegistry;
const { applyTrackedCameraFrame } = services.camera;
const {
setFocusDeemphasisParams,
getFocusDeemphasisParams,
setFocusEvidenceNowMs,
advanceFocusEvidenceNowMs,
} = services.focus;
const { setAircraftRecessionParams, getAircraftRecessionParams } =
services.recession;
/**
* Cheap equirectangular distance (km) — plenty accurate for the ~150 km
* ground-floor clamp gate; runs once per contact per poll, so no trig-heavy
* haversine needed.
* @param {number} lat1 @param {number} lon1 @param {number} lat2 @param {number} lon2
* @returns {number} Approximate great-circle distance in km.
*/
/**
* True when the aircraft's latest metadata reads "on or about the runway"
* (low + slow) — see the landed fast-cull rationale above. Both gates must
* hold, so a plane missing either datum keeps the normal grace.
* @param {string} icao24 - ICAO 24-bit transponder address.
* @returns {boolean}
*/
function _likelyLanded(icao24) {
const info = flightState.records.data.get(icao24);
if (!info) return false;
// Round 7 (owner: "fewer planes than OpenSky's own map" + "parked planes
// never heal"): the fast cull only applies to contacts that were AIRBORNE
// this session — its original target, the post-LANDING ghost. OpenSky's
// ground coverage flaps constantly, so fast-culling every grounded contact
// put parked planes in an evict/re-enter churn: each re-entry was a
// brand-new contact with cold floor state (geoid-height first poll), so
// the apron looked half-empty AND perpetually sunken. First-seen-grounded
// contacts now ride the normal MISSING_POLL_LIMIT grace and keep their
// identity (and warmed floors) across feed flaps.
if (info.wasAirborne !== true) return false;
// Was airborne, then grounded, then VANISHED from the poll — landed ghost.
if (info.onGround) return true;
return (
Number.isFinite(info.altitude) &&
info.altitude < LANDED_ALT_MAX_M &&
Number.isFinite(info.velocity) &&
info.velocity < LANDED_SPEED_MAX_MPS
);
}
/**
* Normalize a value to a trimmed string. A whitespace-only field (" ") is
* truthy, so every label chain must trim FIRST and then fall through.
* @param {*} value - Any value (typically a metadata string or null).
* @returns {string} Trimmed string, or '' if falsy.
*/
function _toCleanText(value) {
return String(value || '').trim();
}
/**
* The layer's ONE label convention: spoken callsign → tail registration → raw
* ICAO hex. Mirrors militaryFlights.js so the same aircraft reads identically
* in both layers.
*
* Registration is aircraft IDENTITY, not route, so unlike the origin/destination
* line it is NOT plausibility-gated — an adsbdb tail number describes the
* airframe itself and cannot go stale the way a leg can.
*
* This is a DISPLAY string only. Identity everywhere in this layer is `icao24`
* (the `_billboards`/`_flightData` key, the `sourceId` detection declutter hashes,
* the `id` that trackById/Context cohorts resolve) — never the label.
* @param {string} icao24 - ICAO 24-bit transponder address (the identity key).
* @param {Object|null|undefined} info - `_flightData` record for this aircraft.
* @returns {string} Display label; never empty.
*/
function _contactLabel(icao24, info) {
return (
_toCleanText(info?.callsign) || _toCleanText(info?.registration) || icao24
);
}
/**
* Build a public descriptor for one aircraft using its best current position
* (dead-reckoned when history exists, billboard position otherwise).
* @param {string} icao24 - ICAO 24-bit transponder address.
* @returns {{icao24: string, callsign: string|null, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null, verticalRateMps: number|null}|null}
* Descriptor with a cloned position, or null if the aircraft is unknown.
*/
function _describeFlight(icao24) {
// DATA, NOT PIXELS — deliberately UNFLOORED (2026-08-19). The display floor
// lifts grounded contacts onto the visible mesh so the sprite you see is not
// buried; that is a rendering correction, not a measurement. This descriptor
// feeds query/analyst/subject APIs — `findByQuery` (voice track-by-name),
// `getTrackedInfo` (cockpit + readout altitude), `getTrackedSubject`
// (proximity counts and distances) — where the honest answer is what the
// aircraft REPORTED, not where its icon was nudged to avoid clipping tiles.
// `altitudeM` is therefore the barometric/aviation value and `renderAltitudeM`
// the fix-time datum, neither of them the floored display height. No
// user-visible surface renders `position` as the plane's on-screen location —
// every visual consumer reads the floored per-frame cache instead (see
// `_trackedDisplayPosition`). If that ever changes, floor this path too.
const info = flightState.records.data.get(icao24);
const bb = flightState._billboards.get(icao24);
const basePos =
parts.motion._deadReckon(icao24) || (bb ? bb.position : null);
if (!basePos) return null;
const displayed = displayedKinematics({
derivedSpeedMps: flightState._drSpeedMps,
derivedTrackDeg: flightState._drCourseDeg,
reportedSpeedMps: info?.velocity,
reportedTrackDeg: info?.true_track,
});
const carto = Cesium.Cartographic.fromCartesian(
basePos,
Cesium.Ellipsoid.WGS84,
flightState._scratchCarto,
);
if (!carto) return null;
return {
icao24,
callsign: String(info?.callsign || '').trim() || null,
position: Cesium.Cartesian3.clone(basePos),
latitude: Cesium.Math.toDegrees(carto.latitude),
longitude: Cesium.Math.toDegrees(carto.longitude),
// The cockpit instrument reports aviation altitude, not the Cesium
// ellipsoid height of the camera/ground-clamped render position. The
// latter can be slightly negative over terrain near the surface.
altitudeM: Number.isFinite(info?.altitude) ? info.altitude : carto.height,
renderAltitudeM: Number.isFinite(info?.renderAltitudeM)
? info.renderAltitudeM
: carto.height,
onGround: info?.onGround === true,
velocityMps: displayed.speedMps,
track: displayed.trackDeg,
// Climb/descent rate, the one kinematic field `mapAnalystRecord` already
// published (as `verticalRateMps`) but this descriptor did not, so a
// consumer reading the tracked seam had to reach back into `_flightData`
// for it. Reported, never derived: `stickyNumber` in records.js keeps the
// last value across a blank poll, and an absent rate stays null rather
// than becoming a level-flight 0 the aircraft never transmitted.
verticalRateMps: Number.isFinite(info?.verticalRate)
? info.verticalRate
: null,
stale: Boolean(
flightState.records.missingPolls.get(icao24) ||
flightState.feed._backoff,
),
// This poll's transponder broadcast (not sticky; see records.js).
squawk: info?.squawk ?? null,
emergency: info?.emergency ? { ...info.emergency } : null,
emergencyBroadcast: emergencyBroadcastPhrase(info?.emergency),
airline: info?.airline ?? null,
// CLASS label follows the TR-3B conversion so every downstream card
// (cockpit, Contacts, analyst) agrees with the triangle on screen.
typeName: tr3bTypeLabel(icao24, info?.typeName ?? null),
typeCode: tr3bTypeLabel(icao24, info?.typeCode ?? null),
// IDENTITY, deliberately NOT converted: registration is the airframe's tail
// number and feeds `_contactLabel`'s callsign → registration → hex chain, so
// a converted contact keeps the label convention every other contact uses.
// Trimmed like `callsign` above so every consumer (cockpit readout, voice
// narration, getTrackedSubject) can use it as a label link without
// re-guarding a whitespace-only enrichment value.
registration: _toCleanText(info?.registration) || null,
origin:
info?.route && parts.tracking._routeIsPlausible(icao24, info.route)
? info.route.origin.code
: null,
destination:
info?.route && parts.tracking._routeIsPlausible(icao24, info.route)
? info.route.destination.code
: null,
route:
info?.route && parts.tracking._routeIsPlausible(icao24, info.route)
? {
origin: { ...info.route.origin },
destination: { ...info.route.destination },
}
: null,
};
}
function _normalizeTrackedIcao(candidate) {
const normalized = String(candidate ?? '')
.trim()
.toLowerCase();
return normalized || null;
}
/**
* Map one aircraft's internal poll record to a plain JSON-safe analyst
* record (analyst query engine seam). Pure — no Cesium types, no fetches;
* enrichment fields read the CACHED adsbdb values only. Missing/unknown
* fields are null, never NaN/undefined. The route-plausibility verdict is
* computed by the CALLER (it needs the billboard position) and passed in,
* so an implausible cached route is never surfaced as fact.
* @param {string} icao24 - ICAO 24-bit transponder address.
* @param {Object|null|undefined} info - `_flightData` record for this aircraft.
* @param {{military?: boolean, routeOk?: boolean}} [flags] - Shared-registry
* military flag + route-plausibility verdict.
* @returns {{id: string, icao24: string, callsign: string|null, lat: number|null,
* lon: number|null, altitudeM: number|null, speedMps: number|null,
* heading: number|null, verticalRateMps: number|null, onGround: boolean,
* military: boolean, aircraftClass: string|null, originCountry: string|null,
* operator: string|null, squawk: string|null, emergency: boolean,
* emergencyKind: string|null, emergencySource: 'squawk'|'ads-b'|null,
* emergencyBroadcast: string|null, lastContactAtMs: number|null, routeOrigin: string|null,
* routeDestination: string|null}}
*/
function mapAnalystRecord(
icao24,
info,
{ military = false, routeOk = false } = {},
) {
const num = (v) => (Number.isFinite(v) ? v : null);
const text = (v) => {
const t = String(v ?? '').trim();
return t || null;
};
const callsign = text(info?.callsign);
return {
// Display identity for the narration layer. `id` is NOT a queryable field
// (see ANALYST_LAYERS) and follow-ups carry whole records, so this is a
// label, not a key — the engine keys on `icao24` below.
id: callsign || text(info?.registration) || icao24,
icao24,
callsign,
lat: num(info?.rawLat),
lon: num(info?.rawLon),
altitudeM: num(info?.altitude), // barometric/MSL — the aviation field, not the render height
speedMps: num(info?.velocity),
heading: num(info?.true_track),
verticalRateMps: num(info?.verticalRate),
onGround: info?.onGround === true,
military,
// A converted contact reports the class it RENDERS as, so an analyst
// filter/superlative agrees with the triangle on screen.
aircraftClass: tr3bAircraftClass(icao24, text(info?.klass)),
originCountry: text(info?.originCountry),
operator: text(info?.airline),
squawk: text(info?.squawk),
// True while this poll broadcast an emergency (7500/7600/7700 or the
// ADS-B status); priority statuses (lifeguard, minfuel) report the kind
// without the flag.
emergency: info?.emergency?.severity === 'emergency',
emergencyKind: text(info?.emergency?.kind),
// Provenance, so a summary can say "broadcasting squawk 7700" rather
// than "ADS-B reports general emergency" (or flatten both): 'squawk'
// when the Mode A code names the status, 'ads-b' for the readsb field.
emergencySource: text(info?.emergency?.source),
emergencyBroadcast: emergencyBroadcastPhrase(info?.emergency),
// Latest known general aircraft-message contact, retained if a poll
// has no time. This is not the receipt time of the current status.
lastContactAtMs: num(info?.lastContactEpochMs),
routeOrigin: routeOk ? text(info?.route?.origin?.code) : null,
routeDestination: routeOk ? text(info?.route?.destination?.code) : null,
};
}
const methods = {
mapAnalystRecord,
id: 'flights',
name: 'Live Flights',
icon: '✈️',
source: flightState.feed._lastSource,
// Browser-harness seam: isolates synthetic display-floor scenarios without
// changing any production lifecycle or cache policy.
_clearDisplayFloorStateForTest:
parts.testing._clearDisplayFloorStateForTest,
/** @type {number} Polling interval (ms) between update() calls */
updateInterval: 30000,
/**
* Live layer params.
* `models3d` toggles 3D glTF model rendering for the FLEET (altitude-gated): when on,
* surrounding aircraft become 3D models once the camera is zoomed in past MODEL_ALT_CEIL_M.
* The TRACKED contact is NOT gated by this — it takes its 3D model by camera distance
* regardless (see `_trackedModelRegimeActive` / trackedModelRegime.js).
* `models3dMode` is 'proximity' (nearest MODEL_MAX in view) or 'all' (every in-view plane).
* @param {{models3d?: boolean, models3dMode?: 'proximity'|'all', selectedFlightsTrackingId?: string|null}} params
*/
setParams(params = {}, { origin = 'programmatic' } = {}) {
if (
isExplicitLayerStateOrigin(origin) &&
!Object.hasOwn(params, 'selectedFlightsTrackingId')
) {
parts.tracking._cancelPendingTrackingRestore();
}
if (
typeof params.models3d === 'boolean' &&
params.models3d !== flightState._models3dEnabled
) {
flightState._models3dEnabled = params.models3d;
if (!flightState._models3dEnabled) {
parts.rendering._releaseModels();
parts.tracking._syncTracked2dRotation();
// Restore fleet billboards (the horizon-cull pass re-asserts next tick), but NEVER the
// tracked plane's own fleet billboard — its tracked entity is the visual, so re-showing it
// here would double-image the tracked plane when 3D is turned off mid-track.
if (flightState._billboardCollection)
for (const [icao, bb] of flightState._billboards) {
if (icao !== flightState._trackedIcao) bb.show = true;
}
}
}
if (
(params.models3dMode === 'proximity' ||
params.models3dMode === 'all') &&
params.models3dMode !== flightState._models3dMode
) {
// The next fleet tick re-derives the eligible set under the new cap and releases the overflow.
flightState._models3dMode = params.models3dMode;
if (flightState._cockpitContactMode) {
parts.tracking._refreshCockpitNearContacts();
flightState._lastFleetTickMs = 0;
}
}
if (
typeof params.irBoost === 'boolean' &&
params.irBoost !== flightState._irBoost
) {
flightState._irBoost = params.irBoost;
parts.rendering._reloadModelsForIrBoost();
// Sprites don't reload with the models — swap the TR-3B glyph between its
// cold and thermal-reactive variants directly. Bounded by the operator's
// own conversions, so this never touches the ordinary fleet.
parts.tracking._refreshTr3bForStyle();
}
if (Object.hasOwn(params, 'selectedFlightsTrackingId')) {
const requested = _normalizeTrackedIcao(
params.selectedFlightsTrackingId,
);
if (requested === flightState._trackedIcao) {
flightState._pendingTrackingRestore = null;
} else if (requested === null) {
parts.tracking._cancelPendingTrackingRestore();
if (flightState._trackedIcao)
parts.tracking._clearTracking(false, { origin });
} else {
const generation = ++flightState._trackingIntentGeneration;
flightState._pendingTrackingRestore = {
id: requested,
generation,
origin,
};
if (flightState._trackedIcao)
parts.tracking._clearTracking(false, { origin });
parts.tracking._applyPendingTrackingRestore();
}
}
return true;
},
getParams() {
return {
models3d: flightState._models3dEnabled,
models3dMode: flightState._models3dMode,
irBoost: flightState._irBoost,
selectedFlightsTrackingId: flightState._trackedIcao,
};
},
/**
* Re-render a contact whose TR-3B conversion just flipped (Easter egg).
* Callers own the registry write; this only re-derives what renders.
* @param {string} icao24 - ICAO 24-bit address.
* @returns {boolean} True when this layer owns the contact.
*/
refreshTr3b(icao24) {
return parts.tracking._refreshTr3bContact(icao24);
},
/**
* Return a subsample of currently visible aircraft for detection overlay
* rendering (e.g. bounding boxes drawn on-screen by the CCTV detection layer).
*
* Uses a deterministic stride + seed to select a spatially distributed
* subset without sorting or shuffling.
*
* @param {object} [options]
* @param {number} [options.maxCount] - Maximum number of objects to return.
* @param {number} [options.seed] - Deterministic offset into the stride pattern.
* @returns {Array<{position: Cesium.Cartesian3, id: string, type: string, skipLabel: boolean}>}
*/
getDetectableObjects(options = {}) {
if (
!flightState._billboardCollection ||
!flightState._billboardCollection.show
)
return [];
// Compute a stride that evenly samples the billboard map.
// seed shifts the starting offset so successive calls can sample
// different aircraft without shuffling the underlying Map order.
const maxCount = Number.isFinite(options.maxCount)
? Math.max(1, Math.floor(options.maxCount))
: flightState._billboards.size;
const seed = Number.isFinite(options.seed) ? Math.floor(options.seed) : 0;
const stride = Math.max(
1,
Math.ceil(flightState._billboards.size / maxCount),
);
const start = seed % stride;
const result = [];
let idx = 0;
for (const [icao24, bb] of flightState._billboards) {
const shouldTake = (idx - start) % stride === 0;
idx++;
if (!shouldTake) continue;
if (
flightState._cockpitContactMode &&
icao24.toLowerCase() === flightState._cockpitSubjectId
)
continue;
const isTracked = icao24 === flightState._trackedIcao;
// Keep planes rendered as a 3D model (billboard hidden) so the detection box
// doesn't vanish on the 2D→3D handoff; bb.position stays current while hidden.
const model = flightState._models.get(icao24) ?? null;
const modelOwnsVisual = parts.rendering._modelOwnsVisual(icao24, model);
if (!isTracked && !bb.show && !modelOwnsVisual) continue;
const info = flightState.records.data.get(icao24);
let object = flightState._detectionObjects.get(icao24);
if (!object) {
object = {
sourceId: icao24,
type: 'AIR',
_weldPos: new Cesium.Cartesian3(),
};
flightState._detectionObjects.set(icao24, object);
}
// WELD: anchor to whatever actually owns the visual. A model-owned contact is
// read straight off the translation the fleet tick already wrote into its
// modelMatrix, so bracket and label sit on the aircraft you can see instead of
// on the buried billboard position, which for a grounded plane is ~100 m below
// and rises only as the coarse ground-floor cell warms. Zero extra sampling and
// no `_modelDisplayPosition` call from postRender. Sprite-owned contacts keep
// `bb.position` — sprite and bracket are co-located there, so association holds.
const spec = modelOwnsVisual
? parts.rendering._modelSpec(info?.klass)
: null;
const pos = isTracked
? parts.motion._trackedVisualCached() || bb.position
: modelOwnsVisual
? modelVisualAnchor(
model.modelMatrix,
spec.visualCenterNative,
Number.isFinite(model.computedScale)
? model.computedScale
: spec.scale,
object._weldPos || (object._weldPos = new Cesium.Cartesian3()),
)
: bb.position;
if (!pos) continue;
object.position = pos;
object.skipLabel = isTracked;
// Card text only — declutter/cohort identity is `object.sourceId` (icao24).
const callsign = info?.callsign;
const registration = info?.registration;
// Records and enrichment may change in place. Cache by field values,
// while keeping the live visual anchor above independent of text.
if (
object.id === undefined ||
object._labelCallsign !== callsign ||
object._labelRegistration !== registration
) {
object.id = _contactLabel(icao24, info);
object._labelCallsign = callsign;
object._labelRegistration = registration;
}
const altitude = info?.altitude;
if (object._altitude !== altitude) {
object._altitude = altitude;
object.metric = formatFlightLevel(altitude); // altitude is metres
}
result.push(object);
if (result.length >= maxCount) break;
}
if (
flightState._detectionObjects.size >
flightState._billboards.size + 512
) {
for (const icao24 of flightState._detectionObjects.keys()) {
if (!flightState._billboards.has(icao24))
flightState._detectionObjects.delete(icao24);
}
}
return result;
},
/**
* Find a single aircraft by free-text query.
* Match priority: exact icao24 hex, exact callsign, callsign prefix,
* then callsign substring (all case-insensitive, trimmed).
* @param {string} query - ICAO24 hex or full/partial callsign.
* @returns {{icao24: string, callsign: string|null, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null}|null}
* Best match with a cloned, dead-reckoned position, or null if none.
*/
findByQuery(query) {
if (!flightState.records.data || flightState.records.data.size === 0)
return null;
const q = String(query || '')
.trim()
.toLowerCase();
if (!q) return null;
// Registration is searched alongside callsign because it is what the
// operator SEES: a callsign-less contact reads as its tail number on the
// card, in the analyst's answer, and in the Contacts list. Matching only
// callsigns meant "follow 6606" — and the analyst → track_entity handoff
// the tool instructions prescribe — answered "nothing matched" for the
// very identity the app had just shown. Ranking is shared with the
// military layer (contactMatch.js) so the two cannot disagree, and it is
// strictly tiered so a registration can never out-rank a real callsign on
// feed order alone.
let best = null;
for (const [icao24, info] of flightState.records.data) {
const candidate = {
tier: rankContactMatch({
query: q,
hex: icao24,
callsign: info?.callsign,
registration: info?.registration,
}),
id: icao24,
};
if (!contactMatchWins(candidate, best)) continue;
best = candidate;
if (candidate.tier === CONTACT_MATCH_TIER.HEX_EXACT) break;
}
return best ? _describeFlight(best.id) : null;
},
/**
* Find aircraft near a given ECEF position, sorted ascending by distance.
* Return shape mirrors militaryFlightsLayer.getNearby (id/icao24/position/distance).
* @param {Cesium.Cartesian3} center - Reference position in ECEF coordinates.
* @param {number} range - Maximum distance in meters (Infinity if not finite).
* @param {number} [maxCount=50] - Maximum number of results to return.
* @param {object} [options] Query membership options.
* @param {boolean} [options.includeHidden=false] Include loaded horizon-hidden aircraft.
* @returns {Array<{id: string, icao24: string, callsign: string|null, position: Cesium.Cartesian3, distance: number, aircraftClass: string|null, altitudeM: number|null, velocityMps: number|null, track: number|null}>}
*/
getNearby(center, range, maxCount = 50, { includeHidden = false } = {}) {
if (
!center ||
!flightState._billboardCollection ||
!flightState._billboardCollection.show
)
return [];
const limit = Number.isFinite(maxCount)
? Math.max(1, Math.floor(maxCount))
: 50;
const maxRange =
Number.isFinite(range) && range > 0 ? range : Number.POSITIVE_INFINITY;
const nearby = [];
for (const [icao24, bb] of flightState._billboards) {
const isTracked = icao24 === flightState._trackedIcao;
// Keep planes rendered as a 3D model (billboard hidden) so proximity counts
// don't drop to zero on the 2D→3D handoff; bb.position stays current while hidden.
if (
!aircraftIncludedInNearby({
isTracked,
billboardShown: bb.show,
modelRendering: parts.rendering._modelOwnsVisual(icao24),
includeHidden,
})
)
continue;
const trackedPos = isTracked
? parts.motion._trackedDisplayCached()
: null; // cached, no recompute (anti-jitter)
const pos = trackedPos || bb.position;
if (!pos) continue;
const distance = Cesium.Cartesian3.distance(center, pos);
if (distance > maxRange) continue;
const info = flightState.records.data.get(icao24);
const callsign = info?.callsign?.trim() || null;
nearby.push({
// Label. Callers that need identity read `icao24` (Context cohorts do).
id: _contactLabel(icao24, info),
icao24,
callsign,
position: pos,
distance,
// Filter surface: the cockpit next/previous path matches on THIS field
// (militaryAwareness.aircraftClassMatchesFilter), so a converted contact
// has to report the class it renders as or a `tr3b` filter skips it.
aircraftClass: tr3bAircraftClass(
icao24,
String(info?.klass || '')
.trim()
.toLowerCase() || null,
),
altitudeM: info?.altitude ?? null,
velocityMps: info?.velocity ?? null,
track: info?.true_track ?? null,
});
}
nearby.sort((a, b) => a.distance - b.distance);
return nearby.slice(0, limit);
},
/**
* Return id/label/position for up to maxCount currently rendered aircraft.
* Cheap snapshot for voice-tool framing — billboard positions only, no
* dead reckoning and no cloning.
* @param {number} [maxCount=500] - Maximum number of entries to return.
* @returns {Array<{id: string, label: string, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number}>}
*/
/**
* Whether this layer still carries a contact, in O(1).
*
* Presence consumers must not infer absence from `getAllPositions`: it stops
* at its cap, and this layer routinely carries ~11k contacts against a
* 1,000-row cap, so "not in the returned rows" is not "gone". Id matching
* mirrors trackById: exact key first, then lowercase.
* A disabled layer keeps its records but hides the collection, so it must
* decline rather than answer from data the user can no longer see —
* otherwise a preserved subject reads as fresh off stale hidden state.
* @param {string} icao24 Contact identifier.
* @returns {boolean|null} Presence, or null when the layer is disabled or
* holds no data and therefore cannot answer.
*/
hasContact(icao24) {
if (
!flightState._billboardCollection ||
!flightState._billboardCollection.show ||
flightState._billboards.size === 0
)
return null;
if (!icao24) return false;
const id = String(icao24).trim();
return (
flightState._billboards.has(id) ||
flightState._billboards.has(id.toLowerCase())
);
},
getAllPositions(maxCount = 500) {
if (
!flightState._billboardCollection ||
flightState._billboards.size === 0
)
return [];
const limit = Number.isFinite(maxCount)
? Math.max(1, Math.floor(maxCount))
: 500;
const result = [];
for (const [icao24, bb] of flightState._billboards) {
const pos = bb.position;
if (!pos) continue;
const carto = Cesium.Cartographic.fromCartesian(
pos,
Cesium.Ellipsoid.WGS84,
flightState._scratchCarto,
);
if (!carto) continue;
const info = flightState.records.data.get(icao24);
result.push({
id: icao24, // identity (trackById resolves this)
label: _contactLabel(icao24, info),
position: pos,
latitude: Cesium.Math.toDegrees(carto.latitude),
longitude: Cesium.Math.toDegrees(carto.longitude),
altitudeM: carto.height,
airline: info?.airline ?? null,
typeName: info?.typeName ?? null,
typeCode: info?.typeCode ?? null,
registration: info?.registration ?? null,
origin:
info?.route && parts.tracking._routeIsPlausible(icao24, info.route)
? info.route.origin.code
: null,
destination:
info?.route && parts.tracking._routeIsPlausible(icao24, info.route)
? info.route.destination.code
: null,
});
if (result.length >= limit) break;
}
return result;
},
/**
* Snapshot the layer's in-memory records as plain JSON-safe objects for
* the analyst query engine. On-demand only (called at most once per
* spoken query) — zero per-frame cost, no listeners, no caching, no
* enrichment fetches (cached adsbdb values only). Returns [] while the
* layer is disabled or empty.
* @param {number} [maxCount=2000] - Maximum records to return (truncation).
* @returns {Array<Object>} See mapAnalystRecord for the record shape.
*/
getAnalystRecords(maxCount = 2000) {
if (
!flightState._billboardCollection ||
!flightState._billboardCollection.show ||
flightState.records.data.size === 0
)
return [];
const limit = Number.isFinite(maxCount)
? Math.max(1, Math.floor(maxCount))
: 2000;
const result = [];
for (const [icao24, info] of flightState.records.data) {
const routeOk =
!!info?.route && parts.tracking._routeIsPlausible(icao24, info.route);
result.push(
mapAnalystRecord(icao24, info, {
military: isMilitaryIcao(icao24),
routeOk,
}),
);
if (result.length >= limit) break;
}
return result;
},
/**
* Start camera-tracking an aircraft by ICAO24 address.
* @param {string} icao24 - ICAO 24-bit transponder address.
* @returns {boolean} True if the aircraft exists and tracking started.
*/
trackById(icao24, { origin = 'programmatic' } = {}) {
if (!icao24) return false;
let id = String(icao24).trim();
if (!flightState._billboards.has(id)) id = id.toLowerCase();
if (!flightState._billboards.has(id)) return false;
if (parts.tracking._isExplicitTrackingOrigin(origin))
parts.tracking._cancelPendingTrackingRestore();
if (flightState._trackedIcao === id)
return parts.tracking._publishTrackedSelection(id, origin);
parts.tracking._trackFlight(id, { origin });
return true;
},
/** Resolve a shared Follow target only against the latest accepted refresh. */
async resolveTrackingRestoreTarget(
icao24,
{ signal = null, origin = 'share-restore' } = {},
) {
if (signal?.aborted)
return {
status: 'cancelled',
reason: String(signal.reason || 'aborted'),
};
const id = _normalizeTrackedIcao(icao24);
if (!id) return { status: 'missing', reason: 'invalid-target' };
const outcome = flightState.feed._lastTrackingRefreshOutcome;
if (outcome.status !== 'accepted') {
return {
status: 'source-unavailable',
reason: 'OpenSky snapshot unavailable',
refreshEpoch: outcome.epoch,
source: outcome.source,
coverage: outcome.coverage,
};
}
if (!outcome.ids.has(id)) {
return {
status: 'missing',
reason: 'target-absent-from-snapshot',
refreshEpoch: outcome.epoch,
source: outcome.source,
coverage: outcome.coverage,
};
}
if (signal?.aborted)
return {
status: 'cancelled',
reason: String(signal.reason || 'aborted'),
};
const followed = this.trackById(id, { origin });
return followed
? {
status: 'found',
refreshEpoch: outcome.epoch,
source: outcome.source,
coverage: outcome.coverage,
}
: {
status: 'source-unavailable',
reason: 'target-not-renderable',
refreshEpoch: outcome.epoch,
};
},
/** Reapply the canonical follow frame without recreating the selected flight. */
refocusTrackedById(icao24, { origin = 'programmatic' } = {}) {
if (
!icao24 ||
flightState._cockpitContactMode ||
!flightState._viewer ||
!flightState._trackedEntity
)
return false;
let id = String(icao24).trim();
if (!flightState._billboards.has(id)) id = id.toLowerCase();
if (
id !== flightState._trackedIcao ||
!flightState._viewer.entities?.contains?.(flightState._trackedEntity) ||
flightState._viewer.trackedEntity !== flightState._trackedEntity
)
return false;
flightState._trackedCameraFrameStop?.();
flightState._viewer.camera.cancelFlight();
flightState._viewer.trackedEntity = flightState._trackedEntity;
flightState._trackedCameraFrameStop =
applyTrackedCameraFrame(
flightState._viewer,
flightState._trackedEntity,
flightState._trackedEntity.viewFrom,
) || null;
parts.tracking._publishTrackedSelection(id, origin);
return true;
},
/**
* Stop tracking the currently followed aircraft (no-op if none).
* @returns {boolean} Always true.
*/
stopTracking({ origin = 'programmatic' } = {}) {
parts.tracking._cancelPendingTrackingRestore();
parts.tracking._clearTracking(false, { origin });
return true;
},
cancelPendingTrackingRestore() {
parts.tracking._cancelPendingTrackingRestore();
},
/**
* Describe the currently tracked aircraft at its dead-reckoned position.
* @returns {{icao24: string, callsign: string|null, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null, verticalRateMps: number|null}|null}
* Tracked aircraft info, or null when nothing is tracked.
*/
getTrackedInfo() {
if (!flightState._trackedIcao) return null;
const described = _describeFlight(flightState._trackedIcao);
if (!described) return null;
const { position, ...rest } = described;
return rest;
},
/**
* Return the current aircraft as a Context subject without changing
* tracking or camera ownership.
* @returns {{layerId: string, id: string, label: string, position: Cesium.Cartesian3}|null}
* Detached subject descriptor, or null when no aircraft is tracked.
*/
getTrackedSubject() {
if (!flightState._trackedIcao) return null;
const described = _describeFlight(flightState._trackedIcao);
if (!described?.position) return null;
return {
layerId: 'flights',
id: described.icao24,
// Same label chain as getNearby/getDetectableObjects: a callsign-less
// contact reads as its registration, never as the raw ICAO hex.
label:
described.callsign ||
_toCleanText(described.registration) ||
described.icao24,
position: Cesium.Cartesian3.clone(described.position),
};
},
...(FOCUS_EVIDENCE_DEV
? {
__focusEvidence: Object.freeze({
setAircraft: parts.evidence._setFocusEvidenceAircraft,
moveAircraft: parts.evidence._moveFocusEvidenceAircraft,
snapshot: parts.evidence._focusEvidenceSnapshot,
setTuning({ focus = {}, horizon = {} } = {}) {
return {
focus: setFocusDeemphasisParams(focus),
horizon: setAircraftRecessionParams(horizon),
};
},
getTuning() {
return {
focus: { ...getFocusDeemphasisParams() },
horizon: { ...getAircraftRecessionParams() },
};
},
takeFrameClock(startMs = 1_000_000_000) {
if (!flightState._viewer || !Number.isFinite(startMs))
return { ok: false, nowMs: null };
flightState._viewer.useDefaultRenderLoop = false;
setFocusEvidenceNowMs(startMs);
flightState._lastFleetTickMs = startMs - FLEET_DR_INTERVAL_MS;
// Cross a browser task boundary, then close Cesium's pending-loop
// latch. Any already-queued callback still observes the false gate,
// while manual evidence renders can begin without waiting on VSYNC.
return new Promise((resolve) =>
setTimeout(() => {
if (flightState._viewer?._cesiumWidget)
flightState._viewer._cesiumWidget._renderLoopRunning = false;
resolve({ ok: true, nowMs: startMs });
}, 0),
);
},
advanceFrameClock(deltaMs = FLEET_DR_INTERVAL_MS) {
return advanceFocusEvidenceNowMs(deltaMs);
},
releaseFrameClock() {
setFocusEvidenceNowMs(null);
if (flightState._viewer)
flightState._viewer.useDefaultRenderLoop = true;
},
}),
}
: {}),
/**
* Return layer health/status for the HUD stats chip.
* @returns {{count: number, lastUpdate: number|null, stale: boolean, error: string|null, status: number|null, retryInSec: number}}
*/
getStats() {
const retryInSec = flightState.feed._retryAt
? Math.max(
0,
Math.ceil((flightState.feed._retryAt - Date.now()) / 1000),
)
: 0;
return {
count: flightState.feed._count,
lastUpdate: flightState.feed._lastUpdate,
stale: flightState.feed._backoff,
error: flightState.feed._lastError,
status: flightState.feed._lastStatus,
retryInSec,
source: flightState.feed._lastSource,
coverage: flightState.feed._lastCoverage,
};
},
};
return {
_approxDistanceKm,
_likelyLanded,
_toCleanText,
_contactLabel,
_describeFlight,
_normalizeTrackedIcao,
mapAnalystRecord,
methods,
};
}