MagDec and Loading indicator for parser

This commit is contained in:
Kilian Hofmann 2025-07-18 23:20:24 +02:00
parent 4e4b921f79
commit e9bab0306f
13 changed files with 79 additions and 47 deletions

2
.vscode/launch.json vendored
View File

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

View File

@ -726,4 +726,4 @@ For prod:
- `cd md11-nav-data`.
- `pnpm build`
- Verify Navdata was copied into `dist`
- Verify NavData was copied into `dist`

View File

@ -16,7 +16,6 @@
"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",

View File

@ -23,9 +23,6 @@ importers:
leaflet-svg-shape-markers:
specifier: ^1.4.0
version: 1.4.0
magvar:
specifier: ^2.0.0
version: 2.0.0
navigraph:
specifier: ^1.4.1
version: 1.4.1
@ -1102,9 +1099,6 @@ packages:
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
magvar@2.0.0:
resolution: {integrity: sha512-00LpSwEJZcnyX3VsdCM2CHSCvB+M6sVQTawLCB3J9oK7eEueNltNx9GFL4YR+/HIkLM+l8rFKiY77JTKnOf0jw==}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@ -2339,8 +2333,6 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.4
magvar@2.0.0: {}
merge2@1.4.1: {}
micromatch@4.0.8:

View File

@ -0,0 +1 @@
Copy over contents of `Data/Primary` retaining the structure.

Binary file not shown.

View File

