Last leg types

This commit is contained in:
2025-07-17 20:57:12 +02:00
parent c5cd3c7a0e
commit 2bdb7e78c4
11 changed files with 419 additions and 13 deletions
+30 -6
View File
@@ -11,7 +11,11 @@ import { TerminatorsFA } from './terminators/FA';
import { TerminatorsFC } from './terminators/FC';
import { TerminatorsFD } from './terminators/FD';
import { TerminatorsFM } from './terminators/FM';
import { TerminatorsHA } from './terminators/HA';
import { TerminatorsHF } from './terminators/HF';
import { TerminatorsHM } from './terminators/HM';
import { TerminatorsIF } from './terminators/IF';
import { TerminatorsPI } from './terminators/PI';
import { TerminatorsRF } from './terminators/RF';
import { TerminatorsTF } from './terminators/TF';
import { TerminatorsVA } from './terminators/VA';
@@ -135,7 +139,10 @@ class Parser {
// Main
for (let index = 0; index < procedure.length; index++) {
console.log('Processing leg with ID', procedure[index].ID);
const leg = procedure[index];
leg.Alt = leg.IsMAP ? String(runway.Elevation + 200).padStart(5, '0') : leg.Alt;
const previousFix = navFixes.at(-1)!;
legOptions.isMAP ||= previousFix.IsMAP ?? false;
const waypoint = this.waypoints.find(({ ID }) => ID === leg.WptID);
@@ -208,11 +215,21 @@ class Parser {
navFixes.at(-1)!.isFlyOver = true;
break;
}
case 'HA':
case 'HF':
case 'HM':
console.error('Unknown TrackCode', leg.TrackCode);
case 'HA': {
const [fixToAdd, lineToAdd] = TerminatorsHA(leg as HATerminalEntry, previousFix);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'HF': {
const [fixToAdd, lineToAdd] = TerminatorsHF(leg as HFTerminalEntry, previousFix);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'HM': {
const [fixToAdd, lineToAdd] = TerminatorsHM(leg as HMTerminalEntry, previousFix);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'IF': {
const fixToAdd = TerminatorsIF(leg as RFTerminalEntry, waypoint);
// Only Runway, replace
@@ -222,9 +239,16 @@ class Parser {
}
break;
}
case 'PI':
console.error('Unknown TrackCode', leg.TrackCode);
case 'PI': {
const nextLeg = procedure[index + 1] as CFTerminalEntry;
const _waypoint = this.waypoints.find(({ ID }) => ID === nextLeg.WptID);
const [fixToAdd, lineToAdd] = TerminatorsPI(leg as PITerminalEntry, nextLeg, previousFix, lastCourse, [
waypoint,
_waypoint,
]);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'RF': {
const [fixToAdd, lineToAdd] = TerminatorsRF(leg as RFTerminalEntry, previousFix, lastCourse, waypoint);
if (fixToAdd) {
+1 -1
View File
@@ -80,7 +80,7 @@ export const TerminatorsFC = (
}
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse),
...computeDestinationPoint(arcEnd, leg.Distance.toMetre(), leg.Course.toTrue(refFix)),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
+88
View File
@@ -0,0 +1,88 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
// NOTE: Distance is interpreted as distance and not time
export const TerminatorsHA = (leg: HATerminalEntry, previousFix: NavFix): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const inboundCrs = leg.Course.toTrue(refFix);
const outboundCrs = inboundCrs.reciprocalCourse();
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Generate top arc
let currentCrs = inboundCrs;
while (!currentCrs.equal(outboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (outboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - outboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
const outboundStart = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
leg.Distance.toMetre(),
outboundCrs
);
line.push([outboundStart.longitude, outboundStart.latitude]);
// Generate bottom arc
currentCrs = outboundCrs;
while (!currentCrs.equal(inboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (inboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - inboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
line.push([refFix.longitude, refFix.latitude]);
return [refFix, line];
};
+88
View File
@@ -0,0 +1,88 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
// NOTE: Distance is interpreted as distance and not time
export const TerminatorsHF = (leg: HFTerminalEntry, previousFix: NavFix): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const inboundCrs = leg.Course.toTrue(refFix);
const outboundCrs = inboundCrs.reciprocalCourse();
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Generate top arc
let currentCrs = inboundCrs;
while (!currentCrs.equal(outboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (outboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - outboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
const outboundStart = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
leg.Distance.toMetre(),
outboundCrs
);
line.push([outboundStart.longitude, outboundStart.latitude]);
// Generate bottom arc
currentCrs = outboundCrs;
while (!currentCrs.equal(inboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (inboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - inboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
line.push([refFix.longitude, refFix.latitude]);
return [refFix, line];
};
+88
View File
@@ -0,0 +1,88 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
// NOTE: Distance is interpreted as distance and not time
export const TerminatorsHM = (leg: HMTerminalEntry, previousFix: NavFix): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const inboundCrs = leg.Course.toTrue(refFix);
const outboundCrs = inboundCrs.reciprocalCourse();
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Generate top arc
let currentCrs = inboundCrs;
while (!currentCrs.equal(outboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (outboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - outboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
const outboundStart = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
leg.Distance.toMetre(),
outboundCrs
);
line.push([outboundStart.longitude, outboundStart.latitude]);
// Generate bottom arc
currentCrs = outboundCrs;
while (!currentCrs.equal(inboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (inboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - inboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
line.push([refFix.longitude, refFix.latitude]);
return [refFix, line];
};
+76
View File
@@ -0,0 +1,76 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { generatePerformanceArc } from '../pathGenerators/generatePerformanceArc';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsPI = (
leg: PITerminalEntry,
nextLeg: CFTerminalEntry, // As per NG docs in the TrmLegTypes.json
previousFix: NavFix,
lastCourse: number,
waypoints: [Waypoint?, Waypoint?]
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const originFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoints[0]?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
const endFix: NavFix = {
latitude: nextLeg.WptLat,
longitude: nextLeg.WptLon,
name: waypoints[1]?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
const outboundCrs = leg.Course.toTrue(endFix);
const interceptCrs = nextLeg.Course.toTrue(endFix);
const line: LineSegment[] = [
[originFix.longitude, originFix.latitude],
[endFix.longitude, endFix.latitude],
];
// Outbound end
const outEnd = computeDestinationPoint(
endFix,
((speed / 3600) * 60).toMetre(), // 1min leg
outboundCrs
);
line.push([outEnd.longitude, outEnd.latitude]);
// Arc
line.push(...generatePerformanceArc(outboundCrs.reciprocalCourse(), outboundCrs, outEnd, speed, leg.TurnDir));
// Intercept
const interceptFix = computeIntersection(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
outboundCrs.reciprocalCourse(),
endFix,
interceptCrs.reciprocalCourse()
);
line.push([interceptFix!.longitude, interceptFix!.latitude]);
line.push([endFix!.longitude, endFix!.latitude]);
return [undefined, line];
};
+6
View File
@@ -0,0 +1,6 @@
export declare global {
type HATerminalEntry = Required<
Pick<TerminalEntry, 'WptID' | 'WptLat' | 'WptLon' | 'TurnDir' | 'Course' | 'Distance' | 'Alt'>
> &
TerminalEntry;
}
+6
View File
@@ -0,0 +1,6 @@
export declare global {
type HFTerminalEntry = Required<
Pick<TerminalEntry, 'WptID' | 'WptLat' | 'WptLon' | 'TurnDir' | 'Course' | 'Distance'>
> &
TerminalEntry;
}
+6
View File
@@ -0,0 +1,6 @@
export declare global {
type HMTerminalEntry = Required<
Pick<TerminalEntry, 'WptID' | 'WptLat' | 'WptLon' | 'TurnDir' | 'Course' | 'Distance'>
> &
TerminalEntry;
}
+19
View File
@@ -0,0 +1,19 @@
export declare global {
type PITerminalEntry = Required<
Pick<
TerminalEntry,
| 'WptID'
| 'WptLat'
| 'WptLon'
| 'NavID'
| 'NavLat'
| 'NavLon'
| 'NavBear'
| 'NavDist'
| 'Course'
| 'Distance'
| 'Alt'
>
> &
TerminalEntry;
}