Types AF;RF

This commit is contained in:
Kilian committed 2025-07-13 22:51:43 +02:00
1 parent 5b39d61cdb
commit 2c99b701ce
22 files changed
+286 -45

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
{
"cSpell.words": ["intc"]
}
+2 -2
View File
@@ -27,7 +27,8 @@ LGAV BIBE1K SID (Cycle 2507, ID 10653)
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`). (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`).
- Arc radius shall be `NavDist`. - Arc radius shall be `NavDist`.
- Arc center shall be navaid identified by `NavID`, `NavLat`, `NavLon`. - Arc center shall be navaid identified by `NavID`, `NavLat`, `NavLon`.
- `Course` shall be the boundary radial on which the arc begins (preceding fix lies hereupon). - `Course` shall be the boundary radial on which the arc begins, measured from the navaid
identified by (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`).
- Arc and turn shall be flown in direction specified by `TurnDir`. - Arc and turn shall be flown in direction specified by `TurnDir`.
### Units ### Units
@@ -512,7 +513,6 @@ LFRN GODA5R SID (cycle 2507, ID 10485)
### Instructions ### Instructions
- From preceding fix, fly a smooth arc to the fix identified by (`WptID`, `WptLat`, `WptLon`). - From preceding fix, fly a smooth arc to the fix identified by (`WptID`, `WptLat`, `WptLon`).
- Arc radius shall be `NavDist`.
- 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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+281 -43
View File
@@ -1,6 +1,7 @@
import fs from "fs"; import fs from "fs";
import geo from "geojson"; import geo from "geojson";
import geolib from "geolib"; import geolib from "geolib";
import magvar from "magvar";
/* Constants */ /* Constants */
const π = Math.PI; const π = Math.PI;
@@ -12,11 +13,13 @@ const AC_VS = 3000;
//const PROCEDURE_ID = 10394; //const PROCEDURE_ID = 10394;
//const PROCEDURE_ID = 10395; //const PROCEDURE_ID = 10395;
/* LFRK */ /* LFRK */
const MAGVAR = 0; //const MAGVAR = 0;
//const PROCEDURE_ID = 10475; //const PROCEDURE_ID = 10475;
//const PROCEDURE_ID = 10480; //const PROCEDURE_ID = 10480;
//const PROCEDURE_ID = 10482; //const PROCEDURE_ID = 10482;
const PROCEDURE_ID = 10485; //const PROCEDURE_ID = 10485;
const MAGVAR = -5;
const PROCEDURE_ID = 10653;
/* Prototypes */ /* Prototypes */
Number.prototype.toRadians = function () { Number.prototype.toRadians = function () {
@@ -33,11 +36,18 @@ Number.prototype.flipCourse = function () {
Number.prototype.normaliseDegrees = function () { Number.prototype.normaliseDegrees = function () {
return this >= 360 ? this - 360 : this < 0 ? this + 360 : this; return this >= 360 ? this - 360 : this < 0 ? this + 360 : this;
}; };
Number.prototype.toTrue = function () { Number.prototype.toTrue = function (fix) {
// IRL Magvar
//return (this - magvar.magvar(fix.lat, fix.lon)).normaliseDegrees();
// NG Magvar
return (this - MAGVAR).normaliseDegrees(); return (this - MAGVAR).normaliseDegrees();
}; };
/* Functions */ /* Functions */
/**
*
* @param {[number, number]} line line segment
*/
const updateLastCourse = (line) => { const updateLastCourse = (line) => {
lastCourse = geolib.getGreatCircleBearing( lastCourse = geolib.getGreatCircleBearing(
{ {
@@ -50,7 +60,20 @@ const updateLastCourse = (line) => {
} }
); );
}; };
/**
*
* @param {number} nmi Nautical Miles
* @returns Metres
*/
const nmiToMetre = (nmi) => nmi * 1852.0; 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) => { const computeIntersection = (p1, brng1, p2, brng2) => {
if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`); if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`); if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);
@@ -126,6 +149,15 @@ const computeIntersection = (p1, brng1, p2, brng2) => {
return { lat, lon, name: "INTC", alt: p1.alt }; 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 generateTangentialArc = (inbdCrs, outbCrs, arcStart, legEnd, turnDir) => {
const line = []; const line = [];
@@ -219,6 +251,14 @@ const generateTangentialArc = (inbdCrs, outbCrs, arcStart, legEnd, turnDir) => {
return line; 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 generatePerformanceArc = (inbdCrs, outbCrs, arcStart, turnDir) => {
const line = [[arcStart.lon, arcStart.lat]]; const line = [[arcStart.lon, arcStart.lat]];
@@ -260,16 +300,147 @@ const generatePerformanceArc = (inbdCrs, outbCrs, arcStart, turnDir) => {
return line.slice(1); return line.slice(1);
}; };
const getCourseAndFixForInterceptions = (leg) => { /**
*
* @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) { switch (leg.TrackCode) {
case "CF": { 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 [ return [
leg.Course.flipCourse().toTrue(), geolib.getGreatCircleBearing(
{ latitude: origin.lat, longitude: origin.lon },
{ latitude: leg.WptLat, longitude: leg.WptLon }
),
{ lat: leg.WptLat, lon: leg.WptLon }, { lat: leg.WptLat, lon: leg.WptLon },
]; ];
} }
case "FM": { case "AF": {
return [leg.Course.toTrue(), { lat: leg.WptLat, lon: leg.WptLon }]; const fix = { lat: leg.WptLat, lon: leg.WptLon };
return [leg.Course.flipCourse().toTrue(fix), fix];
} }
} }
}; };
@@ -322,21 +493,25 @@ const parseAltitude = (alt) => {
}; };
/* Data */ /* Data */
const waypoints = JSON.parse(fs.readFileSync("Waypoints.json")); const waypoints = JSON.parse(
const terminal = JSON.parse(fs.readFileSync("Terminals.json")).filter( fs.readFileSync("browser/public/navdata/Waypoints.json")
({ ID }) => ID === PROCEDURE_ID );
)[0]; const terminal = JSON.parse(
const runway = JSON.parse(fs.readFileSync("Runways.json")).filter( fs.readFileSync("browser/public/navdata/Terminals.json")
({ ID }) => ID === terminal.RwyID ).filter(({ ID }) => ID === PROCEDURE_ID)[0];
)[0]; const runway = JSON.parse(
const procedure = JSON.parse(fs.readFileSync(`TermID_${PROCEDURE_ID}.json`)); 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 */ /* Output */
const points = [ const points = [
{ lat: runway.Latitude, lon: runway.Longitude, alt: runway.Elevation }, { lat: runway.Latitude, lon: runway.Longitude, alt: runway.Elevation },
]; ];
const lines = []; const lines = [];
let lastCourse = 0; let lastCourse = runway.TrueHeading;
/* Main */ /* Main */
for (let index = 0; index < procedure.length; index++) { for (let index = 0; index < procedure.length; index++) {
@@ -344,7 +519,41 @@ for (let index = 0; index < procedure.length; index++) {
const waypoint = waypoints.filter(({ ID }) => ID === leg.WptID)[0]; const waypoint = waypoints.filter(({ ID }) => ID === leg.WptID)[0];
switch (leg.TrackCode) { switch (leg.TrackCode) {
case "AF": 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 "CA":
case "CD": case "CD":
break; break;
@@ -360,8 +569,8 @@ for (let index = 0; index < procedure.length; index++) {
}); });
const line = handleTurnAtFix( const line = handleTurnAtFix(
leg.Course.toTrue(), leg.Course.toTrue(points.at(-1)),
leg.Course.toTrue(), leg.Course.toTrue(points.at(-1)),
points.at(-2), points.at(-2),
points.at(-1), points.at(-1),
leg.TurnDir, leg.TurnDir,
@@ -377,20 +586,21 @@ for (let index = 0; index < procedure.length; index++) {
case "CI": { case "CI": {
// Course into the destination fix and said fix // Course into the destination fix and said fix
const [inbdCrs, fix] = getCourseAndFixForInterceptions( const [inbdCrs, fix] = getCourseAndFixForInterceptions(
procedure[index + 1] procedure[index + 1],
points.at(-1)
); );
// Compute INTC // Compute INTC
const intc = computeIntersection( const intc = computeIntersection(
points.at(-1), points.at(-1),
leg.Course.toTrue(), leg.Course.toTrue(fix),
fix, fix,
inbdCrs inbdCrs
); );
const line = handleTurnAtFix( const line = handleTurnAtFix(
inbdCrs, inbdCrs,
leg.Course.toTrue(), leg.Course.toTrue(fix),
points.at(-1), points.at(-1),
intc, intc,
leg.TurnDir, leg.TurnDir,
@@ -401,7 +611,7 @@ for (let index = 0; index < procedure.length; index++) {
const intc2 = computeIntersection( const intc2 = computeIntersection(
{ lat: line.at(-1)[1], lon: line.at(-1)[0] }, { lat: line.at(-1)[1], lon: line.at(-1)[0] },
leg.Course.toTrue(), leg.Course.toTrue(fix),
fix, fix,
inbdCrs inbdCrs
); );
@@ -420,20 +630,20 @@ for (let index = 0; index < procedure.length; index++) {
} }
case "CR": { case "CR": {
// Course into the destination fix // Course into the destination fix
const inbdCrs = leg.Course.toTrue(); const inbdCrs = leg.Course.toTrue(points.at(-1));
// Compute INTC // Compute INTC
const intc = computeIntersection( const intc = computeIntersection(
points.at(-1), points.at(-1),
inbdCrs, inbdCrs,
{ lat: leg.NavLat, lon: leg.NavLon }, { lat: leg.NavLat, lon: leg.NavLon },
leg.NavBear.toTrue() leg.NavBear.toTrue({ lat: leg.NavLat, lon: leg.NavLon })
); );
points.push(intc); points.push(intc);
const line = handleTurnAtFix( const line = handleTurnAtFix(
inbdCrs, inbdCrs,
leg.Course.toTrue(), leg.Course.toTrue(points.at(-2)),
points.at(-2), points.at(-2),
points.at(-1), points.at(-1),
leg.TurnDir, leg.TurnDir,
@@ -457,12 +667,12 @@ for (let index = 0; index < procedure.length; index++) {
longitude: points.at(-1).lon, longitude: points.at(-1).lon,
}, },
nmiToMetre(10), nmiToMetre(10),
leg.Course.toTrue() leg.Course.toTrue(points.at(-1))
); );
const line = handleTurnAtFix( const line = handleTurnAtFix(
leg.Course.toTrue(), leg.Course.toTrue(points.at(-1)),
leg.Course.toTrue(), leg.Course.toTrue(points.at(-1)),
points.at(-1), points.at(-1),
{ lat: end.latitude, lon: end.longitude }, { lat: end.latitude, lon: end.longitude },
leg.TurnDir, leg.TurnDir,
@@ -479,8 +689,35 @@ for (let index = 0; index < procedure.length; index++) {
case "HM": case "HM":
case "IF": case "IF":
case "PI": case "PI":
case "RF":
break; 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": { case "TF": {
// Push in ending waypoint // Push in ending waypoint
points.push({ points.push({
@@ -523,7 +760,7 @@ for (let index = 0; index < procedure.length; index++) {
((parseAltitude(leg.Alt) - points.at(-1).alt) / AC_VS) * ((parseAltitude(leg.Alt) - points.at(-1).alt) / AC_VS) *
(AC_SPEED / 60) (AC_SPEED / 60)
), ),
leg.Course.toTrue() leg.Course.toTrue(points.at(-1))
); );
points.push({ points.push({
lat: end.latitude, lat: end.latitude,
@@ -535,8 +772,8 @@ for (let index = 0; index < procedure.length; index++) {
}); });
const line = handleTurnAtFix( const line = handleTurnAtFix(
leg.Course.toTrue(), leg.Course.toTrue(points.at(-2)),
leg.Course.toTrue(), leg.Course.toTrue(points.at(-2)),
points.at(-2), points.at(-2),
{ lat: end.latitude, lon: end.longitude }, { lat: end.latitude, lon: end.longitude },
leg.TurnDir, leg.TurnDir,
@@ -558,7 +795,7 @@ for (let index = 0; index < procedure.length; index++) {
}, },
//NOTE: Does not account for slant //NOTE: Does not account for slant
nmiToMetre(leg.Distance), nmiToMetre(leg.Distance),
leg.Course.toTrue() leg.Course.toTrue(points.at(-1))
); );
points.push({ points.push({
lat: end.latitude, lat: end.latitude,
@@ -570,8 +807,8 @@ for (let index = 0; index < procedure.length; index++) {
}); });
const line = handleTurnAtFix( const line = handleTurnAtFix(
leg.Course.toTrue(), leg.Course.toTrue(points.at(-2)),
leg.Course.toTrue(), leg.Course.toTrue(points.at(-2)),
points.at(-2), points.at(-2),
{ lat: end.latitude, lon: end.longitude }, { lat: end.latitude, lon: end.longitude },
leg.TurnDir, leg.TurnDir,
@@ -588,20 +825,21 @@ for (let index = 0; index < procedure.length; index++) {
// Course into the destination fix and said fix // Course into the destination fix and said fix
const [inbdCrs, fix] = getCourseAndFixForInterceptions( const [inbdCrs, fix] = getCourseAndFixForInterceptions(
procedure[index + 1] procedure[index + 1],
points.at(-1)
); );
// Compute INTC // Compute INTC
const intc = computeIntersection( const intc = computeIntersection(
points.at(-1), points.at(-1),
leg.Course.toTrue(), leg.Course.toTrue(fix),
fix, fix,
inbdCrs inbdCrs
); );
const line = handleTurnAtFix( const line = handleTurnAtFix(
inbdCrs, inbdCrs,
leg.Course.toTrue(), leg.Course.toTrue(fix),
points.at(-1), points.at(-1),
intc, intc,
leg.TurnDir, leg.TurnDir,
@@ -612,7 +850,7 @@ for (let index = 0; index < procedure.length; index++) {
const intc2 = computeIntersection( const intc2 = computeIntersection(
{ lat: line.at(-1)[1], lon: line.at(-1)[0] }, { lat: line.at(-1)[1], lon: line.at(-1)[0] },
leg.Course.toTrue(), leg.Course.toTrue(fix),
fix, fix,
inbdCrs inbdCrs
); );
@@ -631,12 +869,12 @@ for (let index = 0; index < procedure.length; index++) {
longitude: points.at(-1).lon, longitude: points.at(-1).lon,
}, },
nmiToMetre(10), nmiToMetre(10),
leg.Course.toTrue() leg.Course.toTrue(points.at(-1))
); );
const line = handleTurnAtFix( const line = handleTurnAtFix(
leg.Course.toTrue(), leg.Course.toTrue(points.at(-1)),
leg.Course.toTrue(), leg.Course.toTrue(points.at(-1)),
points.at(-1), points.at(-1),
{ lat: end.latitude, lon: end.longitude }, { lat: end.latitude, lon: end.longitude },
leg.TurnDir, leg.TurnDir,