@ -1,15 +1,15 @@
import type { DeviceFlowParams } from 'navigraph/auth';
import { QRCodeSVG } from 'qrcode.react';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { ProcedureSelect } from './components//ProcedureSelect';
import { Loader } from './components/Loader';
import { Map } from './components/Map';
import { Sidebar } from './components/Sidebar';
import { useNavigraphAuth } from './contexts/NavigraphAuth/NavigraphAuthContext';
import Parser from './parser/parser';
const parser = await Parser.instance();
function App() {
const [parser, setParser] = useState<Parser>();
const [selectedAirport, setSelectedAirport] = useState<Airport>();
const [selectedRunway, setSelectedRunway] = useState<Runway>();
const [selectedTerminal, setSelectedTerminal] = useState<Terminal>();
@ -22,13 +22,29 @@ function App() {
const handleSignIn = () => signIn((p) => setParams(p));
useEffect(() => {
(async () => {
setParser(await Parser.instance());
})();
}, []);
if (!parser || !initialized) {
return (
<div className="flex min-h-dvh w-full">
<div className="flex h-dvh w-dvw flex-col items-center justify-center gap-2">
<Loader size={3} />
{!parser && <span className="text-3xl">Initialising Parser...</span>}
{!initialized && <span className="text-3xl">Initialising Navigraph SDK...</span>}
</div>
</div>
);
}
return (
<>
{!user ? (
<div className="flex min-h-dvh w-full">
{!initialized && <div>Loading...</div>}
{initialized && !params && !user && (
{initialized && !params && (
<div className="flex h-dvh w-dvw items-center justify-center">
<button
className="cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2 disabled:bg-gray-100"
@ -39,7 +55,7 @@ function App() {
</div>
)}
{params?.verification_uri_complete && !user && (
{params?.verification_uri_complete && (
<div className="flex h-dvh w-dvw flex-col items-center justify-center">
<QRCodeSVG value={params.verification_uri_complete} size={250} />
@ -74,6 +90,7 @@ function App() {
/>
) : (
<ProcedureSelect
parser={parser}
selectedAirport={selectedAirport}
selectedRunway={selectedRunway}
selectedTerminal={selectedTerminal}

View File

@ -2,9 +2,8 @@ import { createRef, useMemo, useState, type Dispatch, type FC, type SetStateActi
import Parser from '../parser/parser';
import { Loader } from './Loader';
const parser = await Parser.instance();
interface ProcedureSelectProps {
parser: Parser;
selectedAirport: Airport | undefined;
selectedRunway: Runway | undefined;
selectedTerminal: Terminal | undefined;
@ -15,6 +14,7 @@ interface ProcedureSelectProps {
}
export const ProcedureSelect: FC<ProcedureSelectProps> = ({
parser,
selectedAirport,
selectedRunway,
selectedTerminal,

View File

@ -25,12 +25,6 @@ import { TerminatorsVM } from './terminators/VM';
import { TerminatorsVR } from './terminators/VR';
import './utils/extensions';
/*
Runway IDs for LIED
26156 - 34L
26157 - 34R
*/
class Parser {
private static _instance: Parser;
@ -41,11 +35,19 @@ class Parser {
private _procedures: TerminalEntry[] = [];
public static MAGVAR: Uint8Array;
public static AC_SPEED = 250;
public static AC_BANK = 30;
public static AC_VS = 1400;
private constructor(airports: Airport[], waypoints: Waypoint[], runways: Runway[], terminals: Terminal[]) {
private constructor(
magVar: Uint8Array,
airports: Airport[],
waypoints: Waypoint[],
runways: Runway[],
terminals: Terminal[]
) {
Parser.MAGVAR = magVar;
this._airports = airports;
this._waypoints = waypoints;
this._runways = runways;
@ -54,12 +56,13 @@ class Parser {
public static instance = async () => {
if (!Parser._instance) {
const magVar = await (await fetch('NavData/magdec.bgl')).bytes();
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(airports, waypoints, runways, terminals);
Parser._instance = new Parser(magVar, airports, waypoints, runways, terminals);
}
return Parser._instance;

View File

@ -59,7 +59,7 @@ export const TerminatorsCF = (
if (endDist <= 25 || (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)) arc = arc1;
}
if (previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsToIntercept))) {
if (!arc && previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsToIntercept))) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);

View File

@ -58,7 +58,7 @@ export const TerminatorsTF = (
if (endDist <= 25 || (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)) arc = arc1;
}
if (previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsIntoEndpoint))) {
if (!arc && previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsIntoEndpoint))) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);

View File

@ -1,4 +1,4 @@
import { magvar } from 'magvar';
import Parser from '../parser';
Number.prototype.toRadians = function () {
return ((this as number) * Math.PI) / 180;
@ -19,8 +19,42 @@ Number.prototype.normaliseDegrees = function () {
: (this as number);
};
Number.prototype.toTrue = function (fix) {
const _magvar = magvar(fix.latitude, fix.longitude); //Magvar is returned + for East
return ((this as number) + _magvar).normaliseDegrees();
const toOffset = (lat: number, lon: number) =>
lon >= 0 ? lon * 362 + lat * 2 + 316 : (lon + 360) * 362 + lat * 2 + 316;
const magVarTable = Parser.MAGVAR;
const lowerLat = Math.floor(fix.latitude);
const upperLat = Math.ceil(fix.latitude);
const ratioLat = fix.latitude - lowerLat;
const lowerLon = Math.floor(fix.longitude);
const upperLon = Math.ceil(fix.longitude);
const ratioLon = fix.longitude - lowerLon;
const lowerLatLowerLonOffset = toOffset(lowerLat, lowerLon);
const lowerLatUpperLonOffset = toOffset(lowerLat, upperLon);
const upperLatLowerLonOffset = toOffset(upperLat, lowerLon);
const upperLatUpperLonOffset = toOffset(upperLat, upperLon);
let lowerLatLowerLonMagVar = (magVarTable[lowerLatLowerLonOffset + 1] << 8) + magVarTable[lowerLatLowerLonOffset];
lowerLatLowerLonMagVar = (lowerLatLowerLonMagVar * 0x168) / 0x10000;
lowerLatLowerLonMagVar = lowerLatLowerLonMagVar > 180 ? lowerLatLowerLonMagVar - 460 : lowerLatLowerLonMagVar;
let lowerLatUpperLonMagVar = (magVarTable[lowerLatUpperLonOffset + 1] << 8) + magVarTable[lowerLatUpperLonOffset];
lowerLatUpperLonMagVar = (lowerLatUpperLonMagVar * 0x168) / 0x10000;
lowerLatUpperLonMagVar = lowerLatUpperLonMagVar > 180 ? lowerLatUpperLonMagVar - 460 : lowerLatUpperLonMagVar;
let upperLatLowerLonMagVar = (magVarTable[upperLatLowerLonOffset + 1] << 8) + magVarTable[upperLatLowerLonOffset];
upperLatLowerLonMagVar = (upperLatLowerLonMagVar * 0x168) / 0x10000;
upperLatLowerLonMagVar = upperLatLowerLonMagVar > 180 ? upperLatLowerLonMagVar - 460 : upperLatLowerLonMagVar;
let upperLatUpperLonMagVar = (magVarTable[upperLatUpperLonOffset + 1] << 8) + magVarTable[upperLatUpperLonOffset];
upperLatUpperLonMagVar = (upperLatUpperLonMagVar * 0x168) / 0x10000;
upperLatUpperLonMagVar = upperLatUpperLonMagVar > 180 ? upperLatUpperLonMagVar - 460 : upperLatUpperLonMagVar;
const lowerLatRatioLon = lowerLatLowerLonMagVar + ratioLon * (lowerLatUpperLonMagVar - lowerLatLowerLonMagVar);
const upperLatRatioLon = upperLatLowerLonMagVar + ratioLon * (upperLatUpperLonMagVar - upperLatLowerLonMagVar);
const magVar = lowerLatRatioLon + ratioLat + (upperLatRatioLon - lowerLatRatioLon);
return ((this as number) + magVar).normaliseDegrees();
};
Number.prototype.toMetre = function () {
return (this as number) * 1852.0;

View File

@ -1,15 +1 @@
@import 'tailwindcss';
@theme {
--animate-fade-in-scale: fade-in-scale 0.3s ease-out;
@keyframes fade-in-scale {
0% {
opacity: 0;
transform: scale(0.95);
}
100% {
opacity: 1;
transform: scale(1);
}
}
}