Compare commits

...

24 Commits

Author SHA1 Message Date
Kilian 266606d9ab More leeway on TF overfly (MHTG)
NG skip
2025-08-17 00:01:22 +02:00
Kilian 900babf4cc workspace 2025-07-29 17:35:23 +02:00
Kilian 048d5129aa Fix CI into AF (DTMB ALTER SID into 07) 2025-07-29 14:12:25 +02:00
Kilian 73373420d3 Fix more CF 2025-07-19 15:14:46 +02:00
Kilian e9bab0306f MagDec and Loading indicator for parser 2025-07-18 23:20:24 +02:00
Kilian 4e4b921f79 Fix EDDM ILS26 OTT 2025-07-18 00:33:57 +02:00
Kilian d102ec4834 update navdata path 2025-07-17 22:51:58 +02:00
Kilian e91323c79b Update compose 2025-07-17 22:44:02 +02:00
Kilian bbda565272 update pw 2025-07-17 22:38:34 +02:00
Kilian 56ecfe38d1 compose env file 2025-07-17 22:33:20 +02:00
Kilian e0ee6d11dd Rename 2025-07-17 22:22:07 +02:00
Kilian 2bdb7e78c4 Last leg types 2025-07-17 20:57:12 +02:00
Kilian c5cd3c7a0e MAP rendering
More CF/TF fixes
2025-07-17 15:33:02 +02:00
Kilian 779a78649e Improve drawin 2025-07-17 03:31:46 +02:00
Kilian e4adf30632 Fix Breakpoints 2025-07-16 16:16:50 +02:00
Kilian 287ad8859b Rebuilt Transition adn chart select 2025-07-16 12:10:01 +02:00
Kilian f593cbd29a Initial NG 2025-07-16 01:34:34 +02:00
Kilian e4ed55cac9 Tailwind 2025-07-15 12:19:41 +02:00
Kilian 13bea68195 Wrap prompt 2025-07-15 11:35:03 +02:00
Kilian 50077746f0 Prettier
Refactor overfly fix
2025-07-15 11:02:25 +02:00
Kilian 8b04be187e FC/IF/VR 2025-07-15 01:36:52 +02:00
Kilian e41d002cef Adj. to new perf arc speed req 2025-07-14 21:50:00 +02:00
Kilian d90ba6c5e3 CD/VD/FD/DF reworked perf. arc calc 2025-07-14 21:06:15 +02:00
Kilian 49788eddab CA/DF 2025-07-14 14:23:45 +02:00
150 changed files with 4205 additions and 4347 deletions
+5 -1
View File
@@ -21,4 +21,8 @@ dist-ssr
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.env
NavData/
+5 -9
View File
@@ -5,16 +5,12 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"type": "node", "type": "chrome",
"request": "launch", "request": "launch",
"name": "Launch Parser Node", "name": "Launch Chrome against localhost",
"skipFiles": ["<node_internals>/**"], "url": "http://localhost:3000",
"args": [ "webRoot": "${workspaceFolder}/md11-nav-data",
"--experimental-strip-types", "sourceMaps": true
"${workspaceFolder}\\browser\\src\\parser\\node.ts"
],
"cwd": "${workspaceFolder}\\browser\\",
"outFiles": ["${workspaceFolder}/**/*.js"]
} }
] ]
} }
+7
View File
@@ -0,0 +1,7 @@
{
"folders": [
{
"path": "."
}
],
}
+79 -34
View File
@@ -10,7 +10,7 @@
### Example ### Example
LGAV BIBE1K SID (Cycle 2507, ID 10653) LGAV 03L BIBE1K SID (Cycle 2507, ID 10653)
### Minimum Required Fields ### Minimum Required Fields
@@ -40,12 +40,14 @@ LGAV BIBE1K SID (Cycle 2507, ID 10653)
While similar to an RF, the center point is coded differently. While similar to an RF, the center point is coded differently.
Calculate distance for FMS based on `2 * π * NavDist * abs(NavBear - Course) 360`.
## Course to Altitude (CA) ## Course to Altitude (CA)
### Example ### Example
LGAV BIBE1L SID (Cycle 2507, ID 10654) LGAV 03L BIBE1L SID (Cycle 2507, ID 10654)
### Minimum Required Fields ### Minimum Required Fields
@@ -55,6 +57,8 @@ LGAV BIBE1L SID (Cycle 2507, ID 10654)
### Instructions ### Instructions
- From preceding fix, fly along `Course` until reaching `Alt`. - From preceding fix, fly along `Course` until reaching `Alt`.
- If preceding fix was overfly, offset outbound `Course` to end of turn (tangent).
- Earliest leg terminations is at point of tangent.
### Units ### Units
@@ -72,7 +76,7 @@ This new origin is an implicit overfly.
### Example ### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657) LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields ### Minimum Required Fields
@@ -84,6 +88,8 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657)
- From preceding fix, fly along `Course` until reaching `Distance` from the navaid identified by - From preceding fix, fly along `Course` until reaching `Distance` from the navaid identified by
`NavID`. `NavID`.
- If preceding fix was overfly, offset outbound `Course` to end of turn (tangent).
- Earliest leg terminations is at point of tangent.
### Units ### Units
@@ -94,14 +100,13 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657)
The leg terminates upon reaching `Distance`. The leg terminates upon reaching `Distance`.
This intercept point then becomes the origin fix of the succeeding leg. This intercept point then becomes the origin fix of the succeeding leg.
This new origin is an implicit overfly.
## Course to Fix (CF) ## Course to Fix (CF)
### Example ### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657) LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields ### Minimum Required Fields
@@ -116,6 +121,7 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657)
- From preceding fix, fly `Course` to the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* - From preceding fix, fly `Course` to the fix identified by (`WptID`, `WptLat`, `WptLon`) *or*
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`). (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`).
- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`.
### Units ### Units
@@ -140,7 +146,7 @@ shall be `TurnDir`.
### Example ### Example
LGAV BIBE1L SID (Cycle 2507, ID 10654) LGAV 03L BIBE1L SID (Cycle 2507, ID 10654)
### Minimum Required Fields ### Minimum Required Fields
@@ -165,7 +171,7 @@ This new origin can never be an overfly due to the intercept nature.
### Example ### Example
LGAV KOR1D SID (Cycle 2507, ID 10679) LGAV 03L KOR1D SID (Cycle 2507, ID 10679)
### Minimum Required Fields ### Minimum Required Fields
@@ -192,7 +198,7 @@ This intercept point then becomes the origin fix of the succeeding leg.
### Example ### Example
LGAV KOR1D SID (Cycle 2507, ID 10679) LGAV 03L KOR1D SID (Cycle 2507, ID 10679)
### Minimum Required Fields ### Minimum Required Fields
@@ -201,18 +207,20 @@ LGAV KOR1D SID (Cycle 2507, ID 10679)
### Instructions ### Instructions
- From present position, fly direct to fix identified by `WptID` - From present position, fly direct to fix identified by `WptID`.
- If preceding fix and fix identified by `WptId` are the same, execute 360° turn
(requires `TurnDir`).
### Units ### Units
- `IsFlyOver`: Boolean indicating if fix is a mandatory fly over. - `IsFlyOver`: Boolean indicating if fix is a mandatory fly over.
## Fix to Altitude (FA) ## Track from Fix to Altitude (FA)
### Example ### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657) LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields ### Minimum Required Fields
@@ -227,6 +235,8 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657)
- From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* - From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or*
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Alt`. (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Alt`.
- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`.
- Earliest leg terminations is at point of intercept.
### Units ### Units
@@ -245,7 +255,7 @@ This new origin is an implicit overfly.
### Example ### Example
LIED CAR6F SID (Cycle 2507, ID 11798) LIED 34L/R CAR6F SID (Cycle 2507, ID 11798)
### Minimum Required Fields ### Minimum Required Fields
@@ -261,6 +271,9 @@ LIED CAR6F SID (Cycle 2507, ID 11798)
- From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* - From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or*
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Distance`. (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Distance`.
- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`.
- Earliest leg terminations is at point of intercept.
### Units ### Units
@@ -278,7 +291,7 @@ This intercept point then becomes the origin fix of the succeeding leg.
### Example ### Example
LGAV BIBE2T SID (Cycle 2507, ID 10659) LGAV 03R BIBE2T SID (Cycle 2507, ID 10659)
### Minimum Required Fields ### Minimum Required Fields
@@ -292,8 +305,10 @@ LGAV BIBE2T SID (Cycle 2507, ID 10659)
### Instructions ### Instructions
- From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* - From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or*
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` until reaching `Distance` from
reaching `Distance` from navaid identified by `NavID`. navaid identified by `NavID`.
- If preceding fix was overfly, continue turn until intercept `Course`, then continue on `Course`.
- Earliest leg terminations is at point of intercept.
### Units ### Units
@@ -304,14 +319,13 @@ LGAV BIBE2T SID (Cycle 2507, ID 10659)
The leg terminates upon reaching `Distance`. The leg terminates upon reaching `Distance`.
This intercept point then becomes the origin fix of the succeeding leg. This intercept point then becomes the origin fix of the succeeding leg.
This new origin is an implicit overfly.
## Track from Fix to Manual Termination (FM) ## Track from Fix to Manual Termination (FM)
### Example ### Example
LFPV PB2V SID (Cycle 2507, ID 10395) LFPV 27 PB2V SID (Cycle 2507, ID 10395)
### Minimum Required Fields ### Minimum Required Fields
@@ -332,7 +346,7 @@ LFPV PB2V SID (Cycle 2507, ID 10395)
- `NavDist` is in **nmi**. `NavDist` is geodesic. - `NavDist` is in **nmi**. `NavDist` is geodesic.
## Holding mandatory (HA) ## Holding mandatory (altitude end) (HA)
### Example ### Example
@@ -406,7 +420,7 @@ My guess is that `Distance` always is a distance. This would match with the exam
The Leg terminates after the hold is exited ath the hold entry fix. The Leg terminates after the hold is exited ath the hold entry fix.
## Holding mandatory (HM) ## Holding mandatory (manual end) (HM)
### Example ### Example
@@ -443,7 +457,7 @@ My guess as for the missing time/distance decider field, assume it to be distanc
### Example ### Example
FAWB VDM29 APP (Cycle 2507, ID 67794), Missed approach procedure FAWB 29 VDM29 APP (Cycle 2507, ID 67794), Missed approach procedure
### Minimum Required Fields ### Minimum Required Fields
@@ -479,8 +493,11 @@ FAUP VDM35 APP (Cycle 2507, ID 67790)
### Instructions ### Instructions
- From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or* - From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or*
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` for `Distance`. (`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly direct to succeeding leg fix.
- Turn `TurnDir` until aligned with succeeding leg course. - Turn `TurnDir` onto `Course`.
- Fly for 1 minute.
- Turn 180°.
- Fly until intercepting course of succeeding leg.
- `Alt` shall be the altitude during the procedure. - `Alt` shall be the altitude during the procedure.
### Units ### Units
@@ -491,15 +508,17 @@ FAUP VDM35 APP (Cycle 2507, ID 67790)
### Notes ### Notes
This needs to look into the succeeding leg in order to calculate the intercept point. This needs to look into the succeeding leg in order to fly the first segment and get the course
This intercept point then becomes the origin fix of the succeeding leg. to intercept after the reversal.
The database does not specify how long the outbound leg shall be.
1 minute is assumed here (googled it, seemed a common one).
## Radius to Fix (RF) ## Radius to Fix (RF)
### Example ### Example
LFRN GODA5R SID (cycle 2507, ID 10485) LFRN 10 GODA5R SID (cycle 2507, ID 10485)
### Minimum Required Fields ### Minimum Required Fields
@@ -516,6 +535,8 @@ LFRN GODA5R SID (cycle 2507, ID 10485)
- Arc center shall be navaid identified by `CenterID`, `CenterLat`, `CenterLon`. - Arc center shall be navaid identified by `CenterID`, `CenterLat`, `CenterLon`.
- Arc and turn shall be flown in direction specified by `TurnDir`. - Arc and turn shall be flown in direction specified by `TurnDir`.
- `Distance` shall be the track miles along the curved path - `Distance` shall be the track miles along the curved path
- `Course` shall be the inbound course of the tangent to the arc at the fix identified by
(`WptID`, `WptLat`, `WptLon`).
### Units ### Units
@@ -528,14 +549,15 @@ While similar to an AF, the center point is coded differently.
No radius is specified, but can be inferred based on center point, both endpoints and arc length No radius is specified, but can be inferred based on center point, both endpoints and arc length
Example has `NavBear` set to `null`, significance of the inbound tangential track is unknown. Example has `NavBear` set to `null`, significance of the inbound tangential track is unknown.
Same for the `Course`, which is set, but lacks any documentation.
Use `Distance` for calculations in FMS.
## Track to Fix (TF) ## Track to Fix (TF)
### Example ### Example
LFRN GODA5R SID (cycle 2507, ID 10485) LFRN 10 GODA5R SID (cycle 2507, ID 10485)
### Minimum Required Fields ### Minimum Required Fields
@@ -556,7 +578,7 @@ LFRN GODA5R SID (cycle 2507, ID 10485)
### Example ### Example
LFRK LGL4X SID (Cycle 2507, ID 10475) LFRK 10 LGL4X SID (Cycle 2507, ID 10475)
### Minimum Required Fields ### Minimum Required Fields
@@ -584,7 +606,7 @@ This new origin is an implicit overfly.
### Example ### Example
LFRK NEVI4Y SID (Cycle 2507, ID 10482) LFRK 31 NEVI4Y SID (Cycle 2507, ID 10482)
### Minimum Required Fields ### Minimum Required Fields
@@ -607,14 +629,13 @@ LFRK NEVI4Y SID (Cycle 2507, ID 10482)
The leg terminates upon reaching `Distance`. The leg terminates upon reaching `Distance`.
This intercept point then becomes the origin fix of the succeeding leg. This intercept point then becomes the origin fix of the succeeding leg.
This new origin is an implicit overfly.
## Heading to Intercept (VI) ## Heading to Intercept (VI)
### Example ### Example
LFRK LUSI4Y SID (Cycle 2507, ID 10480) LFRK 31 LUSI4Y SID (Cycle 2507, ID 10480)
### Minimum Required Fields ### Minimum Required Fields
@@ -641,7 +662,7 @@ This new origin can never be an overfly due to the intercept nature.
### Example ### Example
LFPV PB2P SID (Cycle 2507, ID 10394) LFPV 27 PB2P SID (Cycle 2507, ID 10394)
### Minimum Required Fields ### Minimum Required Fields
@@ -661,7 +682,7 @@ LFPV PB2P SID (Cycle 2507, ID 10394)
### Example ### Example
LIMC MMP8G SID (Cycle 2507, ID 11909) LIMC 35R MMP8G SID (Cycle 2507, ID 11909)
### Minimum Required Fields ### Minimum Required Fields
@@ -681,4 +702,28 @@ LIMC MMP8G SID (Cycle 2507, ID 11909)
### Notes ### Notes
The leg terminates at the intercept point. The leg terminates at the intercept point.
This intercept point then becomes the origin fix of the succeeding leg. This intercept point then becomes the origin fix of the succeeding leg.
# Other Fields
- `Vnav`: Angle from IAF to MAP. Useful for RNAV.
# How to run
- `git clone https://git.hofmannnet.myhome-server.de/TFDi_MD-11_QA/NavDataExplorer.git`.
- `cd md11-nav-data`.
- `pnpm install`
For dev:
- Copy contents of `tfdidesign-aircraft-md11/Design/Primary` into ? `md11-nav-data/public/NavData`
- `cd md11-nav-data`.
- `pnpm dev`
For prod:
- `cd md11-nav-data`.
- `pnpm build`
- Verify NavData was copied into `dist`
View File
-26
View File
@@ -1,26 +0,0 @@
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";
export default tseslint.config([
globalIgnores(["dist"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs["recommended-latest"],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
"@typescript-eslint/no-shadow": "error",
},
},
]);
File diff suppressed because one or more lines are too long
-137
View File
@@ -1,137 +0,0 @@
[
{
"ID": 84218,
"TerminalID": 10394,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 165609,
"WptLat": 48.77185278,
"WptLon": 2.09752222,
"TurnDir": "",
"NavID": 3827,
"NavLat": 48.97466667,
"NavLon": 2.45516667,
"NavBear": 228.5,
"NavDist": 18.7,
"Course": 267,
"Distance": 4,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84219,
"TerminalID": 10394,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 247786,
"WptLat": 48.73058333,
"WptLon": 1.8205,
"TurnDir": "",
"NavID": 3827,
"NavLat": 48.97466667,
"NavLon": 2.45516667,
"NavBear": 239,
"NavDist": 29.1,
"Course": 256,
"Distance": 11,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84220,
"TerminalID": 10394,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 253072,
"WptLat": 48.75372222,
"WptLon": 2.10236111,
"TurnDir": "R",
"NavID": 3827,
"NavLat": 48.97466667,
"NavLon": 2.45516667,
"NavBear": 225.6,
"NavDist": 19.3,
"Course": 107,
"Distance": 11,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84221,
"TerminalID": 10394,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CR",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": 3827,
"NavLat": 48.97466667,
"NavLon": 2.45516667,
"NavBear": 137,
"NavDist": null,
"Course": 73,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84222,
"TerminalID": 10394,
"Type": "2",
"Transition": "ALL",
"TrackCode": "VM",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 73,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-110
View File
@@ -1,110 +0,0 @@
[
{
"ID": 84223,
"TerminalID": 10395,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 165609,
"WptLat": 48.77185278,
"WptLon": 2.09752222,
"TurnDir": "",
"NavID": 3827,
"NavLat": 48.97466667,
"NavLon": 2.45516667,
"NavBear": 228.5,
"NavDist": 18.7,
"Course": 267,
"Distance": 4,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84224,
"TerminalID": 10395,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 165604,
"WptLat": 48.80511389,
"WptLon": 2.01279722,
"TurnDir": "",
"NavID": 3827,
"NavLat": 48.97466667,
"NavLon": 2.45516667,
"NavBear": 239,
"NavDist": 20.3,
"Course": 310,
"Distance": 3,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84225,
"TerminalID": 10395,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CI",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 22,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84226,
"TerminalID": 10395,
"Type": "2",
"Transition": "ALL",
"TrackCode": "FM",
"WptID": 165605,
"WptLat": 48.83954444,
"WptLon": 1.95262222,
"TurnDir": "",
"NavID": 3827,
"NavLat": 48.97466667,
"NavLon": 2.45516667,
"NavBear": 247,
"NavDist": 21.5,
"Course": 67,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-83
View File
@@ -1,83 +0,0 @@
[
{
"ID": 84536,
"TerminalID": 10475,
"Type": "2",
"Transition": "ALL",
"TrackCode": "VA",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 125,
"Distance": null,
"Alt": "00660A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84537,
"TerminalID": 10475,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 247774,
"WptLat": 49.11566667,
"WptLon": -0.32125,
"TurnDir": "",
"NavID": 3836,
"NavLat": 49.17319444,
"NavLon": -0.45527778,
"NavBear": 123.1,
"NavDist": 6.3,
"Course": 125,
"Distance": 6,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84538,
"TerminalID": 10475,
"Type": "2",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 252987,
"WptLat": 48.79061111,
"WptLon": 0.53027778,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-83
View File
@@ -1,83 +0,0 @@
[
{
"ID": 84551,
"TerminalID": 10480,
"Type": "2",
"Transition": "ALL",
"TrackCode": "VD",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": 3851,
"NavLat": 49.17333333,
"NavLon": -0.44711111,
"NavBear": null,
"NavDist": null,
"Course": 305,
"Distance": 5,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84552,
"TerminalID": 10480,
"Type": "2",
"Transition": "ALL",
"TrackCode": "VI",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 243,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84553,
"TerminalID": 10480,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 40633,
"WptLat": 49.21472222,
"WptLon": -1.79555556,
"TurnDir": "",
"NavID": 3836,
"NavLat": 49.17319444,
"NavLon": -0.45527778,
"NavBear": 273.2,
"NavDist": 52.8,
"Course": 273,
"Distance": 44,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-83
View File
@@ -1,83 +0,0 @@
[
{
"ID": 84557,
"TerminalID": 10482,
"Type": "2",
"Transition": "ALL",
"TrackCode": "VD",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": 3851,
"NavLat": 49.17333333,
"NavLon": -0.44711111,
"NavBear": null,
"NavDist": null,
"Course": 305,
"Distance": 5,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84558,
"TerminalID": 10482,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CI",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "R",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 50,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84559,
"TerminalID": 10482,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 8516,
"WptLat": 50,
"WptLon": -0.36823333,
"TurnDir": "",
"NavID": 3836,
"NavLat": 49.17319444,
"NavLon": -0.45527778,
"NavBear": 3.9,
"NavDist": 49.8,
"Course": 4,
"Distance": 42,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-110
View File
@@ -1,110 +0,0 @@
[
{
"ID": 84567,
"TerminalID": 10485,
"Type": "5",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 166062,
"WptLat": 48.06525,
"WptLon": -1.67930556,
"TurnDir": "",
"NavID": 3946,
"NavLat": 48.06925,
"NavLon": -1.74166667,
"NavBear": 95.4,
"NavDist": 2.5,
"Course": 101,
"Distance": 2,
"Alt": "00530A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 195,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84568,
"TerminalID": 10485,
"Type": "5",
"Transition": "ALL",
"TrackCode": "RF",
"WptID": 166064,
"WptLat": 48.03483333,
"WptLon": -1.63894444,
"TurnDir": "R",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 175.3,
"Distance": 2.6,
"Alt": "",
"Vnav": null,
"CenterID": 166063,
"CenterLat": 48.03252778,
"CenterLon": -1.6885,
"IsFlyOver": 0,
"SpeedLimit": 195,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84569,
"TerminalID": 10485,
"Type": "5",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 166061,
"WptLat": 47.91030556,
"WptLon": -1.62611111,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 84570,
"TerminalID": 10485,
"Type": "5",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 40368,
"WptLat": 47.64194444,
"WptLon": -1.96,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-110
View File
@@ -1,110 +0,0 @@
[
{
"ID": 85228,
"TerminalID": 10653,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 166553,
"WptLat": 38.04930556,
"WptLon": 24.04694722,
"TurnDir": "",
"NavID": 3985,
"NavLat": 37.88867778,
"NavLon": 23.80449722,
"NavBear": 45,
"NavDist": 15,
"Course": 32,
"Distance": 9,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85229,
"TerminalID": 10653,
"Type": "2",
"Transition": "ALL",
"TrackCode": "AF",
"WptID": 166645,
"WptLat": 37.75945833,
"WptLon": 24.07472778,
"TurnDir": "R",
"NavID": 3985,
"NavLat": 37.88867778,
"NavLon": 23.80449722,
"NavBear": 116,
"NavDist": 15,
"Course": 45,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85230,
"TerminalID": 10653,
"Type": "2",
"Transition": "ALL",
"TrackCode": "AF",
"WptID": 166646,
"WptLat": 37.73090833,
"WptLon": 24.04940556,
"TurnDir": "R",
"NavID": 3985,
"NavLat": 37.88867778,
"NavLon": 23.80449722,
"NavBear": 124,
"NavDist": 15,
"Course": 116,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 220,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85231,
"TerminalID": 10653,
"Type": "2",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 166620,
"WptLat": 37.20805556,
"WptLon": 24.83444444,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-164
View File
@@ -1,164 +0,0 @@
[
{
"ID": 85232,
"TerminalID": 10654,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CA",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 32,
"Distance": null,
"Alt": "00710A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85233,
"TerminalID": 10654,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 166631,
"WptLat": 38.07855556,
"WptLon": 24.06305,
"TurnDir": "",
"NavID": 4032,
"NavLat": 37.91683889,
"NavLon": 23.91443889,
"NavBear": 31,
"NavDist": 12,
"Course": 31,
"Distance": 10,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85234,
"TerminalID": 10654,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CI",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 31,
"Distance": null,
"Alt": "04000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 210,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85235,
"TerminalID": 10654,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 166663,
"WptLat": 38.04289167,
"WptLon": 24.31536944,
"TurnDir": "",
"NavID": 4015,
"NavLat": 37.99413889,
"NavLon": 24.49490833,
"NavBear": 284,
"NavDist": 9,
"Course": 104,
"Distance": 11,
"Alt": "07000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85236,
"TerminalID": 10654,
"Type": "2",
"Transition": "ALL",
"TrackCode": "DF",
"WptID": 253105,
"WptLat": 37.55716389,
"WptLon": 24.2987,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "24000B",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85237,
"TerminalID": 10654,
"Type": "2",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 166620,
"WptLat": 37.20805556,
"WptLon": 24.83444444,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-137
View File
@@ -1,137 +0,0 @@
[
{
"ID": 85247,
"TerminalID": 10657,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 253138,
"WptLat": 37.91801944,
"WptLon": 23.93798333,
"TurnDir": "",
"NavID": 4040,
"NavLat": 37.91801944,
"NavLon": 23.93798333,
"NavBear": 0,
"NavDist": 0,
"Course": 212,
"Distance": 2,
"Alt": "00700A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85248,
"TerminalID": 10657,
"Type": "2",
"Transition": "ALL",
"TrackCode": "FA",
"WptID": 253138,
"WptLat": 37.91801944,
"WptLon": 23.93798333,
"TurnDir": "",
"NavID": 4040,
"NavLat": 37.91801944,
"NavLon": 23.93798333,
"NavBear": 0,
"NavDist": 0,
"Course": 203,
"Distance": null,
"Alt": "04000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85249,
"TerminalID": 10657,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CD",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": 4007,
"NavLat": 37.55716389,
"NavLon": 24.2987,
"NavBear": null,
"NavDist": null,
"Course": 123,
"Distance": 17,
"Alt": "06000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 210,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85250,
"TerminalID": 10657,
"Type": "2",
"Transition": "ALL",
"TrackCode": "DF",
"WptID": 253105,
"WptLat": 37.55716389,
"WptLon": 24.2987,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "24000B",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85251,
"TerminalID": 10657,
"Type": "2",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 166620,
"WptLat": 37.20805556,
"WptLon": 24.83444444,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-110
View File
@@ -1,110 +0,0 @@
[
{
"ID": 85257,
"TerminalID": 10659,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CA",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 32,
"Distance": null,
"Alt": "00710A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85258,
"TerminalID": 10659,
"Type": "2",
"Transition": "ALL",
"TrackCode": "FD",
"WptID": 253138,
"WptLat": 37.91801944,
"WptLon": 23.93798333,
"TurnDir": "",
"NavID": 4040,
"NavLat": 37.91801944,
"NavLon": 23.93798333,
"NavBear": 0,
"NavDist": 0,
"Course": 32,
"Distance": 12,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85259,
"TerminalID": 10659,
"Type": "2",
"Transition": "ALL",
"TrackCode": "DF",
"WptID": 253105,
"WptLat": 37.55716389,
"WptLon": 24.2987,
"TurnDir": "R",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "24000B06000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 210,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85260,
"TerminalID": 10659,
"Type": "2",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 166620,
"WptLat": 37.20805556,
"WptLon": 24.83444444,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-110
View File
@@ -1,110 +0,0 @@
[
{
"ID": 85331,
"TerminalID": 10679,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CA",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 32,
"Distance": null,
"Alt": "00710A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85332,
"TerminalID": 10679,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 166630,
"WptLat": 38.06507222,
"WptLon": 24.05061944,
"TurnDir": "",
"NavID": 4032,
"NavLat": 37.91683889,
"NavLon": 23.91443889,
"NavBear": 31,
"NavDist": 11,
"Course": 31,
"Distance": 10,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85333,
"TerminalID": 10679,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CR",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "L",
"NavID": 3985,
"NavLat": 37.88867778,
"NavLon": 23.80449722,
"NavBear": 25,
"NavDist": null,
"Course": 253,
"Distance": null,
"Alt": "06000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 240,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 85334,
"TerminalID": 10679,
"Type": "2",
"Transition": "ALL",
"TrackCode": "DF",
"WptID": 247838,
"WptLat": 37.93041111,
"WptLon": 22.9359,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-218
View File
@@ -1,218 +0,0 @@
[
{
"ID": 90645,
"TerminalID": 11798,
"Type": "1",
"Transition": "RW34L",
"TrackCode": "DF",
"WptID": 169637,
"WptLat": 39.36545,
"WptLon": 8.96781667,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 90646,
"TerminalID": 11798,
"Type": "1",
"Transition": "RW34L",
"TrackCode": "FC",
"WptID": 169637,
"WptLat": 39.36545,
"WptLon": 8.96781667,
"TurnDir": "",
"NavID": 4103,
"NavLat": 39.38108333,
"NavLon": 8.96566667,
"NavBear": 170.9,
"NavDist": 0.9,
"Course": 344,
"Distance": 12,
"Alt": "05000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 90647,
"TerminalID": 11798,
"Type": "1",
"Transition": "RW34L",
"TrackCode": "DF",
"WptID": 247859,
"WptLat": 39.36372222,
"WptLon": 8.97402778,
"TurnDir": "L",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 270,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 90648,
"TerminalID": 11798,
"Type": "1",
"Transition": "RW34R",
"TrackCode": "DF",
"WptID": 169638,
"WptLat": 39.36699722,
"WptLon": 8.96985833,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 90649,
"TerminalID": 11798,
"Type": "1",
"Transition": "RW34R",
"TrackCode": "FC",
"WptID": 169638,
"WptLat": 39.36699722,
"WptLon": 8.96985833,
"TurnDir": "",
"NavID": 4103,
"NavLat": 39.38108333,
"NavLon": 8.96566667,
"NavBear": 164,
"NavDist": 0.9,
"Course": 344,
"Distance": 12,
"Alt": "05000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 90650,
"TerminalID": 11798,
"Type": "1",
"Transition": "RW34R",
"TrackCode": "DF",
"WptID": 247859,
"WptLat": 39.36372222,
"WptLon": 8.97402778,
"TurnDir": "L",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 270,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 90651,
"TerminalID": 11798,
"Type": "2",
"Transition": "ALL",
"TrackCode": "IF",
"WptID": 247859,
"WptLat": 39.36372222,
"WptLon": 8.97402778,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 90652,
"TerminalID": 11798,
"Type": "2",
"Transition": "ALL",
"TrackCode": "TF",
"WptID": 253189,
"WptLat": 39.11136111,
"WptLon": 9.50802778,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-623
View File
@@ -1,623 +0,0 @@
[
{
"ID": 91603,
"TerminalID": 11909,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CA",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 346,
"Distance": null,
"Alt": "01170A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91604,
"TerminalID": 11909,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CD",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": 4169,
"NavLat": 45.64077778,
"NavLon": 8.73483333,
"NavBear": null,
"NavDist": null,
"Course": 356,
"Distance": 4.1,
"Alt": "02200A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91605,
"TerminalID": 11909,
"Type": "2",
"Transition": "ALL",
"TrackCode": "VR",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": 4169,
"NavLat": 45.64077778,
"NavLon": 8.73483333,
"NavBear": 9,
"NavDist": null,
"Course": 41,
"Distance": null,
"Alt": "03000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 220,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91606,
"TerminalID": 11909,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 169914,
"WptLat": 45.72388889,
"WptLon": 8.86777778,
"TurnDir": "R",
"NavID": 4204,
"NavLat": 45.64597222,
"NavLon": 9.02158333,
"NavBear": 302.9,
"NavDist": 8,
"Course": 123,
"Distance": 2,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91607,
"TerminalID": 11909,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CA",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 173,
"Distance": null,
"Alt": "04000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 230,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91608,
"TerminalID": 11909,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 253267,
"WptLat": 45.64077778,
"WptLon": 8.73483333,
"TurnDir": "R",
"NavID": 4204,
"NavLat": 45.64597222,
"NavLon": 9.02158333,
"NavBear": 265.6,
"NavDist": 12.1,
"Course": 266,
"Distance": 5,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 230,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91609,
"TerminalID": 11909,
"Type": "3",
"Transition": "AOS5X",
"TrackCode": "IF",
"WptID": 253267,
"WptLat": 45.64077778,
"WptLon": 8.73483333,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91610,
"TerminalID": 11909,
"Type": "3",
"Transition": "AOS5X",
"TrackCode": "TF",
"WptID": 169921,
"WptLat": 45.57482778,
"WptLon": 8.31563056,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91611,
"TerminalID": 11909,
"Type": "3",
"Transition": "AOS5X",
"TrackCode": "TF",
"WptID": 169922,
"WptLat": 45.59140556,
"WptLon": 8.24456111,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "13500A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91612,
"TerminalID": 11909,
"Type": "3",
"Transition": "AOS5X",
"TrackCode": "TF",
"WptID": 169932,
"WptLat": 45.66769722,
"WptLon": 7.91445,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "16000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91613,
"TerminalID": 11909,
"Type": "3",
"Transition": "AOS5X",
"TrackCode": "TF",
"WptID": 42141,
"WptLat": 45.79638889,
"WptLon": 7.34583333,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "19000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91614,
"TerminalID": 11909,
"Type": "3",
"Transition": "LAG9X",
"TrackCode": "IF",
"WptID": 253267,
"WptLat": 45.64077778,
"WptLon": 8.73483333,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91615,
"TerminalID": 11909,
"Type": "3",
"Transition": "LAG9X",
"TrackCode": "TF",
"WptID": 42354,
"WptLat": 45.55416667,
"WptLon": 8.44916667,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91616,
"TerminalID": 11909,
"Type": "3",
"Transition": "LAG9X",
"TrackCode": "TF",
"WptID": 42823,
"WptLat": 45.10916667,
"WptLon": 8.22972222,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91617,
"TerminalID": 11909,
"Type": "3",
"Transition": "LAG9X",
"TrackCode": "TF",
"WptID": 42487,
"WptLat": 44.39416667,
"WptLon": 8.49805556,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "11000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91618,
"TerminalID": 11909,
"Type": "3",
"Transition": "NED9X",
"TrackCode": "IF",
"WptID": 253267,
"WptLat": 45.64077778,
"WptLon": 8.73483333,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91619,
"TerminalID": 11909,
"Type": "3",
"Transition": "NED9X",
"TrackCode": "TF",
"WptID": 42354,
"WptLat": 45.55416667,
"WptLon": 8.44916667,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91620,
"TerminalID": 11909,
"Type": "3",
"Transition": "NED9X",
"TrackCode": "TF",
"WptID": 42823,
"WptLat": 45.10916667,
"WptLon": 8.22972222,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91621,
"TerminalID": 11909,
"Type": "3",
"Transition": "NED9X",
"TrackCode": "TF",
"WptID": 42584,
"WptLat": 44.69388889,
"WptLon": 8.14055556,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "20000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91622,
"TerminalID": 11909,
"Type": "3",
"Transition": "TOP9X",
"TrackCode": "IF",
"WptID": 253267,
"WptLat": 45.64077778,
"WptLon": 8.73483333,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91623,
"TerminalID": 11909,
"Type": "3",
"Transition": "TOP9X",
"TrackCode": "TF",
"WptID": 42354,
"WptLat": 45.55416667,
"WptLon": 8.44916667,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91624,
"TerminalID": 11909,
"Type": "3",
"Transition": "TOP9X",
"TrackCode": "TF",
"WptID": 42823,
"WptLat": 45.10916667,
"WptLon": 8.22972222,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 91625,
"TerminalID": 11909,
"Type": "3",
"Transition": "TOP9X",
"TrackCode": "TF",
"WptID": 253309,
"WptLat": 44.92541667,
"WptLon": 7.86166667,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-83
View File
@@ -1,83 +0,0 @@
[
{
"ID": 97919,
"TerminalID": 12765,
"Type": "2",
"Transition": "ALL",
"TrackCode": "CF",
"WptID": 253373,
"WptLat": 46.95531111,
"WptLon": 15.44944444,
"TurnDir": "",
"NavID": 4275,
"NavLat": 46.95531111,
"NavLon": 15.44944444,
"NavBear": 0,
"NavDist": 0,
"Course": 164,
"Distance": 2,
"Alt": "02200A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 97920,
"TerminalID": 12765,
"Type": "2",
"Transition": "ALL",
"TrackCode": "DF",
"WptID": 253373,
"WptLat": 46.95531111,
"WptLon": 15.44944444,
"TurnDir": "L",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "04000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 97921,
"TerminalID": 12765,
"Type": "2",
"Transition": "ALL",
"TrackCode": "HA",
"WptID": 253373,
"WptLat": 46.95531111,
"WptLon": 15.44944444,
"TurnDir": "R",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 344,
"Distance": 4,
"Alt": "04000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-272
View File
@@ -1,272 +0,0 @@
[
{
"ID": 458535,
"TerminalID": 67790,
"Type": "A",
"Transition": "UPV1",
"TrackCode": "IF",
"WptID": 250431,
"WptLat": -28.40175278,
"WptLon": 21.26041111,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "05800A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458536,
"TerminalID": 67790,
"Type": "A",
"Transition": "UPV1",
"TrackCode": "PI",
"WptID": 250431,
"WptLat": -28.40175278,
"WptLon": 21.26041111,
"TurnDir": "L",
"NavID": 1333,
"NavLat": -28.40175278,
"NavLon": 21.26041111,
"NavBear": 0,
"NavDist": 0,
"Course": 213,
"Distance": 10,
"Alt": "04500A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458537,
"TerminalID": 67790,
"Type": "A",
"Transition": "UPV1",
"TrackCode": "CF",
"WptID": 113102,
"WptLat": -28.51632222,
"WptLon": 21.33836389,
"TurnDir": "",
"NavID": 1333,
"NavLat": -28.40175278,
"NavLon": 21.26041111,
"NavBear": 168,
"NavDist": 8,
"Course": 348,
"Distance": 2,
"Alt": "04500A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458538,
"TerminalID": 67790,
"Type": "A",
"Transition": "UPV2",
"TrackCode": "IF",
"WptID": 250431,
"WptLat": -28.40175278,
"WptLon": 21.26041111,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "05800A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458539,
"TerminalID": 67790,
"Type": "A",
"Transition": "UPV2",
"TrackCode": "HF",
"WptID": 250431,
"WptLat": -28.40175278,
"WptLon": 21.26041111,
"TurnDir": "L",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 348,
"Distance": 8,
"Alt": "04500A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458540,
"TerminalID": 67790,
"Type": "D",
"Transition": "",
"TrackCode": "IF",
"WptID": 113102,
"WptLat": -28.51632222,
"WptLon": 21.33836389,
"TurnDir": "",
"NavID": 1333,
"NavLat": -28.40175278,
"NavLon": 21.26041111,
"NavBear": 168,
"NavDist": 8,
"Course": null,
"Distance": null,
"Alt": "04500A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": -1,
"IsMAP": 0
},
{
"ID": 458541,
"TerminalID": 67790,
"Type": "D",
"Transition": "",
"TrackCode": "CF",
"WptID": null,
"WptLat": -28.41938889,
"WptLon": 21.27444444,
"TurnDir": "",
"NavID": 1333,
"NavLat": -28.40175278,
"NavLon": 21.26041111,
"NavBear": 163.9,
"NavDist": 1.3,
"Course": 348,
"Distance": 6.7,
"Alt": "MAP",
"Vnav": 2.45,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": -1
},
{
"ID": 458542,
"TerminalID": 67790,
"Type": "D",
"Transition": "",
"TrackCode": "CF",
"WptID": 250431,
"WptLat": -28.40175278,
"WptLon": 21.26041111,
"TurnDir": "",
"NavID": 1333,
"NavLat": -28.40175278,
"NavLon": 21.26041111,
"NavBear": 0,
"NavDist": 0,
"Course": 348,
"Distance": 1.3,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458543,
"TerminalID": 67790,
"Type": "D",
"Transition": "",
"TrackCode": "CA",
"WptID": null,
"WptLat": null,
"WptLon": null,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 348,
"Distance": null,
"Alt": "05500A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458544,
"TerminalID": 67790,
"Type": "D",
"Transition": "",
"TrackCode": "DF",
"WptID": 250431,
"WptLat": -28.40175278,
"WptLon": 21.26041111,
"TurnDir": "L",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "05800",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
}
]
-353
View File
@@ -1,353 +0,0 @@
[
{
"ID": 458580,
"TerminalID": 67794,
"Type": "A",
"Transition": "FD291",
"TrackCode": "IF",
"WptID": 113125,
"WptLat": -25.65088056,
"WptLon": 28.29919167,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "08000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458581,
"TerminalID": 67794,
"Type": "A",
"Transition": "FD291",
"TrackCode": "HF",
"WptID": 113125,
"WptLat": -25.65088056,
"WptLon": 28.29919167,
"TurnDir": "R",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 284,
"Distance": 5.5,
"Alt": "07000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 140,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458582,
"TerminalID": 67794,
"Type": "A",
"Transition": "FD292",
"TrackCode": "IF",
"WptID": 113125,
"WptLat": -25.65088056,
"WptLon": 28.29919167,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "07000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458583,
"TerminalID": 67794,
"Type": "A",
"Transition": "FD292",
"TrackCode": "TF",
"WptID": 113123,
"WptLat": -25.64774167,
"WptLon": 28.34813056,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "07000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458584,
"TerminalID": 67794,
"Type": "A",
"Transition": "FD292",
"TrackCode": "FC",
"WptID": 113123,
"WptLat": -25.64774167,
"WptLon": 28.34813056,
"TurnDir": "",
"NavID": 1283,
"NavLat": -25.65591667,
"NavLon": 28.220075,
"NavBear": 104,
"NavDist": 7,
"Course": 59,
"Distance": 2.7,
"Alt": "07000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": -1,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458585,
"TerminalID": 67794,
"Type": "A",
"Transition": "FD292",
"TrackCode": "CF",
"WptID": 113123,
"WptLat": -25.64774167,
"WptLon": 28.34813056,
"TurnDir": "R",
"NavID": 1283,
"NavLat": -25.65591667,
"NavLon": 28.220075,
"NavBear": 104,
"NavDist": 7,
"Course": 284,
"Distance": 3,
"Alt": "07000A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458586,
"TerminalID": 67794,
"Type": "A",
"Transition": "FD292",
"TrackCode": "CF",
"WptID": 113125,
"WptLat": -25.65088056,
"WptLon": 28.29919167,
"TurnDir": "",
"NavID": 1283,
"NavLat": -25.65591667,
"NavLon": 28.220075,
"NavBear": 104,
"NavDist": 4.3,
"Course": 284,
"Distance": 2.7,
"Alt": "05300A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458587,
"TerminalID": 67794,
"Type": "D",
"Transition": "",
"TrackCode": "IF",
"WptID": 113125,
"WptLat": -25.65088056,
"WptLon": 28.29919167,
"TurnDir": "",
"NavID": 1283,
"NavLat": -25.65591667,
"NavLon": 28.220075,
"NavBear": 104,
"NavDist": 4.3,
"Course": null,
"Distance": null,
"Alt": "05300A",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": -1,
"IsMAP": 0
},
{
"ID": 458588,
"TerminalID": 67794,
"Type": "D",
"Transition": "",
"TrackCode": "CF",
"WptID": 113127,
"WptLat": -25.65404722,
"WptLon": 28.24951389,
"TurnDir": "",
"NavID": 1283,
"NavLat": -25.65591667,
"NavLon": 28.220075,
"NavBear": 104,
"NavDist": 1.6,
"Course": 284,
"Distance": 2.7,
"Alt": "MAP",
"Vnav": 3,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": -1
},
{
"ID": 458589,
"TerminalID": 67794,
"Type": "D",
"Transition": "",
"TrackCode": "FD",
"WptID": 113127,
"WptLat": -25.65404722,
"WptLon": 28.24951389,
"TurnDir": "",
"NavID": 1283,
"NavLat": -25.65591667,
"NavLon": 28.220075,
"NavBear": 104,
"NavDist": 1.6,
"Course": 284,
"Distance": 2.2,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458590,
"TerminalID": 67794,
"Type": "D",
"Transition": "",
"TrackCode": "DF",
"WptID": 250381,
"WptLat": -25.65591667,
"WptLon": 28.220075,
"TurnDir": "R",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458591,
"TerminalID": 67794,
"Type": "D",
"Transition": "",
"TrackCode": "TF",
"WptID": 113125,
"WptLat": -25.65088056,
"WptLon": 28.29919167,
"TurnDir": "",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": null,
"Distance": null,
"Alt": "07000",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": null,
"IsFAF": 0,
"IsMAP": 0
},
{
"ID": 458592,
"TerminalID": 67794,
"Type": "D",
"Transition": "",
"TrackCode": "HM",
"WptID": 113125,
"WptLat": -25.65088056,
"WptLon": 28.29919167,
"TurnDir": "R",
"NavID": null,
"NavLat": null,
"NavLon": null,
"NavBear": null,
"NavDist": null,
"Course": 284,
"Distance": 4,
"Alt": "",
"Vnav": null,
"CenterID": null,
"CenterLat": null,
"CenterLon": null,
"IsFlyOver": 0,
"SpeedLimit": 140,
"IsFAF": 0,
"IsMAP": 0
}
]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-131
View File
@@ -1,131 +0,0 @@
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";
const parser = await Parser.instance();
const terminals = [10394, 10395, 10475, 10480, 10482, 10485, 10653];
function App() {
const [selectedTerminal, setSelectedTerminal] = useState(terminals[0]);
const [procedure, setProcedure] = useState<object>();
const mapRef = createRef<Leaflet.Map>();
const layerRef = createRef<Leaflet.GeoJSON>();
useEffect(() => {
(async () => {
setProcedure(await parser.parse(selectedTerminal));
})();
}, [selectedTerminal]);
useEffect(() => {
if (layerRef.current && mapRef.current) {
mapRef.current.flyToBounds(layerRef.current.getBounds(), {
animate: false,
padding: [50, 50],
});
}
});
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='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<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 });
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
style={{
padding: "5px",
display: "flex",
flexDirection: "column",
gap: "10px",
}}
>
{terminals.map((terminal) => (
<div
key={terminal}
style={{
display: "flex",
gap: "10px",
background: selectedTerminal === terminal ? "#eeeeee" : undefined,
}}
>
<pre>ID {terminal}</pre>
<button onClick={() => setSelectedTerminal(terminal)}>
Select
</button>
</div>
))}
</div>
</div>
);
}
export default App;
-8
View File
@@ -1,8 +0,0 @@
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
}
-12
View File
@@ -1,12 +0,0 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import "leaflet/dist/leaflet.css";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);
-17
View File
@@ -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(10480)));
-278
View File
@@ -1,278 +0,0 @@
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";
class Parser {
private static _instance: Parser;
private waypoints: Waypoint[];
private runways: Runway[];
private terminals: Terminal[];
public static AC_SPEED = 250;
public static AC_VS = 1500;
private constructor(
waypoints: Waypoint[],
runways: Runway[],
terminals: Terminal[]
) {
this.waypoints = waypoints;
this.runways = runways;
this.terminals = terminals;
}
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();
Parser._instance = new Parser(waypoints, runways, terminals);
}
return Parser._instance;
};
public parse = async (terminalID: number) => {
// Private functions
/**
* @param line Line segments
*/
const updateLastCourse = (line: LineSegment[]) => {
lastCourse = geolib.getGreatCircleBearing(
{
latitude: line.at(-2)![1],
longitude: line.at(-2)![0],
},
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
}
);
};
// 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
const runway = this.runways.find(({ ID }) => ID === terminal.RwyID);
if (!runway) throw new Error("Procedure links to non existent Runway");
// Load procedure
const procedure = (await (
await fetch(`NavData/TermID_${terminalID}.json`)
).json()) as TerminalEntry[];
// Output variables
const navFixes: NavFix[] = [];
const lineSegments: { line: LineSegment[]; [x: string]: unknown }[] = [];
// Initials
navFixes.push({
latitude: runway.Latitude,
longitude: runway.Longitude,
altitude: runway.Elevation,
speed: 0,
name: runway.Ident,
});
let lastCourse = runway.TrueHeading;
// 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
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "CA":
case "CD":
console.error("Unknown TrackCode", leg.TrackCode);
break;
case "CF": {
const [fixToAdd, lineToAdd] = TerminatorsCF(
leg as CFTerminalEntry,
previousFix,
lastCourse,
waypoint
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "CI": {
const [fixToAdd, lineToAdd] = TerminatorsCI(
leg as CITerminalEntry,
procedure[index + 1],
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "CR": {
const [fixToAdd, lineToAdd] = TerminatorsCR(
leg as CRTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "DF":
case "FA":
case "FC":
case "FD":
console.error("Unknown TrackCode", leg.TrackCode);
break;
case "FM": {
const [fixToAdd, lineToAdd] = TerminatorsFM(
leg as FMTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd, isManual: true });
updateLastCourse(lineToAdd);
}
break;
}
case "HA":
case "HF":
case "HM":
case "IF":
case "PI":
console.error("Unknown TrackCode", leg.TrackCode);
break;
case "RF": {
const [fixToAdd, lineToAdd] = TerminatorsRF(
leg as RFTerminalEntry,
procedure[index + 1],
previousFix,
lastCourse,
waypoint
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "TF": {
const [fixToAdd, lineToAdd] = TerminatorsTF(
leg as TFTerminalEntry,
previousFix,
lastCourse,
waypoint
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VA": {
const [fixToAdd, lineToAdd] = TerminatorsVA(
leg as VATerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VD": {
const [fixToAdd, lineToAdd] = TerminatorsVD(
leg as VDTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VI": {
const [fixToAdd, lineToAdd] = TerminatorsVI(
leg as VITerminalEntry,
procedure[index + 1],
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
updateLastCourse(lineToAdd);
}
break;
}
case "VM": {
const [fixToAdd, lineToAdd] = TerminatorsVM(
leg as VMTerminalEntry,
previousFix,
lastCourse
);
if (fixToAdd) navFixes.push(fixToAdd);
if (lineToAdd) {
lineSegments.push({ line: lineToAdd, isManual: true });
updateLastCourse(lineToAdd);
}
break;
}
case "VR":
default:
console.error("Unknown TrackCode", leg.TrackCode);
break;
}
}
return geojson.parse([...navFixes, ...lineSegments], {
LineString: "line",
Point: ["latitude", "longitude"],
});
};
}
export default Parser;
@@ -1,54 +0,0 @@
import * as geolib from "geolib";
import Parser from "../parser.ts";
/**
* @param crsIntoEndpoint Course into arc endpoint
* @param crsFromOrigin Course from arc origin point
* @param start Arc origin point
* @param turnDir Turn direction
* @returns Line segments
*/
export const generatePerformanceArc = (
crsIntoEndpoint: number,
crsFromOrigin: number,
start: NavFix,
turnDir?: TurnDirection
) => {
const line: LineSegment[] = [[start.longitude, start.latitude]];
// Check if there even is an arc
if (crsIntoEndpoint !== crsFromOrigin) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = crsFromOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
// Generate arc
while (crsFromOrigin !== crsIntoEndpoint) {
if (turnDir === "R") {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
crsFromOrigin += delta < 1 ? delta : 1;
crsFromOrigin = crsFromOrigin.normaliseDegrees();
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
crsFromOrigin -= delta < 1 ? delta : 1;
crsFromOrigin = crsFromOrigin.normaliseDegrees();
}
const arcFix = geolib.computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
(Parser.AC_SPEED / 3600).toMetre(),
crsFromOrigin
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
@@ -1,77 +0,0 @@
import * as geolib from "geolib";
/**
* @param crsIntoEndpoint Course into arc endpoint
* @param crsIntoOrigin Course into arc origin point
* @param start Arc origin point
* @param center Arc center point
* @param turnDir
* @returns Line segments
*/
export const generateRFArc = (
crsIntoEndpoint: number,
crsIntoOrigin: number,
start: NavFix,
center: NavFix,
turnDir: TurnDirection
) => {
const line: LineSegment[] = [[start.longitude, start.latitude]];
if (crsIntoEndpoint !== crsIntoOrigin) {
// Turn Dir
if (!turnDir || turnDir === "E") {
let prov = crsIntoOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R";
}
let crsOrthogonalOnOrigin;
let crsOrthogonalOnEndpoint;
if (turnDir === "R") {
crsOrthogonalOnOrigin = (crsIntoOrigin + 90).normaliseDegrees();
crsOrthogonalOnEndpoint = (crsIntoEndpoint + 90).normaliseDegrees();
} else {
crsOrthogonalOnOrigin = (crsIntoOrigin - 90).normaliseDegrees();
crsOrthogonalOnEndpoint = (crsIntoEndpoint - 90).normaliseDegrees();
}
const arcRad = geolib.getDistance(center, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
// Start turn immediately
if (turnDir === "R") {
crsOrthogonalOnOrigin +=
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
} else {
crsOrthogonalOnOrigin -=
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
}
while (crsOrthogonalOnOrigin !== crsOrthogonalOnEndpoint) {
if (turnDir === "R") {
const delta = (
crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin
).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else {
const delta = (
crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint
).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
}
const arcFix = geolib.computeDestinationPoint(
center,
arcRad,
crsOrthogonalOnOrigin
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
-47
View File
@@ -1,47 +0,0 @@
import * as geolib from "geolib";
import { generateAFArc } from "../pathGenerators/generateAFArc.ts";
import Parser from "../parser.ts";
export const TerminatorsAF = (
leg: AFTerminalEntry,
previousFix: NavFix,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const arcEndCrs = geolib.getGreatCircleBearing(
{
latitude: leg.NavLat,
longitude: leg.NavLon,
},
{
latitude: leg.WptLat,
longitude: leg.WptLon,
}
);
const line = generateAFArc(
arcEndCrs,
leg.Course.toTrue({ latitude: leg.NavLat, longitude: leg.NavLon }),
previousFix,
{ latitude: leg.NavLat, longitude: leg.NavLon },
leg.NavDist,
leg.TurnDir
);
return [targetFix, line];
};
-37
View File
@@ -1,37 +0,0 @@
import Parser from "../parser.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
export const TerminatorsCF = (
leg: CFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
targetFix,
leg.TurnDir
);
return [targetFix, line];
};
-61
View File
@@ -1,61 +0,0 @@
import Parser from "../parser.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import { computeIntersection } from "../utils/computeIntersection.ts";
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
export const TerminatorsCI = (
leg: CITerminalEntry,
nextLeg: TerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
// Compute INTC
const interceptFix: NavFix = {
...computeIntersection(
previousFix,
leg.Course.toTrue(nextFix),
nextFix,
crs
)!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
crs,
leg.Course.toTrue(nextFix),
lastCourse,
previousFix,
interceptFix,
leg.TurnDir
);
// Intercept based on previous intercept
const interceptPoint2 = computeIntersection(
{ latitude: line.at(-2)![1], longitude: line.at(-2)![0] },
leg.Course.toTrue(nextFix),
nextFix,
crs
);
if (interceptPoint2)
return [
{ ...interceptFix, ...interceptPoint2 },
[
...line.slice(0, -1),
[interceptPoint2.longitude, interceptPoint2.latitude],
],
];
return [interceptFix, line];
};
-42
View File
@@ -1,42 +0,0 @@
import { computeIntersection } from "../utils/computeIntersection.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
export const TerminatorsCR = (
leg: CRTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const interceptFix: NavFix = {
...computeIntersection(
previousFix,
crsIntoEndpoint,
{ latitude: leg.NavLat, longitude: leg.NavLon },
leg.NavBear.toTrue({ latitude: leg.NavLat, longitude: leg.NavLon })
)!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
crsIntoEndpoint,
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
interceptFix,
leg.TurnDir
);
return [interceptFix, line];
};
-25
View File
@@ -1,25 +0,0 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
export const TerminatorsFM = (
leg: FMTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const endpoint = geolib.computeDestinationPoint(
previousFix,
(10).toMetre(),
leg.Course.toTrue(previousFix)
);
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
endpoint,
leg.TurnDir
);
return [undefined, line];
};
-39
View File
@@ -1,39 +0,0 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
export const TerminatorsTF = (
leg: TFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const crs = geolib.getGreatCircleBearing(previousFix, targetFix);
const line = handleTurnAtFix(
crs,
crs,
lastCourse,
previousFix,
targetFix,
leg.TurnDir
);
return [targetFix, line];
};
-45
View File
@@ -1,45 +0,0 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVA = (
leg: VATerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
...geolib.computeDestinationPoint(
previousFix,
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) /
Parser.AC_VS) *
(Parser.AC_SPEED / 60)
).toMetre(),
leg.Course.toTrue(previousFix)
),
name: leg.Alt,
isFlyOver: true,
altitude: leg.Alt.parseAltitude(),
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
targetFix,
leg.TurnDir
);
return [targetFix, line];
};
-41
View File
@@ -1,41 +0,0 @@
import * as geolib from "geolib";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import Parser from "../parser.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVD = (
leg: VDTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const targetFix: NavFix = {
...geolib.computeDestinationPoint(
previousFix,
leg.Distance.toMetre(),
leg.Course.toTrue(previousFix)
),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
targetFix,
leg.TurnDir
);
return [targetFix, line];
};
-62
View File
@@ -1,62 +0,0 @@
import Parser from "../parser.ts";
import { handleTurnAtFix } from "../pathGenerators/handleTurnAtFix.ts";
import { computeIntersection } from "../utils/computeIntersection.ts";
import { getCourseAndFixForIntercepts } from "../utils/getCourseAndFixForIntercepts.ts";
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVI = (
leg: VITerminalEntry,
nextLeg: TerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const [crs, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
// Compute INTC
const interceptFix: NavFix = {
...computeIntersection(
previousFix,
leg.Course.toTrue(nextFix),
nextFix,
crs
)!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: leg.SpeedLimit
? leg.SpeedLimit > Parser.AC_SPEED
? Parser.AC_SPEED
: leg.SpeedLimit
: previousFix.speed
? previousFix.speed
: Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line = handleTurnAtFix(
crs,
leg.Course.toTrue(nextFix),
lastCourse,
previousFix,
interceptFix,
leg.TurnDir
);
// Intercept based on previous intercept
const interceptPoint2 = computeIntersection(
{ latitude: line.at(-2)![1], longitude: line.at(-2)![0] },
leg.Course.toTrue(nextFix),
nextFix,
crs
);
if (interceptPoint2)
return [
{ ...interceptFix, ...interceptPoint2 },
[
...line.slice(0, -1),
[interceptPoint2.longitude, interceptPoint2.latitude],
],
];
return [interceptFix, line];
};
-31
View File
@@ -1,31 +0,0 @@
import { magvar } from "magvar";
Number.prototype.toRadians = function () {
return ((this as number) * Math.PI) / 180;
};
Number.prototype.toDegrees = function () {
return ((this as number) * 180) / Math.PI;
};
Number.prototype.reciprocalCourse = function () {
let inv = (this as number) + 180;
inv = inv >= 360 ? inv - 360 : inv;
return inv;
};
Number.prototype.normaliseDegrees = function () {
return (this as number) >= 360
? (this as number) - 360
: (this as number) < 0
? (this as number) + 360
: (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();
};
Number.prototype.toMetre = function () {
return (this as number) * 1852.0;
};
String.prototype.parseAltitude = function () {
return Number.parseInt(this.substring(0, 5));
};
@@ -1,42 +0,0 @@
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] => {
switch (leg.TrackCode) {
case "CF": {
const _leg = leg as CFTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
}
case "FM": {
const _leg = leg as FMTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.toTrue(fix), fix];
}
case "TF": {
const _leg = leg as FMTerminalEntry;
return [
geolib.getGreatCircleBearing(origin, {
latitude: _leg.WptLat,
longitude: _leg.WptLon,
}),
{ latitude: _leg.WptLat, longitude: _leg.WptLon },
];
}
case "AF": {
const _leg = leg as AFTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
}
default: {
return [-1, origin];
}
}
};
-24
View File
@@ -1,24 +0,0 @@
//eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as L from "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"
| string;
radius?: number;
rotation?: number;
}
): Path;
}
-18
View File
@@ -1,18 +0,0 @@
export declare global {
type AFTerminalEntry = Required<
Pick<
TerminalEntry,
| "WptID"
| "WptLat"
| "WptLon"
| "TurnDir"
| "NavID"
| "NavLat"
| "NavLon"
| "NavBear"
| "NavDist"
| "Course"
>
> &
TerminalEntry;
}
-18
View File
@@ -1,18 +0,0 @@
export declare global {
type CFTerminalEntry = Required<
Pick<
TerminalEntry,
| "WptID"
| "WptLat"
| "WptLon"
| "NavID"
| "NavLat"
| "NavLon"
| "NavBear"
| "NavDist"
| "Course"
| "Distance"
>
> &
TerminalEntry;
}
-4
View File
@@ -1,4 +0,0 @@
export declare global {
type CITerminalEntry = Required<Pick<TerminalEntry, "Course">> &
TerminalEntry;
}
-6
View File
@@ -1,6 +0,0 @@
export declare global {
type CRTerminalEntry = Required<
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "NavBear" | "Course">
> &
TerminalEntry;
}
-17
View File
@@ -1,17 +0,0 @@
export declare global {
type FMTerminalEntry = Required<
Pick<
TerminalEntry,
| "WptID"
| "WptLat"
| "WptLon"
| "NavID"
| "NavLat"
| "NavLon"
| "NavBear"
| "NavDist"
| "Course"
>
> &
TerminalEntry;
}
-18
View File
@@ -1,18 +0,0 @@
export declare global {
type RFTerminalEntry = Required<
Pick<
TerminalEntry,
| "WptID"
| "WptLat"
| "WptLon"
| "TurnDir"
| "NavBear"
| "Course"
| "Distance"
| "CenterID"
| "CenterLat"
| "CenterLon"
>
> &
TerminalEntry;
}
-6
View File
@@ -1,6 +0,0 @@
export declare global {
type TFTerminalEntry = Required<
Pick<TerminalEntry, "WptID" | "WptLat" | "WptLon" | "IsFlyOver">
> &
TerminalEntry;
}
-4
View File
@@ -1,4 +0,0 @@
export declare global {
type VATerminalEntry = Required<Pick<TerminalEntry, "Course" | "Alt">> &
TerminalEntry;
}
-6
View File
@@ -1,6 +0,0 @@
export declare global {
type VDTerminalEntry = Required<
Pick<TerminalEntry, "NavID" | "NavLat" | "NavLon" | "Course" | "Distance">
> &
TerminalEntry;
}
-4
View File
@@ -1,4 +0,0 @@
export declare global {
type VITerminalEntry = Required<Pick<TerminalEntry, "Course">> &
TerminalEntry;
}
-4
View File
@@ -1,4 +0,0 @@
export declare global {
type VMTerminalEntry = Required<Pick<TerminalEntry, "Course">> &
TerminalEntry;
}
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
-7
View File
@@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
+5
View File
@@ -0,0 +1,5 @@
node_modules
.git
.gitignore
*.md
dist
+1
View File
@@ -0,0 +1 @@
v22
+3
View File
@@ -0,0 +1,3 @@
public
pnpm-lock.yaml
node_modules
+9
View File
@@ -0,0 +1,9 @@
{
"printWidth": 120,
"tabWidth": 2,
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"arrowParens": "always",
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
}
+19
View File
@@ -0,0 +1,19 @@
FROM node:lts-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
COPY . /app
WORKDIR /app
FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build
FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 3003
ENTRYPOINT [ "pnpm", "serve" ]
+27
View File
@@ -0,0 +1,27 @@
networks:
traefik:
name: traefik
services:
md11-nav-data:
build: .
image: md11-nav-data
container_name: md11-nav-data-server
volumes:
- /Volumes/Data/DockerMD11NavData/NavData:/app/dist/NavData
environment:
PORT: 3003
env_file: ".env"
restart: always
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.md11-nav-data-router.rule=Host(`md11-nav-data.hofmannnet.myhome-server.de`)"
- "traefik.http.routers.md11-nav-data-router.service=md11-nav-data-service"
- "traefik.http.services.md11-nav-data-service.loadbalancer.server.port=3003"
- "traefik.http.routers.md11-nav-data-router.entrypoints=websecure"
- "traefik.http.routers.md11-nav-data-router.tls=true"
- "traefik.http.routers.md11-nav-data-router.tls.certresolver=letsencrypt"
- "traefik.http.routers.md11-nav-data-router.middlewares=md11-nav-data-auth"
- "traefik.http.middlewares.md11-nav-data-auth.basicauth.users=tfdi:$$apr1$$gN9W1XPy$$hucbFbJGcMv/GaUgiZ/mX."
+32
View File
@@ -0,0 +1,32 @@
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']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
'@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
destructuredArrayIgnorePattern: '^_+$',
},
],
},
},
]);
@@ -2,8 +2,10 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <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" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="/src/style.css" rel="stylesheet">
<title>MD-11 NavData Browser</title> <title>MD-11 NavData Browser</title>
</head> </head>
<body> <body>
@@ -4,26 +4,29 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite --port 3000 --host",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint . --ext .ts,.tsx --fix",
"preview": "vite preview", "preview": "vite preview",
"parser": "node --experimental-strip-types src/parser/node.ts" "serve": "pnpm npx http-server dist"
}, },
"dependencies": { "dependencies": {
"browser-image-manipulation": "^0.4.0",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"geolib": "^3.3.4", "geolib": "^3.3.4",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"leaflet-svg-shape-markers": "^1.4.0", "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": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-leaflet": "^5.0.0" "react-leaflet": "^5.0.0",
"tailwindcss": "^4.1.11"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.31.0", "@eslint/js": "^9.31.0",
"@tailwindcss/vite": "^4.1.11",
"@types/leaflet": "^1.9.20", "@types/leaflet": "^1.9.20",
"@types/node": "^24.0.13",
"@types/object-hash": "^3.0.6", "@types/object-hash": "^3.0.6",
"@types/react": "^19.1.8", "@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.6",
@@ -33,8 +36,11 @@
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0", "globals": "^16.3.0",
"object-hash": "^3.0.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": "~5.8.3",
"typescript-eslint": "^8.36.0", "typescript-eslint": "^8.37.0",
"vite": "^7.0.4" "vite": "^7.0.4"
} }
} }
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
Copy over contents of `Data/Primary` retaining the structure.
Binary file not shown.
+138
View File
@@ -0,0 +1,138 @@
import type { DeviceFlowParams } from 'navigraph/auth';
import { QRCodeSVG } from 'qrcode.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';
function App() {
const [parser, setParser] = useState<Parser>();
const [selectedAirport, setSelectedAirport] = useState<Airport>();
const [selectedRunway, setSelectedRunway] = useState<Runway>();
const [selectedTerminal, setSelectedTerminal] = useState<Terminal>();
const [transitions, setTransitions] = useState<Procedure[]>([]);
const [params, setParams] = useState<DeviceFlowParams | null>(null);
const [selectedTransition, setSelectedTransition] = useState<Procedure>();
const [selectedChart, setSelectedChart] = useState<Chart>();
const [skippedNG, setSkippedNG] = useState(false);
const { user, signIn, initialized } = useNavigraphAuth();
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 && !skippedNG ? (
<div className="flex min-h-dvh w-full">
{initialized && !params && (
<div className="flex h-dvh w-dvw flex-col items-center justify-center gap-2">
<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"
onClick={handleSignIn}
>
Sign in to NG
</button>
<button
className="cursor-pointer rounded border border-gray-600 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
onClick={() => setSkippedNG(true)}
>
Skip NG
</button>
</div>
)}
{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} />
<div className="flex w-100 items-center text-center before:mr-1 before:flex-1 before:border-b-1 before:content-[''] after:ml-1 after:flex-1 after:border-b-1 after:content-['']">
or
</div>
<a className="text-3xl" href={params.verification_uri_complete} target="_blank" rel="noreferrer">
Open sign in page
</a>
</div>
)}
</div>
) : (
<div className="flex h-dvh w-dvw">
{selectedAirport && selectedRunway && selectedTerminal ? (
<Sidebar
airport={selectedAirport}
runway={selectedRunway}
terminal={selectedTerminal}
transitions={transitions}
transition={selectedTransition}
chart={selectedChart}
skippedNG={skippedNG}
setTransition={setSelectedTransition}
setChart={setSelectedChart}
backAction={() => {
setSelectedTerminal(undefined);
setSelectedChart(undefined);
setTransitions([]);
setSelectedTransition(undefined);
}}
/>
) : (
<ProcedureSelect
parser={parser}
selectedAirport={selectedAirport}
selectedRunway={selectedRunway}
selectedTerminal={selectedTerminal}
setSelectedAirport={setSelectedAirport}
setSelectedRunway={setSelectedRunway}
setSelectedTerminal={setSelectedTerminal}
handleSelection={(selectedTransitions, _terminal) => {
let _transitions = selectedTransitions
.map((transition) => ({
name: transition,
data: parser.parse(selectedRunway!, _terminal, transition),
}))
.filter(
(transition) =>
!transition.name.startsWith('RW') || transition.name === `RW${selectedRunway?.Ident}`
);
if (_transitions.length > 1)
_transitions = _transitions.filter((transition) => transition.name !== 'ALL');
setTransitions(_transitions);
setSelectedTransition(_transitions[0]);
setSelectedChart(undefined);
}}
/>
)}
<Map
airport={selectedAirport}
chart={selectedChart}
procedures={selectedTransition ? [selectedTransition] : []}
/>
</div>
)}
</>
);
}
export default App;
+22
View File
@@ -0,0 +1,22 @@
import type { FC } from 'react';
interface LoaderProps {
size?: 0.5 | 1 | 2 | 3;
}
export const Loader: FC<LoaderProps> = ({ size = 2 }) => {
const variants = {
0.5: 'size-6 border-1 after:top-1 after:left-0.5 after:size-1.5 after:border-1',
1: 'size-12 border-2 after:top-2 after:left-1 after:size-3 after:border-2',
2: 'size-24 border-4 after:top-3 after:left-2 after:size-6 after:border-4',
3: 'size-36 border-6 after:top-4 after:left-3 after:size-9 after:border-6',
};
return (
<div className="flex justify-center">
<span
className={`relative inline-block ${variants[size]} animate-spin rounded-[50%] border-red-500 after:absolute after:rounded-[50%] after:border-black after:content-['']`}
></span>
</div>
);
};
+124
View File
@@ -0,0 +1,124 @@
import { default as L } from 'leaflet';
import 'leaflet-svg-shape-markers';
import { createRef, Fragment, useEffect, type FC } from 'react';
import { GeoJSON, ImageOverlay, MapContainer, TileLayer } from 'react-leaflet';
interface MapProps {
airport: Airport | undefined;
chart: Chart | undefined;
procedures: Procedure[];
}
export const Map: FC<MapProps> = ({ airport, chart, procedures }) => {
const mapRef = createRef<L.Map>();
const imageRef = createRef<L.ImageOverlay>();
useEffect(() => {
if (airport) mapRef.current?.flyTo([airport?.Latitude, airport?.Longitude], 10, { animate: false });
}, [airport]);
return (
<MapContainer
center={[0, 0]}
zoom={5}
zoomSnap={0}
className="h-full w-full"
ref={(_mapRef) => {
_mapRef?.attributionControl.setPosition('topright');
_mapRef?.zoomControl.setPosition('topright');
mapRef.current = _mapRef;
}}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{chart && chart.bounds && (
<ImageOverlay
url={chart.data}
bounds={chart.bounds}
opacity={0.75}
ref={(_imageRef) => {
if (_imageRef) {
mapRef.current?.fitBounds(_imageRef.getBounds(), {
padding: [-50, -50],
});
}
imageRef.current = _imageRef;
}}
/>
)}
{procedures.map((procedure) => (
<Fragment key={procedure.name}>
<GeoJSON
key={`${procedure.name}-lines`}
data={procedure.data}
style={({ properties }) => ({
color: properties.isMAP ? '#00ffff' : '#ff00ff',
stroke: true,
weight: properties.isMAP ? 2.5 : 5,
opacity: 1,
dashArray: properties.isManual ? '20, 20' : undefined,
})}
filter={(feature) => feature.geometry.type !== 'Point'}
/>
<GeoJSON
key={`${procedure.name}-points`}
data={procedure.data}
style={{
color: 'black',
fill: true,
fillColor: 'transparent',
stroke: true,
weight: 3,
}}
pointToLayer={({ properties }, latlng) => {
if (properties.isIntersection) return L.circleMarker(latlng, { radius: 6 });
else if (properties.IsMAP)
return L.shapeMarker(latlng, {
shape: 'square',
radius: 6,
});
else if (properties.IsFAF)
return L.shapeMarker(latlng, {
shape: 'diamond',
radius: 6,
});
else if (properties.isFlyOver)
return L.shapeMarker(latlng, {
shape: 'triangle',
radius: 6,
});
else if (properties.isInitial)
return L.shapeMarker(latlng, {
shape: 'arrowhead',
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'}
/>
</Fragment>
))}
</MapContainer>
);
};
@@ -0,0 +1,194 @@
import { createRef, useMemo, useState, type Dispatch, type FC, type SetStateAction } from 'react';
import Parser from '../parser/parser';
import { Loader } from './Loader';
interface ProcedureSelectProps {
parser: Parser;
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[], terminal: Terminal) => void;
}
export const ProcedureSelect: FC<ProcedureSelectProps> = ({
parser,
selectedAirport,
selectedRunway,
selectedTerminal,
setSelectedAirport,
setSelectedRunway,
setSelectedTerminal,
handleSelection,
}) => {
const inputRef = createRef<HTMLInputElement>();
const [error, setError] = useState<string>();
const [inFlight, setInFlight] = useState<number>();
const runways = useMemo(
() => parser.runways.filter(({ AirportID }) => AirportID === selectedAirport?.ID),
[selectedAirport]
);
const terminals = useMemo(() => {
const _terminals = parser.terminals.filter(
({ AirportID, RwyID }) => AirportID === selectedAirport?.ID && (!RwyID || RwyID === selectedRunway?.ID)
);
return {
star: _terminals.filter((terminal) => terminal.Proc === 1),
sid: _terminals.filter((terminal) => terminal.Proc === 2),
iap: _terminals.filter((terminal) => terminal.Proc === 3),
};
}, [selectedAirport, selectedRunway]);
return (
<div className="flex h-full w-[300px] shrink-0 flex-col overflow-hidden">
{selectedAirport && (
<button
className="sticky top-0 m-2 cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
onClick={() => {
setError(undefined);
setInFlight(undefined);
if (selectedTerminal) {
setSelectedTerminal(undefined);
} else if (selectedRunway) {
setSelectedRunway(undefined);
} else if (selectedAirport) {
setSelectedAirport(undefined);
}
}}
>
Go back
</button>
)}
<div className="flex h-[calc(100%-50px)] flex-col gap-2 overflow-y-auto px-2 pb-2">
{!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>
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Arrivals</div>
{terminals.star.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 disabled:bg-gray-100"
onClick={() => {
setInFlight(terminal.ID);
parser.loadTerminal(terminal.ID).then(() => {
setSelectedTerminal(terminal);
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
handleSelection(Array.from(transitions), terminal);
setInFlight(undefined);
});
}}
disabled={!!inFlight}
>
<div className="flex justify-between">
{terminal.FullName}
{inFlight === terminal.ID && <Loader size={0.5} />}
</div>
</button>
))}
</div>
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Departures</div>
{terminals.sid.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 disabled:bg-gray-100"
onClick={() => {
setInFlight(terminal.ID);
parser.loadTerminal(terminal.ID).then(() => {
setSelectedTerminal(terminal);
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
handleSelection(Array.from(transitions), terminal);
setInFlight(undefined);
});
}}
disabled={!!inFlight}
>
<div className="flex justify-between">
{terminal.FullName}
{inFlight === terminal.ID && <Loader size={0.5} />}
</div>
</button>
))}
</div>
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Approaches</div>
{terminals.iap.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 disabled:bg-gray-100"
onClick={() => {
setInFlight(terminal.ID);
parser.loadTerminal(terminal.ID).then(() => {
setSelectedTerminal(terminal);
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
handleSelection(Array.from(transitions), terminal);
setInFlight(undefined);
});
}}
disabled={!!inFlight}
>
<div className="flex justify-between">
{terminal.FullName}
{inFlight === terminal.ID && <Loader size={0.5} />}
</div>
</button>
))}
</div>
</div>
)}
</div>
</div>
);
};
+201
View File
@@ -0,0 +1,201 @@
import BrowserImageManipulation from 'browser-image-manipulation';
import L from 'leaflet';
import type { Chart as NGChart } from 'navigraph/charts';
import { useCallback, useEffect, useState, type Dispatch, type FC, type SetStateAction } from 'react';
import { charts } from '../lib/navigraph';
import { Loader } from './Loader';
interface SidebarProps {
airport: Airport;
runway: Runway;
terminal: Terminal;
transitions: Procedure[];
transition: Procedure | undefined;
chart: Chart | undefined;
skippedNG?: boolean;
setTransition: Dispatch<SetStateAction<SidebarProps['transition']>>;
setChart: Dispatch<SetStateAction<SidebarProps['chart']>>;
backAction: () => void;
}
export const Sidebar: FC<SidebarProps> = ({
airport,
runway,
terminal,
transitions,
transition,
chart,
skippedNG,
setTransition,
setChart,
backAction,
}) => {
const [inFlight, setInFlight] = useState(false);
const [chartIndex, setChartIndex] = useState<{
sid: NGChart[];
star: NGChart[];
iap: NGChart[];
}>({ star: [], sid: [], iap: [] });
useEffect(() => {
if (skippedNG) return;
setInFlight(true);
(async () => {
const _chartIndex = await charts.getChartsIndex({ icao: airport.ICAO, version: 'STD' });
const newChartIndex = {
star:
_chartIndex?.filter(
(_chart) =>
_chart.category === 'ARR' && (_chart.runways.length === 0 || _chart.runways.includes(runway.Ident))
) ?? [],
sid:
_chartIndex?.filter(
(_chart) =>
_chart.category === 'DEP' && (_chart.runways.length === 0 || _chart.runways.includes(runway.Ident))
) ?? [],
iap:
_chartIndex?.filter(
(_chart) =>
_chart.category === 'APP' && (_chart.runways.length === 0 || _chart.runways.includes(runway.Ident))
) ?? [],
};
_chartIndex?.filter((_chart) => ['ARR', 'DEP', 'APP'].includes(_chart.category));
setChartIndex(newChartIndex);
setInFlight(false);
})();
}, [airport.ICAO]);
useEffect(() => {
if (terminal) {
const star = chartIndex.star.find((_chart) => _chart.procedures.includes(terminal.FullName));
const sid = chartIndex.sid.find((_chart) => _chart.procedures.includes(terminal.FullName));
const iap = chartIndex.iap.find((_chart) => _chart.procedures.includes(terminal.FullName));
if (star) findChart(star);
if (sid) findChart(sid);
if (iap) findChart(iap);
}
}, [terminal, chartIndex]);
const findChart = useCallback(
async (_chart: NGChart) => {
if (chart?.index_number === _chart.index_number) return;
if (!_chart.bounding_boxes) return;
const planView = _chart.bounding_boxes.planview;
const chartImage = await charts.getChartImage({ chart: _chart, theme: 'light' });
if (!chartImage) return;
// Crop
const dataURL = await new BrowserImageManipulation()
.loadBlob(chartImage)
.crop(
planView.pixels.x2 - planView.pixels.x1,
planView.pixels.y1 - planView.pixels.y2,
planView.pixels.x1,
planView.pixels.y2
)
.saveAsImage();
const bounds = new L.LatLngBounds(
[planView.latlng.lat1, planView.latlng.lng1],
[planView.latlng.lat2, planView.latlng.lng2]
);
setChart({ data: dataURL, index_number: _chart.index_number, bounds });
},
[chart]
);
return (
<div className="flex h-full w-[300px] shrink-0 flex-col overflow-hidden">
<button
className="sticky top-0 m-2 cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
onClick={backAction}
>
Go back
</button>
<div className="flex h-[calc(100%-50px)] flex-col gap-2 overflow-y-auto px-2 pb-2">
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">
Transitions for <span className="font-bold">{terminal.FullName}</span>
</div>
{transitions.map((_procedure) => (
<button
key={_procedure.name}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${_procedure.name === transition?.name ? 'outline-2' : ''}`}
onClick={() => {
if (_procedure.name === transition?.name) return;
setTransition(_procedure);
}}
>
{_procedure.name ? _procedure.name : 'NONE'}
</button>
))}
</div>
{!skippedNG && (
<div className="flex flex-col gap-2">
<div className="sticky top-0 z-10 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Charts</div>
{inFlight && <Loader size={3} />}
<div className="flex flex-col gap-2">
<div className="sticky top-7 -mx-2 -mt-2 bg-gray-500 px-2 text-lg font-semibold text-white">Arrivals</div>
{chartIndex.star
.filter((_chart) => _chart.is_georeferenced)
.map((_chart) => (
<button
key={_chart.index_number}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 text-left font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${chart?.index_number === _chart.index_number ? 'outline-2' : ''}`}
onClick={() => findChart(_chart)}
>
<span className="font-bold">{_chart.index_number}</span>
<br />
{_chart.name}
</button>
))}
</div>
<div className="flex flex-col gap-2">
<div className="sticky top-7 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Departures</div>
{chartIndex.sid
.filter((_chart) => _chart.is_georeferenced)
.map((_chart) => (
<button
key={_chart.index_number}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 text-left font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${chart?.index_number === _chart.index_number ? 'outline-2' : ''}`}
onClick={() => findChart(_chart)}
>
<span className="font-bold">{_chart.index_number}</span>
<br />
{_chart.name}
</button>
))}
</div>
<div className="flex flex-col gap-2">
<div className="sticky top-7 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Approaches</div>
{chartIndex.iap
.filter((_chart) => _chart.is_georeferenced)
.map((_chart) => (
<button
key={_chart.index_number}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 text-left font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${chart?.index_number === _chart.index_number ? 'outline-2' : ''}`}
onClick={() => findChart(_chart)}
>
<span className="font-bold">{_chart.index_number}</span>
<br />
{_chart.name}
</button>
))}
</div>
</div>
)}
</div>
</div>
);
};
@@ -0,0 +1,19 @@
import { type User } from 'navigraph/auth';
import { createContext, useContext } from 'react';
import { auth } from '../../lib/navigraph';
interface NavigraphAuthContext {
initialized: boolean;
user: User | null;
signIn: typeof auth.signInWithDeviceFlow;
}
export const authContext = createContext<NavigraphAuthContext>({
initialized: false,
user: null,
signIn: () => Promise.reject('Not initialized'),
});
export const useNavigraphAuth = () => {
return useContext(authContext);
};
@@ -0,0 +1,37 @@
import type { User } from 'navigraph/auth';
import { useEffect, useState } from 'react';
import { auth } from '../../lib/navigraph';
import { authContext } from './NavigraphAuthContext';
// 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>;
}
+21
View File
@@ -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();
+14
View File
@@ -0,0 +1,14 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import 'leaflet/dist/leaflet.css';
import { NavigraphAuthProvider } from './contexts/NavigraphAuth/NavigraphAuthProvider.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<NavigraphAuthProvider>
<App />
</NavigraphAuthProvider>
</StrictMode>
);
+344
View File
@@ -0,0 +1,344 @@
import geojson from 'geojson';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
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 { TerminatorsHA } from './terminators/HA';
import { TerminatorsHF } from './terminators/HF';
import { TerminatorsHM } from './terminators/HM';
import { TerminatorsIF } from './terminators/IF';
import { TerminatorsPI } from './terminators/PI';
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';
class Parser {
private static _instance: Parser;
private _airports: Airport[];
private _waypoints: Waypoint[];
private _runways: Runway[];
private _terminals: Terminal[];
private _procedures: TerminalEntry[] = [];
public static MAGVAR: Uint8Array;
public static AC_SPEED = 250;
public static AC_BANK = 30;
public static AC_VS = 1400;
private constructor(
magVar: Uint8Array,
airports: Airport[],
waypoints: Waypoint[],
runways: Runway[],
terminals: Terminal[]
) {
Parser.MAGVAR = magVar;
this._airports = airports;
this._waypoints = waypoints;
this._runways = runways;
this._terminals = terminals;
}
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(magVar, airports, waypoints, runways, terminals);
}
return Parser._instance;
};
public get airports() {
return this._airports;
}
public get terminals() {
return this._terminals;
}
public get waypoints() {
return this._waypoints;
}
public get runways() {
return this._runways;
}
public get procedures() {
return this._procedures;
}
public loadTerminal = async (terminalID: number) => {
this._procedures = (await (
await fetch(`NavData/ProcedureLegs/TermID_${terminalID}.json`)
).json()) as TerminalEntry[];
};
public parse = (runway: Runway, terminal: Terminal, transition: string) => {
// Private functions
/**
* @param line Line segments
*/
const updateLastCourse = (line: LineSegment[]) => {
lastCourse = getGreatCircleBearing(
{
latitude: line.at(-2)![1],
longitude: line.at(-2)![0],
},
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
}
);
};
/**
* @param fix New fix
* @param line New line
* @param options Options for line rendering
*/
const update = (fix?: NavFix, line?: LineSegment[], options?: Record<string, unknown>) => {
if (fix) navFixes.push(fix);
if (line) {
lineSegments.push({ line, ...options });
updateLastCourse(line);
}
};
// Output variables
const navFixes: NavFix[] = [];
const lineSegments: { line: LineSegment[]; [x: string]: unknown }[] = [];
let lastCourse = 0;
// RWY as origin for Departures
if (terminal.Proc === 2) {
navFixes.push({
latitude: runway.Latitude,
longitude: runway.Longitude,
altitude: runway.Elevation,
speed: 0,
name: runway.Ident,
});
lastCourse = runway.TrueHeading;
}
const legOptions = { isMAP: false };
const procedure = this._procedures.filter(
({ Transition }) => !Transition || Transition === transition || Transition === 'ALL'
);
// Main
for (let index = 0; index < procedure.length; index++) {
console.log('Processing leg with ID', procedure[index].ID);
const leg = procedure[index];
leg.Alt = leg.IsMAP ? String(runway.Elevation + 200).padStart(5, '0') : leg.Alt;
const waypoint = this.waypoints.find(({ ID }) => ID === leg.WptID);
if (terminal.Proc !== 2 && navFixes.length === 0) {
if (!leg.WptLat || !leg.WptLon)
throw new Error('Non departure procedure has first leg that has no defined starting point. Cannot render.');
navFixes.push({
latitude: leg.WptLat,
longitude: leg.WptLon,
altitude: leg.Alt ? leg.Alt.parseAltitude() : runway.Elevation,
speed: leg.SpeedLimit ? leg.SpeedLimit : Parser.AC_SPEED,
name: waypoint?.Ident,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
});
}
const previousFix = navFixes.at(-1)!;
legOptions.isMAP ||= previousFix.IsMAP ?? false;
switch (leg.TrackCode) {
case 'AF': {
const [fixToAdd, lineToAdd] = TerminatorsAF(leg as AFTerminalEntry, previousFix, waypoint);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CA': {
const [fixToAdd, lineToAdd] = TerminatorsCA(leg as CATerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CD': {
const [fixToAdd, lineToAdd] = TerminatorsCD(leg as CDTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CF': {
const [fixToAdd, lineToAdd] = TerminatorsCF(
leg as CFTerminalEntry,
{ ...previousFix }, // COPY
lastCourse,
waypoint
);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CI': {
const [fixToAdd, lineToAdd] = TerminatorsCI(
leg as CITerminalEntry,
procedure[index + 1],
{ ...previousFix }, // COPY
lastCourse
);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CR': {
const [fixToAdd, lineToAdd] = TerminatorsCR(leg as CRTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'DF': {
const [fixToAdd, lineToAdd] = TerminatorsDF(leg as DFTerminalEntry, previousFix, lastCourse, waypoint);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FA': {
const [fixToAdd, lineToAdd] = TerminatorsFA(leg as FATerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FC': {
const [fixToAdd, lineToAdd] = TerminatorsFC(leg as FCTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FD': {
const [fixToAdd, lineToAdd] = TerminatorsFD(leg as FDTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FM': {
const [fixToAdd, lineToAdd] = TerminatorsFM(leg as FMTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, { isManual: true, ...legOptions });
// Make overfly
navFixes.at(-1)!.isFlyOver = true;
break;
}
case 'HA': {
const [fixToAdd, lineToAdd] = TerminatorsHA(leg as HATerminalEntry, previousFix);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'HF': {
const [fixToAdd, lineToAdd] = TerminatorsHF(leg as HFTerminalEntry, previousFix);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'HM': {
const [fixToAdd, lineToAdd] = TerminatorsHM(leg as HMTerminalEntry, previousFix);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'IF': {
const fixToAdd = TerminatorsIF(leg as RFTerminalEntry, waypoint);
// Only Runway, replace
if (navFixes.length <= 1) {
navFixes.push(fixToAdd);
lastCourse = -1;
}
break;
}
case 'PI': {
const nextLeg = procedure[index + 1] as CFTerminalEntry;
const _waypoint = this.waypoints.find(({ ID }) => ID === nextLeg.WptID);
const [fixToAdd, lineToAdd] = TerminatorsPI(leg as PITerminalEntry, nextLeg, previousFix, [
waypoint,
_waypoint,
]);
update(fixToAdd, lineToAdd, { ...legOptions });
break;
}
case 'RF': {
const [fixToAdd, lineToAdd] = TerminatorsRF(leg as RFTerminalEntry, previousFix, lastCourse, waypoint);
if (fixToAdd) {
navFixes.push(fixToAdd);
lastCourse = (leg as RFTerminalEntry).Course?.toTrue(fixToAdd);
}
if (lineToAdd) {
lineSegments.push({ line: lineToAdd, ...legOptions });
}
break;
}
case 'TF': {
const [fixToAdd, lineToAdd] = TerminatorsTF(
leg as TFTerminalEntry,
{ ...previousFix }, // COPY
lastCourse,
waypoint
);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'VA': {
const [fixToAdd, lineToAdd] = TerminatorsVA(leg as VATerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'VD': {
const [fixToAdd, lineToAdd] = TerminatorsVD(leg as VDTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'VI': {
const [fixToAdd, lineToAdd] = TerminatorsVI(
leg as VITerminalEntry,
procedure[index + 1],
{ ...previousFix }, // COPY
lastCourse
);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'VM': {
const [fixToAdd, lineToAdd] = TerminatorsVM(leg as VMTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, { isManual: true, ...legOptions });
// Make overfly
navFixes.at(-1)!.isFlyOver = true;
break;
}
case 'VR': {
const [fixToAdd, lineToAdd] = TerminatorsVR(leg as VRTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, legOptions);
break;
}
default:
console.error('Unknown TrackCode', leg.TrackCode);
break;
}
}
return geojson.parse([...navFixes, ...lineSegments], {
LineString: 'line',
Point: ['latitude', 'longitude'],
});
};
}
export default Parser;
@@ -1,4 +1,4 @@
import * as geolib from "geolib"; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
/** /**
* @param crsIntoEndpoint Course into arc endpoint * @param crsIntoEndpoint Course into arc endpoint
@@ -6,7 +6,7 @@ import * as geolib from "geolib";
* @param start Arc origin point * @param start Arc origin point
* @param center Arc center point * @param center Arc center point
* @param radius Arc radius in nmi * @param radius Arc radius in nmi
* @param turnDir * @param turnDir Turn direction
* @returns Line segments * @returns Line segments
*/ */
export const generateAFArc = ( export const generateAFArc = (
@@ -21,29 +21,25 @@ export const generateAFArc = (
if (crsIntoEndpoint !== crsFromOrigin) { if (crsIntoEndpoint !== crsFromOrigin) {
// Turn Dir // Turn Dir
if (!turnDir || turnDir === "E") { if (!turnDir || turnDir === 'E') {
let prov = crsFromOrigin - crsIntoEndpoint; let prov = crsFromOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov; prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? "L" : "R"; turnDir = prov > 0 ? 'L' : 'R';
} }
while (crsFromOrigin !== crsIntoEndpoint) { while (crsFromOrigin !== crsIntoEndpoint) {
if (turnDir === "R") { if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees(); const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
crsFromOrigin += delta < 1 ? delta : 1; crsFromOrigin += delta < 0.1 ? delta : 0.1;
crsFromOrigin = crsFromOrigin.normaliseDegrees(); crsFromOrigin = crsFromOrigin.normaliseDegrees();
} else { } else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees(); const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
crsFromOrigin -= delta < 1 ? delta : 1; crsFromOrigin -= delta < 0.1 ? delta : 0.1;
crsFromOrigin = crsFromOrigin.normaliseDegrees(); crsFromOrigin = crsFromOrigin.normaliseDegrees();
} }
if (crsFromOrigin === crsIntoEndpoint) break; if (crsFromOrigin === crsIntoEndpoint) break;
const arcFix = geolib.computeDestinationPoint( const arcFix = computeDestinationPoint(center, radius.toMetre(), crsFromOrigin);
center,
radius.toMetre(),
crsFromOrigin
);
line.push([arcFix.longitude, arcFix.latitude]); line.push([arcFix.longitude, arcFix.latitude]);
} }
@@ -0,0 +1,44 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
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 = getGreatCircleBearing(
{
latitude: line.at(-2)![1],
longitude: line.at(-2)![0],
},
arcEnd
);
}
return [line, arcEnd, crsFromOrigin];
};
@@ -0,0 +1,98 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { computeTurnRate } from '../utils/computeTurnRate';
/**
* @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
* @param force360 If true, force a 360° turn
* @returns Line segments
*/
export const generatePerformanceArc = (
crsIntoEndpoint: number,
crsFromOrigin: number,
start: NavFix,
speed: number,
turnDir?: TurnDirection,
force360?: boolean
) => {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const originalCrsFromOrigin = crsFromOrigin;
const line: LineSegment[] = [[start.longitude, start.latitude]];
// Check if there even is an arc
if (force360 || !crsFromOrigin.equal(crsIntoEndpoint)) {
// Turn Dir
if (!turnDir || turnDir === 'E') {
let prov = crsFromOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!crsFromOrigin.equal(crsIntoEndpoint)) {
let time = 0;
if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
crsFromOrigin
);
line.push([arcFix.longitude, arcFix.latitude]);
}
// Second half
if (force360) {
const temp = crsIntoEndpoint;
crsIntoEndpoint = originalCrsFromOrigin;
crsFromOrigin = temp;
while (!crsFromOrigin.equal(crsIntoEndpoint)) {
let time = 0;
if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
crsFromOrigin
);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
}
return line;
};
@@ -0,0 +1,61 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
/**
* @param crsIntoEndpoint Course into arc endpoint
* @param crsIntoOrigin Course into arc origin point
* @param start Arc origin point
* @param center Arc center point
* @param end Arc end point
* @param turnDir Turn Direction
* @returns Line segments
*/
export const generateRFArc = (
crsIntoEndpoint: number,
crsIntoOrigin: number,
start: NavFix,
center: NavFix,
end: NavFix,
turnDir: TurnDirection
) => {
const line: LineSegment[] = [[start.longitude, start.latitude]];
if (!crsIntoEndpoint.equal(crsIntoOrigin)) {
// Turn Dir
if (!turnDir || turnDir === 'E') {
let prov = crsIntoOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
turnDir = prov > 0 ? 'L' : 'R';
}
let crsOrthogonalOnOrigin = getGreatCircleBearing(center, start);
const crsOrthogonalOnEndpoint = getGreatCircleBearing(center, end);
const arcRad = getPreciseDistance(center, start);
// Start turn immediately
if (turnDir === 'R') {
crsOrthogonalOnOrigin += crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
} else {
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
}
while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
if (turnDir === 'R') {
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else {
const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
}
const arcFix = computeDestinationPoint(center, arcRad, crsOrthogonalOnOrigin);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
return line;
};
@@ -1,5 +1,6 @@
import * as geolib from "geolib"; import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import { computeIntersection } from "../utils/computeIntersection.ts"; import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { computeIntersection } from '../utils/computeIntersection';
/** /**
* @param crsIntoEndpoint Course into arc endpoint * @param crsIntoEndpoint Course into arc endpoint
@@ -19,15 +20,15 @@ export const generateTangentArc = (
const line: LineSegment[] = [[start.longitude, start.latitude]]; const line: LineSegment[] = [[start.longitude, start.latitude]];
// Check if there even is an arc // Check if there even is an arc
if (crsFromOrigin !== crsIntoEndpoint) { if (!crsFromOrigin.equal(crsIntoEndpoint)) {
// Course to the end of the arc // Course to the end of the arc
let crsFromStartToEnd; let crsFromStartToEnd;
if (!turnDir || turnDir === "E") { if (!turnDir || turnDir === 'E') {
let prov = crsFromOrigin - crsIntoEndpoint; let prov = crsFromOrigin - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov; 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(); const delta = (360 - crsFromOrigin + crsIntoEndpoint).normaliseDegrees();
crsFromStartToEnd = (crsFromOrigin + delta / 2).normaliseDegrees(); crsFromStartToEnd = (crsFromOrigin + delta / 2).normaliseDegrees();
} else { } else {
@@ -46,7 +47,7 @@ export const generateTangentArc = (
let crsOrthogonalOnOrigin; let crsOrthogonalOnOrigin;
let crsOrthogonalOnEndpoint; let crsOrthogonalOnEndpoint;
if (turnDir === "R") { if (turnDir === 'R') {
crsOrthogonalOnOrigin = (crsFromOrigin + 90).normaliseDegrees(); crsOrthogonalOnOrigin = (crsFromOrigin + 90).normaliseDegrees();
crsOrthogonalOnEndpoint = (crsIntoEndpoint + 90).normaliseDegrees(); crsOrthogonalOnEndpoint = (crsIntoEndpoint + 90).normaliseDegrees();
} else { } else {
@@ -55,47 +56,46 @@ export const generateTangentArc = (
} }
// Generate arc // Generate arc
const arcCenter = computeIntersection( let arcRad = 0;
let arcCenter = computeIntersection(
start, start,
crsOrthogonalOnOrigin, crsOrthogonalOnOrigin,
intcArcOnCrsIntoEndpoint, intcArcOnCrsIntoEndpoint,
crsOrthogonalOnEndpoint crsOrthogonalOnEndpoint
); );
if (!arcCenter) return null; if (!crsOrthogonalOnEndpoint.equal(crsOrthogonalOnOrigin) && arcCenter)
const arcRad = geolib.getDistance(arcCenter, start); arcRad = getPreciseDistance(arcCenter, start);
else {
arcRad = getPreciseDistance(start, end) / 2;
arcCenter = computeDestinationPoint(start, arcRad, crsOrthogonalOnOrigin);
}
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse(); crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
// Start turn immediately // Start turn immediately
if (turnDir === "R") { if (turnDir === 'R') {
crsOrthogonalOnOrigin += crsOrthogonalOnOrigin += crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
} else { } else {
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
} }
while (crsOrthogonalOnOrigin !== crsOrthogonalOnEndpoint) { let lastDistance = getPreciseDistance(start, end);
if (turnDir === "R") { while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
const delta = ( if (turnDir === 'R') {
crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
).normaliseDegrees(); crsOrthogonalOnOrigin += delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else { } else {
const delta = ( const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint crsOrthogonalOnOrigin -= delta < 0.1 ? delta : 0.1;
).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees(); crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} }
if (crsOrthogonalOnOrigin === crsOrthogonalOnEndpoint) break;
const arcFix = geolib.computeDestinationPoint( const arcFix = computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin);
arcCenter,
arcRad, const newDistance = getPreciseDistance(arcFix, end);
crsOrthogonalOnOrigin if (lastDistance <= newDistance && lastDistance < 25) break;
); lastDistance = newDistance;
line.push([arcFix.longitude, arcFix.latitude]); line.push([arcFix.longitude, arcFix.latitude]);
} }
@@ -1,6 +1,6 @@
import * as geolib from "geolib"; import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateTangentArc } from "./generateTangentArc.ts"; import { generatePerformanceArc } from './generatePerformanceArc';
import { generatePerformanceArc } from "./generatePerformanceArc.ts"; import { generateTangentArc } from './generateTangentArc';
/** /**
* @param crsIntoEndpoint Course into endpoint * @param crsIntoEndpoint Course into endpoint
@@ -8,6 +8,7 @@ import { generatePerformanceArc } from "./generatePerformanceArc.ts";
* @param crsFromOrigin Course from arc origin point * @param crsFromOrigin Course from arc origin point
* @param start Waypoint where leg starts * @param start Waypoint where leg starts
* @param end Waypoint where leg ends * @param end Waypoint where leg ends
* @param speed Speed for performance arc
* @param turnDir Turn direction * @param turnDir Turn direction
* @returns Line segments from `start` to `end` * @returns Line segments from `start` to `end`
*/ */
@@ -17,31 +18,21 @@ export const handleTurnAtFix = (
crsFromOrigin: number, crsFromOrigin: number,
start: NavFix, start: NavFix,
end: NavFix, end: NavFix,
speed: number,
turnDir?: TurnDirection turnDir?: TurnDirection
) => { ) => {
const line: LineSegment[] = []; const line: LineSegment[] = [];
// Overfly turn // Overfly turn
if (start.isFlyOver) { if (start.isFlyOver) {
const arc1 = generateTangentArc( const arc1 = generateTangentArc(crsIntoEndpoint, crsFromOrigin, start, end, turnDir);
crsIntoEndpoint,
crsFromOrigin,
start,
end,
turnDir
);
const arc2 = generatePerformanceArc( const arc2 = generatePerformanceArc(crsIntoIntercept, crsFromOrigin, start, speed, turnDir);
crsIntoIntercept,
crsFromOrigin,
start,
turnDir
);
// Decide on arc // Decide on arc
let arc; let arc;
if (arc1) { if (arc1) {
const endCrs = geolib.getGreatCircleBearing( const endCrs = getGreatCircleBearing(
{ {
latitude: arc1.at(-1)![1], latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0], longitude: arc1.at(-1)![0],
@@ -49,8 +40,7 @@ export const handleTurnAtFix = (
end end
); );
if (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1) if (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1) arc = arc1;
arc = arc1;
else arc = arc2; else arc = arc2;
} else { } else {
arc = arc2; arc = arc2;
@@ -58,9 +48,8 @@ export const handleTurnAtFix = (
line.push(...arc); line.push(...arc);
line.push([end.longitude, end.latitude]); line.push([end.longitude, end.latitude]);
} }
// FIXME: Procedural turn // Procedural turn
else { else {
// Direct line for now
line.push([start.longitude, start.latitude], [end.longitude, end.latitude]); line.push([start.longitude, start.latitude], [end.longitude, end.latitude]);
} }
@@ -0,0 +1,33 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateAFArc } from '../pathGenerators/generateAFArc';
import { computeSpeed } from '../utils/computeSpeed';
export const TerminatorsAF = (
leg: AFTerminalEntry,
previousFix: NavFix,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const navaid = {
latitude: leg.NavLat,
longitude: leg.NavLon,
};
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: computeSpeed(leg, previousFix),
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
const arcEndCrs = getGreatCircleBearing(navaid, targetFix);
const line = generateAFArc(arcEndCrs, leg.Course.toTrue(navaid), previousFix, navaid, leg.NavDist, leg.TurnDir);
return [targetFix, line];
};
@@ -0,0 +1,40 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
export const TerminatorsCA = (
leg: CATerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
// 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 = {
...computeDestinationPoint(
arcEnd,
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
).toMetre(),
crsIntoEndpoint
),
name: leg.Alt,
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
@@ -0,0 +1,53 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
// NOTE: Distance not adjusted for altitude in this demo
export const TerminatorsCD = (
leg: CDTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const navaid = {
latitude: leg.NavLat,
longitude: leg.NavLon,
};
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const speed = computeSpeed(leg, previousFix);
// Compute overfly
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
lastCourse = _lastCourse;
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
remainingDistance -= distToNavaid;
}
// Navaid in front of us
else {
// Navaid will not be passed before distance is hit
if (distToNavaid > remainingDistance) remainingDistance = distToNavaid - remainingDistance;
}
// Compute intercept of crs from arc end and distance
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
+133
View File
@@ -0,0 +1,133 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import Parser from '../parser';
import { generateTangentArc } from '../pathGenerators/generateTangentArc';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsCF = (
leg: CFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const line: LineSegment[] = [];
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
// Compute overfly arc
let arc1: LineSegment[] | null = null;
let arc2: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
if (previousFix.isFlyOver) {
arc1 = generateTangentArc(crsIntoEndpoint, lastCourse, previousFix, targetFix, leg.TurnDir);
} else {
arc1 = [[previousFix.longitude, previousFix.latitude]];
}
let arc;
if (arc1 && arc1.length > 1) {
const endCrs = getGreatCircleBearing(
{
latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0],
},
targetFix
);
const endDist = getPreciseDistance(
{
latitude: arc1.at(-1)![1],
longitude: arc1.at(-1)![0],
},
targetFix
);
if (endDist <= 25 || (endCrs <= crsIntoEndpoint + 1 && endCrs >= crsIntoEndpoint - 1)) arc = arc1;
}
const intercept = computeIntersection(previousFix, lastCourse, targetFix, crsIntoEndpoint.reciprocalCourse());
if (
!arc &&
previousFix.isFlyOver &&
(!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsIntoEndpoint)) &&
!(intercept?.latitude === targetFix.latitude && intercept.longitude === targetFix.longitude)
) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
let lastDistance = getPreciseDistance(previousFix, targetFix);
const reversal = Math.abs(lastCourse - crsIntoEndpoint) >= 90;
while (!updatedCrsToIntercept.equal(crsIntoEndpoint)) {
let interceptAngle = 0;
if (leg.TurnDir === 'R') interceptAngle = Math.abs(lastCourse - crsIntoEndpoint);
else interceptAngle = Math.abs(crsIntoEndpoint - lastCourse);
let time = 0;
const increment = 0.1;
if (interceptAngle < 44.9 || interceptAngle >= 45.1) {
if (leg.TurnDir === 'R') {
lastCourse =
reversal || updatedCrsToIntercept >= crsIntoEndpoint
? (lastCourse + increment).normaliseDegrees()
: lastCourse;
time = increment / turnRate;
} else {
lastCourse =
reversal || updatedCrsToIntercept <= crsIntoEndpoint
? (lastCourse - increment).normaliseDegrees()
: lastCourse;
time = increment / turnRate;
}
} else time = increment / turnRate;
const arcFix = computeDestinationPoint(
{
latitude: arc2.at(-1)![1],
longitude: arc2.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
lastCourse
);
arc2.push([arcFix.longitude, arcFix.latitude]);
// Update previousFix
previousFix.latitude = arcFix.latitude;
previousFix.longitude = arcFix.longitude;
updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
const newDistance = getPreciseDistance(previousFix, targetFix);
if (lastDistance <= newDistance && lastDistance < 25) break;
lastDistance = newDistance;
}
}
if (!arc) arc = arc2;
line.push(...arc);
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
@@ -0,0 +1,78 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
import { getCourseAndFixForIntercepts } from '../utils/getCourseAndFixForIntercepts';
export const TerminatorsCI = (
leg: CITerminalEntry,
nextLeg: TerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const [crsToIntercept, nextFix] = getCourseAndFixForIntercepts(nextLeg, previousFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Compute overfly arc
if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const updatedCrsToIntercept = getGreatCircleBearing(previousFix, nextFix);
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!lastCourse.equal(crsIntoEndpoint) && !updatedCrsToIntercept.equal(crsToIntercept)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
lastCourse
);
line.push([arcFix.longitude, arcFix.latitude]);
// Update previousFix
previousFix.latitude = arcFix.latitude;
previousFix.longitude = arcFix.longitude;
}
}
const interceptFix: NavFix = {
...computeIntersection(previousFix, leg.Course.toTrue(nextFix), nextFix, crsToIntercept)!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
return [interceptFix, line];
};
@@ -0,0 +1,37 @@
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
export const TerminatorsCR = (
leg: CRTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const navaid = {
latitude: leg.NavLat,
longitude: leg.NavLon,
};
const crsFromEndpoint = leg.Course.toTrue(previousFix);
const crsIntoEndpoint = leg.NavBear.toTrue(navaid);
const speed = computeSpeed(leg, previousFix);
// 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,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
return [interceptFix, line];
};
+121
View File
@@ -0,0 +1,121 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsDF = (
leg: DFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const originalCrsFromOrigin = lastCourse;
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoint?.Ident ?? undefined,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
let crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
let force360 = getPreciseDistance(previousFix, targetFix) < 25;
// Check if there even is an arc
if (force360 || !lastCourse.equal(crsIntoEndpoint)) {
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!lastCourse.equal(crsIntoEndpoint)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
lastCourse
);
crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);
line.push([arcFix.longitude, arcFix.latitude]);
// made a loop
if (line.length >= 3600) {
if (!force360) {
line.splice(1);
}
force360 = false;
break;
}
}
// Second half
if (force360) {
const temp = crsIntoEndpoint;
crsIntoEndpoint = originalCrsFromOrigin;
lastCourse = temp;
while (!lastCourse.equal(crsIntoEndpoint)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
lastCourse
);
crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);
line.push([arcFix.longitude, arcFix.latitude]);
}
}
}
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
@@ -0,0 +1,44 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
export const TerminatorsFA = (
leg: FATerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(refFix);
// 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 = {
...computeDestinationPoint(
arcEnd,
(
((leg.Alt.parseAltitude() - (previousFix.altitude ?? 0)) / Parser.AC_VS) *
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 60)
).toMetre(),
crsIntoEndpoint
),
name: leg.Alt,
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
@@ -0,0 +1,97 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
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 = (
leg: FCTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const trackIntoEndpoint = leg.Course.toTrue(refFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
if (previousFix.isFlyOver) {
const crsIntoEndpoint = trackIntoEndpoint;
// Check if there even is an arc
if (!crsIntoEndpoint.equal(lastCourse)) {
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
let condition = false;
do {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed * time) / 3600).toMetre(),
lastCourse
);
line.push([arcFix.longitude, arcFix.latitude]);
if (leg.TurnDir === 'R') {
condition = lastCourse > trackIntoEndpoint;
} else {
condition = lastCourse < trackIntoEndpoint;
}
} while (condition);
}
}
const arcEnd = { latitude: line.at(-1)![1], longitude: line.at(-1)![0] };
if (line.length > 1) {
lastCourse = getGreatCircleBearing(
{
latitude: line.at(-2)![1],
longitude: line.at(-2)![0],
},
arcEnd
);
}
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, leg.Distance.toMetre(), leg.Course.toTrue(refFix)),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
@@ -0,0 +1,57 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
// NOTE: Distance not adjusted for altitude in this demo
export const TerminatorsFD = (
leg: FDTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const navaid = {
latitude: leg.NavLat,
longitude: leg.NavLon,
};
const crsIntoEndpoint = leg.Course.toTrue(refFix);
const speed = computeSpeed(leg, previousFix);
// Compute overfly
const [line, arcEnd, _lastCourse] = generateOverflyArc(crsIntoEndpoint, lastCourse, previousFix, speed, leg.TurnDir);
lastCourse = _lastCourse;
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
remainingDistance -= distToNavaid;
}
// Navaid in front of us
else {
// Navaid will not be passed before distance is hit
if (distToNavaid > remainingDistance) remainingDistance = distToNavaid - remainingDistance;
}
// Compute intercept of crs from arc end and distance
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, remainingDistance, crsIntoEndpoint),
name: leg.Distance.toString(),
isFlyOver: true,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
@@ -0,0 +1,25 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import { handleTurnAtFix } from '../pathGenerators/handleTurnAtFix';
import { computeSpeed } from '../utils/computeSpeed';
export const TerminatorsFM = (
leg: FMTerminalEntry,
previousFix: NavFix,
lastCourse: number
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const endpoint = computeDestinationPoint(previousFix, (10).toMetre(), leg.Course.toTrue(previousFix));
const line = handleTurnAtFix(
leg.Course.toTrue(previousFix),
leg.Course.toTrue(previousFix),
lastCourse,
previousFix,
endpoint,
speed,
leg.TurnDir
);
return [undefined, line];
};
@@ -0,0 +1,88 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
// NOTE: Distance is interpreted as distance and not time
export const TerminatorsHA = (leg: HATerminalEntry, previousFix: NavFix): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const inboundCrs = leg.Course.toTrue(refFix);
const outboundCrs = inboundCrs.reciprocalCourse();
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Generate top arc
let currentCrs = inboundCrs;
while (!currentCrs.equal(outboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (outboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - outboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
const outboundStart = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
leg.Distance.toMetre(),
outboundCrs
);
line.push([outboundStart.longitude, outboundStart.latitude]);
// Generate bottom arc
currentCrs = outboundCrs;
while (!currentCrs.equal(inboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (inboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - inboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
line.push([refFix.longitude, refFix.latitude]);
return [refFix, line];
};
@@ -0,0 +1,88 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
// NOTE: Distance is interpreted as distance and not time
export const TerminatorsHF = (leg: HFTerminalEntry, previousFix: NavFix): [NavFix?, LineSegment[]?] => {
const refFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
};
const speed = computeSpeed(leg, previousFix);
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
const inboundCrs = leg.Course.toTrue(refFix);
const outboundCrs = inboundCrs.reciprocalCourse();
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
// Generate top arc
let currentCrs = inboundCrs;
while (!currentCrs.equal(outboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (outboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - outboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
const outboundStart = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
leg.Distance.toMetre(),
outboundCrs
);
line.push([outboundStart.longitude, outboundStart.latitude]);
// Generate bottom arc
currentCrs = outboundCrs;
while (!currentCrs.equal(inboundCrs)) {
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (inboundCrs - currentCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (currentCrs - inboundCrs).normaliseDegrees();
const increment = delta < 0.1 ? delta : 0.1;
currentCrs = (currentCrs - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
currentCrs
);
line.push([arcFix.longitude, arcFix.latitude]);
}
line.push([refFix.longitude, refFix.latitude]);
return [refFix, line];
};

Some files were not shown because too many files have changed in this diff Show More