mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Merge pull request #1732 from bilawalsidhu/fix/adsblol-true-heading
fix(flights): an aircraft with no ground track faces its true heading, not north
This commit is contained in:
7 files changed
+69
-3
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
- An aircraft that reports no ground track (parked or taxiing, from adsb.lol
|
||||||
|
or a local receiver) now faces its reported true heading instead of north,
|
||||||
|
so one taxiing south no longer looks like it is moving backwards
|
||||||
|
(samehkhamis, #1732).
|
||||||
@@ -64,7 +64,11 @@ export function normalizeAdsbLolAircraftState(aircraft, nowSeconds) {
|
|||||||
const geometricFeet = finiteNumber(aircraft?.alt_geom);
|
const geometricFeet = finiteNumber(aircraft?.alt_geom);
|
||||||
const groundSpeedKnots = finiteNumber(aircraft?.gs);
|
const groundSpeedKnots = finiteNumber(aircraft?.gs);
|
||||||
const verticalRateFpm = readAdsbVerticalRateFpm(aircraft);
|
const verticalRateFpm = readAdsbVerticalRateFpm(aircraft);
|
||||||
const track = finiteNumber(aircraft?.track);
|
// A parked or slow aircraft reports no ground track, but often its true
|
||||||
|
// heading (surface messages): the way its nose points, which is what an
|
||||||
|
// aircraft standing still is drawn by. Magnetic heading is not true north.
|
||||||
|
const track =
|
||||||
|
finiteNumber(aircraft?.track) ?? finiteNumber(aircraft?.true_heading);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
hex,
|
hex,
|
||||||
|
|||||||
@@ -49,3 +49,15 @@ test('keeps grounded fallback contacts and rejects rows without positions', () =
|
|||||||
assert.equal(normalized.states[0][7], null);
|
assert.equal(normalized.states[0][7], null);
|
||||||
assert.equal(normalized.states[0][8], true);
|
assert.equal(normalized.states[0][8], true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('an aircraft with no track takes its true heading, never its magnetic one', () => {
|
||||||
|
const at = (extra) =>
|
||||||
|
normalizeAdsbLolAircraftState(
|
||||||
|
{ hex: 'abc123', lat: 37.7, lon: -122.2, alt_baro: 'ground', ...extra },
|
||||||
|
1000,
|
||||||
|
)[10];
|
||||||
|
assert.equal(at({ track: 56.25, true_heading: 40 }), 56.25, 'track first');
|
||||||
|
assert.equal(at({ true_heading: 244.69 }), 244.69, 'true heading');
|
||||||
|
assert.equal(at({ mag_heading: 278.44 }), null, 'not magnetic');
|
||||||
|
assert.equal(at({}), null, 'none');
|
||||||
|
});
|
||||||
@@ -248,7 +248,8 @@ export function normalizeDump1090Aircraft(json, nowMs, { band = '1090' } = {}) {
|
|||||||
lon: hasPosition ? position.lon : null,
|
lon: hasPosition ? position.lon : null,
|
||||||
altitudeFt: altitude,
|
altitudeFt: altitude,
|
||||||
groundSpeedKt: finiteOrNull(entry.gs),
|
groundSpeedKt: finiteOrNull(entry.gs),
|
||||||
trackDeg: normalizeTrack(entry.track),
|
// No track (parked, slow): the true heading, where the nose points.
|
||||||
|
trackDeg: normalizeTrack(entry.track ?? entry.true_heading),
|
||||||
verticalRateFpm: readAdsbVerticalRateFpm(entry),
|
verticalRateFpm: readAdsbVerticalRateFpm(entry),
|
||||||
lastPositionAt: hasPosition
|
lastPositionAt: hasPosition
|
||||||
? receivedAt - Math.max(0, seenPosS) * 1000
|
? receivedAt - Math.max(0, seenPosS) * 1000
|
||||||
|
|||||||
@@ -68,6 +68,34 @@ test('dump1090 aircraft.json maps to the shared local ADS-B records', () => {
|
|||||||
assert.deepEqual(summary, { heard: 4, positioned: 2 });
|
assert.deepEqual(summary, { heard: 4, positioned: 2 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a dump1090 aircraft with no track takes its true heading, never its magnetic one', () => {
|
||||||
|
const trackOf = (extra) =>
|
||||||
|
normalizeDump1090Aircraft(
|
||||||
|
{
|
||||||
|
now: 1,
|
||||||
|
aircraft: [
|
||||||
|
{
|
||||||
|
hex: 'a4418c',
|
||||||
|
lat: 37.6,
|
||||||
|
lon: -122.38,
|
||||||
|
alt_baro: 'ground',
|
||||||
|
seen: 1,
|
||||||
|
...extra,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
)[0].trackDeg;
|
||||||
|
assert.equal(
|
||||||
|
trackOf({ track: 56.25, true_heading: 40 }),
|
||||||
|
56.25,
|
||||||
|
'track first',
|
||||||
|
);
|
||||||
|
assert.equal(trackOf({ true_heading: 185.62 }), 185.62, 'true heading');
|
||||||
|
assert.equal(trackOf({ mag_heading: 278.44 }), null, 'not magnetic');
|
||||||
|
assert.equal(trackOf({}), null, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
test('dump1090 adapter skips invalid addresses and tolerates missing fields', () => {
|
test('dump1090 adapter skips invalid addresses and tolerates missing fields', () => {
|
||||||
const records = normalizeDump1090Aircraft(
|
const records = normalizeDump1090Aircraft(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ export function normalizeReadsbAircraft(row, snapshotTimeMs) {
|
|||||||
ellipsoidAltitudeM: geoFt == null ? null : geoFt * 0.3048,
|
ellipsoidAltitudeM: geoFt == null ? null : geoFt * 0.3048,
|
||||||
onGround: cleanText(row.alt_baro).toLowerCase() === 'ground',
|
onGround: cleanText(row.alt_baro).toLowerCase() === 'ground',
|
||||||
speedMps: speedKts == null ? null : speedKts * 0.514444,
|
speedMps: speedKts == null ? null : speedKts * 0.514444,
|
||||||
courseDeg: finite(row.track),
|
// No track (parked, slow): the true heading, where the nose points.
|
||||||
|
courseDeg: finite(row.track) ?? finite(row.true_heading),
|
||||||
verticalRateMps: rateFtMin == null ? null : rateFtMin * 0.00508,
|
verticalRateMps: rateFtMin == null ? null : rateFtMin * 0.00508,
|
||||||
category: row.category ?? null,
|
category: row.category ?? null,
|
||||||
typeCode: cleanText(row.t),
|
typeCode: cleanText(row.t),
|
||||||
|
|||||||
@@ -168,6 +168,22 @@ test('readsb units and position/contact ages are normalized once against the sou
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a readsb aircraft with no track takes its true heading, never its magnetic one', () => {
|
||||||
|
const course = (extra) =>
|
||||||
|
normalizeReadsbAircraft(
|
||||||
|
{ hex: 'a4418c', lat: 37.6, lon: -122.38, alt_baro: 'ground', ...extra },
|
||||||
|
now,
|
||||||
|
).courseDeg;
|
||||||
|
assert.equal(
|
||||||
|
course({ track: 56.25, true_heading: 40 }),
|
||||||
|
56.25,
|
||||||
|
'track first',
|
||||||
|
);
|
||||||
|
assert.equal(course({ true_heading: 185.62 }), 185.62, 'true heading');
|
||||||
|
assert.equal(course({ mag_heading: 278.44 }), null, 'not magnetic');
|
||||||
|
assert.equal(course({}), null, 'none');
|
||||||
|
});
|
||||||
|
|
||||||
test('readsb vertical rate falls back to the GNSS rate when no baro rate is sent', () => {
|
test('readsb vertical rate falls back to the GNSS rate when no baro rate is sent', () => {
|
||||||
// readsb reports the rate under geom_rate when the aircraft's source is
|
// readsb reports the rate under geom_rate when the aircraft's source is
|
||||||
// GNSS; 26 of 157 positioned rows in a live /v2/mil fetch had only that.
|
// GNSS; 26 of 157 positioned rows in a live /v2/mil fetch had only that.
|
||||||
|
|||||||
Reference in new issue
Block a user