Compare commits
4
Commits
8b04be187e
...
f593cbd29a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f593cbd29a | ||
|
|
e4ed55cac9 | ||
|
|
13bea68195 | ||
|
|
50077746f0 |
No files matched your search
@@ -22,3 +22,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.env
|
||||
Vendored
+4
-9
@@ -5,16 +5,11 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Parser Node",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"args": [
|
||||
"--experimental-strip-types",
|
||||
"${workspaceFolder}\\browser\\src\\parser\\node.ts"
|
||||
],
|
||||
"cwd": "${workspaceFolder}\\browser\\",
|
||||
"outFiles": ["${workspaceFolder}/**/*.js"]
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
public
|
||||
pnpm-lock.yaml
|
||||
node_modules
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always",
|
||||
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
|
||||
}
|
||||
+16
-10
@@ -1,18 +1,18 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { globalIgnores } from "eslint/config";
|
||||
import js from '@eslint/js';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import { globalIgnores } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config([
|
||||
globalIgnores(["dist"]),
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs["recommended-latest"],
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
@@ -20,7 +20,13 @@ export default tseslint.config([
|
||||
globals: globals.browser,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-shadow": "error",
|
||||
'@typescript-eslint/no-shadow': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
destructuredArrayIgnorePattern: '^_+$',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
+3
-1
@@ -2,8 +2,10 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<link href="/src/style.css" rel="stylesheet">
|
||||
|
||||
<title>MD-11 NavData Browser</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+12
-5
@@ -4,26 +4,30 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "vite --port 3000 --host",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"lint": "eslint . --ext .ts,.tsx --fix",
|
||||
"preview": "vite preview",
|
||||
"parser": "node --experimental-strip-types src/parser/node.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"browser-image-manipulation": "^0.4.0",
|
||||
"geojson": "^0.5.0",
|
||||
"geolib": "^3.3.4",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-svg-shape-markers": "^1.4.0",
|
||||
"magvar": "^2.0.0",
|
||||
"navigraph": "^1.4.1",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-leaflet": "^5.0.0"
|
||||
"react-leaflet": "^5.0.0",
|
||||
"tailwindcss": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/leaflet": "^1.9.20",
|
||||
"@types/node": "^24.0.13",
|
||||
"@types/object-hash": "^3.0.6",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
@@ -33,8 +37,11 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.36.0",
|
||||
"typescript-eslint": "^8.37.0",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
Generated
+783
-136
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1 @@
|
||||
[{"ID":1,"Name":"KATMAI LODGE","ICAO":"00AN","PrimaryID":null,"Latitude":59.09347222,"Longitude":-156.45583333,"Elevation":80,"TransAlt":18000},{"ID":2,"Name":"ARLAND","ICAO":"00AR","PrimaryID":null,"Latitude":38.96965,"Longitude":-97.60155556,"Elevation":1352,"TransAlt":null},{"ID":3,"Name":"WILLIAMS AG","ICAO":"00CL","PrimaryID":null,"Latitude":39.42718889,"Longitude":-121.76342778,"Elevation":87,"TransAlt":null},{"ID":4,"Name":"GOLTL","ICAO":"00IG","PrimaryID":null,"Latitude":39.72402778,"Longitude":-101.39599444,"Elevation":3359,"TransAlt":null},{"ID":5,"Name":"THOMPSON FARMS","ICAO":"00TN","PrimaryID":null,"Latitude":36.12045,"Longitude":-86.35953333,"Elevation":675,"TransAlt":null},{"ID":6,"Name":"NOWHERE","ICAO":"00TT","PrimaryID":null,"Latitude":34.51660556,"Longitude":-99.93694167,"Elevation":1752,"TransAlt":null},{"ID":7,"Name":"DELVA","ICAO":"00TX","PrimaryID":null,"Latitude":32.54445278,"Longitude":-95.67313056,"Elevation":545,"TransAlt":18000},{"ID":8,"Name":"WEESKI RANCH","ICAO":"00XA","PrimaryID":null,"Latitude":30.22403889,"Longitude":-96.01415278,"Elevation":271,"TransAlt":null},{"ID":9,"Name":"MC HONE HEIGHTS","ICAO":"01AN","PrimaryID":null,"Latitude":61.64902778,"Longitude":-149.33902778,"Elevation":610,"TransAlt":null},{"ID":10,"Name":"SWANSBORO COUNTRY","ICAO":"01CL","PrimaryID":null,"Latitude":38.79990278,"Longitude":-120.734375,"Elevation":2594,"TransAlt":18000},{"ID":11,"Name":"COLEMAN HOMESTEAD","ICAO":"01CN","PrimaryID":null,"Latitude":62.10698333,"Longitude":-150.10929722,"Elevation":282,"TransAlt":null},{"ID":12,"Name":"THE FARM","ICAO":"01GE","PrimaryID":null,"Latitude":32.67515,"Longitude":-82.77099444,"Elevation":369,"TransAlt":null},{"ID":13,"Name":"LAVA HOT SPRINGS","ICAO":"01ID","PrimaryID":null,"Latitude":42.60825,"Longitude":-112.03246111,"Elevation":5268,"TransAlt":18000},{"ID":14,"Name":"GRANVILLE","ICAO":"01NK","PrimaryID":null,"Latitude":43.42294444,"Longitude":-73.268025,"Elevation":421,"TransAlt":18000},{"ID":15,"Name":"SPRING CREEK RANCH EAST","ICAO":"01OL","PrimaryID":null,"Latitude":34.37256944,"Longitude":-96.71603611,"Elevation":1060,"TransAlt":null},{"ID":16,"Name":"BEULAH FLD","ICAO":"01TX","PrimaryID":null,"Latitude":33.17058056,"Longitude":-98.62222222,"Elevation":1120,"TransAlt":null},{"ID":17,"Name":"BASS","ICAO":"02AL","PrimaryID":null,"Latitude":30.37146389,"Longitude":-87.76444444,"Elevation":61,"TransAlt":18000},{"ID":18,"Name":"EMERALD RIDGE","ICAO":"02AN","PrimaryID":null,"Latitude":59.70422222,"Longitude":-151.29586944,"Elevation":221,"TransAlt":null},{"ID":19,"Name":"OSBORN","ICAO":"02FA","PrimaryID":null,"Latitude":28.52666667,"Longitude":-81.8748,"Elevation":121,"TransAlt":18000},{"ID":20,"Name":"TRIPLE R RANCH","ICAO":"02FD","PrimaryID":null,"Latitude":30.95234722,"Longitude":-86.63541667,"Elevation":209,"TransAlt":null},{"ID":21,"Name":"FAIRPLAINS AIRPARK","ICAO":"02MI","PrimaryID":null,"Latitude":43.15700278,"Longitude":-85.14892778,"Elevation":850,"TransAlt":null},{"ID":22,"Name":"GREGORY","ICAO":"02MN","PrimaryID":null,"Latitude":45.62973611,"Longitude":-93.17497222,"Elevation":950,"TransAlt":null},{"ID":23,"Name":"TROY","ICAO":"02MO","PrimaryID":null,"Latitude":39.04626111,"Longitude":-91.02821944,"Elevation":630,"TransAlt":18000},{"ID":24,"Name":"FFR ANIMAS LANDING STRIP","ICAO":"02NM","PrimaryID":null,"Latitude":32.09071111,"Longitude":-108.87073611,"Elevation":4240,"TransAlt":null},{"ID":25,"Name":"CAWLEYS SOUTH PRAIRIE","ICAO":"02WA","PrimaryID":null,"Latitude":47.152325,"Longitude":-122.09400278,"Elevation":690,"TransAlt":18000},{"ID":26,"Name":"BLUE WATER","ICAO":"03CO","PrimaryID":null,"Latitude":38.09791944,"Longitude":-106.08975556,"Elevation":7710,"TransAlt":null},{"ID":27,"Name":"GREENS FARM FLD","ICAO":"03FL","PrimaryID":null,"Latitude":26.70547778,"Longitude":-82.05236944,"Elevation":15,"TransAlt":null},{"ID":28,"Name":"FLATLANDER","ICAO":"03IA","PrimaryID":null,"Latitude":41.14141111,"Longitude":-95.19438056,"Elevation":1081,"TransAlt":null},{"ID":29,"Name":"DORTON","ICAO":"03MO","PrimaryID":null,"Latitude":39.28883056,"Longitude":-93.99242222,"Elevation":930,"TransAlt":null},{"ID":30,"Name":"BLUEBIRD","ICAO":"03OL","PrimaryID":null,"Latitude":35.01233333,"Longitude":-97.70273611,"Elevation":1232,"TransAlt":null},{"ID":31,"Name":"BRAZOS POLO","ICAO":"03TT","PrimaryID":null,"Latitude":29.63211667,"Longitude":-95.93248056,"Elevation":117,"TransAlt":null},{"ID":32,"Name":"AEROSTONE AIRFIELD","ICAO":"03WN","PrimaryID":null,"Latitude":45.87545556,"Longitude":-120.672325,"Elevation":2342,"TransAlt":null},{"ID":33,"Name":"NORTH 40","ICAO":"04AN","PrimaryID":null,"Latitude":61.73751111,"Longitude":-148.700725,"Elevation":943,"TransAlt":null},{"ID":34,"Name":"WHITE HILLS","ICAO":"04AZ","PrimaryID":null,"Latitude":35.72177778,"Longitude":-114.45053889,"Elevation":2470,"TransAlt":null},{"ID":35,"Name":"GRAY BUTTE","ICAO":"04CA","PrimaryID":null,"Latitude":34.56664444,"Longitude":-117.67077222,"Elevation":3039,"TransAlt":18000},{"ID":36Line truncated
|
||||
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.
@@ -0,0 +1 @@
|
||||
Copy over contents of `Data/Primary` retaining the structure.
|
||||
+61
-137
@@ -1,153 +1,77 @@
|
||||
import { MapContainer, GeoJSON, TileLayer } from "react-leaflet";
|
||||
import Parser from "./parser/parser";
|
||||
import { createRef, useEffect, useState } from "react";
|
||||
import hash from "object-hash";
|
||||
import Leaflet from "leaflet";
|
||||
import "leaflet-svg-shape-markers";
|
||||
import L from "leaflet";
|
||||
import type { DeviceFlowParams } from 'navigraph/auth';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { useState } from 'react';
|
||||
import { ProcedureSelect } from './components//ProcedureSelect';
|
||||
import { Map } from './components/Map';
|
||||
import { useNavigraphAuth } from './hooks/useNavigraphAuth';
|
||||
import Parser from './parser/parser';
|
||||
|
||||
const parser = await Parser.instance();
|
||||
|
||||
const terminals = [
|
||||
10394, 10395, 10475, 10480, 10482, 10485, 10653, 10654, 10657, 10659, 10679,
|
||||
11798, 11909, 12765,
|
||||
];
|
||||
|
||||
function App() {
|
||||
const [selectedTerminal, setSelectedTerminal] = useState(terminals[0]);
|
||||
const [procedures, setProcedures] = useState<object[]>([]);
|
||||
const [selectedAirport, setSelectedAirport] = useState<Airport>();
|
||||
const [selectedRunway, setSelectedRunway] = useState<Runway>();
|
||||
const [selectedTerminal, setSelectedTerminal] = useState<Terminal>();
|
||||
const [procedures, setProcedures] = useState<{ name: string; data: object }[]>([]);
|
||||
const [params, setParams] = useState<DeviceFlowParams | null>(null);
|
||||
|
||||
const mapRef = createRef<Leaflet.Map>();
|
||||
const layerRef = createRef<Leaflet.GeoJSON>();
|
||||
const { user, signIn, initialized } = useNavigraphAuth();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setProcedures(await parser.parse(selectedTerminal));
|
||||
})();
|
||||
}, [selectedTerminal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (layerRef.current && mapRef.current) {
|
||||
mapRef.current.flyToBounds(layerRef.current.getBounds(), {
|
||||
animate: false,
|
||||
padding: [50, 50],
|
||||
});
|
||||
}
|
||||
});
|
||||
const handleSignIn = () => signIn((p) => setParams(p));
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", height: "100vh", width: "100vw" }}>
|
||||
<MapContainer
|
||||
center={[51.505, -0.09]}
|
||||
zoom={13}
|
||||
zoomSnap={0}
|
||||
zoomDelta={0.1}
|
||||
wheelPxPerZoomLevel={1000}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
ref={mapRef}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{procedures.map((procedure) => (
|
||||
<>
|
||||
<GeoJSON
|
||||
key={hash(procedure ?? "") + "lines"}
|
||||
data={procedure}
|
||||
style={({ properties }) => ({
|
||||
color: "#ff00ff",
|
||||
stroke: true,
|
||||
weight: 5,
|
||||
opacity: 1,
|
||||
dashArray: properties.isManual ? "20, 20" : undefined,
|
||||
})}
|
||||
filter={(feature) => feature.geometry.type !== "Point"}
|
||||
ref={layerRef}
|
||||
/>
|
||||
<GeoJSON
|
||||
key={hash(procedure ?? "") + "points"}
|
||||
data={procedure}
|
||||
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 });
|
||||
{procedures.length === 0 ? (
|
||||
<div className="flex min-h-dvh w-full">
|
||||
{!initialized && <div>Loading...</div>}
|
||||
|
||||
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"}
|
||||
/>
|
||||
{initialized && !params && !user && <button onClick={handleSignIn}>Sign in</button>}
|
||||
|
||||
{params?.verification_uri_complete && !user && (
|
||||
<>
|
||||
<QRCodeSVG value={params.verification_uri_complete} size={250} />
|
||||
<a href={params.verification_uri_complete} target="_blank" rel="noreferrer">
|
||||
Open sign in page
|
||||
</a>
|
||||
</>
|
||||
))}
|
||||
</MapContainer>
|
||||
<div
|
||||
style={{
|
||||
overflowY: "scroll",
|
||||
width: "200px",
|
||||
)}
|
||||
{user && (
|
||||
<ProcedureSelect
|
||||
selectedAirport={selectedAirport}
|
||||
selectedRunway={selectedRunway}
|
||||
selectedTerminal={selectedTerminal}
|
||||
setSelectedAirport={setSelectedAirport}
|
||||
setSelectedRunway={setSelectedRunway}
|
||||
setSelectedTerminal={setSelectedTerminal}
|
||||
handleSelection={(selectedTransitions) =>
|
||||
setProcedures(
|
||||
selectedTransitions.map((transition) => ({
|
||||
name: transition,
|
||||
data: parser.parse(selectedRunway!, transition),
|
||||
}))
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-dvh w-dvw">
|
||||
{procedures.length > 0 && selectedAirport && selectedTerminal ? (
|
||||
<Map
|
||||
airport={selectedAirport}
|
||||
terminal={selectedTerminal}
|
||||
procedures={procedures}
|
||||
backAction={() => {
|
||||
setSelectedTerminal(undefined);
|
||||
setProcedures([]);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{terminals.map((terminal) => (
|
||||
<div
|
||||
key={terminal}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
background: "#eeeeee",
|
||||
border:
|
||||
selectedTerminal === terminal
|
||||
? "1px solid black"
|
||||
: "1px solid #eeeeee",
|
||||
padding: "5px",
|
||||
}}
|
||||
onClick={() => setSelectedTerminal(terminal)}
|
||||
>
|
||||
<span style={{ whiteSpace: "nowrap" }}>
|
||||
{(() => {
|
||||
const t = parser.terminals.find(({ ID }) => ID === terminal);
|
||||
return `${t?.ICAO} - ${t?.FullName}`;
|
||||
})()}
|
||||
</span>
|
||||
<pre>({terminal})</pre>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
/>
|
||||
) : (
|
||||
<h1 className="text-center text-3xl">Error</h1>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import BrowserImageManipulation from 'browser-image-manipulation';
|
||||
import { default as L, type LatLngBoundsExpression } from 'leaflet';
|
||||
import 'leaflet-svg-shape-markers';
|
||||
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;
|
||||
terminal: Terminal;
|
||||
procedures: { name: string; data: object }[];
|
||||
backAction: () => void;
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<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>
|
||||
|
||||
<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='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
|
||||
{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],
|
||||
});
|
||||
}
|
||||
imageRef.current = _imageRef;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
import { createRef, useMemo, useState, type Dispatch, type FC, type SetStateAction } from 'react';
|
||||
import Parser from '../parser/parser';
|
||||
|
||||
const parser = await Parser.instance();
|
||||
|
||||
interface ProcedureSelectProps {
|
||||
selectedAirport: Airport | undefined;
|
||||
selectedRunway: Runway | undefined;
|
||||
selectedTerminal: Terminal | undefined;
|
||||
setSelectedAirport: Dispatch<SetStateAction<Airport | undefined>>;
|
||||
setSelectedRunway: Dispatch<SetStateAction<Runway | undefined>>;
|
||||
setSelectedTerminal: Dispatch<SetStateAction<Terminal | undefined>>;
|
||||
handleSelection: (transitions: string[]) => void;
|
||||
}
|
||||
|
||||
export const ProcedureSelect: FC<ProcedureSelectProps> = ({
|
||||
selectedAirport,
|
||||
selectedRunway,
|
||||
selectedTerminal,
|
||||
setSelectedAirport,
|
||||
setSelectedRunway,
|
||||
setSelectedTerminal,
|
||||
handleSelection,
|
||||
}) => {
|
||||
const inputRef = createRef<HTMLInputElement>();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const runways = useMemo(
|
||||
() => parser.runways.filter(({ AirportID }) => AirportID === selectedAirport?.ID),
|
||||
[selectedAirport]
|
||||
);
|
||||
const terminals = useMemo(
|
||||
() =>
|
||||
parser.terminals.filter(
|
||||
({ AirportID, RwyID }) => AirportID === selectedAirport?.ID && (!RwyID || RwyID === selectedRunway?.ID)
|
||||
),
|
||||
[selectedAirport, selectedRunway]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center justify-center gap-2 p-2">
|
||||
{selectedAirport && (
|
||||
<button
|
||||
className="fixed top-2 left-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={() => {
|
||||
setError(undefined);
|
||||
|
||||
if (selectedTerminal) {
|
||||
setSelectedTerminal(undefined);
|
||||
} else if (selectedRunway) {
|
||||
setSelectedRunway(undefined);
|
||||
} else if (selectedAirport) {
|
||||
setSelectedAirport(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Go back
|
||||
</button>
|
||||
)}
|
||||
|
||||
{!selectedAirport && (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<h1 className="text-center text-3xl">Enter Airport ICAO</h1>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="rounded border border-black px-2 py-1 focus:outline-2 focus-visible:outline-2"
|
||||
onChange={(e) => {
|
||||
if (e.target.value.length <= 4) e.target.value = e.target.value.toUpperCase();
|
||||
else e.target.value = e.target.value.slice(0, 4);
|
||||
}}
|
||||
></input>
|
||||
<button
|
||||
className="w-full cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
|
||||
onClick={() => {
|
||||
const airport = parser.airports.find(({ ICAO }) => ICAO === inputRef.current?.value.toUpperCase());
|
||||
if (!airport) {
|
||||
setError('Airport not found');
|
||||
return;
|
||||
}
|
||||
setSelectedAirport(airport);
|
||||
setError(undefined);
|
||||
}}
|
||||
>
|
||||
Select Airport
|
||||
</button>
|
||||
{error && <span className="text-center text-red-700">{error}</span>}
|
||||
</div>
|
||||
)}
|
||||
{selectedAirport && !selectedRunway && (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<h1 className="text-center text-3xl">Select Runway</h1>
|
||||
{runways.map((runway) => (
|
||||
<button
|
||||
key={runway.ID}
|
||||
className="w-full cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
|
||||
onClick={() => setSelectedRunway(runway)}
|
||||
>
|
||||
Runway {runway.Ident}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{selectedAirport && selectedRunway && !selectedTerminal && (
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<h1 className="text-center text-3xl">Select Procedure</h1>
|
||||
{terminals.map((terminal) => (
|
||||
<button
|
||||
key={terminal.ID}
|
||||
className="cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
|
||||
onClick={() => {
|
||||
parser.loadTerminal(terminal.ID).then(() => {
|
||||
setSelectedTerminal(terminal);
|
||||
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
|
||||
handleSelection(Array.from(transitions));
|
||||
});
|
||||
}}
|
||||
>
|
||||
{terminal.FullName}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
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() {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [initialized, setinitialized] = useState(false);
|
||||
|
||||
// Subscribe to user on mount
|
||||
// Because this sets state in the callback it will cause any
|
||||
// component that utilizes this hook to re-render with the latest auth object.
|
||||
useEffect(() => {
|
||||
const unsubscribe = auth.onAuthStateChanged((u) => {
|
||||
if (!initialized) setinitialized(true);
|
||||
setUser(u);
|
||||
});
|
||||
// Cleanup subscription on unmount
|
||||
return () => unsubscribe();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return {
|
||||
user,
|
||||
initialized,
|
||||
signIn: auth.signInWithDeviceFlow,
|
||||
};
|
||||
}
|
||||
|
||||
// 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>;
|
||||
}
|
||||
|
||||
// Hook for child components to get the auth object
|
||||
// and re-render when it changes.
|
||||
export const useNavigraphAuth = () => {
|
||||
return useContext(authContext);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { initializeApp, Scope, type NavigraphApp } from 'navigraph/app';
|
||||
import { getAuth } from 'navigraph/auth';
|
||||
import { getChartsAPI } from 'navigraph/charts';
|
||||
|
||||
const config: NavigraphApp = {
|
||||
clientId: import.meta.env.VITE_NG_CLIENT_ID,
|
||||
clientSecret: import.meta.env.VITE_NG_CLIENT_SECRET,
|
||||
scopes: [Scope.CHARTS],
|
||||
};
|
||||
|
||||
initializeApp(config);
|
||||
|
||||
export const auth = getAuth({
|
||||
storage: {
|
||||
// Optional
|
||||
getItem: (key) => localStorage.getItem('NG' + key),
|
||||
setItem: (key, value) => localStorage.setItem('NG' + key, value),
|
||||
},
|
||||
});
|
||||
|
||||
export const charts = getChartsAPI();
|
||||
@@ -1,12 +1,14 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
|
||||
import "./index.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { NavigraphAuthProvider } from './hooks/useNavigraphAuth.tsx';
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<NavigraphAuthProvider>
|
||||
<App />
|
||||
</NavigraphAuthProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
@@ -1,17 +0,0 @@
|
||||
import Parser from "./parser.ts";
|
||||
|
||||
// mutate fetch to be local
|
||||
|
||||
// @ts-expect-error Global override
|
||||
// eslint-disable-next-line no-global-assign
|
||||
fetch = async (path: string) => {
|
||||
const fs = await import("fs");
|
||||
return {
|
||||
json: () =>
|
||||
JSON.parse(fs.readFileSync(`public/${path}`) as unknown as string),
|
||||
};
|
||||
};
|
||||
|
||||
const parser = await Parser.instance();
|
||||
|
||||
console.log(JSON.stringify(await parser.parse(12765)));
|
||||
+90
-174
@@ -1,25 +1,25 @@
|
||||
import "./utils/extensions.ts";
|
||||
import * as geolib from "geolib";
|
||||
import geojson from "geojson";
|
||||
import { TerminatorsCF } from "./terminators/CF.ts";
|
||||
import { TerminatorsAF } from "./terminators/AF.ts";
|
||||
import { TerminatorsCR } from "./terminators/CR.ts";
|
||||
import { TerminatorsVM } from "./terminators/VM.ts";
|
||||
import { TerminatorsFM } from "./terminators/FM.ts";
|
||||
import { TerminatorsCI } from "./terminators/CI.ts";
|
||||
import { TerminatorsVA } from "./terminators/VA.ts";
|
||||
import { TerminatorsTF } from "./terminators/TF.ts";
|
||||
import { TerminatorsVI } from "./terminators/VI.ts";
|
||||
import { TerminatorsVD } from "./terminators/VD.ts";
|
||||
import { TerminatorsRF } from "./terminators/RF.ts";
|
||||
import { TerminatorsCA } from "./terminators/CA.ts";
|
||||
import { TerminatorsDF } from "./terminators/DF.ts";
|
||||
import { TerminatorsFD } from "./terminators/FD.ts";
|
||||
import { TerminatorsFA } from "./terminators/FA.ts";
|
||||
import { TerminatorsCD } from "./terminators/CD.ts";
|
||||
import { TerminatorsVR } from "./terminators/VR.ts";
|
||||
import { TerminatorsIF } from "./terminators/IF.ts";
|
||||
import { TerminatorsFC } from "./terminators/FC.ts";
|
||||
import geojson from 'geojson';
|
||||
import * as geolib from 'geolib';
|
||||
import { TerminatorsAF } from './terminators/AF';
|
||||
import { TerminatorsCA } from './terminators/CA';
|
||||
import { TerminatorsCD } from './terminators/CD';
|
||||
import { TerminatorsCF } from './terminators/CF';
|
||||
import { TerminatorsCI } from './terminators/CI';
|
||||
import { TerminatorsCR } from './terminators/CR';
|
||||
import { TerminatorsDF } from './terminators/DF';
|
||||
import { TerminatorsFA } from './terminators/FA';
|
||||
import { TerminatorsFC } from './terminators/FC';
|
||||
import { TerminatorsFD } from './terminators/FD';
|
||||
import { TerminatorsFM } from './terminators/FM';
|
||||
import { TerminatorsIF } from './terminators/IF';
|
||||
import { TerminatorsRF } from './terminators/RF';
|
||||
import { TerminatorsTF } from './terminators/TF';
|
||||
import { TerminatorsVA } from './terminators/VA';
|
||||
import { TerminatorsVD } from './terminators/VD';
|
||||
import { TerminatorsVI } from './terminators/VI';
|
||||
import { TerminatorsVM } from './terminators/VM';
|
||||
import { TerminatorsVR } from './terminators/VR';
|
||||
import './utils/extensions';
|
||||
|
||||
/*
|
||||
Runway IDs for LIED
|
||||
@@ -30,19 +30,19 @@ Runway IDs for LIED
|
||||
class Parser {
|
||||
private static _instance: Parser;
|
||||
|
||||
private _airports: Airport[];
|
||||
private _waypoints: Waypoint[];
|
||||
private _runways: Runway[];
|
||||
private _terminals: Terminal[];
|
||||
|
||||
private _procedures: TerminalEntry[] = [];
|
||||
|
||||
public static AC_SPEED = 250;
|
||||
public static AC_BANK = 30;
|
||||
public static AC_VS = 1400;
|
||||
|
||||
private constructor(
|
||||
waypoints: Waypoint[],
|
||||
runways: Runway[],
|
||||
terminals: Terminal[]
|
||||
) {
|
||||
private constructor(airports: Airport[], waypoints: Waypoint[], runways: Runway[], terminals: Terminal[]) {
|
||||
this._airports = airports;
|
||||
this._waypoints = waypoints;
|
||||
this._runways = runways;
|
||||
this._terminals = terminals;
|
||||
@@ -50,16 +50,20 @@ class Parser {
|
||||
|
||||
public static instance = async () => {
|
||||
if (!Parser._instance) {
|
||||
const waypoints = await (await fetch("NavData/Waypoints.json")).json();
|
||||
const runways = await (await fetch("NavData/Runways.json")).json();
|
||||
const terminals = await (await fetch("NavData/Terminals.json")).json();
|
||||
const airports = await (await fetch('NavData/Airports.json')).json();
|
||||
const waypoints = await (await fetch('NavData/Waypoints.json')).json();
|
||||
const runways = await (await fetch('NavData/Runways.json')).json();
|
||||
const terminals = await (await fetch('NavData/Terminals.json')).json();
|
||||
|
||||
Parser._instance = new Parser(waypoints, runways, terminals);
|
||||
Parser._instance = new Parser(airports, waypoints, runways, terminals);
|
||||
}
|
||||
|
||||
return Parser._instance;
|
||||
};
|
||||
|
||||
public get airports() {
|
||||
return this._airports;
|
||||
}
|
||||
public get terminals() {
|
||||
return this._terminals;
|
||||
}
|
||||
@@ -70,32 +74,17 @@ class Parser {
|
||||
return this._runways;
|
||||
}
|
||||
|
||||
public parse = async (terminalID: number) => {
|
||||
// Get Procedure main
|
||||
const terminal = this.terminals.find(({ ID }) => ID === terminalID);
|
||||
if (!terminal) throw new Error("Procedure does not exists");
|
||||
// Get runway this procedure is for
|
||||
let runway = this.runways.find(({ ID }) => ID === terminal.RwyID);
|
||||
if (!runway) {
|
||||
let id = 26156;
|
||||
if (typeof prompt !== "undefined")
|
||||
// throw new Error("Prompt not defined, cannot continue");
|
||||
|
||||
id = Number.parseInt(prompt("Runway ID") ?? "");
|
||||
runway = this.runways.find(({ ID }) => ID === id);
|
||||
if (!runway) throw new Error("Procedure links to non existent Runway");
|
||||
public get procedures() {
|
||||
return this._procedures;
|
||||
}
|
||||
// Load procedure
|
||||
const procedures = (await (
|
||||
await fetch(`NavData/TermID_${terminalID}.json`)
|
||||
|
||||
public loadTerminal = async (terminalID: number) => {
|
||||
this._procedures = (await (
|
||||
await fetch(`NavData/ProcedureLegs/TermID_${terminalID}.json`)
|
||||
).json()) as TerminalEntry[];
|
||||
// Split into transitions
|
||||
const transitions = new Set(procedures.map((proc) => proc.Transition));
|
||||
};
|
||||
|
||||
const output: object[] = [];
|
||||
|
||||
// Main
|
||||
transitions.forEach((transition) => {
|
||||
public parse = (runway: Runway, transition: string) => {
|
||||
// Private functions
|
||||
/**
|
||||
* @param line Line segments
|
||||
@@ -118,11 +107,7 @@ class Parser {
|
||||
* @param line New line
|
||||
* @param options Options for line rendering
|
||||
*/
|
||||
const update = (
|
||||
fix?: NavFix,
|
||||
line?: LineSegment[],
|
||||
options?: Record<string, unknown>
|
||||
) => {
|
||||
const update = (fix?: NavFix, line?: LineSegment[], options?: Record<string, unknown>) => {
|
||||
if (fix) navFixes.push(fix);
|
||||
if (line) {
|
||||
lineSegments.push({ line, ...options });
|
||||
@@ -143,54 +128,36 @@ class Parser {
|
||||
name: runway.Ident,
|
||||
});
|
||||
let lastCourse = runway.TrueHeading;
|
||||
const procedure = this._procedures.filter(({ Transition }) => !Transition || Transition === transition);
|
||||
|
||||
const procedure = procedures.filter(
|
||||
(proc) => proc.Transition === transition
|
||||
);
|
||||
// Main
|
||||
for (let index = 0; index < procedure.length; index++) {
|
||||
const leg = procedure[index];
|
||||
const previousFix = navFixes.at(-1)!;
|
||||
const waypoint = this.waypoints.filter(({ ID }) => ID === leg.WptID)[0];
|
||||
|
||||
switch (leg.TrackCode) {
|
||||
case "AF": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsAF(
|
||||
leg as AFTerminalEntry,
|
||||
previousFix,
|
||||
waypoint
|
||||
);
|
||||
case 'AF': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsAF(leg as AFTerminalEntry, previousFix, waypoint);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "CA": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCA(
|
||||
leg as CATerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'CA': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCA(leg as CATerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "CD": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCD(
|
||||
leg as CDTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'CD': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCD(leg as CDTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "CF": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCF(
|
||||
leg as CFTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse,
|
||||
waypoint
|
||||
);
|
||||
case 'CF': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCF(leg as CFTerminalEntry, previousFix, lastCourse, waypoint);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "CI": {
|
||||
case 'CI': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCI(
|
||||
leg as CITerminalEntry,
|
||||
procedure[index + 1],
|
||||
@@ -200,76 +167,51 @@ class Parser {
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "CR": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCR(
|
||||
leg as CRTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'CR': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsCR(leg as CRTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "DF": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsDF(
|
||||
leg as DFTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse,
|
||||
waypoint
|
||||
);
|
||||
case 'DF': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsDF(leg as DFTerminalEntry, previousFix, lastCourse, waypoint);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "FA": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFA(
|
||||
leg as FATerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'FA': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFA(leg as FATerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "FC": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFC(
|
||||
leg as FCTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'FC': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFC(leg as FCTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "FD": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFD(
|
||||
leg as FDTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'FD': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFD(leg as FDTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "FM": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFM(
|
||||
leg as FMTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'FM': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsFM(leg as FMTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd, { isManual: true });
|
||||
break;
|
||||
}
|
||||
case "HA":
|
||||
case "HF":
|
||||
case "HM":
|
||||
console.error("Unknown TrackCode", leg.TrackCode);
|
||||
case 'HA':
|
||||
case 'HF':
|
||||
case 'HM':
|
||||
console.error('Unknown TrackCode', leg.TrackCode);
|
||||
break;
|
||||
case "IF": {
|
||||
case 'IF': {
|
||||
const fixToAdd = TerminatorsIF(leg as RFTerminalEntry, waypoint);
|
||||
navFixes.length = 0;
|
||||
navFixes.push(fixToAdd);
|
||||
break;
|
||||
}
|
||||
case "PI":
|
||||
console.error("Unknown TrackCode", leg.TrackCode);
|
||||
case 'PI':
|
||||
console.error('Unknown TrackCode', leg.TrackCode);
|
||||
break;
|
||||
case "RF": {
|
||||
case 'RF': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsRF(
|
||||
leg as RFTerminalEntry,
|
||||
procedure[index + 1],
|
||||
@@ -280,35 +222,22 @@ class Parser {
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "TF": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsTF(
|
||||
leg as TFTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse,
|
||||
waypoint
|
||||
);
|
||||
case 'TF': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsTF(leg as TFTerminalEntry, previousFix, lastCourse, waypoint);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "VA": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVA(
|
||||
leg as VATerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'VA': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVA(leg as VATerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "VD": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVD(
|
||||
leg as VDTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'VD': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVD(leg as VDTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "VI": {
|
||||
case 'VI': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVI(
|
||||
leg as VITerminalEntry,
|
||||
procedure[index + 1],
|
||||
@@ -318,39 +247,26 @@ class Parser {
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
case "VM": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVM(
|
||||
leg as VMTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'VM': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVM(leg as VMTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd, { isManual: true });
|
||||
break;
|
||||
}
|
||||
case "VR": {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVR(
|
||||
leg as VRTerminalEntry,
|
||||
previousFix,
|
||||
lastCourse
|
||||
);
|
||||
case 'VR': {
|
||||
const [fixToAdd, lineToAdd] = TerminatorsVR(leg as VRTerminalEntry, previousFix, lastCourse);
|
||||
update(fixToAdd, lineToAdd);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error("Unknown TrackCode", leg.TrackCode);
|
||||
console.error('Unknown TrackCode', leg.TrackCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
output.push(
|
||||
geojson.parse([...navFixes, ...lineSegments], {
|
||||
LineString: "line",
|
||||
Point: ["latitude", "longitude"],
|
||||
})
|
||||
);
|
||||
return geojson.parse([...navFixes, ...lineSegments], {
|
||||
LineString: 'line',
|
||||
Point: ['latitude', 'longitude'],
|
||||
});
|
||||
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as geolib from "geolib";
|
||||
import * as geolib from 'geolib';
|
||||
|
||||
/**
|
||||
* @param crsIntoEndpoint Course into arc endpoint
|
||||
@@ -21,14 +21,14 @@ export const generateAFArc = (
|
||||
|
||||
if (crsIntoEndpoint !== crsFromOrigin) {
|
||||
// Turn Dir
|
||||
if (!turnDir || turnDir === "E") {
|
||||
if (!turnDir || turnDir === 'E') {
|
||||
let prov = crsFromOrigin - crsIntoEndpoint;
|
||||
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
|
||||
turnDir = prov > 0 ? "L" : "R";
|
||||
turnDir = prov > 0 ? 'L' : 'R';
|
||||
}
|
||||
|
||||
while (crsFromOrigin !== crsIntoEndpoint) {
|
||||
if (turnDir === "R") {
|
||||
if (turnDir === 'R') {
|
||||
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
|
||||
crsFromOrigin += delta < 1 ? delta : 1;
|
||||
crsFromOrigin = crsFromOrigin.normaliseDegrees();
|
||||
@@ -39,11 +39,7 @@ export const generateAFArc = (
|
||||
}
|
||||
if (crsFromOrigin === crsIntoEndpoint) break;
|
||||
|
||||
const arcFix = geolib.computeDestinationPoint(
|
||||
center,
|
||||
radius.toMetre(),
|
||||
crsFromOrigin
|
||||
);
|
||||
const arcFix = geolib.computeDestinationPoint(center, radius.toMetre(), crsFromOrigin);
|
||||
|
||||
line.push([arcFix.longitude, arcFix.latitude]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as geolib from 'geolib';
|
||||
import { generatePerformanceArc } from './generatePerformanceArc';
|
||||
|
||||
/**
|
||||
* @param crsIntoEndpoint Course into arc endpoint
|
||||
* @param crsFromOrigin Course from arc origin point
|
||||
* @param start Arc origin point
|
||||
* @param speed Speed within arc
|
||||
* @param turnDir Turn direction
|
||||
* @returns Line segments, arc endpoint, course into arc endpoint
|
||||
*/
|
||||
export const generateOverflyArc = (
|
||||
crsIntoEndpoint: number,
|
||||
crsFromOrigin: number,
|
||||
start: NavFix,
|
||||
speed: number,
|
||||
turnDir?: TurnDirection,
|
||||
force360?: boolean
|
||||
): [LineSegment[], NavFix, number] => {
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
// Compute overfly arc
|
||||
if (start.isFlyOver) {
|
||||
line = generatePerformanceArc(crsIntoEndpoint, crsFromOrigin, start, speed, turnDir, force360);
|
||||
}
|
||||
// Compute procedural arc
|
||||
else {
|
||||
line.push([start.longitude, start.latitude]);
|
||||
}
|
||||
|
||||
// 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(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
|
||||
return [line, arcEnd, crsFromOrigin];
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import Parser from "../parser.ts";
|
||||
import { computeTurnRate } from "../utils/computeTurnRate.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import Parser from '../parser';
|
||||
import { computeTurnRate } from '../utils/computeTurnRate';
|
||||
|
||||
/**
|
||||
* @param crsIntoEndpoint Course into arc endpoint
|
||||
@@ -27,16 +27,16 @@ export const generatePerformanceArc = (
|
||||
// Check if there even is an arc
|
||||
if (force360 || !crsFromOrigin.equal(crsIntoEndpoint)) {
|
||||
// Turn Dir
|
||||
if (!turnDir || turnDir === "E") {
|
||||
if (!turnDir || turnDir === 'E') {
|
||||
let prov = crsFromOrigin - crsIntoEndpoint;
|
||||
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
|
||||
turnDir = prov > 0 ? "L" : "R";
|
||||
turnDir = prov > 0 ? 'L' : 'R';
|
||||
}
|
||||
|
||||
// Generate arc
|
||||
while (!crsFromOrigin.equal(crsIntoEndpoint)) {
|
||||
let time = 0;
|
||||
if (turnDir === "R") {
|
||||
if (turnDir === 'R') {
|
||||
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
|
||||
const increment = delta < 1 ? delta : 1;
|
||||
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
|
||||
@@ -68,7 +68,7 @@ export const generatePerformanceArc = (
|
||||
|
||||
while (!crsFromOrigin.equal(crsIntoEndpoint)) {
|
||||
let time = 0;
|
||||
if (turnDir === "R") {
|
||||
if (turnDir === 'R') {
|
||||
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
|
||||
const increment = delta < 1 ? delta : 1;
|
||||
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as geolib from "geolib";
|
||||
import * as geolib from 'geolib';
|
||||
|
||||
/**
|
||||
* @param crsIntoEndpoint Course into arc endpoint
|
||||
@@ -19,15 +19,15 @@ export const generateRFArc = (
|
||||
|
||||
if (crsIntoEndpoint !== crsIntoOrigin) {
|
||||
// Turn Dir
|
||||
if (!turnDir || turnDir === "E") {
|
||||
if (!turnDir || turnDir === 'E') {
|
||||
let prov = crsIntoOrigin - crsIntoEndpoint;
|
||||
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
|
||||
turnDir = prov > 0 ? "L" : "R";
|
||||
turnDir = prov > 0 ? 'L' : 'R';
|
||||
}
|
||||
|
||||
let crsOrthogonalOnOrigin;
|
||||
let crsOrthogonalOnEndpoint;
|
||||
if (turnDir === "R") {
|
||||
if (turnDir === 'R') {
|
||||
crsOrthogonalOnOrigin = (crsIntoOrigin + 90).normaliseDegrees();
|
||||
crsOrthogonalOnEndpoint = (crsIntoEndpoint + 90).normaliseDegrees();
|
||||
} else {
|
||||
@@ -40,34 +40,24 @@ export const generateRFArc = (
|
||||
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
|
||||
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
|
||||
// Start turn immediately
|
||||
if (turnDir === "R") {
|
||||
crsOrthogonalOnOrigin +=
|
||||
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
if (turnDir === 'R') {
|
||||
crsOrthogonalOnOrigin += crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
} else {
|
||||
crsOrthogonalOnOrigin -=
|
||||
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
}
|
||||
|
||||
while (crsOrthogonalOnOrigin !== crsOrthogonalOnEndpoint) {
|
||||
if (turnDir === "R") {
|
||||
const delta = (
|
||||
crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin
|
||||
).normaliseDegrees();
|
||||
if (turnDir === 'R') {
|
||||
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
|
||||
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
|
||||
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
|
||||
} else {
|
||||
const delta = (
|
||||
crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint
|
||||
).normaliseDegrees();
|
||||
const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
|
||||
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1;
|
||||
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
|
||||
}
|
||||
|
||||
const arcFix = geolib.computeDestinationPoint(
|
||||
center,
|
||||
arcRad,
|
||||
crsOrthogonalOnOrigin
|
||||
);
|
||||
const arcFix = geolib.computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin);
|
||||
|
||||
line.push([arcFix.longitude, arcFix.latitude]);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeIntersection } from "../utils/computeIntersection.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { computeIntersection } from '../utils/computeIntersection';
|
||||
|
||||
/**
|
||||
* @param crsIntoEndpoint Course into arc endpoint
|
||||
@@ -22,12 +22,12 @@ export const generateTangentArc = (
|
||||
if (!crsFromOrigin.equal(crsIntoEndpoint)) {
|
||||
// Course to the end of the arc
|
||||
let crsFromStartToEnd;
|
||||
if (!turnDir || turnDir === "E") {
|
||||
if (!turnDir || turnDir === 'E') {
|
||||
let prov = crsFromOrigin - crsIntoEndpoint;
|
||||
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
|
||||
turnDir = prov > 0 ? "L" : "R";
|
||||
turnDir = prov > 0 ? 'L' : 'R';
|
||||
}
|
||||
if (turnDir === "R") {
|
||||
if (turnDir === 'R') {
|
||||
const delta = (360 - crsFromOrigin + crsIntoEndpoint).normaliseDegrees();
|
||||
crsFromStartToEnd = (crsFromOrigin + delta / 2).normaliseDegrees();
|
||||
} else {
|
||||
@@ -46,7 +46,7 @@ export const generateTangentArc = (
|
||||
|
||||
let crsOrthogonalOnOrigin;
|
||||
let crsOrthogonalOnEndpoint;
|
||||
if (turnDir === "R") {
|
||||
if (turnDir === 'R') {
|
||||
crsOrthogonalOnOrigin = (crsFromOrigin + 90).normaliseDegrees();
|
||||
crsOrthogonalOnEndpoint = (crsIntoEndpoint + 90).normaliseDegrees();
|
||||
} else {
|
||||
@@ -67,34 +67,24 @@ export const generateTangentArc = (
|
||||
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
|
||||
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
|
||||
// Start turn immediately
|
||||
if (turnDir === "R") {
|
||||
crsOrthogonalOnOrigin +=
|
||||
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
if (turnDir === 'R') {
|
||||
crsOrthogonalOnOrigin += crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
} else {
|
||||
crsOrthogonalOnOrigin -=
|
||||
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
|
||||
}
|
||||
|
||||
while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
|
||||
if (turnDir === "R") {
|
||||
const delta = (
|
||||
crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin
|
||||
).normaliseDegrees();
|
||||
if (turnDir === 'R') {
|
||||
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
|
||||
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
|
||||
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
|
||||
} else {
|
||||
const delta = (
|
||||
crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint
|
||||
).normaliseDegrees();
|
||||
const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
|
||||
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1;
|
||||
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
|
||||
}
|
||||
|
||||
const arcFix = geolib.computeDestinationPoint(
|
||||
arcCenter,
|
||||
arcRad,
|
||||
crsOrthogonalOnOrigin
|
||||
);
|
||||
const arcFix = geolib.computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin);
|
||||
|
||||
line.push([arcFix.longitude, arcFix.latitude]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { generateTangentArc } from "./generateTangentArc.ts";
|
||||
import { generatePerformanceArc } from "./generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { generatePerformanceArc } from './generatePerformanceArc';
|
||||
import { generateTangentArc } from './generateTangentArc';
|
||||
|
||||
/**
|
||||
* @param crsIntoEndpoint Course into endpoint
|
||||
@@ -25,21 +25,9 @@ export const handleTurnAtFix = (
|
||||
|
||||
// Overfly turn
|
||||
if (start.isFlyOver) {
|
||||
const arc1 = generateTangentArc(
|
||||
crsIntoEndpoint,
|
||||
crsFromOrigin,
|
||||
start,
|
||||
end,
|
||||
turnDir
|
||||
);
|
||||
const arc1 = generateTangentArc(crsIntoEndpoint, crsFromOrigin, start, end, turnDir);
|
||||
|
||||
const arc2 = generatePerformanceArc(
|
||||
crsIntoIntercept,
|
||||
crsFromOrigin,
|
||||
start,
|
||||
speed,
|
||||
turnDir
|
||||
);
|
||||
const arc2 = generatePerformanceArc(crsIntoIntercept, crsFromOrigin, start, speed, turnDir);
|
||||
|
||||
// Decide on arc
|
||||
let arc;
|
||||
@@ -52,8 +40,7 @@ export const handleTurnAtFix = (
|
||||
end
|
||||
);
|
||||
|
||||
if (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)
|
||||
arc = arc1;
|
||||
if (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1) arc = arc1;
|
||||
else arc = arc2;
|
||||
} else {
|
||||
arc = arc2;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { generateAFArc } from "../pathGenerators/generateAFArc.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { generateAFArc } from '../pathGenerators/generateAFArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsAF = (
|
||||
leg: AFTerminalEntry,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as geolib from "geolib";
|
||||
import Parser from "../parser.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import Parser from '../parser';
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsCA = (
|
||||
leg: CATerminalEntry,
|
||||
@@ -11,37 +11,16 @@ export const TerminatorsCA = (
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
line = generatePerformanceArc(
|
||||
crsIntoEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute intercept of crs from arc end and expected altitude
|
||||
const targetFix: NavFix = {
|
||||
...geolib.computeDestinationPoint(
|
||||
arcEnd,
|
||||
(
|
||||
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
|
||||
Parser.AC_VS) *
|
||||
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *
|
||||
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
|
||||
).toMetre(),
|
||||
crsIntoEndpoint
|
||||
@@ -53,7 +32,6 @@ export const TerminatorsCA = (
|
||||
speedConstraint: leg.SpeedLimit,
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
line.push([targetFix.longitude, targetFix.latitude]);
|
||||
|
||||
return [targetFix, line];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
// NOTE: Distance not adjusted for altitude in this demo
|
||||
export const TerminatorsCD = (
|
||||
@@ -12,35 +12,14 @@ export const TerminatorsCD = (
|
||||
latitude: leg.NavLat,
|
||||
longitude: leg.NavLon,
|
||||
};
|
||||
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
|
||||
|
||||
line = generatePerformanceArc(
|
||||
crsIntoEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute distance to fly from arc end
|
||||
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
|
||||
const distToNavaid = geolib.getDistance(arcEnd, navaid);
|
||||
let remainingDistance = leg.Distance.toMetre();
|
||||
@@ -51,9 +30,10 @@ export const TerminatorsCD = (
|
||||
// Navaid in front of us
|
||||
else {
|
||||
// Navaid will not be passed before distance is hit
|
||||
if (distToNavaid > remainingDistance)
|
||||
remainingDistance = distToNavaid - remainingDistance;
|
||||
if (distToNavaid > remainingDistance) remainingDistance = distToNavaid - remainingDistance;
|
||||
}
|
||||
|
||||
// Compute intercept of crs from arc end and distance
|
||||
const targetFix: NavFix = {
|
||||
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
|
||||
name: leg.Distance.toString(),
|
||||
@@ -63,7 +43,6 @@ export const TerminatorsCD = (
|
||||
speedConstraint: leg.SpeedLimit,
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
line.push([targetFix.longitude, targetFix.latitude]);
|
||||
|
||||
return [targetFix, line];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsCF = (
|
||||
leg: CFTerminalEntry,
|
||||
@@ -20,6 +20,7 @@ export const TerminatorsCF = (
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
// Compute arc
|
||||
const line = handleTurnAtFix(
|
||||
leg.Course.toTrue(previousFix),
|
||||
leg.Course.toTrue(previousFix),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
|
||||
import { computeIntersection } from "../utils/computeIntersection.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
|
||||
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
|
||||
import { computeIntersection } from '../utils/computeIntersection';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
|
||||
|
||||
export const TerminatorsCI = (
|
||||
leg: CITerminalEntry,
|
||||
@@ -12,14 +12,9 @@ export const TerminatorsCI = (
|
||||
const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
|
||||
// Compute INTC
|
||||
// Compute intercept fix
|
||||
const interceptFix: NavFix = {
|
||||
...computeIntersection(
|
||||
previousFix,
|
||||
leg.Course.toTrue(nextFix),
|
||||
nextFix,
|
||||
crs
|
||||
)!,
|
||||
...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!,
|
||||
isFlyOver: leg.IsFlyOver,
|
||||
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
|
||||
speed: speed,
|
||||
@@ -27,6 +22,7 @@ export const TerminatorsCI = (
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
// Compute arc
|
||||
const line = handleTurnAtFix(
|
||||
crs,
|
||||
leg.Course.toTrue(nextFix),
|
||||
@@ -37,7 +33,7 @@ export const TerminatorsCI = (
|
||||
leg.TurnDir
|
||||
);
|
||||
|
||||
// Intercept based on previous intercept
|
||||
// Recompute intercept
|
||||
const interceptPoint2 = computeIntersection(
|
||||
{ latitude: line.at(-2)![1], longitude: line.at(-2)![0] },
|
||||
leg.Course.toTrue(nextFix),
|
||||
@@ -47,10 +43,7 @@ export const TerminatorsCI = (
|
||||
if (interceptPoint2)
|
||||
return [
|
||||
{ ...interceptFix, ...interceptPoint2 },
|
||||
[
|
||||
...line.slice(0, -1),
|
||||
[interceptPoint2.longitude, interceptPoint2.latitude],
|
||||
],
|
||||
[...line.slice(0, -1), [interceptPoint2.longitude, interceptPoint2.latitude]],
|
||||
];
|
||||
|
||||
return [interceptFix, line];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeIntersection } from "../utils/computeIntersection.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeIntersection } from '../utils/computeIntersection';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsCR = (
|
||||
leg: CRTerminalEntry,
|
||||
@@ -16,31 +15,11 @@ export const TerminatorsCR = (
|
||||
const crsIntoEndpoint = leg.NavBear.toTrue(navaid);
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
line = generatePerformanceArc(
|
||||
crsFromEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsFromEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute intercept of crs from arc end and radial
|
||||
const interceptFix: NavFix = {
|
||||
...computeIntersection(arcEnd, crsFromEndpoint, navaid, crsIntoEndpoint)!,
|
||||
isFlyOver: leg.IsFlyOver,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsDF = (
|
||||
leg: DFTerminalEntry,
|
||||
@@ -21,26 +21,18 @@ export const TerminatorsDF = (
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
const crsIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix);
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
const crsIntoEndpoint = geolib.getGreatCircleBearing(
|
||||
previousFix,
|
||||
targetFix
|
||||
);
|
||||
|
||||
line = generatePerformanceArc(
|
||||
// Compute overfly
|
||||
const [line, _, _lastCourse] = generateOverflyArc(
|
||||
crsIntoEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir,
|
||||
previousFix.latitude.equal(targetFix.latitude) &&
|
||||
previousFix.longitude.equal(targetFix.longitude)
|
||||
previousFix.latitude.equal(targetFix.latitude) && previousFix.longitude.equal(targetFix.longitude)
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
line.push([targetFix.longitude, targetFix.latitude]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import Parser from "../parser.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import Parser from '../parser';
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsFA = (
|
||||
leg: FATerminalEntry,
|
||||
@@ -15,37 +15,16 @@ export const TerminatorsFA = (
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
const crsIntoEndpoint = leg.Course.toTrue(refFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
line = generatePerformanceArc(
|
||||
crsIntoEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute intercept of crs from arc end and expected altitude
|
||||
const targetFix: NavFix = {
|
||||
...geolib.computeDestinationPoint(
|
||||
arcEnd,
|
||||
(
|
||||
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
|
||||
Parser.AC_VS) *
|
||||
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *
|
||||
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
|
||||
).toMetre(),
|
||||
crsIntoEndpoint
|
||||
@@ -57,7 +36,6 @@ export const TerminatorsFA = (
|
||||
speedConstraint: leg.SpeedLimit,
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
line.push([targetFix.longitude, targetFix.latitude]);
|
||||
|
||||
return [targetFix, line];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import Parser from "../parser.ts";
|
||||
import { computeTurnRate } from "../utils/computeTurnRate.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import Parser from '../parser';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
import { computeTurnRate } from '../utils/computeTurnRate';
|
||||
|
||||
// NOTE: Distance not adjusted for altitude in this demo
|
||||
export const TerminatorsFC = (
|
||||
@@ -20,22 +20,22 @@ export const TerminatorsFC = (
|
||||
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
let crsIntoEndpoint = trackIntoEndpoint;
|
||||
const crsIntoEndpoint = trackIntoEndpoint;
|
||||
|
||||
// Check if there even is an arc
|
||||
if (!crsIntoEndpoint.equal(lastCourse)) {
|
||||
// Turn Dir
|
||||
if (!leg.TurnDir || leg.TurnDir === "E") {
|
||||
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";
|
||||
leg.TurnDir = prov > 0 ? 'L' : 'R';
|
||||
}
|
||||
|
||||
// Generate arc
|
||||
let condition = false;
|
||||
do {
|
||||
let time = 0;
|
||||
if (leg.TurnDir === "R") {
|
||||
if (leg.TurnDir === 'R') {
|
||||
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
|
||||
const increment = delta < 1 ? delta : 1;
|
||||
lastCourse = (lastCourse + increment).normaliseDegrees();
|
||||
@@ -58,7 +58,7 @@ export const TerminatorsFC = (
|
||||
|
||||
line.push([arcFix.longitude, arcFix.latitude]);
|
||||
|
||||
if (leg.TurnDir === "R") {
|
||||
if (leg.TurnDir === 'R') {
|
||||
condition = lastCourse > trackIntoEndpoint;
|
||||
} else {
|
||||
condition = lastCourse < trackIntoEndpoint;
|
||||
@@ -79,11 +79,7 @@ export const TerminatorsFC = (
|
||||
}
|
||||
|
||||
const targetFix: NavFix = {
|
||||
...geolib.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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
// NOTE: Distance not adjusted for altitude in this demo
|
||||
export const TerminatorsFD = (
|
||||
@@ -16,35 +16,14 @@ export const TerminatorsFD = (
|
||||
latitude: leg.NavLat,
|
||||
longitude: leg.NavLon,
|
||||
};
|
||||
const crsIntoEndpoint = leg.Course.toTrue(refFix);
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
const crsIntoEndpoint = leg.Course.toTrue(refFix);
|
||||
|
||||
line = generatePerformanceArc(
|
||||
crsIntoEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute distance to fly from arc end
|
||||
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
|
||||
const distToNavaid = geolib.getDistance(arcEnd, navaid);
|
||||
let remainingDistance = leg.Distance.toMetre();
|
||||
@@ -55,9 +34,10 @@ export const TerminatorsFD = (
|
||||
// Navaid in front of us
|
||||
else {
|
||||
// Navaid will not be passed before distance is hit
|
||||
if (distToNavaid > remainingDistance)
|
||||
remainingDistance = distToNavaid - remainingDistance;
|
||||
if (distToNavaid > remainingDistance) remainingDistance = distToNavaid - remainingDistance;
|
||||
}
|
||||
|
||||
// Compute intercept of crs from arc end and distance
|
||||
const targetFix: NavFix = {
|
||||
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
|
||||
name: leg.Distance.toString(),
|
||||
@@ -67,7 +47,6 @@ export const TerminatorsFD = (
|
||||
speedConstraint: leg.SpeedLimit,
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
line.push([targetFix.longitude, targetFix.latitude]);
|
||||
|
||||
return [targetFix, line];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsFM = (
|
||||
leg: FMTerminalEntry,
|
||||
@@ -9,11 +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 = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
|
||||
|
||||
const line = handleTurnAtFix(
|
||||
leg.Course.toTrue(previousFix),
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import Parser from "../parser.ts";
|
||||
import Parser from '../parser';
|
||||
|
||||
export const TerminatorsIF = (
|
||||
leg: IFTerminalEntry,
|
||||
waypoint?: Waypoint
|
||||
): NavFix => {
|
||||
export const TerminatorsIF = (leg: IFTerminalEntry, waypoint?: Waypoint): NavFix => {
|
||||
const targetFix: NavFix = {
|
||||
latitude: leg.WptLat,
|
||||
longitude: leg.WptLon,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
|
||||
import { generateRFArc } from "../pathGenerators/generateRFArc.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generateRFArc } from '../pathGenerators/generateRFArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
|
||||
|
||||
export const TerminatorsRF = (
|
||||
leg: RFTerminalEntry,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import Parser from "../parser.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import Parser from '../parser';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
export const TerminatorsTF = (
|
||||
leg: TFTerminalEntry,
|
||||
@@ -21,10 +21,7 @@ export const TerminatorsTF = (
|
||||
|
||||
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
|
||||
|
||||
const trackIntoEndpoint = geolib.getGreatCircleBearing(
|
||||
previousFix,
|
||||
targetFix
|
||||
);
|
||||
const trackIntoEndpoint = geolib.getGreatCircleBearing(previousFix, targetFix);
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
let crsIntoEndpoint = trackIntoEndpoint;
|
||||
@@ -32,16 +29,16 @@ export const TerminatorsTF = (
|
||||
// Check if there even is an arc
|
||||
if (crsIntoEndpoint !== lastCourse) {
|
||||
// Turn Dir
|
||||
if (!leg.TurnDir || leg.TurnDir === "E") {
|
||||
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";
|
||||
leg.TurnDir = prov > 0 ? 'L' : 'R';
|
||||
}
|
||||
|
||||
// Generate arc
|
||||
let condition = false;
|
||||
do {
|
||||
if (leg.TurnDir === "R") {
|
||||
if (leg.TurnDir === 'R') {
|
||||
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
|
||||
lastCourse += delta < 1 ? delta : 1;
|
||||
lastCourse = lastCourse.normaliseDegrees();
|
||||
@@ -56,9 +53,7 @@ export const TerminatorsTF = (
|
||||
latitude: line.at(-1)![1],
|
||||
longitude: line.at(-1)![0],
|
||||
},
|
||||
(
|
||||
(previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 3600
|
||||
).toMetre(),
|
||||
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 3600).toMetre(),
|
||||
lastCourse
|
||||
);
|
||||
|
||||
@@ -66,7 +61,7 @@ export const TerminatorsTF = (
|
||||
|
||||
crsIntoEndpoint = geolib.getGreatCircleBearing(arcFix, targetFix);
|
||||
|
||||
if (leg.TurnDir === "R") {
|
||||
if (leg.TurnDir === 'R') {
|
||||
condition = crsIntoEndpoint > trackIntoEndpoint;
|
||||
} else {
|
||||
condition = crsIntoEndpoint < trackIntoEndpoint;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as geolib from "geolib";
|
||||
import Parser from "../parser.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import Parser from '../parser';
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
// NOTE: No wind adjustments to be made, no clue how *that* would draw
|
||||
export const TerminatorsVA = (
|
||||
@@ -12,37 +12,16 @@ export const TerminatorsVA = (
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
line = generatePerformanceArc(
|
||||
crsIntoEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute intercept of crs from arc end and expected altitude
|
||||
const targetFix: NavFix = {
|
||||
...geolib.computeDestinationPoint(
|
||||
arcEnd,
|
||||
(
|
||||
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
|
||||
Parser.AC_VS) *
|
||||
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *
|
||||
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
|
||||
).toMetre(),
|
||||
crsIntoEndpoint
|
||||
@@ -54,7 +33,6 @@ export const TerminatorsVA = (
|
||||
speedConstraint: leg.SpeedLimit,
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
line.push([targetFix.longitude, targetFix.latitude]);
|
||||
|
||||
return [targetFix, line];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
// NOTE: No wind adjustments to be made, no clue how *that* would draw
|
||||
// NOTE: Distance not adjusted for altitude in this demo
|
||||
@@ -13,35 +13,14 @@ export const TerminatorsVD = (
|
||||
latitude: leg.NavLat,
|
||||
longitude: leg.NavLon,
|
||||
};
|
||||
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
|
||||
|
||||
line = generatePerformanceArc(
|
||||
crsIntoEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute distance to fly from arc end
|
||||
const crsToNavaid = geolib.getGreatCircleBearing(arcEnd, navaid);
|
||||
const distToNavaid = geolib.getDistance(arcEnd, navaid);
|
||||
let remainingDistance = leg.Distance.toMetre();
|
||||
@@ -52,9 +31,10 @@ export const TerminatorsVD = (
|
||||
// Navaid in front of us
|
||||
else {
|
||||
// Navaid will not be passed before distance is hit
|
||||
if (distToNavaid > remainingDistance)
|
||||
remainingDistance = distToNavaid - remainingDistance;
|
||||
if (distToNavaid > remainingDistance) remainingDistance = distToNavaid - remainingDistance;
|
||||
}
|
||||
|
||||
// Compute intercept of crs from arc end and distance
|
||||
const targetFix: NavFix = {
|
||||
...geolib.computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
|
||||
name: leg.Distance.toString(),
|
||||
@@ -64,7 +44,6 @@ export const TerminatorsVD = (
|
||||
speedConstraint: leg.SpeedLimit,
|
||||
altitudeConstraint: leg.Alt,
|
||||
};
|
||||
|
||||
line.push([targetFix.longitude, targetFix.latitude]);
|
||||
|
||||
return [targetFix, line];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
|
||||
import { computeIntersection } from "../utils/computeIntersection.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
|
||||
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
|
||||
import { computeIntersection } from '../utils/computeIntersection';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
|
||||
|
||||
// NOTE: No wind adjustments to be made, no clue how *that* would draw
|
||||
export const TerminatorsVI = (
|
||||
@@ -15,12 +15,7 @@ export const TerminatorsVI = (
|
||||
|
||||
// Compute INTC
|
||||
const interceptFix: NavFix = {
|
||||
...computeIntersection(
|
||||
previousFix,
|
||||
leg.Course.toTrue(nextFix),
|
||||
nextFix,
|
||||
crs
|
||||
)!,
|
||||
...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crs)!,
|
||||
isFlyOver: leg.IsFlyOver,
|
||||
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
|
||||
speed: speed,
|
||||
@@ -48,10 +43,7 @@ export const TerminatorsVI = (
|
||||
if (interceptPoint2)
|
||||
return [
|
||||
{ ...interceptFix, ...interceptPoint2 },
|
||||
[
|
||||
...line.slice(0, -1),
|
||||
[interceptPoint2.longitude, interceptPoint2.latitude],
|
||||
],
|
||||
[...line.slice(0, -1), [interceptPoint2.longitude, interceptPoint2.latitude]],
|
||||
];
|
||||
|
||||
return [interceptFix, line];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import * as geolib from 'geolib';
|
||||
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
// NOTE: No wind adjustments to be made, no clue how *that* would draw
|
||||
export const TerminatorsVM = (
|
||||
@@ -10,11 +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 = geolib.computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
|
||||
|
||||
const line = handleTurnAtFix(
|
||||
leg.Course.toTrue(previousFix),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as geolib from "geolib";
|
||||
import { computeIntersection } from "../utils/computeIntersection.ts";
|
||||
import { computeSpeed } from "../utils/computeSpeed.ts";
|
||||
import { generatePerformanceArc } from "../pathGenerators/generatePerformanceArc.ts";
|
||||
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
|
||||
import { computeIntersection } from '../utils/computeIntersection';
|
||||
import { computeSpeed } from '../utils/computeSpeed';
|
||||
|
||||
// NOTE: No wind adjustments to be made, no clue how *that* would draw
|
||||
export const TerminatorsVR = (
|
||||
@@ -17,31 +16,11 @@ export const TerminatorsVR = (
|
||||
const crsIntoEndpoint = leg.NavBear.toTrue(navaid);
|
||||
const speed = computeSpeed(leg, previousFix);
|
||||
|
||||
let line: LineSegment[] = [];
|
||||
|
||||
if (previousFix.isFlyOver) {
|
||||
line = generatePerformanceArc(
|
||||
crsFromEndpoint,
|
||||
lastCourse,
|
||||
previousFix,
|
||||
speed,
|
||||
leg.TurnDir
|
||||
);
|
||||
} else {
|
||||
line.push([previousFix.longitude, previousFix.latitude]);
|
||||
}
|
||||
|
||||
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
|
||||
if (line.length > 1) {
|
||||
lastCourse = geolib.getGreatCircleBearing(
|
||||
{
|
||||
latitude: line.at(-2)![1],
|
||||
longitude: line.at(-2)![0],
|
||||
},
|
||||
arcEnd
|
||||
);
|
||||
}
|
||||
// Compute overfly
|
||||
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsFromEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
|
||||
lastCourse = _lastCourse;
|
||||
|
||||
// Compute intercept of crs from arc end and radial
|
||||
const interceptFix: NavFix = {
|
||||
...computeIntersection(arcEnd, crsFromEndpoint, navaid, crsIntoEndpoint)!,
|
||||
isFlyOver: leg.IsFlyOver,
|
||||
|
||||
@@ -5,12 +5,7 @@
|
||||
* @param brng2 bearing from Point 2
|
||||
* @returns Intersection point
|
||||
*/
|
||||
export const computeIntersection = (
|
||||
p1: NavFix,
|
||||
brng1: number,
|
||||
p2: NavFix,
|
||||
brng2: number
|
||||
): NavFix | undefined => {
|
||||
export const computeIntersection = (p1: NavFix, brng1: number, p2: NavFix, brng2: number): NavFix | undefined => {
|
||||
if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
|
||||
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);
|
||||
|
||||
@@ -31,20 +26,13 @@ export const computeIntersection = (
|
||||
const δ12 =
|
||||
2 *
|
||||
Math.asin(
|
||||
Math.sqrt(
|
||||
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
||||
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
|
||||
)
|
||||
Math.sqrt(Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2))
|
||||
);
|
||||
if (Math.abs(δ12) < Number.EPSILON) return p1; // coincident points
|
||||
|
||||
// initial/final bearings between points
|
||||
const cosθa =
|
||||
(Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) /
|
||||
(Math.sin(δ12) * Math.cos(φ1));
|
||||
const cosθb =
|
||||
(Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) /
|
||||
(Math.sin(δ12) * Math.cos(φ2));
|
||||
const cosθa = (Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ1));
|
||||
const cosθb = (Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) / (Math.sin(δ12) * Math.cos(φ2));
|
||||
const θa = Math.acos(Math.min(Math.max(cosθa, -1), 1)); // protect against rounding errors
|
||||
const θb = Math.acos(Math.min(Math.max(cosθb, -1), 1)); // protect against rounding errors
|
||||
|
||||
@@ -57,29 +45,15 @@ export const computeIntersection = (
|
||||
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return undefined; // infinite intersections
|
||||
if (Math.sin(α1) * Math.sin(α2) < 0) return undefined; // ambiguous intersection (antipodal/360°)
|
||||
|
||||
const cosα3 =
|
||||
-Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
|
||||
const cosα3 = -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
|
||||
|
||||
const δ13 = Math.atan2(
|
||||
Math.sin(δ12) * Math.sin(α1) * Math.sin(α2),
|
||||
Math.cos(α2) + Math.cos(α1) * cosα3
|
||||
);
|
||||
const δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2), Math.cos(α2) + Math.cos(α1) * cosα3);
|
||||
|
||||
const φ3 = Math.asin(
|
||||
Math.min(
|
||||
Math.max(
|
||||
Math.sin(φ1) * Math.cos(δ13) +
|
||||
Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13),
|
||||
-1
|
||||
),
|
||||
1
|
||||
)
|
||||
Math.min(Math.max(Math.sin(φ1) * Math.cos(δ13) + Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13), -1), 1)
|
||||
);
|
||||
|
||||
const Δλ13 = Math.atan2(
|
||||
Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1),
|
||||
Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3)
|
||||
);
|
||||
const Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1), Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));
|
||||
const λ3 = λ1 + Δλ13;
|
||||
|
||||
const lat = φ3.toDegrees();
|
||||
@@ -89,7 +63,7 @@ export const computeIntersection = (
|
||||
...p1,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
name: "INTC",
|
||||
name: 'INTC',
|
||||
isIntersection: true,
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import Parser from "../parser.ts";
|
||||
import Parser from '../parser';
|
||||
|
||||
export const computeSpeed = (leg: TerminalEntry, previousFix: NavFix) => {
|
||||
if (leg.SpeedLimit) return leg.SpeedLimit;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { magvar } from "magvar";
|
||||
import { magvar } from 'magvar';
|
||||
|
||||
Number.prototype.toRadians = function () {
|
||||
return ((this as number) * Math.PI) / 180;
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import * as geolib from "geolib";
|
||||
import * as geolib from 'geolib';
|
||||
|
||||
/**
|
||||
* @param leg Leg to examine
|
||||
* @param origin Origin of current leg
|
||||
* @returns Adjusted course and fix
|
||||
*/
|
||||
export const getCourseAndFixForIntercepts = (
|
||||
leg: TerminalEntry,
|
||||
origin: NavFix
|
||||
): [number, NavFix] => {
|
||||
export const getCourseAndFixForIntercepts = (leg: TerminalEntry, origin: NavFix): [number, NavFix] => {
|
||||
switch (leg.TrackCode) {
|
||||
case "CF": {
|
||||
case 'CF': {
|
||||
const _leg = leg as CFTerminalEntry;
|
||||
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
|
||||
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
|
||||
}
|
||||
case "FM": {
|
||||
case 'FM': {
|
||||
const _leg = leg as FMTerminalEntry;
|
||||
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
|
||||
return [_leg.Course.toTrue(fix), fix];
|
||||
}
|
||||
case "TF": {
|
||||
case 'TF': {
|
||||
const _leg = leg as FMTerminalEntry;
|
||||
return [
|
||||
geolib.getGreatCircleBearing(origin, {
|
||||
@@ -30,11 +27,16 @@ export const getCourseAndFixForIntercepts = (
|
||||
{ latitude: _leg.WptLat, longitude: _leg.WptLon },
|
||||
];
|
||||
}
|
||||
case "AF": {
|
||||
case 'AF': {
|
||||
const _leg = leg as AFTerminalEntry;
|
||||
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
|
||||
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
|
||||
}
|
||||
case 'DF': {
|
||||
const _leg = leg as FMTerminalEntry;
|
||||
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
|
||||
return [-1, fix];
|
||||
}
|
||||
default: {
|
||||
return [-1, origin];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import readline from 'readline';
|
||||
|
||||
export const prompting = (message: string) => {
|
||||
return new Promise<string | null>((resolve) => {
|
||||
if (typeof prompt !== 'undefined') {
|
||||
return resolve(prompt(message));
|
||||
}
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
rl.question(`${message}: `, (id) => {
|
||||
rl.close();
|
||||
|
||||
resolve(id);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
@import 'tailwindcss';
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
declare module "geojson" {
|
||||
declare module 'geojson' {
|
||||
export const parse: (data: object, format: object) => object;
|
||||
}
|
||||
Vendored
+12
-13
@@ -1,21 +1,20 @@
|
||||
//eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import * as L from "leaflet";
|
||||
import 'leaflet';
|
||||
|
||||
declare module "leaflet" {
|
||||
declare module 'leaflet' {
|
||||
export function shapeMarker(
|
||||
latlng: LatLngExpression,
|
||||
options?: PathOptions & {
|
||||
shape?:
|
||||
| "diamond"
|
||||
| "square"
|
||||
| "triangle"
|
||||
| "triangle-up"
|
||||
| "triangle-down"
|
||||
| "arrowhead"
|
||||
| "arrowhead-up"
|
||||
| "arrowhead-down"
|
||||
| "circle"
|
||||
| "x"
|
||||
| 'diamond'
|
||||
| 'square'
|
||||
| 'triangle'
|
||||
| 'triangle-up'
|
||||
| 'triangle-down'
|
||||
| 'arrowhead'
|
||||
| 'arrowhead-up'
|
||||
| 'arrowhead-down'
|
||||
| 'circle'
|
||||
| 'x'
|
||||
| string;
|
||||
radius?: number;
|
||||
rotation?: number;
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
declare module "magvar" {
|
||||
declare module 'magvar' {
|
||||
export const magvar: (latitude: number, longitude: number) => number;
|
||||
}
|
||||
+1
-10
@@ -2,16 +2,7 @@ export declare global {
|
||||
type AFTerminalEntry = Required<
|
||||
Pick<
|
||||
TerminalEntry,
|
||||
| "WptID"
|
||||
| "WptLat"
|
||||
| "WptLon"
|
||||
| "TurnDir"
|
||||
| "NavID"
|
||||
| "NavLat"
|
||||
| "NavLon"
|
||||
| "NavBear"
|
||||
| "NavDist"
|
||||
| "Course"
|
||||
'WptID' | 'WptLat' | 'WptLon' | 'TurnDir' | 'NavID' | 'NavLat' | 'NavLon' | 'NavBear' | 'NavDist' | 'Course'
|
||||
>
|
||||
> &
|
||||
TerminalEntry;
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
export declare global {
|
||||
type CATerminalEntry = Required<Pick<TerminalEntry, "Course" | "Alt">> &
|
||||
TerminalEntry;
|
||||
type CATerminalEntry = Required<Pick<TerminalEntry, 'Course' | 'Alt'>> & TerminalEntry;
|
||||
}
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
export declare global {
|
||||
type CDTerminalEntry = Required<
|
||||
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "Course" | "Distance">
|
||||
> &
|
||||
type CDTerminalEntry = Required<Pick<TerminalEntry, 'NavID' | 'NavLat' | 'NavLon' | 'Course' | 'Distance'>> &
|
||||
TerminalEntry;
|
||||
}
|
||||
+1
-10
@@ -2,16 +2,7 @@ export declare global {
|
||||
type CFTerminalEntry = Required<
|
||||
Pick<
|
||||
TerminalEntry,
|
||||
| "WptID"
|
||||
| "WptLat"
|
||||
| "WptLon"
|
||||
| "NavID"
|
||||
| "NavLat"
|
||||
| "NavLon"
|
||||
| "NavBear"
|
||||
| "NavDist"
|
||||
| "Course"
|
||||
| "Distance"
|
||||
'WptID' | 'WptLat' | 'WptLon' | 'NavID' | 'NavLat' | 'NavLon' | 'NavBear' | 'NavDist' | 'Course' | 'Distance'
|
||||
>
|
||||
> &
|
||||
TerminalEntry;
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
export declare global {
|
||||
type CITerminalEntry = Required<Pick<TerminalEntry, "Course">> &
|
||||
TerminalEntry;
|
||||
type CITerminalEntry = Required<Pick<TerminalEntry, 'Course'>> & TerminalEntry;
|
||||
}
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
export declare global {
|
||||
type CRTerminalEntry = Required<
|
||||
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "NavBear" | "Course">
|
||||
> &
|
||||
type CRTerminalEntry = Required<Pick<TerminalEntry, 'NavID' | 'NavLat' | 'NavLon' | 'NavBear' | 'Course'>> &
|
||||
TerminalEntry;
|
||||
}
|
||||
+1
-4
@@ -1,6 +1,3 @@
|
||||
export declare global {
|
||||
type DFTerminalEntry = Required<
|
||||
Pick<TerminalEntry, "WptID" | "WptLat" | "WptLon" | "IsFlyOver">
|
||||
> &
|
||||
TerminalEntry;
|
||||
type DFTerminalEntry = Required<Pick<TerminalEntry, 'WptID' | 'WptLat' | 'WptLon' | 'IsFlyOver'>> & TerminalEntry;
|
||||
}
|
||||
+1
-10
@@ -2,16 +2,7 @@ export declare global {
|
||||
type FATerminalEntry = Required<
|
||||
Pick<
|
||||
TerminalEntry,
|
||||
| "WptID"
|
||||
| "WptLat"
|
||||
| "WptLon"
|
||||
| "NavID"
|
||||
| "NavLat"
|
||||
| "NavLon"
|
||||
| "NavBear"
|
||||
| "NavDist"
|
||||
| "Course"
|
||||
| "Alt"
|
||||
'WptID' | 'WptLat' | 'WptLon' | 'NavID' | 'NavLat' | 'NavLon' | 'NavBear' | 'NavDist' | 'Course' | 'Alt'
|
||||
>
|
||||
> &
|
||||
TerminalEntry;
|
||||
|
||||
+11
-11
@@ -2,17 +2,17 @@ export declare global {
|
||||
type FCTerminalEntry = Required<
|
||||
Pick<
|
||||
TerminalEntry,
|
||||
| "WptID"
|
||||
| "WptLat"
|
||||
| "WptLon"
|
||||
| "IsFlyOver"
|
||||
| "NavID"
|
||||
| "NavLat"
|
||||
| "NavLon"
|
||||
| "NavBear"
|
||||
| "NavDist"
|
||||
| "Course"
|
||||
| "Distance"
|
||||
| 'WptID'
|
||||
| 'WptLat'
|
||||
| 'WptLon'
|
||||
| 'IsFlyOver'
|
||||
| 'NavID'
|
||||
| 'NavLat'
|
||||
| 'NavLon'
|
||||
| 'NavBear'
|
||||
| 'NavDist'
|
||||
| 'Course'
|
||||
| 'Distance'
|
||||
>
|
||||
> &
|
||||
TerminalEntry;
|
||||
|
||||
+1
-10
@@ -2,16 +2,7 @@ export declare global {
|
||||
type FDTerminalEntry = Required<
|
||||
Pick<
|
||||
TerminalEntry,
|
||||
| "WptID"
|
||||
| "WptLat"
|
||||
| "WptLon"
|
||||
| "NavID"
|
||||
| "NavLat"
|
||||
| "NavLon"
|
||||
| "NavBear"
|
||||
| "NavDist"
|
||||
| "Course"
|
||||
| "Distance"
|
||||
'WptID' | 'WptLat' | 'WptLon' | 'NavID' | 'NavLat' | 'NavLon' | 'NavBear' | 'NavDist' | 'Course' | 'Distance'
|
||||
>
|
||||
> &
|
||||
TerminalEntry;
|
||||
|
||||
+1
-9
@@ -2,15 +2,7 @@ export declare global {
|
||||
type FMTerminalEntry = Required<
|
||||
Pick<
|
||||
TerminalEntry,
|
||||
| "WptID"
|
||||
| "WptLat"
|
||||
| "WptLon"
|
||||
| "NavID"
|
||||
| "NavLat"
|
||||
| "NavLon"
|
||||
| "NavBear"
|
||||
| "NavDist"
|
||||
| "Course"
|
||||
'WptID' | 'WptLat' | 'WptLon' | 'NavID' | 'NavLat' | 'NavLon' | 'NavBear' | 'NavDist' | 'Course'
|
||||
>
|
||||
> &
|
||||
TerminalEntry;
|
||||
|
||||
+1
-4
@@ -1,6 +1,3 @@
|
||||
export declare global {
|
||||
type IFTerminalEntry = Required<
|
||||
Pick<TerminalEntry, "WptID" | "WptLat" | "WptLon">
|
||||
> &
|
||||
TerminalEntry;
|
||||
type IFTerminalEntry = Required<Pick<TerminalEntry, 'WptID' | 'WptLat' | 'WptLon'>> & TerminalEntry;
|
||||
}
|
||||
+10
-10
@@ -2,16 +2,16 @@ export declare global {
|
||||
type RFTerminalEntry = Required<
|
||||
Pick<
|
||||
TerminalEntry,
|
||||
| "WptID"
|
||||
| "WptLat"
|
||||
| "WptLon"
|
||||
| "TurnDir"
|
||||
| "NavBear"
|
||||
| "Course"
|
||||
| "Distance"
|
||||
| "CenterID"
|
||||
| "CenterLat"
|
||||
| "CenterLon"
|
||||
| 'WptID'
|
||||
| 'WptLat'
|
||||
| 'WptLon'
|
||||
| 'TurnDir'
|
||||
| 'NavBear'
|
||||
| 'Course'
|
||||
| 'Distance'
|
||||
| 'CenterID'
|
||||
| 'CenterLat'
|
||||
| 'CenterLon'
|
||||
>
|
||||
> &
|
||||
TerminalEntry;
|
||||
|
||||
+1
-4
@@ -1,6 +1,3 @@
|
||||
export declare global {
|
||||
type TFTerminalEntry = Required<
|
||||
Pick<TerminalEntry, "WptID" | "WptLat" | "WptLon" | "IsFlyOver">
|
||||
> &
|
||||
TerminalEntry;
|
||||
type TFTerminalEntry = Required<Pick<TerminalEntry, 'WptID' | 'WptLat' | 'WptLon' | 'IsFlyOver'>> & TerminalEntry;
|
||||
}
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
export declare global {
|
||||
type VATerminalEntry = Required<Pick<TerminalEntry, "Course" | "Alt">> &
|
||||
TerminalEntry;
|
||||
type VATerminalEntry = Required<Pick<TerminalEntry, 'Course' | 'Alt'>> & TerminalEntry;
|
||||
}
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
export declare global {
|
||||
type VDTerminalEntry = Required<
|
||||
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "Course" | "Distance">
|
||||
> &
|
||||
type VDTerminalEntry = Required<Pick<TerminalEntry, 'NavID' | 'NavLat' | 'NavLon' | 'Course' | 'Distance'>> &
|
||||
TerminalEntry;
|
||||
}
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
export declare global {
|
||||
type VITerminalEntry = Required<Pick<TerminalEntry, "Course">> &
|
||||
TerminalEntry;
|
||||
type VITerminalEntry = Required<Pick<TerminalEntry, 'Course'>> & TerminalEntry;
|
||||
}
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
export declare global {
|
||||
type VMTerminalEntry = Required<Pick<TerminalEntry, "Course">> &
|
||||
TerminalEntry;
|
||||
type VMTerminalEntry = Required<Pick<TerminalEntry, 'Course'>> & TerminalEntry;
|
||||
}
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
export declare global {
|
||||
type VRTerminalEntry = Required<
|
||||
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "NavBear" | "Course">
|
||||
> &
|
||||
type VRTerminalEntry = Required<Pick<TerminalEntry, 'NavID' | 'NavLat' | 'NavLon' | 'NavBear' | 'Course'>> &
|
||||
TerminalEntry;
|
||||
}
|
||||
Vendored
+36
-27
@@ -6,6 +6,7 @@ export declare global {
|
||||
|
||||
type Runway = {
|
||||
ID: number;
|
||||
AirportID: number;
|
||||
Latitude: number;
|
||||
Longitude: number;
|
||||
Elevation: number;
|
||||
@@ -14,31 +15,31 @@ export declare global {
|
||||
};
|
||||
|
||||
type TrackCode =
|
||||
| "AF"
|
||||
| "CA"
|
||||
| "CD"
|
||||
| "CF"
|
||||
| "CI"
|
||||
| "CR"
|
||||
| "DF"
|
||||
| "FA"
|
||||
| "FC"
|
||||
| "FD"
|
||||
| "FM"
|
||||
| "HA"
|
||||
| "HF"
|
||||
| "HM"
|
||||
| "IF"
|
||||
| "PI"
|
||||
| "RF"
|
||||
| "TF"
|
||||
| "VA"
|
||||
| "VD"
|
||||
| "VI"
|
||||
| "VM"
|
||||
| "VR";
|
||||
| 'AF'
|
||||
| 'CA'
|
||||
| 'CD'
|
||||
| 'CF'
|
||||
| 'CI'
|
||||
| 'CR'
|
||||
| 'DF'
|
||||
| 'FA'
|
||||
| 'FC'
|
||||
| 'FD'
|
||||
| 'FM'
|
||||
| 'HA'
|
||||
| 'HF'
|
||||
| 'HM'
|
||||
| 'IF'
|
||||
| 'PI'
|
||||
| 'RF'
|
||||
| 'TF'
|
||||
| 'VA'
|
||||
| 'VD'
|
||||
| 'VI'
|
||||
| 'VM'
|
||||
| 'VR';
|
||||
|
||||
type TurnDirection = "E" | "L" | "R";
|
||||
type TurnDirection = 'E' | 'L' | 'R';
|
||||
|
||||
type TerminalEntry = {
|
||||
ID: number;
|
||||
@@ -70,9 +71,10 @@ export declare global {
|
||||
|
||||
type Terminal = {
|
||||
ID: number;
|
||||
FullName: string;
|
||||
AirportID: number;
|
||||
ICAO: string;
|
||||
RwyID: number;
|
||||
FullName: string;
|
||||
RwyID?: number;
|
||||
};
|
||||
|
||||
type NavFix = {
|
||||
@@ -82,7 +84,7 @@ export declare global {
|
||||
speed?: number;
|
||||
name?: string;
|
||||
isFlyOver?: boolean;
|
||||
"marker-color"?: string;
|
||||
'marker-color'?: string;
|
||||
altitudeConstraint?: string;
|
||||
speedConstraint?: number;
|
||||
// For map
|
||||
@@ -90,4 +92,11 @@ export declare global {
|
||||
};
|
||||
|
||||
type LineSegment = [number, number];
|
||||
|
||||
type Airport = {
|
||||
ID: number;
|
||||
ICAO: string;
|
||||
Latitude: number;
|
||||
Longitude: number;
|
||||
};
|
||||
}
|
||||
Vendored
+13
@@ -1 +1,14 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ViteTypeOptions {
|
||||
strictImportMetaEnv: unknown;
|
||||
}
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_NG_CLIENT_ID: string;
|
||||
readonly VITE_NG_CLIENT_SECRET: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"sourceMap": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"sourceMap": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
plugins: [tailwindcss(), react()],
|
||||
build: {
|
||||
sourcemap: true,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user