121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
|
|
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
|
|
import getPreciseDistance from 'geolib/es/getPreciseDistance';
|
|
import Parser from '../parser';
|
|
import { generateTangentArc } from '../pathGenerators/generateTangentArc';
|
|
import { computeSpeed } from '../utils/computeSpeed';
|
|
import { computeTurnRate } from '../utils/computeTurnRate';
|
|
|
|
export const TerminatorsCF = (
|
|
leg: CFTerminalEntry,
|
|
previousFix: NavFix,
|
|
lastCourse: number,
|
|
waypoint?: Waypoint
|
|
): [NavFix?, LineSegment[]?] => {
|
|
const speed = computeSpeed(leg, previousFix);
|
|
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
|
|
const line: LineSegment[] = [];
|
|
|
|
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: speed,
|
|
speedConstraint: leg.SpeedLimit,
|
|
altitudeConstraint: leg.Alt,
|
|
IsFAF: leg.IsFAF,
|
|
IsMAP: leg.IsMAP,
|
|
};
|
|
const crsToIntercept = leg.Course.toTrue(targetFix);
|
|
|
|
// Compute overfly arc
|
|
let arc1: LineSegment[] | null = null;
|
|
let arc2: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
|
|
if (previousFix.isFlyOver) {
|
|
arc1 = generateTangentArc(crsIntoEndpoint, lastCourse, previousFix, targetFix, leg.TurnDir);
|
|
} else {
|
|
arc1 = [[previousFix.longitude, previousFix.latitude]];
|
|
}
|
|
|
|
let arc;
|
|
if (arc1 && arc1.length > 1) {
|
|
const endCrs = getGreatCircleBearing(
|
|
{
|
|
latitude: arc1.at(-1)![1],
|
|
longitude: arc1.at(-1)![0],
|
|
},
|
|
targetFix
|
|
);
|
|
const endDist = getPreciseDistance(
|
|
{
|
|
latitude: arc1.at(-1)![1],
|
|
longitude: arc1.at(-1)![0],
|
|
},
|
|
targetFix
|
|
);
|
|
|
|
if (endDist <= 25 || (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)) arc = arc1;
|
|
}
|
|
|
|
if (previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsToIntercept))) {
|
|
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
|
|
let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
|
|
|
|
// 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
|
|
let lastDistance = getPreciseDistance(previousFix, targetFix);
|
|
while (!updatedCrsToIntercept.equal(crsToIntercept)) {
|
|
let interceptAngle = 0;
|
|
if (leg.TurnDir === 'R') interceptAngle = Math.abs(lastCourse - crsToIntercept);
|
|
else interceptAngle = Math.abs(crsToIntercept - lastCourse);
|
|
|
|
let time = 0;
|
|
const increment = 0.1;
|
|
if (interceptAngle < 44.9 || interceptAngle >= 45.1) {
|
|
if (leg.TurnDir === 'R') {
|
|
lastCourse = (lastCourse + increment).normaliseDegrees();
|
|
time = increment / turnRate;
|
|
} else {
|
|
lastCourse = (lastCourse - increment).normaliseDegrees();
|
|
time = increment / turnRate;
|
|
}
|
|
} else time = increment / turnRate;
|
|
|
|
const arcFix = computeDestinationPoint(
|
|
{
|
|
latitude: arc2.at(-1)![1],
|
|
longitude: arc2.at(-1)![0],
|
|
},
|
|
((speed / 3600) * time).toMetre(),
|
|
lastCourse
|
|
);
|
|
|
|
arc2.push([arcFix.longitude, arcFix.latitude]);
|
|
|
|
// Update previousFix
|
|
previousFix.latitude = arcFix.latitude;
|
|
previousFix.longitude = arcFix.longitude;
|
|
updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
|
|
|
|
const newDistance = getPreciseDistance(previousFix, targetFix);
|
|
if (lastDistance <= newDistance && lastDistance < 25) break;
|
|
lastDistance = newDistance;
|
|
}
|
|
}
|
|
|
|
if (!arc) arc = arc2;
|
|
line.push(...arc);
|
|
|
|
line.push([targetFix.longitude, targetFix.latitude]);
|
|
|
|
return [targetFix, line];
|
|
};
|