mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge pull request #1448 from Rohilalala/fix/e-local-adsb-geom-rate
fix(local-adsb): show a decoder feed's GNSS-sourced vertical rate
This commit is contained in:
10 files changed
+88
-8
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
- Local ADS-B decoder feeds now carry a GNSS-sourced vertical rate
|
||||||
|
(`geom_rate`) when no barometric rate is sent, so the card shows V/S
|
||||||
|
instead of a dash (Aditya, @Rohilalala, #1448).
|
||||||
|
- Local ADS-B, military and the adsb.lol flight fallback read the vertical
|
||||||
|
rate through one shared readsb field reader, so all three prefer the
|
||||||
|
barometric rate and fall back to the GNSS rate the same way (#1448).
|
||||||
@@ -6322,7 +6322,9 @@ Receivers publish one record shape from `src/sources/adsbRecords.js`
|
|||||||
`lastMessageAt`, `messageCount`, `rssiDbfs`, `band`, `source`). `band` is `1090` or `978`; `source` is
|
`lastMessageAt`, `messageCount`, `rssiDbfs`, `band`, `source`). `band` is `1090` or `978`; `source` is
|
||||||
`webusb` (browser SDR) or `feed` (decoder feed).
|
`webusb` (browser SDR) or `feed` (decoder feed).
|
||||||
`normalizeDump1090Aircraft(json, nowMs, { band })` maps a dump1090/readsb/
|
`normalizeDump1090Aircraft(json, nowMs, { band })` maps a dump1090/readsb/
|
||||||
skyaware978 `aircraft.json` document into the same records. Voice
|
skyaware978 `aircraft.json` document into the same records; a row without
|
||||||
|
`baro_rate` reads its vertical rate from `geom_rate`, where a GNSS-sourced rate
|
||||||
|
is sent. Voice
|
||||||
`set_layer_visibility` accepts `local-adsb` ("local ADS-B", "my receiver",
|
`set_layer_visibility` accepts `local-adsb` ("local ADS-B", "my receiver",
|
||||||
"my antenna").
|
"my antenna").
|
||||||
|
|
||||||
|
|||||||
@@ -1183,5 +1183,6 @@
|
|||||||
"src/firstRunLauncherView.test.mjs",
|
"src/firstRunLauncherView.test.mjs",
|
||||||
"src/firstRunCopy.js",
|
"src/firstRunCopy.js",
|
||||||
"src/data/headingDegrees.test.mjs",
|
"src/data/headingDegrees.test.mjs",
|
||||||
"src/data/distanceFormat.test.mjs"
|
"src/data/distanceFormat.test.mjs",
|
||||||
|
"src/sources/adsbFields.test.mjs"
|
||||||
]
|
]
|
||||||
@@ -179,6 +179,7 @@
|
|||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": ["./server/providers/live"],
|
"exports": ["./server/providers/live"],
|
||||||
"modules": [
|
"modules": [
|
||||||
|
"src/sources/adsbFields.js",
|
||||||
"server/providers/live.js",
|
"server/providers/live.js",
|
||||||
"server/providers/common/http.js",
|
"server/providers/common/http.js",
|
||||||
"server/providers/common/query.js",
|
"server/providers/common/query.js",
|
||||||
@@ -198,7 +199,7 @@
|
|||||||
},
|
},
|
||||||
"adsb-lol-source": {
|
"adsb-lol-source": {
|
||||||
"exports": ["./sources/adsb-lol"],
|
"exports": ["./sources/adsb-lol"],
|
||||||
"modules": ["src/data/adsbLolFallback.js"],
|
"modules": ["src/data/adsbLolFallback.js", "src/sources/adsbFields.js"],
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"place-providers": {
|
"place-providers": {
|
||||||
@@ -1247,6 +1248,7 @@
|
|||||||
"live-observations": {
|
"live-observations": {
|
||||||
"exports": ["./sources/live"],
|
"exports": ["./sources/live"],
|
||||||
"modules": [
|
"modules": [
|
||||||
|
"src/sources/adsbFields.js",
|
||||||
"src/i18n/marker.js",
|
"src/i18n/marker.js",
|
||||||
"src/data/aircraftEmergency.js",
|
"src/data/aircraftEmergency.js",
|
||||||
"src/sources/live/aircraft.js",
|
"src/sources/live/aircraft.js",
|
||||||
@@ -2203,6 +2205,7 @@
|
|||||||
"./standalone/catalog"
|
"./standalone/catalog"
|
||||||
],
|
],
|
||||||
"modules": [
|
"modules": [
|
||||||
|
"src/sources/adsbFields.js",
|
||||||
"src/data/headingDegrees.js",
|
"src/data/headingDegrees.js",
|
||||||
"src/data/distanceFormat.js",
|
"src/data/distanceFormat.js",
|
||||||
"src/ui/placeNames.js",
|
"src/ui/placeNames.js",
|
||||||
@@ -3179,6 +3182,7 @@
|
|||||||
"application-layer-construction": {
|
"application-layer-construction": {
|
||||||
"exports": ["./application/layers"],
|
"exports": ["./application/layers"],
|
||||||
"modules": [
|
"modules": [
|
||||||
|
"src/sources/adsbFields.js",
|
||||||
"src/data/headingDegrees.js",
|
"src/data/headingDegrees.js",
|
||||||
"src/data/distanceFormat.js",
|
"src/data/distanceFormat.js",
|
||||||
"src/cyberSonar.js",
|
"src/cyberSonar.js",
|
||||||
@@ -4721,6 +4725,7 @@
|
|||||||
"tools-services": {
|
"tools-services": {
|
||||||
"exports": ["./tools/services"],
|
"exports": ["./tools/services"],
|
||||||
"modules": [
|
"modules": [
|
||||||
|
"src/sources/adsbFields.js",
|
||||||
"src/tools/services.js",
|
"src/tools/services.js",
|
||||||
"src/layers/bikeshare/registry.js",
|
"src/layers/bikeshare/registry.js",
|
||||||
"src/layers/bikeshare/source.js",
|
"src/layers/bikeshare/source.js",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { readAdsbVerticalRateFpm } from '../sources/adsbFields.js';
|
||||||
|
|
||||||
const KNOT_TO_MPS = 0.514444;
|
const KNOT_TO_MPS = 0.514444;
|
||||||
const FOOT_TO_M = 0.3048;
|
const FOOT_TO_M = 0.3048;
|
||||||
const FPM_TO_MPS = 0.00508;
|
const FPM_TO_MPS = 0.00508;
|
||||||
@@ -61,8 +63,7 @@ export function normalizeAdsbLolAircraftState(aircraft, nowSeconds) {
|
|||||||
const barometricFeet = onGround ? null : finiteNumber(aircraft?.alt_baro);
|
const barometricFeet = onGround ? null : finiteNumber(aircraft?.alt_baro);
|
||||||
const geometricFeet = finiteNumber(aircraft?.alt_geom);
|
const geometricFeet = finiteNumber(aircraft?.alt_geom);
|
||||||
const groundSpeedKnots = finiteNumber(aircraft?.gs);
|
const groundSpeedKnots = finiteNumber(aircraft?.gs);
|
||||||
const verticalRateFpm =
|
const verticalRateFpm = readAdsbVerticalRateFpm(aircraft);
|
||||||
finiteNumber(aircraft?.baro_rate) ?? finiteNumber(aircraft?.geom_rate);
|
|
||||||
const track = finiteNumber(aircraft?.track);
|
const track = finiteNumber(aircraft?.track);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/** A readsb rate field: a finite JSON number, else null. */
|
||||||
|
function rateField(value) {
|
||||||
|
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical rate in ft/min from a readsb-style aircraft row (readsb, tar1090,
|
||||||
|
* adsb.lol): the barometric `baro_rate`, else the GNSS-sourced `geom_rate`
|
||||||
|
* readsb sends instead when it has no barometric rate. A barometric 0 is kept.
|
||||||
|
* Only a finite number counts; null when neither field has one.
|
||||||
|
* @param {object|null|undefined} row
|
||||||
|
* @returns {number|null}
|
||||||
|
*/
|
||||||
|
export function readAdsbVerticalRateFpm(row) {
|
||||||
|
return rateField(row?.baro_rate) ?? rateField(row?.geom_rate);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { readAdsbVerticalRateFpm } from './adsbFields.js';
|
||||||
|
|
||||||
|
test('the barometric rate wins, a barometric 0 included', () => {
|
||||||
|
assert.equal(
|
||||||
|
readAdsbVerticalRateFpm({ baro_rate: -640, geom_rate: -832 }),
|
||||||
|
-640,
|
||||||
|
);
|
||||||
|
assert.equal(readAdsbVerticalRateFpm({ baro_rate: 0, geom_rate: 600 }), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a GNSS rate stands in when no barometric rate is usable', () => {
|
||||||
|
for (const baro_rate of [
|
||||||
|
undefined,
|
||||||
|
null,
|
||||||
|
Number.NaN,
|
||||||
|
Infinity,
|
||||||
|
'300',
|
||||||
|
false,
|
||||||
|
]) {
|
||||||
|
assert.equal(readAdsbVerticalRateFpm({ baro_rate, geom_rate: -832 }), -832);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no usable rate reads as null', () => {
|
||||||
|
assert.equal(readAdsbVerticalRateFpm({}), null);
|
||||||
|
assert.equal(readAdsbVerticalRateFpm(null), null);
|
||||||
|
assert.equal(
|
||||||
|
readAdsbVerticalRateFpm({ baro_rate: '300', geom_rate: true }),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -30,6 +30,8 @@
|
|||||||
* This module is portable: no Cesium, DOM, network or storage access.
|
* This module is portable: no Cesium, DOM, network or storage access.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { readAdsbVerticalRateFpm } from './adsbFields.js';
|
||||||
|
|
||||||
/** A marker is dropped once its newest position is this old. */
|
/** A marker is dropped once its newest position is this old. */
|
||||||
export const LOCAL_ADSB_POSITION_STALE_MS = 60_000;
|
export const LOCAL_ADSB_POSITION_STALE_MS = 60_000;
|
||||||
/** An aircraft is forgotten once no message has arrived for this long. */
|
/** An aircraft is forgotten once no message has arrived for this long. */
|
||||||
@@ -247,7 +249,7 @@ export function normalizeDump1090Aircraft(json, nowMs, { band = '1090' } = {}) {
|
|||||||
altitudeFt: altitude,
|
altitudeFt: altitude,
|
||||||
groundSpeedKt: finiteOrNull(entry.gs),
|
groundSpeedKt: finiteOrNull(entry.gs),
|
||||||
trackDeg: normalizeTrack(entry.track),
|
trackDeg: normalizeTrack(entry.track),
|
||||||
verticalRateFpm: finiteOrNull(entry.baro_rate),
|
verticalRateFpm: readAdsbVerticalRateFpm(entry),
|
||||||
lastPositionAt: hasPosition
|
lastPositionAt: hasPosition
|
||||||
? receivedAt - Math.max(0, seenPosS) * 1000
|
? receivedAt - Math.max(0, seenPosS) * 1000
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -169,6 +169,20 @@ test('a feed document names its band on every record', () => {
|
|||||||
assert.ok(uat.every((record) => record.source === 'feed'));
|
assert.ok(uat.every((record) => record.source === 'feed'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a decoder feed reads a GNSS-sourced vertical rate when no baro rate is sent', () => {
|
||||||
|
// dump1090-fa and readsb file a velocity message's rate under geom_rate
|
||||||
|
// instead of baro_rate when its source bit says GNSS.
|
||||||
|
const rate = (fields) =>
|
||||||
|
normalizeDump1090Aircraft(
|
||||||
|
{ aircraft: [{ hex: 'abc123', seen: 1, ...fields }] },
|
||||||
|
10_000,
|
||||||
|
)[0].verticalRateFpm;
|
||||||
|
assert.equal(rate({ geom_rate: -832 }), -832);
|
||||||
|
assert.equal(rate({ baro_rate: -768, geom_rate: -832 }), -768);
|
||||||
|
assert.equal(rate({ baro_rate: 0, geom_rate: -832 }), 0);
|
||||||
|
assert.equal(rate({}), null);
|
||||||
|
});
|
||||||
|
|
||||||
function heard(overrides = {}) {
|
function heard(overrides = {}) {
|
||||||
return {
|
return {
|
||||||
icao: 'abc123',
|
icao: 'abc123',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
epoch,
|
epoch,
|
||||||
finite,
|
finite,
|
||||||
} from './contract.js';
|
} from './contract.js';
|
||||||
|
import { readAdsbVerticalRateFpm } from '../adsbFields.js';
|
||||||
|
|
||||||
const openSkyId = (row) =>
|
const openSkyId = (row) =>
|
||||||
Array.isArray(row) ? cleanText(row[0]).toLowerCase() : '';
|
Array.isArray(row) ? cleanText(row[0]).toLowerCase() : '';
|
||||||
@@ -48,8 +49,7 @@ export function normalizeReadsbAircraft(row, snapshotTimeMs) {
|
|||||||
const baroFt = finite(row.alt_baro),
|
const baroFt = finite(row.alt_baro),
|
||||||
geoFt = finite(row.alt_geom);
|
geoFt = finite(row.alt_geom);
|
||||||
const speedKts = finite(row.gs),
|
const speedKts = finite(row.gs),
|
||||||
// readsb sends a GNSS-sourced rate as geom_rate instead of baro_rate.
|
rateFtMin = readAdsbVerticalRateFpm(row);
|
||||||
rateFtMin = finite(row.baro_rate) ?? finite(row.geom_rate);
|
|
||||||
const seen = finite(row.seen),
|
const seen = finite(row.seen),
|
||||||
seenPos = finite(row.seen_pos);
|
seenPos = finite(row.seen_pos);
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in new issue
Block a user