Refactor complete

This commit is contained in:
2025-07-14 03:31:57 +02:00
parent 2bdbb583a8
commit af7ac30926
40 changed files with 986 additions and 1013 deletions
+78 -7
View File
@@ -1,43 +1,114 @@
import { MapContainer, GeoJSON, TileLayer } from "react-leaflet";
import Parser from "./parser/parser";
import { useEffect, useState } from "react";
import { createRef, useEffect, useState } from "react";
import hash from "object-hash";
import Leaflet from "leaflet";
import L from "leaflet";
const parser = await Parser.instance();
const terminals = [10394, 10395, 10475, 10480, 10482, 10485, 10653];
function App() {
const [selectedTerminal, setSelectedTerminal] = useState(terminals[0]);
const [procedure, setProcedure] = useState<string>();
console.log(procedure);
const mapRef = createRef<Leaflet.Map>();
const layerRef = createRef<Leaflet.GeoJSON>();
useEffect(() => {
(async () => {
setProcedure(await parser.parse(10394));
setProcedure(await parser.parse(selectedTerminal));
})();
}, []);
}, [selectedTerminal]);
useEffect(() => {
if (layerRef.current && mapRef.current) {
mapRef.current.flyToBounds(layerRef.current.getBounds(), {
animate: false,
padding: [50, 50],
});
}
});
return (
<div style={{ display: "flex", height: "100vh", width: "100vw" }}>
<MapContainer
center={[51.505, -0.09]}
zoom={13}
zoomSnap={0}
zoomDelta={0.1}
wheelPxPerZoomLevel={1000}
style={{ height: "100%", width: "100%" }}
ref={mapRef}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<GeoJSON
key={hash(procedure ?? "")}
key={hash(procedure ?? "") + "lines"}
data={procedure}
style={{
color: "#00ffff",
stroke: true,
weight: 2,
weight: 5,
opacity: 1,
}}
filter={(feature) => feature.geometry.type !== "Point"}
ref={layerRef}
/>
<GeoJSON
key={hash(procedure ?? "") + "points"}
data={procedure}
style={(feature) => ({
color: feature.properties["marker-color"],
stroke: false,
fill: true,
fillOpacity: 1,
})}
pointToLayer={(_, latlng) => {
return L.circleMarker(latlng, { radius: 5 });
}}
onEachFeature={({ geometry, properties }, layer) => {
if (geometry.type === "Point") {
layer.bindPopup(
`${properties.name}<br>
${properties.altitude} ft<br>
${properties.speed} kts<br>
CNSTR:
${properties.altitudeConstraint ?? ""}
${properties.speedConstraint ?? ""}<br>`
);
}
}}
filter={(feature) => feature.geometry.type === "Point"}
/>
</MapContainer>
<div></div>
<div
style={{
padding: "5px",
display: "flex",
flexDirection: "column",
gap: "10px",
}}
>
{terminals.map((terminal) => (
<div
key={terminal}
style={{
display: "flex",
gap: "10px",
background: selectedTerminal === terminal ? "#eeeeee" : undefined,
}}
>
<pre>ID {terminal}</pre>
<button onClick={() => setSelectedTerminal(terminal)}>
Select
</button>
</div>
))}
</div>
</div>
);
}
+1 -1
View File
@@ -13,4 +13,4 @@ fetch = async (path: string) => {
const parser = await Parser.instance();
console.log(JSON.stringify(await parser.parse(10394)));
console.log(JSON.stringify(await parser.parse(10480)));
+166 -36
View File
@@ -1,7 +1,17 @@
import "./utils/extensions.ts";
import { handleTurnAtFix } from "./utils/handleTurnAtFix.ts";
import * as geolib from "geolib";
import geojson from "geojson";
import { TerminatorsCF } from "./terminators/CF.ts";
import { TerminatorsAF } from "./terminators/AF.ts";
import { TerminatorsCR } from "./terminators/CR.ts";
import { TerminatorsVM } from "./terminators/VM.ts";
import { TerminatorsFM } from "./terminators/FM.ts";
import { TerminatorsCI } from "./terminators/CI.ts";
import { TerminatorsVA } from "./terminators/VA.ts";
import { TerminatorsTF } from "./terminators/TF.ts";
import { TerminatorsVI } from "./terminators/VI.ts";
import { TerminatorsVD } from "./terminators/VD.ts";
import { TerminatorsRF } from "./terminators/RF.ts";
class Parser {
private static _instance: Parser;
@@ -11,6 +21,7 @@ class Parser {
private terminals: Terminal[];
public static AC_SPEED = 250;
public static AC_VS = 1500;
private constructor(
waypoints: Waypoint[],
@@ -84,53 +95,172 @@ class Parser {
const waypoint = this.waypoints.filter(({ ID }) => ID === leg.WptID)[0];
switch (leg.TrackCode) {
case "AF":
case "CA":
case "CD":
break;
case "CF": {
const _leg = leg as CFTerminalEntry;
const targetFix: NavFix = {
latitude: _leg.WptLat,
longitude: _leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": _leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: _leg.IsFlyOver,
altitude: previousFix.altitude,
};
navFixes.push(targetFix);
const line = handleTurnAtFix(
_leg.Course.toTrue(previousFix),
_leg.Course.toTrue(previousFix),
lastCourse,
case "AF": {
const [fixToAdd, lineToAdd] = TerminatorsAF(
leg as AFTerminalEntry,
previousFix,
targetFix,
_leg.TurnDir
waypoint
);
lineSegments.push({ line });
updateLastCourse(lineSegments.at(-1)!.line);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "CA":
case "CD":
console.error("Unknown TrackCode", leg.TrackCode);
break;
case "CF": {
const [fixToAdd, lineToAdd] = TerminatorsCF(
leg as CFTerminalEntry,
previousFix,
lastCourse,
waypoint
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "CI": {
const [fixToAdd, lineToAdd] = TerminatorsCI(
leg as CITerminalEntry,
procedure[index + 1],
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "CR": {
const [fixToAdd, lineToAdd] = TerminatorsCR(
leg as CRTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "CI":
case "CR":
case "DF":
case "FA":
case "FC":
case "FD":
case "FM":
console.error("Unknown TrackCode", leg.TrackCode);
break;
case "FM": {
const [fixToAdd, lineToAdd] = TerminatorsFM(
leg as FMTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "HA":
case "HF":
case "HM":
case "IF":
case "PI":
case "RF":
case "TF":
case "VA":
case "VD":
case "VI":
case "VM":
console.error("Unknown TrackCode", leg.TrackCode);
break;
case "RF": {
const [fixToAdd, lineToAdd] = TerminatorsRF(
leg as RFTerminalEntry,
procedure[index + 1],
previousFix,
lastCourse,
waypoint
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "TF": {
const [fixToAdd, lineToAdd] = TerminatorsTF(
leg as TFTerminalEntry,
previousFix,
lastCourse,
waypoint
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VA": {
const [fixToAdd, lineToAdd] = TerminatorsVA(
leg as VATerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VD": {
const [fixToAdd, lineToAdd] = TerminatorsVD(
leg as VDTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VI": {
const [fixToAdd, lineToAdd] = TerminatorsVI(
leg as VITerminalEntry,
procedure[index + 1],
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VM": {
const [fixToAdd, lineToAdd] = TerminatorsVM(
leg as VMTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VR":
default:
console.error("Unknown TrackCode", leg.TrackCode);
@@ -139,8 +269,8 @@ class Parser {
}
return geojson.parse([...navFixes, ...lineSegments], {
Point: ["latitude", "longitude"],
LineString: "line",
Point: ["latitude", "longitude"],
});
};
}
@@ -0,0 +1,53 @@
import * as geolib from "geolib";
/**
* @param crsIntoEndpoint Course into arc endpoint
* @param crsFromOrigin Course into arc origin point
* @param start Arc origin point
* @param center Arc center point
* @param radius Arc radius in nmi
* @param turnDir
* @returns Line segments
*/
export const generateAFArc = (
crsIntoEndpoint: number,
crsFromOrigin: number,
start: NavFix,
center: NavFix,
radius: number,
turnDir: TurnDirection
) => {
const line: LineSegment[] = [[start.longitude, start.latitude]];
if (crsIntoEndpoint !== crsFromOrigin) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = crsFromOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
while (crsFromOrigin !== crsIntoEndpoint) {
if (turnDir === "R") {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
crsFromOrigin += delta < 1 ? delta : 1;
crsFromOrigin = crsFromOrigin.normaliseDegrees();
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
crsFromOrigin -= delta < 1 ? delta : 1;
crsFromOrigin = crsFromOrigin.normaliseDegrees();
}
if (crsFromOrigin === crsIntoEndpoint) break;
const arcFix = geolib.computeDestinationPoint(
center,
radius.toMetre(),
crsFromOrigin
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
@@ -0,0 +1,77 @@
import * as geolib from "geolib";
/**
* @param crsIntoEndpoint Course into arc endpoint
* @param crsIntoOrigin Course into arc origin point
* @param start Arc origin point
* @param center Arc center point
* @param turnDir
* @returns Line segments
*/
export const generateRFArc = (
crsIntoEndpoint: number,
crsIntoOrigin: number,
start: NavFix,
center: NavFix,
turnDir: TurnDirection
) => {
const line: LineSegment[] = [[start.longitude, start.latitude]];
if (crsIntoEndpoint !== crsIntoOrigin) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = crsIntoOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
let crsOrthogonalOnOrigin;
let crsOrthogonalOnEndpoint;
if (turnDir === "R") {
crsOrthogonalOnOrigin = (crsIntoOrigin + 90).normaliseDegrees();
crsOrthogonalOnEndpoint = (crsIntoEndpoint + 90).normaliseDegrees();
} else {
crsOrthogonalOnOrigin = (crsIntoOrigin - 90).normaliseDegrees();
crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees();
}
const arcRad = geolib.getDistance(center, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
// Start turn immediately
if (turnDir === "R") {
crsOrthogonalOnOrigin +=
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
} else {
crsOrthogonalOnOrigin -=
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
}
while (crsOrthogonalOnOrigin !== crsOrthogonalOnEndpoint) {
if (turnDir === "R") {
const delta = (
crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin
).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else {
const delta = (
crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint
).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
}
const arcFix = geolib.computeDestinationPoint(
center,
arcRad,
crsOrthogonalOnOrigin
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
@@ -1,5 +1,5 @@
import * as geolib from "geolib";
import { computeIntersection } from "./computeIntersection.ts";
import { computeIntersection } from "../utils/computeIntersection.ts";
/**
* @param crsIntoEndpoint Course into arc endpoint
+48 -1
View File
@@ -1 +1,48 @@
export const TerminatorsAF = () => {};
import * as geolib from "geolib";
import { generateAFArc } from "../pathGenerators/generateAFArc.ts";
import Parser from "../parser.ts";
export const TerminatorsAF = (
leg: AFTerminalEntry,
previousFix: NavFix,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const arcEndCrs = geolib.getGreatCircleBearing(
{
latitude: leg.NavLat,
longitude: leg.NavLon,
},
{
latitude: leg.WptLat,
longitude: leg.WptLon,
}
);
const line = generateAFArc(
arcEndCrs,
leg.Course.toTrue({ latitude: leg.NavLat, longitude: leg.NavLon }),
previousFix,
{ latitude: leg.NavLat, longitude: leg.NavLon },
leg.NavDist,
leg.TurnDir
);
return [targetFix, line];
};
+38
View File
@@ -0,0 +1,38 @@
import Parser from "../parser.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
export const TerminatorsCF = (
leg: CFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
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];
};
+62
View File
@@ -0,0 +1,62 @@
import Parser from "../parser.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import { computeIntersection } from "../utils/computeIntersection.ts";
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
export const TerminatorsCI = (
leg: CITerminalEntry,
nextLeg: TerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
// Compute INTC
const interceptFix: NavFix = {
...computeIntersection(
previousFix,
leg.Course.toTrue(nextFix),
nextFix,
crs
)!,
"marker-color": leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
crs,
leg.Course.toTrue(nextFix),
lastCourse,
previousFix,
interceptFix,
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];
};
+43
View File
@@ -0,0 +1,43 @@
import { computeIntersection } from "../utils/computeIntersection.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
export const TerminatorsCR = (
leg: CRTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const interceptFix: NavFix = {
...computeIntersection(
previousFix,
crsIntoEndpoint,
{ latitude: leg.NavLat, longitude: leg.NavLon },
leg.NavBear.toTrue({ latitude: leg.NavLat, longitude: leg.NavLon })
)!,
"marker-color": leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
crsIntoEndpoint,
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
interceptFix,
leg.TurnDir
);
return [interceptFix, line];
};
+25
View File
@@ -0,0 +1,25 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
export const TerminatorsFM = (
leg: FMTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const endpoint = geolib.computeDestinationPoint(
previousFix,
(10).toMetre(),
leg.Course.toTrue(previousFix)
);
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
endpoint,
leg.TurnDir
);
return [undefined, line];
};
+42
View File
@@ -0,0 +1,42 @@
import Parser from "../parser.ts";
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
import { generateRFArc } from "../pathGenerators/generateRFArc.ts";
export const TerminatorsRF = (
leg: RFTerminalEntry,
nextLeg: TerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const [crs] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const line = generateRFArc(
crs,
lastCourse,
previousFix,
{ latitude: leg.CenterLat, longitude: leg.CenterLon },
leg.TurnDir
);
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
+40
View File
@@ -0,0 +1,40 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
export const TerminatorsTF = (
leg: TFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const crs = geolib.getGreatCircleBearing(previousFix, targetFix);
const line = handleTurnAtFix(
crs,
crs,
lastCourse,
previousFix,
targetFix,
leg.TurnDir
);
return [targetFix, line];
};
+46
View File
@@ -0,0 +1,46 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVA = (
leg: VATerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
...geolib.computeDestinationPoint(
previousFix,
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
Parser.AC_VS) *
(Parser.AC_SPEED / 60)
).toMetre(),
leg.Course.toTrue(previousFix)
),
name: leg.Alt,
"marker-color": "#ff0000",
isFlyOver: true,
altitude: leg.Alt.parseAltitude(),
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
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];
};
+42
View File
@@ -0,0 +1,42 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVD = (
leg: VDTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
...geolib.computeDestinationPoint(
previousFix,
leg.Distance.toMetre(),
leg.Course.toTrue(previousFix)
),
name: leg.Distance.toString(),
"marker-color": "#ff0000",
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
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];
};
+63
View File
@@ -0,0 +1,63 @@
import Parser from "../parser.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import { computeIntersection } from "../utils/computeIntersection.ts";
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVI = (
leg: VITerminalEntry,
nextLeg: TerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
// Compute INTC
const interceptFix: NavFix = {
...computeIntersection(
previousFix,
leg.Course.toTrue(nextFix),
nextFix,
crs
)!,
"marker-color": leg.IsFlyOver ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
crs,
leg.Course.toTrue(nextFix),
lastCourse,
previousFix,
interceptFix,
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];
};
+26
View File
@@ -0,0 +1,26 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVM = (
leg: VMTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const endpoint = geolib.computeDestinationPoint(
previousFix,
(10).toMetre(),
leg.Course.toTrue(previousFix)
);
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
endpoint,
leg.TurnDir
);
return [undefined, line];
};
@@ -10,7 +10,7 @@ export const computeIntersection = (
brng1: number,
p2: NavFix,
brng2: number
): NavFix | null => {
): NavFix | undefined => {
if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);
@@ -54,8 +54,8 @@ export const computeIntersection = (
const α1 = θ13 - θ12; // angle 2-1-3
const α2 = θ21 - θ23; // angle 1-2-3
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return null; // infinite intersections
if (Math.sin(α1) * Math.sin(α2) < 0) return null; // ambiguous intersection (antipodal/360°)
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°)
const cosα3 =
-Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
+4
View File
@@ -25,3 +25,7 @@ Number.prototype.toTrue = function (fix) {
Number.prototype.toMetre = function () {
return (this as number) * 1852.0;
};
String.prototype.parseAltitude = function () {
return Number.parseInt(this.substring(0, 5));
};
@@ -0,0 +1,42 @@
import * as geolib from "geolib";
/**
* @param leg Leg to examine
* @param origin Origin of current leg
* @returns Adjusted course and fix
*/
export const getCourseAndFixForIntercepts = (
leg: TerminalEntry,
origin: NavFix
): [number, NavFix] => {
switch (leg.TrackCode) {
case "CF": {
const _leg = leg as CFTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
}
case "FM": {
const _leg = leg as FMTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.toTrue(fix), fix];
}
case "TF": {
const _leg = leg as FMTerminalEntry;
return [
geolib.getGreatCircleBearing(origin, {
latitude: _leg.WptLat,
longitude: _leg.WptLon,
}),
{ latitude: _leg.WptLat, longitude: _leg.WptLon },
];
}
case "AF": {
const _leg = leg as AFTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
}
default: {
return [-1, origin];
}
}
};
+7
View File
@@ -26,4 +26,11 @@ export declare global {
*/
toMetre: () => number;
}
interface String {
/**
* @returns Value parsed from altitude constraint
*/
parseAltitude: () => number;
}
}
+1 -1
View File
@@ -2,10 +2,10 @@ export declare global {
type AFTerminalEntry = Required<
Pick<
TerminalEntry,
| "TurnDir"
| "WptID"
| "WptLat"
| "WptLon"
| "TurnDir"
| "NavID"
| "NavLat"
| "NavLon"
+4
View File
@@ -0,0 +1,4 @@
export declare global {
type CITerminalEntry = Required<Pick<TerminalEntry, "Course">> &
TerminalEntry;
}
+6
View File
@@ -0,0 +1,6 @@
export declare global {
type CRTerminalEntry = Required<
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "NavBear" | "Course">
> &
TerminalEntry;
}
+17
View File
@@ -0,0 +1,17 @@
export declare global {
type FMTerminalEntry = Required<
Pick<
TerminalEntry,
| "WptID"
| "WptLat"
| "WptLon"
| "NavID"
| "NavLat"
| "NavLon"
| "NavBear"
| "NavDist"
| "Course"
>
> &
TerminalEntry;
}
+18
View File
@@ -0,0 +1,18 @@
export declare global {
type RFTerminalEntry = Required<
Pick<
TerminalEntry,
| "WptID"
| "WptLat"
| "WptLon"
| "TurnDir"
| "NavBear"
| "Course"
| "Distance"
| "CenterID"
| "CenterLat"
| "CenterLon"
>
> &
TerminalEntry;
}
+6
View File
@@ -0,0 +1,6 @@
export declare global {
type TFTerminalEntry = Required<
Pick<TerminalEntry, "WptID" | "WptLat" | "WptLon" | "IsFlyOver">
> &
TerminalEntry;
}
+4
View File
@@ -0,0 +1,4 @@
export declare global {
type VATerminalEntry = Required<Pick<TerminalEntry, "Course" | "Alt">> &
TerminalEntry;
}
+6
View File
@@ -0,0 +1,6 @@
export declare global {
type VDTerminalEntry = Required<
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "Course" | "Distance">
> &
TerminalEntry;
}
+4
View File
@@ -0,0 +1,4 @@
export declare global {
type VITerminalEntry = Required<Pick<TerminalEntry, "Course">> &
TerminalEntry;
}
+4
View File
@@ -0,0 +1,4 @@
export declare global {
type VMTerminalEntry = Required<Pick<TerminalEntry, "Course">> &
TerminalEntry;
}
+2 -2
View File
@@ -63,7 +63,7 @@ export declare global {
CenterLat?: number;
CenterLon?: number;
IsFlyOver: boolean;
SpeedLimit?: string;
SpeedLimit?: number;
IsFAF: boolean;
IsMAP: boolean;
};
@@ -84,7 +84,7 @@ export declare global {
isFlyOver?: boolean;
"marker-color"?: string;
altitudeConstraint?: string;
speedConstraint?: string;
speedConstraint?: number;
};
type LineSegment = [number, number];