mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
merge main 17daf897 into feat/voice-outlines
This commit is contained in:
commit
c059e12a11
107 files changed
+1598
-167
No files matched your search
@@ -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.
|
||||
*/
|
||||
|
||||
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,7 +249,7 @@ export function normalizeDump1090Aircraft(json, nowMs, { band = '1090' } = {}) {
|
||||
altitudeFt: altitude,
|
||||
groundSpeedKt: finiteOrNull(entry.gs),
|
||||
trackDeg: normalizeTrack(entry.track),
|
||||
verticalRateFpm: finiteOrNull(entry.baro_rate),
|
||||
verticalRateFpm: readAdsbVerticalRateFpm(entry),
|
||||
lastPositionAt: hasPosition
|
||||
? receivedAt - Math.max(0, seenPosS) * 1000
|
||||
: null,
|
||||
|
||||
@@ -169,6 +169,20 @@ test('a feed document names its band on every record', () => {
|
||||
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 = {}) {
|
||||
return {
|
||||
icao: 'abc123',
|
||||
|
||||
@@ -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,7 +49,7 @@ export function normalizeReadsbAircraft(row, snapshotTimeMs) {
|
||||
const baroFt = finite(row.alt_baro),
|
||||
geoFt = finite(row.alt_geom);
|
||||
const speedKts = finite(row.gs),
|
||||
rateFtMin = finite(row.baro_rate);
|
||||
rateFtMin = readAdsbVerticalRateFpm(row);
|
||||
const seen = finite(row.seen),
|
||||
seenPos = finite(row.seen_pos);
|
||||
return {
|
||||
|
||||
@@ -145,6 +145,20 @@ test('readsb units and position/contact ages are normalized once against the sou
|
||||
);
|
||||
});
|
||||
|
||||
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
|
||||
// GNSS; 26 of 157 positioned rows in a live /v2/mil fetch had only that.
|
||||
const row = { hex: 'ae638d', lat: 20.3, lon: -158.5, geom_rate: -2432 };
|
||||
assert.equal(
|
||||
normalizeReadsbAircraft(row, now).verticalRateMps,
|
||||
-2432 * 0.00508,
|
||||
);
|
||||
assert.equal(
|
||||
normalizeReadsbAircraft({ ...row, baro_rate: -2400 }, now).verticalRateMps,
|
||||
-2400 * 0.00508,
|
||||
);
|
||||
});
|
||||
|
||||
test('vessel identity, opaque reference and heading/course remain distinct, unknowns stay null', () => {
|
||||
const row = normalizeVesselObservation(
|
||||
{
|
||||
|
||||
@@ -72,3 +72,16 @@ export function tleCatalogNumber(line) {
|
||||
const field = String(line).slice(2, 7);
|
||||
return decodeCatalogField(field.replace(/^ +/, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch year from TLE line 1's international designator (columns 10-11);
|
||||
* null when the designator is blank.
|
||||
* @param {string} line1
|
||||
* @returns {number|null}
|
||||
*/
|
||||
export function tleLineLaunchYear(line1) {
|
||||
const digits = String(line1 || '').slice(9, 11);
|
||||
if (!/^\d{2}$/.test(digits)) return null;
|
||||
const shortYear = Number(digits);
|
||||
return shortYear >= 57 ? 1900 + shortYear : 2000 + shortYear;
|
||||
}
|
||||
Reference in new issue
Block a user