@fboes/aerofly-custom-missions 1.6.0 → 1.8.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 (64) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/dto/AeroflyMission.js +43 -0
  3. package/dist/dto/AeroflyMission.test.js +11 -3
  4. package/dist/dto/AeroflyMissionConditions.js +24 -0
  5. package/dist/dto/AeroflyMissionsList.js +11 -0
  6. package/dist/dto-flight/AeroflyFlight.test.js +1 -0
  7. package/dist/dto-flight/AeroflyNavRouteAirports.js +8 -0
  8. package/dist/dto-flight/AeroflyNavRouteBase.js +6 -0
  9. package/dist/dto-flight/AeroflyNavRouteRunway.js +10 -0
  10. package/dist/dto-flight/AeroflyNavRouteTransition.js +9 -0
  11. package/dist/dto-flight/AeroflyNavRouteWaypoint.js +29 -1
  12. package/dist/dto-flight/AeroflyNavigationConfig.js +7 -0
  13. package/dist/dto-flight/AeroflySettingsCloud.js +7 -0
  14. package/dist/dto-flight/AeroflySettingsFlight.js +7 -0
  15. package/dist/dto-flight/AeroflySettingsFuelLoad.js +13 -0
  16. package/dist/node/Convert.js +6 -0
  17. package/docs/custom_missions_user.json +131 -0
  18. package/docs/custom_missions_user.schema.json +269 -0
  19. package/docs/custom_missions_user.tmc +8 -3
  20. package/docs/custom_missions_user.xml +8 -3
  21. package/docs/flight-generator-prompt.md +91 -0
  22. package/docs/flight.json +134 -0
  23. package/package.json +1 -1
  24. package/src/dto/AeroflyMission.test.ts +18 -3
  25. package/src/dto/AeroflyMission.ts +44 -0
  26. package/src/dto/AeroflyMissionConditions.ts +27 -0
  27. package/src/dto/AeroflyMissionsList.ts +12 -0
  28. package/src/dto-flight/AeroflyFlight.test.ts +2 -0
  29. package/src/dto-flight/AeroflyNavRouteAirports.ts +9 -0
  30. package/src/dto-flight/AeroflyNavRouteBase.ts +7 -0
  31. package/src/dto-flight/AeroflyNavRouteRunway.ts +11 -0
  32. package/src/dto-flight/AeroflyNavRouteTransition.ts +10 -0
  33. package/src/dto-flight/AeroflyNavRouteWaypoint.ts +35 -0
  34. package/src/dto-flight/AeroflyNavigationConfig.ts +8 -0
  35. package/src/dto-flight/AeroflySettingsCloud.ts +8 -0
  36. package/src/dto-flight/AeroflySettingsFlight.ts +28 -1
  37. package/src/dto-flight/AeroflySettingsFuelLoad.ts +17 -0
  38. package/src/node/Convert.ts +8 -0
  39. package/types/dto/AeroflyMission.d.ts +1 -0
  40. package/types/dto/AeroflyMission.d.ts.map +1 -1
  41. package/types/dto/AeroflyMissionConditions.d.ts +1 -0
  42. package/types/dto/AeroflyMissionConditions.d.ts.map +1 -1
  43. package/types/dto/AeroflyMissionsList.d.ts +2 -0
  44. package/types/dto/AeroflyMissionsList.d.ts.map +1 -1
  45. package/types/dto-flight/AeroflyNavRouteAirports.d.ts +1 -0
  46. package/types/dto-flight/AeroflyNavRouteAirports.d.ts.map +1 -1
  47. package/types/dto-flight/AeroflyNavRouteBase.d.ts +3 -0
  48. package/types/dto-flight/AeroflyNavRouteBase.d.ts.map +1 -1
  49. package/types/dto-flight/AeroflyNavRouteRunway.d.ts +1 -0
  50. package/types/dto-flight/AeroflyNavRouteRunway.d.ts.map +1 -1
  51. package/types/dto-flight/AeroflyNavRouteTransition.d.ts +1 -0
  52. package/types/dto-flight/AeroflyNavRouteTransition.d.ts.map +1 -1
  53. package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts +14 -1
  54. package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts.map +1 -1
  55. package/types/dto-flight/AeroflyNavigationConfig.d.ts +1 -0
  56. package/types/dto-flight/AeroflyNavigationConfig.d.ts.map +1 -1
  57. package/types/dto-flight/AeroflySettingsCloud.d.ts +1 -0
  58. package/types/dto-flight/AeroflySettingsCloud.d.ts.map +1 -1
  59. package/types/dto-flight/AeroflySettingsFlight.d.ts +17 -1
  60. package/types/dto-flight/AeroflySettingsFlight.d.ts.map +1 -1
  61. package/types/dto-flight/AeroflySettingsFuelLoad.d.ts +4 -0
  62. package/types/dto-flight/AeroflySettingsFuelLoad.d.ts.map +1 -1
  63. package/types/node/Convert.d.ts +2 -0
  64. package/types/node/Convert.d.ts.map +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  This changelog documents all notable changes to the Aerofly Custom Missions project. Each version entry includes a list of changes, with the most recent version at the top.
