@fboes/aerofly-custom-missions 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/dist/dto-flight/AeroflyNavRouteAirports.js +3 -3
  3. package/dist/dto-flight/AeroflyNavRouteBase.js +60 -9
  4. package/dist/dto-flight/AeroflyNavRouteBase.test.js +26 -0
  5. package/dist/dto-flight/AeroflyNavRouteRunway.js +5 -5
  6. package/dist/dto-flight/AeroflyNavRouteTransition.js +3 -3
  7. package/dist/dto-flight/AeroflyNavRouteWaypoint.js +17 -9
  8. package/dist/dto-flight/AeroflyNavigationConfig.js +4 -4
  9. package/dist/dto-flight/AeroflySettingsFlight.js +8 -8
  10. package/dist/dto-flight/AeroflySettingsFuelLoad.js +6 -6
  11. package/dist/node/Convert.js +107 -72
  12. package/dist/node/Convert.test.js +12 -12
  13. package/docs/flight.json +12 -9
  14. package/docs/flight.mcf +13 -9
  15. package/docs/flight.xml +13 -9
  16. package/package.json +1 -1
  17. package/src/dto-flight/AeroflyNavRouteAirports.ts +3 -3
  18. package/src/dto-flight/AeroflyNavRouteBase.test.ts +30 -0
  19. package/src/dto-flight/AeroflyNavRouteBase.ts +71 -10
  20. package/src/dto-flight/AeroflyNavRouteRunway.ts +5 -5
  21. package/src/dto-flight/AeroflyNavRouteTransition.ts +3 -3
  22. package/src/dto-flight/AeroflyNavRouteWaypoint.ts +24 -9
  23. package/src/dto-flight/AeroflyNavigationConfig.ts +4 -4
  24. package/src/dto-flight/AeroflySettingsFlight.ts +17 -8
  25. package/src/dto-flight/AeroflySettingsFuelLoad.ts +11 -7
  26. package/src/node/Convert.test.ts +21 -13
  27. package/src/node/Convert.ts +120 -81
  28. package/types/dto-flight/AeroflyNavRouteBase.d.ts +24 -2
  29. package/types/dto-flight/AeroflyNavRouteBase.d.ts.map +1 -1
  30. package/types/dto-flight/AeroflyNavRouteBase.test.d.ts +2 -0
  31. package/types/dto-flight/AeroflyNavRouteBase.test.d.ts.map +1 -0
  32. package/types/dto-flight/AeroflyNavRouteRunway.d.ts.map +1 -1
  33. package/types/dto-flight/AeroflyNavRouteTransition.d.ts.map +1 -1
  34. package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts +8 -0
  35. package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts.map +1 -1
  36. package/types/dto-flight/AeroflySettingsFlight.d.ts +1 -1
  37. package/types/dto-flight/AeroflySettingsFlight.d.ts.map +1 -1
  38. package/types/dto-flight/AeroflySettingsFuelLoad.d.ts +1 -1
  39. package/types/dto-flight/AeroflySettingsFuelLoad.d.ts.map +1 -1
  40. package/types/node/Convert.d.ts +52 -14
  41. package/types/node/Convert.d.ts.map +1 -1
  42. package/AGENTS.md +0 -43
@@ -1,41 +1,41 @@
1
1
  import { describe, it } from "node:test";
2
2
  import { strict as assert } from "node:assert";
