Fix Breakpoints

This commit is contained in:
Kilian Hofmann 2025-07-16 16:16:50 +02:00
parent 287ad8859b
commit e4adf30632
29 changed files with 283 additions and 151 deletions

2
.gitignore vendored
View File

@ -24,3 +24,5 @@ dist-ssr
*.sw? *.sw?
.env .env
NavData/

3
.vscode/launch.json vendored
View File

@ -9,7 +9,8 @@
"request": "launch", "request": "launch",
"name": "Launch Chrome against localhost", "name": "Launch Chrome against localhost",
"url": "http://localhost:3000", "url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/browser" "webRoot": "${workspaceFolder}/browser",
"sourceMaps": true
} }
] ]
} }

View File

@ -530,6 +530,8 @@ LFRN GODA5R SID (cycle 2507, ID 10485)
- Arc center shall be navaid identified by `CenterID`, `CenterLat`, `CenterLon`. - Arc center shall be navaid identified by `CenterID`, `CenterLat`, `CenterLon`.
- Arc and turn shall be flown in direction specified by `TurnDir`. - Arc and turn shall be flown in direction specified by `TurnDir`.
- `Distance` shall be the track miles along the curved path - `Distance` shall be the track miles along the curved path
- `Course` shall be the inbound course of the tangent to the arc at the fix identified by
(`WptID`, `WptLat`, `WptLon`).
### Units ### Units
@ -542,7 +544,6 @@ While similar to an AF, the center point is coded differently.
No radius is specified, but can be inferred based on center point, both endpoints and arc length No radius is specified, but can be inferred based on center point, both endpoints and arc length
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.
Same for the `Course`, which is set, but lacks any documentation.
## Track to Fix (TF) ## Track to Fix (TF)

View File

@ -46,14 +46,15 @@ function App() {
setSelectedAirport={setSelectedAirport} setSelectedAirport={setSelectedAirport}
setSelectedRunway={setSelectedRunway} setSelectedRunway={setSelectedRunway}
setSelectedTerminal={setSelectedTerminal} setSelectedTerminal={setSelectedTerminal}
handleSelection={(selectedTransitions) => handleSelection={(selectedTransitions) => {
setTransitions( const _transitions = selectedTransitions.map((transition) => ({
selectedTransitions.map((transition) => ({ name: transition,
name: transition, data: parser.parse(selectedRunway!, transition),
data: parser.parse(selectedRunway!, transition), }));
})) setTransitions(_transitions);
) setSelectedTransition(_transitions[0]);
} setSelectedChart(undefined);
}}
/> />
)} )}
</div> </div>

View File

@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client';
import App from './App.tsx'; import App from './App.tsx';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import { NavigraphAuthProvider } from './hooks/useNavigraphAuth.tsx'; import { NavigraphAuthProvider } from './contexts/NavigraphAuth/NavigraphAuthProvider.tsx';
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>

View File