4
4
 
5
+ ## [1.7.0] - 2026-04-19
6
+
7
+ - Added new flight configuration strings to `AeroflySettingsFlight`
8
+
9
+ ## [1.6.0]
10
+
11
+ - Added `toJSON` improvements to most DTOs
12
+ - Added JSON schema for custom missions as JSON
13
+
5
14
  ## [1.5.0]
6
15
 
7
16
  - Added clouds elements getter
@@ -180,4 +180,47 @@ export class AeroflyMission {
180
180
  toString() {
181
181
  return this.getElement().toString();
182
182
  }
183
+ static fromJSON(json) {
184
+ if (typeof json !== "object" || json === null) {
185
+ throw new Error("Invalid mission data");
186
+ }
187
+ const data = json;
188
+ return new AeroflyMission(String(data.title ?? ""), {
189
+ tutorialName: String(data.tutorialName ?? ""),
190
+ description: String(data.description ?? ""),
191
+ localizedTexts: [], // TODO
192
+ tags: [], // TODO
193
+ isFeatured: data.isFeatured !== undefined ? Boolean(data.isFeatured) : null,
194
+ difficulty: data.difficulty !== undefined ? Number(data.difficulty) : null,
195
+ flightSetting: String(data.flightSetting ?? "taxi"),
196
+ aircraft: {
197
+ name: "c172",
198
+ icao: "",
199
+ livery: "",
200
+ },
201
+ callsign: String(data.callsign ?? ""),
202
+ fuelMass: data.fuelMass !== undefined ? Number(data.fuelMass) : null,
203
+ payloadMass: data.payloadMass !== undefined ? Number(data.payloadMass) : null,
204
+ origin: {
205
+ icao: "",
206
+ longitude: 0,
207
+ latitude: 0,
208
+ dir: 0,
209
+ alt: 0,
210
+ },
211
+ destination: {
212
+ icao: "",
213
+ longitude: 0,
214
+ latitude: 0,
215
+ dir: 0,
216
+ alt: 0,
217
+ },
218
+ distance: data.distance !== undefined ? Number(data.distance) : null,
219
+ duration: data.duration !== undefined ? Number(data.duration) : null,
220
+ isScheduled: data.isScheduled !== undefined ? Boolean(data.isScheduled) : null,
221
+ finish: null, // TODO
222
+ conditions: AeroflyMissionConditions.fromJSON(data.conditions),
223
+ checkpoints: [], // TODO
224
+ });
225
+ }
183
226
  }
@@ -75,10 +75,11 @@ describe("AeroflyMission", () => {
75
75
  flyOver: false,
76
76
  }),
77
77
  ];
