Improve drawin

This commit is contained in:
Kilian Hofmann 2025-07-17 03:31:46 +02:00
parent e4adf30632
commit 779a78649e
18 changed files with 340 additions and 131 deletions

View File

@ -10,7 +10,7 @@
### Example ### Example
LGAV BIBE1K SID (Cycle 2507, ID 10653) LGAV 03L BIBE1K SID (Cycle 2507, ID 10653)
### Minimum Required Fields ### Minimum Required Fields
@ -40,12 +40,14 @@ LGAV BIBE1K SID (Cycle 2507, ID 10653)
While similar to an RF, the center point is coded differently. While similar to an RF, the center point is coded differently.
Calculate distance for FMS based on ` 2 * π NavDist * abs(NavBear - Course) 360`.
## Course to Altitude (CA) ## Course to Altitude (CA)
### Example ### Example
LGAV BIBE1L SID (Cycle 2507, ID 10654) LGAV 03L BIBE1L SID (Cycle 2507, ID 10654)
### Minimum Required Fields ### Minimum Required Fields
@ -74,7 +76,7 @@ This new origin is an implicit overfly.
### Example ### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657) LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields ### Minimum Required Fields
@ -105,7 +107,7 @@ This new origin is an implicit overfly.
### Example ### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657) LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields ### Minimum Required Fields
@ -145,7 +147,7 @@ shall be `TurnDir`.
### Example ### Example
LGAV BIBE1L SID (Cycle 2507, ID 10654) LGAV 03L BIBE1L SID (Cycle 2507, ID 10654)
### Minimum Required Fields ### Minimum Required Fields
@ -170,7 +172,7 @@ This new origin can never be an overfly due to the intercept nature.
### Example ### Example
LGAV KOR1D SID (Cycle 2507, ID 10679) LGAV 03L KOR1D SID (Cycle 2507, ID 10679)
### Minimum Required Fields ### Minimum Required Fields
@ -197,7 +199,7 @@ This intercept point then becomes the origin fix of the succeeding leg.
### Example ### Example
LGAV KOR1D SID (Cycle 2507, ID 10679) LGAV 03L KOR1D SID (Cycle 2507, ID 10679)
### Minimum Required Fields ### Minimum Required Fields
@ -219,7 +221,7 @@ LGAV KOR1D SID (Cycle 2507, ID 10679)
### Example ### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657) LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields ### Minimum Required Fields
@ -254,7 +256,7 @@ This new origin is an implicit overfly.
### Example ### Example
LIED CAR6F SID (Cycle 2507, ID 11798) LIED 34L/R CAR6F SID (Cycle 2507, ID 11798)
### Minimum Required Fields ### Minimum Required Fields
@ -290,7 +292,7 @@ This intercept point then becomes the origin fix of the succeeding leg.
### Example ### Example
LGAV BIBE2T SID (Cycle 2507, ID 10659) LGAV 03R BIBE2T SID (Cycle 2507, ID 10659)
### Minimum Required Fields ### Minimum Required Fields
@ -325,7 +327,7 @@ This new origin is an implicit overfly.
### Example ### Example
LFPV PB2V SID (Cycle 2507, ID 10395) LFPV 27 PB2V SID (Cycle 2507, ID 10395)
### Minimum Required Fields ### Minimum Required Fields
@ -457,7 +459,7 @@ My guess as for the missing time/distance decider field, assume it to be distanc
### Example ### Example
FAWB VDM29 APP (Cycle 2507, ID 67794), Missed approach procedure FAWB 29 VDM29 APP (Cycle 2507, ID 67794), Missed approach procedure
### Minimum Required Fields ### Minimum Required Fields
@ -513,7 +515,7 @@ This intercept point then becomes the origin fix of the succeeding leg.
### Example ### Example
LFRN GODA5R SID (cycle 2507, ID 10485) LFRN 10 GODA5R SID (cycle 2507, ID 10485)
### Minimum Required Fields ### Minimum Required Fields
@ -545,12 +547,14 @@ No radius is specified, but can be inferred based on center point, both endpoint
Example has `NavBear` set to `null`, significance of the inbound tangential track is unknown. Example has `NavBear` set to `null`, significance of the inbound tangential track is unknown.
Use `Distance` for calculations in FMS.
## Track to Fix (TF) ## Track to Fix (TF)
### Example ### Example
LFRN GODA5R SID (cycle 2507, ID 10485) LFRN 10 GODA5R SID (cycle 2507, ID 10485)
### Minimum Required Fields ### Minimum Required Fields
@ -571,7 +575,7 @@ LFRN GODA5R SID (cycle 2507, ID 10485)
### Example ### Example
LFRK LGL4X SID (Cycle 2507, ID 10475) LFRK 10 LGL4X SID (Cycle 2507, ID 10475)
### Minimum Required Fields ### Minimum Required Fields
@ -599,7 +603,7 @@ This new origin is an implicit overfly.
### Example ### Example
LFRK NEVI4Y SID (Cycle 2507, ID 10482) LFRK 31 NEVI4Y SID (Cycle 2507, ID 10482)
### Minimum Required Fields ### Minimum Required Fields
@ -629,7 +633,7 @@ This new origin is an implicit overfly.
### Example ### Example
LFRK LUSI4Y SID (Cycle 2507, ID 10480) LFRK 31 LUSI4Y SID (Cycle 2507, ID 10480)
### Minimum Required Fields ### Minimum Required Fields
@ -656,7 +660,7 @@ This new origin can never be an overfly due to the intercept nature.
### Example ### Example
LFPV PB2P SID (Cycle 2507, ID 10394) LFPV 27 PB2P SID (Cycle 2507, ID 10394)
### Minimum Required Fields ### Minimum Required Fields
@ -676,7 +680,7 @@ LFPV PB2P SID (Cycle 2507, ID 10394)
### Example ### Example
LIMC MMP8G SID (Cycle 2507, ID 11909) LIMC 35R MMP8G SID (Cycle 2507, ID 11909)
### Minimum Required Fields ### Minimum Required Fields