3
- import { Convert } from "./Convert.js";
3
+ import { convertDegreeToMatrix, convertFeetToMeter, convertLonLatToVector, convertMatrixToDegree, convertMeterToFeet, convertVectorToLonLat, } from "./Convert.js";
4
4
  describe("Convert", () => {
5
5
  it("should convert feet to meters correctly", () => {
6
6
  const feet = 1000;
7
- const expectedMeters = 304.79999024640034;
8
- const actualMeters = Convert.convertFeetToMeter(feet);
7
+ const expectedMeters = 304.8;
8
+ const actualMeters = convertFeetToMeter(feet);
9
9
  assert.strictEqual(actualMeters, expectedMeters, `Expected ${feet} feet to be ${expectedMeters} meters, got ${actualMeters} meters`);
10
10
  });
11
11
  it("should convert meters to feet correctly", () => {
12
12
  const meters = 500;
13
- const expectedFeet = 1640.42;
14
- const actualFeet = Convert.convertMeterToFeet(meters);
15
- assert.strictEqual(actualFeet, expectedFeet, `Expected ${meters} meters to be ${expectedFeet} feet, got ${actualFeet} feet`);
13
+ const expectedFeet = 1640.4199;
14
+ const actualFeet = convertMeterToFeet(meters);
15
+ assert.strictEqual(actualFeet.toPrecision(4), expectedFeet.toPrecision(4), `Expected ${meters} meters to be ${expectedFeet} feet, got ${actualFeet} feet`);
16
16
  });
17
17
  it("should convert longitude / latitude to vector and back correctly", () => {
18
18
  const longitude = -122.3088;
19
19
  const latitude = 47.4502;
20
20
  const expectedLongitude = -122.3088;
21
21
  const expectedLatitude = 47.4502;
22
- const vector = Convert.convertLonLatToVector(longitude, latitude, 0);
23
- const lonLat = Convert.convertVectorToLonLat(vector);
22
+ const vector = convertLonLatToVector(longitude, latitude, 0);
23
+ const lonLat = convertVectorToLonLat(vector);
24
24
  assert.strictEqual(lonLat.longitude.toFixed(4), expectedLongitude.toFixed(4), `Expected longitude to be ${expectedLongitude.toFixed(4)}, got ${lonLat.longitude.toFixed(4)}`);
25
25
  assert.strictEqual(lonLat.latitude.toFixed(4), expectedLatitude.toFixed(4), `Expected latitude to be ${expectedLatitude.toFixed(4)}, got ${lonLat.latitude.toFixed(4)}`);
26
26
  });
27
27
  it("should convert heading degree to orientation matrix and back correctly", () => {
28
28
  const orientationMatrix = [0, -1, 0, 1, 0, 0, 0, 0, 1];
29
29
  const expectedHeadingDegree = 90;
30
- const actualHeadingDegree = Convert.convertMatrixToDegree(orientationMatrix);
30
+ const actualHeadingDegree = convertMatrixToDegree(orientationMatrix);
31
31
  assert.strictEqual(actualHeadingDegree.toFixed(4), expectedHeadingDegree.toFixed(4), `Expected heading degree to be ${expectedHeadingDegree.toFixed(4)}, got ${actualHeadingDegree.toFixed(4)}`);
32
32
  });
33
33
  it("should convert heading degree to orientation matrix and back correctly", () => {
34
34
  const heading_degree = 90;
35
35
  const expectedHeadingDegree = 90;
36
- const orientationMatrix = Convert.convertDegreeToMatrix(heading_degree);
36
+ const orientationMatrix = convertDegreeToMatrix(heading_degree);
37
37
  console.log("Orientation Matrix:", orientationMatrix);
38
- const actualHeadingDegree = Convert.convertMatrixToDegree(orientationMatrix);
38
+ const actualHeadingDegree = convertMatrixToDegree(orientationMatrix);
39
39
  assert.strictEqual(actualHeadingDegree.toFixed(4), expectedHeadingDegree.toFixed(4), `Expected heading degree to be ${expectedHeadingDegree.toFixed(4)}, got ${actualHeadingDegree.toFixed(4)}`);
40
40
  });
41
41
  it("should show the orientation at Tellruide correctly", () => {
@@ -44,7 +44,7 @@ describe("Convert", () => {
44
44
  -0.206738179058507, -0.207947329711721, -0.710293000184874, 0.672489228132418,
45
45
  ];
46
46
  const expectedHeadingDegree = 117.3585;
47
- const actualHeadingDegree = Convert.convertMatrixToDegree(orientationMatrix);
47
+ const actualHeadingDegree = convertMatrixToDegree(orientationMatrix);
48
48
  assert.strictEqual(actualHeadingDegree.toFixed(4), expectedHeadingDegree.toFixed(4), `Expected heading degree to be ${expectedHeadingDegree.toFixed(4)}, got ${actualHeadingDegree.toFixed(4)}`);
49
49
  });
50
50
  });
package/docs/flight.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "onGround": true,
18
18
  "airport": "KTEX",
19
19
  "runway": "",
20
- "altitude_ft": 9072.275604893097
20
+ "altitude_ft": 9072.275314580287
21
21
  },
22
22
  "fuelLoadSetting": {
23
23
  "aircraft": "c172",
@@ -56,7 +56,7 @@
56
56
  "longitude": -81.75955555555561,
57
57
  "latitude": 24.55611111111108,
58
58
  "uid": "5033935394767185920",
59
- "elevation_ft": 3.000000096
59
+ "elevation_ft": 3
60
60
  },
61
61
  {
62
62
  "type": "departure_runway",
@@ -65,8 +65,8 @@
65
65
  "latitude": 24.556052777777765,
66
66
  "uid": "5033564859348430848",
67
67
  "direction_degree": null,
68
- "elevation_ft": 3.000000096,
69
- "runwayLength_ft": 4801.000153632001
68
+ "elevation_ft": 3,
69
+ "runwayLength_ft": 4801
70
70
  },
71
71
  {
72
72
  "type": "waypoint",
@@ -74,6 +74,7 @@
74
74
  "longitude": -122.3088,
75
75
  "latitude": 47.4502,
76
76
  "uid": null,
77
+ "navaidUid": null,
77
78
  "flyOver": false,
78
79
  "altitude_ft": null,
79
80
  "navaidFrequency_khz": 260
@@ -84,6 +85,7 @@
84
85
  "longitude": -122.3088,
85
86
  "latitude": 47.4502,
86
87
  "uid": null,
88
+ "navaidUid": null,
87
89
  "flyOver": false,
88
90
  "altitude_ft": null,
89
91
  "navaidFrequency_khz": null
@@ -94,6 +96,7 @@
94
96
  "longitude": -122.3088,
95
97
  "latitude": 47.4502,
96
98
  "uid": null,
99
+ "navaidUid": null,
97
100
  "flyOver": false,
98
101
  "altitude_ft": null,
99
102
  "navaidFrequency_khz": 108200
@@ -107,7 +110,7 @@
107
110
  "airport": "KMIA",
108
111
  "transition": "",
109
112
  "transitionUid": null,
110
- "elevation_ft": 8.000000256
113
+ "elevation_ft": 8
111
114
  },
