NavDataExplorer/geojson.js
2025-07-13 22:51:43 +02:00

902 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from "fs";
import geo from "geojson";
import geolib from "geolib";
import magvar from "magvar";
/* Constants */
const π = Math.PI;
const AC_SPEED = 250;
const AC_VS = 3000;
// ADJ to location, (NG), E is neg.
/* LFPV */
//const MAGVAR = -1;
//const PROCEDURE_ID = 10394;
//const PROCEDURE_ID = 10395;
/* LFRK */
//const MAGVAR = 0;
//const PROCEDURE_ID = 10475;
//const PROCEDURE_ID = 10480;
//const PROCEDURE_ID = 10482;
//const PROCEDURE_ID = 10485;
const MAGVAR = -5;
const PROCEDURE_ID = 10653;
/* Prototypes */
Number.prototype.toRadians = function () {
return (this * Math.PI) / 180;
};
Number.prototype.toDegrees = function () {
return (this * 180) / Math.PI;
};
Number.prototype.flipCourse = function () {
let inv = this + 180;
inv = inv >= 360 ? inv - 360 : inv;
return inv;
};
Number.prototype.normaliseDegrees = function () {
return this >= 360 ? this - 360 : this < 0 ? this + 360 : this;
};
Number.prototype.toTrue = function (fix) {
// IRL Magvar
//return (this - magvar.magvar(fix.lat, fix.lon)).normaliseDegrees();
// NG Magvar
return (this - MAGVAR).normaliseDegrees();
};
/* Functions */
/**
*
* @param {[number, number]} line line segment
*/
const updateLastCourse = (line) => {
lastCourse = geolib.getGreatCircleBearing(
{
latitude: line.at(-2)[1],
longitude: line.at(-2)[0],
},
{
latitude: line.at(-1)[1],
longitude: line.at(-1)[0],
}
);
};
/**
*
* @param {number} nmi Nautical Miles
* @returns Metres
*/
const nmiToMetre = (nmi) => nmi * 1852.0;
/**
*
* @param {{lat: number, lon: number}} p1 Point 1
* @param {number} brng1 Bearing from Point 1
* @param {{lat: number, lon: number}} p2 Point 2
* @param {number} brng2 bearing from Point 2
* @returns {{lat: number, lon: number}} Intersection point
*/
const computeIntersection = (p1, brng1, p2, brng2) => {
if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);
// see www.edwilliams.org/avform.htm#Intersection
const φ1 = p1.lat.toRadians(),
λ1 = p1.lon.toRadians();
const φ2 = p2.lat.toRadians(),
λ2 = p2.lon.toRadians();
const θ13 = Number(brng1).toRadians(),
θ23 = Number(brng2).toRadians();
const Δφ = φ2 - φ1,
Δλ = λ2 - λ1;
// angular distance p1-p2
const δ12 =
2 *
Math.asin(
Math.sqrt(
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
)
);
if (Math.abs(δ12) < Number.EPSILON) return p1; // coincident points
// initial/final bearings between points
const cosθa =
(Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) /
(Math.sin(δ12) * Math.cos(φ1));
const cosθb =
(Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) /
(Math.sin(δ12) * Math.cos(φ2));
const θa = Math.acos(Math.min(Math.max(cosθa, -1), 1)); // protect against rounding errors
const θb = Math.acos(Math.min(Math.max(cosθb, -1), 1)); // protect against rounding errors
const θ12 = Math.sin(λ2 - λ1) > 0 ? θa : 2 * π - θa;
const θ21 = Math.sin(λ2 - λ1) > 0 ? 2 * π - θb : θb;
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°)
const cosα3 =
-Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
const δ13 = Math.atan2(
Math.sin(δ12) * Math.sin(α1) * Math.sin(α2),
Math.cos(α2) + Math.cos(α1) * cosα3
);
const φ3 = Math.asin(
Math.min(
Math.max(
Math.sin(φ1) * Math.cos(δ13) +
Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13),
-1
),
1
)
);
const Δλ13 = Math.atan2(
Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1),
Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3)
);
const λ3 = λ1 + Δλ13;
const lat = φ3.toDegrees();
const lon = λ3.toDegrees();
return { lat, lon, name: "INTC", alt: p1.alt };
};
/**
*
* @param {number} inbdCrs Course into arc endpoint
* @param {*} outbCrs Course into arc origin point
* @param {{lat: number, lon: number}} arcStart Arc origin point
* @param {{lat: number, lon: number}} legEnd Arc endpoint
* @param {"R"|"L"|"E"|null} turnDir Turn direction
* @returns {[[number, number]]} Line segments
*/
const generateTangentialArc = (inbdCrs, outbCrs, arcStart, legEnd, turnDir) => {
const line = [];
if (inbdCrs !== outbCrs) {
// Course to the end of the arc
let crsToArcEnd;
if (!turnDir || turnDir === "E") {
let prov = outbCrs - inbdCrs;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
if (turnDir === "R") {
const delta = (360 - outbCrs + inbdCrs).normaliseDegrees();
crsToArcEnd = (outbCrs + delta / 2).normaliseDegrees();
} else {
const delta = (outbCrs + 360 - inbdCrs).normaliseDegrees();
crsToArcEnd = (outbCrs - delta / 2).normaliseDegrees();
}
// Arc end
const arcEnd = computeIntersection(
arcStart,
crsToArcEnd,
legEnd,
inbdCrs.flipCourse()
);
if (!arcEnd) {
// Early end due to no intercept
return null;
}
let startPerpCrs;
let endPerpCrs;
if (turnDir === "R") {
startPerpCrs = (outbCrs + 90).normaliseDegrees();
endPerpCrs = (inbdCrs + 90).normaliseDegrees();
} else {
startPerpCrs = (outbCrs - 90).normaliseDegrees();
endPerpCrs = (inbdCrs - 90).normaliseDegrees();
}
// Generate arc
const arcCenter = computeIntersection(
arcStart,
startPerpCrs,
arcEnd,
endPerpCrs
);
const arcRad = geolib.getDistance(
{
latitude: arcCenter.lat,
longitude: arcCenter.lon,
},
{
latitude: arcStart.lat,
longitude: arcStart.lon,
}
);
startPerpCrs = startPerpCrs.flipCourse();
endPerpCrs = endPerpCrs.flipCourse();
// Start turn immediately
if (turnDir === "R") {
startPerpCrs += startPerpCrs < 1 ? startPerpCrs : 1;
} else {
startPerpCrs -= startPerpCrs < 1 ? startPerpCrs : 1;
}
while (startPerpCrs !== endPerpCrs) {
if (turnDir === "R") {
const delta = (endPerpCrs - startPerpCrs).normaliseDegrees();
startPerpCrs += delta < 1 ? delta : 1;
startPerpCrs = startPerpCrs.normaliseDegrees();
} else {
const delta = (startPerpCrs - endPerpCrs).normaliseDegrees();
startPerpCrs -= delta < 1 ? delta : 1;
startPerpCrs = startPerpCrs.normaliseDegrees();
}
if (startPerpCrs === endPerpCrs) break;
const arcFix = geolib.computeDestinationPoint(
arcCenter,
arcRad,
startPerpCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
/**
*
* @param {number} inbdCrs Course into arc endpoint
* @param {number} outbCrs Course into arc origin point
* @param {{lat: number, lon: number}} arcStart Arc origin point
* @param {"R"|"L"|"E"|null} turnDir Turn direction
* @returns {[[number, number]]} Line segments
*/
const generatePerformanceArc = (inbdCrs, outbCrs, arcStart, turnDir) => {
const line = [[arcStart.lon, arcStart.lat]];
if (inbdCrs !== outbCrs) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = outbCrs - inbdCrs;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
// Generate arc
while (outbCrs !== inbdCrs) {
if (turnDir === "R") {
const delta = (inbdCrs - outbCrs).normaliseDegrees();
outbCrs += delta < 1 ? delta : 1;
outbCrs = outbCrs.normaliseDegrees();
} else {
const delta = (outbCrs - inbdCrs).normaliseDegrees();
outbCrs -= delta < 1 ? delta : 1;
outbCrs = outbCrs.normaliseDegrees();
}
if (outbCrs === inbdCrs) break;
const arcFix = geolib.computeDestinationPoint(
{
latitude: line.at(-1)[1],
longitude: line.at(-1)[0],
},
nmiToMetre(240 / 3600),
outbCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
} else {
line.push([arcStart.lon, arcStart.lat]);
}
return line.slice(1);
};
/**
*
* @param {number} inbdCrs Course into arc endpoint
* @param {number} outbCrs Course into arc origin point
* @param {{lat: number, lon: number}} arcStart Arc origin point
* @param {{lat: number, lon: number}} center Arc center point
* @param {"R"|"L"|"E"|null} turnDir
* @returns {[[number, number]]} Line segments
*/
const generateRFArc = (inbdCrs, outbCrs, arcStart, center, turnDir) => {
const line = [];
if (inbdCrs !== outbCrs) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = outbCrs - inbdCrs;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
let startPerpCrs;
let endPerpCrs;
if (turnDir === "R") {
startPerpCrs = (outbCrs + 90).normaliseDegrees();
endPerpCrs = (inbdCrs + 90).normaliseDegrees();
} else {
startPerpCrs = (outbCrs - 90).normaliseDegrees();
endPerpCrs = (inbdCrs - 90).normaliseDegrees();
}
const arcRad = geolib.getDistance(
{
latitude: center.lat,
longitude: center.lon,
},
{
latitude: arcStart.lat,
longitude: arcStart.lon,
}
);
startPerpCrs = startPerpCrs.flipCourse();
endPerpCrs = endPerpCrs.flipCourse();
// Start turn immediately
if (turnDir === "R") {
startPerpCrs += startPerpCrs < 1 ? startPerpCrs : 1;
} else {
startPerpCrs -= startPerpCrs < 1 ? startPerpCrs : 1;
}
while (startPerpCrs !== endPerpCrs) {
if (turnDir === "R") {
const delta = (endPerpCrs - startPerpCrs).normaliseDegrees();
startPerpCrs += delta < 1 ? delta : 1;
startPerpCrs = startPerpCrs.normaliseDegrees();
} else {
const delta = (startPerpCrs - endPerpCrs).normaliseDegrees();
startPerpCrs -= delta < 1 ? delta : 1;
startPerpCrs = startPerpCrs.normaliseDegrees();
}
if (startPerpCrs === endPerpCrs) break;
const arcFix = geolib.computeDestinationPoint(
center,
arcRad,
startPerpCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
/**
*
* @param {number} inbdCrs Course into arc endpoint
* @param {number} outbCrs Course into arc origin point
* @param {{lat: number, lon: number}} arcStart Arc origin point
* @param {{lat: number, lon: number}} center Arc center point
* @param {number} radius Arc radius in nmi
* @param {"R"|"L"|"E"|null} turnDir
* @returns {[[number, number]]} Line segments
*/
const generateAFArc = (inbdCrs, outbCrs, arcStart, center, radius, turnDir) => {
const line = [[arcStart.lon, arcStart.lat]];
if (inbdCrs !== outbCrs) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = outbCrs - inbdCrs;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
while (outbCrs !== inbdCrs) {
if (turnDir === "R") {
const delta = (inbdCrs - outbCrs).normaliseDegrees();
outbCrs += delta < 1 ? delta : 1;
outbCrs = outbCrs.normaliseDegrees();
} else {
const delta = (outbCrs - inbdCrs).normaliseDegrees();
outbCrs -= delta < 1 ? delta : 1;
outbCrs = outbCrs.normaliseDegrees();
}
if (outbCrs === inbdCrs) break;
const arcFix = geolib.computeDestinationPoint(
center,
nmiToMetre(radius),
outbCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
const getCourseAndFixForInterceptions = (leg, origin) => {
switch (leg.TrackCode) {
case "CF": {
const fix = { lat: leg.WptLat, lon: leg.WptLon };
return [leg.Course.flipCourse().toTrue(fix), fix];
}
case "FM": {
const fix = { lat: leg.WptLat, lon: leg.WptLon };
return [leg.Course.toTrue(fix), fix];
}
case "TF": {
return [
geolib.getGreatCircleBearing(
{ latitude: origin.lat, longitude: origin.lon },
{ latitude: leg.WptLat, longitude: leg.WptLon }
),
{ lat: leg.WptLat, lon: leg.WptLon },
];
}
case "AF": {
const fix = { lat: leg.WptLat, lon: leg.WptLon };
return [leg.Course.flipCourse().toTrue(fix), fix];
}
}
};
const handleTurnAtFix = (inbdCrs, inbdCrs2, start, end, turnDir) => {
// Begin line drawing at previous end
const line = [[start.lon, start.lat]];
// Draw arcs only if origin was flyover
if (start.isFlyOver) {
const arc1 = generateTangentialArc(
inbdCrs,
lastCourse,
start,
end,
turnDir
);
const arc2 = generatePerformanceArc(inbdCrs2, lastCourse, start, turnDir);
let arc;
if (arc1) {
const endCrs = geolib.getGreatCircleBearing(
{
latitude: arc1.at(-1)[1],
longitude: arc1.at(-1)[0],
},
{
latitude: end.lat,
longitude: end.lon,
}
);
if (endCrs <= inbdCrs + 1 && endCrs >= inbdCrs - 1) arc = arc1;
else arc = arc2;
} else {
arc = arc2;
}
// Push line
line.push(...arc);
} else {
//FIXME: Non Flyover
//line.push([end.lon, end.lat]);
}
return line;
};
const parseAltitude = (alt) => {
return Number.parseInt(alt.substring(0, 5));
};
/* Data */
const waypoints = JSON.parse(
fs.readFileSync("browser/public/navdata/Waypoints.json")
);
const terminal = JSON.parse(
fs.readFileSync("browser/public/navdata/Terminals.json")
).filter(({ ID }) => ID === PROCEDURE_ID)[0];
const runway = JSON.parse(
fs.readFileSync("browser/public/navdata/Runways.json")
).filter(({ ID }) => ID === terminal.RwyID)[0];
const procedure = JSON.parse(
fs.readFileSync(`browser/public/navdata/TermID_${PROCEDURE_ID}.json`)
);
/* Output */
const points = [
{ lat: runway.Latitude, lon: runway.Longitude, alt: runway.Elevation },
];
const lines = [];
let lastCourse = runway.TrueHeading;
/* Main */
for (let index = 0; index < procedure.length; index++) {
const leg = procedure[index];
const waypoint = waypoints.filter(({ ID }) => ID === leg.WptID)[0];
switch (leg.TrackCode) {
case "AF": {
// Push in ending waypoint
points.push({
lat: leg.WptLat,
lon: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver !== 0,
alt: points.at(-1).alt,
});
const arcEndCrs = geolib.getGreatCircleBearing(
{
latitude: leg.NavLat,
longitude: leg.NavLon,
},
{
latitude: leg.WptLat,
longitude: leg.WptLon,
}
);
const line = generateAFArc(
arcEndCrs,
leg.Course.toTrue({ lat: leg.NavLat, lon: leg.NavLon }),
points.at(-2),
{ lat: leg.NavLat, lon: leg.NavLon },
leg.NavDist,
leg.TurnDir
);
lines.push({ line });
updateLastCourse(line);
break;
}
case "CA":
case "CD":
break;
case "CF": {
// Push in ending waypoint
points.push({
lat: leg.WptLat,
lon: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver !== 0,
alt: points.at(-1).alt,
});
const line = handleTurnAtFix(
leg.Course.toTrue(points.at(-1)),
leg.Course.toTrue(points.at(-1)),
points.at(-2),
points.at(-1),
leg.TurnDir,
index
);
line.push([leg.WptLon, leg.WptLat]);
lines.push({ line });
updateLastCourse(lines.at(-1).line);
break;
}
case "CI": {
// Course into the destination fix and said fix
const [inbdCrs, fix] = getCourseAndFixForInterceptions(
procedure[index + 1],
points.at(-1)
);
// Compute INTC
const intc = computeIntersection(
points.at(-1),
leg.Course.toTrue(fix),
fix,
inbdCrs
);
const line = handleTurnAtFix(
inbdCrs,
leg.Course.toTrue(fix),
points.at(-1),
intc,
leg.TurnDir,
index
);
lines.push({ line });
updateLastCourse(line);
const intc2 = computeIntersection(
{ lat: line.at(-1)[1], lon: line.at(-1)[0] },
leg.Course.toTrue(fix),
fix,
inbdCrs
);
if (intc2) {
points.push(intc2);
lines.push({ line: [line.at(-1), [intc2.lon, intc2.lat]] });
updateLastCourse(line);
} else {
points.push(intc);
lines.push({
line: [line.at(-1), [intc.lon, intc.lat]],
});
}
break;
}
case "CR": {
// Course into the destination fix
const inbdCrs = leg.Course.toTrue(points.at(-1));
// Compute INTC
const intc = computeIntersection(
points.at(-1),
inbdCrs,
{ lat: leg.NavLat, lon: leg.NavLon },
leg.NavBear.toTrue({ lat: leg.NavLat, lon: leg.NavLon })
);
points.push(intc);
const line = handleTurnAtFix(
inbdCrs,
leg.Course.toTrue(points.at(-2)),
points.at(-2),
points.at(-1),
leg.TurnDir,
index
);
line.push([intc.lon, intc.lat]);
lines.push({ line });
updateLastCourse(line);
break;
}
case "DF":
case "FA":
case "FC":
case "FD":
break;
case "FM": {
const end = geolib.computeDestinationPoint(
{
latitude: points.at(-1).lat,
longitude: points.at(-1).lon,
},
nmiToMetre(10),
leg.Course.toTrue(points.at(-1))
);
const line = handleTurnAtFix(
leg.Course.toTrue(points.at(-1)),
leg.Course.toTrue(points.at(-1)),
points.at(-1),
{ lat: end.latitude, lon: end.longitude },
leg.TurnDir,
index
);
lines.push({ line });
line.push([end.longitude, end.latitude]);
updateLastCourse(line);
break;
}
case "HA":
case "HF":
case "HM":
case "IF":
case "PI":
break;
case "RF": {
// Push in ending waypoint
points.push({
lat: leg.WptLat,
lon: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver !== 0,
alt: points.at(-1).alt,
});
const [inbdCrs] = getCourseAndFixForInterceptions(
procedure[index + 1],
points.at(-1)
);
const line = generateRFArc(
inbdCrs,
lastCourse,
points.at(-1),
{ lat: leg.CenterLat, lon: leg.CenterLon },
leg.TurnDir
);
lines.push({ line });
updateLastCourse(line);
break;
}
case "TF": {
// Push in ending waypoint
points.push({
lat: leg.WptLat,
lon: leg.WptLon,
name: waypoint?.Ident ?? undefined,
"marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
isFlyOver: leg.IsFlyOver !== 0,
alt: points.at(-1).alt,
});
const inbdCrs = geolib.getGreatCircleBearing(
{ latitude: points.at(-2).lat, longitude: points.at(-2).lon },
{ latitude: points.at(-1).lat, longitude: points.at(-1).lon }
);
const line = handleTurnAtFix(
inbdCrs,
inbdCrs,
points.at(-2),
points.at(-1),
leg.TurnDir,
index
);
line.push([leg.WptLon, leg.WptLat]);
lines.push({ line });
updateLastCourse(line);
break;
}
case "VA": {
// NOTE: No wind adjustments to be made, no clue how *that* would draw
const end = geolib.computeDestinationPoint(
{
latitude: points.at(-1).lat,
longitude: points.at(-1).lon,
},
nmiToMetre(
((parseAltitude(leg.Alt) - points.at(-1).alt) / AC_VS) *
(AC_SPEED / 60)
),
leg.Course.toTrue(points.at(-1))
);
points.push({
lat: end.latitude,
lon: end.longitude,
name: leg.Alt,
"marker-color": "#ff0000",
isFlyOver: true,
alt: parseAltitude(leg.Alt),
});
const line = handleTurnAtFix(
leg.Course.toTrue(points.at(-2)),
leg.Course.toTrue(points.at(-2)),
points.at(-2),
{ lat: end.latitude, lon: end.longitude },
leg.TurnDir,
index
);
line.push([end.longitude, end.latitude]);
lines.push({ line });
updateLastCourse(line);
break;
}
case "VD": {
// NOTE: No wind adjustments to be made, no clue how *that* would draw
const end = geolib.computeDestinationPoint(
{
latitude: points.at(-1).lat,
longitude: points.at(-1).lon,
},
//NOTE: Does not account for slant
nmiToMetre(leg.Distance),
leg.Course.toTrue(points.at(-1))
);
points.push({
lat: end.latitude,
lon: end.longitude,
name: leg.Alt,
"marker-color": "#ff0000",
isFlyOver: true,
alt: points.at(-1).alt,
});
const line = handleTurnAtFix(
leg.Course.toTrue(points.at(-2)),
leg.Course.toTrue(points.at(-2)),
points.at(-2),
{ lat: end.latitude, lon: end.longitude },
leg.TurnDir,
index
);
line.push([end.longitude, end.latitude]);
lines.push({ line });
updateLastCourse(line);
break;
}
case "VI": {
// NOTE: No wind adjustments to be made, no clue how *that* would draw
// Course into the destination fix and said fix
const [inbdCrs, fix] = getCourseAndFixForInterceptions(
procedure[index + 1],
points.at(-1)
);
// Compute INTC
const intc = computeIntersection(
points.at(-1),
leg.Course.toTrue(fix),
fix,
inbdCrs
);
const line = handleTurnAtFix(
inbdCrs,
leg.Course.toTrue(fix),
points.at(-1),
intc,
leg.TurnDir,
index
);
lines.push({ line });
updateLastCourse(line);
const intc2 = computeIntersection(
{ lat: line.at(-1)[1], lon: line.at(-1)[0] },
leg.Course.toTrue(fix),
fix,
inbdCrs
);
if (intc2) {
points.push(intc2);
lines.push({ line: [line.at(-1), [intc2.lon, intc2.lat]] });
updateLastCourse(line);
}
break;
}
case "VM": {
const end = geolib.computeDestinationPoint(
{
latitude: points.at(-1).lat,
longitude: points.at(-1).lon,
},
nmiToMetre(10),
leg.Course.toTrue(points.at(-1))
);
const line = handleTurnAtFix(
leg.Course.toTrue(points.at(-1)),
leg.Course.toTrue(points.at(-1)),
points.at(-1),
{ lat: end.latitude, lon: end.longitude },
leg.TurnDir,
index
);
line.push([end.longitude, end.latitude]);
lines.push({ line });
updateLastCourse(line);
break;
}
case "VR":
case "AF":
default:
break;
}
}
/* geoJSON */
const output = geo.parse([...points, ...lines], {
Point: ["lat", "lon"],
LineString: "line",
});
console.log(JSON.stringify(output, null, 2));