View File

@ -128,13 +128,16 @@ class Parser {
name: runway.Ident, name: runway.Ident,
}); });
let lastCourse = runway.TrueHeading; let lastCourse = runway.TrueHeading;
const procedure = this._procedures.filter(({ Transition }) => !Transition || Transition === transition); const procedure = this._procedures.filter(
({ Transition }) => !Transition || Transition === transition || Transition === 'ALL'
);
// Main // Main
for (let index = 0; index < procedure.length; index++) { for (let index = 0; index < procedure.length; index++) {
//lastCourse = runway.TrueHeading;
const leg = procedure[index]; const leg = procedure[index];
const previousFix = navFixes.at(-1)!; const previousFix = navFixes.at(-1)!;
const waypoint = this.waypoints.filter(({ ID }) => ID === leg.WptID)[0]; const waypoint = this.waypoints.find(({ ID }) => ID === leg.WptID);
switch (leg.TrackCode) { switch (leg.TrackCode) {
case 'AF': { case 'AF': {
@ -200,6 +203,8 @@ class Parser {
case 'FM': { case 'FM': {
const [fixToAdd, lineToAdd] = TerminatorsFM(leg as FMTerminalEntry, previousFix, lastCourse); const [fixToAdd, lineToAdd] = TerminatorsFM(leg as FMTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, { isManual: true }); update(fixToAdd, lineToAdd, { isManual: true });
// Make overfly
navFixes.at(-1)!.isFlyOver = true;
break; break;
} }
case 'HA': case 'HA':
@ -209,8 +214,11 @@ class Parser {
break; break;
case 'IF': { case 'IF': {
const fixToAdd = TerminatorsIF(leg as RFTerminalEntry, waypoint); const fixToAdd = TerminatorsIF(leg as RFTerminalEntry, waypoint);
navFixes.length = 0; // Only Runway, replace
navFixes.push(fixToAdd); if (navFixes.length <= 1) {
navFixes.push(fixToAdd);
lastCourse = -1;
}
break; break;
} }
case 'PI': case 'PI':
@ -228,7 +236,12 @@ class Parser {
break; break;
} }
case 'TF': { case 'TF': {
const [fixToAdd, lineToAdd] = TerminatorsTF(leg as TFTerminalEntry, previousFix, lastCourse, waypoint); const [fixToAdd, lineToAdd] = TerminatorsTF(
leg as TFTerminalEntry,
{ ...previousFix }, // COPY
lastCourse,
waypoint
);
update(fixToAdd, lineToAdd); update(fixToAdd, lineToAdd);
break; break;
} }
@ -255,6 +268,8 @@ class Parser {
case 'VM': { case 'VM': {
const [fixToAdd, lineToAdd] = TerminatorsVM(leg as VMTerminalEntry, previousFix, lastCourse); const [fixToAdd, lineToAdd] = TerminatorsVM(leg as VMTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, { isManual: true }); update(fixToAdd, lineToAdd, { isManual: true });
// Make overfly
navFixes.at(-1)!.isFlyOver = true;
break; break;
} }
case 'VR': { case 'VR': {

View File

@ -30,11 +30,11 @@ export const generateAFArc = (
while (crsFromOrigin !== crsIntoEndpoint) { while (crsFromOrigin !== crsIntoEndpoint) {
if (turnDir === 'R') { if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees(); const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
crsFromOrigin += delta < 1 ? delta : 1; crsFromOrigin += delta < 0.1 ? delta : 0.1;
crsFromOrigin = crsFromOrigin.normaliseDegrees(); crsFromOrigin = crsFromOrigin.normaliseDegrees();
} else { } else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees(); const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
crsFromOrigin -= delta < 1 ? delta : 1; crsFromOrigin -= delta < 0.1 ? delta : 0.1;
crsFromOrigin = crsFromOrigin.normaliseDegrees(); crsFromOrigin = crsFromOrigin.normaliseDegrees();
} }
if (crsFromOrigin === crsIntoEndpoint) break; if (crsFromOrigin === crsIntoEndpoint) break;

View File

@ -38,12 +38,12 @@ export const generatePerformanceArc = (
let time = 0; let time = 0;
if (turnDir === 'R') { if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees(); const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees(); crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} else { } else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees(); const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees(); crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} }
@ -70,12 +70,12 @@ export const generatePerformanceArc = (
let time = 0; let time = 0;
if (turnDir === 'R') { if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees(); const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees(); crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} else { } else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees(); const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees(); crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} }

View File

@ -1,5 +1,5 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance'; import getPreciseDistance from 'geolib/es/getPreciseDistance';
/** /**
* @param crsIntoEndpoint Course into arc endpoint * @param crsIntoEndpoint Course into arc endpoint
@ -36,7 +36,7 @@ export const generateRFArc = (
crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees(); crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees();
} }
const arcRad = getDistance(center, start); const arcRad = getPreciseDistance(center, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse(); crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
@ -50,11 +50,11 @@ export const generateRFArc = (
while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) { while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
if (turnDir === 'R') { if (turnDir === 'R') {
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees(); const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 1 ? delta : 1; crsOrthogonalOnOrigin += delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else { } else {
const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees(); const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1; crsOrthogonalOnOrigin -= delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} }

View File

@ -1,5 +1,5 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance'; import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { computeIntersection } from '../utils/computeIntersection'; import { computeIntersection } from '../utils/computeIntersection';
/** /**
@ -56,14 +56,18 @@ export const generateTangentArc = (
} }
// Generate arc // Generate arc
const arcCenter = computeIntersection( let arcCenter = computeIntersection(
start, start,
crsOrthogonalOnOrigin, crsOrthogonalOnOrigin,
intcArcOnCrsIntoEndpoint, intcArcOnCrsIntoEndpoint,
crsOrthogonalOnEndpoint crsOrthogonalOnEndpoint
); );
if (!arcCenter) return null; let arcRad = 0;
const arcRad = getDistance(arcCenter, start); if (arcCenter) arcRad = getPreciseDistance(arcCenter, start);
else {
arcRad = getPreciseDistance(start, end) / 2;
arcCenter = computeDestinationPoint(start, arcRad, crsOrthogonalOnOrigin);
}
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse(); crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
@ -74,19 +78,24 @@ export const generateTangentArc = (
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1; crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
} }
let lastDistance = getPreciseDistance(start, end);
while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) { while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
if (turnDir === 'R') { if (turnDir === 'R') {
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees(); const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 1 ? delta : 1; crsOrthogonalOnOrigin += delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else { } else {
const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees(); const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1; crsOrthogonalOnOrigin -= delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} }
const arcFix = computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin); const arcFix = computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin);
const newDistance = getPreciseDistance(arcFix, end);
if (lastDistance <= newDistance && lastDistance < 25) break;
lastDistance = newDistance;
line.push([arcFix.longitude, arcFix.latitude]); line.push([arcFix.longitude, arcFix.latitude]);
} }
} }

View File

@ -1,6 +1,6 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -23,7 +23,7 @@ export const TerminatorsCD = (
// Compute distance to fly from arc end // Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid); const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid); const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre(); let remainingDistance = leg.Distance.toMetre();
// Navaid behind us // Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) { if (Math.abs(crsToNavaid - lastCourse) > 90) {

View File

@ -1,6 +1,8 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import Parser from '../parser'; import Parser from '../parser';
import { generateTangentArc } from '../pathGenerators/generateTangentArc';
import { computeIntersection } from '../utils/computeIntersection'; import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate'; import { computeTurnRate } from '../utils/computeTurnRate';
@ -13,7 +15,7 @@ export const TerminatorsCF = (
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix); const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix); const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]]; const line: LineSegment[] = [];
const targetFix: NavFix = { const targetFix: NavFix = {
latitude: leg.WptLat, latitude: leg.WptLat,
@ -28,7 +30,15 @@ export const TerminatorsCF = (
const crsToIntercept = leg.Course.toTrue(targetFix); const crsToIntercept = leg.Course.toTrue(targetFix);
// Compute overfly arc // Compute overfly arc
if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) { let arc1: LineSegment[] | null = null;
let arc2: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
if (previousFix.isFlyOver) {
arc1 = generateTangentArc(crsIntoEndpoint, lastCourse, previousFix, targetFix, leg.TurnDir);
} else {
arc1 = [[previousFix.longitude, previousFix.latitude]];
}
if (previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsToIntercept))) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK); const turnRate = computeTurnRate(speed, Parser.AC_BANK);
let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix); let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
@ -44,26 +54,26 @@ export const TerminatorsCF = (
let time = 0; let time = 0;
if (leg.TurnDir === 'R') { if (leg.TurnDir === 'R') {
//const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); //const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = 1; //delta < 1 ? delta : 1; const increment = 0.1; //delta < 1 ? delta : 1;
lastCourse = (lastCourse + increment).normaliseDegrees(); lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} else { } else {
//const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees(); //const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = 1; //delta < 1 ? delta : 1; const increment = 0.1; //delta < 1 ? delta : 1;
lastCourse = (lastCourse - increment).normaliseDegrees(); lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} }
const arcFix = computeDestinationPoint( const arcFix = computeDestinationPoint(
{ {
latitude: line.at(-1)![1], latitude: arc2.at(-1)![1],
longitude: line.at(-1)![0], longitude: arc2.at(-1)![0],
}, },
((speed / 3600) * time).toMetre(), ((speed / 3600) * time).toMetre(),
lastCourse lastCourse
); );
line.push([arcFix.longitude, arcFix.latitude]); arc2.push([arcFix.longitude, arcFix.latitude]);
// Update previousFix // Update previousFix
previousFix.latitude = arcFix.latitude; previousFix.latitude = arcFix.latitude;
@ -71,22 +81,54 @@ export const TerminatorsCF = (
updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix); updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
let interceptAngle = 0; let interceptAngle = 0;
if (leg.TurnDir === 'R') Math.abs((interceptAngle = lastCourse - crsToIntercept)); if (leg.TurnDir === 'R') interceptAngle = lastCourse - crsToIntercept;
else interceptAngle = Math.abs(crsToIntercept - lastCourse); else interceptAngle = crsToIntercept - lastCourse;
if (interceptAngle >= 45) break; if (interceptAngle > 0 && interceptAngle <= 45) {
const interceptFix: NavFix = {
...computeIntersection(
previousFix,
leg.Course.toTrue(previousFix),
targetFix,
crsToIntercept.reciprocalCourse()
)!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
if (interceptFix.latitude) arc2.push([interceptFix.longitude, interceptFix.latitude]);
break;
}
} }
} }
const interceptFix: NavFix = { // Decide on arc
...computeIntersection(previousFix, leg.Course.toTrue(previousFix), targetFix, crsToIntercept.reciprocalCourse())!, let arc;
isFlyOver: leg.IsFlyOver, if (arc1 && arc1.length > 1) {
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, const endCrs = getGreatCircleBearing(
speed: speed, {
speedConstraint: leg.SpeedLimit, latitude: arc1.at(-1)![1],
altitudeConstraint: leg.Alt, longitude: arc1.at(-1)![0],
}; },
if (interceptFix.latitude) line.push([interceptFix.longitude, interceptFix.latitude]); targetFix
);
const endDist = getPreciseDistance(
{
latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0],
},
targetFix
);
if (endDist <= 25 || (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)) arc = arc1;
else arc = arc2;
} else {
arc = arc2;
}
line.push(...arc);
line.push([targetFix.longitude, targetFix.latitude]); line.push([targetFix.longitude, targetFix.latitude]);

View File

@ -34,12 +34,12 @@ export const TerminatorsCI = (
let time = 0; let time = 0;
if (leg.TurnDir === 'R') { if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees(); lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} else { } else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees(); const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees(); lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} }

View File

@ -1,6 +1,9 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import getPreciseDistance from 'geolib/es/getPreciseDistance';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsDF = ( export const TerminatorsDF = (
leg: DFTerminalEntry, leg: DFTerminalEntry,
@ -9,6 +12,9 @@ export const TerminatorsDF = (
waypoint?: Waypoint waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix); const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const originalCrsFromOrigin = lastCourse;
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
const targetFix: NavFix = { const targetFix: NavFix = {
latitude: leg.WptLat, latitude: leg.WptLat,
@ -21,18 +27,91 @@ export const TerminatorsDF = (
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
const crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix); let crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
let force360 = getPreciseDistance(previousFix, targetFix) < 25;
// Compute overfly // Check if there even is an arc
const [line, _, _lastCourse] = generateOverflyArc( if (force360 || !lastCourse.equal(crsIntoEndpoint)) {
crsIntoEndpoint, // Turn Dir
lastCourse, if (!leg.TurnDir || leg.TurnDir === 'E') {
previousFix, let prov = lastCourse - crsIntoEndpoint;
speed, prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir, leg.TurnDir = prov > 0 ? 'L' : 'R';
previousFix.latitude.equal(targetFix.latitude) && previousFix.longitude.equal(targetFix.longitude) }
);
lastCourse = _lastCourse; // Generate arc
while (!lastCourse.equal(crsIntoEndpoint)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
lastCourse
);
crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);
line.push([arcFix.longitude, arcFix.latitude]);
// made a loop
if (line.length >= 3600) {
if (!force360) {
line.splice(1);
}
force360 = false;
break;
}
}
// Second half
if (force360) {
const temp = crsIntoEndpoint;
crsIntoEndpoint = originalCrsFromOrigin;
lastCourse = temp;
while (!lastCourse.equal(crsIntoEndpoint)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
lastCourse
);
crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
}
line.push([targetFix.longitude, targetFix.latitude]); line.push([targetFix.longitude, targetFix.latitude]);

View File

@ -1,8 +1,8 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser'; import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate'; import { computeTurnRate } from '../utils/computeTurnRate';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
// NOTE: Distance not adjusted for altitude in this demo // NOTE: Distance not adjusted for altitude in this demo
export const TerminatorsFC = ( export const TerminatorsFC = (
@ -38,12 +38,12 @@ export const TerminatorsFC = (
let time = 0; let time = 0;
if (leg.TurnDir === 'R') { if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees(); lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} else { } else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees(); const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees(); lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} }

View File

@ -1,6 +1,6 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -27,7 +27,7 @@ export const TerminatorsFD = (
// Compute distance to fly from arc end // Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid); const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid); const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre(); let remainingDistance = leg.Distance.toMetre();
// Navaid behind us // Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) { if (Math.abs(crsToNavaid - lastCourse) > 90) {

View File

@ -1,6 +1,7 @@
import { generateRFArc } from '../pathGenerators/generateRFArc'; import { generateRFArc } from '../pathGenerators/generateRFArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
// NOTE: Direct entry into an RF does not calculate a usable line, given inbound course is unknown.
export const TerminatorsRF = ( export const TerminatorsRF = (
leg: RFTerminalEntry, leg: RFTerminalEntry,
previousFix: NavFix, previousFix: NavFix,

View File

@ -1,7 +1,11 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import Parser from '../parser';
import { generateTangentArc } from '../pathGenerators/generateTangentArc';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsTF = ( export const TerminatorsTF = (
leg: TFTerminalEntry, leg: TFTerminalEntry,
@ -9,68 +13,117 @@ export const TerminatorsTF = (
lastCourse: number, lastCourse: number,
waypoint?: Waypoint waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const line: LineSegment[] = [];
const targetFix: NavFix = { const targetFix: NavFix = {
latitude: leg.WptLat, latitude: leg.WptLat,
longitude: leg.WptLon, longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined, name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver, isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: computeSpeed(leg, previousFix), speed: speed,
speedConstraint: leg.SpeedLimit, speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
const crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]]; // Compute overfly arc
let arc1: LineSegment[] | null = null;
const trackIntoEndpoint = getGreatCircleBearing(previousFix, targetFix); let arc2: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
if (previousFix.isFlyOver) { if (previousFix.isFlyOver) {
let crsIntoEndpoint = trackIntoEndpoint; arc1 = generateTangentArc(crsIntoEndpoint, lastCourse, previousFix, targetFix, leg.TurnDir);
} else {
arc1 = [[previousFix.longitude, previousFix.latitude]];
}
// Check if there even is an arc if (previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsIntoEndpoint))) {
if (crsIntoEndpoint !== lastCourse) { const turnRate = computeTurnRate(speed, Parser.AC_BANK);
// Turn Dir let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint; // Turn Dir
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov; if (!leg.TurnDir || leg.TurnDir === 'E') {
leg.TurnDir = prov > 0 ? 'L' : 'R'; let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!updatedCrsToIntercept.equal(crsIntoEndpoint)) {
let time = 0;
if (leg.TurnDir === 'R') {
//const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = 0.1; //delta < 1 ? delta : 1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
//const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = 0.1; //delta < 1 ? delta : 1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
} }
// Generate arc const arcFix = computeDestinationPoint(
let condition = false; {
do { latitude: arc2.at(-1)![1],
if (leg.TurnDir === 'R') { longitude: arc2.at(-1)![0],
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); },
lastCourse += delta < 1 ? delta : 1; ((speed / 3600) * time).toMetre(),
lastCourse = lastCourse.normaliseDegrees(); lastCourse
} else { );
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
lastCourse -= delta < 1 ? delta : 1;
lastCourse = lastCourse.normaliseDegrees();
}
const arcFix = computeDestinationPoint( arc2.push([arcFix.longitude, arcFix.latitude]);
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 3600).toMetre(),
lastCourse
);
line.push([arcFix.longitude, arcFix.latitude]); // Update previousFix
previousFix.latitude = arcFix.latitude;
previousFix.longitude = arcFix.longitude;
updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix); let interceptAngle = 0;
if (leg.TurnDir === 'R') interceptAngle = lastCourse - crsIntoEndpoint;
else interceptAngle = crsIntoEndpoint - lastCourse;
if (leg.TurnDir === 'R') { if (interceptAngle > 0 && interceptAngle <= 45) {
condition = crsIntoEndpoint > trackIntoEndpoint; const interceptFix: NavFix = {
} else { ...computeIntersection(previousFix, crsIntoEndpoint, targetFix, crsIntoEndpoint.reciprocalCourse())!,
condition = crsIntoEndpoint < trackIntoEndpoint; isFlyOver: leg.IsFlyOver,
} altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
} while (condition); speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
if (interceptFix.latitude) line.push([interceptFix.longitude, interceptFix.latitude]);
break;
}
} }
} }
// Decide on arc
let arc;
if (arc1 && arc1.length > 1) {
const endCrs = getGreatCircleBearing(
{
latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0],
},
targetFix
);
const endDist = getPreciseDistance(
{
latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0],
},
targetFix
);
if (endDist <= 25 || (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)) arc = arc1;
else arc = arc2;
} else {
arc = arc2;
}
line.push(...arc);
line.push([targetFix.longitude, targetFix.latitude]); line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line]; return [targetFix, line];

View File

@ -1,6 +1,6 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -24,7 +24,7 @@ export const TerminatorsVD = (
// Compute distance to fly from arc end // Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid); const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid); const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre(); let remainingDistance = leg.Distance.toMetre();
// Navaid behind us // Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) { if (Math.abs(crsToNavaid - lastCourse) > 90) {

View File

@ -1,10 +1,10 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser'; import Parser from '../parser';
import { computeIntersection } from '../utils/computeIntersection'; import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate'; import { computeTurnRate } from '../utils/computeTurnRate';
import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts'; import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
// NOTE: No wind adjustments to be made, no clue how *that* would draw // NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVI = ( export const TerminatorsVI = (
@ -35,12 +35,12 @@ export const TerminatorsVI = (
let time = 0; let time = 0;
if (leg.TurnDir === 'R') { if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees(); lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} else { } else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees(); const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1; const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees(); lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate; time = increment / turnRate;
} }

View File

@ -5,7 +5,12 @@
* @param brng2 bearing from Point 2 * @param brng2 bearing from Point 2
* @returns Intersection point * @returns Intersection point
*/ */
export const computeIntersection = (p1: NavFix, brng1: number, p2: NavFix, brng2: number): NavFix | undefined => { 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(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`); if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);
@ -43,7 +48,7 @@ export const computeIntersection = (p1: NavFix, brng1: number, p2: NavFix, brng2
const α2 = θ21 - θ23; // angle 1-2-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) == 0 && Math.sin(α2) == 0) return undefined; // infinite intersections
if (Math.sin(α1) * Math.sin(α2) < 0) return undefined; // ambiguous intersection (antipodal/360°) 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 cosα3 = -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);

View File

@ -75,6 +75,7 @@ export declare global {
ICAO: string; ICAO: string;
FullName: string; FullName: string;
RwyID?: number; RwyID?: number;
Proc: 1 | 2 | 3;
}; };
type NavFix = { type NavFix = {