78
- const mission = new AeroflyMission("KCCR #1: Concord / Buchanan Field", {
78
+ const mission = new AeroflyMission("Landing practice #1: Concord / Buchanan Field", {
79
79
  checkpoints,
80
80
  conditions,
81
- description: `It is a gusty, clear early morning, and you are 8 NM to the north of the towered airport Concord / Buchanan Field (27ft). As the wind is 11 kts from 190°, the main landing runway is 19L (191° / 844m). Fly the pattern and land safely.
81
+ description: `\
82
+ It is a gusty, clear early morning, and you are 8 NM to the north of the towered airport Concord / Buchanan Field (27ft). As the wind is 11 kts from 190°, the main landing runway is 19L (191° / 844m). Fly the pattern and land safely.
82
83
 
83
84
  - Local tower / CTAF frequency: 123.90
84
85
  - Local navigational aids: VOR/DME CCR (117.00) 3.4 NM to the north`,
@@ -89,6 +90,8 @@ describe("AeroflyMission", () => {
89
90
  icao: "C172",
90
91
  },
91
92
  callsign: "N51911",
93
+ fuelMass: 80,
94
+ payloadMass: 90,
92
95
  origin: {
93
96
  icao: "KCCR",
94
97
  longitude: -122.0736009331662,
@@ -133,7 +136,11 @@ describe("AeroflyMission", () => {
133
136
  //console.log(missionListString);
134
137
  mission.difficulty = 1.0;
135
138
  mission.isFeatured = true;
136
- mission.localizedTexts.push(new AeroflyLocalizedText("de", "Landeübung #1", "Probier die Landung"));
139
+ mission.localizedTexts.push(new AeroflyLocalizedText("de", `\
140
+ Landeübung #1: Concord / Buchanan Field`, `Es ist ein böiger, klarer früher Morgen, und du bist 8 Seemeilen nördlich des turmhohen Flughafens Concord / Buchanan Field (27 Fuß). Da der Wind 11 Knoten aus 190° erreicht, beträgt die Hauptlandebahn 19L (191° / 844m). Fliege das Muster und lande sicher.
141
+
142
+ - Lokaler Tower / CTAF-Frequenz: 123,90
143
+ - Lokale Navigationshilfen: VOR/DME CCR (117,00) 3,4 Seemeilen nördlich`));
137
144
  mission.distance = 1400;
138
145
  mission.duration = 2 * 60 * 60;
139
146
  mission.tags.push("approach");
@@ -162,5 +169,6 @@ describe("AeroflyMission", () => {
162
169
  fs.writeFileSync("docs/custom_missions_user.tmc", fileContent);
163
170
  const xmlContent = file.toXmlString();
164
171
  fs.writeFileSync("docs/custom_missions_user.xml", xmlContent);
172
+ fs.writeFileSync("docs/custom_missions_user.json", JSON.stringify(file, null, 2));
165
173
  });
166
174
  });
@@ -119,4 +119,28 @@ export class AeroflyMissionConditions {
119
119
  toString() {
120
120
  return this.getElement().toString();
121
121
  }
122
+ static fromJSON(json) {
123
+ if (typeof json !== "object" || json === null) {
124
+ throw new Error("Invalid mission condition data");
125
+ }
126
+ const data = json;
127
+ if (typeof data.wind !== "object" || data.wind === null) {
128
+ throw new Error("Invalid wind data");
129
+ }
130
+ const wind = data.wind;
131
+ return new AeroflyMissionConditions({
132
+ time: data.time ? new Date(String(data.time)) : new Date(),
133
+ wind: {
134
+ direction: Number(wind.direction ?? 0),
135
+ speed: Number(wind.speed ?? 0),
136
+ gusts: Number(wind.gusts ?? 0),
137
+ },
138
+ turbulenceStrength: Number(data.turbulenceStrength ?? 0),
139
+ thermalStrength: Number(data.thermalStrength ?? 0),
140
+ visibility: Number(data.visibility ?? 25_000),
141
+ visibility_sm: Number(data.visibility_sm ?? 0),
142
+ temperature: Number(data.temperature ?? 0),
143
+ clouds: [], // TODO
144
+ });
145
+ }
122
146
  }
@@ -1,4 +1,5 @@
1
1
  import { AeroflyConfigurationNode, AeroflyConfigurationNodeSpacer } from "../node/AeroflyConfigurationNode.js";
2
+ import { AeroflyMission } from "./AeroflyMission.js";
2
3
  /**
3
4
  * @class
4
5
  * A list of flight plans.
@@ -29,4 +30,14 @@ export class AeroflyMissionsList {
29
30
  toXmlString() {
30
31
  return this.getElement().toXmlString();
31
32
  }
33
+ toJSON() {
34
+ return this.missions;
35
+ }
36
+ static fromJSON(json) {
37
+ if (!Array.isArray(json)) {
38
+ throw new Error("Mission list base node must be of array type");
39
+ }
40
+ const data = json;
41
+ return new AeroflyMissionsList(data.map((d) => AeroflyMission.fromJSON(d)));
42
+ }
32
43
  }
@@ -63,6 +63,7 @@ describe("AeroflyFlight", () => {
63
63
  fs.writeFileSync("docs/flight.mcf", fileContent);
64
64
  const xmlContent = flight.toXmlString();
65
65
  fs.writeFileSync("docs/flight.xml", xmlContent);
66
+ fs.writeFileSync("docs/flight.json", JSON.stringify(flight, null, 2));
66
67
  assertValidAeroflyStructure(fileContent);
67
68
  assert.ok(fileContent.includes("c172"));
68
69
  });
@@ -34,6 +34,14 @@ class AeroflyNavRouteAirport extends AeroflyNavRouteBase {
34
34
  }
35
35
  return element;
36
36
  }
37
+ toJSON() {
38
+ return {
39
+ ...this,
40
+ uid: this.uid !== null ? this.uid.toString() : null,
41
+ elevation: undefined,
42
+ elevation_ft: this.elevation_ft,
43
+ };
44
+ }
37
45
  }
38
46
  export class AeroflyNavRouteOrigin extends AeroflyNavRouteAirport {
39
47
  /**
@@ -45,6 +45,12 @@ export class AeroflyNavRouteBase {
45
45
  : new AeroflyConfigurationNodeComment("uint64", "Uid", ""));
46
46
  return element;
47
47
  }
48
+ toJSON() {
49
+ return {
50
+ ...this,
51
+ uid: this.uid !== null ? this.uid.toString() : null,
52
+ };
53
+ }
48
54
  /**
49
55
  * @returns {string} to use in Aerofly FS4's `main.mcf`
50
56
  */
@@ -51,6 +51,16 @@ class AeroflyNavRouteRunway extends AeroflyNavRouteBase {
51
51
  }
52
52
  return element;
53
53
  }
54
+ toJSON() {
55
+ return {
56
+ ...this,
57
+ uid: this.uid !== null ? this.uid.toString() : null,
58
+ elevation: undefined,
59
+ elevation_ft: this.elevation_ft,
60
+ runwayLength: undefined,
61
+ runwayLength_ft: this.runwayLength_ft,
62
+ };
63
+ }
54
64
  }
55
65
  export class AeroflyNavRouteDepartureRunway extends AeroflyNavRouteRunway {
56
66
  /**
@@ -39,6 +39,15 @@ class AeroflyNavRouteTransition extends AeroflyNavRouteBase {
39
39
  .appendChild("uint64", "TransitionUid", this.transitionUid ?? 0);
40
40
  return element;
41
41
  }
42
+ toJSON() {
43
+ return {
44
+ ...this,
45
+ uid: this.uid !== null ? this.uid.toString() : null,
46
+ transitionUid: this.transitionUid !== null ? this.transitionUid.toString() : null,
47
+ elevation: undefined,
48
+ elevation_ft: this.elevation_ft,
49
+ };
50
+ }
42
51
  }
43
52
  export class AeroflyNavRouteApproach extends AeroflyNavRouteTransition {
44
53
  /**
@@ -12,7 +12,7 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
12
12
  * @param {boolean} [options.flyOver] if true, the waypoint is meant to be flown over, otherwise it can be used as a fly-by waypoint
13
13
  * @param {?bigint} [options.uid] unique identifier for the waypoint, will be generated automatically if not provided
14
14
  */
15
- constructor(identifier, longitude, latitude, { navaidFrequency = null, altitude = null, altitude_ft = null, flyOver = false, uid = null, } = {}) {
15
+ constructor(identifier, longitude, latitude, { navaidFrequency = null, navaidFrequency_khz = null, navaidFrequency_mhz = null, altitude = null, altitude_ft = null, flyOver = false, uid = null, } = {}) {
16
16
  super("waypoint", identifier, longitude, latitude, { uid });
17
17
  this.identifier = identifier;
18
18
  this.longitude = longitude;
@@ -22,6 +22,12 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
22
22
  if (altitude_ft !== null) {
23
23
  this.altitude_ft = altitude_ft;
24
24
  }
25
+ if (navaidFrequency_khz !== null) {
26
+ this.navaidFrequency_khz = navaidFrequency_khz;
27
+ }
28
+ if (navaidFrequency_mhz !== null) {
29
+ this.navaidFrequency_mhz = navaidFrequency_mhz;
30
+ }
25
31
  this.flyOver = flyOver;
26
32
  }
27
33
  /**
@@ -33,6 +39,18 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
33
39
  set altitude_ft(altitude_ft) {
34
40
  this.altitude = altitude_ft !== null ? Convert.convertFeetToMeter(altitude_ft) : null;
35
41
  }
42
+ get navaidFrequency_khz() {
43
+ return this.navaidFrequency ? this.navaidFrequency / 1000 : null;
44
+ }
45
+ set navaidFrequency_khz(navaidFrequency_khz) {
46
+ this.navaidFrequency = navaidFrequency_khz !== null ? navaidFrequency_khz * 1000 : null;
47
+ }
48
+ get navaidFrequency_mhz() {
49
+ return this.navaidFrequency ? this.navaidFrequency / 1000_000 : null;
50
+ }
51
+ set navaidFrequency_mhz(navaidFrequency_mhz) {
52
+ this.navaidFrequency = navaidFrequency_mhz !== null ? navaidFrequency_mhz * 1000_000 : null;
53
+ }
36
54
  /**
37
55
  * @returns {AeroflyVector3Float} to use in Aerofly FS4's `main.mcf`
38
56
  */
@@ -55,4 +73,14 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
55
73
  .appendChild("bool", "FlyOver", this.flyOver);
56
74
  return element;
57
75
  }
76
+ toJSON() {
77
+ return {
78
+ ...this,
79
+ uid: this.uid !== null ? this.uid.toString() : null,
80
+ altitude: undefined,
81
+ altitude_ft: this.altitude_ft,
82
+ navaidFrequency: undefined,
83
+ navaidFrequency_khz: this.navaidFrequency_khz,
84
+ };
85
+ }
58
86
  }
@@ -41,6 +41,13 @@ export class AeroflyNavigationConfig {
41
41
  .append(new AeroflyConfigurationNode("pointer_list_tmnav_route_way", "Ways").append(...this.getCheckpointElements())))
42
42
  .append(new AeroflyConfigurationNode("list_fixed_tmnav_fix", "Fixes", []));
43
43
  }
44
+ toJSON() {
45
+ return {
46
+ ...this,
47
+ cruiseAltitude: undefined,
48
+ cruiseAltitude_ft: this.cruiseAltitude_ft,
49
+ };
50
+ }
44
51
  /**
45
52
  * @returns {string} to use in Aerofly FS4's `main.mcf`
46
53
  */
@@ -91,6 +91,13 @@ export class AeroflySettingsCloud {
91
91
  new AeroflyConfigurationNodeComment("float64", `${indexString}_height`, this.height, `${Math.floor(this.height_ft)} ft AGL`),
92
92
  ];
93
93
  }
94
+ toJSON() {
95
+ return {
96
+ ...this,
97
+ height: undefined,
98
+ height_ft: this.height_ft,
99
+ };
100
+ }
94
101
  /**
95
102
  * @param {number} index if used in an array will set the array index
96
103
  * @returns {string} to use in Aerofly FS4's `main.mcf`
@@ -86,6 +86,13 @@ export class AeroflySettingsFlight {
86
86
  .appendChild("string8u", "airport", this.airport)
87
87
  .appendChild("string8u", "runway", this.runway);
88
88
  }
89
+ toJSON() {
90
+ return {
91
+ ...this,
92
+ altitude_meter: undefined,
93
+ altitude_ft: this.altitude_ft,
94
+ };
95
+ }
89
96
  /**
90
97
  * @returns {string} to use in Aerofly FS4's `main.mcf`
91
98
  */
@@ -1,4 +1,5 @@
1
1
  import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
+ import { Convert } from "../node/Convert.js";
2
3
  export class AeroflySettingsFuelLoad {
3
4
  /**
4
5
  * @param {string} aircraft aerofly aircraft name (e.g. "c172")
@@ -14,6 +15,18 @@ export class AeroflySettingsFuelLoad {
14
15
  this.payloadMass = payloadMass;
15
16
  this.configuration = configuration;
16
17
  }
18
+ get fuelMass_lb() {
19
+ return Convert.convertKgToLb(this.fuelMass);
20
+ }
21
+ set fuelMass_lb(fuelMass_lb) {
22
+ this.fuelMass = Convert.convertLbToKg(fuelMass_lb);
23
+ }
24
+ get payloadMass_lb() {
25
+ return Convert.convertKgToLb(this.payloadMass);
26
+ }
27
+ set payloadMass_lb(payloadMass_lb) {
28
+ this.payloadMass = Convert.convertLbToKg(payloadMass_lb);
29
+ }
17
30
  getElement() {
18
31
  return new AeroflyConfigurationNode("tmsettings_fuel_load", "fuel_load_setting")
19
32
  .appendChild("fuel_load_configuration", "configuration", this.fuelMass > 0 ? "Keep" : this.configuration)
@@ -69,4 +69,10 @@ export class Convert {
69
69
  static convertFeetToMeter(feet) {
70
70
  return feet / 3.28084;
71
71
  }
72
+ static convertKgToLb(kg) {
73
+ return kg;
74
+ }
75
+ static convertLbToKg(lb) {
76
+ return lb;
77
+ }
72
78
  }
@@ -0,0 +1,131 @@
1
+ [
2
+ {
3
+ "tutorialName": "c172",
4
+ "title": "Landing practice #1: Concord / Buchanan Field",
5
+ "checkpoints": [
6
+ {
7
+ "type": "origin",
8
+ "name": "KCCR",
9
+ "longitude": -122.057,
10
+ "latitude": 37.9897,
11
+ "altitude": 8,
12
+ "altitudeConstraint": null,
13
+ "direction": null,
14
+ "slope": null,
15
+ "length": null,
16
+ "frequency": null,
17
+ "flyOver": null
18
+ },
19
+ {
20
+ "type": "departure_runway",
21
+ "name": "19L",
22
+ "longitude": -122.05504061196366,
23
+ "latitude": 37.993168229891225,
24
+ "altitude": 0,
25
+ "altitudeConstraint": null,
26
+ "direction": null,
27
+ "slope": null,
28
+ "length": 844.2959729825288,
29
+ "frequency": null,
30
+ "flyOver": null
31
+ },
32
+ {
33
+ "type": "destination_runway",
34
+ "name": "24",
35
+ "longitude": -70.60730234370952,
36
+ "latitude": 41.399093035543366,
37
+ "altitude": 20,
38
+ "altitudeConstraint": null,
39
+ "direction": null,
40
+ "slope": null,
41
+ "length": 1677.6191463161874,
42
+ "frequency": 108700000,
43
+ "flyOver": null
44
+ },
45
+ {
46
+ "type": "destination",
47
+ "name": "KMVY",
48
+ "longitude": -70.6139,
49
+ "latitude": 41.3934,
50
+ "altitude": 20,
51
+ "altitudeConstraint": null,
52
+ "direction": null,
53
+ "slope": null,
54
+ "length": null,
55
+ "frequency": null,
56
+ "flyOver": false
57
+ }
58
+ ],
59
+ "description": "It is a gusty, clear early morning, and you are 8 NM to the north of the towered airport Concord / Buchanan Field (27ft). As the wind is 11 kts from 190°, the main landing runway is 19L (191° / 844m). Fly the pattern and land safely.\n\n- Local tower / CTAF frequency: 123.90\n- Local navigational aids: VOR/DME CCR (117.00) 3.4 NM to the north",
60
+ "localizedTexts": [
61
+ {
62
+ "language": "de",
63
+ "title": "Landeübung #1: Concord / Buchanan Field",
64
+ "description": "Es ist ein böiger, klarer früher Morgen, und du bist 8 Seemeilen nördlich des turmhohen Flughafens Concord / Buchanan Field (27 Fuß). Da der Wind 11 Knoten aus 190° erreicht, beträgt die Hauptlandebahn 19L (191° / 844m). Fliege das Muster und lande sicher.\n\n- Lokaler Tower / CTAF-Frequenz: 123,90\n- Lokale Navigationshilfen: VOR/DME CCR (117,00) 3,4 Seemeilen nördlich"
65
+ }
66
+ ],
67
+ "tags": [
68
+ "approach",
69
+ "pattern"
70
+ ],
71
+ "isFeatured": true,
72
+ "difficulty": 1,
73
+ "flightSetting": "cruise",
74
+ "aircraft": {
75
+ "name": "c172",
76
+ "livery": "default",
77
+ "icao": "C172"
78
+ },
79
+ "callsign": "N51911",
80
+ "fuelMass": 80,
81
+ "payloadMass": 90,
82
+ "origin": {
83
+ "icao": "KCCR",
84
+ "longitude": -122.0736009331662,
85
+ "latitude": 38.122300745843944,
86
+ "dir": 174.37511511143452,
87
+ "alt": 1066.799965862401
88
+ },
89
+ "destination": {
90
+ "icao": "KMVY",
91
+ "longitude": -70.6139,
92
+ "latitude": 41.3934,
93
+ "dir": 221,
94
+ "alt": 20
95
+ },
96
+ "distance": 1400,
97
+ "duration": 7200,
98
+ "isScheduled": true,
99
+ "finish": {
100
+ "longitude": 0,
101
+ "latitude": 1,
102
+ "dir": 2,
103
+ "name": "finish"
104
+ },
105
+ "conditions": {
106
+ "clouds": [
107
+ {
108
+ "cover": 0.1,
109
+ "base": 1523.9999512320016
110
+ },
111
+ {
112
+ "cover": 0.2,
113
+ "base": 2285.9999268480024
114
+ },
115
+ {
116
+ "cover": 0.1,
117
+ "base": 2895.599907340803
118
+ }
119
+ ],
120
+ "time": "2024-06-14T13:15:38.000Z",
121
+ "wind": {
122
+ "direction": 190,
123
+ "speed": 11,
124
+ "gusts": 22
125
+ },
126
+ "turbulenceStrength": 1,
127
+ "thermalStrength": 0.5184,
128
+ "visibility": 14484.096000000001
129
+ }
130
+ }
131
+ ]