Read an ADS-B vertical rate from one shared field reader

This commit is contained in:
Sameh Khamis committed 2026-11-02 00:09:18 -08:00
1 parent 780d8903ee
commit d479c1c6f6
8 files changed
+68 -9

No files matched your search

+16
View File
@@ -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);
}
+33
View File
@@ -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,
);
});
+3 -3
View File
@@ -30,6 +30,8 @@
* 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. */
export const LOCAL_ADSB_POSITION_STALE_MS = 60_000;
/** An aircraft is forgotten once no message has arrived for this long. */
@@ -247,9 +249,7 @@ export function normalizeDump1090Aircraft(json, nowMs, { band = '1090' } = {}) {
altitudeFt: altitude,
groundSpeedKt: finiteOrNull(entry.gs),
trackDeg: normalizeTrack(entry.track),
// A GNSS-sourced rate is sent as geom_rate instead of baro_rate.
verticalRateFpm:
finiteOrNull(entry.baro_rate) ?? finiteOrNull(entry.geom_rate),
verticalRateFpm: readAdsbVerticalRateFpm(entry),
lastPositionAt: hasPosition
? receivedAt - Math.max(0, seenPosS) * 1000
: null,
+2 -2
View File
@@ -6,6 +6,7 @@ import {
epoch,
finite,
} from './contract.js';
import { readAdsbVerticalRateFpm } from '../adsbFields.js';
const openSkyId = (row) =>
Array.isArray(row) ? cleanText(row[0]).toLowerCase() : '';
@@ -48,8 +49,7 @@ export function normalizeReadsbAircraft(row, snapshotTimeMs) {
const baroFt = finite(row.alt_baro),
geoFt = finite(row.alt_geom);
const speedKts = finite(row.gs),
// readsb sends a GNSS-sourced rate as geom_rate instead of baro_rate.
rateFtMin = finite(row.baro_rate) ?? finite(row.geom_rate);
rateFtMin = readAdsbVerticalRateFpm(row);
const seen = finite(row.seen),
seenPos = finite(row.seen_pos);
return {