112
115
  {
113
116
  "type": "destination_runway",
@@ -116,8 +119,8 @@
116
119
  "latitude": 25.80289722222224,
117
120
  "uid": "5108645015094833152",
118
121
  "direction_degree": null,
119
- "elevation_ft": 8.000000256,
120
- "runwayLength_ft": 8600.0002752
122
+ "elevation_ft": 8,
123
+ "runwayLength_ft": 8600
121
124
  },
122
125
  {
123
126
  "type": "destination",
@@ -125,10 +128,10 @@
125
128
  "longitude": -80.29011111111112,
126
129
  "latitude": 25.79536111111115,
127
130
  "uid": "5109231054766614528",
128
- "elevation_ft": 9.000000287999999
131
+ "elevation_ft": 8.999999999999998
129
132
  }
130
133
  ],
131
- "cruiseAltitude_ft": 32000.001024
134
+ "cruiseAltitude_ft": 32000
132
135
  },
133
136
  "visibility_meter": 7999.994999999999,
134
137
  "_missionTitle": "Mission title",
package/docs/flight.mcf CHANGED
@@ -47,41 +47,45 @@
47
47
  >
48
48
  <[tmnavigation_config][navigation][]
49
49
  <[tmnav_route][Route][]
50
- <[float64][CruiseAltitude][9753.6]> // 32001 ft
50
+ <[float64][CruiseAltitude][9753.6]> // 32000 ft
51
51
  <[pointer_list_tmnav_route_way][Ways][]
52
52
  <[tmnav_route_origin][KEYW][0]
53
53
  <[string8u][Identifier][KEYW]>
54
54
  <[vector3_float64][Position][831962.2473395835 -5744690.224311131 2634431.8120697998]> // Lon -81.759556, Lat 24.556111
55
55
  <[uint64][Uid][5033935394767185920]>
56
- <[float64][Elevation][0.9144]> // Elevation 4 ft
56
+ <[float64][Elevation][0.9144]> // Elevation 3 ft
57
57
  >
58
58
  <[tmnav_route_departure_runway][09][1]
59
59
  <[string8u][Identifier][09]>
60
60
  <[vector3_float64][Position][831239.6121807017 -5744797.5451743435 2634425.9351001014]> // Lon -81.766767, Lat 24.556053
61
61
  <[uint64][Uid][5033564859348430848]>
62
- <[float64][Elevation][0.9144]> // Elevation 4 ft
62
+ <[float64][Elevation][0.9144]> // Elevation 3 ft
63
63
  <[float64][RunwayLength][1463.3448]> // Runway length 4801 ft
64
64
  >
65
65
  <[tmnav_route_waypoint][MTH][2]
66
66
  <[string8u][Identifier][MTH]>
67
67
  <[vector3_float64][Position][-2309470.142824746 -3651978.5471377173 4675755.385390146]> // Lon -122.308800, Lat 47.450200
68
- // <[uint64][Uid][]>
68
+ <[uint64][Uid][2956153176169775104]> // Fallback UID, not matching Aerofly FS internal UID
69
69
  <[float64][NavaidFrequency][260000]>
70
+ <[uint64][NavaidUid][0]>
70
71
  <[vector2_float64][Altitude][-1001 100001]> // unrestricted
71
72
  <[bool][FlyOver][false]>
72
73
  >
73
74
  <[tmnav_route_waypoint][MNATE][3]
74
75
  <[string8u][Identifier][MNATE]>
75
76
  <[vector3_float64][Position][-2309470.142824746 -3651978.5471377173 4675755.385390146]> // Lon -122.308800, Lat 47.450200
76
- // <[uint64][Uid][]>
77
+ <[uint64][Uid][2956153176169775104]> // Fallback UID, not matching Aerofly FS internal UID
78
+ <[float64][NavaidFrequency][0]>
79
+ <[uint64][NavaidUid][0]>
77
80
  <[vector2_float64][Altitude][-1001 100001]> // unrestricted
78
81
  <[bool][FlyOver][false]>
79
82
  >
80
83
  <[tmnav_route_waypoint][HST][4]
81
84
  <[string8u][Identifier][HST]>
82
85
  <[vector3_float64][Position][-2309470.142824746 -3651978.5471377173 4675755.385390146]> // Lon -122.308800, Lat 47.450200
83
- // <[uint64][Uid][]>
86
+ <[uint64][Uid][2956153176169775104]> // Fallback UID, not matching Aerofly FS internal UID
84
87
  <[float64][NavaidFrequency][108200000]>
88
+ <[uint64][NavaidUid][0]>
85
89
  <[vector2_float64][Altitude][-1001 100001]> // unrestricted
