diff --git a/.gitignore b/.gitignore index 3b0b403..6961c7a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ dist-ssr *.sln *.sw? -.env \ No newline at end of file +.env + +NavData/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 2321b76..dba57b7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,8 @@ "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}/browser" + "webRoot": "${workspaceFolder}/browser", + "sourceMaps": true } ] } diff --git a/README.md b/README.md index 9d29937..9dd61f2 100644 --- a/README.md +++ b/README.md @@ -530,6 +530,8 @@ LFRN GODA5R SID (cycle 2507, ID 10485) - Arc center shall be navaid identified by `CenterID`, `CenterLat`, `CenterLon`. - Arc and turn shall be flown in direction specified by `TurnDir`. - `Distance` shall be the track miles along the curved path +- `Course` shall be the inbound course of the tangent to the arc at the fix identified by + (`WptID`, `WptLat`, `WptLon`). ### Units @@ -542,7 +544,6 @@ While similar to an AF, the center point is coded differently. No radius is specified, but can be inferred based on center point, both endpoints and arc length Example has `NavBear` set to `null`, significance of the inbound tangential track is unknown. -Same for the `Course`, which is set, but lacks any documentation. ## Track to Fix (TF) diff --git a/browser/src/App.tsx b/browser/src/App.tsx index 56123d7..ebdb5a6 100644 --- a/browser/src/App.tsx +++ b/browser/src/App.tsx @@ -46,14 +46,15 @@ function App() { setSelectedAirport={setSelectedAirport} setSelectedRunway={setSelectedRunway} setSelectedTerminal={setSelectedTerminal} - handleSelection={(selectedTransitions) => - setTransitions( - selectedTransitions.map((transition) => ({ - name: transition, - data: parser.parse(selectedRunway!, transition), - })) - ) - } + handleSelection={(selectedTransitions) => { + const _transitions = selectedTransitions.map((transition) => ({ + name: transition, + data: parser.parse(selectedRunway!, transition), + })); + setTransitions(_transitions); + setSelectedTransition(_transitions[0]); + setSelectedChart(undefined); + }} /> )} diff --git a/browser/src/main.tsx b/browser/src/main.tsx index 17006d0..566e310 100644 --- a/browser/src/main.tsx +++ b/browser/src/main.tsx @@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import 'leaflet/dist/leaflet.css'; -import { NavigraphAuthProvider } from './hooks/useNavigraphAuth.tsx'; +import { NavigraphAuthProvider } from './contexts/NavigraphAuth/NavigraphAuthProvider.tsx'; createRoot(document.getElementById('root')!).render( diff --git a/browser/src/parser/parser.ts b/browser/src/parser/parser.ts index eac10d4..d7beab5 100644 --- a/browser/src/parser/parser.ts +++ b/browser/src/parser/parser.ts @@ -1,5 +1,5 @@ import geojson from 'geojson'; -import * as geolib from 'geolib'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { TerminatorsAF } from './terminators/AF'; import { TerminatorsCA } from './terminators/CA'; import { TerminatorsCD } from './terminators/CD'; @@ -90,7 +90,7 @@ class Parser { * @param line Line segments */ const updateLastCourse = (line: LineSegment[]) => { - lastCourse = geolib.getGreatCircleBearing( + lastCourse = getGreatCircleBearing( { latitude: line.at(-2)![1], longitude: line.at(-2)![0], @@ -153,7 +153,12 @@ class Parser { break; } case 'CF': { - const [fixToAdd, lineToAdd] = TerminatorsCF(leg as CFTerminalEntry, previousFix, lastCourse, waypoint); + const [fixToAdd, lineToAdd] = TerminatorsCF( + leg as CFTerminalEntry, + { ...previousFix }, // COPY + lastCourse, + waypoint + ); update(fixToAdd, lineToAdd); break; } @@ -161,7 +166,7 @@ class Parser { const [fixToAdd, lineToAdd] = TerminatorsCI( leg as CITerminalEntry, procedure[index + 1], - previousFix, + { ...previousFix }, // COPY lastCourse ); update(fixToAdd, lineToAdd); @@ -212,14 +217,14 @@ class Parser { console.error('Unknown TrackCode', leg.TrackCode); break; case 'RF': { - const [fixToAdd, lineToAdd] = TerminatorsRF( - leg as RFTerminalEntry, - procedure[index + 1], - previousFix, - lastCourse, - waypoint - ); - update(fixToAdd, lineToAdd); + const [fixToAdd, lineToAdd] = TerminatorsRF(leg as RFTerminalEntry, previousFix, lastCourse, waypoint); + if (fixToAdd) { + navFixes.push(fixToAdd); + lastCourse = (leg as RFTerminalEntry).Course?.toTrue(fixToAdd); + } + if (lineToAdd) { + lineSegments.push({ line: lineToAdd }); + } break; } case 'TF': { @@ -241,7 +246,7 @@ class Parser { const [fixToAdd, lineToAdd] = TerminatorsVI( leg as VITerminalEntry, procedure[index + 1], - previousFix, + { ...previousFix }, // COPY lastCourse ); update(fixToAdd, lineToAdd); diff --git a/browser/src/parser/pathGenerators/generateAFArc.ts b/browser/src/parser/pathGenerators/generateAFArc.ts index 78c1cc4..f9c6fc0 100644 --- a/browser/src/parser/pathGenerators/generateAFArc.ts +++ b/browser/src/parser/pathGenerators/generateAFArc.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; /** * @param crsIntoEndpoint Course into arc endpoint @@ -39,7 +39,7 @@ export const generateAFArc = ( } if (crsFromOrigin === crsIntoEndpoint) break; - const arcFix = geolib.computeDestinationPoint(center, radius.toMetre(), crsFromOrigin); + const arcFix = computeDestinationPoint(center, radius.toMetre(), crsFromOrigin); line.push([arcFix.longitude, arcFix.latitude]); } diff --git a/browser/src/parser/pathGenerators/generateOverflyArc.ts b/browser/src/parser/pathGenerators/generateOverflyArc.ts index b1061b6..86f7166 100644 --- a/browser/src/parser/pathGenerators/generateOverflyArc.ts +++ b/browser/src/parser/pathGenerators/generateOverflyArc.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { generatePerformanceArc } from './generatePerformanceArc'; /** @@ -31,7 +31,7 @@ export const generateOverflyArc = ( // Get arc endpoint and crs into arc endpoint const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] }; if (line.length > 1) { - crsFromOrigin = geolib.getGreatCircleBearing( + crsFromOrigin = getGreatCircleBearing( { latitude: line.at(-2)![1], longitude: line.at(-2)![0], diff --git a/browser/src/parser/pathGenerators/generatePerformanceArc.ts b/browser/src/parser/pathGenerators/generatePerformanceArc.ts index 16817b8..83fd31f 100644 --- a/browser/src/parser/pathGenerators/generatePerformanceArc.ts +++ b/browser/src/parser/pathGenerators/generatePerformanceArc.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import Parser from '../parser'; import { computeTurnRate } from '../utils/computeTurnRate'; @@ -48,7 +48,7 @@ export const generatePerformanceArc = ( time = increment / turnRate; } - const arcFix = geolib.computeDestinationPoint( + const arcFix = computeDestinationPoint( { latitude: line.at(-1)![1], longitude: line.at(-1)![0], @@ -80,7 +80,7 @@ export const generatePerformanceArc = ( time = increment / turnRate; } - const arcFix = geolib.computeDestinationPoint( + const arcFix = computeDestinationPoint( { latitude: line.at(-1)![1], longitude: line.at(-1)![0], diff --git a/browser/src/parser/pathGenerators/generateRFArc.ts b/browser/src/parser/pathGenerators/generateRFArc.ts index 4eeabca..4fbcc19 100644 --- a/browser/src/parser/pathGenerators/generateRFArc.ts +++ b/browser/src/parser/pathGenerators/generateRFArc.ts @@ -1,4 +1,5 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; +import getDistance from 'geolib/es/getDistance'; /** * @param crsIntoEndpoint Course into arc endpoint @@ -17,7 +18,7 @@ export const generateRFArc = ( ) => { const line: LineSegment[] = [[start.longitude, start.latitude]]; - if (crsIntoEndpoint !== crsIntoOrigin) { + if (!crsIntoEndpoint.equal(crsIntoOrigin)) { // Turn Dir if (!turnDir || turnDir === 'E') { let prov = crsIntoOrigin - crsIntoEndpoint; @@ -35,7 +36,7 @@ export const generateRFArc = ( crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees(); } - const arcRad = geolib.getDistance(center, start); + const arcRad = getDistance(center, start); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse(); crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse(); @@ -46,7 +47,7 @@ export const generateRFArc = ( crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1; } - while (crsOrthogonalOnOrigin !== crsOrthogonalOnEndpoint) { + while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) { if (turnDir === 'R') { const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees(); crsOrthogonalOnOrigin += delta < 1 ? delta : 1; @@ -57,7 +58,7 @@ export const generateRFArc = ( crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); } - const arcFix = geolib.computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin); + const arcFix = computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin); line.push([arcFix.longitude, arcFix.latitude]); } diff --git a/browser/src/parser/pathGenerators/generateTangentArc.ts b/browser/src/parser/pathGenerators/generateTangentArc.ts index 8fd6d08..2a8d8b7 100644 --- a/browser/src/parser/pathGenerators/generateTangentArc.ts +++ b/browser/src/parser/pathGenerators/generateTangentArc.ts @@ -1,4 +1,5 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; +import getDistance from 'geolib/es/getDistance'; import { computeIntersection } from '../utils/computeIntersection'; /** @@ -62,7 +63,7 @@ export const generateTangentArc = ( crsOrthogonalOnEndpoint ); if (!arcCenter) return null; - const arcRad = geolib.getDistance(arcCenter, start); + const arcRad = getDistance(arcCenter, start); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse(); crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse(); @@ -84,7 +85,7 @@ export const generateTangentArc = ( crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); } - const arcFix = geolib.computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin); + const arcFix = computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin); line.push([arcFix.longitude, arcFix.latitude]); } diff --git a/browser/src/parser/pathGenerators/handleTurnAtFix.ts b/browser/src/parser/pathGenerators/handleTurnAtFix.ts index 3420022..b65b840 100644 --- a/browser/src/parser/pathGenerators/handleTurnAtFix.ts +++ b/browser/src/parser/pathGenerators/handleTurnAtFix.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { generatePerformanceArc } from './generatePerformanceArc'; import { generateTangentArc } from './generateTangentArc'; @@ -32,7 +32,7 @@ export const handleTurnAtFix = ( // Decide on arc let arc; if (arc1) { - const endCrs = geolib.getGreatCircleBearing( + const endCrs = getGreatCircleBearing( { latitude: arc1.at(-1)![1], longitude: arc1.at(-1)![0], @@ -48,9 +48,8 @@ export const handleTurnAtFix = ( line.push(...arc); line.push([end.longitude, end.latitude]); } - // FIXME: Procedural turn + // Procedural turn else { - // Direct line for now line.push([start.longitude, start.latitude], [end.longitude, end.latitude]); } diff --git a/browser/src/parser/terminators/AF.ts b/browser/src/parser/terminators/AF.ts index ae4ee73..5575793 100644 --- a/browser/src/parser/terminators/AF.ts +++ b/browser/src/parser/terminators/AF.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { generateAFArc } from '../pathGenerators/generateAFArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -18,7 +18,7 @@ export const TerminatorsAF = ( altitudeConstraint: leg.Alt, }; - const arcEndCrs = geolib.getGreatCircleBearing( + const arcEndCrs = getGreatCircleBearing( { latitude: leg.NavLat, longitude: leg.NavLon, diff --git a/browser/src/parser/terminators/CA.ts b/browser/src/parser/terminators/CA.ts index 0ebb51b..6b1510a 100644 --- a/browser/src/parser/terminators/CA.ts +++ b/browser/src/parser/terminators/CA.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import Parser from '../parser'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -17,7 +17,7 @@ export const TerminatorsCA = ( // Compute intercept of crs from arc end and expected altitude const targetFix: NavFix = { - ...geolib.computeDestinationPoint( + ...computeDestinationPoint( arcEnd, ( ((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) * diff --git a/browser/src/parser/terminators/CD.ts b/browser/src/parser/terminators/CD.ts index ee80e92..e1c1193 100644 --- a/browser/src/parser/terminators/CD.ts +++ b/browser/src/parser/terminators/CD.ts @@ -1,4 +1,6 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; +import getDistance from 'geolib/es/getDistance'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -20,8 +22,8 @@ export const TerminatorsCD = ( lastCourse = _lastCourse; // Compute distance to fly from arc end - const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid); - const distToNavaid = geolib.getDistance(arcEnd, navaid); + const crsToNavaid = getGreatCircleBearing(arcEnd, navaid); + const distToNavaid = getDistance(arcEnd, navaid); let remainingDistance = leg.Distance.toMetre(); // Navaid behind us if (Math.abs(crsToNavaid - lastCourse) > 90) { @@ -35,7 +37,7 @@ export const TerminatorsCD = ( // Compute intercept of crs from arc end and distance const targetFix: NavFix = { - ...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse), + ...computeDestinationPoint(arcEnd, remainingDistance, lastCourse), name: leg.Distance.toString(), isFlyOver: true, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, diff --git a/browser/src/parser/terminators/CF.ts b/browser/src/parser/terminators/CF.ts index c907d44..d5c3e4c 100644 --- a/browser/src/parser/terminators/CF.ts +++ b/browser/src/parser/terminators/CF.ts @@ -1,5 +1,9 @@ -import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; +import Parser from '../parser'; +import { computeIntersection } from '../utils/computeIntersection'; import { computeSpeed } from '../utils/computeSpeed'; +import { computeTurnRate } from '../utils/computeTurnRate'; export const TerminatorsCF = ( leg: CFTerminalEntry, @@ -8,6 +12,8 @@ export const TerminatorsCF = ( waypoint?: Waypoint ): [NavFix?, LineSegment[]?] => { const speed = computeSpeed(leg, previousFix); + const crsIntoEndpoint = leg.Course.toTrue(previousFix); + const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]]; const targetFix: NavFix = { latitude: leg.WptLat, @@ -19,17 +25,70 @@ export const TerminatorsCF = ( speedConstraint: leg.SpeedLimit, altitudeConstraint: leg.Alt, }; + const crsToIntercept = leg.Course.toTrue(targetFix); - // Compute arc - const line = handleTurnAtFix( - leg.Course.toTrue(previousFix), - leg.Course.toTrue(previousFix), - lastCourse, - previousFix, - targetFix, - speed, - leg.TurnDir - ); + // Compute overfly arc + if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) { + 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 + while (!updatedCrsToIntercept.equal(crsToIntercept)) { + let time = 0; + if (leg.TurnDir === 'R') { + //const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); + const increment = 1; //delta < 1 ? delta : 1; + lastCourse = (lastCourse + increment).normaliseDegrees(); + time = increment / turnRate; + } else { + //const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees(); + const increment = 1; //delta < 1 ? delta : 1; + lastCourse = (lastCourse - increment).normaliseDegrees(); + time = increment / turnRate; + } + + const arcFix = computeDestinationPoint( + { + latitude: line.at(-1)![1], + longitude: line.at(-1)![0], + }, + ((speed / 3600) * time).toMetre(), + lastCourse + ); + + line.push([arcFix.longitude, arcFix.latitude]); + + // Update previousFix + previousFix.latitude = arcFix.latitude; + previousFix.longitude = arcFix.longitude; + updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix); + + let interceptAngle = 0; + if (leg.TurnDir === 'R') Math.abs((interceptAngle = lastCourse - crsToIntercept)); + else interceptAngle = Math.abs(crsToIntercept - lastCourse); + + if (interceptAngle >= 45) break; + } + } + + const interceptFix: NavFix = { + ...computeIntersection(previousFix, leg.Course.toTrue(previousFix), targetFix, crsToIntercept.reciprocalCourse())!, + isFlyOver: leg.IsFlyOver, + altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, + speed: speed, + speedConstraint: leg.SpeedLimit, + altitudeConstraint: leg.Alt, + }; + if (interceptFix.latitude) line.push([interceptFix.longitude, interceptFix.latitude]); + + line.push([targetFix.longitude, targetFix.latitude]); return [targetFix, line]; }; diff --git a/browser/src/parser/terminators/CI.ts b/browser/src/parser/terminators/CI.ts index 318ec8a..9ebaafb 100644 --- a/browser/src/parser/terminators/CI.ts +++ b/browser/src/parser/terminators/CI.ts @@ -1,6 +1,9 @@ -import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; +import Parser from '../parser'; import { computeIntersection } from '../utils/computeIntersection'; import { computeSpeed } from '../utils/computeSpeed'; +import { computeTurnRate } from '../utils/computeTurnRate'; import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts'; export const TerminatorsCI = ( @@ -9,12 +12,57 @@ export const TerminatorsCI = ( previousFix: NavFix, lastCourse: number ): [NavFix?, LineSegment[]?] => { - const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix); const speed = computeSpeed(leg, previousFix); + const crsIntoEndpoint = leg.Course.toTrue(previousFix); + const [crsToIntercept, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix); + const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]]; + + // Compute overfly arc + if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) { + const turnRate = computeTurnRate(speed, Parser.AC_BANK); + const updatedCrsToIntercept = getGreatCircleBearing(previousFix, nextFix); + + // 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 + while (!lastCourse.equal(crsIntoEndpoint) && !updatedCrsToIntercept.equal(crsToIntercept)) { + let time = 0; + if (leg.TurnDir === 'R') { + const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); + const increment = delta < 1 ? delta : 1; + lastCourse = (lastCourse + increment).normaliseDegrees(); + time = increment / turnRate; + } else { + const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees(); + const increment = delta < 1 ? delta : 1; + lastCourse = (lastCourse - increment).normaliseDegrees(); + time = increment / turnRate; + } + + const arcFix = computeDestinationPoint( + { + latitude: line.at(-1)![1], + longitude: line.at(-1)![0], + }, + ((speed / 3600) * time).toMetre(), + lastCourse + ); + + line.push([arcFix.longitude, arcFix.latitude]); + + // Update previousFix + previousFix.latitude = arcFix.latitude; + previousFix.longitude = arcFix.longitude; + } + } - // Compute intercept fix const interceptFix: NavFix = { - ...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!, + ...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crsToIntercept)!, isFlyOver: leg.IsFlyOver, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, speed: speed, @@ -22,29 +70,7 @@ export const TerminatorsCI = ( altitudeConstraint: leg.Alt, }; - // Compute arc - const line = handleTurnAtFix( - crs, - leg.Course.toTrue(nextFix), - lastCourse, - previousFix, - interceptFix, - speed, - leg.TurnDir - ); - - // Recompute 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]], - ]; + line.push([interceptFix.longitude, interceptFix.latitude]); return [interceptFix, line]; }; diff --git a/browser/src/parser/terminators/DF.ts b/browser/src/parser/terminators/DF.ts index 1db746b..dd37b93 100644 --- a/browser/src/parser/terminators/DF.ts +++ b/browser/src/parser/terminators/DF.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -21,7 +21,7 @@ export const TerminatorsDF = ( altitudeConstraint: leg.Alt, }; - const crsIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix); + const crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix); // Compute overfly const [line, _, _lastCourse] = generateOverflyArc( diff --git a/browser/src/parser/terminators/FA.ts b/browser/src/parser/terminators/FA.ts index f15c29f..fe87314 100644 --- a/browser/src/parser/terminators/FA.ts +++ b/browser/src/parser/terminators/FA.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import Parser from '../parser'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -21,7 +21,7 @@ export const TerminatorsFA = ( // Compute intercept of crs from arc end and expected altitude const targetFix: NavFix = { - ...geolib.computeDestinationPoint( + ...computeDestinationPoint( arcEnd, ( ((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) * diff --git a/browser/src/parser/terminators/FC.ts b/browser/src/parser/terminators/FC.ts index 40a898a..a90f3a7 100644 --- a/browser/src/parser/terminators/FC.ts +++ b/browser/src/parser/terminators/FC.ts @@ -1,7 +1,8 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import Parser from '../parser'; import { computeSpeed } from '../utils/computeSpeed'; import { computeTurnRate } from '../utils/computeTurnRate'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; // NOTE: Distance not adjusted for altitude in this demo export const TerminatorsFC = ( @@ -47,7 +48,7 @@ export const TerminatorsFC = ( time = increment / turnRate; } - const arcFix = geolib.computeDestinationPoint( + const arcFix = computeDestinationPoint( { latitude: line.at(-1)![1], longitude: line.at(-1)![0], @@ -69,7 +70,7 @@ export const TerminatorsFC = ( const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] }; if (line.length > 1) { - lastCourse = geolib.getGreatCircleBearing( + lastCourse = getGreatCircleBearing( { latitude: line.at(-2)![1], longitude: line.at(-2)![0], @@ -79,7 +80,7 @@ export const TerminatorsFC = ( } const targetFix: NavFix = { - ...geolib.computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse), + ...computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse), name: leg.Distance.toString(), isFlyOver: true, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, diff --git a/browser/src/parser/terminators/FD.ts b/browser/src/parser/terminators/FD.ts index 55df3b1..27c9693 100644 --- a/browser/src/parser/terminators/FD.ts +++ b/browser/src/parser/terminators/FD.ts @@ -1,4 +1,6 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; +import getDistance from 'geolib/es/getDistance'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -24,8 +26,8 @@ export const TerminatorsFD = ( lastCourse = _lastCourse; // Compute distance to fly from arc end - const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid); - const distToNavaid = geolib.getDistance(arcEnd, navaid); + const crsToNavaid = getGreatCircleBearing(arcEnd, navaid); + const distToNavaid = getDistance(arcEnd, navaid); let remainingDistance = leg.Distance.toMetre(); // Navaid behind us if (Math.abs(crsToNavaid - lastCourse) > 90) { @@ -39,7 +41,7 @@ export const TerminatorsFD = ( // Compute intercept of crs from arc end and distance const targetFix: NavFix = { - ...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse), + ...computeDestinationPoint(arcEnd, remainingDistance, lastCourse), name: leg.Distance.toString(), isFlyOver: true, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, diff --git a/browser/src/parser/terminators/FM.ts b/browser/src/parser/terminators/FM.ts index 9d8ada5..ba49d7e 100644 --- a/browser/src/parser/terminators/FM.ts +++ b/browser/src/parser/terminators/FM.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; import { computeSpeed } from '../utils/computeSpeed'; @@ -9,7 +9,7 @@ export const TerminatorsFM = ( ): [NavFix?, LineSegment[]?] => { const speed = computeSpeed(leg, previousFix); - const endpoint = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix)); + const endpoint = computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix)); const line = handleTurnAtFix( leg.Course.toTrue(previousFix), diff --git a/browser/src/parser/terminators/RF.ts b/browser/src/parser/terminators/RF.ts index 3b32707..3b57d09 100644 --- a/browser/src/parser/terminators/RF.ts +++ b/browser/src/parser/terminators/RF.ts @@ -1,10 +1,8 @@ import { generateRFArc } from '../pathGenerators/generateRFArc'; import { computeSpeed } from '../utils/computeSpeed'; -import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts'; export const TerminatorsRF = ( leg: RFTerminalEntry, - nextLeg: TerminalEntry, previousFix: NavFix, lastCourse: number, waypoint?: Waypoint @@ -20,10 +18,8 @@ export const TerminatorsRF = ( altitudeConstraint: leg.Alt, }; - const [crs] = getCourseAndFixForIntercepts(nextLeg, previousFix); - const line = generateRFArc( - crs, + leg.Course.toTrue(targetFix), lastCourse, previousFix, { latitude: leg.CenterLat, longitude: leg.CenterLon }, diff --git a/browser/src/parser/terminators/TF.ts b/browser/src/parser/terminators/TF.ts index b4c668c..e750f80 100644 --- a/browser/src/parser/terminators/TF.ts +++ b/browser/src/parser/terminators/TF.ts @@ -1,6 +1,7 @@ -import * as geolib from 'geolib'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import Parser from '../parser'; import { computeSpeed } from '../utils/computeSpeed'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; export const TerminatorsTF = ( leg: TFTerminalEntry, @@ -21,7 +22,7 @@ export const TerminatorsTF = ( const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]]; - const trackIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix); + const trackIntoEndpoint = getGreatCircleBearing(previousFix, targetFix); if (previousFix.isFlyOver) { let crsIntoEndpoint = trackIntoEndpoint; @@ -48,7 +49,7 @@ export const TerminatorsTF = ( lastCourse = lastCourse.normaliseDegrees(); } - const arcFix = geolib.computeDestinationPoint( + const arcFix = computeDestinationPoint( { latitude: line.at(-1)![1], longitude: line.at(-1)![0], @@ -59,7 +60,7 @@ export const TerminatorsTF = ( line.push([arcFix.longitude, arcFix.latitude]); - crsIntoEndpoint = geolib.getGreatCircleBearing(arcFix, targetFix); + crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix); if (leg.TurnDir === 'R') { condition = crsIntoEndpoint > trackIntoEndpoint; diff --git a/browser/src/parser/terminators/VA.ts b/browser/src/parser/terminators/VA.ts index 15a834b..8254d50 100644 --- a/browser/src/parser/terminators/VA.ts +++ b/browser/src/parser/terminators/VA.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import Parser from '../parser'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -18,7 +18,7 @@ export const TerminatorsVA = ( // Compute intercept of crs from arc end and expected altitude const targetFix: NavFix = { - ...geolib.computeDestinationPoint( + ...computeDestinationPoint( arcEnd, ( ((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) * diff --git a/browser/src/parser/terminators/VD.ts b/browser/src/parser/terminators/VD.ts index 36b64f3..c5be10f 100644 --- a/browser/src/parser/terminators/VD.ts +++ b/browser/src/parser/terminators/VD.ts @@ -1,4 +1,6 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; +import getDistance from 'geolib/es/getDistance'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; import { generateOverflyArc } from '../pathGenerators/generateOverflyArc'; import { computeSpeed } from '../utils/computeSpeed'; @@ -21,8 +23,8 @@ export const TerminatorsVD = ( lastCourse = _lastCourse; // Compute distance to fly from arc end - const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid); - const distToNavaid = geolib.getDistance(arcEnd, navaid); + const crsToNavaid = getGreatCircleBearing(arcEnd, navaid); + const distToNavaid = getDistance(arcEnd, navaid); let remainingDistance = leg.Distance.toMetre(); // Navaid behind us if (Math.abs(crsToNavaid - lastCourse) > 90) { @@ -36,7 +38,7 @@ export const TerminatorsVD = ( // Compute intercept of crs from arc end and distance const targetFix: NavFix = { - ...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse), + ...computeDestinationPoint(arcEnd, remainingDistance, lastCourse), name: leg.Distance.toString(), isFlyOver: true, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, diff --git a/browser/src/parser/terminators/VI.ts b/browser/src/parser/terminators/VI.ts index 905c059..7645452 100644 --- a/browser/src/parser/terminators/VI.ts +++ b/browser/src/parser/terminators/VI.ts @@ -1,7 +1,10 @@ -import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; +import Parser from '../parser'; import { computeIntersection } from '../utils/computeIntersection'; import { computeSpeed } from '../utils/computeSpeed'; +import { computeTurnRate } from '../utils/computeTurnRate'; import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; // NOTE: No wind adjustments to be made, no clue how *that* would draw export const TerminatorsVI = ( @@ -10,12 +13,57 @@ export const TerminatorsVI = ( previousFix: NavFix, lastCourse: number ): [NavFix?, LineSegment[]?] => { - const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix); const speed = computeSpeed(leg, previousFix); + const crsIntoEndpoint = leg.Course.toTrue(previousFix); + const [crsToIntercept, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix); + const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]]; + + // Compute overfly arc + if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) { + const turnRate = computeTurnRate(speed, Parser.AC_BANK); + const updatedCrsToIntercept = getGreatCircleBearing(previousFix, nextFix); + + // 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 + while (!lastCourse.equal(crsIntoEndpoint) && !updatedCrsToIntercept.equal(crsToIntercept)) { + let time = 0; + if (leg.TurnDir === 'R') { + const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees(); + const increment = delta < 1 ? delta : 1; + lastCourse = (lastCourse + increment).normaliseDegrees(); + time = increment / turnRate; + } else { + const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees(); + const increment = delta < 1 ? delta : 1; + lastCourse = (lastCourse - increment).normaliseDegrees(); + time = increment / turnRate; + } + + const arcFix = computeDestinationPoint( + { + latitude: line.at(-1)![1], + longitude: line.at(-1)![0], + }, + ((speed / 3600) * time).toMetre(), + lastCourse + ); + + line.push([arcFix.longitude, arcFix.latitude]); + + // Update previousFix + previousFix.latitude = arcFix.latitude; + previousFix.longitude = arcFix.longitude; + } + } - // Compute INTC const interceptFix: NavFix = { - ...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!, + ...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crsToIntercept)!, isFlyOver: leg.IsFlyOver, altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude, speed: speed, @@ -23,28 +71,7 @@ export const TerminatorsVI = ( altitudeConstraint: leg.Alt, }; - const line = handleTurnAtFix( - crs, - leg.Course.toTrue(nextFix), - lastCourse, - previousFix, - interceptFix, - speed, - 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]], - ]; + line.push([interceptFix.longitude, interceptFix.latitude]); return [interceptFix, line]; }; diff --git a/browser/src/parser/terminators/VM.ts b/browser/src/parser/terminators/VM.ts index 069cf8f..ab593fc 100644 --- a/browser/src/parser/terminators/VM.ts +++ b/browser/src/parser/terminators/VM.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import computeDestinationPoint from 'geolib/es/computeDestinationPoint'; import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix'; import { computeSpeed } from '../utils/computeSpeed'; @@ -10,7 +10,7 @@ export const TerminatorsVM = ( ): [NavFix?, LineSegment[]?] => { const speed = computeSpeed(leg, previousFix); - const endpoint = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix)); + const endpoint = computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix)); const line = handleTurnAtFix( leg.Course.toTrue(previousFix), diff --git a/browser/src/parser/utils/getCourseAndFixForIntercepts.ts b/browser/src/parser/utils/getCourseAndFixForIntercepts.ts index 5c021dd..50b66f8 100644 --- a/browser/src/parser/utils/getCourseAndFixForIntercepts.ts +++ b/browser/src/parser/utils/getCourseAndFixForIntercepts.ts @@ -1,4 +1,4 @@ -import * as geolib from 'geolib'; +import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing'; /** * @param leg Leg to examine @@ -18,9 +18,9 @@ export const getCourseAndFixForIntercepts = (leg: TerminalEntry, origin: NavFix) return [_leg.Course.toTrue(fix), fix]; } case 'TF': { - const _leg = leg as FMTerminalEntry; + const _leg = leg as TFTerminalEntry; return [ - geolib.getGreatCircleBearing(origin, { + getGreatCircleBearing(origin, { latitude: _leg.WptLat, longitude: _leg.WptLon, }), @@ -33,10 +33,15 @@ export const getCourseAndFixForIntercepts = (leg: TerminalEntry, origin: NavFix) return [_leg.Course.reciprocalCourse().toTrue(fix), fix]; } case 'DF': { - const _leg = leg as FMTerminalEntry; + const _leg = leg as DFTerminalEntry; const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon }; return [-1, fix]; } + case 'RF': { + const _leg = leg as RFTerminalEntry; + const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon }; + return [_leg.Course.toTrue(fix), fix]; + } default: { return [-1, origin]; }