Compare commits

...

14 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
123 changed files with 1756 additions and 3561 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/browser",
"webRoot": "${workspaceFolder}/md11-nav-data",
"sourceMaps": true
}
]
+7
View File
@@ -0,0 +1,7 @@
{
"folders": [
{
"path": "."
}
],
}
+58 -28
View File
@@ -10,7 +10,7 @@
### Example
LGAV BIBE1K SID (Cycle 2507, ID 10653)
LGAV 03L BIBE1K SID (Cycle 2507, ID 10653)
### 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.
Calculate distance for FMS based on `2 * π * NavDist * abs(NavBear - Course) 360`.
## Course to Altitude (CA)
### Example
LGAV BIBE1L SID (Cycle 2507, ID 10654)
LGAV 03L BIBE1L SID (Cycle 2507, ID 10654)
### Minimum Required Fields
@@ -74,7 +76,7 @@ This new origin is an implicit overfly.
### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657)
LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields
@@ -98,14 +100,13 @@ LGAV BIBE2F SID (Cycle 2507, ID 10657)
The leg terminates upon reaching `Distance`.
This intercept point then becomes the origin fix of the succeeding leg.
This new origin is an implicit overfly.
## Course to Fix (CF)
### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657)
LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields
@@ -145,7 +146,7 @@ shall be `TurnDir`.
### Example
LGAV BIBE1L SID (Cycle 2507, ID 10654)
LGAV 03L BIBE1L SID (Cycle 2507, ID 10654)
### Minimum Required Fields
@@ -170,7 +171,7 @@ This new origin can never be an overfly due to the intercept nature.
### Example
LGAV KOR1D SID (Cycle 2507, ID 10679)
LGAV 03L KOR1D SID (Cycle 2507, ID 10679)
### Minimum Required Fields
@@ -197,7 +198,7 @@ This intercept point then becomes the origin fix of the succeeding leg.
### Example
LGAV KOR1D SID (Cycle 2507, ID 10679)
LGAV 03L KOR1D SID (Cycle 2507, ID 10679)
### Minimum Required Fields
@@ -219,7 +220,7 @@ LGAV KOR1D SID (Cycle 2507, ID 10679)
### Example
LGAV BIBE2F SID (Cycle 2507, ID 10657)
LGAV 21L BIBE2F SID (Cycle 2507, ID 10657)
### Minimum Required Fields
@@ -254,7 +255,7 @@ This new origin is an implicit overfly.
### Example
LIED CAR6F SID (Cycle 2507, ID 11798)
LIED 34L/R CAR6F SID (Cycle 2507, ID 11798)
### Minimum Required Fields
@@ -290,7 +291,7 @@ This intercept point then becomes the origin fix of the succeeding leg.
### Example
LGAV BIBE2T SID (Cycle 2507, ID 10659)
LGAV 03R BIBE2T SID (Cycle 2507, ID 10659)
### Minimum Required Fields
@@ -318,14 +319,13 @@ LGAV BIBE2T SID (Cycle 2507, ID 10659)
The leg terminates upon reaching `Distance`.
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)
### Example
LFPV PB2V SID (Cycle 2507, ID 10395)
LFPV 27 PB2V SID (Cycle 2507, ID 10395)
### Minimum Required Fields
@@ -346,7 +346,7 @@ LFPV PB2V SID (Cycle 2507, ID 10395)
- `NavDist` is in **nmi**. `NavDist` is geodesic.
## Holding mandatory (HA)
## Holding mandatory (altitude end) (HA)
### Example
@@ -420,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.
## Holding mandatory (HM)
## Holding mandatory (manual end) (HM)
### Example
@@ -457,7 +457,7 @@ My guess as for the missing time/distance decider field, assume it to be distanc
### 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
@@ -493,8 +493,11 @@ FAUP VDM35 APP (Cycle 2507, ID 67790)
### Instructions
- From the fix identified by (`WptID`, `WptLat`, `WptLon`) *or*
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly `Course` for `Distance`.
- Turn `TurnDir` until aligned with succeeding leg course.
(`NavID`, `NavLat`, `NavLon`, `NavDist`, `NavBear`), fly direct to succeeding leg fix.
- 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.
### Units
@@ -505,15 +508,17 @@ FAUP VDM35 APP (Cycle 2507, ID 67790)
### Notes
This needs to look into the succeeding leg in order to calculate the intercept point.
This intercept point then becomes the origin fix of the succeeding leg.
This needs to look into the succeeding leg in order to fly the first segment and get the course
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)
### Example
LFRN GODA5R SID (cycle 2507, ID 10485)
LFRN 10 GODA5R SID (cycle 2507, ID 10485)
### Minimum Required Fields
@@ -545,12 +550,14 @@ No radius is specified, but can be inferred based on center point, both endpoint
Example has `NavBear` set to `null`, significance of the inbound tangential track is unknown.
Use `Distance` for calculations in FMS.
## Track to Fix (TF)
### Example
LFRN GODA5R SID (cycle 2507, ID 10485)
LFRN 10 GODA5R SID (cycle 2507, ID 10485)
### Minimum Required Fields
@@ -571,7 +578,7 @@ LFRN GODA5R SID (cycle 2507, ID 10485)
### Example
LFRK LGL4X SID (Cycle 2507, ID 10475)
LFRK 10 LGL4X SID (Cycle 2507, ID 10475)
### Minimum Required Fields
@@ -599,7 +606,7 @@ This new origin is an implicit overfly.
### Example
LFRK NEVI4Y SID (Cycle 2507, ID 10482)
LFRK 31 NEVI4Y SID (Cycle 2507, ID 10482)
### Minimum Required Fields
@@ -622,14 +629,13 @@ LFRK NEVI4Y SID (Cycle 2507, ID 10482)
The leg terminates upon reaching `Distance`.
This intercept point then becomes the origin fix of the succeeding leg.
This new origin is an implicit overfly.
## Heading to Intercept (VI)
### Example
LFRK LUSI4Y SID (Cycle 2507, ID 10480)
LFRK 31 LUSI4Y SID (Cycle 2507, ID 10480)
### Minimum Required Fields
@@ -656,7 +662,7 @@ This new origin can never be an overfly due to the intercept nature.
### Example
LFPV PB2P SID (Cycle 2507, ID 10394)
LFPV 27 PB2P SID (Cycle 2507, ID 10394)
### Minimum Required Fields
@@ -676,7 +682,7 @@ LFPV PB2P SID (Cycle 2507, ID 10394)
### Example
LIMC MMP8G SID (Cycle 2507, ID 11909)
LIMC 35R MMP8G SID (Cycle 2507, ID 11909)
### Minimum Required Fields
@@ -697,3 +703,27 @@ LIMC MMP8G SID (Cycle 2507, ID 11909)
The leg terminates at the intercept point.
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`
File diff suppressed because one or more lines are too long
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
}
]
@@ -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
File diff suppressed because one or more lines are too long
-89
View File
@@ -1,89 +0,0 @@
import type { DeviceFlowParams } from 'navigraph/auth';
import { QRCodeSVG } from 'qrcode.react';
import { useState } from 'react';
import { ProcedureSelect } from './components//ProcedureSelect';
import { Map } from './components/Map';
import { Sidebar } from './components/Sidebar';
import { useNavigraphAuth } from './contexts/NavigraphAuth/NavigraphAuthContext';
import Parser from './parser/parser';
const parser = await Parser.instance();
function App() {
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 { user, signIn, initialized } = useNavigraphAuth();
const handleSignIn = () => signIn((p) => setParams(p));
return (
<>
{transitions.length === 0 ? (
<div className="flex min-h-dvh w-full">
{!initialized && <div>Loading...</div>}
{initialized && !params && !user && <button onClick={handleSignIn}>Sign in</button>}
{params?.verification_uri_complete && !user && (
<>
<QRCodeSVG value={params.verification_uri_complete} size={250} />
<a href={params.verification_uri_complete} target="_blank" rel="noreferrer">
Open sign in page
</a>
</>
)}
{user && (
<ProcedureSelect
selectedAirport={selectedAirport}
selectedRunway={selectedRunway}
selectedTerminal={selectedTerminal}
setSelectedAirport={setSelectedAirport}
setSelectedRunway={setSelectedRunway}
setSelectedTerminal={setSelectedTerminal}
handleSelection={(selectedTransitions) => {
const _transitions = selectedTransitions.map((transition) => ({
name: transition,
data: parser.parse(selectedRunway!, transition),
}));
setTransitions(_transitions);
setSelectedTransition(_transitions[0]);
setSelectedChart(undefined);
}}
/>
)}
</div>
) : (
<div className="flex h-dvh w-dvw">
{transitions.length > 0 && selectedAirport && selectedTerminal ? (
<>
<Sidebar
airport={selectedAirport}
terminal={selectedTerminal}
transitions={transitions}
transition={selectedTransition}
chart={selectedChart}
setTransition={setSelectedTransition}
setChart={setSelectedChart}
backAction={() => {
setSelectedTerminal(undefined);
setTransitions([]);
}}
/>
<Map airport={selectedAirport} chart={selectedChart} procedure={selectedTransition} />
</>
) : (
<h1 className="text-center text-3xl">Error</h1>
)}
</div>
)}
</>
);
}
export default App;
-125
View File
@@ -1,125 +0,0 @@
import { createRef, useMemo, useState, type Dispatch, type FC, type SetStateAction } from 'react';
import Parser from '../parser/parser';
const parser = await Parser.instance();
interface ProcedureSelectProps {
selectedAirport: Airport | undefined;
selectedRunway: Runway | undefined;
selectedTerminal: Terminal | undefined;
setSelectedAirport: Dispatch<SetStateAction<Airport | undefined>>;
setSelectedRunway: Dispatch<SetStateAction<Runway | undefined>>;
setSelectedTerminal: Dispatch<SetStateAction<Terminal | undefined>>;
handleSelection: (transitions: string[]) => void;
}
export const ProcedureSelect: FC<ProcedureSelectProps> = ({
selectedAirport,
selectedRunway,
selectedTerminal,
setSelectedAirport,
setSelectedRunway,
setSelectedTerminal,
handleSelection,
}) => {
const inputRef = createRef<HTMLInputElement>();
const [error, setError] = useState<string>();
const runways = useMemo(
() => parser.runways.filter(({ AirportID }) => AirportID === selectedAirport?.ID),
[selectedAirport]
);
const terminals = useMemo(
() =>
parser.terminals.filter(
({ AirportID, RwyID }) => AirportID === selectedAirport?.ID && (!RwyID || RwyID === selectedRunway?.ID)
),
[selectedAirport, selectedRunway]
);
return (
<div className="flex w-full flex-col items-center justify-center gap-2 p-2">
{selectedAirport && (
<button
className="fixed top-2 left-2 cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
onClick={() => {
setError(undefined);
if (selectedTerminal) {
setSelectedTerminal(undefined);
} else if (selectedRunway) {
setSelectedRunway(undefined);
} else if (selectedAirport) {
setSelectedAirport(undefined);
}
}}
>
Go back
</button>
)}
{!selectedAirport && (
<div className="flex w-full flex-col gap-2">
<h1 className="text-center text-3xl">Enter Airport ICAO</h1>
<input
ref={inputRef}
className="rounded border border-black px-2 py-1 focus:outline-2 focus-visible:outline-2"
onChange={(e) => {
if (e.target.value.length <= 4) e.target.value = e.target.value.toUpperCase();
else e.target.value = e.target.value.slice(0, 4);
}}
></input>
<button
className="w-full cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
onClick={() => {
const airport = parser.airports.find(({ ICAO }) => ICAO === inputRef.current?.value.toUpperCase());
if (!airport) {
setError('Airport not found');
return;
}
setSelectedAirport(airport);
setError(undefined);
}}
>
Select Airport
</button>
{error && <span className="text-center text-red-700">{error}</span>}
</div>
)}
{selectedAirport && !selectedRunway && (
<div className="flex w-full flex-col gap-2">
<h1 className="text-center text-3xl">Select Runway</h1>
{runways.map((runway) => (
<button
key={runway.ID}
className="w-full cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
onClick={() => setSelectedRunway(runway)}
>
Runway {runway.Ident}
</button>
))}
</div>
)}
{selectedAirport && selectedRunway && !selectedTerminal && (
<div className="flex w-full flex-col gap-2">
<h1 className="text-center text-3xl">Select Procedure</h1>
{terminals.map((terminal) => (
<button
key={terminal.ID}
className="cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus-visible:outline-2"
onClick={() => {
parser.loadTerminal(terminal.ID).then(() => {
setSelectedTerminal(terminal);
const transitions = new Set(parser.procedures.map((proc) => proc.Transition));
handleSelection(Array.from(transitions));
});
}}
>
{terminal.FullName}
</button>
))}
</div>
)}
</div>
);
};
-109
View File
@@ -1,109 +0,0 @@
import BrowserImageManipulation from 'browser-image-manipulation';
import L from 'leaflet';
import type { Chart as NGChart } from 'navigraph/charts';
import { useEffect, useState, type Dispatch, type FC, type SetStateAction } from 'react';
import { charts } from '../lib/navigraph';
interface SidebarProps {
airport: Airport;
terminal: Terminal;
transitions: Procedure[];
transition: Procedure | undefined;
chart: Chart | undefined;
setTransition: Dispatch<SetStateAction<SidebarProps['transition']>>;
setChart: Dispatch<SetStateAction<SidebarProps['chart']>>;
backAction: () => void;
}
export const Sidebar: FC<SidebarProps> = ({
airport,
terminal,
transitions,
transition,
chart,
setTransition,
setChart,
backAction,
}) => {
const [chartIndex, setChartIndex] = useState<NGChart[]>([]);
useEffect(() => {
(async () => {
setChartIndex((await charts.getChartsIndex({ icao: airport.ICAO, version: 'STD' })) ?? []);
})();
}, [airport.ICAO]);
return (
<div className="flex h-full w-[300px] shrink-0 flex-col overflow-hidden">
<button
className="sticky top-0 m-2 cursor-pointer rounded border border-red-500 bg-red-500 px-2 py-1 font-semibold text-stone-50 focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black"
onClick={backAction}
>
Go back
</button>
<div className="flex h-[calc(100%-50px)] flex-col gap-2 overflow-y-auto px-2 pb-2">
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">
Transitions for <span className="font-bold">{terminal.FullName}</span>
</div>
{transitions.map((_procedure) => (
<button
key={_procedure.name}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${_procedure.name === transition?.name ? 'outline-2' : ''}`}
onClick={() => {
if (_procedure.name === transition?.name) return;
setTransition(_procedure);
}}
>
{_procedure.name ? _procedure.name : 'ZZZZ'}
</button>
))}
</div>
<div className="flex flex-col gap-2">
<div className="sticky top-0 -mx-2 bg-gray-500 px-2 text-lg font-semibold text-white">Charts</div>
{chartIndex
.filter((_chart) => _chart.is_georeferenced)
.map((_chart) => (
<button
key={_chart.index_number}
className={`cursor-pointer rounded border border-gray-300 bg-gray-300 px-2 py-1 text-left font-semibold focus:outline-2 focus:outline-black focus-visible:outline-2 focus-visible:outline-black ${chart?.index_number === _chart.index_number ? 'outline-2' : ''}`}
onClick={async () => {
if (chart?.index_number === _chart.index_number) return;
if (!_chart.bounding_boxes) return;
const planView = _chart.bounding_boxes.planview;
const chartImage = await charts.getChartImage({ chart: _chart, theme: 'light' });
if (!chartImage) return;
// Crop
const dataURL = await new BrowserImageManipulation()
.loadBlob(chartImage)
.crop(
planView.pixels.x2 - planView.pixels.x1,
planView.pixels.y1 - planView.pixels.y2,
planView.pixels.x1,
planView.pixels.y2
)
.saveAsImage();
const bounds = new L.LatLngBounds(
[planView.latlng.lat1, planView.latlng.lng1],
[planView.latlng.lat2, planView.latlng.lng2]
);
setChart({ data: dataURL, index_number: _chart.index_number, bounds });
}}
>
<span className="font-bold">{_chart.index_number}</span>
<br />
{_chart.name}
</button>
))}
</div>
</div>
</div>
);
};
-94
View File
@@ -1,94 +0,0 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsCF = (
leg: CFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const crsIntoEndpoint = leg.Course.toTrue(previousFix);
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
const targetFix: NavFix = {
latitude: leg.WptLat,
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,
};
const crsToIntercept = leg.Course.toTrue(targetFix);
// Compute overfly arc
if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint)) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
while (!updatedCrsToIntercept.equal(crsToIntercept)) {
let time = 0;
if (leg.TurnDir === 'R') {
//const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = 1; //delta < 1 ? delta : 1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
//const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = 1; //delta < 1 ? delta : 1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((speed / 3600) * time).toMetre(),
lastCourse
);
line.push([arcFix.longitude, arcFix.latitude]);
// Update previousFix
previousFix.latitude = arcFix.latitude;
previousFix.longitude = arcFix.longitude;
updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
let interceptAngle = 0;
if (leg.TurnDir === 'R') Math.abs((interceptAngle = lastCourse - crsToIntercept));
else interceptAngle = Math.abs(crsToIntercept - lastCourse);
if (interceptAngle >= 45) break;
}
}
const interceptFix: NavFix = {
...computeIntersection(previousFix, leg.Course.toTrue(previousFix), targetFix, crsToIntercept.reciprocalCourse())!,
isFlyOver: leg.IsFlyOver,
altitude: leg.Alt ? leg.Alt.parseAltitude() : previousFix.altitude,
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
if (interceptFix.latitude) line.push([interceptFix.longitude, interceptFix.latitude]);
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
-40
View File
@@ -1,40 +0,0 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
export const TerminatorsDF = (
leg: DFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
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,
};
const crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
// Compute overfly
const [line, _, _lastCourse] = generateOverflyArc(
crsIntoEndpoint,
lastCourse,
previousFix,
speed,
leg.TurnDir,
previousFix.latitude.equal(targetFix.latitude) && previousFix.longitude.equal(targetFix.longitude)
);
lastCourse = _lastCourse;
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
-77
View File
@@ -1,77 +0,0 @@
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import Parser from '../parser';
import { computeSpeed } from '../utils/computeSpeed';
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
export const TerminatorsTF = (
leg: TFTerminalEntry,
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: computeSpeed(leg, previousFix),
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
};
const line: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
const trackIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
if (previousFix.isFlyOver) {
let crsIntoEndpoint = trackIntoEndpoint;
// Check if there even is an arc
if (crsIntoEndpoint !== 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 {
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
lastCourse += delta < 1 ? delta : 1;
lastCourse = lastCourse.normaliseDegrees();
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
lastCourse -= delta < 1 ? delta : 1;
lastCourse = lastCourse.normaliseDegrees();
}
const arcFix = computeDestinationPoint(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
((previousFix.speed ? previousFix.speed : Parser.AC_SPEED) / 3600).toMetre(),
lastCourse
);
line.push([arcFix.longitude, arcFix.latitude]);
crsIntoEndpoint = getGreatCircleBearing(arcFix, targetFix);
if (leg.TurnDir === 'R') {
condition = crsIntoEndpoint > trackIntoEndpoint;
} else {
condition = crsIntoEndpoint < trackIntoEndpoint;
}
} while (condition);
}
}
line.push([targetFix.longitude, targetFix.latitude]);
return [targetFix, line];
};
-46
View File
@@ -1,46 +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;
};
Number.prototype.equal = function (other: number) {
return Math.abs((this as number) - other) < 0.1;
};
String.prototype.parseAltitude = function () {
switch (this.length) {
case 5:
return Number.parseInt(this as string);
case 6:
return Number.parseInt(this.substring(0, 5));
case 12: {
const upper = Number.parseInt(this.substring(0, 5));
const lower = Number.parseInt(this.substring(6, 12));
return lower + (upper - lower) / 2;
}
default:
return -1;
}
};
-19
View File
@@ -1,19 +0,0 @@
import readline from 'readline';
export const prompting = (message: string) => {
return new Promise<string | null>((resolve) => {
if (typeof prompt !== 'undefined') {
return resolve(prompt(message));
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(`${message}: `, (id) => {
rl.close();
resolve(id);
});
});
};
+5
View File
@@ -0,0 +1,5 @@
node_modules
.git
.gitignore
*.md
dist
+1
View File
@@ -0,0 +1 @@
v22
+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."
@@ -8,7 +8,7 @@
"build": "tsc -b && vite build",
"lint": "eslint . --ext .ts,.tsx --fix",
"preview": "vite preview",
"parser": "node --experimental-strip-types src/parser/node.ts"
"serve": "pnpm npx http-server dist"
},
"dependencies": {
"browser-image-manipulation": "^0.4.0",
@@ -16,7 +16,6 @@
"geolib": "^3.3.4",
"leaflet": "^1.9.4",
"leaflet-svg-shape-markers": "^1.4.0",
"magvar": "^2.0.0",
"navigraph": "^1.4.1",
"qrcode.react": "^4.2.0",
"react": "^19.1.0",
-8
View File
@@ -23,9 +23,6 @@ importers:
leaflet-svg-shape-markers:
specifier: ^1.4.0
version: 1.4.0
magvar:
specifier: ^2.0.0
version: 2.0.0
navigraph:
specifier: ^1.4.1
version: 1.4.1
@@ -1102,9 +1099,6 @@ packages:
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
magvar@2.0.0:
resolution: {integrity: sha512-00LpSwEJZcnyX3VsdCM2CHSCvB+M6sVQTawLCB3J9oK7eEueNltNx9GFL4YR+/HIkLM+l8rFKiY77JTKnOf0jw==}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -2339,8 +2333,6 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.4
magvar@2.0.0: {}
merge2@1.4.1: {}
micromatch@4.0.8:
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>
);
};
@@ -1,22 +1,26 @@
import { default as L } from 'leaflet';
import 'leaflet-svg-shape-markers';
import { createRef, type FC } from 'react';
import { createRef, Fragment, useEffect, type FC } from 'react';
import { GeoJSON, ImageOverlay, MapContainer, TileLayer } from 'react-leaflet';
interface MapProps {
airport: Airport;
airport: Airport | undefined;
chart: Chart | undefined;
procedure: Procedure | undefined;
procedures: Procedure[];
}
export const Map: FC<MapProps> = ({ airport, chart, 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={[airport.Latitude, airport.Longitude]}
zoom={13}
center={[0, 0]}
zoom={5}
zoomSnap={0}
className="h-full w-full"
ref={(_mapRef) => {
@@ -46,15 +50,15 @@ export const Map: FC<MapProps> = ({ airport, chart, procedure }) => {
/>
)}
{procedure && (
<>
{procedures.map((procedure) => (
<Fragment key={procedure.name}>
<GeoJSON
key={`${procedure.name}-lines`}
data={procedure.data}
style={({ properties }) => ({
color: '#ff00ff',
color: properties.isMAP ? '#00ffff' : '#ff00ff',
stroke: true,
weight: 5,
weight: properties.isMAP ? 2.5 : 5,
opacity: 1,
dashArray: properties.isManual ? '20, 20' : undefined,
})}
@@ -71,12 +75,27 @@ export const Map: FC<MapProps> = ({ airport, chart, procedure }) => {
weight: 3,
}}
pointToLayer={({ properties }, latlng) => {
if (properties.isFlyOver)
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,
});
if (properties.isIntersection) return L.circleMarker(latlng, { radius: 6 });
else if (properties.isInitial)
return L.shapeMarker(latlng, {
shape: 'arrowhead',
radius: 6,
});
return L.shapeMarker(latlng, {
shape: 'star-4',
@@ -98,8 +117,8 @@ export const Map: FC<MapProps> = ({ airport, chart, procedure }) => {
}}
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>
);
};
@@ -11,7 +11,11 @@ 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';
@@ -21,12 +25,6 @@ import { TerminatorsVM } from './terminators/VM';
import { TerminatorsVR } from './terminators/VR';
import './utils/extensions';
/*
Runway IDs for LIED
26156 - 34L
26157 - 34R
*/
class Parser {
private static _instance: Parser;
@@ -37,11 +35,19 @@ class Parser {
private _procedures: TerminalEntry[] = [];
public static MAGVAR: Uint8Array;
public static AC_SPEED = 250;
public static AC_BANK = 30;
public static AC_VS = 1400;
private constructor(airports: Airport[], waypoints: Waypoint[], runways: Runway[], terminals: Terminal[]) {
private constructor(
magVar: Uint8Array,
airports: Airport[],
waypoints: Waypoint[],
runways: Runway[],
terminals: Terminal[]
) {
Parser.MAGVAR = magVar;
this._airports = airports;
this._waypoints = waypoints;
this._runways = runways;
@@ -50,12 +56,13 @@ class Parser {
public static instance = async () => {
if (!Parser._instance) {
const magVar = await (await fetch('NavData/magdec.bgl')).bytes();
const airports = await (await fetch('NavData/Airports.json')).json();
const waypoints = await (await fetch('NavData/Waypoints.json')).json();
const runways = await (await fetch('NavData/Runways.json')).json();
const terminals = await (await fetch('NavData/Terminals.json')).json();
Parser._instance = new Parser(airports, waypoints, runways, terminals);
Parser._instance = new Parser(magVar, airports, waypoints, runways, terminals);
}
return Parser._instance;
@@ -84,7 +91,7 @@ class Parser {
).json()) as TerminalEntry[];
};
public parse = (runway: Runway, transition: string) => {
public parse = (runway: Runway, terminal: Terminal, transition: string) => {
// Private functions
/**
* @param line Line segments
@@ -119,37 +126,67 @@ class Parser {
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;
const procedure = this._procedures.filter(({ Transition }) => !Transition || Transition === transition);
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)!;
const waypoint = this.waypoints.filter(({ ID }) => ID === leg.WptID)[0];
legOptions.isMAP ||= previousFix.IsMAP ?? false;
switch (leg.TrackCode) {
case 'AF': {
const [fixToAdd, lineToAdd] = TerminatorsAF(leg as AFTerminalEntry, previousFix, waypoint);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CA': {
const [fixToAdd, lineToAdd] = TerminatorsCA(leg as CATerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CD': {
const [fixToAdd, lineToAdd] = TerminatorsCD(leg as CDTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CF': {
@@ -159,7 +196,7 @@ class Parser {
lastCourse,
waypoint
);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CI': {
@@ -169,53 +206,75 @@ class Parser {
{ ...previousFix }, // COPY
lastCourse
);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'CR': {
const [fixToAdd, lineToAdd] = TerminatorsCR(leg as CRTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'DF': {
const [fixToAdd, lineToAdd] = TerminatorsDF(leg as DFTerminalEntry, previousFix, lastCourse, waypoint);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FA': {
const [fixToAdd, lineToAdd] = TerminatorsFA(leg as FATerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FC': {
const [fixToAdd, lineToAdd] = TerminatorsFC(leg as FCTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FD': {
const [fixToAdd, lineToAdd] = TerminatorsFD(leg as FDTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'FM': {
const [fixToAdd, lineToAdd] = TerminatorsFM(leg as FMTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, { isManual: true });
update(fixToAdd, lineToAdd, { isManual: true, ...legOptions });
// Make overfly
navFixes.at(-1)!.isFlyOver = true;
break;
}
case 'HA':
case 'HF':
case 'HM':
console.error('Unknown TrackCode', leg.TrackCode);
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);
navFixes.length = 0;
navFixes.push(fixToAdd);
// Only Runway, replace
if (navFixes.length <= 1) {
navFixes.push(fixToAdd);
lastCourse = -1;
}
break;
}
case 'PI':
console.error('Unknown TrackCode', leg.TrackCode);
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) {
@@ -223,23 +282,28 @@ class Parser {
lastCourse = (leg as RFTerminalEntry).Course?.toTrue(fixToAdd);
}
if (lineToAdd) {
lineSegments.push({ line: lineToAdd });
lineSegments.push({ line: lineToAdd, ...legOptions });
}
break;
}
case 'TF': {
const [fixToAdd, lineToAdd] = TerminatorsTF(leg as TFTerminalEntry, previousFix, lastCourse, waypoint);
update(fixToAdd, lineToAdd);
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);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'VD': {
const [fixToAdd, lineToAdd] = TerminatorsVD(leg as VDTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'VI': {
@@ -249,17 +313,19 @@ class Parser {
{ ...previousFix }, // COPY
lastCourse
);
update(fixToAdd, lineToAdd);
update(fixToAdd, lineToAdd, legOptions);
break;
}
case 'VM': {
const [fixToAdd, lineToAdd] = TerminatorsVM(leg as VMTerminalEntry, previousFix, lastCourse);
update(fixToAdd, lineToAdd, { isManual: true });
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);
update(fixToAdd, lineToAdd, legOptions);
break;
}
default:
@@ -6,7 +6,7 @@ import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
* @param start Arc origin point
* @param center Arc center point
* @param radius Arc radius in nmi
* @param turnDir
* @param turnDir Turn direction
* @returns Line segments
*/
export const generateAFArc = (
@@ -30,11 +30,11 @@ export const generateAFArc = (
while (crsFromOrigin !== crsIntoEndpoint) {
if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
crsFromOrigin += delta < 1 ? delta : 1;
crsFromOrigin += delta < 0.1 ? delta : 0.1;
crsFromOrigin = crsFromOrigin.normaliseDegrees();
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
crsFromOrigin -= delta < 1 ? delta : 1;
crsFromOrigin -= delta < 0.1 ? delta : 0.1;
crsFromOrigin = crsFromOrigin.normaliseDegrees();
}
if (crsFromOrigin === crsIntoEndpoint) break;
@@ -38,12 +38,12 @@ export const generatePerformanceArc = (
let time = 0;
if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees();
time = increment / turnRate;
}
@@ -70,12 +70,12 @@ export const generatePerformanceArc = (
let time = 0;
if (turnDir === 'R') {
const delta = (crsIntoEndpoint - crsFromOrigin).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (crsFromOrigin - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
crsFromOrigin = (crsFromOrigin - increment).normaliseDegrees();
time = increment / turnRate;
}
@@ -1,12 +1,14 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
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 turnDir
* @param end Arc end point
* @param turnDir Turn Direction
* @returns Line segments
*/
export const generateRFArc = (
@@ -14,6 +16,7 @@ export const generateRFArc = (
crsIntoOrigin: number,
start: NavFix,
center: NavFix,
end: NavFix,
turnDir: TurnDirection
) => {
const line: LineSegment[] = [[start.longitude, start.latitude]];
@@ -26,20 +29,10 @@ export const generateRFArc = (
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();
}
let crsOrthogonalOnOrigin = getGreatCircleBearing(center, start);
const crsOrthogonalOnEndpoint = getGreatCircleBearing(center, end);
const arcRad = getPreciseDistance(center, start);
const arcRad = getDistance(center, start);
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
// Start turn immediately
if (turnDir === 'R') {
crsOrthogonalOnOrigin += crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
@@ -50,11 +43,11 @@ export const generateRFArc = (
while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
if (turnDir === 'R') {
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
crsOrthogonalOnOrigin += delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else {
const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1;
crsOrthogonalOnOrigin -= delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
}
@@ -1,5 +1,5 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { computeIntersection } from '../utils/computeIntersection';
/**
@@ -56,14 +56,19 @@ export const generateTangentArc = (
}
// Generate arc
const arcCenter = computeIntersection(
let arcRad = 0;
let arcCenter = computeIntersection(
start,
crsOrthogonalOnOrigin,
intcArcOnCrsIntoEndpoint,
crsOrthogonalOnEndpoint
);
if (!arcCenter) return null;
const arcRad = getDistance(arcCenter, start);
if (!crsOrthogonalOnEndpoint.equal(crsOrthogonalOnOrigin) && arcCenter)
arcRad = getPreciseDistance(arcCenter, start);
else {
arcRad = getPreciseDistance(start, end) / 2;
arcCenter = computeDestinationPoint(start, arcRad, crsOrthogonalOnOrigin);
}
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.reciprocalCourse();
crsOrthogonalOnEndpoint = crsOrthogonalOnEndpoint.reciprocalCourse();
@@ -74,19 +79,24 @@ export const generateTangentArc = (
crsOrthogonalOnOrigin -= crsOrthogonalOnOrigin < 1 ? crsOrthogonalOnOrigin : 1;
}
let lastDistance = getPreciseDistance(start, end);
while (!crsOrthogonalOnOrigin.equal(crsOrthogonalOnEndpoint)) {
if (turnDir === 'R') {
const delta = (crsOrthogonalOnEndpoint - crsOrthogonalOnOrigin).normaliseDegrees();
crsOrthogonalOnOrigin += delta < 1 ? delta : 1;
crsOrthogonalOnOrigin += delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
} else {
const delta = (crsOrthogonalOnOrigin - crsOrthogonalOnEndpoint).normaliseDegrees();
crsOrthogonalOnOrigin -= delta < 1 ? delta : 1;
crsOrthogonalOnOrigin -= delta < 0.1 ? delta : 0.1;
crsOrthogonalOnOrigin = crsOrthogonalOnOrigin.normaliseDegrees();
}
const arcFix = computeDestinationPoint(arcCenter, arcRad, crsOrthogonalOnOrigin);
const newDistance = getPreciseDistance(arcFix, end);
if (lastDistance <= newDistance && lastDistance < 25) break;
lastDistance = newDistance;
line.push([arcFix.longitude, arcFix.latitude]);
}
}
@@ -7,6 +7,11 @@ export const TerminatorsAF = (
previousFix: NavFix,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const navaid = {
latitude: leg.NavLat,
longitude: leg.NavLon,
};
const targetFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
@@ -16,27 +21,13 @@ export const TerminatorsAF = (
speed: computeSpeed(leg, previousFix),
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
const arcEndCrs = getGreatCircleBearing(
{
latitude: leg.NavLat,
longitude: leg.NavLon,
},
{
latitude: leg.WptLat,
longitude: leg.WptLon,
}
);
const arcEndCrs = getGreatCircleBearing(navaid, targetFix);
const line = generateAFArc(
arcEndCrs,
leg.Course.toTrue({ latitude: leg.NavLat, longitude: leg.NavLon }),
previousFix,
{ latitude: leg.NavLat, longitude: leg.NavLon },
leg.NavDist,
leg.TurnDir
);
const line = generateAFArc(arcEndCrs, leg.Course.toTrue(navaid), previousFix, navaid, leg.NavDist, leg.TurnDir);
return [targetFix, line];
};
@@ -31,6 +31,8 @@ export const TerminatorsCA = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
@@ -1,6 +1,6 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
@@ -23,7 +23,7 @@ export const TerminatorsCD = (
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid);
const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
@@ -44,6 +44,8 @@ export const TerminatorsCD = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
+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];
};
@@ -34,12 +34,12 @@ export const TerminatorsCI = (
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
@@ -68,6 +68,8 @@ export const TerminatorsCI = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
@@ -27,6 +27,8 @@ export const TerminatorsCR = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
+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];
};
@@ -35,6 +35,8 @@ export const TerminatorsFA = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
@@ -1,8 +1,8 @@
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';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
// NOTE: Distance not adjusted for altitude in this demo
export const TerminatorsFC = (
@@ -38,12 +38,12 @@ export const TerminatorsFC = (
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
@@ -80,13 +80,15 @@ export const TerminatorsFC = (
}
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, leg.Distance.toMetre(), lastCourse),
...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]);
@@ -1,6 +1,6 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
@@ -27,7 +27,7 @@ export const TerminatorsFD = (
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid);
const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
@@ -41,13 +41,15 @@ export const TerminatorsFD = (
// Compute intercept of crs from arc end and distance
const targetFix: NavFix = {
...computeDestinationPoint(arcEnd, remainingDistance, lastCourse),
...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]);
@@ -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];
};
@@ -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 TerminatorsHM = (leg: HMTerminalEntry, 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];
};
@@ -10,6 +10,9 @@ export const TerminatorsIF = (leg: IFTerminalEntry, waypoint?: Waypoint): NavFix
speed: leg.SpeedLimit ? leg.SpeedLimit : Parser.AC_SPEED,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
isInitial: true,
};
return targetFix;
@@ -0,0 +1,72 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import { generatePerformanceArc } from '../pathGenerators/generatePerformanceArc';
import { computeIntersection } from '../utils/computeIntersection';
import { computeSpeed } from '../utils/computeSpeed';
export const TerminatorsPI = (
leg: PITerminalEntry,
nextLeg: CFTerminalEntry, // As per NG docs in the TrmLegTypes.json
previousFix: NavFix,
waypoints: [Waypoint?, Waypoint?]
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, previousFix);
const originFix: NavFix = {
latitude: leg.WptLat,
longitude: leg.WptLon,
name: waypoints[0]?.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,
};
const endFix: NavFix = {
latitude: nextLeg.WptLat,
longitude: nextLeg.WptLon,
name: waypoints[1]?.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,
};
const outboundCrs = leg.Course.toTrue(endFix);
const interceptCrs = nextLeg.Course.toTrue(endFix);
const line: LineSegment[] = [
[originFix.longitude, originFix.latitude],
[endFix.longitude, endFix.latitude],
];
// Outbound end
const outEnd = computeDestinationPoint(
endFix,
((speed / 3600) * 60).toMetre(), // 1min leg
outboundCrs
);
line.push([outEnd.longitude, outEnd.latitude]);
// Arc
line.push(...generatePerformanceArc(outboundCrs.reciprocalCourse(), outboundCrs, outEnd, speed, leg.TurnDir));
// Intercept
const interceptFix = computeIntersection(
{
latitude: line.at(-1)![1],
longitude: line.at(-1)![0],
},
outboundCrs.reciprocalCourse(),
endFix,
interceptCrs.reciprocalCourse()
);
line.push([interceptFix!.longitude, interceptFix!.latitude]);
line.push([endFix!.longitude, endFix!.latitude]);
return [undefined, line];
};
@@ -1,6 +1,7 @@
import { generateRFArc } from '../pathGenerators/generateRFArc';
import { computeSpeed } from '../utils/computeSpeed';
// NOTE: Direct entry into an RF does not calculate a usable line, given inbound course is unknown.
export const TerminatorsRF = (
leg: RFTerminalEntry,
previousFix: NavFix,
@@ -16,6 +17,8 @@ export const TerminatorsRF = (
speed: computeSpeed(leg, previousFix),
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
const line = generateRFArc(
@@ -23,6 +26,7 @@ export const TerminatorsRF = (
lastCourse,
previousFix,
{ latitude: leg.CenterLat, longitude: leg.CenterLon },
targetFix,
leg.TurnDir
);
+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 { generateTangentArc } from '../pathGenerators/generateTangentArc';
import { computeSpeed } from '../utils/computeSpeed';
import { computeTurnRate } from '../utils/computeTurnRate';
export const TerminatorsTF = (
leg: TFTerminalEntry,
previousFix: NavFix,
lastCourse: number,
waypoint?: Waypoint
): [NavFix?, LineSegment[]?] => {
const speed = computeSpeed(leg, 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,
};
const crsIntoEndpoint = getGreatCircleBearing(previousFix, targetFix);
// Compute overfly arc
let arc1: LineSegment[] | null = null;
let arc2: LineSegment[] = [[previousFix.longitude, previousFix.latitude]];
if (previousFix.isFlyOver && !lastCourse.equal(crsIntoEndpoint, 0.5)) {
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;
}
if (!arc && previousFix.isFlyOver && (!lastCourse.equal(crsIntoEndpoint) || !lastCourse.equal(crsIntoEndpoint))) {
const turnRate = computeTurnRate(speed, Parser.AC_BANK);
let updatedCrsToIntercept = getGreatCircleBearing(previousFix, targetFix);
// Turn Dir
if (!leg.TurnDir || leg.TurnDir === 'E') {
let prov = lastCourse - crsIntoEndpoint;
prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
leg.TurnDir = prov > 0 ? 'L' : 'R';
}
// Generate arc
let lastDistance = getPreciseDistance(previousFix, targetFix);
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 =
updatedCrsToIntercept >= crsIntoEndpoint ? (lastCourse + increment).normaliseDegrees() : lastCourse;
time = increment / turnRate;
} else {
lastCourse =
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];
};
@@ -32,6 +32,8 @@ export const TerminatorsVA = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
@@ -1,6 +1,6 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getDistance from 'geolib/es/getDistance';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
import getPreciseDistance from 'geolib/es/getPreciseDistance';
import { generateOverflyArc } from '../pathGenerators/generateOverflyArc';
import { computeSpeed } from '../utils/computeSpeed';
@@ -24,7 +24,7 @@ export const TerminatorsVD = (
// Compute distance to fly from arc end
const crsToNavaid = getGreatCircleBearing(arcEnd, navaid);
const distToNavaid = getDistance(arcEnd, navaid);
const distToNavaid = getPreciseDistance(arcEnd, navaid);
let remainingDistance = leg.Distance.toMetre();
// Navaid behind us
if (Math.abs(crsToNavaid - lastCourse) > 90) {
@@ -45,6 +45,8 @@ export const TerminatorsVD = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([targetFix.longitude, targetFix.latitude]);
@@ -1,10 +1,10 @@
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';
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
// NOTE: No wind adjustments to be made, no clue how *that* would draw
export const TerminatorsVI = (
@@ -35,12 +35,12 @@ export const TerminatorsVI = (
let time = 0;
if (leg.TurnDir === 'R') {
const delta = (crsIntoEndpoint - lastCourse).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse + increment).normaliseDegrees();
time = increment / turnRate;
} else {
const delta = (lastCourse - crsIntoEndpoint).normaliseDegrees();
const increment = delta < 1 ? delta : 1;
const increment = delta < 0.1 ? delta : 0.1;
lastCourse = (lastCourse - increment).normaliseDegrees();
time = increment / turnRate;
}
@@ -69,6 +69,8 @@ export const TerminatorsVI = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
@@ -28,6 +28,8 @@ export const TerminatorsVR = (
speed: speed,
speedConstraint: leg.SpeedLimit,
altitudeConstraint: leg.Alt,
IsFAF: leg.IsFAF,
IsMAP: leg.IsMAP,
};
line.push([interceptFix.longitude, interceptFix.latitude]);
@@ -5,7 +5,12 @@
* @param brng2 bearing from Point 2
* @returns Intersection point
*/
export const computeIntersection = (p1: NavFix, brng1: number, p2: NavFix, brng2: number): NavFix | undefined => {
export const computeIntersection = (
p1: NavFix,
brng1: number,
p2: NavFix,
brng2: number
): NavFix | undefined | null => {
if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);
@@ -43,7 +48,7 @@ export const computeIntersection = (p1: NavFix, brng1: number, p2: NavFix, brng2
const α2 = θ21 - θ23; // angle 1-2-3
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return undefined; // infinite intersections
if (Math.sin(α1) * Math.sin(α2) < 0) return undefined; // ambiguous intersection (antipodal/360°)
if (Math.sin(α1) * Math.sin(α2) < 0) return p2; // ambiguous intersection (antipodal/360°)
const cosα3 = -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);
@@ -0,0 +1,80 @@
import Parser from '../parser';
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 toOffset = (lat: number, lon: number) =>
lon >= 0 ? lon * 362 + lat * 2 + 316 : (lon + 360) * 362 + lat * 2 + 316;
const magVarTable = Parser.MAGVAR;
const lowerLat = Math.floor(fix.latitude);
const upperLat = Math.ceil(fix.latitude);
const ratioLat = fix.latitude - lowerLat;
const lowerLon = Math.floor(fix.longitude);
const upperLon = Math.ceil(fix.longitude);
const ratioLon = fix.longitude - lowerLon;
const lowerLatLowerLonOffset = toOffset(lowerLat, lowerLon);
const lowerLatUpperLonOffset = toOffset(lowerLat, upperLon);
const upperLatLowerLonOffset = toOffset(upperLat, lowerLon);
const upperLatUpperLonOffset = toOffset(upperLat, upperLon);
let lowerLatLowerLonMagVar = (magVarTable[lowerLatLowerLonOffset + 1] << 8) + magVarTable[lowerLatLowerLonOffset];
lowerLatLowerLonMagVar = (lowerLatLowerLonMagVar * 0x168) / 0x10000;
lowerLatLowerLonMagVar = lowerLatLowerLonMagVar > 180 ? lowerLatLowerLonMagVar - 360 : lowerLatLowerLonMagVar;
let lowerLatUpperLonMagVar = (magVarTable[lowerLatUpperLonOffset + 1] << 8) + magVarTable[lowerLatUpperLonOffset];
lowerLatUpperLonMagVar = (lowerLatUpperLonMagVar * 0x168) / 0x10000;
lowerLatUpperLonMagVar = lowerLatUpperLonMagVar > 180 ? lowerLatUpperLonMagVar - 360 : lowerLatUpperLonMagVar;
let upperLatLowerLonMagVar = (magVarTable[upperLatLowerLonOffset + 1] << 8) + magVarTable[upperLatLowerLonOffset];
upperLatLowerLonMagVar = (upperLatLowerLonMagVar * 0x168) / 0x10000;
upperLatLowerLonMagVar = upperLatLowerLonMagVar > 180 ? upperLatLowerLonMagVar - 360 : upperLatLowerLonMagVar;
let upperLatUpperLonMagVar = (magVarTable[upperLatUpperLonOffset + 1] << 8) + magVarTable[upperLatUpperLonOffset];
upperLatUpperLonMagVar = (upperLatUpperLonMagVar * 0x168) / 0x10000;
upperLatUpperLonMagVar = upperLatUpperLonMagVar > 180 ? upperLatUpperLonMagVar - 360 : upperLatUpperLonMagVar;
const lowerLatRatioLon = lowerLatLowerLonMagVar + ratioLon * (lowerLatUpperLonMagVar - lowerLatLowerLonMagVar);
const upperLatRatioLon = upperLatLowerLonMagVar + ratioLon * (upperLatUpperLonMagVar - upperLatLowerLonMagVar);
const magVar = lowerLatRatioLon + ratioLat + (upperLatRatioLon - lowerLatRatioLon);
return ((this as number) + magVar).normaliseDegrees();
};
Number.prototype.toMetre = function () {
return (this as number) * 1852.0;
};
Number.prototype.equal = function (other: number, accuracy = 0.1) {
return Math.abs((this as number) - other) <= accuracy;
};
String.prototype.parseAltitude = function () {
switch (this.length) {
case 5:
return Number.parseInt(this as string);
case 6:
return Number.parseInt(this.substring(0, 5));
case 12: {
const upper = Number.parseInt(this.substring(0, 5));
const lower = Number.parseInt(this.substring(6, 12));
return lower + (upper - lower) / 2;
}
default:
return -1;
}
};
@@ -1,3 +1,4 @@
import computeDestinationPoint from 'geolib/es/computeDestinationPoint';
import getGreatCircleBearing from 'geolib/es/getGreatCircleBearing';
/**
@@ -29,8 +30,12 @@ export const getCourseAndFixForIntercepts = (leg: TerminalEntry, origin: NavFix)
}
case 'AF': {
const _leg = leg as AFTerminalEntry;
const fix = { latitude: _leg.WptLat, longitude: _leg.WptLon };
return [_leg.Course.reciprocalCourse().toTrue(fix), fix];
const navAid = { latitude: _leg.NavLat, longitude: _leg.NavLon };
const fix = {
...computeDestinationPoint(navAid, _leg.NavDist.toMetre(), _leg.Course.toTrue(navAid)),
isIntersection: true,
};
return [_leg.Course.toTrue(fix), fix];
}
case 'DF': {
const _leg = leg as DFTerminalEntry;
@@ -26,9 +26,9 @@ export declare global {
*/
toMetre: () => number;
/**
* @returns True if delta is less than 0.1
* @returns True if delta is less than accuracy (default 0.1)
*/
equal: (other: number) => boolean;
equal: (other: number, accuracy?: number) => boolean;
}
interface String {
@@ -75,6 +75,7 @@ export declare global {
ICAO: string;
FullName: string;
RwyID?: number;
Proc: 1 | 2 | 3;
};
type NavFix = {
@@ -84,11 +85,13 @@ export declare global {
speed?: number;
name?: string;
isFlyOver?: boolean;
'marker-color'?: string;
altitudeConstraint?: string;
speedConstraint?: number;
IsFAF?: boolean;
IsMAP?: boolean;
// For map
isIntersection?: boolean;
isInitial?: boolean;
};
type LineSegment = [number, number];

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