86
90
  <[bool][FlyOver][false]>
87
91
  >
@@ -91,7 +95,7 @@
91
95
  <[uint64][Uid][5109231054766956544]>
92
96
  <[string8u][Airport][KMIA]>
93
97
  <[vector3_float64][Direction][0 0 0]>
94
- <[float64][Elevation][2.4384]> // Elevation 9 ft
98
+ <[float64][Elevation][2.4384]> // Elevation 8 ft
95
99
  <[string8u][Transition][]>
96
100
  <[uint64][TransitionUid][0]>
97
101
  >
@@ -99,14 +103,14 @@
99
103
  <[string8u][Identifier][08L]>
100
104
  <[vector3_float64][Position][967964.1844961813 -5663741.310440378 2759419.1621422493]> // Lon -80.301542, Lat 25.802897
101
105
  <[uint64][Uid][5108645015094833152]>
102
- <[float64][Elevation][2.4384]> // Elevation 9 ft
106
+ <[float64][Elevation][2.4384]> // Elevation 8 ft
103
107
  <[float64][RunwayLength][2621.28]> // Runway length 8600 ft
104
108
  >
105
109
  <[tmnav_route_destination][KMIA][7]
106
110
  <[string8u][Identifier][KMIA]>
107
111
  <[vector3_float64][Position][969155.3700254696 -5663906.240326158 2758667.49173681]> // Lon -80.290111, Lat 25.795361
108
112
  <[uint64][Uid][5109231054766614528]>
109
- <[float64][Elevation][2.7432]> // Elevation 10 ft
113
+ <[float64][Elevation][2.7432]> // Elevation 9 ft
110
114
  >
111
115
  >
112
116
  >
package/docs/flight.xml CHANGED
@@ -47,41 +47,45 @@
47
47
  </clouds>
48
48
  <navigation type="tmnavigation_config">
49
49
  <Route type="tmnav_route">
50
- <CruiseAltitude type="float64">9753.6</CruiseAltitude> <!-- 32001 ft -->
50
+ <CruiseAltitude type="float64">9753.6</CruiseAltitude> <!-- 32000 ft -->
51
51
  <Ways type="pointer_list_tmnav_route_way">
52
52
  <KEYW type="tmnav_route_origin" index="0">
53
53
  <Identifier type="string8u">KEYW</Identifier>
54
54
  <Position type="vector3_float64">831962.2473395835 -5744690.224311131 2634431.8120697998</Position> <!-- Lon -81.759556, Lat 24.556111 -->
55
55
  <Uid type="uint64">5033935394767185920</Uid>
56
- <Elevation type="float64">0.9144</Elevation> <!-- Elevation 4 ft -->
56
+ <Elevation type="float64">0.9144</Elevation> <!-- Elevation 3 ft -->
57
57
  </KEYW>
58
58
  <09 type="tmnav_route_departure_runway" index="1">
59
59
  <Identifier type="string8u">09</Identifier>
60
60
  <Position type="vector3_float64">831239.6121807017 -5744797.5451743435 2634425.9351001014</Position> <!-- Lon -81.766767, Lat 24.556053 -->
61
61
  <Uid type="uint64">5033564859348430848</Uid>
62
- <Elevation type="float64">0.9144</Elevation> <!-- Elevation 4 ft -->
62
+ <Elevation type="float64">0.9144</Elevation> <!-- Elevation 3 ft -->
63
63
  <RunwayLength type="float64">1463.3448</RunwayLength> <!-- Runway length 4801 ft -->
64
64
  </09>
65
65
  <MTH type="tmnav_route_waypoint" index="2">
66
66
  <Identifier type="string8u">MTH</Identifier>
67
67
  <Position type="vector3_float64">-2309470.142824746 -3651978.5471377173 4675755.385390146</Position> <!-- Lon -122.308800, Lat 47.450200 -->
68
- <!-- Uid type="uint64"></Uid -->
68
+ <Uid type="uint64">2956153176169775104</Uid> <!-- Fallback UID, not matching Aerofly FS internal UID -->
69
69
  <NavaidFrequency type="float64">260000</NavaidFrequency>
70
+ <NavaidUid type="uint64">0</NavaidUid>
70
71
  <Altitude type="vector2_float64">-1001 100001</Altitude> <!-- unrestricted -->
71
72
  <FlyOver type="bool">false</FlyOver>
72
73
  </MTH>
73
74
  <MNATE type="tmnav_route_waypoint" index="3">
74
75
  <Identifier type="string8u">MNATE</Identifier>
75
76
  <Position type="vector3_float64">-2309470.142824746 -3651978.5471377173 4675755.385390146</Position> <!-- Lon -122.308800, Lat 47.450200 -->
76
- <!-- Uid type="uint64"></Uid -->
77
+ <Uid type="uint64">2956153176169775104</Uid> <!-- Fallback UID, not matching Aerofly FS internal UID -->
78
+ <NavaidFrequency type="float64">0</NavaidFrequency>
79
+ <NavaidUid type="uint64">0</NavaidUid>
77
80
  <Altitude type="vector2_float64">-1001 100001</Altitude> <!-- unrestricted -->