@ -1,5 +1,5 @@
import geojson from 'geojson'; import geojson from 'geojson';
import * as geolib from 'geolib'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { TerminatorsAF } from './terminators/AF'; import { TerminatorsAF } from './terminators/AF';
import { TerminatorsCA } from './terminators/CA'; import { TerminatorsCA } from './terminators/CA';
import { TerminatorsCD } from './terminators/CD'; import { TerminatorsCD } from './terminators/CD';
@ -90,7 +90,7 @@ class Parser {
* @param line Line segments * @param line Line segments
*/ */
const updateLastCourse = (line: LineSegment[]) => { const updateLastCourse = (line: LineSegment[]) => {
lastCourse = geolib.getGreatCircleBearing( lastCourse = getGreatCircleBearing(
{ {
latitude: line.at(-2)![1], latitude: line.at(-2)![1],
longitude: line.at(-2)![0], longitude: line.at(-2)![0],
@ -153,7 +153,12 @@ class Parser {
break; break;
} }
case 'CF': { case 'CF': {
const [fixToAdd, lineToAdd] = TerminatorsCF(leg as CFTerminalEntry, previousFix, lastCourse, waypoint); const [fixToAdd, lineToAdd] = TerminatorsCF(
leg as CFTerminalEntry,
{ ...previousFix }, // COPY
lastCourse,
waypoint
);
update(fixToAdd, lineToAdd); update(fixToAdd, lineToAdd);
break; break;
} }
@ -161,7 +166,7 @@ class Parser {
const [fixToAdd, lineToAdd] = TerminatorsCI( const [fixToAdd, lineToAdd] = TerminatorsCI(
leg as CITerminalEntry, leg as CITerminalEntry,
procedure[index + 1], procedure[index + 1],
previousFix, { ...previousFix }, // COPY
lastCourse lastCourse
); );
update(fixToAdd, lineToAdd); update(fixToAdd, lineToAdd);
@ -212,14 +217,14 @@ class Parser {
console.error('Unknown TrackCode', leg.TrackCode); console.error('Unknown TrackCode', leg.TrackCode);
break; break;
case 'RF': { case 'RF': {
const [fixToAdd, lineToAdd] = TerminatorsRF( const [fixToAdd, lineToAdd] = TerminatorsRF(leg as RFTerminalEntry, previousFix, lastCourse, waypoint);
leg as RFTerminalEntry, if (fixToAdd) {
procedure[index + 1], navFixes.push(fixToAdd);
previousFix, lastCourse = (leg as RFTerminalEntry).Course?.toTrue(fixToAdd);
lastCourse, }
waypoint if (lineToAdd) {
); lineSegments.push({ line: lineToAdd });
update(fixToAdd, lineToAdd); }
break; break;
} }
case 'TF': { case 'TF': {
@ -241,7 +246,7 @@ class Parser {
const [fixToAdd, lineToAdd] = TerminatorsVI( const [fixToAdd, lineToAdd] = TerminatorsVI(
leg as VITerminalEntry, leg as VITerminalEntry,
procedure[index + 1], procedure[index + 1],
previousFix, { ...previousFix }, // COPY
lastCourse lastCourse
); );
update(fixToAdd, lineToAdd); update(fixToAdd, lineToAdd);

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
/** /**
* @param crsIntoEndpoint Course into arc endpoint * @param crsIntoEndpoint Course into arc endpoint
@ -39,7 +39,7 @@ export const generateAFArc = (
} }
if (crsFromOrigin === crsIntoEndpoint) break; if (crsFromOrigin === crsIntoEndpoint) break;
const arcFix = geolib.computeDestinationPoint(center, radius.toMetre(), crsFromOrigin); const arcFix = computeDestinationPoint(center, radius.toMetre(), crsFromOrigin);
line.push([arcFix.longitude, arcFix.latitude]); line.push([arcFix.longitude, arcFix.latitude]);
} }

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generatePerformanceArc } from './generatePerformanceArc'; import { generatePerformanceArc } from './generatePerformanceArc';
/** /**
@ -31,7 +31,7 @@ export const generateOverflyArc = (
// Get arc endpoint and crs into arc endpoint // Get arc endpoint and crs into arc endpoint
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] }; const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
if (line.length > 1) { if (line.length > 1) {
crsFromOrigin = geolib.getGreatCircleBearing( crsFromOrigin = getGreatCircleBearing(
{ {
latitude: line.at(-2)![1], latitude: line.at(-2)![1],
longitude: line.at(-2)![0], longitude: line.at(-2)![0],

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser'; import Parser from '../parser';
import { computeTurnRate } from '../utils/computeTurnRate'; import { computeTurnRate } from '../utils/computeTurnRate';
@ -48,7 +48,7 @@ export const generatePerformanceArc = (
time = increment / turnRate; time = increment / turnRate;
} }
const arcFix = geolib.computeDestinationPoint( const arcFix = computeDestinationPoint(
{ {
latitude: line.at(-1)![1], latitude: line.at(-1)![1],
longitude: line.at(-1)![0], longitude: line.at(-1)![0],
@ -80,7 +80,7 @@ export const generatePerformanceArc = (
time = increment / turnRate; time = increment / turnRate;
} }
const arcFix = geolib.computeDestinationPoint( const arcFix = computeDestinationPoint(
{ {
latitude: line.at(-1)![1], latitude: line.at(-1)![1],
longitude: line.at(-1)![0], longitude: line.at(-1)![0],

View File

@ -1,4 +1,5 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
/** /**
* @param crsIntoEndpoint Course into arc endpoint * @param crsIntoEndpoint Course into arc endpoint
@ -17,7 +18,7 @@ export const generateRFArc = (
) => { ) => {
const line: LineSegment[] = [[start.longitude, start.latitude]]; const line: LineSegment[] = [[start.longitude, start.latitude]];
if (crsIntoEndpoint !== crsIntoOrigin) { if (!crsIntoEndpoint.equal(crsIntoOrigin)) {
// Turn Dir // Turn Dir
if (!turnDir || turnDir === 'E') { if (!turnDir || turnDir === 'E') {
let prov = crsIntoOrigin - crsIntoEndpoint; let prov = crsIntoOrigin - crsIntoEndpoint;
@ -35,7 +36,7 @@ export const generateRFArc = (
crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees(); crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees();
} }
const arcRad = geolib.getDistance(center, start); const arcRad = getDistance(center, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse(); crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
@ -46,7 +47,7 @@ export const generateRFArc = (
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1; crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
} }
while (crsOrthogonalOnOrigin !== 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 < 1 ? delta : 1;
@ -57,7 +58,7 @@ export const generateRFArc = (
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} }
const arcFix = geolib.computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin); const arcFix = computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin);
line.push([arcFix.longitude, arcFix.latitude]); line.push([arcFix.longitude, arcFix.latitude]);
} }

View File

@ -1,4 +1,5 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import { computeIntersection } from '../utils/computeIntersection'; import { computeIntersection } from '../utils/computeIntersection';
/** /**
@ -62,7 +63,7 @@ export const generateTangentArc = (
crsOrthogonalOnEndpoint crsOrthogonalOnEndpoint
); );
if (!arcCenter) return null; if (!arcCenter) return null;
const arcRad = geolib.getDistance(arcCenter, start); const arcRad = getDistance(arcCenter, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse(); crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
@ -84,7 +85,7 @@ export const generateTangentArc = (
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} }
const arcFix = geolib.computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin); const arcFix = computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin);
line.push([arcFix.longitude, arcFix.latitude]); line.push([arcFix.longitude, arcFix.latitude]);
} }

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generatePerformanceArc } from './generatePerformanceArc'; import { generatePerformanceArc } from './generatePerformanceArc';
import { generateTangentArc } from './generateTangentArc'; import { generateTangentArc } from './generateTangentArc';
@ -32,7 +32,7 @@ export const handleTurnAtFix = (
// Decide on arc // Decide on arc
let arc; let arc;
if (arc1) { if (arc1) {
const endCrs = geolib.getGreatCircleBearing( const endCrs = getGreatCircleBearing(
{ {
latitude: arc1.at(-1)![1], latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0], longitude: arc1.at(-1)![0],
@ -48,9 +48,8 @@ export const handleTurnAtFix = (
line.push(...arc); line.push(...arc);
line.push([end.longitude, end.latitude]); line.push([end.longitude, end.latitude]);
} }
// FIXME: Procedural turn // Procedural turn
else { else {
// Direct line for now
line.push([start.longitude, start.latitude], [end.longitude, end.latitude]); line.push([start.longitude, start.latitude], [end.longitude, end.latitude]);
} }

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateAFArc } from '../pathGenerators/generateAFArc'; import { generateAFArc } from '../pathGenerators/generateAFArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -18,7 +18,7 @@ export const TerminatorsAF = (
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
const arcEndCrs = geolib.getGreatCircleBearing( const arcEndCrs = getGreatCircleBearing(
{ {
latitude: leg.NavLat, latitude: leg.NavLat,
longitude: leg.NavLon, longitude: leg.NavLon,

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser'; import Parser from '../parser';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -17,7 +17,7 @@ export const TerminatorsCA = (
// Compute intercept of crs from arc end and expected altitude // Compute intercept of crs from arc end and expected altitude
const targetFix: NavFix = { const targetFix: NavFix = {
...geolib.computeDestinationPoint( ...computeDestinationPoint(
arcEnd, arcEnd,
( (
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) * ((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *

View File

@ -1,4 +1,6 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -20,8 +22,8 @@ export const TerminatorsCD = (
lastCourse = _lastCourse; lastCourse = _lastCourse;
// Compute distance to fly from arc end // Compute distance to fly from arc end
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid); const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = geolib.getDistance(arcEnd, navaid); const distToNavaid = getDistance(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) {
@ -35,7 +37,7 @@ export const TerminatorsCD = (
// Compute intercept of crs from arc end and distance // Compute intercept of crs from arc end and distance
const targetFix: NavFix = { const targetFix: NavFix = {
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse), ...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(), name: leg.Distance.toString(),
isFlyOver: true, isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,5 +1,9 @@
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsCF = ( export const TerminatorsCF = (
leg: CFTerminalEntry, leg: CFTerminalEntry,
@ -8,6 +12,8 @@ export const TerminatorsCF = (
waypoint?: Waypoint waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix); const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
const targetFix: NavFix = { const targetFix: NavFix = {
latitude: leg.WptLat, latitude: leg.WptLat,
@ -19,17 +25,70 @@ export const TerminatorsCF = (
speedConstraint: leg.SpeedLimit, speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
const crsToIntercept = leg.Course.toTrue(targetFix);
// Compute arc // Compute overfly arc
const line = handleTurnAtFix( if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) {
leg.Course.toTrue(previousFix), const turnRate = computeTurnRate(speed, Parser.AC_BANK);
leg.Course.toTrue(previousFix), let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
lastCourse,
previousFix, // Turn Dir
targetFix, if (!leg.TurnDir || leg.TurnDir === 'E') {
speed, let prov = lastCourse - crsIntoEndpoint;
leg.TurnDir prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
); leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!updatedCrsToIntercept.equal(crsToIntercept)) {
let time = 0;
if (leg.TurnDir === 'R') {
//const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = 1; //delta < 1 ? delta : 1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
//const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = 1; //delta < 1 ? delta : 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
);
line.push([arcFix.longitude, arcFix.latitude]);
// Update previousFix
previousFix.latitude = arcFix.latitude;
previousFix.longitude = arcFix.longitude;
updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
let interceptAngle = 0;
if (leg.TurnDir === 'R') Math.abs((interceptAngle = lastCourse - crsToIntercept));
else interceptAngle = Math.abs(crsToIntercept - lastCourse);
if (interceptAngle >= 45) break;
}
}
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) line.push([interceptFix.longitude, interceptFix.latitude]);
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line]; return [targetFix, line];
}; };

View File

@ -1,6 +1,9 @@
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
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 { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts'; import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
export const TerminatorsCI = ( export const TerminatorsCI = (
@ -9,12 +12,57 @@ export const TerminatorsCI = (
previousFix: NavFix, previousFix: NavFix,
lastCourse: number lastCourse: number
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const speed = computeSpeed(leg, previousFix); const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const [crsToIntercept, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Compute overfly arc
if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const updatedCrsToIntercept = getGreatCircleBearing(previousFix, nextFix);
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!lastCourse.equal(crsIntoEndpoint) && !updatedCrsToIntercept.equal(crsToIntercept)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 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
);
line.push([arcFix.longitude, arcFix.latitude]);
// Update previousFix
previousFix.latitude = arcFix.latitude;
previousFix.longitude = arcFix.longitude;
}
}
// Compute intercept fix
const interceptFix: NavFix = { const interceptFix: NavFix = {
...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!, ...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crsToIntercept)!,
isFlyOver: leg.IsFlyOver, isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed, speed: speed,
@ -22,29 +70,7 @@ export const TerminatorsCI = (
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
// Compute arc line.push([interceptFix.longitude, interceptFix.latitude]);
const line = handleTurnAtFix(
crs,
leg.Course.toTrue(nextFix),
lastCourse,
previousFix,
interceptFix,
speed,
leg.TurnDir
);
// Recompute intercept
const interceptPoint2 = computeIntersection(
{ latitude: line.at(-2)![1], longitude: line.at(-2)![0] },
leg.Course.toTrue(nextFix),
nextFix,
crs
);
if (interceptPoint2)
return [
{ ...interceptFix, ...interceptPoint2 },
[...line.slice(0, -1), [interceptPoint2.longitude, interceptPoint2.latitude]],
];
return [interceptFix, line]; return [interceptFix, line];
}; };

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -21,7 +21,7 @@ export const TerminatorsDF = (
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
const crsIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix); const crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
// Compute overfly // Compute overfly
const [line, _, _lastCourse] = generateOverflyArc( const [line, _, _lastCourse] = generateOverflyArc(

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser'; import Parser from '../parser';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -21,7 +21,7 @@ export const TerminatorsFA = (
// Compute intercept of crs from arc end and expected altitude // Compute intercept of crs from arc end and expected altitude
const targetFix: NavFix = { const targetFix: NavFix = {
...geolib.computeDestinationPoint( ...computeDestinationPoint(
arcEnd, arcEnd,
( (
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) * ((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *

View File

@ -1,7 +1,8 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
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 = (
@ -47,7 +48,7 @@ export const TerminatorsFC = (
time = increment / turnRate; time = increment / turnRate;
} }
const arcFix = geolib.computeDestinationPoint( const arcFix = computeDestinationPoint(
{ {
latitude: line.at(-1)![1], latitude: line.at(-1)![1],
longitude: line.at(-1)![0], longitude: line.at(-1)![0],
@ -69,7 +70,7 @@ export const TerminatorsFC = (
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] }; const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
if (line.length > 1) { if (line.length > 1) {
lastCourse = geolib.getGreatCircleBearing( lastCourse = getGreatCircleBearing(
{ {
latitude: line.at(-2)![1], latitude: line.at(-2)![1],
longitude: line.at(-2)![0], longitude: line.at(-2)![0],
@ -79,7 +80,7 @@ export const TerminatorsFC = (
} }
const targetFix: NavFix = { const targetFix: NavFix = {
...geolib.computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse), ...computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse),
name: leg.Distance.toString(), name: leg.Distance.toString(),
isFlyOver: true, isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,4 +1,6 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -24,8 +26,8 @@ export const TerminatorsFD = (
lastCourse = _lastCourse; lastCourse = _lastCourse;
// Compute distance to fly from arc end // Compute distance to fly from arc end
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid); const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = geolib.getDistance(arcEnd, navaid); const distToNavaid = getDistance(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) {
@ -39,7 +41,7 @@ export const TerminatorsFD = (
// Compute intercept of crs from arc end and distance // Compute intercept of crs from arc end and distance
const targetFix: NavFix = { const targetFix: NavFix = {
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse), ...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(), name: leg.Distance.toString(),
isFlyOver: true, isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -9,7 +9,7 @@ export const TerminatorsFM = (
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix); const speed = computeSpeed(leg, previousFix);
const endpoint = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix)); const endpoint = computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
const line = handleTurnAtFix( const line = handleTurnAtFix(
leg.Course.toTrue(previousFix), leg.Course.toTrue(previousFix),

View File

@ -1,10 +1,8 @@
import { generateRFArc } from '../pathGenerators/generateRFArc'; import { generateRFArc } from '../pathGenerators/generateRFArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
export const TerminatorsRF = ( export const TerminatorsRF = (
leg: RFTerminalEntry, leg: RFTerminalEntry,
nextLeg: TerminalEntry,
previousFix: NavFix, previousFix: NavFix,
lastCourse: number, lastCourse: number,
waypoint?: Waypoint waypoint?: Waypoint
@ -20,10 +18,8 @@ export const TerminatorsRF = (
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
const [crs] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const line = generateRFArc( const line = generateRFArc(
crs, leg.Course.toTrue(targetFix),
lastCourse, lastCourse,
previousFix, previousFix,
{ latitude: leg.CenterLat, longitude: leg.CenterLon }, { latitude: leg.CenterLat, longitude: leg.CenterLon },

View File

@ -1,6 +1,7 @@
import * as geolib from 'geolib'; 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 computeDestinationPoint from 'geolib/es/computeDestinationPoint';
export const TerminatorsTF = ( export const TerminatorsTF = (
leg: TFTerminalEntry, leg: TFTerminalEntry,
@ -21,7 +22,7 @@ export const TerminatorsTF = (
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]]; const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
const trackIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix); const trackIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
if (previousFix.isFlyOver) { if (previousFix.isFlyOver) {
let crsIntoEndpoint = trackIntoEndpoint; let crsIntoEndpoint = trackIntoEndpoint;
@ -48,7 +49,7 @@ export const TerminatorsTF = (
lastCourse = lastCourse.normaliseDegrees(); lastCourse = lastCourse.normaliseDegrees();
} }
const arcFix = geolib.computeDestinationPoint( const arcFix = computeDestinationPoint(
{ {
latitude: line.at(-1)![1], latitude: line.at(-1)![1],
longitude: line.at(-1)![0], longitude: line.at(-1)![0],
@ -59,7 +60,7 @@ export const TerminatorsTF = (
line.push([arcFix.longitude, arcFix.latitude]); line.push([arcFix.longitude, arcFix.latitude]);
crsIntoEndpoint = geolib.getGreatCircleBearing(arcFix, targetFix); crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);
if (leg.TurnDir === 'R') { if (leg.TurnDir === 'R') {
condition = crsIntoEndpoint > trackIntoEndpoint; condition = crsIntoEndpoint > trackIntoEndpoint;

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser'; import Parser from '../parser';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -18,7 +18,7 @@ export const TerminatorsVA = (
// Compute intercept of crs from arc end and expected altitude // Compute intercept of crs from arc end and expected altitude
const targetFix: NavFix = { const targetFix: NavFix = {
...geolib.computeDestinationPoint( ...computeDestinationPoint(
arcEnd, arcEnd,
( (
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) * ((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *

View File

@ -1,4 +1,6 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -21,8 +23,8 @@ export const TerminatorsVD = (
lastCourse = _lastCourse; lastCourse = _lastCourse;
// Compute distance to fly from arc end // Compute distance to fly from arc end
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid); const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = geolib.getDistance(arcEnd, navaid); const distToNavaid = getDistance(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) {
@ -36,7 +38,7 @@ export const TerminatorsVD = (
// Compute intercept of crs from arc end and distance // Compute intercept of crs from arc end and distance
const targetFix: NavFix = { const targetFix: NavFix = {
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse), ...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(), name: leg.Distance.toString(),
isFlyOver: true, isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,7 +1,10 @@
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
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 { 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 = (
@ -10,12 +13,57 @@ export const TerminatorsVI = (
previousFix: NavFix, previousFix: NavFix,
lastCourse: number lastCourse: number
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const speed = computeSpeed(leg, previousFix); const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const [crsToIntercept, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Compute overfly arc
if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const updatedCrsToIntercept = getGreatCircleBearing(previousFix, nextFix);
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!lastCourse.equal(crsIntoEndpoint) && !updatedCrsToIntercept.equal(crsToIntercept)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 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
);
line.push([arcFix.longitude, arcFix.latitude]);
// Update previousFix
previousFix.latitude = arcFix.latitude;
previousFix.longitude = arcFix.longitude;
}
}
// Compute INTC
const interceptFix: NavFix = { const interceptFix: NavFix = {
...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!, ...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crsToIntercept)!,
isFlyOver: leg.IsFlyOver, isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed, speed: speed,
@ -23,28 +71,7 @@ export const TerminatorsVI = (
altitudeConstraint: leg.Alt, altitudeConstraint: leg.Alt,
}; };
const line = handleTurnAtFix( line.push([interceptFix.longitude, interceptFix.latitude]);
crs,
leg.Course.toTrue(nextFix),
lastCourse,
previousFix,
interceptFix,
speed,
leg.TurnDir
);
// Intercept based on previous intercept
const interceptPoint2 = computeIntersection(
{ latitude: line.at(-2)![1], longitude: line.at(-2)![0] },
leg.Course.toTrue(nextFix),
nextFix,
crs
);
if (interceptPoint2)
return [
{ ...interceptFix, ...interceptPoint2 },
[...line.slice(0, -1), [interceptPoint2.longitude, interceptPoint2.latitude]],
];
return [interceptFix, line]; return [interceptFix, line];
}; };

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
import { computeSpeed } from '../utils/computeSpeed'; import { computeSpeed } from '../utils/computeSpeed';
@ -10,7 +10,7 @@ export const TerminatorsVM = (
): [NavFix?, LineSegment[]?] => { ): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix); const speed = computeSpeed(leg, previousFix);
const endpoint = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix)); const endpoint = computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
const line = handleTurnAtFix( const line = handleTurnAtFix(
leg.Course.toTrue(previousFix), leg.Course.toTrue(previousFix),

View File

@ -1,4 +1,4 @@
import * as geolib from 'geolib'; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
/** /**
* @param leg Leg to examine * @param leg Leg to examine
@ -18,9 +18,9 @@ export const getCourseAndFixForIntercepts = (leg: TerminalEntry, origin: NavFix)
return [_leg.Course.toTrue(fix), fix]; return [_leg.Course.toTrue(fix), fix];
} }
case 'TF': { case 'TF': {
const _leg = leg as FMTerminalEntry; const _leg = leg as TFTerminalEntry;
return [ return [
geolib.getGreatCircleBearing(origin, { getGreatCircleBearing(origin, {
latitude: _leg.WptLat, latitude: _leg.WptLat,
longitude: _leg.WptLon, longitude: _leg.WptLon,
}), }),
@ -33,10 +33,15 @@ export const getCourseAndFixForIntercepts = (leg: TerminalEntry, origin: NavFix)
return [_leg.Course.reciprocalCourse().toTrue(fix), fix]; return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
} }
case 'DF': { case 'DF': {
const _leg = leg as FMTerminalEntry; const _leg = leg as DFTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon }; const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [-1, fix]; return [-1, fix];
} }
case 'RF': {
const _leg = leg as RFTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.toTrue(fix), fix];
}
default: { default: {
return [-1, origin]; return [-1, origin];
} }