mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
131 lines
4.0 KiB
JavaScript
131 lines
4.0 KiB
JavaScript
const KNOT_TO_MPS = 0.514444;
|
|
const FOOT_TO_M = 0.3048;
|
|
const FPM_TO_MPS = 0.00508;
|
|
|
|
function finiteNumber(value) {
|
|
if (value === null || value === undefined || value === '') return null;
|
|
const number = Number(value);
|
|
return Number.isFinite(number) ? number : null;
|
|
}
|
|
|
|
function emitterCategory(value) {
|
|
const category = String(value || '')
|
|
.trim()
|
|
.toUpperCase();
|
|
const categories = {
|
|
A1: 2,
|
|
A2: 3,
|
|
A3: 4,
|
|
A4: 5,
|
|
A5: 6,
|
|
A6: 7,
|
|
A7: 8,
|
|
B1: 9,
|
|
B2: 10,
|
|
B3: 11,
|
|
B4: 12,
|
|
B6: 14,
|
|
B7: 15,
|
|
};
|
|
return categories[category] || 0;
|
|
}
|
|
|
|
/**
|
|
* Convert one adsb.lol v2 aircraft record into the OpenSky state-vector shape
|
|
* consumed by the existing Flights renderer.
|
|
* @param {object} aircraft adsb.lol aircraft record.
|
|
* @param {number} nowSeconds Feed response time in epoch seconds.
|
|
* @returns {Array|null} OpenSky-compatible state vector, or null when invalid.
|
|
*/
|
|
export function normalizeAdsbLolAircraftState(aircraft, nowSeconds) {
|
|
const hex = String(aircraft?.hex || '')
|
|
.trim()
|
|
.toLowerCase();
|
|
const latitude = finiteNumber(aircraft?.lat);
|
|
const longitude = finiteNumber(aircraft?.lon);
|
|
if (
|
|
!hex ||
|
|
latitude === null ||
|
|
longitude === null ||
|
|
Math.abs(latitude) > 90 ||
|
|
Math.abs(longitude) > 180
|
|
)
|
|
return null;
|
|
|
|
const seenPosition = Math.max(
|
|
0,
|
|
finiteNumber(aircraft?.seen_pos) ?? finiteNumber(aircraft?.seen) ?? NaN,
|
|
);
|
|
const seen = Math.max(0, finiteNumber(aircraft?.seen) ?? seenPosition);
|
|
const onGround = aircraft?.alt_baro === 'ground';
|
|
const barometricFeet = onGround ? null : finiteNumber(aircraft?.alt_baro);
|
|
const geometricFeet = finiteNumber(aircraft?.alt_geom);
|
|
const groundSpeedKnots = finiteNumber(aircraft?.gs);
|
|
const verticalRateFpm =
|
|
finiteNumber(aircraft?.baro_rate) ?? finiteNumber(aircraft?.geom_rate);
|
|
const track = finiteNumber(aircraft?.track);
|
|
|
|
return [
|
|
hex,
|
|
String(aircraft?.flight || aircraft?.r || '').trim() || null,
|
|
null,
|
|
Number.isFinite(seenPosition)
|
|
? Math.max(0, nowSeconds - seenPosition)
|
|
: null,
|
|
Number.isFinite(seen) ? Math.max(0, nowSeconds - seen) : null,
|
|
longitude,
|
|
latitude,
|
|
barometricFeet === null ? null : barometricFeet * FOOT_TO_M,
|
|
onGround,
|
|
groundSpeedKnots === null ? null : groundSpeedKnots * KNOT_TO_MPS,
|
|
track,
|
|
verticalRateFpm === null ? null : verticalRateFpm * FPM_TO_MPS,
|
|
null,
|
|
geometricFeet === null ? null : geometricFeet * FOOT_TO_M,
|
|
aircraft?.squawk || null,
|
|
aircraft?.spi === 1,
|
|
0,
|
|
emitterCategory(aircraft?.category),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Normalize an adsb.lol point response to an OpenSky-compatible response.
|
|
* Invalid rows and positionless contacts are intentionally excluded.
|
|
* @param {object} payload adsb.lol v2 response.
|
|
* @returns {{time:number,states:Array[]}}
|
|
*/
|
|
export function normalizeAdsbLolPointResponse(payload) {
|
|
const responseNow = finiteNumber(payload?.now);
|
|
const nowSeconds =
|
|
responseNow === null
|
|
? Math.floor(Date.now() / 1000)
|
|
: Math.floor(
|
|
responseNow > 10_000_000_000 ? responseNow / 1000 : responseNow,
|
|
);
|
|
const aircraft = Array.isArray(payload?.ac) ? payload.ac : [];
|
|
const states = [];
|
|
// Keep the OpenSky vectors unchanged. The regional fallback's extra readsb
|
|
// status travels alongside them, keyed only to admitted aircraft.
|
|
const admitted = new Set();
|
|
const statuses = [];
|
|
for (const row of aircraft) {
|
|
const state = normalizeAdsbLolAircraftState(row, nowSeconds);
|
|
if (!state) continue;
|
|
states.push(state);
|
|
// The live contract admits the first valid vector for each ICAO. Its
|
|
// status must come from that same row, never an invalid or later duplicate.
|
|
const id = state[0];
|
|
if (admitted.has(id)) continue;
|
|
admitted.add(id);
|
|
if (typeof row.emergency === 'string')
|
|
statuses.push([id, row.emergency.trim().toLowerCase()]);
|
|
}
|
|
const emergencyByIcao = Object.fromEntries(statuses);
|
|
return {
|
|
time: nowSeconds,
|
|
states,
|
|
...(Object.keys(emergencyByIcao).length ? { emergencyByIcao } : {}),
|
|
};
|
|
}
|