78
81
  <FlyOver type="bool">false</FlyOver>
79
82
  </MNATE>
80
83
  <HST type="tmnav_route_waypoint" index="4">
81
84
  <Identifier type="string8u">HST</Identifier>
82
85
  <Position type="vector3_float64">-2309470.142824746 -3651978.5471377173 4675755.385390146</Position> <!-- Lon -122.308800, Lat 47.450200 -->
83
- <!-- Uid type="uint64"></Uid -->
86
+ <Uid type="uint64">2956153176169775104</Uid> <!-- Fallback UID, not matching Aerofly FS internal UID -->
84
87
  <NavaidFrequency type="float64">108200000</NavaidFrequency>
88
+ <NavaidUid type="uint64">0</NavaidUid>
85
89
  <Altitude type="vector2_float64">-1001 100001</Altitude> <!-- unrestricted -->
86
90
  <FlyOver type="bool">false</FlyOver>
87
91
  </HST>
@@ -91,7 +95,7 @@
91
95
  <Uid type="uint64">5109231054766956544</Uid>
92
96
  <Airport type="string8u">KMIA</Airport>
93
97
  <Direction type="vector3_float64">0 0 0</Direction>
94
- <Elevation type="float64">2.4384</Elevation> <!-- Elevation 9 ft -->
98
+ <Elevation type="float64">2.4384</Elevation> <!-- Elevation 8 ft -->
95
99
  <Transition type="string8u"></Transition>
96
100
  <TransitionUid type="uint64">0</TransitionUid>
97
101
  </L08L>
@@ -99,14 +103,14 @@
99
103
  <Identifier type="string8u">08L</Identifier>
100
104
  <Position type="vector3_float64">967964.1844961813 -5663741.310440378 2759419.1621422493</Position> <!-- Lon -80.301542, Lat 25.802897 -->
101
105
  <Uid type="uint64">5108645015094833152</Uid>
102
- <Elevation type="float64">2.4384</Elevation> <!-- Elevation 9 ft -->
106
+ <Elevation type="float64">2.4384</Elevation> <!-- Elevation 8 ft -->
103
107
  <RunwayLength type="float64">2621.28</RunwayLength> <!-- Runway length 8600 ft -->
104
108
  </08L>
105
109
  <KMIA type="tmnav_route_destination" index="7">
106
110
  <Identifier type="string8u">KMIA</Identifier>
107
111
  <Position type="vector3_float64">969155.3700254696 -5663906.240326158 2758667.49173681</Position> <!-- Lon -80.290111, Lat 25.795361 -->
108
112
  <Uid type="uint64">5109231054766614528</Uid>
109
- <Elevation type="float64">2.7432</Elevation> <!-- Elevation 10 ft -->
113
+ <Elevation type="float64">2.7432</Elevation> <!-- Elevation 9 ft -->
110
114
  </KMIA>
111
115
  </Ways>
112
116
  </Route>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fboes/aerofly-custom-missions",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Builder for Aerofly FS4 Custom Missions Files",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -1,5 +1,5 @@
1
1
  import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
- import { Convert } from "../node/Convert.js";
2
+ import { convertFeetToMeter, convertMeterToFeet } from "../node/Convert.js";
3
3
  import { AeroflyNavRouteBase, type AeroflyNavRouteType } from "./AeroflyNavRouteBase.js";
4
4
 
