Compare commits

..

No commits in common. "e4adf306321736ea4171ff66f598776432688513" and "f593cbd29a43a514d841114a050367432e310194" have entirely different histories.

36 changed files with 366 additions and 531 deletions

2
.gitignore vendored
View File

@ -24,5 +24,3 @@ dist-ssr
*.sw?
.env
NavData/

3
.vscode/launch.json vendored
View File

@ -9,8 +9,7 @@
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/browser",
"sourceMaps": true
"webRoot": "${workspaceFolder}/browser"
}
]
}

View File

@ -530,8 +530,6 @@ 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
@ -544,6 +542,7 @@ 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)

View File

@ -0,0 +1,6 @@
Revise list of charts to only show those applicable to the selected procedure (so terminal itself and all transitions accompanied)
Revise image overlay
- Find center of full page
- Find center of georeferenced area
- Calculate skew parameters
- Skew geobounds

View File

@ -3,8 +3,7 @@ import { QRCodeSVG } from 'qrcode.react';
import { useState } from 'react';
import { ProcedureSelect } from './components//ProcedureSelect';
import { Map } from './components/Map';
import { Sidebar } from './components/Sidebar';
import { useNavigraphAuth } from './contexts/NavigraphAuth/NavigraphAuthContext';
import { useNavigraphAuth } from './hooks/useNavigraphAuth';
import Parser from './parser/parser';
const parser = await Parser.instance();
@ -13,10 +12,8 @@ function App() {
const [selectedAirport, setSelectedAirport] = useState<Airport>();
const [selectedRunway, setSelectedRunway] = useState<Runway>();
const [selectedTerminal, setSelectedTerminal] = useState<Terminal>();
const [transitions, setTransitions] = useState<Procedure[]>([]);
const [procedures, setProcedures] = useState<{ name: string; data: object }[]>([]);
const [params, setParams] = useState<DeviceFlowParams | null>(null);
const [selectedTransition, setSelectedTransition] = useState<Procedure>();
const [selectedChart, setSelectedChart] = useState<Chart>();
const { user, signIn, initialized } = useNavigraphAuth();
@ -24,7 +21,7 @@ function App() {
return (
<>
{transitions.length === 0 ? (
{procedures.length === 0 ? (
<div className="flex min-h-dvh w-full">
{!initialized && <div>Loading...</div>}
@ -46,37 +43,29 @@ function App() {
setSelectedAirport={setSelectedAirport}
setSelectedRunway={setSelectedRunway}
setSelectedTerminal={setSelectedTerminal}
handleSelection={(selectedTransitions) => {
const _transitions = selectedTransitions.map((transition) => ({
name: transition,
data: parser.parse(selectedRunway!, transition),
}));
setTransitions(_transitions);
setSelectedTransition(_transitions[0]);
setSelectedChart(undefined);
}}
handleSelection={(selectedTransitions) =>
setProcedures(
selectedTransitions.map((transition) => ({
name: transition,
data: parser.parse(selectedRunway!, transition),
}))
)
}
/>
)}
</div>
) : (
<div className="flex h-dvh w-dvw">
{transitions.length > 0 && selectedAirport && selectedTerminal ? (
<>
<Sidebar
airport={selectedAirport}
terminal={selectedTerminal}
transitions={transitions}
transition={selectedTransition}
chart={selectedChart}
setTransition={setSelectedTransition}
setChart={setSelectedChart}
backAction={() => {
setSelectedTerminal(undefined);
setTransitions([]);
}}
/>
<Map airport={selectedAirport} chart={selectedChart} procedure={selectedTransition} />
</>
{procedures.length > 0 && selectedAirport && selectedTerminal ? (
<Map
airport={selectedAirport}
terminal={selectedTerminal}
procedures={procedures}
backAction={() => {
setSelectedTerminal(undefined);
setProcedures([]);
}}
/>
) : (
<h1 className="text-center text-3xl">Error</h1>
)}

View File

@ -1,105 +1,197 @@
import { default as L } from 'leaflet';
import BrowserImageManipulation from 'browser-image-manipulation';
import { default as L, type LatLngBoundsExpression } from 'leaflet';
import 'leaflet-svg-shape-markers';
import { createRef, type FC } from 'react';
import { type Chart } from 'navigraph/charts';
import { createRef, useEffect, useState, type FC } from 'react';
import { GeoJSON, ImageOverlay, MapContainer, TileLayer } from 'react-leaflet';
import { charts } from '../lib/navigraph';
interface MapProps {
airport: Airport;
chart: Chart | undefined;
procedure: Procedure | undefined;
terminal: Terminal;
procedures: { name: string; data: object }[];
backAction: () => void;
}
export const Map: FC<MapProps> = ({ airport, chart, procedure }) => {
export const Map: FC<MapProps> = ({ airport, terminal, procedures, backAction }) => {
const [selectedProcedure, setSelectedProcedure] = useState(procedures[0]);
const [chartIndex, setChartIndex] = useState<Chart[]>([]);
const [selectedChart, setSelectedChart] = useState<{
data: string;
index_number: string;
bounds: LatLngBoundsExpression;
}>();
const mapRef = createRef<L.Map>();
const imageRef = createRef<L.ImageOverlay>();
useEffect(() => {
(async () => {
setChartIndex((await charts.getChartsIndex({ icao: airport.ICAO, version: 'STD' })) ?? []);
})();
}, []);
return (
<MapContainer
center={[airport.Latitude, airport.Longitude]}
zoom={13}
zoomSnap={0}
className="h-full w-full"
ref={(_mapRef) => {
_mapRef?.attributionControl.setPosition('topright');
_mapRef?.zoomControl.setPosition('topright');
mapRef.current = _mapRef;
}}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<>
<button
className="fixed top-2 left-2 z-[5000] cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
onClick={backAction}
>
Go back
</button>
{chart && chart.bounds && (
<ImageOverlay
url={chart.data}
bounds={chart.bounds}
opacity={0.75}
ref={(_imageRef) => {
if (_imageRef) {
mapRef.current?.fitBounds(_imageRef.getBounds(), {
padding: [-50, -50],
});
}
imageRef.current = _imageRef;
}}
<MapContainer
center={[airport.Latitude, airport.Longitude]}
zoom={13}
zoomSnap={0}
className="h-full w-full"
ref={(_mapRef) => {
_mapRef?.attributionControl.setPosition('topright');
_mapRef?.zoomControl.setPosition('topright');
mapRef.current = _mapRef;
}}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
)}
{procedure && (
<>
<GeoJSON
key={`${procedure.name}-lines`}
data={procedure.data}
style={({ properties }) => ({
color: '#ff00ff',
stroke: true,
weight: 5,
opacity: 1,
dashArray: properties.isManual ? '20, 20' : undefined,
})}
filter={(feature) => feature.geometry.type !== 'Point'}
/>
<GeoJSON
key={`${procedure.name}-points`}
data={procedure.data}
style={{
color: 'black',
fill: true,
fillColor: 'transparent',
stroke: true,
weight: 3,
}}
pointToLayer={({ properties }, latlng) => {
if (properties.isFlyOver)
return L.shapeMarker(latlng, {
shape: 'triangle',
radius: 6,
{selectedChart && selectedChart.bounds && (
<ImageOverlay
url={selectedChart.data}
bounds={selectedChart.bounds}
opacity={0.75}
ref={(_imageRef) => {
if (_imageRef) {
mapRef.current?.fitBounds(_imageRef.getBounds(), {
padding: [-50, -50],
});
if (properties.isIntersection) return L.circleMarker(latlng, { radius: 6 });
return L.shapeMarker(latlng, {
shape: 'star-4',
radius: 10,
rotation: 45,
});
}
imageRef.current = _imageRef;
}}
onEachFeature={({ geometry, properties }, layer) => {
if (geometry.type === 'Point') {
layer.bindPopup(
`${properties.name}<br>
/>
)}
<GeoJSON
key={`${selectedProcedure.name}-lines`}
data={selectedProcedure.data}
style={({ properties }) => ({
color: '#ff00ff',
stroke: true,
weight: 5,
opacity: 1,
dashArray: properties.isManual ? '20, 20' : undefined,
})}
filter={(feature) => feature.geometry.type !== 'Point'}
/>
<GeoJSON
key={`${selectedProcedure.name}-points`}
data={selectedProcedure.data}
style={{
color: 'black',
fill: true,
fillColor: 'transparent',
stroke: true,
weight: 3,
}}
pointToLayer={({ properties }, latlng) => {
if (properties.isFlyOver)
return L.shapeMarker(latlng, {
shape: 'triangle',
radius: 6,
});
if (properties.isIntersection) return L.circleMarker(latlng, { radius: 6 });
return L.shapeMarker(latlng, {
shape: 'star-4',
radius: 10,
rotation: 45,
});
}}
onEachFeature={({ geometry, properties }, layer) => {
if (geometry.type === 'Point') {
layer.bindPopup(
`${properties.name}<br>
${properties.altitude} ft<br>
${properties.speed} kts<br>
CNSTR:
${properties.altitudeConstraint ?? ''}
${properties.speedConstraint ?? ''}<br>`
);
}
}}
filter={(feature) => feature.geometry.type === 'Point'}
/>
</>
)}
</MapContainer>
);
}
}}
filter={(feature) => feature.geometry.type === 'Point'}
/>
</MapContainer>
<div className="absolute right-0 bottom-0 left-0 z-[5000] bg-[#ffffff77] bg-blend-color backdrop-blur-xs">
<div className="flex items-center gap-2 overflow-x-auto p-2">
<span className="text-lg font-semibold">Procedures:</span>
{procedures.map((procedure) => (
<button
key={procedure.name}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${selectedProcedure.name === procedure.name ? 'outline-2' : ''}`}
onClick={() => {
if (selectedProcedure.name === procedure.name) return;
setSelectedProcedure(procedure);
}}
>
{procedure.name ? procedure.name : 'ZZZZ'}
</button>
))}
</div>
<div className="flex items-center gap-2">
<span className="text-lg font-semibold">Charts:</span>
<div className="flex items-center gap-2 overflow-x-auto p-2">
{chartIndex
.filter((chart) => chart.is_georeferenced)
.map((chart) => (
<button
key={chart.index_number}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold whitespace-nowrap focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${selectedChart?.index_number === chart.index_number ? 'outline-2' : ''}`}
onClick={async () => {
if (selectedChart?.index_number === chart.index_number) return;
if (!mapRef.current) return;
if (!chart.bounding_boxes) return;
const planView = chart.bounding_boxes.planview;
const chartImage = await charts.getChartImage({ chart, theme: 'light' });
if (!chartImage) return;
// Crop
const dataURL = await new BrowserImageManipulation()
.loadBlob(chartImage)
.crop(
planView.pixels.x2 - planView.pixels.x1,
planView.pixels.y1 - planView.pixels.y2,
planView.pixels.x1,
planView.pixels.y2
)
.saveAsImage();
const bounds = new L.LatLngBounds(
[planView.latlng.lat1, planView.latlng.lng1],
[planView.latlng.lat2, planView.latlng.lng2]
);
setSelectedChart({ data: dataURL, index_number: chart.index_number, bounds });
if (imageRef.current) {
mapRef.current?.fitBounds(imageRef.current.getBounds(), {
padding: [-50, -50],
});
}
}}
>
{chart.name}
</button>
))}
</div>
</div>
</div>
</>
);
};

View File

@ -1,109 +0,0 @@
import BrowserImageManipulation from 'browser-image-manipulation';
import L from 'leaflet';
import type { Chart as NGChart } from 'navigraph/charts';
import { useEffect, useState, type Dispatch, type FC, type SetStateAction } from 'react';
import { charts } from '../lib/navigraph';
interface SidebarProps {
airport: Airport;
terminal: Terminal;
transitions: Procedure[];
transition: Procedure | undefined;
chart: Chart | undefined;
setTransition: Dispatch<SetStateAction<SidebarProps['transition']>>;
setChart: Dispatch<SetStateAction<SidebarProps['chart']>>;
backAction: () => void;
}
export const Sidebar: FC<SidebarProps> = ({
airport,
terminal,
transitions,
transition,
chart,
setTransition,
setChart,
backAction,
}) => {
const [chartIndex, setChartIndex] = useState<NGChart[]>([]);
useEffect(() => {
(async () => {
setChartIndex((await charts.getChartsIndex({ icao: airport.ICAO, version: 'STD' })) ?? []);
})();
}, [airport.ICAO]);
return (
<div className="flex h-full w-[300px] shrink-0 flex-col overflow-hidden">
<button
className="sticky top-0 m-2 cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
onClick={backAction}
>
Go back
</button>
<div className="flex h-[calc(100%-50px)] flex-col gap-2 overflow-y-auto px-2 pb-2">
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">
Transitions for <span className="font-bold">{terminal.FullName}</span>
</div>
{transitions.map((_procedure) => (
<button
key={_procedure.name}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${_procedure.name === transition?.name ? 'outline-2' : ''}`}
onClick={() => {
if (_procedure.name === transition?.name) return;
setTransition(_procedure);
}}
>
{_procedure.name ? _procedure.name : 'ZZZZ'}
</button>
))}
</div>
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Charts</div>
{chartIndex
.filter((_chart) => _chart.is_georeferenced)
.map((_chart) => (
<button
key={_chart.index_number}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 text-left font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${chart?.index_number === _chart.index_number ? 'outline-2' : ''}`}
onClick={async () => {
if (chart?.index_number === _chart.index_number) return;
if (!_chart.bounding_boxes) return;
const planView = _chart.bounding_boxes.planview;
const chartImage = await charts.getChartImage({ chart: _chart, theme: 'light' });
if (!chartImage) return;
// Crop
const dataURL = await new BrowserImageManipulation()
.loadBlob(chartImage)
.crop(
planView.pixels.x2 - planView.pixels.x1,
planView.pixels.y1 - planView.pixels.y2,
planView.pixels.x1,
planView.pixels.y2
)
.saveAsImage();
const bounds = new L.LatLngBounds(
[planView.latlng.lat1, planView.latlng.lng1],
[planView.latlng.lat2, planView.latlng.lng2]
);
setChart({ data: dataURL, index_number: _chart.index_number, bounds });
}}
>
<span className="font-bold">{_chart.index_number}</span>
<br />
{_chart.name}
</button>
))}
</div>
</div>
</div>
);
};

View File

@ -1,19 +0,0 @@
import { type User } from 'navigraph/auth';
import { createContext, useContext } from 'react';
import { auth } from '../../lib/navigraph';
interface NavigraphAuthContext {
initialized: boolean;
user: User | null;
signIn: typeof auth.signInWithDeviceFlow;
}
export const authContext = createContext<NavigraphAuthContext>({
initialized: false,
user: null,
signIn: () => Promise.reject('Not initialized'),
});
export const useNavigraphAuth = () => {
return useContext(authContext);
};

View File

@ -1,7 +1,18 @@
import type { User } from 'navigraph/auth';
import { useEffect, useState } from 'react';
import { auth } from '../../lib/navigraph';
import { authContext } from './NavigraphAuthContext';
import { type User } from 'navigraph/auth';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { auth } from '../lib/navigraph';
interface NavigraphAuthContext {
initialized: boolean;
user: User | null;
signIn: typeof auth.signInWithDeviceFlow;
}
const authContext = createContext<NavigraphAuthContext>({
initialized: false,
user: null,
signIn: () => Promise.reject('Not initialized'),
});
// Provider hook that creates auth object and handles state
function useProvideAuth() {
@ -31,7 +42,12 @@ function useProvideAuth() {
// Provider component that wraps your app and makes auth object
// available to any child component that calls useAuth().
export function NavigraphAuthProvider({ children }: { children: React.ReactNode }) {
const _auth = useProvideAuth();
return <authContext.Provider value={_auth}>{children}</authContext.Provider>;
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
// Hook for child components to get the auth object
// and re-render when it changes.
export const useNavigraphAuth = () => {
return useContext(authContext);
};

View File

@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import 'leaflet/dist/leaflet.css';
import { NavigraphAuthProvider } from './contexts/NavigraphAuth/NavigraphAuthProvider.tsx';
import { NavigraphAuthProvider } from './hooks/useNavigraphAuth.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>

View File

@ -1,5 +1,5 @@
import geojson from 'geojson';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
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 = getGreatCircleBearing(
lastCourse = geolib.getGreatCircleBearing(
{
latitude: line.at(-2)![1],
longitude: line.at(-2)![0],
@ -153,12 +153,7 @@ class Parser {
break;
}
case 'CF': {
const [fixToAdd, lineToAdd] = TerminatorsCF(
leg as CFTerminalEntry,
{ ...previousFix }, // COPY
lastCourse,
waypoint
);
const [fixToAdd, lineToAdd] = TerminatorsCF(leg as CFTerminalEntry, previousFix, lastCourse, waypoint);
update(fixToAdd, lineToAdd);
break;
}
@ -166,7 +161,7 @@ class Parser {
const [fixToAdd, lineToAdd] = TerminatorsCI(
leg as CITerminalEntry,
procedure[index + 1],
{ ...previousFix }, // COPY
previousFix,
lastCourse
);
update(fixToAdd, lineToAdd);
@ -217,14 +212,14 @@ class Parser {
console.error('Unknown TrackCode', leg.TrackCode);
break;
case 'RF': {
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 });
}
const [fixToAdd, lineToAdd] = TerminatorsRF(
leg as RFTerminalEntry,
procedure[index + 1],
previousFix,
lastCourse,
waypoint
);
update(fixToAdd, lineToAdd);
break;
}
case 'TF': {
@ -246,7 +241,7 @@ class Parser {
const [fixToAdd, lineToAdd] = TerminatorsVI(
leg as VITerminalEntry,
procedure[index + 1],
{ ...previousFix }, // COPY
previousFix,
lastCourse
);
update(fixToAdd, lineToAdd);

View File

@ -1,4 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
/**
* @param crsIntoEndpoint Course into arc endpoint
@ -39,7 +39,7 @@ export const generateAFArc = (
}
if (crsFromOrigin === crsIntoEndpoint) break;
const arcFix = computeDestinationPoint(center, radius.toMetre(), crsFromOrigin);
const arcFix = geolib.computeDestinationPoint(center, radius.toMetre(), crsFromOrigin);
line.push([arcFix.longitude, arcFix.latitude]);
}

View File

@ -1,4 +1,4 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
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 = getGreatCircleBearing(
crsFromOrigin = geolib.getGreatCircleBearing(
{
latitude: line.at(-2)![1],
longitude: line.at(-2)![0],

View File

@ -1,4 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
import Parser from '../parser';
import { computeTurnRate } from '../utils/computeTurnRate';
@ -48,7 +48,7 @@ export const generatePerformanceArc = (
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
const arcFix = geolib.computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
@ -80,7 +80,7 @@ export const generatePerformanceArc = (
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
const arcFix = geolib.computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],

View File

@ -1,5 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import * as geolib from 'geolib';
/**
* @param crsIntoEndpoint Course into arc endpoint
@ -18,7 +17,7 @@ export const generateRFArc = (
) => {
const line: LineSegment[] = [[start.longitude, start.latitude]];
if (!crsIntoEndpoint.equal(crsIntoOrigin)) {
if (crsIntoEndpoint !== crsIntoOrigin) {
// Turn Dir
if (!turnDir || turnDir === 'E') {
let prov = crsIntoOrigin - crsIntoEndpoint;
@ -36,7 +35,7 @@ export const generateRFArc = (
crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees();
}
const arcRad = getDistance(center, start);
const arcRad = geolib.getDistance(center, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
@ -47,7 +46,7 @@ export const generateRFArc = (
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
}
while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
while (crsOrthogonalOnOrigin !== crsOrthogonalOnEndpoint) {
if (turnDir === 'R') {
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
@ -58,7 +57,7 @@ export const generateRFArc = (
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
}
const arcFix = computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin);
const arcFix = geolib.computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin);
line.push([arcFix.longitude, arcFix.latitude]);
}

View File

@ -1,5 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import * as geolib from 'geolib';
import { computeIntersection } from '../utils/computeIntersection';
/**
@ -63,7 +62,7 @@ export const generateTangentArc = (
crsOrthogonalOnEndpoint
);
if (!arcCenter) return null;
const arcRad = getDistance(arcCenter, start);
const arcRad = geolib.getDistance(arcCenter, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
@ -85,7 +84,7 @@ export const generateTangentArc = (
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
}
const arcFix = computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin);
const arcFix = geolib.computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin);
line.push([arcFix.longitude, arcFix.latitude]);
}

View File

@ -1,4 +1,4 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
import { generatePerformanceArc } from './generatePerformanceArc';
import { generateTangentArc } from './generateTangentArc';
@ -32,7 +32,7 @@ export const handleTurnAtFix = (
// Decide on arc
let arc;
if (arc1) {
const endCrs = getGreatCircleBearing(
const endCrs = geolib.getGreatCircleBearing(
{
latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0],
@ -48,8 +48,9 @@ export const handleTurnAtFix = (
line.push(...arc);
line.push([end.longitude, end.latitude]);
}
// Procedural turn
// FIXME: Procedural turn
else {
// Direct line for now
line.push([start.longitude, start.latitude], [end.longitude, end.latitude]);
}

View File

@ -1,4 +1,4 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
import { generateAFArc } from '../pathGenerators/generateAFArc';
import { computeSpeed } from '../utils/computeSpeed';
@ -18,7 +18,7 @@ export const TerminatorsAF = (
altitudeConstraint: leg.Alt,
};
const arcEndCrs = getGreatCircleBearing(
const arcEndCrs = geolib.getGreatCircleBearing(
{
latitude: leg.NavLat,
longitude: leg.NavLon,

View File

@ -1,4 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
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 = {
...computeDestinationPoint(
...geolib.computeDestinationPoint(
arcEnd,
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *

View File

@ -1,6 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
@ -22,8 +20,8 @@ export const TerminatorsCD = (
lastCourse = _lastCourse;
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid);
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = geolib.getDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
@ -37,7 +35,7 @@ export const TerminatorsCD = (
// Compute intercept of crs from arc end and distance
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,9 +1,5 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { computeIntersection } from '../utils/computeIntersection';
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsCF = (
leg: CFTerminalEntry,
@ -12,8 +8,6 @@ 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,
@ -25,70 +19,17 @@ export const TerminatorsCF = (
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const crsToIntercept = leg.Course.toTrue(targetFix);
// 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]);
// Compute arc
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
targetFix,
speed,
leg.TurnDir
);
return [targetFix, line];
};

View File

@ -1,9 +1,6 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
export const TerminatorsCI = (
@ -12,57 +9,12 @@ 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, crsToIntercept)!,
...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
@ -70,7 +22,29 @@ export const TerminatorsCI = (
altitudeConstraint: leg.Alt,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
// 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]],
];
return [interceptFix, line];
};

View File

@ -1,4 +1,4 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
@ -21,7 +21,7 @@ export const TerminatorsDF = (
altitudeConstraint: leg.Alt,
};
const crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
const crsIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix);
// Compute overfly
const [line, _, _lastCourse] = generateOverflyArc(

View File

@ -1,4 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
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 = {
...computeDestinationPoint(
...geolib.computeDestinationPoint(
arcEnd,
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *

View File

@ -1,8 +1,7 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
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 = (
@ -48,7 +47,7 @@ export const TerminatorsFC = (
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
const arcFix = geolib.computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
@ -70,7 +69,7 @@ export const TerminatorsFC = (
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
if (line.length > 1) {
lastCourse = getGreatCircleBearing(
lastCourse = geolib.getGreatCircleBearing(
{
latitude: line.at(-2)![1],
longitude: line.at(-2)![0],
@ -80,7 +79,7 @@ export const TerminatorsFC = (
}
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse),
...geolib.computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,6 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
@ -26,8 +24,8 @@ export const TerminatorsFD = (
lastCourse = _lastCourse;
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid);
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = geolib.getDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
@ -41,7 +39,7 @@ export const TerminatorsFD = (
// Compute intercept of crs from arc end and distance
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,4 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
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 = computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
const endpoint = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),

View File

@ -1,8 +1,10 @@
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
@ -18,8 +20,10 @@ export const TerminatorsRF = (
altitudeConstraint: leg.Alt,
};
const [crs] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const line = generateRFArc(
leg.Course.toTrue(targetFix),
crs,
lastCourse,
previousFix,
{ latitude: leg.CenterLat, longitude: leg.CenterLon },

View File

@ -1,7 +1,6 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
export const TerminatorsTF = (
leg: TFTerminalEntry,
@ -22,7 +21,7 @@ export const TerminatorsTF = (
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
const trackIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
const trackIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix);
if (previousFix.isFlyOver) {
let crsIntoEndpoint = trackIntoEndpoint;
@ -49,7 +48,7 @@ export const TerminatorsTF = (
lastCourse = lastCourse.normaliseDegrees();
}
const arcFix = computeDestinationPoint(
const arcFix = geolib.computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
@ -60,7 +59,7 @@ export const TerminatorsTF = (
line.push([arcFix.longitude, arcFix.latitude]);
crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);
crsIntoEndpoint = geolib.getGreatCircleBearing(arcFix, targetFix);
if (leg.TurnDir === 'R') {
condition = crsIntoEndpoint > trackIntoEndpoint;

View File

@ -1,4 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
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 = {
...computeDestinationPoint(
...geolib.computeDestinationPoint(
arcEnd,
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *

View File

@ -1,6 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
@ -23,8 +21,8 @@ export const TerminatorsVD = (
lastCourse = _lastCourse;
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid);
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = geolib.getDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
@ -38,7 +36,7 @@ export const TerminatorsVD = (
// Compute intercept of crs from arc end and distance
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,

View File

@ -1,10 +1,7 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
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 = (
@ -13,57 +10,12 @@ 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, crsToIntercept)!,
...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
@ -71,7 +23,28 @@ export const TerminatorsVI = (
altitudeConstraint: leg.Alt,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
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]],
];
return [interceptFix, line];
};

View File

@ -1,4 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import * as geolib from 'geolib';
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 = computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
const endpoint = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),

View File

@ -1,4 +1,4 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import * as geolib from 'geolib';
/**
* @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 TFTerminalEntry;
const _leg = leg as FMTerminalEntry;
return [
getGreatCircleBearing(origin, {
geolib.getGreatCircleBearing(origin, {
latitude: _leg.WptLat,
longitude: _leg.WptLon,
}),
@ -33,15 +33,10 @@ export const getCourseAndFixForIntercepts = (leg: TerminalEntry, origin: NavFix)
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
}
case 'DF': {
const _leg = leg as DFTerminalEntry;
const _leg = leg as FMTerminalEntry;
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];
}

View File

@ -1,9 +0,0 @@
export declare global {
type Chart = {
data: string;
index_number: string;
bounds: LatLngBoundsExpression;
};
type Procedure = { name: string; data: object };
}