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]; };