5
5
  class AeroflyNavRouteAirport extends AeroflyNavRouteBase {
@@ -36,11 +36,11 @@ class AeroflyNavRouteAirport extends AeroflyNavRouteBase {
36
36
  * @returns {number | null} elevation in feet, null if not set
37
37
  */
38
38
  get elevation_ft(): number | null {
39
- return this.elevation !== null ? Convert.convertMeterToFeet(this.elevation) : null;
39
+ return this.elevation !== null ? convertMeterToFeet(this.elevation) : null;
40
40
  }
41
41
 
42
42
  set elevation_ft(elevation_ft: number | null) {
43
- this.elevation = elevation_ft !== null ? Convert.convertFeetToMeter(elevation_ft) : null;
43
+ this.elevation = elevation_ft !== null ? convertFeetToMeter(elevation_ft) : null;
44
44
  }
45
45
 
46
46
  getElement(index: number = 0): AeroflyConfigurationNode {
@@ -0,0 +1,30 @@
1
+ import { describe, it } from "node:test";
2
+ import { AeroflyNavRouteDestination } from "../index.js";
3
+
4
+ describe("AeroflyNavRouteBase", () => {
5
+ it("should create a valid AeroflyNavRouteBase structure", () => {
6
+ const waypoints = [
7
+ { wp: new AeroflyNavRouteDestination("MAX-NE", 179.999, 89.999), uid: 0n },
8
+ { wp: new AeroflyNavRouteDestination("MAX-SW", -179.999, -89.999), uid: 0n },
9
+ { wp: new AeroflyNavRouteDestination("KEYW", -81.7599558, 24.5561197), uid: 5033914504046249216n },
10
+ { wp: new AeroflyNavRouteDestination("EGLL", -0.45277777, 51.47138888), uid: 9199731073154483456n },
11
+ { wp: new AeroflyNavRouteDestination("YSSY", 151.177, -33.4961), uid: 16969837613165511936n },
12
+ { wp: new AeroflyNavRouteDestination("RJTT", 139.779, 35.709), uid: 16385890514078663936n },
13
+ ];
14
+
15
+ for (const waypoint of waypoints) {
16
+ console.log(waypoint.wp.getUidFallback(), waypoint.uid);
17
+ }
18
+ });
19
+
20
+ it("should test some unpacking of UIDs", () => {
21
+ const unpackPayload = (packed: bigint) => {
22
+ return Number(packed & 0xffffn);
23
+ };
24
+
25
+ console.log("KEYW", unpackPayload(5033914504046314752n));
26
+ console.log("EGLL", unpackPayload(9200173076798702848n));
27
+ console.log("YSSY", unpackPayload(16969826619723154688n));
28
+ console.log("RJTT", unpackPayload(16385782762532177152n));
29
+ });
30
+ });
@@ -1,5 +1,5 @@
1
- import { Convert, type AeroflyVector3Float } from "../node/Convert.js";
2
- import { AeroflyConfigurationNode, AeroflyConfigurationNodeComment } from "../node/AeroflyConfigurationNode.js";
1
+ import { convertLonLatToVector, convertVectorToLonLat, type AeroflyVector3Float } from "../node/Convert.js";
2
+ import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
3
3
 
4
4
  export type AeroflyNavRouteType =
5
5
  | "origin"
@@ -33,7 +33,7 @@ export class AeroflyNavRouteBase {
33
33
  latitude: number;
34
34
 
35
35
  /**
36
- * @property {?bigint} uid unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission, can be null for new waypoints
36
+ * @property {?bigint} uid unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission. Obviously the UID encodes the geographic position as well as the name of the waypoint.
37
37
  */
38
38
  uid: bigint | null;
39
39
 
@@ -43,7 +43,7 @@ export class AeroflyNavRouteBase {
43
43
  * @param {number} longitude WGS84
44
44
  * @param {number} latitude WGS84
45
45
  * @param {object} [options] additional options for the waypoint
46
- * @param {?bigint} [options.uid] unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission, can be null for new waypoints
46
+ * @param {?bigint} [options.uid] unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission. Obviously the UID encodes the geographic position as well as the name of the waypoint.
47
47
  */
48
48
  constructor(
49
49
  type: AeroflyNavRouteType,
@@ -63,11 +63,11 @@ export class AeroflyNavRouteBase {
63
63
  * @returns {AeroflyVector3Float} to use in Aerofly FS4's `main.mcf`
64
64
  */
65
65
  get position(): AeroflyVector3Float {
66
- return Convert.convertLonLatToVector(this.longitude, this.latitude, 0);
66
+ return convertLonLatToVector(this.longitude, this.latitude, 0);
67
67
  }
68
68
 
69
69
  set position(position: AeroflyVector3Float) {
70
- const latLonAlt = Convert.convertVectorToLonLat(position);
70
+ const latLonAlt = convertVectorToLonLat(position);
71
71
  this.longitude = latLonAlt.longitude;
72
72
  this.latitude = latLonAlt.latitude;
73
73
  }
@@ -91,15 +91,76 @@ export class AeroflyNavRouteBase {
91
91
  this.position,
92
92
  `Lon ${this.longitude.toFixed(6)}, Lat ${this.latitude.toFixed(6)}`,
93
93
  )
94
- .append(
95
- this.uid
96
- ? new AeroflyConfigurationNode("uint64", "Uid", this.uid)
97
- : new AeroflyConfigurationNodeComment("uint64", "Uid", ""),
94
+ .appendChild(
95
+ "uint64",
96
+ "Uid",
97
+ this.uid ?? this.getUidFallback(),
98
+ this.uid ? "" : "Fallback UID, not matching Aerofly FS internal UID",
98
99
  );
99
100
 
100
101
  return element;
101
102
  }
102
103
 
104
+ /**
105
+ * @returns {bigint} 24 bit longitude
106
+ * @see https://www.aerofly.com/community/forum/index.php?thread/29490-navdata-coordinates-to-uid/
107
+ */
108
+ private encodeLongitude(): bigint {
109
+ let scaled = this.longitude / 180;
110
+ scaled = 65536.0 * (0.5 + 0.5 * scaled); // 16 bit
111
+ return BigInt(Math.round(256.0 * scaled + 0.5)); // 24 bit
112
+ }
113
+
114
+ /**
115
+ * @returns {bigint} 24 bit latitude
116
+ * @see https://www.aerofly.com/community/forum/index.php?thread/29490-navdata-coordinates-to-uid/
117
+ */
118
+ private encodeLatitude(): bigint {
119
+ const worldGridConstantA = 2.3311223704144;
120
+
121
+ let scaled = this.latitude / 180;
122
+ scaled = Math.tan(worldGridConstantA * scaled) / worldGridConstantA;
123
+ scaled = 65536.0 * (0.5 + 0.5 * scaled); // 16 bit
124
+ return BigInt(Math.round(256.0 * scaled + 0.5)); // 24 bit
125
+ }
126
+
127
+ /**
128
+ * @returns {number} 16 bit type
129
+ */
130
+ private encodeWaypointType(): number {
131
+ // Type code (airport (0500) / runway (0800) / SID (4400) / STAR (4800) / Approach (4C00) / RNAV waypoint (C000) / airways (4000)
132
+ switch (this.type) {
133
+ case "origin":
134
+ case "destination":
135
+ return 0x0500;
136
+ case "departure_runway":
137
+ case "destination_runway":
138
+ return 0x0800;
139
+ case "departure":
140
+ return 0x4400;
141
+ case "arrival":
142
+ return 0x4800;
143
+ case "approach":
144
+ return 0x4c00;
145
+ }
146
+ return 0;
147
+ }
148
+
149
+ /**
150
+ * @returns {bigint} Packs both into a single 64-bit value:
151
+ * - Bits 63..40 → longitude (24 bit)
152
+ * - Bits 39..16 → latitude (24 bit)
153
+ * - Bits 15..0 → payload (16 bit)
154
+ * @see https://www.aerofly.com/community/forum/index.php?thread/29490-navdata-coordinates-to-uid/
155
+ */
156
+ getUidFallback(): bigint {
157
+ return (
158
+ (this.encodeLongitude() << 40n) |
159
+ (this.encodeLatitude() << 16n) |
160
+ (BigInt(this.encodeWaypointType()) & 0xffffn)
161
+ );
162
+ }
163
+
103
164
  toJSON() {
104
165
  return {
105
166
  ...this,
@@ -1,4 +1,4 @@
1
- import { type AeroflyVector3Float, Convert } from "../node/Convert.js";
1
+ import { convertFeetToMeter, convertMeterToFeet, type AeroflyVector3Float } from "../node/Convert.js";
2
2
  import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
3
3
  import { AeroflyNavRouteBase, type AeroflyNavRouteType } from "./AeroflyNavRouteBase.js";
4
4
 
@@ -44,22 +44,22 @@ class AeroflyNavRouteRunway extends AeroflyNavRouteBase {
44
44
  * @returns {number | null} elevation in feet, null if not set
45
45
  */
46
46
  get elevation_ft(): number | null {
47
- return this.elevation !== null ? Convert.convertMeterToFeet(this.elevation) : null;
47
+ return this.elevation !== null ? convertMeterToFeet(this.elevation) : null;
48
48
  }
49
49
 
50
50
  set elevation_ft(elevation_ft: number | null) {
51
- this.elevation = elevation_ft !== null ? Convert.convertFeetToMeter(elevation_ft) : null;
51
+ this.elevation = elevation_ft !== null ? convertFeetToMeter(elevation_ft) : null;
52
52
  }
53
53
 
54
54
  /**
55
55
  * @returns {number | null} runway length in feet, null if not set
56
56
  */
57
57
  get runwayLength_ft(): number | null {
58
- return this.runwayLength !== null ? Convert.convertMeterToFeet(this.runwayLength) : null;
58
+ return this.runwayLength !== null ? convertMeterToFeet(this.runwayLength) : null;
59
59
  }
60
60
 
61
61
  set runwayLength_ft(runwayLength_ft: number | null) {
62
- this.runwayLength = runwayLength_ft !== null ? Convert.convertFeetToMeter(runwayLength_ft) : null;
62
+ this.runwayLength = runwayLength_ft !== null ? convertFeetToMeter(runwayLength_ft) : null;
63
63
  }
64
64
 
65
65
  /**
@@ -1,4 +1,4 @@
1
- import { type AeroflyVector3Float, Convert } from "../node/Convert.js";
1
+ import { convertFeetToMeter, convertMeterToFeet, type AeroflyVector3Float } from "../node/Convert.js";
2
2
  import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
3
3
  import { AeroflyNavRouteBase, type AeroflyNavRouteType } from "./AeroflyNavRouteBase.js";
4
4
 
@@ -41,11 +41,11 @@ class AeroflyNavRouteTransition extends AeroflyNavRouteBase {
41
41
  * @returns {number | null} elevation in feet, null if not set
42
42
  */
43
43
  get elevation_ft(): number | null {
44
- return this.elevation !== null ? Convert.convertMeterToFeet(this.elevation) : null;
44
+ return this.elevation !== null ? convertMeterToFeet(this.elevation) : null;
45
45
  }
46
46
 
47
47
  set elevation_ft(elevation_ft: number | null) {
48
- this.elevation = elevation_ft !== null ? Convert.convertFeetToMeter(elevation_ft) : null;
48
+ this.elevation = elevation_ft !== null ? convertFeetToMeter(elevation_ft) : null;
49
49
  }
50
50
 
51
51
  /**
@@ -1,4 +1,10 @@
1
- import { Convert, type AeroflyVector3Float } from "../node/Convert.js";
1
+ import {
2
+ convertFeetToMeter,
3
+ convertLonLatToVector,
4
+ convertMeterToFeet,
5
+ convertVectorToLonLat,
6
+ type AeroflyVector3Float,
7
+ } from "../node/Convert.js";
2
8
  import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
3
9
  import { AeroflyNavRouteBase } from "./AeroflyNavRouteBase.js";
4
10
 
@@ -8,6 +14,11 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
8
14
  */
9
15
  navaidFrequency: number | null;
10
16
 
17
+ /**
18
+ * @property {?bigint} navaidUid if the waypoint is a navaid, its unique identifier, must match Aerofly FS internal UID if used in an existing mission
19
+ */
20
+ navaidUid: bigint | null;
21
+
11
22
  /**
12
23
  * @property {number | null} altitude in meter, null if not set
13
24
  */
@@ -24,6 +35,9 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
24
35
  * @param {number} latitude WGS84
25
36
  * @param {object} [options] additional options for the waypoint
26
37
  * @param {?number} [options.navaidFrequency] if the waypoint is a navaid, its frequency in Hz
38
+ * @param {?number} [options.navaidFrequency_khz] if the waypoint is a navaid, its frequency in kHz, will override navaidFrequency in Hz if provided
39
+ * @param {?number} [options.navaidFrequency_mhz] if the waypoint is a navaid, its frequency in MHz, will override navaidFrequency in Hz if provided
40
+ * @param {?bigint} [options.navaidUid] if the waypoint is a navaid, its unique identifier, must match Aerofly FS internal UID if used in an existing mission
27
41
  * @param {number} [options.altitude] in meter
28
42
  * @param {?number} [options.altitude_ft] altitude in feet, will override altitude in meter if provided
29
43
  * @param {boolean} [options.flyOver] if true, the waypoint is meant to be flown over, otherwise it can be used as a fly-by waypoint
@@ -37,6 +51,7 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
37
51
  navaidFrequency = null,
38
52
  navaidFrequency_khz = null,
39
53
  navaidFrequency_mhz = null,
54
+ navaidUid = null,
40
55
  altitude = null,
41
56
  altitude_ft = null,
42
57
  flyOver = false,
@@ -45,6 +60,7 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
45
60
  ) {
46
61
  super("waypoint", identifier, longitude, latitude, { uid });
47
62
  this.navaidFrequency = navaidFrequency;
63
+ this.navaidUid = navaidUid;
48
64
  this.altitude = altitude;
49
65
 
50
66
  if (altitude_ft !== null) {
@@ -64,11 +80,11 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
64
80
  * @returns {number | null} altitude in feet, null if not set
65
81
  */
66
82
  get altitude_ft(): number | null {
67
- return this.altitude !== null ? Convert.convertMeterToFeet(this.altitude) : null;
83
+ return this.altitude !== null ? convertMeterToFeet(this.altitude) : null;
68
84
  }
69
85
 
70
86
  set altitude_ft(altitude_ft: number | null) {
71
- this.altitude = altitude_ft !== null ? Convert.convertFeetToMeter(altitude_ft) : null;
87
+ this.altitude = altitude_ft !== null ? convertFeetToMeter(altitude_ft) : null;
72
88
  }
73
89
 
74
90
  get navaidFrequency_khz(): number | null {
@@ -91,11 +107,11 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
91
107
  * @returns {AeroflyVector3Float} to use in Aerofly FS4's `main.mcf`
92
108
  */
93
109
  get position(): AeroflyVector3Float {
94
- return Convert.convertLonLatToVector(this.longitude, this.latitude, this.altitude || 0);
110
+ return convertLonLatToVector(this.longitude, this.latitude, this.altitude || 0);
95
111
  }
96
112
 
97
113
  set position(position: AeroflyVector3Float) {
98
- const { longitude, latitude, altitude_meter } = Convert.convertVectorToLonLat(position);
114
+ const { longitude, latitude, altitude_meter } = convertVectorToLonLat(position);
99
115
  this.longitude = longitude;
100
116
  this.latitude = latitude;
101
117
  this.altitude = altitude_meter;
@@ -104,11 +120,9 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
104
120
  getElement(index: number = 0): AeroflyConfigurationNode {
105
121
  const element = super.getElement(index);
106
122
 
107
- if (this.navaidFrequency) {
108
- element.appendChild("float64", "NavaidFrequency", this.navaidFrequency);
109
- }
110
-
111
123
  element
124
+ .appendChild("float64", "NavaidFrequency", this.navaidFrequency ?? 0)
125
+ .appendChild("uint64", "NavaidUid", this.navaidUid ?? BigInt(0))
112
126
  .appendChild(
113
127
  "vector2_float64",
114
128
  "Altitude",
@@ -128,6 +142,7 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
128
142
  altitude_ft: this.altitude_ft,
129
143
  navaidFrequency: undefined,
130
144
  navaidFrequency_khz: this.navaidFrequency_khz,
145
+ navaidUid: this.navaidUid !== null ? this.navaidUid.toString() : null,
131
146
  };
132
147
  }
133
148
  }