mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
118 lines
3.9 KiB
JavaScript
118 lines
3.9 KiB
JavaScript
// src/data/routePlausible.js
|
|
/**
|
|
* Is an adsbdb scheduled route consistent with where the plane actually is and
|
|
* what it is doing? adsbdb returns the scheduled route for a callsign, which
|
|
* is sometimes the wrong leg — reject a route rather than display a wrong one.
|
|
* Adapted from skylight (MIT) web/src/display/renderer.ts routePlausible(),
|
|
* with the vertical-trend check made observer-free (skylight anchors it to a
|
|
* fixed ground station; GEV has no observer, so the check is anchored to the
|
|
* PLANE: climbing hard + low → its origin should be nearby; descending hard +
|
|
* low → its destination should be nearby).
|
|
*/
|
|
|
|
const D2R = Math.PI / 180;
|
|
const R_KM = 6371;
|
|
|
|
/** Haversine great-circle distance in km. */
|
|
export function greatCircleKm(lat1, lon1, lat2, lon2) {
|
|
const p1 = lat1 * D2R;
|
|
const p2 = lat2 * D2R;
|
|
const dp = (lat2 - lat1) * D2R;
|
|
const dl = (lon2 - lon1) * D2R;
|
|
const a =
|
|
Math.sin(dp / 2) ** 2 + Math.cos(p1) * Math.cos(p2) * Math.sin(dl / 2) ** 2;
|
|
return R_KM * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
}
|
|
|
|
function bearingRad(lat1, lon1, lat2, lon2) {
|
|
const p1 = lat1 * D2R;
|
|
const p2 = lat2 * D2R;
|
|
const dl = (lon2 - lon1) * D2R;
|
|
const y = Math.sin(dl) * Math.cos(p2);
|
|
const x =
|
|
Math.cos(p1) * Math.sin(p2) - Math.sin(p1) * Math.cos(p2) * Math.cos(dl);
|
|
return Math.atan2(y, x);
|
|
}
|
|
|
|
/** Signed cross-track distance (km) of point from the great circle p1→p2. */
|
|
export function crossTrackKm(lat, lon, lat1, lon1, lat2, lon2) {
|
|
const d13 = greatCircleKm(lat1, lon1, lat, lon) / R_KM;
|
|
const b13 = bearingRad(lat1, lon1, lat, lon);
|
|
const b12 = bearingRad(lat1, lon1, lat2, lon2);
|
|
return Math.asin(Math.sin(d13) * Math.sin(b13 - b12)) * R_KM;
|
|
}
|
|
|
|
const NEAR_ENDPOINT_KM = 130;
|
|
const CROSS_TRACK_KM = 200;
|
|
const LOW_ALT_M = 3700; // ~12 000 ft
|
|
const VERT_TREND_MPS = 2; // ~400 fpm
|
|
const LOCAL_AIRPORT_KM = 150;
|
|
|
|
/**
|
|
* @param {object} p
|
|
* @param {number} p.latDeg / p.lonDeg — plane's current position
|
|
* @param {number|null} [p.altitudeM]
|
|
* @param {number|null} [p.verticalRateMps] — positive = climbing
|
|
* @param {{lat:number|null, lon:number|null}|null} [p.origin]
|
|
* @param {{lat:number|null, lon:number|null}|null} [p.destination]
|
|
* @returns {boolean} false ONLY when the route is confidently wrong.
|
|
*/
|
|
export function routePlausible({
|
|
latDeg,
|
|
lonDeg,
|
|
altitudeM = null,
|
|
verticalRateMps = null,
|
|
origin = null,
|
|
destination = null,
|
|
}) {
|
|
const haveO = Number.isFinite(origin?.lat) && Number.isFinite(origin?.lon);
|
|
const haveD =
|
|
Number.isFinite(destination?.lat) && Number.isFinite(destination?.lon);
|
|
if (!haveO && !haveD) return true; // no coordinates — cannot judge, do not hide
|
|
|
|
// (a) Geographic consistency: near an endpoint, or roughly on the path.
|
|
const near = (pt) =>
|
|
greatCircleKm(latDeg, lonDeg, pt.lat, pt.lon) < NEAR_ENDPOINT_KM;
|
|
let geomOk = (haveO && near(origin)) || (haveD && near(destination));
|
|
if (!geomOk && haveO && haveD) {
|
|
geomOk =
|
|
Math.abs(
|
|
crossTrackKm(
|
|
latDeg,
|
|
lonDeg,
|
|
origin.lat,
|
|
origin.lon,
|
|
destination.lat,
|
|
destination.lon,
|
|
),
|
|
) < CROSS_TRACK_KM;
|
|
} else if (!geomOk) {
|
|
geomOk = true; // only one endpoint known and not near — cannot judge, allow
|
|
}
|
|
if (!geomOk) return false;
|
|
|
|
// (b) Vertical-trend consistency for low traffic (observer-free adaptation).
|
|
if (
|
|
Number.isFinite(altitudeM) &&
|
|
altitudeM < LOW_ALT_M &&
|
|
Number.isFinite(verticalRateMps) &&
|
|
Math.abs(verticalRateMps) > VERT_TREND_MPS
|
|
) {
|
|
if (verticalRateMps > 0) {
|
|
if (
|
|
haveO &&
|
|
greatCircleKm(latDeg, lonDeg, origin.lat, origin.lon) > LOCAL_AIRPORT_KM
|
|
)
|
|
return false; // departing — origin should be local
|
|
} else {
|
|
if (
|
|
haveD &&
|
|
greatCircleKm(latDeg, lonDeg, destination.lat, destination.lon) >
|
|
LOCAL_AIRPORT_KM
|
|
)
|
|
return false; // arriving — destination should be local
|
|
}
|
|
}
|
|
return true;
|
|
}
|