75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
/**
|
||
* @param p1 Point 1
|
||
* @param brng1 Bearing from Point 1
|
||
* @param p2 Point 2
|
||
* @param brng2 bearing from Point 2
|
||
* @returns Intersection point
|
||
*/
|
||
export const computeIntersection = (
|
||
p1: NavFix,
|
||
brng1: number,
|
||
p2: NavFix,
|
||
brng2: number
|
||
): NavFix | undefined | null => {
|
||
if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
|
||
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);
|
||
|
||
const π = Math.PI;
|
||
|
||
// see www.edwilliams.org/avform.htm#Intersection
|
||
|
||
const φ1 = p1.latitude.toRadians(),
|
||
λ1 = p1.longitude.toRadians();
|
||
const φ2 = p2.latitude.toRadians(),
|
||
λ2 = p2.longitude.toRadians();
|
||
const θ13 = Number(brng1).toRadians(),
|
||
θ23 = Number(brng2).toRadians();
|
||
const Δφ = φ2 - φ1,
|
||
Δλ = λ2 - λ1;
|
||
|
||
// angular distance p1-p2
|
||
const δ12 =
|
||
2 *
|
||
Math.asin(
|
||
Math.sqrt(Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2))
|
||
);
|
||
if (Math.abs(δ12) < Number.EPSILON) return p1; // coincident points
|
||
|
||
// initial/final bearings between points
|
||
const cosθa = (Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ1));
|
||
const cosθb = (Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ2));
|
||
const θa = Math.acos(Math.min(Math.max(cosθa, -1), 1)); // protect against rounding errors
|
||
const θb = Math.acos(Math.min(Math.max(cosθb, -1), 1)); // protect against rounding errors
|
||
|
||
const θ12 = Math.sin(λ2 - λ1) > 0 ? θa : 2 * π - θa;
|
||
const θ21 = Math.sin(λ2 - λ1) > 0 ? 2 * π - θb : θb;
|
||
|
||
const α1 = θ13 - θ12; // angle 2-1-3
|
||
const α2 = θ21 - θ23; // angle 1-2-3
|
||
|
||
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return undefined; // infinite intersections
|
||
if (Math.sin(α1) * Math.sin(α2) < 0) return p2; // ambiguous intersection (antipodal/360°)
|
||
|
||
const cosα3 = -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
|
||
|
||
const δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2), Math.cos(α2) + Math.cos(α1) * cosα3);
|
||
|
||
const φ3 = Math.asin(
|
||
Math.min(Math.max(Math.sin(φ1) * Math.cos(δ13) + Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13), -1), 1)
|
||
);
|
||
|
||
const Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1), Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));
|
||
const λ3 = λ1 + Δλ13;
|
||
|
||
const lat = φ3.toDegrees();
|
||
const lon = λ3.toDegrees();
|
||
|
||
return {
|
||
...p1,
|
||
latitude: lat,
|
||
longitude: lon,
|
||
name: 'INTC',
|
||
isIntersection: true,
|
||
};
|
||
};
|