@fboes/aerofly-custom-missions 1.0.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.
package/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ root = true
4
+
5
+ [*]
6
+ end_of_line = lf
7
+ insert_final_newline = true
8
+ trim_trailing_whitespace = true
9
+ charset = utf-8
10
+ indent_style = space
11
+ indent_size = 2
12
+ max_line_length = 120
@@ -0,0 +1,3 @@
1
+ *.json
2
+ dist/**/*.js
3
+ .vscode
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ - Initial commit
package/LICENSE.txt ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2024 Frank Boës
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # Aerofly FS4 Custom Mission
2
+
3
+ > Builder for Aerofly FS4 Custom Missions Files
4
+
5
+ [Aerofly Flight Simulator 4](https://www.aerofly.com/) has a custom mission file `custom_missions_user.tmc` with a very unique format. To help build this format file, this JavaScript / TypeScript library offers Data Transfers Objects (DTOs) to create this flight plan files programatically.
6
+
7
+ This library is intended to work in modern browsers as well as [Node.js](https://nodejs.org/en).
8
+
9
+ ## Installation
10
+
11
+ Either download the [`dist/index.js`](dist/index.js) to a sensible location in your web project, or do a NPM installation:
12
+
13
+ ```bash
14
+ npm install @fboes/aerofly-custom-missions --save
15
+ ```
16
+
17
+ Instead of a local installation you may also load the library from https://unpkg.com/. Beware: This makes https://unpkg.com/ a dependency of your project and may pose data protection issues.
18
+
19
+ ```html
20
+ <script type="module" src="https://unpkg.com/@fboes/aerofly-custom-missions@latest/dist/index.js"></script>
21
+ ```
22
+
23
+ Everything required for the functionality of this library is contained in [`dist/index.js`](dist/index.js).
24
+
25
+ ## Usage
26
+
27
+ Loading the library prior to use:
28
+
29
+ ```javascript
30
+ // 1. NodeJS - NPM installation
31
+ import {
32
+ AeroflyMissionsList,
33
+ AeroflyMission,
34
+ AeroflyMissionConditions,
35
+ AeroflyMissionConditionsCloud,
36
+ AeroflyMissionCheckpoint,
37
+ } from "@fboes/aerofly-custom-missions";
38
+
39
+ // 2. Local installation and/or browser usage
40
+ import {
41
+ AeroflyMissionsList,
42
+ AeroflyMission,
43
+ AeroflyMissionConditions,
44
+ AeroflyMissionConditionsCloud,
45
+ AeroflyMissionCheckpoint,
46
+ } from "dist/index.js";
47
+ ```
48
+
49
+ You might want to enable TypeScript type checking by adding `// @ts-check` as your first line in your scripts.
50
+
51
+ ### Basic idea
52
+
53
+ All objects are basic structures needed for the mission list. The constructors tell you which properties are required, and will start with sensible defaults for all other properties.
54
+
55
+ You can alter the properties of the objects afterwards, or (in some cases) by passing an optional configuration object to the constructor.
56
+
57
+ All objects can be exported as JSON or as string via the `toString()` methods. Exporting the `AeroflyMissionsList` via `toString()` gives you the complete source code for a valid `custom_missions_user.tmc`.
58
+
59
+ ### Building a missions file
60
+
61
+ A mission file contains multiple missions. Build this file starts with the outer container, wich contains the missions:
62
+
63
+ ```javascript
64
+ // Build a missions list
65
+ const missionList = new AeroflyMissionsList();
66
+
67
+ // You can now add missions to this `missionList`:
68
+ missionList.missions.push(new AeroflyMission("Mission 1"));
69
+ missionList.missions.push(new AeroflyMission("Mission 2"));
70
+
71
+ // Show output of actual missions file
72
+ console.log(missionList.toString());
73
+ ```
74
+
75
+ ### Building a mission
76
+
77
+ A mission needs multiple properties:
78
+
79
+ - The aircraft, its position and state
80
+ - The time and weather conditions
81
+ - The actual flight plan
82
+
83
+ ```javascript
84
+ // Build time and weather
85
+ const conditions = new AeroflyMissionConditions({
86
+ time: new Date(),
87
+ wind: {
88
+ direction: 190,
89
+ speed: 11,
90
+ gusts: 22,
91
+ },
92
+ visibility: 25000,
93
+ clouds: [new AeroflyMissionConditionsCloud(0.1, 1524), new AeroflyMissionConditionsCloud(0.2, 2286)],
94
+ });
95
+
96
+ // Build checkpoints
97
+ const checkpoints = [
98
+ new AeroflyMissionCheckpoint("KCCR", "origin", -122.057, 37.9897),
99
+ new AeroflyMissionCheckpoint("19L", "departure_runway", -122.055, 37.993),
100
+ new AeroflyMissionCheckpoint("24", "destination_runway", -70.607, 41.399),
101
+ new AeroflyMissionCheckpoint("KMVY", "destination", -70.6139, 41.3934),
102
+ ];
103
+
104
+ // Build mission
105
+ const mission = new AeroflyMission("From Concord to Martha's Vineyard", {
106
+ aircraft: {
107
+ name: "c172",
108
+ livery: "",
109
+ icao: "C172",
110
+ },
111
+ checkpoints,
112
+ conditions,
113
+ });
114
+
115
+ // Build mission list
116
+ const missionList = new AeroflyMissionsList([mission]);
117
+
118
+ // Show output of actual missions file
119
+ console.log(missionList.toString());
120
+ ```
121
+
122
+ As there are lots of properties for the flight plan as well as explanation for the properties mentioned above, check the type hinting on the various objects to find out which properties you are able to set.
123
+
124
+ ### Important notices
125
+
126
+ - Be aware that `mission.origin` and `mission.destination` do not need to match the flight plan. In case of `origin` you may want to set the position to the actual parking position of your aircraft, which may not be the first way point in your flight plan.
127
+ - Flightplan almost always require at least 4 checkpoint:
128
+ - `origin`
129
+ - `departure_runway`
130
+ - `destination_runway`
131
+ - `destination`
132
+ - Be aware that all untis for altitude, elevation or distance are measured in meters! In most cases there will be helper functions for defining these values in feet or statute miles (for visbility).
133
+
134
+ ## Status
135
+
136
+ [![GitHub version](https://badge.fury.io/gh/fboes%2Faerofly-custom-missions.svg)](https://badge.fury.io/gh/fboes%2Faerofly-custom-missions)
137
+ [![`npm` version](https://badge.fury.io/js/%40fboes%2Faerofly-custom-missions.svg)](https://badge.fury.io/js/%40fboes%2Faerofly-custom-missions)
138
+ ![MIT license](https://img.shields.io/github/license/fboes/aerofly-custom-missions.svg)
139
+
140
+ ## Legal stuff
141
+
142
+ Author: [Frank Boës](https://3960.org)
143
+
144
+ Copyright & license: See [LICENSE.txt](LICENSE.txt)
package/dist/index.js ADDED
@@ -0,0 +1,418 @@
1
+ const feetPerMeter = 3.28084;
2
+ const meterPerStatuteMile = 1609.344;
3
+ /**
4
+ * A list of flight plans.
5
+ *
6
+ * The purpose of this class is to collect data needed for Aerofly FS4's
7
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
8
+ * for this file via the `toString()` method.
9
+ */
10
+ export class AeroflyMissionsList {
11
+ /**
12
+ * @param missions to add to mission list
13
+ */
14
+ constructor(missions = []) {
15
+ this.missions = missions;
16
+ }
17
+ /**
18
+ * @returns String to use in Aerofly FS4's `custom_missions_user.tmc`
19
+ */
20
+ toString() {
21
+ const separator = "\n// -----------------------------------------------------------------------------\n";
22
+ return `<[file][][]
23
+ <[tmmissions_list][][]
24
+ <[list_tmmission_definition][missions][]${separator + this.missions.join(separator) + separator} >
25
+ >
26
+ >`;
27
+ }
28
+ }
29
+ /**
30
+ * A single flighplan, containing aircraft and weather data as well.
31
+ *
32
+ * The purpose of this class is to collect data needed for Aerofly FS4's
33
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
34
+ * for this file via the `toString()` method.
35
+ */
36
+ export class AeroflyMission {
37
+ /**
38
+ *
39
+ * @param title of this flight plan
40
+ * @param additionalAttributes allows to set additional attributes on creation
41
+ */
42
+ constructor(title, additionalAttributes = {}) {
43
+ /**
44
+ * Additional description text, mission briefing, etc
45
+ */
46
+ this.description = "";
47
+ /**
48
+ * Flight settings of aircraft, like "taxi", "cruise"
49
+ */
50
+ this.flightSetting = "taxi";
51
+ /**
52
+ * Aircraft for this mission
53
+ */
54
+ this.aircraft = {
55
+ name: "c172",
56
+ livery: "",
57
+ icao: "",
58
+ };
59
+ /**
60
+ * Uppercase callsign of aircraft
61
+ */
62
+ this.callsign = "";
63
+ /**
64
+ * Starting position of aircraft, as well as name of starting airport. Position does not have match airport.
65
+ */
66
+ this.origin = {
67
+ icao: "",
68
+ longitude: 0,
69
+ latitude: 0,
70
+ dir: 0,
71
+ alt: 0,
72
+ };
73
+ /**
74
+ * Intended end position of aircraft, as well as name of destination airport. Position does not have match airport.
75
+ */
76
+ this.destination = {
77
+ icao: "",
78
+ longitude: 0,
79
+ latitude: 0,
80
+ dir: 0,
81
+ alt: 0,
82
+ };
83
+ /**
84
+ * Time and weather for mission
85
+ */
86
+ this.conditions = new AeroflyMissionConditions();
87
+ /**
88
+ * The actual flight plan
89
+ */
90
+ this.checkpoints = [];
91
+ this.title = title;
92
+ this.checkpoints = additionalAttributes.checkpoints ?? this.checkpoints;
93
+ this.description = additionalAttributes.description ?? this.description;
94
+ this.flightSetting = additionalAttributes.flightSetting ?? this.flightSetting;
95
+ this.aircraft = additionalAttributes.aircraft ?? this.aircraft;
96
+ this.callsign = additionalAttributes.callsign ?? this.callsign;
97
+ this.origin = additionalAttributes.origin ?? this.origin;
98
+ this.destination = additionalAttributes.destination ?? this.destination;
99
+ this.conditions = additionalAttributes.conditions ?? this.conditions;
100
+ }
101
+ /**
102
+ * @returns indexed checkpoints
103
+ */
104
+ getCheckpointsString() {
105
+ return this.checkpoints
106
+ .map((c, index) => {
107
+ return c.toString(index);
108
+ })
109
+ .join("\n");
110
+ }
111
+ /**
112
+ * @throws {Error} on missing waypoints
113
+ * @returns String to use in Aerofly FS4's `custom_missions_user.tmc`
114
+ */
115
+ toString() {
116
+ if (this.checkpoints.length < 2) {
117
+ throw Error("this.checkpoints.length < 2");
118
+ }
119
+ if (!this.origin.icao) {
120
+ const firstCheckpoint = this.checkpoints[0];
121
+ this.origin = {
122
+ icao: firstCheckpoint.name,
123
+ longitude: firstCheckpoint.longitude,
124
+ latitude: firstCheckpoint.latitude,
125
+ dir: this.origin.dir,
126
+ alt: firstCheckpoint.altitude,
127
+ };
128
+ }
129
+ if (!this.destination.icao) {
130
+ const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
131
+ this.destination = {
132
+ icao: lastCheckpoint.name,
133
+ longitude: lastCheckpoint.longitude,
134
+ latitude: lastCheckpoint.latitude,
135
+ dir: lastCheckpoint.direction ?? 0,
136
+ alt: lastCheckpoint.altitude,
137
+ };
138
+ }
139
+ return ` <[tmmission_definition][mission][]
140
+ <[string8][title][${this.title}]>
141
+ <[string8][description][${this.description}]>
142
+ <[string8] [flight_setting] [${this.flightSetting}]>
143
+ <[string8u] [aircraft_name] [${this.aircraft.name}]>
144
+ //<[string8u][aircraft_livery] [${this.aircraft.livery}]>
145
+ <[stringt8c] [aircraft_icao] [${this.aircraft.icao}]>
146
+ <[stringt8c] [callsign] [${this.callsign}]>
147
+ <[stringt8c] [origin_icao] [${this.origin.icao}]>
148
+ <[tmvector2d][origin_lon_lat] [${this.origin.longitude} ${this.origin.latitude}]>
149
+ <[float64] [origin_dir] [${this.origin.dir}]>
150
+ <[float64] [origin_alt] [${this.origin.alt}]> // ${this.origin.alt * feetPerMeter} ft MSL
151
+ <[stringt8c] [destination_icao] [${this.destination.icao}]>
152
+ <[tmvector2d][destination_lon_lat][${this.destination.longitude} ${this.destination.latitude}]>
153
+ <[float64] [destination_dir] [${this.destination.dir}]>
154
+ ${this.conditions}
155
+ <[list_tmmission_checkpoint][checkpoints][]
156
+ ${this.getCheckpointsString()}
157
+ >
158
+ >`;
159
+ }
160
+ }
161
+ /**
162
+ * Time and weather data for the given flight plan
163
+ *
164
+ * The purpose of this class is to collect data needed for Aerofly FS4's
165
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
166
+ * for this file via the `toString()` method.
167
+ */
168
+ export class AeroflyMissionConditions {
169
+ /**
170
+ * @param additionalAttributes allows to set additional attributes on creation
171
+ */
172
+ constructor(additionalAttributes = {}) {
173
+ /**
174
+ * Start time of flight plan. Relevant is the UTC part, so
175
+ * consider setting this date in UTC.
176
+ */
177
+ this.time = new Date();
178
+ /**
179
+ * Current wind state
180
+ */
181
+ this.wind = {
182
+ direction: 0,
183
+ speed: 0,
184
+ gusts: 0,
185
+ };
186
+ /**
187
+ * 0..1, percentage
188
+ */
189
+ this.turbulenceStrength = 0;
190
+ /**
191
+ * 0..1, percentage
192
+ */
193
+ this.thermalStrength = 0;
194
+ /**
195
+ * Visibility in meters
196
+ */
197
+ this.visibility = 25000;
198
+ this.clouds = [];
199
+ this.time = additionalAttributes.time ?? this.time;
200
+ this.wind.direction = additionalAttributes.wind?.direction ?? this.wind.direction;
201
+ this.wind.speed = additionalAttributes.wind?.speed ?? this.wind.speed;
202
+ this.wind.gusts = additionalAttributes.wind?.gusts ?? this.wind.gusts;
203
+ this.turbulenceStrength = additionalAttributes.turbulenceStrength ?? this.turbulenceStrength;
204
+ this.visibility = additionalAttributes.visibility ?? this.visibility;
205
+ this.clouds = additionalAttributes.clouds ?? this.clouds;
206
+ }
207
+ /**
208
+ * @returns the Aerofly value for UTC hours + minutes/60 + seconds/3600. Ignores milliseconds ;)
209
+ */
210
+ get time_hours() {
211
+ return this.time.getUTCHours() + this.time.getUTCMinutes() / 60 + this.time.getUTCSeconds() / 3600;
212
+ }
213
+ /**
214
+ * @returns Time representation like "20:15:00"
215
+ */
216
+ get time_presentational() {
217
+ return [this.time.getUTCHours(), this.time.getUTCMinutes(), this.time.getUTCSeconds()]
218
+ .map((t) => {
219
+ return String(t).padStart(2, "0");
220
+ })
221
+ .join(":");
222
+ }
223
+ /**
224
+ * @param visibility_sm `this.visibility` in statute miles instead of meters
225
+ */
226
+ set visibility_sm(visibility_sm) {
227
+ this.visibility = visibility_sm * meterPerStatuteMile;
228
+ }
229
+ /**
230
+ * @returns
231
+ */
232
+ getCloudsString() {
233
+ return this.clouds
234
+ .map((c, index) => {
235
+ return c.toString(index);
236
+ })
237
+ .join("\n");
238
+ }
239
+ /**
240
+ * @returns String to use in Aerofly FS4's `custom_missions_user.tmc`
241
+ */
242
+ toString() {
243
+ if (this.clouds.length < 1) {
244
+ this.clouds = [new AeroflyMissionConditionsCloud(0, 0)];
245
+ }
246
+ return ` <[tmmission_conditions][conditions][]
247
+ <[tm_time_utc][time][]
248
+ <[int32][time_year][${this.time.getUTCFullYear()}]>
249
+ <[int32][time_month][${this.time.getUTCMonth() + 1}]>
250
+ <[int32][time_day][${this.time.getUTCDate()}]>
251
+ <[float64][time_hours][${this.time_hours}]> // ${this.time_presentational} UTC
252
+ >
253
+ <[float64][wind_direction][${this.wind.direction}]>
254
+ <[float64][wind_speed][${this.wind.speed}]> // kts
255
+ <[float64][wind_gusts][${this.wind.gusts}]> // kts
256
+ <[float64][turbulence_strength][${this.turbulenceStrength}]>
257
+ <[float64][thermal_strength][${this.thermalStrength}]>
258
+ <[float64][visibility][${this.visibility}]> // ${this.visibility / meterPerStatuteMile} SM
259
+ ${this.getCloudsString()}
260
+ >`;
261
+ }
262
+ }
263
+ /**
264
+ * A cloud layer for the current flight plan's weather data
265
+ *
266
+ * The purpose of this class is to collect data needed for Aerofly FS4's
267
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
268
+ * for this file via the `toString()` method.
269
+ */
270
+ export class AeroflyMissionConditionsCloud {
271
+ /**
272
+ * @param cover 0..1, percentage
273
+ * @param base in meters AGL
274
+ */
275
+ constructor(cover, base) {
276
+ this.cover = cover;
277
+ this.base = base;
278
+ }
279
+ /**
280
+ * @param cover 0..1, percentage
281
+ * @param base_feet base, but in feet AGL instead of meters AGL
282
+ * @returns {AeroflyMissionConditionsCloud}
283
+ */
284
+ static createInFeet(cover, base_feet) {
285
+ return new AeroflyMissionConditionsCloud(cover, base_feet / feetPerMeter);
286
+ }
287
+ /**
288
+ * @param base_feet `this.base` in feet instead of meters
289
+ */
290
+ set base_feet(base_feet) {
291
+ this.base = base_feet / feetPerMeter;
292
+ }
293
+ /**
294
+ * @returns Cloud coverage as text representation like "OVC" for `this.cover`
295
+ */
296
+ get cover_code() {
297
+ if (this.cover < 1 / 8) {
298
+ return "CLR";
299
+ }
300
+ else if (this.cover <= 2 / 8) {
301
+ return "FEW";
302
+ }
303
+ else if (this.cover <= 4 / 8) {
304
+ return "SCT";
305
+ }
306
+ else if (this.cover <= 7 / 8) {
307
+ return "BKN";
308
+ }
309
+ return "OVC";
310
+ }
311
+ /**
312
+ * @param index if used in an array will se the array index
313
+ * @returns String to use in Aerofly FS4's `custom_missions_user.tmc`
314
+ */
315
+ toString(index = 0) {
316
+ const indexString = index === 0 ? "" : String(index + 1);
317
+ const comment = index === 0 ? "" : "//";
318
+ return ` ${comment}<[float64][cloud_cover${indexString}][${this.cover ?? 0}]> // ${this.cover_code}
319
+ ${comment}<[float64][cloud_base${indexString}][${this.base}]> // ${this.base * feetPerMeter} ft AGL`;
320
+ }
321
+ }
322
+ /**
323
+ * A single way point for the given flight plan
324
+ *
325
+ * The purpose of this class is to collect data needed for Aerofly FS4's
326
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
327
+ * for this file via the `toString()` method.
328
+ */
329
+ export class AeroflyMissionCheckpoint {
330
+ /**
331
+ * @param name ICAO code for airport, runway designator, navaid
332
+ * designator, fix name, or custom name
333
+ * @param type Type of checkpoint, like "departure_runway"
334
+ * @param longitude easting, using the World Geodetic
335
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
336
+ * of decimal degrees; -180..180
337
+ * @param latitude northing, using the World Geodetic
338
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
339
+ * of decimal degrees; -90..90
340
+ * @param additionalAttributes allows to set additional attributes on creation
341
+ */
342
+ constructor(name, type, longitude, latitude, additionalAttributes = {}) {
343
+ /**
344
+ * The height in meters above or below the WGS
345
+ * 84 reference ellipsoid
346
+ */
347
+ this.altitude = 0;
348
+ /**
349
+ * For runways: in degree
350
+ */
351
+ this.direction = null;
352
+ /**
353
+ * For runways
354
+ */
355
+ this.slope = null;
356
+ /**
357
+ * For runways: in meters
358
+ */
359
+ this.length = null;
360
+ /**
361
+ * For runways and navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
362
+ */
363
+ this.frequency = null;
364
+ this.type = type;
365
+ this.name = name;
366
+ this.longitude = longitude;
367
+ this.latitude = latitude;
368
+ if (additionalAttributes.altitude_feet) {
369
+ additionalAttributes.altitude = additionalAttributes.altitude_feet / feetPerMeter;
370
+ }
371
+ this.altitude = additionalAttributes.altitude ?? this.altitude;
372
+ this.direction = additionalAttributes.direction ?? this.direction;
373
+ this.slope = additionalAttributes.slope ?? this.slope;
374
+ this.length = additionalAttributes.length ?? this.length;
375
+ this.frequency = additionalAttributes.frequency ?? this.frequency;
376
+ }
377
+ /**
378
+ * @param altitude_feet
379
+ */
380
+ set altitude_feet(altitude_feet) {
381
+ this.altitude = altitude_feet / feetPerMeter;
382
+ }
383
+ get frequency_string() {
384
+ if (!this.frequency) {
385
+ return "None";
386
+ }
387
+ if (this.frequency > 1000000) {
388
+ return String(this.frequency / 1000000) + " MHz";
389
+ }
390
+ if (this.frequency > 1000) {
391
+ return String(this.frequency / 1000) + " kHz";
392
+ }
393
+ return String(this.frequency) + " Hz";
394
+ }
395
+ /**
396
+ * @param index if used in an array will se the array index
397
+ * @returns String to use in Aerofly FS4's `custom_missions_user.tmc`
398
+ */
399
+ toString(index = 0) {
400
+ return ` <[tmmission_checkpoint][element][${index}]
401
+ <[string8u][type][${this.type}]>
402
+ <[string8u][name][${this.name}]>
403
+ <[vector2_float64][lon_lat][${this.longitude} ${this.latitude}]>
404
+ <[float64][altitude][${this.altitude}]> // ${this.altitude * feetPerMeter} ft
405
+ <[float64][direction][${this.direction ?? (index === 0 ? -1 : 0)}]>
406
+ <[float64][slope][${this.slope ?? 0}]>
407
+ <[float64][length][${this.length ?? 0}]> // ${(this.length ?? 0) * feetPerMeter} ft
408
+ <[float64][frequency][${this.frequency ?? 0}]> // ${this.frequency_string}
409
+ >`;
410
+ }
411
+ }
412
+ export default {
413
+ AeroflyMissionsList,
414
+ AeroflyMission,
415
+ AeroflyMissionConditions,
416
+ AeroflyMissionConditionsCloud,
417
+ AeroflyMissionCheckpoint,
418
+ };