diff --git a/README.md b/README.md index 5a965af..ca8a312 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ LGAV BIBE1L SID (Cycle 2507, ID 10654) ### Instructions - From preceding fix, fly along `Course` until reaching `Alt`. +- If preceding fix was overfly, offset outbound `Course` to end of turn (tangent). +- Earliest leg terminations is at point of tangent. ### Units @@ -84,6 +86,8 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657) - From preceding fix, fly along `Course` until reaching `Distance` from the navaid identified by `NavID`. +- If preceding fix was overfly, offset outbound `Course` to end of turn (tangent). +- Earliest leg terminations is at point of tangent. ### Units @@ -116,6 +120,7 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657) - From preceding fix, fly `Course` to the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`). +- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`. ### Units @@ -208,7 +213,7 @@ LGAV KOR1D SID (Cycle 2507, ID 10679) - `IsFlyOver`: Boolean indicating if fix is a mandatory fly over. -## Fix to Altitude (FA) +## Track from Fix to Altitude (FA) ### Example @@ -227,6 +232,8 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657) - From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Alt`. +- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`. +- Earliest leg terminations is at point of intercept. ### Units @@ -261,6 +268,9 @@ LIED CAR6F SID (Cycle 2507, ID 11798) - From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Distance`. +- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`. +- Earliest leg terminations is at point of intercept. + ### Units @@ -292,8 +302,10 @@ LGAV BIBE2T SID (Cycle 2507, ID 10659) ### Instructions - From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* - (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until - reaching `Distance` from navaid identified by `NavID`. + (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Distance` from + navaid identified by `NavID`. +- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`. +- Earliest leg terminations is at point of intercept. ### Units diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..e1c6495 --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +- Revisit CA/FA/VA legs +- Revisit CD/FD/VD/FC legs \ No newline at end of file diff --git a/browser/src/App.tsx b/browser/src/App.tsx index 4545c5b..2280d23 100644 --- a/browser/src/App.tsx +++ b/browser/src/App.tsx @@ -8,7 +8,9 @@ import L from "leaflet"; const parser = await Parser.instance(); -const terminals = [10394, 10395, 10475, 10480, 10482, 10485, 10653, 10654]; +const terminals = [ + 10394, 10395, 10475, 10480, 10482, 10485, 10653, 10654, 10657, 10659, +]; function App() { const [selectedTerminal, setSelectedTerminal] = useState(terminals[0]); @@ -106,6 +108,7 @@ function App() { display: "flex", flexDirection: "column", gap: "10px", + width: "max-content", }} > {terminals.map((terminal) => ( @@ -113,14 +116,18 @@ function App() { key={terminal} style={{ display: "flex", - gap: "10px", + flexDirection: "column", background: selectedTerminal === terminal ? "#eeeeee" : undefined, }} + onClick={() => setSelectedTerminal(terminal)} > -
ID {terminal}
-
+
+ {(() => {
+ const t = parser.terminals.find(({ ID }) => ID === terminal);
+ return `${t?.ICAO} - ${t?.FullName}`;
+ })()}
+
+ ({terminal})
))}
diff --git a/browser/src/parser/node.ts b/browser/src/parser/node.ts
index 283e6e6..39d7f14 100644
--- a/browser/src/parser/node.ts
+++ b/browser/src/parser/node.ts
@@ -14,4 +14,4 @@ fetch = async (path: string) => {
const parser = await Parser.instance();
-console.log(JSON.stringify(await parser.parse(10654)));
+console.log(JSON.stringify(await parser.parse(10657)));
diff --git a/browser/src/parser/parser.ts b/browser/src/parser/parser.ts
index 377e4a7..6ce96c1 100644
--- a/browser/src/parser/parser.ts
+++ b/browser/src/parser/parser.ts
@@ -14,25 +14,29 @@ import { TerminatorsVD } from "./terminators/VD.ts";
import { TerminatorsRF } from "./terminators/RF.ts";
import { TerminatorsCA } from "./terminators/CA.ts";
import { TerminatorsDF } from "./terminators/DF.ts";
+import { TerminatorsFD } from "./terminators/FD.ts";
+import { TerminatorsFA } from "./terminators/FA.ts";
+import { TerminatorsCD } from "./terminators/CD.ts";
class Parser {
private static _instance: Parser;
- private waypoints: Waypoint[];
- private runways: Runway[];
- private terminals: Terminal[];
+ private _waypoints: Waypoint[];
+ private _runways: Runway[];
+ private _terminals: Terminal[];
public static AC_SPEED = 250;
- public static AC_VS = 1500;
+ public static AC_BANK = 30;
+ public static AC_VS = 1400;
private constructor(
waypoints: Waypoint[],
runways: Runway[],
terminals: Terminal[]
) {
- this.waypoints = waypoints;
- this.runways = runways;
- this.terminals = terminals;
+ this._waypoints = waypoints;
+ this._runways = runways;
+ this._terminals = terminals;
}
public static instance = async () => {
@@ -47,6 +51,16 @@ class Parser {
return Parser._instance;
};
+ public get terminals() {
+ return this._terminals;
+ }
+ public get waypoints() {
+ return this._waypoints;
+ }
+ public get runways() {
+ return this._runways;
+ }
+
public parse = async (terminalID: number) => {
// Private functions
/**
@@ -132,9 +146,15 @@ class Parser {
update(fixToAdd, lineToAdd);
break;
}
- case "CD":
- console.error("Unknown TrackCode", leg.TrackCode);
+ case "CD": {
+ const [fixToAdd, lineToAdd] = TerminatorsCD(
+ leg as CDTerminalEntry,
+ previousFix,
+ lastCourse
+ );
+ update(fixToAdd, lineToAdd);
break;
+ }
case "CF": {
const [fixToAdd, lineToAdd] = TerminatorsCF(
leg as CFTerminalEntry,
@@ -174,11 +194,27 @@ class Parser {
update(fixToAdd, lineToAdd);
break;
}
- case "FA":
+ case "FA": {
+ const [fixToAdd, lineToAdd] = TerminatorsFA(
+ leg as FATerminalEntry,
+ previousFix,
+ lastCourse
+ );
+ update(fixToAdd, lineToAdd);
+ break;
+ }
case "FC":
- case "FD":
console.error("Unknown TrackCode", leg.TrackCode);
break;
+ case "FD": {
+ const [fixToAdd, lineToAdd] = TerminatorsFD(
+ leg as FDTerminalEntry,
+ previousFix,
+ lastCourse
+ );
+ update(fixToAdd, lineToAdd);
+ break;
+ }
case "FM": {
const [fixToAdd, lineToAdd] = TerminatorsFM(
leg as FMTerminalEntry,
diff --git a/browser/src/parser/pathGenerators/generatePerformanceArc.ts b/browser/src/parser/pathGenerators/generatePerformanceArc.ts
index 289b76e..a839c07 100644
--- a/browser/src/parser/pathGenerators/generatePerformanceArc.ts
+++ b/browser/src/parser/pathGenerators/generatePerformanceArc.ts
@@ -1,10 +1,12 @@
import * as geolib from "geolib";
import Parser from "../parser.ts";
+import { computeTurnRate } from "../utils/computeTurnRate.ts";
/**
* @param crsIntoEndpoint Course into arc endpoint
* @param crsFromOrigin Course from arc origin point
* @param start Arc origin point
+ * @param speed Speed within arc
* @param turnDir Turn direction
* @returns Line segments
*/
@@ -12,12 +14,15 @@ export const generatePerformanceArc = (
crsIntoEndpoint: number,
crsFromOrigin: number,
start: NavFix,
+ speed: number,
turnDir?: TurnDirection
) => {
+ const turnRate = computeTurnRate(speed, Parser.AC_BANK);
+
const line: LineSegment[] = [[start.longitude, start.latitude]];
// Check if there even is an arc
- if (crsIntoEndpoint !== crsFromOrigin) {
+ if (!crsFromOrigin.equal(crsIntoEndpoint)) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = crsFromOrigin - crsIntoEndpoint;
@@ -26,15 +31,18 @@ export const generatePerformanceArc = (
}
// Generate arc
- while (crsFromOrigin !== crsIntoEndpoint) {
+ while (!crsFromOrigin.equal(crsIntoEndpoint)) {
+ let time = 0;
if (turnDir === "R") {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
- crsFromOrigin += delta < 1 ? delta : 1;
- crsFromOrigin = crsFromOrigin.normaliseDegrees();
+ const increment = delta < 1 ? delta : 1;
+ crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
+ time = increment / turnRate;
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
- crsFromOrigin -= delta < 1 ? delta : 1;
- crsFromOrigin = crsFromOrigin.normaliseDegrees();
+ const increment = delta < 1 ? delta : 1;
+ crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees();
+ time = increment / turnRate;
}
const arcFix = geolib.computeDestinationPoint(
@@ -42,7 +50,7 @@ export const generatePerformanceArc = (
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
- ((start.speed ?? Parser.AC_SPEED) / 3600).toMetre(),
+ ((speed / 3600) * time).toMetre(),
crsFromOrigin
);
diff --git a/browser/src/parser/pathGenerators/handleTurnAtFix.ts b/browser/src/parser/pathGenerators/handleTurnAtFix.ts
index 9f54d60..7845816 100644
--- a/browser/src/parser/pathGenerators/handleTurnAtFix.ts
+++ b/browser/src/parser/pathGenerators/handleTurnAtFix.ts
@@ -1,6 +1,7 @@
import * as geolib from "geolib";
import { generateTangentArc } from "./generateTangentArc.ts";
import { generatePerformanceArc } from "./generatePerformanceArc.ts";
+import Parser from "../parser.ts";
/**
* @param crsIntoEndpoint Course into endpoint
@@ -35,6 +36,7 @@ export const handleTurnAtFix = (
crsIntoIntercept,
crsFromOrigin,
start,
+ start.speed ? start.speed : Parser.AC_SPEED,
turnDir
);
diff --git a/browser/src/parser/terminators/CA.ts b/browser/src/parser/terminators/CA.ts
index 36cd2fb..fb6ac9b 100644
--- a/browser/src/parser/terminators/CA.ts
+++ b/browser/src/parser/terminators/CA.ts
@@ -14,7 +14,7 @@ export const TerminatorsCA = (
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
Parser.AC_VS) *
- (Parser.AC_SPEED / 60)
+ ((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
).toMetre(),
leg.Course.toTrue(previousFix)
),
diff --git a/browser/src/parser/terminators/CD.ts b/browser/src/parser/terminators/CD.ts
new file mode 100644
index 0000000..d73f20b
--- /dev/null
+++ b/browser/src/parser/terminators/CD.ts
@@ -0,0 +1,67 @@
+import * as geolib from "geolib";
+import { computeSpeed } from "../utils/computeSpeed.ts";
+import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
+
+// NOTE: Distance not adjusted for altitude in this demo
+export const TerminatorsCD = (
+ leg: CDTerminalEntry,
+ previousFix: NavFix,
+ lastCourse: number
+): [NavFix?, LineSegment[]?] => {
+ const navaid = {
+ latitude: leg.NavLat,
+ longitude: leg.NavLon,
+ };
+ const speed = computeSpeed(leg, previousFix);
+
+ let line: LineSegment[] = [];
+ if (previousFix.isFlyOver) {
+ const crsIntoEndpoint = leg.Course.toTrue(previousFix);
+
+ line = generatePerformanceArc(
+ crsIntoEndpoint,
+ lastCourse,
+ previousFix,
+ speed,
+ leg.TurnDir
+ );
+ } else {
+ line.push([previousFix.longitude, previousFix.latitude]);
+ }
+
+ const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
+ if (line.length > 1) {
+ lastCourse = geolib.getGreatCircleBearing(
+ {
+ latitude: line.at(-2)![1],
+ longitude: line.at(-2)![0],
+ },
+ arcEnd
+ );
+ }
+
+ const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
+ const distToNavaid = geolib.getDistance(arcEnd, navaid);
+ let remainingDistance = leg.Distance.toMetre();
+ // Navaid behind us
+ if (Math.abs(crsToNavaid - lastCourse) > 90) {
+ remainingDistance -= distToNavaid;
+ }
+ // Navaid in front of us
+ else {
+ remainingDistance = distToNavaid - remainingDistance;
+ }
+ const targetFix: NavFix = {
+ ...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
+ name: leg.Distance.toString(),
+ isFlyOver: true,
+ altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
+ speed: computeSpeed(leg, previousFix),
+ speedConstraint: leg.SpeedLimit,
+ altitudeConstraint: leg.Alt,
+ };
+
+ line.push([targetFix.longitude, targetFix.latitude]);
+
+ return [targetFix, line];
+};
diff --git a/browser/src/parser/terminators/DF.ts b/browser/src/parser/terminators/DF.ts
index b05a5d9..326b289 100644
--- a/browser/src/parser/terminators/DF.ts
+++ b/browser/src/parser/terminators/DF.ts
@@ -1,6 +1,6 @@
import * as geolib from "geolib";
import { computeSpeed } from "../utils/computeSpeed.ts";
-import Parser from "../parser.ts";
+import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
export const TerminatorsDF = (
leg: DFTerminalEntry,
@@ -8,58 +8,35 @@ export const TerminatorsDF = (
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
+ const speed = computeSpeed(leg, previousFix);
+
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
- speed: computeSpeed(leg, previousFix),
+ speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
- const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
-
+ let line: LineSegment[] = [];
if (previousFix.isFlyOver) {
const crsIntoEndpoint = geolib.getGreatCircleBearing(
previousFix,
targetFix
);
- // Check if there even is an arc
- if (crsIntoEndpoint !== lastCourse) {
- // 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 !== crsIntoEndpoint) {
- if (leg.TurnDir === "R") {
- const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
- lastCourse += delta < 1 ? delta : 1;
- lastCourse = lastCourse.normaliseDegrees();
- } else {
- const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
- lastCourse -= delta < 1 ? delta : 1;
- lastCourse = lastCourse.normaliseDegrees();
- }
-
- const arcFix = geolib.computeDestinationPoint(
- {
- latitude: line.at(-1)![1],
- longitude: line.at(-1)![0],
- },
- ((previousFix.speed ?? Parser.AC_SPEED) / 3600).toMetre(),
- lastCourse
- );
-
- line.push([arcFix.longitude, arcFix.latitude]);
- }
- }
+ line = generatePerformanceArc(
+ crsIntoEndpoint,
+ lastCourse,
+ previousFix,
+ speed,
+ leg.TurnDir
+ );
+ } else {
+ line.push([previousFix.longitude, previousFix.latitude]);
}
line.push([targetFix.longitude, targetFix.latitude]);
diff --git a/browser/src/parser/terminators/FA.ts b/browser/src/parser/terminators/FA.ts
new file mode 100644
index 0000000..00ad68b
--- /dev/null
+++ b/browser/src/parser/terminators/FA.ts
@@ -0,0 +1,39 @@
+import * as geolib from "geolib";
+import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
+import { computeSpeed } from "../utils/computeSpeed.ts";
+import Parser from "../parser.ts";
+
+export const TerminatorsFA = (
+ leg: FATerminalEntry,
+ previousFix: NavFix,
+ lastCourse: number
+): [NavFix?, LineSegment[]?] => {
+ const targetFix: NavFix = {
+ ...geolib.computeDestinationPoint(
+ { latitude: leg.WptLat, longitude: leg.WptLon },
+ (
+ ((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
+ Parser.AC_VS) *
+ ((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
+ ).toMetre(),
+ leg.Course.toTrue(previousFix)
+ ),
+ name: leg.Alt,
+ isFlyOver: true,
+ altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
+ speed: computeSpeed(leg, previousFix),
+ speedConstraint: leg.SpeedLimit,
+ altitudeConstraint: leg.Alt,
+ };
+
+ const line = handleTurnAtFix(
+ leg.Course.toTrue(previousFix),
+ leg.Course.toTrue(previousFix),
+ lastCourse,
+ previousFix,
+ targetFix,
+ leg.TurnDir
+ );
+
+ return [targetFix, line];
+};
diff --git a/browser/src/parser/terminators/FD.ts b/browser/src/parser/terminators/FD.ts
new file mode 100644
index 0000000..2702210
--- /dev/null
+++ b/browser/src/parser/terminators/FD.ts
@@ -0,0 +1,71 @@
+import * as geolib from "geolib";
+import { computeSpeed } from "../utils/computeSpeed.ts";
+import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
+
+// NOTE: Distance not adjusted for altitude in this demo
+export const TerminatorsFD = (
+ leg: FDTerminalEntry,
+ previousFix: NavFix,
+ lastCourse: number
+): [NavFix?, LineSegment[]?] => {
+ const refFix = {
+ latitude: leg.WptLat,
+ longitude: leg.WptLon,
+ };
+ const navaid = {
+ latitude: leg.NavLat,
+ longitude: leg.NavLon,
+ };
+ const speed = computeSpeed(leg, previousFix);
+
+ let line: LineSegment[] = [];
+ if (previousFix.isFlyOver) {
+ const crsIntoEndpoint = leg.Course.toTrue(refFix);
+
+ line = generatePerformanceArc(
+ crsIntoEndpoint,
+ lastCourse,
+ previousFix,
+ speed,
+ leg.TurnDir
+ );
+ } else {
+ line.push([previousFix.longitude, previousFix.latitude]);
+ }
+
+ const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
+ if (line.length > 1) {
+ lastCourse = geolib.getGreatCircleBearing(
+ {
+ latitude: line.at(-2)![1],
+ longitude: line.at(-2)![0],
+ },
+ arcEnd
+ );
+ }
+
+ const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
+ const distToNavaid = geolib.getDistance(arcEnd, navaid);
+ let remainingDistance = leg.Distance.toMetre();
+ // Navaid behind us
+ if (Math.abs(crsToNavaid - lastCourse) > 90) {
+ remainingDistance -= distToNavaid;
+ }
+ // Navaid in front of us
+ else {
+ remainingDistance = distToNavaid - remainingDistance;
+ }
+ const targetFix: NavFix = {
+ ...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
+ name: leg.Distance.toString(),
+ isFlyOver: true,
+ altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
+ speed: computeSpeed(leg, previousFix),
+ speedConstraint: leg.SpeedLimit,
+ altitudeConstraint: leg.Alt,
+ };
+
+ line.push([targetFix.longitude, targetFix.latitude]);
+
+ return [targetFix, line];
+};
diff --git a/browser/src/parser/terminators/TF.ts b/browser/src/parser/terminators/TF.ts
index 1e72cb9..153b546 100644
--- a/browser/src/parser/terminators/TF.ts
+++ b/browser/src/parser/terminators/TF.ts
@@ -39,6 +39,7 @@ export const TerminatorsTF = (
}
// Generate arc
+ let condition = false;
do {
if (leg.TurnDir === "R") {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
@@ -55,14 +56,22 @@ export const TerminatorsTF = (
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
- ((previousFix.speed ?? Parser.AC_SPEED) / 3600).toMetre(),
+ (
+ (previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 3600
+ ).toMetre(),
lastCourse
);
line.push([arcFix.longitude, arcFix.latitude]);
crsIntoEndpoint = geolib.getGreatCircleBearing(arcFix, targetFix);
- } while (crsIntoEndpoint > trackIntoEndpoint);
+
+ if (leg.TurnDir === "R") {
+ condition = crsIntoEndpoint > trackIntoEndpoint;
+ } else {
+ condition = crsIntoEndpoint < trackIntoEndpoint;
+ }
+ } while (condition);
}
}
diff --git a/browser/src/parser/terminators/VA.ts b/browser/src/parser/terminators/VA.ts
index f168db4..8e9a3f8 100644
--- a/browser/src/parser/terminators/VA.ts
+++ b/browser/src/parser/terminators/VA.ts
@@ -15,7 +15,7 @@ export const TerminatorsVA = (
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
Parser.AC_VS) *
- (Parser.AC_SPEED / 60)
+ ((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
).toMetre(),
leg.Course.toTrue(previousFix)
),
diff --git a/browser/src/parser/terminators/VD.ts b/browser/src/parser/terminators/VD.ts
index 4d48392..e706abc 100644
--- a/browser/src/parser/terminators/VD.ts
+++ b/browser/src/parser/terminators/VD.ts
@@ -1,19 +1,59 @@
import * as geolib from "geolib";
-import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import { computeSpeed } from "../utils/computeSpeed.ts";
+import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
+// NOTE: Distance not adjusted for altitude in this demo
export const TerminatorsVD = (
leg: VDTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
- const targetFix: NavFix = {
- ...geolib.computeDestinationPoint(
+ const navaid = {
+ latitude: leg.NavLat,
+ longitude: leg.NavLon,
+ };
+ const speed = computeSpeed(leg, previousFix);
+
+ let line: LineSegment[] = [];
+ if (previousFix.isFlyOver) {
+ const crsIntoEndpoint = leg.Course.toTrue(previousFix);
+
+ line = generatePerformanceArc(
+ crsIntoEndpoint,
+ lastCourse,
previousFix,
- leg.Distance.toMetre(),
- leg.Course.toTrue(previousFix)
- ),
+ speed,
+ leg.TurnDir
+ );
+ } else {
+ line.push([previousFix.longitude, previousFix.latitude]);
+ }
+
+ const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
+ if (line.length > 1) {
+ lastCourse = geolib.getGreatCircleBearing(
+ {
+ latitude: line.at(-2)![1],
+ longitude: line.at(-2)![0],
+ },
+ arcEnd
+ );
+ }
+
+ const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
+ const distToNavaid = geolib.getDistance(arcEnd, navaid);
+ let remainingDistance = leg.Distance.toMetre();
+ // Navaid behind us
+ if (Math.abs(crsToNavaid - lastCourse) > 90) {
+ remainingDistance -= distToNavaid;
+ }
+ // Navaid in front of us
+ else {
+ remainingDistance = distToNavaid - remainingDistance;
+ }
+ const targetFix: NavFix = {
+ ...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
@@ -22,14 +62,7 @@ export const TerminatorsVD = (
altitudeConstraint: leg.Alt,
};
- const line = handleTurnAtFix(
- leg.Course.toTrue(previousFix),
- leg.Course.toTrue(previousFix),
- lastCourse,
- previousFix,
- targetFix,
- leg.TurnDir
- );
+ line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
diff --git a/browser/src/parser/utils/computeTurnRate.ts b/browser/src/parser/utils/computeTurnRate.ts
new file mode 100644
index 0000000..d612988
--- /dev/null
+++ b/browser/src/parser/utils/computeTurnRate.ts
@@ -0,0 +1,3 @@
+export const computeTurnRate = (speed: number, bank: number) => {
+ return (1091 * Math.tan(bank.toRadians())) / speed;
+};
diff --git a/browser/src/parser/utils/extensions.ts b/browser/src/parser/utils/extensions.ts
index 954a86d..163c1a1 100644
--- a/browser/src/parser/utils/extensions.ts
+++ b/browser/src/parser/utils/extensions.ts
@@ -25,6 +25,9 @@ Number.prototype.toTrue = function (fix) {
Number.prototype.toMetre = function () {
return (this as number) * 1852.0;
};
+Number.prototype.equal = function (other: number) {
+ return Math.abs((this as number) - other) < 0.1;
+};
String.prototype.parseAltitude = function () {
switch (this.length) {
diff --git a/browser/src/types/extensions.d.ts b/browser/src/types/extensions.d.ts
index 10476b9..8670fcf 100644
--- a/browser/src/types/extensions.d.ts
+++ b/browser/src/types/extensions.d.ts
@@ -25,6 +25,10 @@ export declare global {
* @returns Value converted from nmi to metres
*/
toMetre: () => number;
+ /**
+ * @returns True if delta is less than 0.1
+ */
+ equal: (other: number) => boolean;
}
interface String {
diff --git a/browser/src/types/terminators/CD.d.ts b/browser/src/types/terminators/CD.d.ts
new file mode 100644
index 0000000..081c4be
--- /dev/null
+++ b/browser/src/types/terminators/CD.d.ts
@@ -0,0 +1,6 @@
+export declare global {
+ type CDTerminalEntry = Required<
+ Pick