mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Merge pull request #718 from daikaginza/fix/tracked-info-vertical-rate
feat(flights): publish verticalRateMps on the tracked-aircraft descriptor
This commit is contained in:
6 files changed
+125
-26
No files matched your search
+5
-21
@@ -1,26 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
- Local FM seek finds stations after wrapping around 87.5–108 MHz, covers both
|
||||
band edges, skips the starting station and rejects spectra from an earlier
|
||||
tune (Aditya, @Rohilalala, #1174).
|
||||
- Local RTL-SDR keeps USB reads queued in order and bounds pending decoder
|
||||
work. A stalled or overloaded decoder stops with an explicit error; stopping
|
||||
terminates its worker and rejects late audio. FM playback buffers short
|
||||
scheduling gaps, caps its audio queue at 256 ms, and reduces display updates
|
||||
and spectrum calculations while listening.
|
||||
|
||||
- Add Bogotá (Tembici, 250 stations) and Santiago de Chile (Bike Itaú, 200
|
||||
stations) to the GBFS bikeshare registry, the layer's first stations in
|
||||
Colombia and Chile. Both are PBSC feeds like the Brazilian Bike Itaú cities,
|
||||
so no proxy change is needed. Both feeds also list depot stations on other
|
||||
continents (São Paulo, Madrid) as in service; stations outside a city's load
|
||||
radius are now dropped instead of drawn as live docks.
|
||||
|
||||
- Add Barcelona's bike share to the GBFS registry: Bicing (543 stations, the
|
||||
city's PBSC system) and AMBici (237 e-bike stations across the metropolitan
|
||||
area, run by nextbike). Both are keyless GBFS 2.3 feeds listed in
|
||||
MobilityData's systems catalogue; AMBici's is CC0. `gbfs.nextbike.net` joins
|
||||
the proxy allowlist. These are the layer's first stations in Spain.
|
||||
- The tracked-aircraft descriptor now publishes `verticalRateMps`, on both the
|
||||
civil and military layers, so a consumer reading `getTrackedInfo()` no longer
|
||||
has to reach past it into the raw poll record for climb/descent. Reported,
|
||||
never derived: an aircraft that has not transmitted a rate reads as `null`
|
||||
rather than a level-flight `0`.
|
||||
|
||||
- Add nine Ontario transit feeds: TTC, MiWay, Hamilton HSR, Durham Region,
|
||||
YRT/Viva, Guelph, Burlington, Barrie and Kingston, each carrying its own
|
||||
|
||||
@@ -861,6 +861,13 @@ use the supplied asset resolver, including the preload path. Enrichment receives
|
||||
an abort signal and cannot update a later lifecycle after destruction. Existing
|
||||
camera, terrain floor, trail, selection and measured model-size policies remain.
|
||||
|
||||
The tracked-aircraft descriptor both flight layers hand to the cockpit carries
|
||||
`verticalRateMps` beside `velocityMps` and `track`. Civil records store the rate as
|
||||
`verticalRate` and military records as `verticalRateMps`; the query seam maps both to
|
||||
one name, so `resolveTrackedAircraftInfo` returns a single shape for either layer. The
|
||||
rate is reported, never derived: a blank poll keeps the last transmitted value and an
|
||||
absent rate stays null rather than reading as level flight.
|
||||
|
||||
## Transponder emergency highlighting
|
||||
|
||||
`src/data/aircraftEmergency.js` derives one status per poll from the Mode A
|
||||
|
||||
@@ -726,7 +726,15 @@ const LABEL_ICAO = 'ae1fa4';
|
||||
const LABEL_POSITION = Cesium.Cartesian3.fromDegrees(-97.71, 30.21, 10_668);
|
||||
|
||||
/** Seed one civil contact through the production refresh seam. */
|
||||
function seedLabelContact({ callsign, registration, tracked = false }) {
|
||||
function seedLabelContact({
|
||||
callsign,
|
||||
registration,
|
||||
tracked = false,
|
||||
// Per-test metadata overrides, spread last so a test can set a field to
|
||||
// `undefined` to model "the feed never sent it" — a default parameter
|
||||
// could not express that, it would just re-supply the default.
|
||||
meta: metaOverrides = {},
|
||||
}) {
|
||||
_setTrackedFlightRefreshStateForTest({
|
||||
icao24: LABEL_ICAO,
|
||||
entity: null,
|
||||
@@ -752,6 +760,7 @@ function seedLabelContact({ callsign, registration, tracked = false }) {
|
||||
turnRateDps: 0,
|
||||
rawLat: 30.21,
|
||||
rawLon: -97.71,
|
||||
...metaOverrides,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -825,6 +834,44 @@ test('civil label chain: the cockpit descriptor exposes a trimmed registration',
|
||||
assert.equal(flightsLayer.getTrackedInfo()?.registration, null);
|
||||
});
|
||||
|
||||
test('the tracked descriptor publishes the reported vertical rate', () => {
|
||||
// `mapAnalystRecord` has always published `verticalRateMps`; the tracked
|
||||
// seam did not, so a consumer that needs climb/descent (a flight-phase
|
||||
// classifier, the cockpit) had to reach past getTrackedInfo() into the
|
||||
// raw record. Both seams now answer with the same field name and unit.
|
||||
seedLabelContact({
|
||||
callsign: 'SWA696',
|
||||
registration: 'N123AB',
|
||||
tracked: true,
|
||||
meta: { verticalRate: -6.4 },
|
||||
});
|
||||
assert.equal(flightsLayer.getTrackedInfo()?.verticalRateMps, -6.4);
|
||||
assert.equal(
|
||||
mapAnalystRecord(LABEL_ICAO, { verticalRate: -6.4 }).verticalRateMps,
|
||||
-6.4,
|
||||
'the analyst seam and the tracked seam must agree',
|
||||
);
|
||||
});
|
||||
|
||||
test('an unreported vertical rate stays null, never a level-flight zero', () => {
|
||||
// Absent is not level. `stickyNumber` seeds this field with null, so a
|
||||
// contact that has never transmitted a rate must not read as 0 m/s — a
|
||||
// phase classifier would call that cruise while the aircraft climbs.
|
||||
for (const missing of [undefined, null, Number.NaN]) {
|
||||
seedLabelContact({
|
||||
callsign: 'SWA696',
|
||||
registration: 'N123AB',
|
||||
tracked: true,
|
||||
meta: { verticalRate: missing },
|
||||
});
|
||||
assert.equal(
|
||||
flightsLayer.getTrackedInfo()?.verticalRateMps,
|
||||
null,
|
||||
`vertical rate ${String(missing)} must publish as null`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('civil label chain: identity stays icao24 while the label moves', () => {
|
||||
for (const fixture of [
|
||||
{ callsign: 'SWA696', registration: 'N123AB' },
|
||||
|
||||
@@ -834,3 +834,48 @@ test('two contacts matching at the same strength resolve deterministically', ()
|
||||
'and the same query resolves the same way every time',
|
||||
);
|
||||
});
|
||||
|
||||
test('the military tracked descriptor publishes the reported vertical rate', () => {
|
||||
// Shape parity with the civil layer's getTrackedInfo(). The two layers store
|
||||
// the rate under different record keys (`verticalRateMps` here,
|
||||
// `verticalRate` there), so `resolveTrackedAircraftInfo` only hands back one
|
||||
// shape if BOTH descriptors map it — a consumer that reads the field off the
|
||||
// civil seam and finds it missing on the military one would silently treat
|
||||
// every military contact as level flight.
|
||||
const seedRate = (verticalRateMps) =>
|
||||
_setTrackedMilitaryRefreshStateForTest({
|
||||
icao24: 'ae7f01',
|
||||
entity: { gevLabelModel: { title: 'TEST', details: [] } },
|
||||
billboard: {
|
||||
position: Cesium.Cartesian3.fromDegrees(-97.0, 31.0, 8_000),
|
||||
color: Cesium.Color.WHITE,
|
||||
show: true,
|
||||
},
|
||||
billboardCollection: { show: true, remove() {} },
|
||||
viewer: { camera: { positionCartographic: null }, scene: {} },
|
||||
meta: {
|
||||
callsign: 'RCH512',
|
||||
rawLat: 31,
|
||||
rawLon: -97,
|
||||
altitudeFt: 26_000,
|
||||
speedMps: 220,
|
||||
track: 95,
|
||||
onGround: false,
|
||||
verticalRateMps,
|
||||
},
|
||||
});
|
||||
|
||||
seedRate(12.7);
|
||||
assert.equal(militaryFlightsLayer.getTrackedInfo()?.verticalRateMps, 12.7);
|
||||
|
||||
// Absent is not level: a contact that has never transmitted a rate must read
|
||||
// as null, not as a 0 m/s a phase classifier would call cruise.
|
||||
for (const missing of [undefined, null, Number.NaN]) {
|
||||
seedRate(missing);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getTrackedInfo()?.verticalRateMps,
|
||||
null,
|
||||
`vertical rate ${String(missing)} must publish as null`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -114,7 +114,7 @@ export function createQueries({
|
||||
* 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}|null}
|
||||
* @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.
|
||||
*/
|
||||
|
||||
@@ -164,6 +164,15 @@ export function createQueries({
|
||||
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,
|
||||
@@ -856,7 +865,7 @@ export function createQueries({
|
||||
|
||||
/**
|
||||
* 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}|null}
|
||||
* @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() {
|
||||
|
||||
@@ -88,7 +88,7 @@ export function createQueries({
|
||||
* Build a public descriptor for one aircraft using its best current position
|
||||
* (dead-reckoned when history exists, billboard position otherwise).
|
||||
* @param {string} icao24 - ICAO hex identifier of the aircraft.
|
||||
* @returns {{icao24: string, callsign: string|null, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null}|null}
|
||||
* @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.
|
||||
*/
|
||||
|
||||
@@ -131,6 +131,13 @@ export function createQueries({
|
||||
onGround: info?.onGround === true,
|
||||
velocityMps: displayed.speedMps,
|
||||
track: displayed.trackDeg,
|
||||
// Same field, same name, same null-when-absent rule as the civil
|
||||
// descriptor — the two layers store the record under different keys
|
||||
// (`verticalRateMps` here, `verticalRate` there), so only the mapping
|
||||
// makes `resolveTrackedAircraftInfo` hand back one shape for both.
|
||||
verticalRateMps: Number.isFinite(info?.verticalRateMps)
|
||||
? info.verticalRateMps
|
||||
: null,
|
||||
stale: Boolean(
|
||||
flightState.records.missingPolls.get(icao24) ||
|
||||
flightState.feed._backoff,
|
||||
@@ -777,7 +784,7 @@ export function createQueries({
|
||||
|
||||
/**
|
||||
* 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}|null}
|
||||
* @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() {
|
||||
|
||||
Reference in new issue
Block a user