pyBADA 0.1.0__py3-none-any.whl

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 (99) hide show
  1. pyBADA/TCL.py +8731 -0
  2. pyBADA/__init__.py +0 -0
  3. pyBADA/aircraft/BADA3/DUMMY/BADA.GPF +113 -0
  4. pyBADA/aircraft/BADA3/DUMMY/BZJT__.APF +25 -0
  5. pyBADA/aircraft/BADA3/DUMMY/BZJT__.OPF +61 -0
  6. pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTD +139 -0
  7. pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTF +73 -0
  8. pyBADA/aircraft/BADA3/DUMMY/GA____.APF +25 -0
  9. pyBADA/aircraft/BADA3/DUMMY/GA____.OPF +61 -0
  10. pyBADA/aircraft/BADA3/DUMMY/GA____.PTD +71 -0
  11. pyBADA/aircraft/BADA3/DUMMY/GA____.PTF +39 -0
  12. pyBADA/aircraft/BADA3/DUMMY/J2H___.APF +25 -0
  13. pyBADA/aircraft/BADA3/DUMMY/J2H___.OPF +61 -0
  14. pyBADA/aircraft/BADA3/DUMMY/J2H___.PTD +131 -0
  15. pyBADA/aircraft/BADA3/DUMMY/J2H___.PTF +69 -0
  16. pyBADA/aircraft/BADA3/DUMMY/J2M___.APF +25 -0
  17. pyBADA/aircraft/BADA3/DUMMY/J2M___.OPF +61 -0
  18. pyBADA/aircraft/BADA3/DUMMY/J2M___.PTD +123 -0
  19. pyBADA/aircraft/BADA3/DUMMY/J2M___.PTF +65 -0
  20. pyBADA/aircraft/BADA3/DUMMY/J4H___.APF +25 -0
  21. pyBADA/aircraft/BADA3/DUMMY/J4H___.OPF +61 -0
  22. pyBADA/aircraft/BADA3/DUMMY/J4H___.PTD +139 -0
  23. pyBADA/aircraft/BADA3/DUMMY/J4H___.PTF +73 -0
  24. pyBADA/aircraft/BADA3/DUMMY/ReleaseSummary +35 -0
  25. pyBADA/aircraft/BADA3/DUMMY/SYNONYM.NEW +155 -0
  26. pyBADA/aircraft/BADA3/DUMMY/TP2M__.APF +25 -0
  27. pyBADA/aircraft/BADA3/DUMMY/TP2M__.OPF +61 -0
  28. pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTD +99 -0
  29. pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTF +53 -0
  30. pyBADA/aircraft/BADA4/DUMMY/ACM_BADA4.xsd +556 -0
  31. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.ATF +106 -0
  32. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.xml +244 -0
  33. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTD +182 -0
  34. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTF +41 -0
  35. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTD +182 -0
  36. pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTF +41 -0
  37. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.ATF +191 -0
  38. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.xml +540 -0
  39. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTD +218 -0
  40. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTF +49 -0
  41. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTD +218 -0
  42. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTF +49 -0
  43. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/LRC.dat +38 -0
  44. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MEC.dat +58 -0
  45. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MRC.dat +38 -0
  46. pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/OPTALT.dat +37 -0
  47. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.ATF +204 -0
  48. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.xml +648 -0
  49. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTD +281 -0
  50. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTF +63 -0
  51. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTD +281 -0
  52. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTF +63 -0
  53. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/ECON.OPT +37 -0
  54. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/LRC.OPT +38 -0
  55. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MEC.OPT +58 -0
  56. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MRC.OPT +38 -0
  57. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/OPTALT.OPT +37 -0
  58. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.ATF +238 -0
  59. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.xml +737 -0
  60. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTD +281 -0
  61. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTF +63 -0
  62. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTD +281 -0
  63. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTF +63 -0
  64. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/ECON.OPT +37 -0
  65. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/LRC.OPT +36 -0
  66. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MEC.OPT +56 -0
  67. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MRC.OPT +36 -0
  68. pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/OPTALT.OPT +37 -0
  69. pyBADA/aircraft/BADA4/DUMMY/GPF.xml +130 -0
  70. pyBADA/aircraft/BADA4/DUMMY/GPF_BADA4.xsd +75 -0
  71. pyBADA/aircraft/BADA4/DUMMY/aircraft_model_default.xml +311 -0
  72. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.ATF +97 -0
  73. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.xml +82 -0
  74. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTD +632 -0
  75. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTF +71 -0
  76. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTD +632 -0
  77. pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTF +71 -0
  78. pyBADA/aircraft/BADAH/DUMMY/DUMH/LRC.OPT +142 -0
  79. pyBADA/aircraft/BADAH/DUMMY/DUMH/MEC.OPT +142 -0
  80. pyBADA/aircraft/BADAH/DUMMY/DUMH/MRC.OPT +142 -0
  81. pyBADA/aircraft.py +414 -0
  82. pyBADA/atmosphere.py +345 -0
  83. pyBADA/bada3.py +4566 -0
  84. pyBADA/bada4.py +5327 -0
  85. pyBADA/badaE.py +3317 -0
  86. pyBADA/badaH.py +3632 -0
  87. pyBADA/configuration.py +98 -0
  88. pyBADA/constants.py +64 -0
  89. pyBADA/conversions.py +197 -0
  90. pyBADA/data/magneticDeclinationGridData.json +247067 -0
  91. pyBADA/flightTrajectory.py +929 -0
  92. pyBADA/geodesic.py +760 -0
  93. pyBADA/magnetic.py +119 -0
  94. pyBADA/trajectoryPrediction.py +175 -0
  95. pybada-0.1.0.dist-info/METADATA +57 -0
  96. pybada-0.1.0.dist-info/RECORD +99 -0
  97. pybada-0.1.0.dist-info/WHEEL +4 -0
  98. pybada-0.1.0.dist-info/licenses/AUTHORS +2 -0
  99. pybada-0.1.0.dist-info/licenses/LICENCE.txt +287 -0
pyBADA/bada3.py ADDED
@@ -0,0 +1,4566 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ pyBADA
4
+ Generic BADA3 aircraft performance module
5
+ Developped @EUROCONTROL (EIH)
6
+ 2024
7
+ """
8
+
9
+ __author__ = "Henrich Glaser-Opitz"
10
+ __copyright__ = "Copyright 2024, EUROCONTROL (EIH)"
11
+ __license__ = "BADA Eurocontrol"
12
+ __version__ = "1.0.0"
13
+ __maintainer__ = "Henrich Glaser-Opitz"
14
+ __email__ = "henrich.glaser-opitz@eurocontrol.int"
15
+ __status__ = "Development"
16
+ __docformat__ = "reStructuredText"
17
+
18
+ from math import sqrt, isnan, asin, atan
19
+ import numpy as np
20
+
21
+ import os
22
+ from datetime import date
23
+ import xml.etree.ElementTree as ET
24
+ import pandas as pd
25
+
26
+ from pyBADA import constants as const
27
+ from pyBADA import conversions as conv
28
+ from pyBADA import atmosphere as atm
29
+ from pyBADA import configuration as configuration
30
+ from pyBADA.aircraft import Airplane, BadaFamily
31
+
32
+
33
+ def proper_round(num, dec=0):
34
+ num = str(num)[: str(num).index(".") + dec + 2]
35
+ if num[-1] >= "5":
36
+ return float(num[: -2 - (not dec)] + str(int(num[-2 - (not dec)]) + 1))
37
+ return float(num[:-1])
38
+
39
+
40
+ def checkArgument(argument, **kwargs):
41
+ if kwargs.get(argument) is not None:
42
+ return kwargs.get(argument)
43
+ else:
44
+ raise TypeError("Missing " + argument + " argument")
45
+
46
+
47
+ class Parser(object):
48
+ """This class implements the BADA3 parsing mechanism to parse APF, OPF and GPF BADA3 files."""
49
+
50
+ def __init__(self):
51
+ pass
52
+
53
+ @staticmethod
54
+ def list_subfolders(folderPath):
55
+ # List all entries in the directory
56
+ entries = os.listdir(folderPath)
57
+
58
+ # Filter out entries that are directories
59
+ subfolders = [
60
+ entry for entry in entries if os.path.isdir(os.path.join(folderPath, entry))
61
+ ]
62
+
63
+ return subfolders
64
+
65
+ @staticmethod
66
+ def parseXML(filePath, badaVersion, acName):
67
+ """This function parses BADA3 xml formatted file
68
+
69
+ :param filePath: path to the folder with BADA4 xml formatted file.
70
+ :param acName: name of Aircraft for BADA3 xml formatted file.
71
+ :type filePath: str.
72
+ :type acName: str.
73
+ :raises: IOError
74
+ """
75
+
76
+ filename = os.path.join(filePath, "BADA3", badaVersion, acName, acName) + ".xml"
77
+
78
+ try:
79
+ tree = ET.parse(filename)
80
+ root = tree.getroot()
81
+ except:
82
+ raise IOError(filename + " not found or in correct format")
83
+
84
+ modificationDateOPF = "UNKNOWN"
85
+ modificationDateAPF = "UNKNOWN"
86
+
87
+ # Parse general aircraft data
88
+ model = root.find("model").text # aircraft model
89
+ engineType = root.find("type").text # engine type
90
+ engines = root.find("engine").text # engine name
91
+
92
+ ICAO = root.find("ICAO").find("designator").text
93
+ WTC = root.find("ICAO").find("WTC").text
94
+
95
+ # Parse engine data
96
+ AFCM = root.find("AFCM") # get AFCM
97
+ PFM = root.find("PFM") # get PFM
98
+ ALM = root.find("ALM") # get ALM
99
+ Ground = root.find("Ground") # get Ground
100
+ ARPM = root.find("ARPM") # get ARPM
101
+
102
+ # AFCM
103
+ S = float(AFCM.find("S").text)
104
+ MREF = float(AFCM.find("mref").text)
105
+
106
+ mass = {}
107
+ mass["reference"] = float(AFCM.find("mref").text)
108
+
109
+ name = {}
110
+ HLids = []
111
+ d = {}
112
+ CD0 = {}
113
+ CD2 = {}
114
+ Vstall = {}
115
+
116
+ for conf in AFCM.findall("Configuration"):
117
+ HLid = int(conf.get("HLid"))
118
+ HLids.append(str(HLid))
119
+ name[HLid] = conf.find("name").text
120
+
121
+ d[HLid] = {}
122
+ CD0[HLid] = {}
123
+ CD2[HLid] = {}
124
+ Vstall[HLid] = {}
125
+
126
+ LGUP = conf.find("LGUP")
127
+ LGDN = conf.find("LGDN")
128
+
129
+ if LGUP is not None:
130
+ DPM = LGUP.find("DPM")
131
+
132
+ d[HLid]["LGUP"] = []
133
+ for i in DPM.find("CD").findall("d"):
134
+ d[HLid]["LGUP"].append(float(i.text))
135
+
136
+ CD0[HLid]["LGUP"] = d[HLid]["LGUP"][0]
137
+ CD2[HLid]["LGUP"] = d[HLid]["LGUP"][1]
138
+
139
+ BLM = LGUP.find("BLM")
140
+
141
+ Vstall[HLid]["LGUP"] = []
142
+ if BLM is not None: # BLM is not clean
143
+ Vstall[HLid]["LGUP"] = float(BLM.find("VS").text)
144
+
145
+ else: # BLM is clean
146
+ BLM = LGUP.find("BLM_clean")
147
+
148
+ Vstall[HLid]["LGUP"] = float(BLM.find("VS").text)
149
+
150
+ CL_clean = BLM.find("CL_clean")
151
+
152
+ Clbo = float(CL_clean.find("Clbo").text)
153
+ k = float(CL_clean.find("k").text)
154
+
155
+ if LGDN is not None: # Landing gear NOT allowed in clean configuration
156
+
157
+ d[HLid]["LGDN"] = []
158
+ for i in LGDN.find("DPM").find("CD").findall("d"):
159
+ d[HLid]["LGDN"].append(float(i.text))
160
+
161
+ CD0[HLid]["LGDN"] = d[HLid]["LGDN"][0]
162
+ CD2[HLid]["LGDN"] = d[HLid]["LGDN"][1]
163
+
164
+ if LGDN.find("DPM").find("DeltaCD") is None:
165
+ DeltaCD = 0.0
166
+ else:
167
+ DeltaCD = float(LGDN.find("DPM").find("DeltaCD").text)
168
+
169
+ Vstall[HLid]["LGDN"] = float(LGDN.find("BLM").find("VS").text)
170
+
171
+ elif LGDN is None:
172
+ CD0[HLid]["LGDN"] = 0.0
173
+ CD2[HLid]["LGDN"] = 0.0
174
+ DeltaCD = 0.0
175
+
176
+ drone = False
177
+ if Vstall[0]["LGUP"] == 0.0:
178
+ drone = True
179
+
180
+ # PFM
181
+ numberOfEngines = float(PFM.find("n_eng").text)
182
+
183
+ CT = PFM.find("CT")
184
+ CTc1 = float(CT.find("CTc1").text)
185
+ CTc2 = float(CT.find("CTc2").text)
186
+ CTc3 = float(CT.find("CTc3").text)
187
+ CTc4 = float(CT.find("CTc4").text)
188
+ CTc5 = float(CT.find("CTc5").text)
189
+ Ct = [CTc1, CTc2, CTc3, CTc4, CTc5]
190
+
191
+ CTdeslow = float(CT.find("CTdeslow").text)
192
+ CTdeshigh = float(CT.find("CTdeshigh").text)
193
+ CTdesapp = float(CT.find("CTdesapp").text)
194
+ CTdesld = float(CT.find("CTdesld").text)
195
+ HpDes = float(CT.find("Hpdes").text)
196
+
197
+ CF = PFM.find("CF")
198
+
199
+ Cf1 = float(CF.find("Cf1").text)
200
+ Cf2 = float(CF.find("Cf2").text)
201
+ Cf3 = float(CF.find("Cf3").text)
202
+ Cf4 = float(CF.find("Cf4").text)
203
+ Cfcr = float(CF.find("Cfcr").text)
204
+
205
+ CfDes = [Cf3, Cf4]
206
+ CfCrz = float(CF.find("Cfcr").text)
207
+ Cf = [Cf1, Cf2]
208
+
209
+ # ALM
210
+ GLM = ALM.find("GLM")
211
+ hmo = float(GLM.find("hmo").text)
212
+ Hmax = float(GLM.find("hmax").text)
213
+ tempGrad = float(GLM.find("temp_grad").text)
214
+ massGrad = float(GLM.find("mass_grad").text)
215
+ mass["mass grad"] = float(GLM.find("mass_grad").text)
216
+
217
+ KLM = ALM.find("KLM")
218
+ MMO = float(KLM.find("mmo").text)
219
+ VMO = float(KLM.find("vmo").text)
220
+
221
+ DLM = ALM.find("DLM")
222
+ MTOW = float(DLM.find("MTOW").text)
223
+ OEW = float(DLM.find("OEW").text)
224
+ MPL = float(DLM.find("MPL").text)
225
+
226
+ mass["minimum"] = float(DLM.find("OEW").text)
227
+ mass["maximum"] = float(DLM.find("MTOW").text)
228
+ mass["max payload"] = float(DLM.find("MPL").text)
229
+
230
+ # Ground
231
+ dimensions = Ground.find("Dimensions")
232
+ Runway = Ground.find("Runway")
233
+ TOL = float(Runway.find("TOL").text)
234
+ LDL = float(Runway.find("LDL").text)
235
+ span = float(dimensions.find("span").text)
236
+ length = float(dimensions.find("length").text)
237
+
238
+ # ARPM
239
+ aeroConfSchedule = ARPM.find("AeroConfSchedule")
240
+
241
+ # all aerodynamic configurations
242
+ aeroConfig = {}
243
+ for conf in aeroConfSchedule.findall("AeroPhase"):
244
+ name = conf.find("name").text
245
+ HLid = int(conf.find("HLid").text)
246
+ LG = "LG" + conf.find("LG").text
247
+ aeroConfig[name] = {"name": name, "HLid": HLid, "LG": LG}
248
+
249
+ speedScheduleList = ARPM.find("SpeedScheduleList")
250
+ SpeedSchedule = speedScheduleList.find("SpeedSchedule")
251
+
252
+ # all phases of flight
253
+ speedSchedule = {}
254
+ for phaseOfFlight in SpeedSchedule.findall("SpeedPhase"):
255
+ name = phaseOfFlight.find("name").text
256
+ CAS1 = conv.kt2ms(float(phaseOfFlight.find("CAS1").text))
257
+ CAS2 = conv.kt2ms(float(phaseOfFlight.find("CAS2").text))
258
+ M = float(phaseOfFlight.find("M").text)
259
+ speedSchedule[name] = {"CAS1": CAS1, "CAS2": CAS2, "M": M}
260
+
261
+ V1 = {}
262
+ V1["cl"] = speedSchedule["Climb"]["CAS1"]
263
+ V1["cr"] = speedSchedule["Cruise"]["CAS1"]
264
+ V1["des"] = speedSchedule["Descent"]["CAS1"]
265
+
266
+ V2 = {}
267
+ V2["cl"] = speedSchedule["Climb"]["CAS2"]
268
+ V2["cr"] = speedSchedule["Cruise"]["CAS2"]
269
+ V2["des"] = speedSchedule["Descent"]["CAS2"]
270
+
271
+ M = {}
272
+ M["cl"] = speedSchedule["Climb"]["M"]
273
+ M["cr"] = speedSchedule["Cruise"]["M"]
274
+ M["des"] = speedSchedule["Descent"]["M"]
275
+
276
+ xmlFiles = True
277
+
278
+ # Single row dataframe
279
+ data = {
280
+ "acName": [acName],
281
+ "model": [model],
282
+ "engineType": [engineType],
283
+ "engines": [engines],
284
+ "ICAO": [ICAO],
285
+ "WTC": [WTC],
286
+ "modificationDateOPF": [modificationDateOPF],
287
+ "modificationDateAPF": [modificationDateAPF],
288
+ "S": [S],
289
+ "MREF": [MREF],
290
+ "mass": [mass],
291
+ "name": [name],
292
+ "HLids": [HLids],
293
+ "d": [d],
294
+ "CD0": [CD0],
295
+ "CD2": [CD2],
296
+ "Vstall": [Vstall],
297
+ "Clbo": [Clbo],
298
+ "k": [k],
299
+ "DeltaCD": [DeltaCD],
300
+ "drone": [drone],
301
+ "numberOfEngines": [numberOfEngines],
302
+ "CTc1": [CTc1],
303
+ "CTc2": [CTc2],
304
+ "CTc3": [CTc3],
305
+ "CTc4": [CTc4],
306
+ "CTc5": [CTc5],
307
+ "Ct": [Ct],
308
+ "CTdeslow": [CTdeslow],
309
+ "CTdeshigh": [CTdeshigh],
310
+ "CTdeshigh": [CTdeshigh],
311
+ "CTdesapp": [CTdesapp],
312
+ "CTdesld": [CTdesld],
313
+ "HpDes": [HpDes],
314
+ "Cf1": [Cf1],
315
+ "Cf2": [Cf2],
316
+ "Cf3": [Cf3],
317
+ "Cf4": [Cf4],
318
+ "Cfcr": [Cfcr],
319
+ "CfDes": [CfDes],
320
+ "CfCrz": [CfCrz],
321
+ "Cf": [Cf],
322
+ "hmo": [hmo],
323
+ "Hmax": [Hmax],
324
+ "tempGrad": [tempGrad],
325
+ "massGrad": [massGrad],
326
+ "mass": [mass],
327
+ "MMO": [MMO],
328
+ "VMO": [VMO],
329
+ "MTOW": [MTOW],
330
+ "OEW": [OEW],
331
+ "MPL": [MPL],
332
+ "TOL": [TOL],
333
+ "LDL": [LDL],
334
+ "span": [span],
335
+ "length": [length],
336
+ "span": [span],
337
+ "length": [length],
338
+ "aeroConfig": [aeroConfig],
339
+ "speedSchedule": [speedSchedule],
340
+ "V1": [V1],
341
+ "V2": [V2],
342
+ "M": [M],
343
+ "speedSchedule": [speedSchedule],
344
+ "xmlFiles": [xmlFiles],
345
+ }
346
+ df_single = pd.DataFrame(data)
347
+
348
+ return df_single
349
+
350
+ @staticmethod
351
+ def findData(f):
352
+ line = f.readline()
353
+ while line is not None and not line.startswith("CD"):
354
+ line = f.readline()
355
+
356
+ if line is None:
357
+ return f, None
358
+
359
+ line = " ".join(line.split())
360
+ line = line.strip().split(" ")
361
+ return f, line
362
+
363
+ @staticmethod
364
+ def parseOPF(filePath, badaVersion, acName):
365
+ """This function parses BADA3 ascii formatted file
366
+
367
+ :param filePath: path to the BADA3 ascii formatted file.
368
+ :param acName: ICAO aircraft designation
369
+ :type filePath: str.
370
+ :type acName: str
371
+ :raises: IOError
372
+ """
373
+
374
+ filename = (
375
+ os.path.join(
376
+ filePath,
377
+ "BADA3",
378
+ badaVersion,
379
+ acName,
380
+ )
381
+ + ".OPF"
382
+ )
383
+
384
+ idx = 0
385
+ with open(filename, "r", encoding="latin-1") as f:
386
+ while True:
387
+ line = f.readline()
388
+
389
+ if idx == 13:
390
+ if "with" in line:
391
+ engines = line.split("with")[1].split("engines")[0].strip()
392
+ else:
393
+ engines = "unknown"
394
+ idx += 1
395
+
396
+ if not line:
397
+ break
398
+ elif "Modification_date" in line:
399
+ data = line.split(":")[1].strip().split(" ")
400
+ modificationDateOPF = " ".join([data[0], data[1], data[2]])
401
+
402
+ elif "CC====== Actype" in line:
403
+ f, line = Parser.findData(f=f)
404
+ if line is None:
405
+ break
406
+ ICAO = line[1].replace("_", "")
407
+ numberOfEngines = int(line[2])
408
+ engineType = line[4].upper()
409
+ WTC = line[5]
410
+
411
+ elif "CC====== Mass (t)" in line:
412
+ f, line = Parser.findData(f=f)
413
+ if line is None:
414
+ break
415
+ mass = {}
416
+ MREF = float(line[1]) * 1000.0
417
+ mass["reference"] = float(line[1]) * 1000.0
418
+ mass["minimum"] = float(line[2]) * 1000.0
419
+ mass["maximum"] = float(line[3]) * 1000.0
420
+ mass["max payload"] = float(line[4]) * 1000.0
421
+ mass["mass grad"] = float(line[5])
422
+
423
+ MTOW = mass["maximum"]
424
+ OEW = mass["minimum"]
425
+ MPL = mass["max payload"]
426
+ massGrad = mass["mass grad"]
427
+
428
+ elif "CC====== Flight envelope" in line:
429
+ f, line = Parser.findData(f=f)
430
+ if line is None:
431
+ break
432
+ VMO = float(line[1])
433
+ MMO = float(line[2])
434
+ hmo = float(line[3])
435
+ Hmax = float(line[4])
436
+ tempGrad = float(line[5])
437
+
438
+ elif "CC====== Aerodynamics" in line:
439
+ f, line = Parser.findData(f=f)
440
+ if line is None:
441
+ break
442
+ ndrst = int(line[1])
443
+ S = float(line[2])
444
+ Clbo = float(line[3])
445
+ k = float(line[4])
446
+
447
+ n = 1
448
+ Vstall = {}
449
+ CD0 = {}
450
+ CD2 = {}
451
+ HLids = []
452
+ while n <= ndrst:
453
+ f, line = Parser.findData(f=f)
454
+ if line is None:
455
+ break
456
+ HLid = line[2]
457
+
458
+ Vstall[HLid] = float(line[-5])
459
+ CD0[HLid] = float(line[-4])
460
+ CD2[HLid] = float(line[-3])
461
+ HLids.append(str(HLid))
462
+ n += 1
463
+
464
+ drone = False
465
+ if Vstall["CR"] == 0.0:
466
+ drone = True
467
+
468
+ iterator = 1
469
+ while iterator <= 2:
470
+ f, line = Parser.findData(f=f)
471
+ if "EXT" in line[2]:
472
+ CD2["SPOILER_EXT"] = float(line[3])
473
+ iterator += 1
474
+
475
+ iterator = 1
476
+ while iterator <= 2:
477
+ f, line = Parser.findData(f=f)
478
+ if "DOWN" in line[2]:
479
+ CD0["GEAR_DOWN"] = float(line[3])
480
+ CD2["GEAR_DOWN"] = float(line[4])
481
+ iterator += 1
482
+
483
+ iterator = 1
484
+ while iterator <= 2:
485
+ f, line = Parser.findData(f=f)
486
+ if "ON" in line[2]:
487
+ CD2["BRAKES_ON"] = float(line[3])
488
+ iterator += 1
489
+
490
+ elif "CC====== Engine Thrust" in line:
491
+ f, line = Parser.findData(f=f)
492
+ if line is None:
493
+ break
494
+ Ct = [float(i) for i in line[1:-1]]
495
+ f, line = Parser.findData(f=f)
496
+ if line is None:
497
+ break
498
+
499
+ CTdeslow = float(line[1])
500
+ CTdeshigh = float(line[2])
501
+ CTdesapp = float(line[4])
502
+ CTdesld = float(line[5])
503
+ HpDes = float(line[3])
504
+
505
+ # self.CtDes = {}
506
+ # self.CtDes["low"] = float(line[1])
507
+ # self.CtDes["high"] = float(line[2])
508
+ # self.HpDes = float(line[3])
509
+ # self.CtDes["app"] = float(line[4])
510
+ # self.CtDes["lnd"] = float(line[5])
511
+ f, line = Parser.findData(f=f)
512
+ if line is None:
513
+ break
514
+
515
+ elif "CC====== Fuel Consumption" in line:
516
+ f, line = Parser.findData(f=f)
517
+ if line is None:
518
+ break
519
+ Cf = [float(i) for i in line[1:-1]]
520
+ f, line = Parser.findData(f=f)
521
+ if line is None:
522
+ break
523
+ CfDes = [float(i) for i in line[1:-1]]
524
+ f, line = Parser.findData(f=f)
525
+ if line is None:
526
+ break
527
+ CfCrz = float(line[1])
528
+
529
+ elif "CC====== Ground" in line:
530
+ f, line = Parser.findData(f=f)
531
+ if line is None:
532
+ break
533
+ TOL = float(line[1])
534
+ LDL = float(line[2])
535
+ span = float(line[3])
536
+ length = float(line[4])
537
+
538
+ # Single row dataframe
539
+ data = {
540
+ "acName": [acName],
541
+ "engineType": [engineType],
542
+ "engines": [engines],
543
+ "ICAO": [ICAO],
544
+ "WTC": [WTC],
545
+ "modificationDateOPF": [modificationDateOPF],
546
+ "modificationDateOPF": [modificationDateOPF],
547
+ "S": [S],
548
+ "MREF": [MREF],
549
+ "mass": [mass],
550
+ "HLids": [HLids],
551
+ "CD0": [CD0],
552
+ "CD2": [CD2],
553
+ "Vstall": [Vstall],
554
+ "Clbo": [Clbo],
555
+ "k": [k],
556
+ "drone": [drone],
557
+ "numberOfEngines": [numberOfEngines],
558
+ "Ct": [Ct],
559
+ "CTdeslow": [CTdeslow],
560
+ "CTdeshigh": [CTdeshigh],
561
+ "CTdeshigh": [CTdeshigh],
562
+ "CTdesapp": [CTdesapp],
563
+ "CTdesld": [CTdesld],
564
+ "HpDes": [HpDes],
565
+ "CfDes": [CfDes],
566
+ "CfCrz": [CfCrz],
567
+ "Cf": [Cf],
568
+ "hmo": [hmo],
569
+ "Hmax": [Hmax],
570
+ "tempGrad": [tempGrad],
571
+ "mass": [mass],
572
+ "MMO": [MMO],
573
+ "VMO": [VMO],
574
+ "massGrad": [massGrad],
575
+ "MTOW": [MTOW],
576
+ "OEW": [OEW],
577
+ "MPL": [MPL],
578
+ "TOL": [TOL],
579
+ "LDL": [LDL],
580
+ "span": [span],
581
+ "length": [length],
582
+ "span": [span],
583
+ "length": [length],
584
+ }
585
+ df_single = pd.DataFrame(data)
586
+
587
+ return df_single
588
+
589
+ @staticmethod
590
+ def parseAPF(filePath, badaVersion, acName):
591
+ """This function parses BADA3 APF ascii formatted file
592
+
593
+ :param filePath: path to the BADA3 APF ascii formatted file.
594
+ :param acName: ICAO aircraft designation
595
+ :type filePath: str.
596
+ :type acName: str
597
+ :raises: IOError
598
+ """
599
+
600
+ filename = os.path.join(filePath, "BADA3", badaVersion, acName) + ".APF"
601
+
602
+ dataLines = list()
603
+ with open(filename, "r", encoding="latin-1") as f:
604
+ while True:
605
+ line = f.readline()
606
+
607
+ if line.startswith("CC"):
608
+ if "Modification_date" in line:
609
+ data = line.split(":")[1].strip().split(" ")
610
+ modificationDateAPF = " ".join([data[0], data[1], data[2]])
611
+ if line.startswith("CD"):
612
+ line = " ".join(line.split())
613
+ line = line.strip().split(" ")
614
+
615
+ if "LO" in line:
616
+ line = line[line.index("LO") + 1 :]
617
+ elif "AV" in line:
618
+ line = line[line.index("AV") + 1 :]
619
+ elif "HI" in line:
620
+ line = line[line.index("HI") + 1 :]
621
+
622
+ dataLines.append(line)
623
+ elif "THE END" in line:
624
+ break
625
+ dataLines.pop(0) # remove first line that does not contain usefull data
626
+
627
+ # AV - average - line with average data
628
+ AVLine = dataLines[1]
629
+ # reading of V1 parameter from APF file
630
+
631
+ V1 = {}
632
+ V1["cl"] = conv.kt2ms(AVLine[0])
633
+ V1["cr"] = conv.kt2ms(AVLine[3])
634
+ V1["des"] = conv.kt2ms(AVLine[8])
635
+
636
+ V2 = {}
637
+ V2["cl"] = conv.kt2ms(AVLine[1])
638
+ V2["cr"] = conv.kt2ms(AVLine[4])
639
+ V2["des"] = conv.kt2ms(AVLine[7])
640
+
641
+ M = {}
642
+ M["cl"] = float(AVLine[2]) / 100
643
+ M["cr"] = float(AVLine[5]) / 100
644
+ M["des"] = float(AVLine[6]) / 100
645
+
646
+ # Single row dataframe
647
+ data = {
648
+ "modificationDateAPF": [modificationDateAPF],
649
+ "V1": [V1],
650
+ "V2": [V2],
651
+ "M": [M],
652
+ }
653
+ df_single = pd.DataFrame(data)
654
+
655
+ return df_single
656
+
657
+ @staticmethod
658
+ def combineOPF_APF(OPFDataFrame, APFDataFrame):
659
+
660
+ # Combine data with GPF data (temporary solution)
661
+ combined_df = pd.concat(
662
+ [OPFDataFrame.reset_index(drop=True), APFDataFrame.reset_index(drop=True)],
663
+ axis=1,
664
+ )
665
+
666
+ return combined_df
667
+
668
+ @staticmethod
669
+ def readSynonym(filePath, badaVersion):
670
+
671
+ filename = os.path.join(filePath, "BADA3", badaVersion, "SYNONYM.NEW")
672
+
673
+ # synonym - file name pair dictionary
674
+ synonym_fileName = {}
675
+
676
+ if os.path.isfile(filename):
677
+ with open(filename, "r", encoding="latin-1") as f:
678
+ while True:
679
+ line = f.readline()
680
+
681
+ if not line:
682
+ break
683
+
684
+ if line.startswith("CD"):
685
+ line = " ".join(line.split())
686
+ line = line.strip().split(" ")
687
+
688
+ model = str(line[2])
689
+ file = str(line[-3])
690
+
691
+ synonym_fileName[model] = file
692
+
693
+ return synonym_fileName
694
+
695
+ @staticmethod
696
+ def readSynonymXML(filePath, badaVersion):
697
+
698
+ filename = os.path.join(filePath, "BADA3", badaVersion, "SYNONYM.xml")
699
+
700
+ # synonym - file name pair dictionary
701
+ synonym_fileName = {}
702
+
703
+ if os.path.isfile(filename):
704
+ try:
705
+ tree = ET.parse(filename)
706
+ root = tree.getroot()
707
+ except:
708
+ raise IOError(filename + " not found or in correct format")
709
+
710
+ for child in root.iter("SYN"):
711
+ code = child.find("code").text
712
+ manufacturer = child.find("manu").text
713
+ file = child.find("file").text
714
+ ICAO = child.find("ICAO").text
715
+
716
+ synonym_fileName[code] = file
717
+
718
+ return synonym_fileName
719
+
720
+ @staticmethod
721
+ def parseSynonym(filePath, badaVersion, acName):
722
+
723
+ synonym_fileName = Parser.readSynonym(filePath, badaVersion)
724
+
725
+ # if ASCI synonym does not exist, try XML synonym file
726
+ if not synonym_fileName:
727
+ synonym_fileName = Parser.readSynonymXML(filePath, badaVersion)
728
+
729
+ if synonym_fileName and acName in synonym_fileName:
730
+ fileName = synonym_fileName[acName]
731
+ return fileName
732
+ else:
733
+ return None
734
+
735
+ @staticmethod
736
+ def readGPF(filePath, badaVersion):
737
+ """This function parses BADA3 GPF ascii formatted file
738
+
739
+ :param filePath: path to the BADA3 GPF ascii formatted file.
740
+ :type filePath: str.
741
+ :raises: IOError
742
+ """
743
+
744
+ filename = os.path.join(filePath, "BADA3", badaVersion, "BADA.GPF")
745
+
746
+ GPFparamList = list()
747
+
748
+ if os.path.isfile(filename):
749
+ with open(filename, "r", encoding="latin-1") as f:
750
+ while True:
751
+ line = f.readline()
752
+
753
+ if not line:
754
+ break
755
+
756
+ if line.startswith("CD"):
757
+ line = " ".join(line.split())
758
+ line = line.strip().split(" ")
759
+ param = {
760
+ "name": str(line[1]),
761
+ "value": float(line[5]),
762
+ "engine": str(line[3]).split(","),
763
+ "phase": str(line[4]).split(","),
764
+ "flight": str(line[2]),
765
+ }
766
+
767
+ GPFparamList.append(param)
768
+ return GPFparamList
769
+
770
+ @staticmethod
771
+ def readGPFXML(filePath, badaVersion):
772
+ """This function parses BADA3 GPF xml formatted file
773
+
774
+ :param filePath: path to the GPF xml formatted file.
775
+ :type filePath: str.
776
+ :raises: IOError
777
+ """
778
+
779
+ filename = os.path.join(filePath, "BADA3", badaVersion, "GPF.xml")
780
+
781
+ GPFparamList = list()
782
+
783
+ if os.path.isfile(filename):
784
+ try:
785
+ tree = ET.parse(filename)
786
+ root = tree.getroot()
787
+ except:
788
+ raise IOError(filename + " not found or in correct format")
789
+
790
+ allEngines = ["JET", "TURBOPROP", "PISTON", "ELECTRIC"]
791
+ allPhases = ["to", "ic", "cl", "cr", "des", "hold", "app", "lnd"]
792
+ allFlights = ["civ", "mil"]
793
+
794
+ # Parse general aircraft data
795
+ AccMax = root.find("AccMax")
796
+ GPFparamList.append(
797
+ {
798
+ "name": "acc_long_max",
799
+ "value": float(AccMax.find("long").text),
800
+ "engine": allEngines,
801
+ "phase": allPhases,
802
+ "flight": allFlights,
803
+ }
804
+ )
805
+ GPFparamList.append(
806
+ {
807
+ "name": "acc_norm_max",
808
+ "value": float(AccMax.find("norm").text),
809
+ "engine": allEngines,
810
+ "phase": allPhases,
811
+ "flight": allFlights,
812
+ }
813
+ )
814
+
815
+ AngBank = root.find("AngBank")
816
+ GPFparamList.append(
817
+ {
818
+ "name": "ang_bank_nom",
819
+ "value": float(AngBank.find("Nom").find("Civ").find("ToLd").text),
820
+ "engine": allEngines,
821
+ "phase": ["to", "ld"],
822
+ "flight": ["civ"],
823
+ }
824
+ )
825
+ GPFparamList.append(
826
+ {
827
+ "name": "ang_bank_nom",
828
+ "value": float(AngBank.find("Nom").find("Civ").find("Others").text),
829
+ "engine": allEngines,
830
+ "phase": ["ic", "cl", "cr", "des", "hold", "app"],
831
+ "flight": ["civ"],
832
+ }
833
+ )
834
+ GPFparamList.append(
835
+ {
836
+ "name": "ang_bank_nom",
837
+ "value": float(AngBank.find("Nom").find("Mil").text),
838
+ "engine": allEngines,
839
+ "phase": allPhases,
840
+ "flight": ["mil"],
841
+ }
842
+ )
843
+ GPFparamList.append(
844
+ {
845
+ "name": "ang_bank_max",
846
+ "value": float(AngBank.find("Max").find("Civ").find("ToLd").text),
847
+ "engine": allEngines,
848
+ "phase": ["to", "ld"],
849
+ "flight": ["civ"],
850
+ }
851
+ )
852
+ GPFparamList.append(
853
+ {
854
+ "name": "ang_bank_max",
855
+ "value": float(AngBank.find("Max").find("Civ").find("Hold").text),
856
+ "engine": allEngines,
857
+ "phase": ["hold"],
858
+ "flight": ["civ"],
859
+ }
860
+ )
861
+ GPFparamList.append(
862
+ {
863
+ "name": "ang_bank_max",
864
+ "value": float(AngBank.find("Max").find("Civ").find("Others").text),
865
+ "engine": allEngines,
866
+ "phase": ["ic", "cl", "cr", "des", "app"],
867
+ "flight": ["civ"],
868
+ }
869
+ )
870
+ GPFparamList.append(
871
+ {
872
+ "name": "ang_bank_max",
873
+ "value": float(AngBank.find("Max").find("Mil").text),
874
+ "engine": allEngines,
875
+ "phase": allPhases,
876
+ "flight": ["mil"],
877
+ }
878
+ )
879
+
880
+ GPFparamList.append(
881
+ {
882
+ "name": "C_des_exp",
883
+ "value": float(root.find("CDesExp").text),
884
+ "engine": allEngines,
885
+ "phase": allPhases,
886
+ "flight": allFlights,
887
+ }
888
+ )
889
+ GPFparamList.append(
890
+ {
891
+ "name": "C_th_to",
892
+ "value": float(root.find("CThTO").text),
893
+ "engine": allEngines,
894
+ "phase": allPhases,
895
+ "flight": allFlights,
896
+ }
897
+ )
898
+ GPFparamList.append(
899
+ {
900
+ "name": "C_th_cr",
901
+ "value": float(root.find("CTcr").text),
902
+ "engine": allEngines,
903
+ "phase": allPhases,
904
+ "flight": allFlights,
905
+ }
906
+ )
907
+ GPFparamList.append(
908
+ {
909
+ "name": "C_v_min_to",
910
+ "value": float(root.find("CVminTO").text),
911
+ "engine": allEngines,
912
+ "phase": allPhases,
913
+ "flight": allFlights,
914
+ }
915
+ )
916
+ GPFparamList.append(
917
+ {
918
+ "name": "C_v_min",
919
+ "value": float(root.find("CVmin").text),
920
+ "engine": allEngines,
921
+ "phase": allPhases,
922
+ "flight": allFlights,
923
+ }
924
+ )
925
+
926
+ HmaxList = {}
927
+ for phase in root.find("HmaxList").findall("HmaxPhase"):
928
+
929
+ HmaxList[phase.find("Phase").text] = float(phase.find("Hmax").text)
930
+
931
+ if phase.find("Phase").text == "TO":
932
+ GPFparamList.append(
933
+ {
934
+ "name": "H_max_to",
935
+ "value": float(phase.find("Hmax").text),
936
+ "engine": allEngines,
937
+ "phase": ["to"],
938
+ "flight": allFlights,
939
+ }
940
+ )
941
+
942
+ elif phase.find("Phase").text == "IC":
943
+ GPFparamList.append(
944
+ {
945
+ "name": "H_max_ic",
946
+ "value": float(phase.find("Hmax").text),
947
+ "engine": allEngines,
948
+ "phase": ["ic"],
949
+ "flight": allFlights,
950
+ }
951
+ )
952
+
953
+ elif phase.find("Phase").text == "AP":
954
+ GPFparamList.append(
955
+ {
956
+ "name": "H_max_app",
957
+ "value": float(phase.find("Hmax").text),
958
+ "engine": allEngines,
959
+ "phase": ["app"],
960
+ "flight": allFlights,
961
+ }
962
+ )
963
+
964
+ elif phase.find("Phase").text == "LD":
965
+ GPFparamList.append(
966
+ {
967
+ "name": "H_max_ld",
968
+ "value": float(phase.find("Hmax").text),
969
+ "engine": allEngines,
970
+ "phase": ["lnd"],
971
+ "flight": allFlights,
972
+ }
973
+ )
974
+
975
+ VdList = {}
976
+ for vdphase in root.find("VdList").findall("VdPhase"):
977
+
978
+ Phase = vdphase.find("Phase")
979
+ name = Phase.find("name").text
980
+ index = int(Phase.find("index").text)
981
+ Vd = float(vdphase.find("Vd").text)
982
+
983
+ if name not in VdList:
984
+ VdList[name] = {}
985
+
986
+ VdList[name][index] = Vd
987
+
988
+ if name == "CL":
989
+ if index == 1:
990
+ V_cl_1 = Vd
991
+ GPFparamList.append(
992
+ {
993
+ "name": "V_cl_1",
994
+ "value": V_cl_1,
995
+ "engine": allEngines,
996
+ "phase": ["cl"],
997
+ "flight": allFlights,
998
+ }
999
+ )
1000
+ elif index == 2:
1001
+ V_cl_2 = Vd
1002
+ GPFparamList.append(
1003
+ {
1004
+ "name": "V_cl_2",
1005
+ "value": V_cl_2,
1006
+ "engine": allEngines,
1007
+ "phase": ["cl"],
1008
+ "flight": allFlights,
1009
+ }
1010
+ )
1011
+ elif index == 3:
1012
+ V_cl_3 = Vd
1013
+ GPFparamList.append(
1014
+ {
1015
+ "name": "V_cl_3",
1016
+ "value": V_cl_3,
1017
+ "engine": allEngines,
1018
+ "phase": ["cl"],
1019
+ "flight": allFlights,
1020
+ }
1021
+ )
1022
+ elif index == 4:
1023
+ V_cl_4 = Vd
1024
+ GPFparamList.append(
1025
+ {
1026
+ "name": "V_cl_4",
1027
+ "value": V_cl_4,
1028
+ "engine": allEngines,
1029
+ "phase": ["cl"],
1030
+ "flight": allFlights,
1031
+ }
1032
+ )
1033
+ elif index == 5:
1034
+ V_cl_5 = Vd
1035
+ GPFparamList.append(
1036
+ {
1037
+ "name": "V_cl_5",
1038
+ "value": V_cl_5,
1039
+ "engine": allEngines,
1040
+ "phase": ["cl"],
1041
+ "flight": allFlights,
1042
+ }
1043
+ )
1044
+ elif index == 6:
1045
+ V_cl_6 = Vd
1046
+ GPFparamList.append(
1047
+ {
1048
+ "name": "V_cl_6",
1049
+ "value": V_cl_6,
1050
+ "engine": allEngines,
1051
+ "phase": ["cl"],
1052
+ "flight": allFlights,
1053
+ }
1054
+ )
1055
+ elif index == 7:
1056
+ V_cl_7 = Vd
1057
+ GPFparamList.append(
1058
+ {
1059
+ "name": "V_cl_7",
1060
+ "value": V_cl_7,
1061
+ "engine": allEngines,
1062
+ "phase": ["cl"],
1063
+ "flight": allFlights,
1064
+ }
1065
+ )
1066
+ elif index == 8:
1067
+ V_cl_8 = Vd
1068
+ GPFparamList.append(
1069
+ {
1070
+ "name": "V_cl_8",
1071
+ "value": V_cl_8,
1072
+ "engine": allEngines,
1073
+ "phase": ["cl"],
1074
+ "flight": allFlights,
1075
+ }
1076
+ )
1077
+
1078
+ if name == "DES":
1079
+ if index == 1:
1080
+ V_des_1 = Vd
1081
+ GPFparamList.append(
1082
+ {
1083
+ "name": "V_des_1",
1084
+ "value": V_des_1,
1085
+ "engine": allEngines,
1086
+ "phase": ["des"],
1087
+ "flight": allFlights,
1088
+ }
1089
+ )
1090
+ elif index == 2:
1091
+ V_des_2 = Vd
1092
+ GPFparamList.append(
1093
+ {
1094
+ "name": "V_des_2",
1095
+ "value": V_des_2,
1096
+ "engine": allEngines,
1097
+ "phase": ["des"],
1098
+ "flight": allFlights,
1099
+ }
1100
+ )
1101
+ elif index == 3:
1102
+ V_des_3 = Vd
1103
+ GPFparamList.append(
1104
+ {
1105
+ "name": "V_des_3",
1106
+ "value": V_des_3,
1107
+ "engine": allEngines,
1108
+ "phase": ["des"],
1109
+ "flight": allFlights,
1110
+ }
1111
+ )
1112
+ elif index == 4:
1113
+ V_des_4 = Vd
1114
+ GPFparamList.append(
1115
+ {
1116
+ "name": "V_des_4",
1117
+ "value": V_des_4,
1118
+ "engine": allEngines,
1119
+ "phase": ["des"],
1120
+ "flight": allFlights,
1121
+ }
1122
+ )
1123
+ elif index == 5:
1124
+ V_des_5 = Vd
1125
+ GPFparamList.append(
1126
+ {
1127
+ "name": "V_des_5",
1128
+ "value": V_des_5,
1129
+ "engine": allEngines,
1130
+ "phase": ["des"],
1131
+ "flight": allFlights,
1132
+ }
1133
+ )
1134
+ elif index == 6:
1135
+ V_des_6 = Vd
1136
+ GPFparamList.append(
1137
+ {
1138
+ "name": "V_des_6",
1139
+ "value": V_des_6,
1140
+ "engine": allEngines,
1141
+ "phase": ["des"],
1142
+ "flight": allFlights,
1143
+ }
1144
+ )
1145
+ elif index == 7:
1146
+ V_des_7 = Vd
1147
+ GPFparamList.append(
1148
+ {
1149
+ "name": "V_des_7",
1150
+ "value": V_des_7,
1151
+ "engine": allEngines,
1152
+ "phase": ["des"],
1153
+ "flight": allFlights,
1154
+ }
1155
+ )
1156
+
1157
+ VList = {}
1158
+ for vphase in root.find("VList").findall("VPhase"):
1159
+
1160
+ Phase = vphase.find("Phase")
1161
+ name = Phase.find("name").text
1162
+ index = int(Phase.find("index").text)
1163
+ V = float(vphase.find("V").text)
1164
+
1165
+ if name not in VList:
1166
+ VList[name] = {}
1167
+
1168
+ VList[name][index] = V
1169
+
1170
+ if name == "HOLD":
1171
+ if index == 1:
1172
+ V_hold_1 = V
1173
+ GPFparamList.append(
1174
+ {
1175
+ "name": "V_hold_1",
1176
+ "value": V_hold_1,
1177
+ "engine": allEngines,
1178
+ "phase": ["hold"],
1179
+ "flight": allFlights,
1180
+ }
1181
+ )
1182
+ elif index == 2:
1183
+ V_hold_2 = V
1184
+ GPFparamList.append(
1185
+ {
1186
+ "name": "V_hold_2",
1187
+ "value": V_hold_2,
1188
+ "engine": allEngines,
1189
+ "phase": ["hold"],
1190
+ "flight": allFlights,
1191
+ }
1192
+ )
1193
+ elif index == 3:
1194
+ V_hold_3 = V
1195
+ GPFparamList.append(
1196
+ {
1197
+ "name": "V_hold_3",
1198
+ "value": V_hold_3,
1199
+ "engine": allEngines,
1200
+ "phase": ["hold"],
1201
+ "flight": allFlights,
1202
+ }
1203
+ )
1204
+ elif index == 4:
1205
+ V_hold_4 = V
1206
+ GPFparamList.append(
1207
+ {
1208
+ "name": "V_hold_4",
1209
+ "value": V_hold_4,
1210
+ "engine": allEngines,
1211
+ "phase": ["hold"],
1212
+ "flight": allFlights,
1213
+ }
1214
+ )
1215
+
1216
+ V_backtrack = float(root.find("Vground").find("backtrack").text)
1217
+ GPFparamList.append(
1218
+ {
1219
+ "name": "V_backtrack",
1220
+ "value": V_backtrack,
1221
+ "engine": allEngines,
1222
+ "phase": ["gnd"],
1223
+ "flight": allFlights,
1224
+ }
1225
+ )
1226
+ V_taxi = float(root.find("Vground").find("taxi").text)
1227
+ GPFparamList.append(
1228
+ {
1229
+ "name": "V_taxi",
1230
+ "value": V_taxi,
1231
+ "engine": allEngines,
1232
+ "phase": ["gnd"],
1233
+ "flight": allFlights,
1234
+ }
1235
+ )
1236
+ V_apron = float(root.find("Vground").find("apron").text)
1237
+ GPFparamList.append(
1238
+ {
1239
+ "name": "V_apron",
1240
+ "value": V_apron,
1241
+ "engine": allEngines,
1242
+ "phase": ["gnd"],
1243
+ "flight": allFlights,
1244
+ }
1245
+ )
1246
+ V_gate = float(root.find("Vground").find("gate").text)
1247
+ GPFparamList.append(
1248
+ {
1249
+ "name": "V_gate",
1250
+ "value": V_gate,
1251
+ "engine": allEngines,
1252
+ "phase": ["gnd"],
1253
+ "flight": allFlights,
1254
+ }
1255
+ )
1256
+
1257
+ CredList = {}
1258
+ for CredEng in root.find("CredList").findall("CredEng"):
1259
+ EngineType = CredEng.find("EngineType").text
1260
+ Cred = float(CredEng.find("Cred").text)
1261
+
1262
+ CredList[EngineType] = Cred
1263
+
1264
+ if EngineType == "JET":
1265
+ GPFparamList.append(
1266
+ {
1267
+ "name": "C_red_jet",
1268
+ "value": Cred,
1269
+ "engine": ["jet"],
1270
+ "phase": ["ic", "cl"],
1271
+ "flight": allFlights,
1272
+ }
1273
+ )
1274
+ elif EngineType == "TURBOPROP":
1275
+ GPFparamList.append(
1276
+ {
1277
+ "name": "C_red_turbo",
1278
+ "value": Cred,
1279
+ "engine": ["tbp"],
1280
+ "phase": ["ic", "cl"],
1281
+ "flight": allFlights,
1282
+ }
1283
+ )
1284
+ elif EngineType == "PISTON":
1285
+ GPFparamList.append(
1286
+ {
1287
+ "name": "C_red_piston",
1288
+ "value": Cred,
1289
+ "engine": ["pst"],
1290
+ "phase": ["ic", "cl"],
1291
+ "flight": allFlights,
1292
+ }
1293
+ )
1294
+ elif EngineType == "ELECTRIC":
1295
+ GPFparamList.append(
1296
+ {
1297
+ "name": "C_red_elec",
1298
+ "value": Cred,
1299
+ "engine": ["elc"],
1300
+ "phase": ["ic", "cl"],
1301
+ "flight": allFlights,
1302
+ }
1303
+ )
1304
+
1305
+ return GPFparamList
1306
+
1307
+ @staticmethod
1308
+ def parseGPF(filePath, badaVersion):
1309
+ GPFdata = Parser.readGPF(filePath, badaVersion)
1310
+
1311
+ # if ASCI GPF does not exist, try XML GPF file
1312
+ if not GPFdata:
1313
+ GPFdata = Parser.readGPFXML(filePath, badaVersion)
1314
+
1315
+ # Single row dataframe
1316
+ data = {"GPFdata": [GPFdata]}
1317
+ df_single = pd.DataFrame(data)
1318
+
1319
+ return df_single
1320
+
1321
+ @staticmethod
1322
+ def getGPFValue(GPFdata, name, engine="JET", phase="cr", flight="civ"):
1323
+ """This function returns value of the GPF parameter based on the defined features
1324
+ like flight, Engine and Phase of flight
1325
+
1326
+ :param Name: name of the GPF parameter.
1327
+ :param Engine: type of the engine where this parameter can be applied.
1328
+ :param Phase: phase of the flight, where this parameter can be applied.
1329
+ :param flight: flight where this parameter can be applied (civ or mil).
1330
+ :type Name: str.
1331
+ :type Engine: str.
1332
+ :type Phase: str.
1333
+ :type flight: str.
1334
+
1335
+ """
1336
+ # implementation required because 3.16 GPF contains different engine names than 3.15 GPF file
1337
+ if engine == "JET":
1338
+ engineList = [engine, "jet"]
1339
+ if engine == "TURBOPROP":
1340
+ engineList = [engine, "turbo", "tbp"]
1341
+ if engine == "PISTON":
1342
+ engineList = [engine, "piston", "pst"]
1343
+ if engine == "ELECTRIC":
1344
+ engineList = [engine, "electric", "elc"]
1345
+
1346
+ for param in GPFdata:
1347
+ if (
1348
+ (param["name"] == name)
1349
+ & (any(i in engineList for i in param["engine"]))
1350
+ & (phase in param["phase"])
1351
+ & (flight in param["flight"])
1352
+ ):
1353
+ return float(param["value"])
1354
+ return None
1355
+
1356
+ @staticmethod
1357
+ def combineACDATA_GPF(ACDataFrame, GPFDataframe):
1358
+ """This function combines 2 dataframes, the parsed aircraft file
1359
+ and parsed GPF file
1360
+ """
1361
+
1362
+ # Combine data with GPF data (temporary solution)
1363
+ combined_df = pd.concat(
1364
+ [ACDataFrame.reset_index(drop=True), GPFDataframe.reset_index(drop=True)],
1365
+ axis=1,
1366
+ )
1367
+
1368
+ return combined_df
1369
+
1370
+ @staticmethod
1371
+ def parseAll(badaVersion, filePath=None):
1372
+ """This function parses all BADA3 formatted file and stores
1373
+ all data in the final dataframe containing all the BADA data.
1374
+
1375
+ :param filePath: path to the BADA3 formatted file.
1376
+ :type filePath: str.
1377
+ :raises: IOError
1378
+ """
1379
+
1380
+ if filePath == None:
1381
+ filePath = configuration.getAircraftPath()
1382
+ else:
1383
+ filePath = filePath
1384
+
1385
+ # parsing GPF file
1386
+ GPFparsedDataframe = Parser.parseGPF(filePath, badaVersion)
1387
+
1388
+ # try to get subfolders, if they exist
1389
+ # get names of all the folders in the main BADA model folder to search for XML files
1390
+ folderPath = os.path.join(filePath, "BADA3", badaVersion)
1391
+ subfolders = Parser.list_subfolders(folderPath)
1392
+
1393
+ if not subfolders:
1394
+ # use APF and OPF files
1395
+ merged_df = pd.DataFrame()
1396
+
1397
+ # get synonym-filename pairs
1398
+ synonym_fileName = Parser.readSynonym(filePath, badaVersion)
1399
+
1400
+ for synonym in synonym_fileName:
1401
+ file = synonym_fileName[synonym]
1402
+
1403
+ # parse the original data of a model
1404
+ OPFDataFrame = Parser.parseOPF(filePath, badaVersion, file)
1405
+ APFDataFrame = Parser.parseAPF(filePath, badaVersion, file)
1406
+
1407
+ df = Parser.combineOPF_APF(OPFDataFrame, APFDataFrame)
1408
+
1409
+ # rename acName in the dateaframe to match the synonym model name
1410
+ df.at[0, "acName"] = synonym
1411
+
1412
+ # Combine data with GPF data (temporary solution)
1413
+ combined_df = Parser.combineACDATA_GPF(df, GPFparsedDataframe)
1414
+
1415
+ # Merge DataFrames
1416
+ merged_df = pd.concat([merged_df, combined_df], ignore_index=True)
1417
+
1418
+ return merged_df
1419
+
1420
+ else:
1421
+ # use xml files inside those subfolders
1422
+ merged_df = pd.DataFrame()
1423
+
1424
+ # get synonym-filename pairs
1425
+ synonym_fileName = Parser.readSynonymXML(filePath, badaVersion)
1426
+
1427
+ for synonym in synonym_fileName:
1428
+ file = synonym_fileName[synonym]
1429
+
1430
+ if file in subfolders:
1431
+ # parse the original XML of a model
1432
+ df = Parser.parseXML(filePath, badaVersion, file)
1433
+
1434
+ # rename acName in the dateaframe to match the synonym model name
1435
+ df.at[0, "acName"] = synonym
1436
+
1437
+ # Combine data with GPF data (temporary solution)
1438
+ combined_df = Parser.combineACDATA_GPF(df, GPFparsedDataframe)
1439
+
1440
+ # Merge DataFrames
1441
+ merged_df = pd.concat([merged_df, combined_df], ignore_index=True)
1442
+
1443
+ return merged_df
1444
+
1445
+ @staticmethod
1446
+ def getBADAParameters(df, acName, parameters):
1447
+ """Retrieves specified parameters for a given aircraft name from the DataFrame.
1448
+
1449
+ :param df: The DataFrame containing aircraft data.
1450
+ :param acName: The name of the aircraft to search for
1451
+ :param parameters: A list of column names to retrieve or a single column name
1452
+ :type df: pandas dataframe.
1453
+ :type acName: list[string].
1454
+ :type parameters: list[string].
1455
+ :return: parameter values: dataframe
1456
+ :rtype: dataframe.
1457
+ """
1458
+
1459
+ # Ensure parameters is a list
1460
+ if isinstance(parameters, str):
1461
+ parameters = [parameters]
1462
+
1463
+ # Ensure acName is a list
1464
+ if isinstance(acName, str):
1465
+ acName = [acName]
1466
+
1467
+ # Ensure all requested parameters exist in the DataFrame
1468
+ missing_cols = [col for col in parameters if col not in df.columns]
1469
+ if missing_cols:
1470
+ raise ValueError(
1471
+ f"The following parameters are not in the DataFrame columns: {missing_cols}"
1472
+ )
1473
+
1474
+ # Filter rows where 'acName' matches any of the specified aircraft names
1475
+ filtered_df = df[df["acName"].isin(acName)]
1476
+
1477
+ # Check if any rows were found
1478
+ if filtered_df.empty:
1479
+ raise ValueError(f"No entries found for aircraft(s): {acName}.")
1480
+ else:
1481
+ # Select the required columns
1482
+ result_df = filtered_df[["acName"] + parameters].reset_index(drop=True)
1483
+ return result_df
1484
+
1485
+ @staticmethod
1486
+ def safe_get(df, column_name, default_value=None):
1487
+ """Accessing a potentially dropped column from a dataframe"""
1488
+
1489
+ if column_name in df.columns:
1490
+ return df[column_name].iloc[0]
1491
+ else:
1492
+ return default_value
1493
+
1494
+
1495
+ class BADA3(Airplane):
1496
+ """This class implements the part of BADA3 performance model that will be used in other classes following the BADA3 manual.
1497
+
1498
+ :param AC: parsed aircraft.
1499
+ :type AC: bada3.Parse.
1500
+ """
1501
+
1502
+ def __init__(self, AC):
1503
+ super().__init__()
1504
+ self.AC = AC
1505
+
1506
+ def CL(self, sigma, mass, tas, nz=1.0):
1507
+ """This function computes the lift coefficient
1508
+
1509
+ :param tas: true airspeed [m s^-1].
1510
+ :param sigma: normalised air density [-].
1511
+ :param mass: aircraft mass [kg].
1512
+ :param nz: load factor [-].
1513
+ :type tas: float.
1514
+ :type sigma: float.
1515
+ :type mass: float.
1516
+ :type nz: float.
1517
+ :returns: Lift coefficient [-].
1518
+ :rtype: float.
1519
+ """
1520
+
1521
+ return 2 * mass * const.g * nz / (sigma * const.rho_0 * tas * tas * self.AC.S)
1522
+
1523
+ def CD(
1524
+ self, CL, config, expedite=False, speedBrakes={"deployed": False, "value": 0.03}
1525
+ ):
1526
+ """This function computes the drag coefficient
1527
+
1528
+ :param CL: Lift coefficient [-].
1529
+ :param config: aircraft aerodynamic configuration [CR/IC/TO/AP/LD][-].
1530
+ :param expedite: expedite descend factor [-].
1531
+ :param speedBrakes: speed brakes used or not [-].
1532
+ :type CL: float.
1533
+ :type config: str.
1534
+ :type expedite: boolean.
1535
+ :type speedBrakes: boolean.
1536
+ :returns: Drag coefficient [-].
1537
+ :rtype: float
1538
+ :raises: ValueError
1539
+ """
1540
+
1541
+ if self.AC.xmlFiles:
1542
+ HLid_CR = self.AC.aeroConfig["CR"]["HLid"]
1543
+ LG_CR = self.AC.aeroConfig["CR"]["LG"]
1544
+ HLid_AP = self.AC.aeroConfig["AP"]["HLid"]
1545
+ LG_AP = self.AC.aeroConfig["AP"]["LG"]
1546
+ HLid_LD = self.AC.aeroConfig["LD"]["HLid"]
1547
+ LG_LD = self.AC.aeroConfig["LD"]["LG"]
1548
+
1549
+ if (
1550
+ self.AC.CD0[HLid_AP][LG_AP] == 0.0
1551
+ and self.AC.CD0[HLid_LD][LG_LD] == 0.0
1552
+ and self.AC.CD2[HLid_AP][LG_AP] == 0.0
1553
+ and self.AC.CD2[HLid_LD][LG_LD] == 0.0
1554
+ and self.AC.DeltaCD == 0.0
1555
+ ):
1556
+
1557
+ CD = self.AC.CD0[HLid_CR][LG_CR] + self.AC.CD2[HLid_CR][LG_CR] * (
1558
+ CL * CL
1559
+ )
1560
+ else:
1561
+ if config == "CR" or config == "IC" or config == "TO":
1562
+ CD = (
1563
+ self.AC.CD0[HLid_CR][LG_CR]
1564
+ + self.AC.CD2[HLid_CR][LG_CR] * CL**2
1565
+ )
1566
+ elif config == "AP":
1567
+ CD = (
1568
+ self.AC.CD0[HLid_AP][LG_AP]
1569
+ + self.AC.CD2[HLid_AP][LG_AP] * CL**2
1570
+ )
1571
+ elif config == "LD":
1572
+ CD = (
1573
+ self.AC.CD0[HLid_LD][LG_LD]
1574
+ + self.AC.DeltaCD
1575
+ + self.AC.CD2[HLid_LD][LG_LD] * CL**2
1576
+ )
1577
+ else:
1578
+ return float("Nan")
1579
+ else:
1580
+ if (
1581
+ self.AC.CD0["AP"] == 0.0
1582
+ and self.AC.CD0["LD"] == 0.0
1583
+ and self.AC.CD2["AP"] == 0.0
1584
+ and self.AC.CD2["LD"] == 0.0
1585
+ and self.AC.CD0["GEAR_DOWN"] == 0.0
1586
+ ):
1587
+ CD = self.AC.CD0["CR"] + self.AC.CD2["CR"] * (CL * CL)
1588
+
1589
+ else:
1590
+ if config == "CR" or config == "IC" or config == "TO":
1591
+ CD = self.AC.CD0["CR"] + self.AC.CD2["CR"] * CL**2
1592
+ elif config == "AP":
1593
+ CD = self.AC.CD0[config] + self.AC.CD2[config] * CL**2
1594
+ elif config == "LD":
1595
+ CD = (
1596
+ self.AC.CD0[config]
1597
+ + self.AC.CD0["GEAR_DOWN"]
1598
+ + self.AC.CD2[config] * CL**2
1599
+ )
1600
+ else:
1601
+ return float("Nan")
1602
+
1603
+ # implementation of a simple speed brakes model
1604
+ if speedBrakes["deployed"]:
1605
+ if speedBrakes["value"] is not None:
1606
+ CD = CD + speedBrakes["value"]
1607
+ else:
1608
+ CD = CD + 0.03
1609
+ return CD
1610
+
1611
+ # expedite descent
1612
+ C_des_exp = 1.0
1613
+ if expedite:
1614
+ C_des_exp = Parser.getGPFValue(self.AC.GPFdata, "C_des_exp", phase="des")
1615
+ CD = CD * C_des_exp
1616
+
1617
+ return CD
1618
+
1619
+ def D(self, sigma, tas, CD):
1620
+ """This function computes the aerodynamic drag
1621
+
1622
+ :param tas: true airspeed [m s^-1].
1623
+ :param sigma: normalised air density [-].
1624
+ :param CD: Drag coefficient [-].
1625
+ :type tas: float.
1626
+ :type sigma: float.
1627
+ :type CD: float.
1628
+ :returns: Aerodynamic drag [N].
1629
+ :rtype: float.
1630
+ """
1631
+
1632
+ return 0.5 * sigma * const.rho_0 * tas * tas * self.AC.S * CD
1633
+
1634
+ def L(self, sigma, tas, CL):
1635
+ """This function computes the aerodynamic lift
1636
+
1637
+ :param tas: true airspeed [m s^-1].
1638
+ :param sigma: normalised air density [-].
1639
+ :param CL: Lift coefficient [-].
1640
+ :type tas: float.
1641
+ :type sigma: float.
1642
+ :type CL: float.
1643
+ :returns: Aerodynamic lift [N].
1644
+ :rtype: float.
1645
+ """
1646
+
1647
+ return 0.5 * sigma * const.rho_0 * tas * tas * self.AC.S * CL
1648
+
1649
+ def Thrust(self, h, DeltaTemp, rating, v, config, **kwargs):
1650
+ """This function computes the aircraft thrust
1651
+
1652
+ :param rating: engine rating {MCMB,MCRZ,MTKF,LIDL}.
1653
+ :param h: altitude [m].
1654
+ :param DeltaTemp: deviation with respect to ISA [K]
1655
+ :param v: true airspeed (TAS) [m s^-1].
1656
+ :param config: aircraft aerodynamic configuration [CR/IC/TO/AP/LD][-].
1657
+ :type rating: string.
1658
+ :type DeltaTemp: float
1659
+ :type h: float.
1660
+ :type v: float.
1661
+ :type config: string.
1662
+ :returns: Thrust [N].
1663
+ :rtype: float
1664
+ """
1665
+
1666
+ if rating == "MCMB":
1667
+ # MCMB
1668
+ T = self.TMax(h=h, DeltaTemp=DeltaTemp, rating=rating, v=v)
1669
+
1670
+ elif rating == "MTKF":
1671
+ # MTKF
1672
+ T = self.TMax(h=h, DeltaTemp=DeltaTemp, rating=rating, v=v)
1673
+
1674
+ elif rating == "MCRZ":
1675
+ # MCRZ
1676
+ T = self.TMax(h=h, DeltaTemp=DeltaTemp, rating=rating, v=v)
1677
+
1678
+ elif rating == "LIDL":
1679
+ # LIDL
1680
+ T = self.TDes(h=h, DeltaTemp=DeltaTemp, v=v, config=config)
1681
+
1682
+ elif rating == "ADAPTED":
1683
+ # ADAPTED
1684
+ ROCD = checkArgument("ROCD", **kwargs)
1685
+ mass = checkArgument("mass", **kwargs)
1686
+ v = checkArgument("v", **kwargs)
1687
+ acc = checkArgument("acc", **kwargs)
1688
+ Drag = checkArgument("Drag", **kwargs)
1689
+ T = self.TAdapted(
1690
+ h=h, DeltaTemp=DeltaTemp, ROCD=ROCD, mass=mass, v=v, acc=acc, Drag=Drag
1691
+ )
1692
+
1693
+ else:
1694
+ T = float("Nan")
1695
+
1696
+ return T
1697
+
1698
+ def TAdapted(self, h, DeltaTemp, ROCD, mass, v, acc, Drag):
1699
+ """This function computes the adapted thrust
1700
+
1701
+ :param h: altitude [m].
1702
+ :param DeltaTemp: deviation with respect to ISA [K]
1703
+ :param ROCD: rate of climb/descend [m s^-1].
1704
+ :param mass: actual aircraft weight [kg]
1705
+ :param v: true airspeed (TAS) [m s^-1].
1706
+ :param acc: acceleration [m s^-2].
1707
+ :param Drag: aerodynamic drag [N].
1708
+ :type h: float.
1709
+ :type DeltaTemp: float.
1710
+ :type ROCD: float.
1711
+ :type mass: float.
1712
+ :type acc: float.
1713
+ :type Drag: float.
1714
+ :returns: maximum thrust [N].
1715
+ :rtype: float
1716
+ """
1717
+
1718
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
1719
+ tau_const = (theta * const.temp_0) / (theta * const.temp_0 - DeltaTemp)
1720
+ Tadapted = ROCD * mass * const.g * tau_const / v + mass * acc + Drag
1721
+
1722
+ return Tadapted
1723
+
1724
+ def TMax(self, h, DeltaTemp, rating, v):
1725
+ """This function computes the maximum thrust
1726
+
1727
+ :param rating: engine rating {MCMB,MCRZ,MTKF}.
1728
+ :param h: altitude [m].
1729
+ :param v: true airspeed (TAS) [m s^-1].
1730
+ :param DeltaTemp: deviation with respect to ISA [K]
1731
+ :type rating: string.
1732
+ :type h: float.
1733
+ :type v: float.
1734
+ :type DeltaTemp: float
1735
+ :returns: maximum thrust [N].
1736
+ :rtype: float
1737
+ """
1738
+
1739
+ acModel = self.AC.engineType
1740
+
1741
+ if acModel == "JET":
1742
+ TMaxISA = self.AC.Ct[0] * (
1743
+ 1
1744
+ - (conv.m2ft(h)) / self.AC.Ct[1]
1745
+ + self.AC.Ct[2] * (conv.m2ft(h)) * (conv.m2ft(h))
1746
+ )
1747
+
1748
+ elif acModel == "TURBOPROP":
1749
+ TMaxISA = (self.AC.Ct[0] / conv.ms2kt(v)) * (
1750
+ 1 - conv.m2ft(h) / self.AC.Ct[1]
1751
+ ) + self.AC.Ct[2]
1752
+
1753
+ elif acModel == "PISTON" or acModel == "ELECTRIC":
1754
+ TMaxISA = self.AC.Ct[0] * (1 - conv.m2ft(h) / self.AC.Ct[1]) + (
1755
+ self.AC.Ct[2] / conv.ms2kt(v)
1756
+ )
1757
+
1758
+ else:
1759
+ return float("Nan")
1760
+
1761
+ DeltaTempEff = DeltaTemp - self.AC.Ct[3]
1762
+
1763
+ if self.AC.Ct[4] < 0:
1764
+ Ctc5 = 0
1765
+ else:
1766
+ Ctc5 = self.AC.Ct[4]
1767
+
1768
+ DeltaTemp_ = Ctc5 * DeltaTempEff
1769
+
1770
+ if DeltaTemp_ <= 0:
1771
+ DeltaTemp_ = 0
1772
+ elif DeltaTemp_ > 0.4:
1773
+ DeltaTemp_ = 0.4
1774
+
1775
+ TMax = TMaxISA * (1 - DeltaTemp_)
1776
+
1777
+ if rating == "MCMB" or rating == "MTKF":
1778
+ return TMax
1779
+
1780
+ elif rating == "MCRZ":
1781
+ return TMax * Parser.getGPFValue(self.AC.GPFdata, "C_th_cr", phase="cr")
1782
+
1783
+ def TDes(self, h, DeltaTemp, v, config):
1784
+ """This function computes the descent thrust
1785
+
1786
+ :param h: altitude [m].
1787
+ :param DeltaTemp: deviation with respect to ISA [K]
1788
+ :param config: aircraft aerodynamic configuration [CR/IC/TO/AP/LD][-].
1789
+ :param v: true airspeed (TAS) [m s^-1].
1790
+ :type h: float.
1791
+ :type DeltaTemp: float.
1792
+ :type config: string.
1793
+ :type v: float.
1794
+ :returns: minimum thrust [N].
1795
+ :rtype: float
1796
+ """
1797
+
1798
+ H_max_app = Parser.getGPFValue(self.AC.GPFdata, "H_max_app", phase="app")
1799
+
1800
+ if (
1801
+ self.AC.engineType == "PISTON"
1802
+ or self.AC.engineType == "ELECTRIC"
1803
+ or self.AC.engineType == "TURBOPROP"
1804
+ ):
1805
+ TMaxClimb = self.TMax(rating="MCMB", h=h, DeltaTemp=DeltaTemp, v=v)
1806
+ elif self.AC.engineType == "JET":
1807
+ TMaxClimb = self.TMax(rating="MCMB", h=h, DeltaTemp=DeltaTemp, v=v)
1808
+
1809
+ # non-clean data available -> Hp,des cannot be below Hmax,AP
1810
+ HpDes_ = self.AC.HpDes
1811
+
1812
+ if self.AC.xmlFiles:
1813
+ [HLid_CR, LG_CR] = self.flightEnvelope.getAeroConfig(config="CR")
1814
+ [HLid_AP, LG_AP] = self.flightEnvelope.getAeroConfig(config="AP")
1815
+ [HLid_LD, LG_LD] = self.flightEnvelope.getAeroConfig(config="LD")
1816
+
1817
+ if (
1818
+ self.AC.CD0[HLid_AP][LG_AP] != 0.0
1819
+ and self.AC.CD0[HLid_LD][LG_LD] != 0.0
1820
+ and self.AC.CD2[HLid_AP][LG_AP] != 0.0
1821
+ and self.AC.CD2[HLid_LD][LG_LD] != 0.0
1822
+ and self.AC.DeltaCD != 0.0
1823
+ ):
1824
+
1825
+ if HpDes_ < H_max_app:
1826
+ HpDes_ = H_max_app
1827
+
1828
+ else:
1829
+ if (
1830
+ self.AC.CD0["AP"] != 0.0
1831
+ and self.AC.CD0["LD"] != 0.0
1832
+ and self.AC.CD2["AP"] != 0.0
1833
+ and self.AC.CD2["LD"] != 0.0
1834
+ and self.AC.CD0["GEAR_DOWN"] != 0.0
1835
+ ):
1836
+ if HpDes_ < H_max_app:
1837
+ HpDes_ = H_max_app
1838
+
1839
+ if h > conv.ft2m(HpDes_):
1840
+ Tdes = self.AC.CTdeshigh * TMaxClimb
1841
+ elif h <= conv.ft2m(HpDes_):
1842
+ if config == "CR":
1843
+ Tdes = self.AC.CTdeslow * TMaxClimb
1844
+ elif config == "AP":
1845
+ Tdes = self.AC.CTdesapp * TMaxClimb
1846
+ elif config == "LD":
1847
+ Tdes = self.AC.CTdesld * TMaxClimb
1848
+ else:
1849
+ Tdes = float("Nan")
1850
+
1851
+ return Tdes
1852
+
1853
+ def ffnom(self, v, T):
1854
+ """This function computes the nominal fuel flow
1855
+
1856
+ :param v: true airspeed (TAS) [m s^-1].
1857
+ :param T: Thrust [N].
1858
+ :type v: float.
1859
+ :type T: float.
1860
+ :returns: nominal fuel flow [kg s^-1].
1861
+ :rtype: float
1862
+ """
1863
+
1864
+ if self.AC.engineType == "JET":
1865
+ eta = self.AC.Cf[0] * (1 + conv.ms2kt(v) / self.AC.Cf[1]) / (1000 * 60)
1866
+ ffnom = eta * T
1867
+
1868
+ elif self.AC.engineType == "TURBOPROP":
1869
+ eta = (
1870
+ self.AC.Cf[0]
1871
+ * (1 - conv.ms2kt(v) / self.AC.Cf[1])
1872
+ * (conv.ms2kt(v) / 1000)
1873
+ / (1000 * 60)
1874
+ )
1875
+ ffnom = eta * T
1876
+
1877
+ elif self.AC.engineType == "PISTON" or self.AC.engineType == "ELECTRIC":
1878
+ ffnom = self.AC.Cf[0] / 60
1879
+
1880
+ return ffnom
1881
+
1882
+ def ffMin(self, h):
1883
+ """This function computes the minimum fuel flow
1884
+
1885
+ :param h: altitude [m].
1886
+ :type h: float.
1887
+ :returns: Minimum fuel flow [kg s^-1].
1888
+ :rtype: float
1889
+ """
1890
+
1891
+ if self.AC.engineType == "JET" or self.AC.engineType == "TURBOPROP":
1892
+ ffmin = self.AC.CfDes[0] * (1 - (conv.m2ft(h)) / self.AC.CfDes[1]) / 60
1893
+ elif self.AC.engineType == "PISTON" or self.AC.engineType == "ELECTRIC":
1894
+ ffmin = self.AC.CfDes[0] / 60 # Cf3 param
1895
+
1896
+ return ffmin
1897
+
1898
+ def ff(self, h, v, T, config=None, flightPhase=None, adapted=False):
1899
+ """This function computes the fuel flow based on the flight phase and flight situation
1900
+
1901
+ :param h: altitude [m].
1902
+ :param rating: engine rating {MCMB,MCRZ,LIDL}.
1903
+ :param v: true airspeed (TAS) [m s^-1].
1904
+ :param T: Thrust [N].
1905
+ :type h: float.
1906
+ :type rating: string.
1907
+ :type v: float.
1908
+ :type T: float.
1909
+ :returns: fuel flow [kg s^-1].
1910
+ :rtype: float
1911
+ """
1912
+
1913
+ if adapted:
1914
+ # adapted thrust
1915
+ ffnom = self.ffnom(v=v, T=T)
1916
+ ff = max(ffnom, self.ffMin(h=h))
1917
+ else:
1918
+ if flightPhase == "Climb":
1919
+ # climb thrust
1920
+ ffnom = self.ffnom(v=v, T=T)
1921
+ ff = max(ffnom, self.ffMin(h=h))
1922
+
1923
+ elif flightPhase == "Cruise":
1924
+ # cruise thrust
1925
+ ffnom = self.ffnom(v=v, T=T)
1926
+ ff = ffnom * self.AC.CfCrz
1927
+
1928
+ elif flightPhase == "Descent":
1929
+ # descent in IDLE
1930
+ if config == "CR":
1931
+ ff = self.ffMin(h=h)
1932
+ elif config == "AP" or config == "LD":
1933
+ ffnom = self.ffnom(v=v, T=T)
1934
+ ff = max(ffnom, self.ffMin(h=h))
1935
+ else:
1936
+ ff = float("Nan")
1937
+
1938
+ return ff
1939
+
1940
+ def reducedPower(self, h, mass, DeltaTemp):
1941
+ """This function computes the reduced climb power coefficient
1942
+
1943
+ :param h: altitude [m].
1944
+ :param DeltaTemp: deviation with respect to ISA [K]
1945
+ :param mass: actual aircraft weight [kg]
1946
+ :param hMax: aircraft flight envelope max Altitude [m]
1947
+ :type h: float.
1948
+ :type DeltaTemp: float.
1949
+ :type mass: float.
1950
+ :type hMax: float.
1951
+ :returns: reduced climb power coefficient [-].
1952
+ :rtype: float
1953
+ """
1954
+
1955
+ hMax = self.flightEnvelope.maxAltitude(mass=mass, DeltaTemp=DeltaTemp)
1956
+ mMax = self.AC.mass["maximum"]
1957
+ mMin = self.AC.mass["minimum"]
1958
+
1959
+ ep = 1e-6 # floating point precision
1960
+ if (h + ep) < 0.8 * hMax:
1961
+ if self.AC.engineType == "JET":
1962
+ CRed = Parser.getGPFValue(
1963
+ self.AC.GPFdata, "C_red_jet", engine=self.AC.engineType, phase="cl"
1964
+ )
1965
+ elif self.AC.engineType == "TURBOPROP":
1966
+ CRed = Parser.getGPFValue(
1967
+ self.AC.GPFdata, "C_red_turbo", engine="TURBOPROP", phase="cl"
1968
+ )
1969
+ elif self.AC.engineType == "PISTON":
1970
+ CRed = Parser.getGPFValue(
1971
+ self.AC.GPFdata, "C_red_piston", engine="PISTON", phase="cl"
1972
+ )
1973
+ elif self.AC.engineType == "ELECTRIC":
1974
+ CRed = Parser.getGPFValue(
1975
+ self.AC.GPFdata, "C_red_elec", engine="ELECTRIC", phase="cl"
1976
+ )
1977
+ else:
1978
+ CRed = 0
1979
+
1980
+ CPowRed = 1 - CRed * (mMax - mass) / (mMax - mMin)
1981
+ return CPowRed
1982
+
1983
+ def ROCD(self, T, D, v, mass, ESF, h, DeltaTemp, reducedPower=False):
1984
+ """This function computes the Rate of Climb or Descent
1985
+
1986
+ :param h: altitude [m].
1987
+ :param T: aircraft thrust [N].
1988
+ :param D: aircraft drag [N].
1989
+ :param v: aircraft true airspeed [TAS] [m s^-1].
1990
+ :param mass: actual aircraft mass [kg].
1991
+ :param ESF: energy share factor [-].
1992
+ :param DeltaTemp: deviation with respect to ISA [K]
1993
+ :param reducedPower: power reduction [-]
1994
+ :type h: float.
1995
+ :type T: float.
1996
+ :type D: float.
1997
+ :type v: float.
1998
+ :type mass: float.
1999
+ :type ESF: float.
2000
+ :type DeltaTemp: float.
2001
+ :type reducedPower: boolean.
2002
+ :returns: rate of climb/descend [m/s].
2003
+ :rtype: float
2004
+ """
2005
+
2006
+ theta = atm.theta(h=h, DeltaTemp=DeltaTemp)
2007
+ temp = theta * const.temp_0
2008
+
2009
+ CPowRed = 1.0
2010
+ if reducedPower:
2011
+ CPowRed = self.reducedPower(h=h, mass=mass, DeltaTemp=DeltaTemp)
2012
+
2013
+ ROCD = (
2014
+ ((temp - DeltaTemp) / temp) * (T - D) * v * ESF * CPowRed / (mass * const.g)
2015
+ )
2016
+
2017
+ return ROCD
2018
+
2019
+
2020
+ class FlightEnvelope(BADA3):
2021
+ """This class is a BADA3 aircraft subclass and implements the flight envelope caclulations
2022
+ following the BADA3 manual.
2023
+
2024
+ :param AC: parsed aircraft.
2025
+ :type AC: bada3.Parse.
2026
+ """
2027
+
2028
+ def __init__(self, AC):
2029
+ super().__init__(AC)
2030
+
2031
+ def maxAltitude(self, mass, DeltaTemp):
2032
+ """This function computes the maximum altitude for any given aircraft mass
2033
+
2034
+ :param DeltaTemp: deviation with respect to ISA [K]
2035
+ :param mass: actual aircraft weight [kg]
2036
+ :type DeltaTemp: float.
2037
+ :type mass: float.
2038
+ :returns: maximum altitude [m].
2039
+ :rtype: float
2040
+ """
2041
+
2042
+ Gt = self.AC.tempGrad
2043
+ Gw = self.AC.mass["mass grad"]
2044
+ Ctc4 = self.AC.Ct[3]
2045
+ mMax = self.AC.mass["maximum"]
2046
+
2047
+ if Gw < 0:
2048
+ Gw = 0
2049
+ if Gt > 0:
2050
+ Gt = 0
2051
+
2052
+ var = DeltaTemp - Ctc4
2053
+ if var < 0:
2054
+ var = 0
2055
+
2056
+ if self.AC.Hmax == 0:
2057
+ hMax = self.AC.hmo
2058
+ else:
2059
+ hMax = min(self.AC.hmo, self.AC.Hmax + Gt * var + Gw * (mMax - mass))
2060
+
2061
+ return conv.ft2m(hMax)
2062
+
2063
+ def VStall(self, mass, config):
2064
+ """This function computes the mass correction for stall speed calculation
2065
+
2066
+ :param config: aircraft configuration [CR/IC/TO/AP/LD][-]
2067
+ :param mass: aircraft operating mass [kg]
2068
+ :type config: string.
2069
+ :type mass: string
2070
+ :returns: aircraft stall speed [m s^-1]
2071
+ :rtype: float
2072
+ """
2073
+
2074
+ if self.AC.xmlFiles:
2075
+ [HLid, LG] = self.getAeroConfig(config=config)
2076
+ vStall = conv.kt2ms(self.AC.Vstall[HLid][LG]) * sqrt(
2077
+ mass / self.AC.mass["reference"]
2078
+ )
2079
+ else:
2080
+ vStall = conv.kt2ms(self.AC.Vstall[config]) * sqrt(
2081
+ mass / self.AC.mass["reference"]
2082
+ )
2083
+
2084
+ return vStall
2085
+
2086
+ def VMin(self, h, mass, config, DeltaTemp, nz=1.2, envelopeType="OPERATIONAL"):
2087
+ """This function computes the minimum speed
2088
+
2089
+ :param h: altitude [m].
2090
+ :param config: aircraft configuration [CR/IC/TO/AP/LD][-]
2091
+ :param mass: aircraft operating mass [kg]
2092
+ :param DeltaTemp: deviation with respect to ISA [K]
2093
+ :type h: float.
2094
+ :type config: string
2095
+ :type mass: float
2096
+ :type DeltaTemp: float.
2097
+ :returns: minimum speed [m s^-1].
2098
+ :rtype: float
2099
+ """
2100
+
2101
+ if envelopeType == "OPERATIONAL":
2102
+ if config == "TO":
2103
+ VminStall = Parser.getGPFValue(
2104
+ self.AC.GPFdata, "C_v_min_to", phase="to"
2105
+ ) * self.VStall(mass=mass, config=config)
2106
+ else:
2107
+ VminStall = Parser.getGPFValue(
2108
+ self.AC.GPFdata, "C_v_min"
2109
+ ) * self.VStall(mass=mass, config=config)
2110
+ elif envelopeType == "CERTIFIED":
2111
+ VminStall = self.VStall(mass=mass, config=config)
2112
+
2113
+ if self.AC.Clbo == 0.0 and self.AC.k == 0.0:
2114
+ Vmin = VminStall
2115
+ else:
2116
+ if h < conv.ft2m(15000):
2117
+ Vmin = VminStall
2118
+ elif h >= conv.ft2m(15000):
2119
+ # low speed buffeting limit applies only for JET and TURBOPROP
2120
+ if self.AC.engineType == "JET" or self.AC.engineType == "TURBOPROP":
2121
+ [theta, delta, sigma] = atm.atmosphereProperties(
2122
+ h=h, DeltaTemp=DeltaTemp
2123
+ )
2124
+ buffetLimit = self.lowSpeedBuffetLimit(
2125
+ h=h, mass=mass, DeltaTemp=DeltaTemp, nz=nz
2126
+ )
2127
+ if buffetLimit == float("Nan"):
2128
+ Vmin = VminStall
2129
+ else:
2130
+ Vmin = max(
2131
+ VminStall,
2132
+ atm.mach2Cas(
2133
+ buffetLimit, theta=theta, delta=delta, sigma=sigma
2134
+ ),
2135
+ )
2136
+ elif self.AC.engineType == "PISTON" or self.AC.engineType == "ELECTRIC":
2137
+ Vmin = VminStall
2138
+
2139
+ return Vmin
2140
+
2141
+ def Vmax_thrustLimited(self, h, mass, DeltaTemp, rating, config):
2142
+ """This function computes the maximum CAS speed within the certified flight envelope taking into account the trust limitation.
2143
+
2144
+ :param h: altitude [m].
2145
+ :param mass: aircraft operating mass [kg]
2146
+ :param DeltaTemp: deviation with respect to ISA [K]
2147
+ :param rating: aircraft engine rating [MTKF/MCMB/MCRZ][-]
2148
+ :param config: aircraft configuration [TO/IC/CR][-]
2149
+ :type h: float.
2150
+ :type mass: float
2151
+ :type DeltaTemp: float.
2152
+ :type config: string
2153
+ :type rating: string
2154
+ :returns: maximum thrust lmited speed [m s^-1].
2155
+ :rtype: float
2156
+ """
2157
+
2158
+ [theta, delta, sigma] = atm.atmosphereProperties(h=h, DeltaTemp=DeltaTemp)
2159
+
2160
+ VmaxCertified = self.VMax(h=h, DeltaTemp=DeltaTemp)
2161
+ VminCertified = self.VMin(
2162
+ h=h,
2163
+ mass=mass,
2164
+ config=config,
2165
+ DeltaTemp=DeltaTemp,
2166
+ nz=1.0,
2167
+ envelopeType="CERTIFIED",
2168
+ )
2169
+
2170
+ maxCASList = []
2171
+ DragValue = None
2172
+ ThrustValue = None
2173
+ CDvalue = None
2174
+ CASValue = None
2175
+ MValue = None
2176
+ for CAS in np.linspace(VminCertified, VmaxCertified, num=200, endpoint=True):
2177
+ TAS = atm.cas2Tas(cas=CAS, delta=delta, sigma=sigma)
2178
+ M = atm.cas2Mach(cas=CAS, theta=theta, delta=delta, sigma=sigma)
2179
+ maxThrust = self.Thrust(
2180
+ h=h, DeltaTemp=DeltaTemp, rating=rating, v=TAS, config=config
2181
+ )
2182
+ CL = self.CL(sigma=sigma, mass=mass, tas=TAS, nz=1.0)
2183
+ CD = self.CD(CL=CL, config=config)
2184
+ Drag = self.D(sigma=sigma, tas=TAS, CD=CD)
2185
+
2186
+ if maxThrust >= Drag:
2187
+ maxCASList.append(CAS)
2188
+ DragValue = Drag
2189
+ ThrustValue = maxThrust
2190
+ CDvalue = CD
2191
+ CASValue = conv.ms2kt(CAS)
2192
+ MValue = M
2193
+
2194
+ if not maxCASList:
2195
+ return None
2196
+ else:
2197
+ return max(maxCASList)
2198
+
2199
+ def Vx(self, h, mass, DeltaTemp, rating, config):
2200
+ """This function computes the best angle of climb CAS speed.
2201
+
2202
+ :param h: altitude [m].
2203
+ :param mass: aircraft operating mass [kg]
2204
+ :param DeltaTemp: deviation with respect to ISA [K]
2205
+ :param rating: aircraft engine rating [MTKF/MCMB/MCRZ][-]
2206
+ :param config: aircraft configuration [TO/IC/CR][-]
2207
+ :type h: float.
2208
+ :type mass: float
2209
+ :type DeltaTemp: float.
2210
+ :type config: string
2211
+ :type rating: string
2212
+ :returns: Vx - best angle of climb speed [m s^-1].
2213
+ :rtype: float
2214
+ """
2215
+
2216
+ [theta, delta, sigma] = atm.atmosphereProperties(h=h, DeltaTemp=DeltaTemp)
2217
+
2218
+ VmaxCertified = self.VMax(h=h, DeltaTemp=DeltaTemp)
2219
+ VminCertified = self.VMin(
2220
+ h=h,
2221
+ mass=mass,
2222
+ config=config,
2223
+ DeltaTemp=DeltaTemp,
2224
+ nz=1.0,
2225
+ envelopeType="CERTIFIED",
2226
+ )
2227
+
2228
+ excessThrustList = []
2229
+ VxList = []
2230
+
2231
+ for CAS in np.linspace(VminCertified, VmaxCertified, num=200, endpoint=True):
2232
+ TAS = atm.cas2Tas(cas=CAS, delta=delta, sigma=sigma)
2233
+ maxThrust = self.Thrust(
2234
+ h=h, DeltaTemp=DeltaTemp, rating=rating, v=TAS, config=config
2235
+ )
2236
+ CL = self.CL(sigma=sigma, mass=mass, tas=TAS, nz=1.0)
2237
+ CD = self.CD(CL=CL, config=config)
2238
+ Drag = self.D(sigma=sigma, tas=TAS, CD=CD)
2239
+
2240
+ excessThrustList.append(maxThrust - Drag)
2241
+ VxList.append(CAS)
2242
+
2243
+ idx = excessThrustList.index(max(excessThrustList))
2244
+
2245
+ return VxList[idx]
2246
+
2247
+ def VMax(self, h, DeltaTemp):
2248
+ """This function computes the maximum speed
2249
+
2250
+ :param h: altitude [m].
2251
+ :param DeltaTemp: deviation with respect to ISA [K]
2252
+ :type h: float.
2253
+ :type DeltaTemp: float.
2254
+ :returns: maximum speed [m s^-1].
2255
+ :rtype: float
2256
+ """
2257
+
2258
+ crossoverAlt = atm.crossOver(cas=conv.kt2ms(self.AC.VMO), Mach=self.AC.MMO)
2259
+
2260
+ if h >= crossoverAlt:
2261
+ [theta, delta, sigma] = atm.atmosphereProperties(h=h, DeltaTemp=DeltaTemp)
2262
+ VMax = atm.mach2Cas(Mach=self.AC.MMO, theta=theta, delta=delta, sigma=sigma)
2263
+ else:
2264
+ VMax = conv.kt2ms(self.AC.VMO)
2265
+
2266
+ return VMax
2267
+
2268
+ def lowSpeedBuffetLimit(self, h, mass, DeltaTemp, nz=1.2):
2269
+ """This function computes the low speed Buffeting limit using numerical methods by numpy
2270
+
2271
+ :param h: altitude [m].
2272
+ :param mass: aircraft mass [kg]
2273
+ :param DeltaTemp: deviation with respect to ISA [K]
2274
+ :type h: float.
2275
+ :type mass: float.
2276
+ :type DeltaTemp: float.
2277
+ :returns: low speed buffet limit as M [-].
2278
+ :rtype: float
2279
+ """
2280
+
2281
+ p = atm.delta(h, DeltaTemp) * const.p_0
2282
+
2283
+ a1 = self.AC.k
2284
+ a2 = -(self.AC.Clbo)
2285
+ a3 = (mass * const.g) / (self.AC.S * p * (0.7 / nz))
2286
+
2287
+ coef = [a1, a2, 0, a3]
2288
+ roots = np.roots(coef)
2289
+
2290
+ Mb = list()
2291
+ for root in roots:
2292
+ if root > 0 and not isinstance(root, complex):
2293
+ Mb.append(root)
2294
+ if not Mb:
2295
+ return float("Nan")
2296
+
2297
+ return min(Mb)
2298
+
2299
+ def getConfig(self, phase, h, mass, v, DeltaTemp, hRWY=0.0, nz=1.2):
2300
+ """This function returns the aircraft aerodynamic configuration
2301
+ based on the aircraft altitude and speed and phase of flight
2302
+
2303
+ :param hRWY: runway elevation AMSL [m].
2304
+ :param phase: aircraft phase of flight [cl/cr/des][-].
2305
+ :param h: altitude [m].
2306
+ :param v: calibrated airspeed (CAS) [m s^-1].
2307
+ :param mass: aircraft mass [kg]
2308
+ :param DeltaTemp: deviation with respect to ISA [K]
2309
+ :type hRWY: float.
2310
+ :type phase: string.
2311
+ :type h: float.
2312
+ :type v: float.
2313
+ :type mass: float.
2314
+ :type DeltaTemp: float.
2315
+ :returns: aircraft aerodynamic configuration [TO/IC/CR/AP/LD][-].
2316
+ :rtype: string
2317
+ """
2318
+
2319
+ config = None
2320
+
2321
+ # aircraft AGL altitude assuming being close to the RWY [m]
2322
+ h_AGL = h - hRWY
2323
+
2324
+ HmaxTO_AGL = (
2325
+ conv.ft2m(Parser.getGPFValue(self.AC.GPFdata, "H_max_to", phase="to"))
2326
+ - hRWY
2327
+ )
2328
+ HmaxIC_AGL = (
2329
+ conv.ft2m(Parser.getGPFValue(self.AC.GPFdata, "H_max_ic", phase="ic"))
2330
+ - hRWY
2331
+ )
2332
+ HmaxAPP_AGL = (
2333
+ conv.ft2m(Parser.getGPFValue(self.AC.GPFdata, "H_max_app", phase="app"))
2334
+ - hRWY
2335
+ )
2336
+ HmaxLD_AGL = (
2337
+ conv.ft2m(Parser.getGPFValue(self.AC.GPFdata, "H_max_ld", phase="lnd"))
2338
+ - hRWY
2339
+ )
2340
+
2341
+ if phase == "Climb" and h_AGL <= HmaxTO_AGL:
2342
+ config = "TO"
2343
+ elif phase == "Climb" and (h_AGL > HmaxTO_AGL and h_AGL <= HmaxIC_AGL):
2344
+ config = "IC"
2345
+ else:
2346
+ vMinCR = self.VMin(h=h, mass=mass, config="CR", DeltaTemp=DeltaTemp, nz=nz)
2347
+ vMinAPP = self.VMin(h=h, mass=mass, config="AP", DeltaTemp=DeltaTemp, nz=nz)
2348
+ ep = 1e-6
2349
+ if (
2350
+ phase == "Descent"
2351
+ and (h_AGL + ep) < HmaxLD_AGL
2352
+ and (v + ep) < (vMinAPP + conv.kt2ms(10))
2353
+ ):
2354
+ config = "LD"
2355
+ elif (
2356
+ phase == "Descent"
2357
+ and h_AGL >= HmaxLD_AGL
2358
+ and (h_AGL + ep) < HmaxAPP_AGL
2359
+ and (v - ep) < (vMinCR + conv.kt2ms(10))
2360
+ ) or (
2361
+ phase == "Descent"
2362
+ and (h_AGL + ep) < HmaxLD_AGL
2363
+ and (
2364
+ (v - ep) < (vMinCR + conv.kt2ms(10))
2365
+ and v >= (vMinAPP + conv.kt2ms(10))
2366
+ )
2367
+ ):
2368
+ config = "AP"
2369
+ elif (
2370
+ (phase == "Climb" and h_AGL > HmaxIC_AGL)
2371
+ or phase == "Cruise"
2372
+ or (phase == "Descent" and h_AGL >= HmaxAPP_AGL)
2373
+ or (
2374
+ phase == "Descent"
2375
+ and (h_AGL + ep) < HmaxAPP_AGL
2376
+ and v >= (vMinCR + conv.kt2ms(10))
2377
+ )
2378
+ ):
2379
+ config = "CR"
2380
+
2381
+ if config is None:
2382
+ raise TypeError("Unable to determine aircraft configuration")
2383
+
2384
+ return config
2385
+
2386
+ def getAeroConfig(self, config):
2387
+ """This function returns the aircraft aerodynamic configuration
2388
+ based on the aerodynamic configuration ID
2389
+
2390
+ :param config: aircraft configuration [CR/IC/TO/AP/LD][-]
2391
+ :type config: string
2392
+ :returns: aircraft aerodynamic configuration combination of HLID and LG [-].
2393
+ :rtype: [float, string]
2394
+ """
2395
+
2396
+ HLid = self.AC.aeroConfig[config]["HLid"]
2397
+ LG = self.AC.aeroConfig[config]["LG"]
2398
+
2399
+ return [HLid, LG]
2400
+
2401
+ def getHoldSpeed(self, h, theta, delta, sigma, DeltaTemp):
2402
+ """This function returns the aircraft holding speed based on the altitude
2403
+
2404
+ :param h: altitude [m].
2405
+ :param DeltaTemp: deviation with respect to ISA [K]
2406
+ :type h: float.
2407
+ :type DeltaTemp: float.
2408
+ :returns: aircraft Holding calibrated airspeed (CAS) [m s^-1].
2409
+ :rtype: float
2410
+ """
2411
+
2412
+ if h <= conv.ft2m(14000):
2413
+ vHold = Parser.getGPFValue(self.AC.GPFdata, "V_hold_1", phase="hold")
2414
+ elif h > conv.ft2m(14000) and h <= conv.ft2m(20000):
2415
+ vHold = Parser.getGPFValue(self.AC.GPFdata, "V_hold_2", phase="hold")
2416
+ elif h > conv.ft2m(20000) and h <= conv.ft2m(34000):
2417
+ vHold = Parser.getGPFValue(self.AC.GPFdata, "V_hold_3", phase="hold")
2418
+ elif h > conv.ft2m(34000):
2419
+ MHold = Parser.getGPFValue(self.AC.GPFdata, "V_hold_4", phase="hold")
2420
+ vHold = atm.mach2Cas(Mach=M, theta=theta, delta=delta, sigma=sigma)
2421
+
2422
+ return conv.kt2ms(vHold)
2423
+
2424
+ def getGroundMovementSpeed(self, pos):
2425
+ """This function returns the aircraft ground movement speed based on postion on the ground
2426
+
2427
+ :param pos: aircraft position on airport ground [backtrack/taxi/apron/gate][-].
2428
+ :type pos: string.
2429
+ :returns: aircraft ground movement calibrated airspeed (CAS) [m s^-1].
2430
+ :rtype: float
2431
+ """
2432
+
2433
+ if pos == "backtrack":
2434
+ vGround = Parser.getGPFValue(self.AC.GPFdata, "V_backtrack", phase="gnd")
2435
+ elif pos == "taxi":
2436
+ vGround = Parser.getGPFValue(self.AC.GPFdata, "V_taxi", phase="gnd")
2437
+ elif pos == "apron":
2438
+ vGround = Parser.getGPFValue(self.AC.GPFdata, "V_apron", phase="gnd")
2439
+ elif pos == "gate":
2440
+ vGround = Parser.getGPFValue(self.AC.GPFdata, "V_gate", phase="gnd")
2441
+
2442
+ return conv.kt2ms(vGround)
2443
+
2444
+ def getBankAngle(self, phase, flightUnit, value):
2445
+ """This function returns the aircraft bank angle based on phase of flight
2446
+
2447
+ :param phase: aircraft phase of flight [to/ic/cl/cr/...][-].
2448
+ :param flightUnit: flight unit [civ/mil][-].
2449
+ :param value: nominal or maximum value [nom/max][-].
2450
+ :type phase: string.
2451
+ :type flightUnit: string.
2452
+ :type value: string.
2453
+ :returns: bank angle [deg]
2454
+ :rtype: float
2455
+ """
2456
+
2457
+ nomBankAngle = Parser.getGPFValue(
2458
+ self.AC.GPFdata, "ang_bank_nom", flightUnit=flightUnit, phase=phase
2459
+ )
2460
+ maxBankAngle = Parser.getGPFValue(
2461
+ self.AC.GPFdata, "ang_bank_max", flightUnit=flightUnit, phase=phase
2462
+ )
2463
+
2464
+ if value == "nom":
2465
+ return nomBankAngle
2466
+ elif value == "max":
2467
+ return maxBankAngle
2468
+
2469
+ def isAccOK(self, v1, v2, type="long", flightUnit="civ", deltaTime=1.0):
2470
+ """This function checks the limits for longitudinal and normal acceleration
2471
+
2472
+ :param type: logitudinal or normal acceleration [long/norm][-].
2473
+ :param flightUnit: flight unit [civ/mil][-].
2474
+ :param v1: (long) true airspeed (TAS) at step k-1 [m s^-1].
2475
+ :param v1: (norm) vertical airspeed (ROCD) at step k-1 [m s^-1].
2476
+ :param v2: (long) true airspeed (TAS) at step k [m s^-1].
2477
+ :param v2: (norm) vertical airspeed (ROCD) at step k [m s^-1].
2478
+ :param deltaTime: time interval between k and k-1 [s].
2479
+ :type type: string.
2480
+ :type flightUnit: string.
2481
+ :type v1: float.
2482
+ :type v2: float.
2483
+ :type deltaTime: float.
2484
+ :returns: acceleration OK [True] or NOK [False] [-]
2485
+ :rtype: boolean
2486
+ """
2487
+
2488
+ OK = False
2489
+
2490
+ if flightUnit == "civ":
2491
+ if type == "long":
2492
+ if (
2493
+ abs(v2 - v1)
2494
+ <= conv.ft2m(Parser.getGPFValue(self.AC.GPFdata, "acc_long_max"))
2495
+ * deltaTime
2496
+ ):
2497
+ OK = True
2498
+
2499
+ elif type == "norm":
2500
+ if (
2501
+ abs(v2 - v1)
2502
+ <= conv.ft2m(Parser.getGPFValue(self.AC.GPFdata, "acc_norm_max"))
2503
+ * deltaTime
2504
+ ):
2505
+ OK = True
2506
+
2507
+ # currently undefined for BADA3
2508
+ elif flightUnit == "mil":
2509
+ OK = True
2510
+
2511
+ return OK
2512
+
2513
+ def getSpeedSchedule(self, phase):
2514
+ """This function returns the speed schedule
2515
+ based on the phase of flight {Climb, Cruise, Descent}
2516
+
2517
+ :param phase: aircraft phase of flight {Climb, Cruise, Descent}
2518
+ :type phase: string
2519
+ :returns: speed schedule combination of CAS1, CAS2 and M [m s^-1, m s^-1, -].
2520
+ :rtype: [float, float, float]
2521
+ """
2522
+
2523
+ if phase == "Climb":
2524
+ phase = "cl"
2525
+ if phase == "Cruise":
2526
+ phase = "cr"
2527
+ if phase == "Descent":
2528
+ phase = "des"
2529
+
2530
+ CAS1 = self.AC.V1[phase]
2531
+ CAS2 = self.AC.V2[phase]
2532
+ M = self.AC.M[phase]
2533
+
2534
+ return [CAS1, CAS2, M]
2535
+
2536
+ def checkConfigurationContinuity(self, phase, previousConfig, currentConfig):
2537
+ """This function ensures the continuity of the configuration change,
2538
+ so, the aerodynamic configuration does not change in the wrong direction based on the phase of the flight
2539
+
2540
+ :param phase: aircraft phase of flight {Climb, Cruise, Descent}
2541
+ :param previousConfig: aircraft previous aerodynamic configuration
2542
+ :param currentConfig: aircraft current aerodynamic configuration
2543
+ :type phase: string
2544
+ :type previousConfig: string
2545
+ :type currentConfig: string
2546
+ :returns: speed new current configuration
2547
+ :rtype: string
2548
+ """
2549
+
2550
+ newConfig = ""
2551
+
2552
+ # previous configuration is NOT empty/unknown
2553
+ if previousConfig is not None:
2554
+ if phase == "Descent":
2555
+ if currentConfig == "CR" and (
2556
+ previousConfig == "AP" or previousConfig == "LD"
2557
+ ):
2558
+ newConfig = previousConfig
2559
+ elif currentConfig == "AP" and previousConfig == "LD":
2560
+ newConfig = previousConfig
2561
+ else:
2562
+ newConfig = currentConfig
2563
+
2564
+ elif phase == "Climb":
2565
+ if currentConfig == "TO" and (
2566
+ previousConfig == "IC" or previousConfig == "CR"
2567
+ ):
2568
+ newConfig = previousConfig
2569
+ elif currentConfig == "IC" and previousConfig == "CR":
2570
+ newConfig = previousConfig
2571
+ else:
2572
+ newConfig = currentConfig
2573
+
2574
+ elif phase == "Cruise":
2575
+ newConfig = currentConfig
2576
+
2577
+ # previous configuration is empty/unknown
2578
+ else:
2579
+ newConfig = currentConfig
2580
+
2581
+ return newConfig
2582
+
2583
+
2584
+ class ARPM:
2585
+ """This class is a BADA3 aircraft subclass and implements the Airline Procedure Model (ARPM)
2586
+ following the BADA3 user manual.
2587
+
2588
+ :param AC: parsed aircraft.
2589
+ :type AC: bada3.Parse.
2590
+ """
2591
+
2592
+ def __init__(self, AC):
2593
+ self.AC = AC
2594
+
2595
+ self.flightEnvelope = FlightEnvelope(AC)
2596
+
2597
+ def climbSpeed(
2598
+ self,
2599
+ theta,
2600
+ delta,
2601
+ mass,
2602
+ h,
2603
+ DeltaTemp,
2604
+ speedSchedule_default=None,
2605
+ applyLimits=True,
2606
+ config=None,
2607
+ procedure="BADA",
2608
+ NADP1_ALT=3000,
2609
+ NADP2_ALT=[1000, 3000],
2610
+ ):
2611
+ """This function computes the climb speed schedule CAS speed for any given altitude
2612
+
2613
+ :param h: altitude [m].
2614
+ :param mass: aircraft mass [kg].
2615
+ :param theta: normalised air temperature [-].
2616
+ :param delta: normalised air pressure [-].
2617
+ :param DeltaTemp: deviation with respect to ISA [K]
2618
+ :param speedSchedule_default: default speed schedule that will overwrite the BADA schedule [Vcl1, Vcl2, Mcl].
2619
+ :param applyLimits: apply min/max speed limitation [-].
2620
+ :type h: float.
2621
+ :type mass: float.
2622
+ :type theta: float.
2623
+ :type delta: float.
2624
+ :type DeltaTemp: float.
2625
+ :type speedSchedule_default: [float, float, float].
2626
+ :type applyLimits: [boolean].
2627
+ :returns: climb calibrated airspeed (CAS) [m s^-1].
2628
+ :rtype: float
2629
+ """
2630
+
2631
+ phase = "cl"
2632
+ acModel = self.AC.engineType
2633
+ Cvmin = Parser.getGPFValue(self.AC.GPFdata, "C_v_min", phase=phase)
2634
+ CvminTO = Parser.getGPFValue(self.AC.GPFdata, "C_v_min_to", phase="to")
2635
+ VstallTO = self.flightEnvelope.VStall(config="TO", mass=mass)
2636
+ VstallCR = self.flightEnvelope.VStall(config="CR", mass=mass)
2637
+
2638
+ [Vcl1, Vcl2, Mcl] = self.flightEnvelope.getSpeedSchedule(
2639
+ phase=phase
2640
+ ) # BADA Climb speed schedule
2641
+
2642
+ if speedSchedule_default is not None:
2643
+ Vcl1 = speedSchedule_default[0]
2644
+ Vcl2 = speedSchedule_default[1]
2645
+ Mcl = speedSchedule_default[2]
2646
+
2647
+ crossOverAlt = atm.crossOver(cas=Vcl2, Mach=Mcl)
2648
+ sigma = atm.sigma(theta=theta, delta=delta)
2649
+
2650
+ if procedure == "BADA":
2651
+ if acModel == "JET":
2652
+ speed = list()
2653
+ speed.append(min(Vcl1, conv.kt2ms(250)))
2654
+ speed.append(
2655
+ Cvmin * VstallTO
2656
+ + conv.kt2ms(
2657
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_5", phase=phase)
2658
+ )
2659
+ )
2660
+ speed.append(
2661
+ Cvmin * VstallTO
2662
+ + conv.kt2ms(
2663
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_4", phase=phase)
2664
+ )
2665
+ )
2666
+ speed.append(
2667
+ Cvmin * VstallTO
2668
+ + conv.kt2ms(
2669
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_3", phase=phase)
2670
+ )
2671
+ )
2672
+ speed.append(
2673
+ Cvmin * VstallTO
2674
+ + conv.kt2ms(
2675
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_2", phase=phase)
2676
+ )
2677
+ )
2678
+ speed.append(
2679
+ Cvmin * VstallTO
2680
+ + conv.kt2ms(
2681
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_1", phase=phase)
2682
+ )
2683
+ )
2684
+
2685
+ n = 1
2686
+ while n < len(speed):
2687
+ if speed[n] > speed[n - 1]:
2688
+ speed[n] = speed[n - 1]
2689
+ n = n + 1
2690
+
2691
+ if h < conv.ft2m(1500):
2692
+ cas = speed[5]
2693
+ elif h >= conv.ft2m(1500) and h < conv.ft2m(3000):
2694
+ cas = speed[4]
2695
+ elif h >= conv.ft2m(3000) and h < conv.ft2m(4000):
2696
+ cas = speed[3]
2697
+ elif h >= conv.ft2m(4000) and h < conv.ft2m(5000):
2698
+ cas = speed[2]
2699
+ elif h >= conv.ft2m(5000) and h < conv.ft2m(6000):
2700
+ cas = speed[1]
2701
+ elif h >= conv.ft2m(6000) and h < conv.ft2m(10000):
2702
+ cas = speed[0]
2703
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
2704
+ cas = Vcl2
2705
+ elif h >= crossOverAlt:
2706
+ cas = atm.mach2Cas(Mach=Mcl, theta=theta, delta=delta, sigma=sigma)
2707
+
2708
+ elif acModel == "TURBOPROP" or acModel == "PISTON" or acModel == "ELECTRIC":
2709
+ speed = list()
2710
+ speed.append(min(Vcl1, conv.kt2ms(250)))
2711
+ speed.append(
2712
+ Cvmin * VstallTO
2713
+ + conv.kt2ms(
2714
+ Parser.getGPFValue(
2715
+ self.AC.GPFdata, "V_cl_8", engine="TURBOPROP", phase=phase
2716
+ )
2717
+ )
2718
+ )
2719
+ speed.append(
2720
+ Cvmin * VstallTO
2721
+ + conv.kt2ms(
2722
+ Parser.getGPFValue(
2723
+ self.AC.GPFdata, "V_cl_7", engine="TURBOPROP", phase=phase
2724
+ )
2725
+ )
2726
+ )
2727
+ speed.append(
2728
+ Cvmin * VstallTO
2729
+ + conv.kt2ms(
2730
+ Parser.getGPFValue(
2731
+ self.AC.GPFdata, "V_cl_6", engine="TURBOPROP", phase=phase
2732
+ )
2733
+ )
2734
+ )
2735
+
2736
+ n = 1
2737
+ while n < len(speed):
2738
+ if speed[n] > speed[n - 1]:
2739
+ speed[n] = speed[n - 1]
2740
+ n = n + 1
2741
+
2742
+ if h < conv.ft2m(500):
2743
+ cas = speed[3]
2744
+ elif h >= conv.ft2m(500) and h < conv.ft2m(1000):
2745
+ cas = speed[2]
2746
+ elif h >= conv.ft2m(1000) and h < conv.ft2m(1500):
2747
+ cas = speed[1]
2748
+ elif h >= conv.ft2m(1500) and h < conv.ft2m(10000):
2749
+ cas = speed[0]
2750
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
2751
+ cas = Vcl2
2752
+ elif h >= crossOverAlt:
2753
+ cas = atm.mach2Cas(Mach=Mcl, theta=theta, delta=delta, sigma=sigma)
2754
+
2755
+ elif procedure == "NADP1":
2756
+ if acModel == "JET":
2757
+ speed = list()
2758
+ speed.append(min(Vcl1, conv.kt2ms(250)))
2759
+ speed.append(
2760
+ CvminTO * VstallTO
2761
+ + conv.kt2ms(
2762
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_2", phase=phase)
2763
+ )
2764
+ )
2765
+ n = 1
2766
+ while n < len(speed):
2767
+ if speed[n] > speed[n - 1]:
2768
+ speed[n] = speed[n - 1]
2769
+ n = n + 1
2770
+
2771
+ if h < conv.ft2m(NADP1_ALT):
2772
+ cas = speed[1]
2773
+ elif h >= conv.ft2m(NADP1_ALT) and h < conv.ft2m(10000):
2774
+ cas = speed[0]
2775
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
2776
+ cas = Vcl2
2777
+ elif h >= crossOverAlt:
2778
+ sigma = atm.sigma(theta=theta, delta=delta)
2779
+ cas = atm.mach2Cas(Mach=Mcl, theta=theta, delta=delta, sigma=sigma)
2780
+
2781
+ elif acModel == "TURBOPROP" or acModel == "PISTON":
2782
+ speed = list()
2783
+ speed.append(min(Vcl1, conv.kt2ms(250)))
2784
+ speed.append(
2785
+ CvminTO * VstallTO
2786
+ + conv.kt2ms(
2787
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_1", phase=phase)
2788
+ )
2789
+ )
2790
+
2791
+ n = 1
2792
+ while n < len(speed):
2793
+ if speed[n] > speed[n - 1]:
2794
+ speed[n] = speed[n - 1]
2795
+ n = n + 1
2796
+
2797
+ if h < conv.ft2m(NADP1_ALT):
2798
+ cas = speed[1]
2799
+ elif h >= conv.ft2m(NADP1_ALT) and h < conv.ft2m(10000):
2800
+ cas = speed[0]
2801
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
2802
+ cas = Vcl2
2803
+ elif h >= crossOverAlt:
2804
+ sigma = atm.sigma(theta=theta, delta=delta)
2805
+ cas = atm.mach2Cas(Mach=Mcl, theta=theta, delta=delta, sigma=sigma)
2806
+
2807
+ elif procedure == "NADP2":
2808
+ if acModel == "JET":
2809
+ speed = list()
2810
+ speed.append(min(Vcl1, conv.kt2ms(250)))
2811
+ speed.append(
2812
+ Cvmin * VstallCR
2813
+ + conv.kt2ms(
2814
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_2", phase=phase)
2815
+ )
2816
+ )
2817
+ speed.append(
2818
+ CvminTO * VstallTO
2819
+ + conv.kt2ms(
2820
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_2", phase=phase)
2821
+ )
2822
+ )
2823
+
2824
+ n = 1
2825
+ while n < len(speed):
2826
+ if speed[n] > speed[n - 1]:
2827
+ speed[n] = speed[n - 1]
2828
+ n = n + 1
2829
+
2830
+ if h < conv.ft2m(NADP2_ALT[0]):
2831
+ cas = speed[2]
2832
+ elif h >= conv.ft2m(NADP2_ALT[0]) and h < conv.ft2m(NADP2_ALT[1]):
2833
+ cas = speed[1]
2834
+ elif h >= conv.ft2m(NADP2_ALT[1]) and h < conv.ft2m(10000):
2835
+ cas = speed[0]
2836
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
2837
+ cas = Vcl2
2838
+ elif h >= crossOverAlt:
2839
+ sigma = atm.sigma(theta=theta, delta=delta)
2840
+ cas = atm.mach2Cas(Mach=Mcl, theta=theta, delta=delta, sigma=sigma)
2841
+
2842
+ elif acModel == "TURBOPROP" or acModel == "PISTON":
2843
+ speed = list()
2844
+ speed.append(min(Vcl1, conv.kt2ms(250)))
2845
+ speed.append(
2846
+ Cvmin * VstallCR
2847
+ + conv.kt2ms(
2848
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_2", phase=phase)
2849
+ )
2850
+ )
2851
+ speed.append(
2852
+ CvminTO * VstallTO
2853
+ + conv.kt2ms(
2854
+ Parser.getGPFValue(self.AC.GPFdata, "V_cl_1", phase=phase)
2855
+ )
2856
+ )
2857
+
2858
+ n = 1
2859
+ while n < len(speed):
2860
+ if speed[n] > speed[n - 1]:
2861
+ speed[n] = speed[n - 1]
2862
+ n = n + 1
2863
+
2864
+ if h < conv.ft2m(NADP2_ALT[0]):
2865
+ cas = speed[2]
2866
+ elif h >= conv.ft2m(NADP2_ALT[0]) and h < conv.ft2m(NADP2_ALT[1]):
2867
+ cas = speed[1]
2868
+ elif h >= conv.ft2m(NADP2_ALT[1]) and h < conv.ft2m(10000):
2869
+ cas = speed[0]
2870
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
2871
+ cas = Vcl2
2872
+ elif h >= crossOverAlt:
2873
+ sigma = atm.sigma(theta=theta, delta=delta)
2874
+ cas = atm.mach2Cas(Mach=Mcl, theta=theta, delta=delta, sigma=sigma)
2875
+
2876
+ if applyLimits:
2877
+ # check if the speed is within the limits of minimum and maximum speed from the flight envelope, if not, overwrite calculated speed with flight envelope min/max speed
2878
+ if config is None:
2879
+ config = self.flightEnvelope.getConfig(
2880
+ h=h, phase="Climb", v=cas, mass=mass, DeltaTemp=DeltaTemp
2881
+ )
2882
+ minSpeed = self.flightEnvelope.VMin(
2883
+ h=h, mass=mass, config=config, DeltaTemp=DeltaTemp
2884
+ )
2885
+ maxSpeed = self.flightEnvelope.VMax(h=h, DeltaTemp=DeltaTemp)
2886
+
2887
+ eps = 1e-6 # float calculation precision
2888
+ # empty envelope - keep the original calculated CAS speed
2889
+ if maxSpeed < minSpeed:
2890
+ if (cas - eps) > maxSpeed and (cas - eps) > minSpeed:
2891
+ return [cas, "V"]
2892
+ elif (cas + eps) < minSpeed and (cas + eps) < maxSpeed:
2893
+ return [cas, "v"]
2894
+ else:
2895
+ return [cas, "vV"]
2896
+
2897
+ if minSpeed > (cas + eps):
2898
+ return [minSpeed, "C"]
2899
+
2900
+ if maxSpeed < (cas - eps):
2901
+ return [maxSpeed, "C"]
2902
+
2903
+ return [cas, ""]
2904
+
2905
+ def cruiseSpeed(
2906
+ self,
2907
+ theta,
2908
+ delta,
2909
+ mass,
2910
+ h,
2911
+ DeltaTemp,
2912
+ speedSchedule_default=None,
2913
+ applyLimits=True,
2914
+ config=None,
2915
+ ):
2916
+ """This function computes the cruise speed schedule CAS speed for any given altitude
2917
+
2918
+ :param h: altitude [m].
2919
+ :param mass: aircraft mass [kg].
2920
+ :param theta: normalised air temperature [-].
2921
+ :param delta: normalised air pressure [-].
2922
+ :param DeltaTemp: deviation with respect to ISA [K]
2923
+ :param speedSchedule_default: default speed schedule that will overwrite the BADA schedule [Vcr1, Vcr2, Mcr].
2924
+ :param applyLimits: apply min/max speed limitation [-].
2925
+ :type h: float.
2926
+ :type mass: float.
2927
+ :type theta: float.
2928
+ :type delta: float.
2929
+ :type DeltaTemp: float.
2930
+ :type speedSchedule_default: [float, float, float].
2931
+ :type applyLimits: [boolean].
2932
+ :returns: climb calibrated airspeed (CAS) [m s^-1].
2933
+ :rtype: float
2934
+ """
2935
+
2936
+ phase = "cr"
2937
+ acModel = self.AC.engineType
2938
+
2939
+ [Vcr1, Vcr2, Mcr] = self.flightEnvelope.getSpeedSchedule(
2940
+ phase=phase
2941
+ ) # BADA Cruise speed schedule
2942
+
2943
+ if speedSchedule_default is not None:
2944
+ Vcr1 = speedSchedule_default[0]
2945
+ Vcr2 = speedSchedule_default[1]
2946
+ Mcr = speedSchedule_default[2]
2947
+
2948
+ crossOverAlt = atm.crossOver(cas=Vcr2, Mach=Mcr)
2949
+ sigma = atm.sigma(theta=theta, delta=delta)
2950
+
2951
+ if acModel == "JET":
2952
+ if h < conv.ft2m(3000):
2953
+ cas = min(Vcr1, conv.kt2ms(170))
2954
+ elif h >= conv.ft2m(3000) and h < conv.ft2m(6000):
2955
+ cas = min(Vcr1, conv.kt2ms(220))
2956
+ elif h >= conv.ft2m(6000) and h < conv.ft2m(14000):
2957
+ cas = min(Vcr1, conv.kt2ms(250))
2958
+ elif h >= conv.ft2m(14000) and h < crossOverAlt:
2959
+ cas = Vcr2
2960
+ elif h >= crossOverAlt:
2961
+ cas = atm.mach2Cas(Mach=Mcr, theta=theta, delta=delta, sigma=sigma)
2962
+
2963
+ elif acModel == "TURBOPROP" or acModel == "PISTON" or acModel == "ELECTRIC":
2964
+ if h < conv.ft2m(3000):
2965
+ cas = min(Vcr1, conv.kt2ms(150))
2966
+ elif h >= conv.ft2m(3000) and h < conv.ft2m(6000):
2967
+ cas = min(Vcr1, conv.kt2ms(180))
2968
+ elif h >= conv.ft2m(6000) and h < conv.ft2m(10000):
2969
+ cas = min(Vcr1, conv.kt2ms(250))
2970
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
2971
+ cas = Vcr2
2972
+ elif h >= crossOverAlt:
2973
+ cas = atm.mach2Cas(Mach=Mcr, theta=theta, delta=delta, sigma=sigma)
2974
+
2975
+ if applyLimits:
2976
+ # check if the speed is within the limits of minimum and maximum speed from the flight envelope, if not, overwrite calculated speed with flight envelope min/max speed
2977
+ if config is None:
2978
+ config = self.flightEnvelope.getConfig(
2979
+ h=h, phase="Cruise", v=cas, mass=mass, DeltaTemp=DeltaTemp
2980
+ )
2981
+
2982
+ minSpeed = self.flightEnvelope.VMin(
2983
+ h=h, mass=mass, config=config, DeltaTemp=DeltaTemp
2984
+ )
2985
+ maxSpeed = self.flightEnvelope.VMax(h=h, DeltaTemp=DeltaTemp)
2986
+
2987
+ eps = 1e-6 # float calculation precision
2988
+ # empty envelope - keep the original calculated CAS speed
2989
+ if maxSpeed < minSpeed:
2990
+ if (cas - eps) > maxSpeed and (cas - eps) > minSpeed:
2991
+ return [cas, "V"]
2992
+ elif (cas + eps) < minSpeed and (cas + eps) < maxSpeed:
2993
+ return [cas, "v"]
2994
+ else:
2995
+ return [cas, "vV"]
2996
+
2997
+ if minSpeed > (cas + eps):
2998
+ return [minSpeed, "C"]
2999
+
3000
+ if maxSpeed < (cas - eps):
3001
+ return [maxSpeed, "C"]
3002
+
3003
+ return [cas, ""]
3004
+
3005
+ def descentSpeed(
3006
+ self,
3007
+ theta,
3008
+ delta,
3009
+ mass,
3010
+ h,
3011
+ DeltaTemp,
3012
+ speedSchedule_default=None,
3013
+ applyLimits=True,
3014
+ config=None,
3015
+ ):
3016
+ """This function computes the descent speed schedule CAS speed for any given altitude
3017
+
3018
+ :param h: altitude [m].
3019
+ :param mass: aircraft mass [kg].
3020
+ :param theta: normalised air temperature [-].
3021
+ :param delta: normalised air pressure [-].
3022
+ :param DeltaTemp: deviation with respect to ISA [K]
3023
+ :param speedSchedule_default: default speed schedule that will overwrite the BADA schedule [Vdes1, Vdes2, Mdes].
3024
+ :param applyLimits: apply min/max speed limitation [-].
3025
+ :type h: float.
3026
+ :type mass: float.
3027
+ :type theta: float.
3028
+ :type delta: float.
3029
+ :type DeltaTemp: float.
3030
+ :type speedSchedule_default: [float, float, float].
3031
+ :type applyLimits: [boolean].
3032
+ :returns: climb calibrated airspeed (CAS) [m s^-1].
3033
+ :rtype: float
3034
+ """
3035
+
3036
+ phase = "des"
3037
+ acModel = self.AC.engineType
3038
+ Cvmin = Parser.getGPFValue(self.AC.GPFdata, "C_v_min", phase=phase)
3039
+ VstallDES = self.flightEnvelope.VStall(config="LD", mass=mass)
3040
+
3041
+ [Vdes1, Vdes2, Mdes] = self.flightEnvelope.getSpeedSchedule(
3042
+ phase=phase
3043
+ ) # BADA Descent speed schedule
3044
+
3045
+ if speedSchedule_default is not None:
3046
+ Vdes1 = speedSchedule_default[0]
3047
+ Vdes2 = speedSchedule_default[1]
3048
+ Mdes = speedSchedule_default[2]
3049
+
3050
+ crossOverAlt = atm.crossOver(cas=Vdes2, Mach=Mdes)
3051
+ sigma = atm.sigma(theta=theta, delta=delta)
3052
+
3053
+ if acModel == "JET" or acModel == "TURBOPROP":
3054
+ speed = list()
3055
+ speed.append(min(Vdes1, conv.kt2ms(220)))
3056
+ speed.append(
3057
+ Cvmin * VstallDES
3058
+ + conv.kt2ms(
3059
+ Parser.getGPFValue(self.AC.GPFdata, "V_des_4", phase=phase)
3060
+ )
3061
+ )
3062
+ speed.append(
3063
+ Cvmin * VstallDES
3064
+ + conv.kt2ms(
3065
+ Parser.getGPFValue(self.AC.GPFdata, "V_des_3", phase=phase)
3066
+ )
3067
+ )
3068
+ speed.append(
3069
+ Cvmin * VstallDES
3070
+ + conv.kt2ms(
3071
+ Parser.getGPFValue(self.AC.GPFdata, "V_des_2", phase=phase)
3072
+ )
3073
+ )
3074
+ speed.append(
3075
+ Cvmin * VstallDES
3076
+ + conv.kt2ms(
3077
+ Parser.getGPFValue(self.AC.GPFdata, "V_des_1", phase=phase)
3078
+ )
3079
+ )
3080
+
3081
+ n = 1
3082
+ while n < len(speed):
3083
+ if speed[n] > speed[n - 1]:
3084
+ speed[n] = speed[n - 1]
3085
+ n = n + 1
3086
+
3087
+ if h < conv.ft2m(1000):
3088
+ cas = speed[4]
3089
+ elif h >= conv.ft2m(1000) and h < conv.ft2m(1500):
3090
+ cas = speed[3]
3091
+ elif h >= conv.ft2m(1500) and h < conv.ft2m(2000):
3092
+ cas = speed[2]
3093
+ elif h >= conv.ft2m(2000) and h < conv.ft2m(3000):
3094
+ cas = speed[1]
3095
+ elif h >= conv.ft2m(3000) and h < conv.ft2m(6000):
3096
+ cas = speed[0]
3097
+ elif h >= conv.ft2m(6000) and h < conv.ft2m(10000):
3098
+ cas = min(Vdes1, conv.kt2ms(250))
3099
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
3100
+ cas = Vdes2
3101
+ elif h >= crossOverAlt:
3102
+ cas = atm.mach2Cas(Mach=Mdes, theta=theta, delta=delta, sigma=sigma)
3103
+
3104
+ elif acModel == "PISTON" or acModel == "ELECTRIC":
3105
+ speed = list()
3106
+ speed.append(Vdes1)
3107
+ speed.append(
3108
+ Cvmin * VstallDES
3109
+ + conv.kt2ms(
3110
+ Parser.getGPFValue(
3111
+ self.AC.GPFdata, "V_des_7", engine="PISTON", phase=phase
3112
+ )
3113
+ )
3114
+ )
3115
+ speed.append(
3116
+ Cvmin * VstallDES
3117
+ + conv.kt2ms(
3118
+ Parser.getGPFValue(
3119
+ self.AC.GPFdata, "V_des_6", engine="PISTON", phase=phase
3120
+ )
3121
+ )
3122
+ )
3123
+ speed.append(
3124
+ Cvmin * VstallDES
3125
+ + conv.kt2ms(
3126
+ Parser.getGPFValue(
3127
+ self.AC.GPFdata, "V_des_5", engine="PISTON", phase=phase
3128
+ )
3129
+ )
3130
+ )
3131
+
3132
+ n = 1
3133
+ while n < len(speed):
3134
+ if speed[n] > speed[n - 1]:
3135
+ speed[n] = speed[n - 1]
3136
+ n = n + 1
3137
+
3138
+ if h < conv.ft2m(500):
3139
+ cas = speed[3]
3140
+ elif h >= conv.ft2m(500) and h < conv.ft2m(1000):
3141
+ cas = speed[2]
3142
+ elif h >= conv.ft2m(1000) and h < conv.ft2m(1500):
3143
+ cas = speed[1]
3144
+ elif h >= conv.ft2m(1500) and h < conv.ft2m(10000):
3145
+ cas = speed[0]
3146
+ elif h >= conv.ft2m(10000) and h < crossOverAlt:
3147
+ cas = Vdes2
3148
+ elif h >= crossOverAlt:
3149
+ cas = atm.mach2Cas(Mach=Mdes, theta=theta, delta=delta, sigma=sigma)
3150
+
3151
+ if applyLimits:
3152
+ # check if the speed is within the limits of minimum and maximum speed from the flight envelope, if not, overwrite calculated speed with flight envelope min/max speed
3153
+ if config is None:
3154
+ config = self.flightEnvelope.getConfig(
3155
+ h=h, phase="Descent", v=cas, mass=mass, DeltaTemp=DeltaTemp
3156
+ )
3157
+ minSpeed = self.flightEnvelope.VMin(
3158
+ h=h, mass=mass, config=config, DeltaTemp=DeltaTemp
3159
+ )
3160
+
3161
+ maxSpeed = self.flightEnvelope.VMax(h=h, DeltaTemp=DeltaTemp)
3162
+
3163
+ eps = 1e-6 # float calculation precision
3164
+ # empty envelope - keep the original calculated CAS speed
3165
+ if maxSpeed < minSpeed:
3166
+ if (cas - eps) > maxSpeed and (cas - eps) > minSpeed:
3167
+ return [cas, "V"]
3168
+ elif (cas + eps) < minSpeed and (cas + eps) < maxSpeed:
3169
+ return [cas, "v"]
3170
+ else:
3171
+ return [cas, "vV"]
3172
+
3173
+ if minSpeed > (cas + eps):
3174
+ return [minSpeed, "C"]
3175
+
3176
+ if maxSpeed < (cas - eps):
3177
+ return [maxSpeed, "C"]
3178
+
3179
+ return [cas, ""]
3180
+
3181
+
3182
+ class PTD(BADA3):
3183
+ """This class implements the PTD file creator for BADA3 aircraft following BADA3 manual.
3184
+
3185
+ :param AC: parsed aircraft.
3186
+ :type AC: bada3.Parse.
3187
+ """
3188
+
3189
+ def __init__(self, AC):
3190
+ super().__init__(AC)
3191
+
3192
+ self.flightEnvelope = FlightEnvelope(AC)
3193
+ self.ARPM = ARPM(AC)
3194
+
3195
+ def create(self, DeltaTemp, saveToPath):
3196
+ """This function creates the BADA3 PTD file
3197
+
3198
+ :param saveToPath: path to directory where PTF should be stored [-]
3199
+ :param DeltaTemp: deviation from ISA temperature [K]
3200
+ :type saveToPath: string.
3201
+ :type DeltaTemp: float.
3202
+ :returns: NONE
3203
+ """
3204
+
3205
+ # 3 different mass levels [kg]
3206
+ if 1.2 * self.AC.mass["minimum"] > self.AC.mass["reference"]:
3207
+ massLow = self.AC.mass["minimum"]
3208
+ else:
3209
+ massLow = 1.2 * self.AC.mass["minimum"]
3210
+
3211
+ massList = [massLow, self.AC.mass["reference"], self.AC.mass["maximum"]]
3212
+ max_alt_ft = self.AC.hmo
3213
+
3214
+ # original PTD altitude list
3215
+ altitudeList = list(range(0, 2000, 500))
3216
+ altitudeList.extend(range(2000, 4000, 1000))
3217
+
3218
+ if int(max_alt_ft) < 30000:
3219
+ altitudeList.extend(range(4000, int(max_alt_ft), 2000))
3220
+ altitudeList.append(int(max_alt_ft))
3221
+ else:
3222
+ altitudeList.extend(range(4000, 30000, 2000))
3223
+ altitudeList.extend(range(29000, int(max_alt_ft), 2000))
3224
+ altitudeList.append(int(max_alt_ft))
3225
+
3226
+ CLList = []
3227
+ for mass in massList:
3228
+ CLList.append(
3229
+ self.PTD_climb(
3230
+ mass=mass, altitudeList=altitudeList, DeltaTemp=DeltaTemp
3231
+ )
3232
+ )
3233
+ DESList_med = self.PTD_descent(
3234
+ mass=self.AC.mass["reference"],
3235
+ altitudeList=altitudeList,
3236
+ DeltaTemp=DeltaTemp,
3237
+ )
3238
+
3239
+ self.save2PTD(
3240
+ saveToPath=saveToPath,
3241
+ CLList_low=CLList[0],
3242
+ CLList_med=CLList[1],
3243
+ CLList_high=CLList[2],
3244
+ DESList_med=DESList_med,
3245
+ DeltaTemp=DeltaTemp,
3246
+ )
3247
+
3248
+ def save2PTD(
3249
+ self, saveToPath, CLList_low, CLList_med, CLList_high, DESList_med, DeltaTemp
3250
+ ):
3251
+ """This function saves data to PTD file
3252
+
3253
+ :param saveToPath: path to directory where PTD should be stored [-]
3254
+ :param CLList_low: list of PTD data in CLIMB at low aircraft mass [-].
3255
+ :param CLList_med: list of PTD data in CLIMB at medium aircraft mass [-].
3256
+ :param CLList_high: list of PTD data in CLIMB at high aircraft mass [-].
3257
+ :param DESList_med: list of PTD data in DESCENT at medium aircraft mass [-].
3258
+ :param DeltaTemp: deviation from ISA temperature [K]
3259
+ :type saveToPath: string.
3260
+ :type CLList_low: list.
3261
+ :type CLList_med: list.
3262
+ :type CLList_high: list.
3263
+ :type DESList_med: list.
3264
+ :type DeltaTemp: float.
3265
+ :returns: NONE
3266
+ """
3267
+
3268
+ def Nan2Zero(list):
3269
+ # replace NAN values by 0 for printing purposes
3270
+ for k in range(len(list)):
3271
+ for m in range(len(list[k])):
3272
+ if isinstance(list[k][m], float):
3273
+ if isnan(list[k][m]):
3274
+ list[k][m] = 0
3275
+ return list
3276
+
3277
+ newpath = saveToPath
3278
+ if not os.path.exists(newpath):
3279
+ os.makedirs(newpath)
3280
+
3281
+ if DeltaTemp == 0.0:
3282
+ ISA = ""
3283
+ elif DeltaTemp > 0.0:
3284
+ ISA = "+" + str(int(DeltaTemp))
3285
+ elif DeltaTemp < 0.0:
3286
+ ISA = str(int(DeltaTemp))
3287
+
3288
+ acName = self.AC.acName
3289
+
3290
+ while len(acName) < 6:
3291
+ acName = acName + "_"
3292
+ filename = saveToPath + acName + "_ISA" + ISA + ".PTD"
3293
+
3294
+ file = open(filename, "w")
3295
+ file.write("BADA PERFORMANCE FILE RESULTS\n")
3296
+ file = open(filename, "a")
3297
+ file.write("=============================\n=============================\n\n")
3298
+ file.write("Low mass CLIMBS\n")
3299
+ file.write("===============\n\n")
3300
+ file.write(
3301
+ " FL[-] T[K] p[Pa] rho[kg/m3] a[m/s] TAS[kt] CAS[kt] M[-] mass[kg] Thrust[N] Drag[N] Fuel[kgm] ESF[-] ROC[fpm] TDC[N] PWC[-]\n"
3302
+ )
3303
+
3304
+ # replace NAN values by 0 for printing purposes
3305
+ CLList_low = Nan2Zero(CLList_low)
3306
+ CLList_med = Nan2Zero(CLList_med)
3307
+ CLList_high = Nan2Zero(CLList_high)
3308
+ DESList_med = Nan2Zero(DESList_med)
3309
+
3310
+ for k in range(0, len(CLList_low[0])):
3311
+ file.write(
3312
+ "%6d %3.0f %6.0f %7.3f %7.0f %8.2f %8.2f %7.2f %6.0f %9.0f %9.0f %7.1f %7.2f %7.0f %8.0f %7.2f \n"
3313
+ % (
3314
+ CLList_low[0][k],
3315
+ CLList_low[1][k],
3316
+ CLList_low[2][k],
3317
+ CLList_low[3][k],
3318
+ CLList_low[4][k],
3319
+ CLList_low[5][k],
3320
+ CLList_low[6][k],
3321
+ CLList_low[7][k],
3322
+ CLList_low[8][k],
3323
+ CLList_low[9][k],
3324
+ CLList_low[10][k],
3325
+ CLList_low[11][k],
3326
+ CLList_low[12][k],
3327
+ CLList_low[13][k],
3328
+ CLList_low[14][k],
3329
+ CLList_low[15][k],
3330
+ )
3331
+ )
3332
+
3333
+ file.write("\n\nMedium mass CLIMBS\n")
3334
+ file.write("==================\n\n")
3335
+ file.write(
3336
+ " FL[-] T[K] p[Pa] rho[kg/m3] a[m/s] TAS[kt] CAS[kt] M[-] mass[kg] Thrust[N] Drag[N] Fuel[kgm] ESF[-] ROC[fpm] TDC[N] PWC[-]\n"
3337
+ )
3338
+
3339
+ for k in range(0, len(CLList_med[0])):
3340
+ file.write(
3341
+ "%6d %3.0f %6.0f %7.3f %7.0f %8.2f %8.2f %7.2f %6.0f %9.0f %9.0f %7.1f %7.2f %7.0f %8.0f %7.2f \n"
3342
+ % (
3343
+ CLList_med[0][k],
3344
+ CLList_med[1][k],
3345
+ CLList_med[2][k],
3346
+ CLList_med[3][k],
3347
+ CLList_med[4][k],
3348
+ CLList_med[5][k],
3349
+ CLList_med[6][k],
3350
+ CLList_med[7][k],
3351
+ CLList_med[8][k],
3352
+ CLList_med[9][k],
3353
+ CLList_med[10][k],
3354
+ CLList_med[11][k],
3355
+ CLList_med[12][k],
3356
+ CLList_med[13][k],
3357
+ CLList_med[14][k],
3358
+ CLList_med[15][k],
3359
+ )
3360
+ )
3361
+
3362
+ file.write("\n\nHigh mass CLIMBS\n")
3363
+ file.write("================\n\n")
3364
+ file.write(
3365
+ " FL[-] T[K] p[Pa] rho[kg/m3] a[m/s] TAS[kt] CAS[kt] M[-] mass[kg] Thrust[N] Drag[N] Fuel[kgm] ESF[-] ROC[fpm] TDC[N] PWC[-]\n"
3366
+ )
3367
+
3368
+ for k in range(0, len(CLList_high[0])):
3369
+ file.write(
3370
+ "%6d %3.0f %6.0f %7.3f %7.0f %8.2f %8.2f %7.2f %6.0f %9.0f %9.0f %7.1f %7.2f %7.0f %8.0f %7.2f \n"
3371
+ % (
3372
+ CLList_high[0][k],
3373
+ CLList_high[1][k],
3374
+ CLList_high[2][k],
3375
+ CLList_high[3][k],
3376
+ CLList_high[4][k],
3377
+ CLList_high[5][k],
3378
+ CLList_high[6][k],
3379
+ CLList_high[7][k],
3380
+ CLList_high[8][k],
3381
+ CLList_high[9][k],
3382
+ CLList_high[10][k],
3383
+ CLList_high[11][k],
3384
+ CLList_high[12][k],
3385
+ CLList_high[13][k],
3386
+ CLList_high[14][k],
3387
+ CLList_high[15][k],
3388
+ )
3389
+ )
3390
+
3391
+ file.write("\nMedium mass DESCENTS\n")
3392
+ file.write("====================\n\n")
3393
+ file.write(
3394
+ " FL[-] T[K] p[Pa] rho[kg/m3] a[m/s] TAS[kt] CAS[kt] M[-] mass[kg] Thrust[N] Drag[N] Fuel[kgm] ESF[-] ROD[fpm] TDC[N] gammaTAS[deg]\n"
3395
+ )
3396
+
3397
+ for k in range(0, len(DESList_med[0])):
3398
+ file.write(
3399
+ "%6d %3.0f %6.0f %7.3f %7.0f %8.2f %8.2f %7.2f %6.0f %9.0f %9.0f %7.1f %7.2f %7.0f %8.0f %8.2f \n"
3400
+ % (
3401
+ DESList_med[0][k],
3402
+ DESList_med[1][k],
3403
+ DESList_med[2][k],
3404
+ DESList_med[3][k],
3405
+ DESList_med[4][k],
3406
+ DESList_med[5][k],
3407
+ DESList_med[6][k],
3408
+ DESList_med[7][k],
3409
+ DESList_med[8][k],
3410
+ DESList_med[9][k],
3411
+ DESList_med[10][k],
3412
+ DESList_med[11][k],
3413
+ DESList_med[12][k],
3414
+ DESList_med[13][k],
3415
+ DESList_med[14][k],
3416
+ DESList_med[15][k],
3417
+ )
3418
+ )
3419
+
3420
+ file.write("\nTDC stands for (Thrust - Drag) * Cred\n")
3421
+
3422
+ def PTD_climb(self, mass, altitudeList, DeltaTemp):
3423
+ """This function calculates the BADA3 PTD data in CLIMB
3424
+
3425
+ :param mass: aircraft mass [kg]
3426
+ :param altitudeList: aircraft altitude list [ft]
3427
+ :param DeltaTemp: deviation from ISA temperature [K]
3428
+ :type mass: float.
3429
+ :type altitudeList: list of int.
3430
+ :type DeltaTemp: float.
3431
+ :returns: list of PTD CLIMB data [-]
3432
+ :rtype: list
3433
+ """
3434
+
3435
+ FL_complet = []
3436
+ T_complet = []
3437
+ p_complet = []
3438
+ rho_complet = []
3439
+ a_complet = []
3440
+ TAS_complet = []
3441
+ CAS_complet = []
3442
+ M_complet = []
3443
+ mass_complet = []
3444
+ Thrust_complet = []
3445
+ Drag_complet = []
3446
+ ff_comlet = []
3447
+ ESF_complet = []
3448
+ ROCD_complet = []
3449
+ TDC_complet = []
3450
+ PWC_complet = []
3451
+
3452
+ phase = "cl"
3453
+
3454
+ Vcl1 = self.AC.V1[phase]
3455
+ Vcl2 = self.AC.V2[phase]
3456
+ Mcl = self.AC.M[phase]
3457
+
3458
+ Vcl1 = min(Vcl1, conv.kt2ms(250))
3459
+ crossAlt = atm.crossOver(cas=Vcl2, Mach=Mcl)
3460
+
3461
+ for h in altitudeList:
3462
+ H_m = conv.ft2m(h) # altitude [m]
3463
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
3464
+ [cas, speedUpdated] = self.ARPM.climbSpeed(
3465
+ theta=theta,
3466
+ delta=delta,
3467
+ h=H_m,
3468
+ mass=mass,
3469
+ DeltaTemp=DeltaTemp,
3470
+ speedSchedule_default=[Vcl1, Vcl2, Mcl],
3471
+ applyLimits=False,
3472
+ )
3473
+ tas = atm.cas2Tas(cas=cas, delta=delta, sigma=sigma)
3474
+ M = atm.tas2Mach(v=tas, theta=theta)
3475
+ a = atm.aSound(theta=theta)
3476
+ FL = h / 100
3477
+
3478
+ config = self.flightEnvelope.getConfig(
3479
+ h=H_m, phase="Climb", v=cas, mass=mass, DeltaTemp=DeltaTemp
3480
+ )
3481
+
3482
+ Thrust = self.Thrust(
3483
+ rating="MCMB", v=tas, h=H_m, config=config, DeltaTemp=DeltaTemp
3484
+ )
3485
+ ff = self.ff(flightPhase="Climb", v=tas, h=H_m, T=Thrust) * 60
3486
+
3487
+ CL = self.CL(tas=tas, sigma=sigma, mass=mass)
3488
+
3489
+ CD = self.CD(CL=CL, config=config)
3490
+ Drag = self.D(tas=tas, sigma=sigma, CD=CD)
3491
+
3492
+ CPowRed = self.reducedPower(h=H_m, mass=mass, DeltaTemp=DeltaTemp)
3493
+ TDC = (Thrust - Drag) * CPowRed
3494
+
3495
+ if H_m < crossAlt:
3496
+ ESF = self.esf(
3497
+ h=H_m, flightEvolution="constCAS", M=M, DeltaTemp=DeltaTemp
3498
+ )
3499
+ else:
3500
+ ESF = self.esf(
3501
+ h=H_m, flightEvolution="constM", M=M, DeltaTemp=DeltaTemp
3502
+ )
3503
+
3504
+ ROCD = (
3505
+ conv.m2ft(
3506
+ self.ROCD(
3507
+ h=H_m,
3508
+ T=Thrust,
3509
+ D=Drag,
3510
+ v=tas,
3511
+ mass=mass,
3512
+ ESF=ESF,
3513
+ DeltaTemp=DeltaTemp,
3514
+ reducedPower=True,
3515
+ )
3516
+ )
3517
+ * 60
3518
+ )
3519
+
3520
+ FL_complet.append(proper_round(FL))
3521
+ T_complet.append(theta * const.temp_0)
3522
+ p_complet.append(delta * const.p_0)
3523
+ rho_complet.append(sigma * const.rho_0)
3524
+ a_complet.append(a)
3525
+ TAS_complet.append(conv.ms2kt(tas))
3526
+ CAS_complet.append(conv.ms2kt(cas))
3527
+ M_complet.append(M)
3528
+ mass_complet.append(mass)
3529
+ Thrust_complet.append(Thrust)
3530
+ Drag_complet.append(Drag)
3531
+ ff_comlet.append(ff)
3532
+ ESF_complet.append(ESF)
3533
+ ROCD_complet.append(ROCD)
3534
+ TDC_complet.append(TDC)
3535
+ PWC_complet.append(CPowRed)
3536
+
3537
+ CLList = [
3538
+ FL_complet,
3539
+ T_complet,
3540
+ p_complet,
3541
+ rho_complet,
3542
+ a_complet,
3543
+ TAS_complet,
3544
+ CAS_complet,
3545
+ M_complet,
3546
+ mass_complet,
3547
+ Thrust_complet,
3548
+ Drag_complet,
3549
+ ff_comlet,
3550
+ ESF_complet,
3551
+ ROCD_complet,
3552
+ TDC_complet,
3553
+ PWC_complet,
3554
+ ]
3555
+
3556
+ return CLList
3557
+
3558
+ def PTD_descent(self, mass, altitudeList, DeltaTemp):
3559
+ """This function calculates the BADA3 PTD data in DESCENT
3560
+
3561
+ :param mass: aircraft mass [kg]
3562
+ :param altitudeList: list of aircraft maximum altitude [ft]
3563
+ :param DeltaTemp: deviation from ISA temperature [K]
3564
+ :type mass: float.
3565
+ :type altitudeList: list of int.
3566
+ :type DeltaTemp: float.
3567
+ :returns: list of PTD DESCENT data [-]
3568
+ :rtype: list
3569
+ """
3570
+
3571
+ FL_complet = []
3572
+ T_complet = []
3573
+ p_complet = []
3574
+ rho_complet = []
3575
+ a_complet = []
3576
+ TAS_complet = []
3577
+ CAS_complet = []
3578
+ M_complet = []
3579
+ mass_complet = []
3580
+ Thrust_complet = []
3581
+ Drag_complet = []
3582
+ ff_comlet = []
3583
+ ESF_complet = []
3584
+ ROCD_complet = []
3585
+ TDC_complet = []
3586
+ gamma_complet = []
3587
+
3588
+ phase = "des"
3589
+
3590
+ Vdes1 = self.AC.V1[phase]
3591
+ Vdes2 = self.AC.V2[phase]
3592
+ Mdes = self.AC.M[phase]
3593
+
3594
+ Vdes1 = min(Vdes1, conv.kt2ms(250))
3595
+ crossAlt = atm.crossOver(cas=Vdes2, Mach=Mdes)
3596
+
3597
+ for h in altitudeList:
3598
+ H_m = conv.ft2m(h) # altitude [m]
3599
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
3600
+ [cas, speedUpdated] = self.ARPM.descentSpeed(
3601
+ theta=theta,
3602
+ delta=delta,
3603
+ h=H_m,
3604
+ mass=mass,
3605
+ DeltaTemp=DeltaTemp,
3606
+ speedSchedule_default=[Vdes1, Vdes2, Mdes],
3607
+ applyLimits=False,
3608
+ )
3609
+ tas = atm.cas2Tas(cas=cas, delta=delta, sigma=sigma)
3610
+ M = atm.tas2Mach(v=tas, theta=theta)
3611
+ a = atm.aSound(theta=theta)
3612
+ FL = h / 100
3613
+
3614
+ CL = self.CL(tas=tas, sigma=sigma, mass=mass)
3615
+ config = self.flightEnvelope.getConfig(
3616
+ h=H_m, phase="Descent", v=cas, mass=mass, DeltaTemp=DeltaTemp
3617
+ )
3618
+ CD = self.CD(CL=CL, config=config)
3619
+ Drag = self.D(tas=tas, sigma=sigma, CD=CD)
3620
+
3621
+ if self.AC.engineType == "PISTON" or self.AC.engineType == "ELECTRIC":
3622
+ # PISTON and ELECTRIC uses LIDL throughout the whole descent phase
3623
+ Thrust = self.Thrust(
3624
+ rating="LIDL", v=tas, h=H_m, config="CR", DeltaTemp=DeltaTemp
3625
+ )
3626
+ ff = (
3627
+ self.ff(
3628
+ flightPhase="Descent",
3629
+ v=tas,
3630
+ h=H_m,
3631
+ T=Thrust,
3632
+ config="CR",
3633
+ adapted=False,
3634
+ )
3635
+ * 60
3636
+ )
3637
+ else:
3638
+ Thrust = self.Thrust(
3639
+ rating="LIDL", v=tas, h=H_m, config=config, DeltaTemp=DeltaTemp
3640
+ )
3641
+ ff = (
3642
+ self.ff(
3643
+ flightPhase="Descent",
3644
+ v=tas,
3645
+ h=H_m,
3646
+ T=Thrust,
3647
+ config=config,
3648
+ adapted=False,
3649
+ )
3650
+ * 60
3651
+ )
3652
+
3653
+ CPowRed = 1.0
3654
+ TDC = (Thrust - Drag) * CPowRed
3655
+
3656
+ if H_m < crossAlt:
3657
+ ESF = self.esf(
3658
+ h=H_m, flightEvolution="constCAS", M=M, DeltaTemp=DeltaTemp
3659
+ )
3660
+ else:
3661
+ ESF = self.esf(
3662
+ h=H_m, flightEvolution="constM", M=M, DeltaTemp=DeltaTemp
3663
+ )
3664
+
3665
+ ROCD = (
3666
+ conv.m2ft(
3667
+ self.ROCD(
3668
+ h=H_m,
3669
+ T=Thrust,
3670
+ D=Drag,
3671
+ v=tas,
3672
+ mass=mass,
3673
+ ESF=ESF,
3674
+ DeltaTemp=DeltaTemp,
3675
+ )
3676
+ )
3677
+ * 60
3678
+ )
3679
+
3680
+ tau_const = (theta * const.temp_0) / (theta * const.temp_0 - DeltaTemp)
3681
+ dhdt = (conv.ft2m(ROCD / 60)) * tau_const
3682
+
3683
+ if self.AC.drone:
3684
+ gamma = conv.rad2deg(atan(dhdt / tas))
3685
+ else:
3686
+ gamma = conv.rad2deg(asin(dhdt / tas))
3687
+
3688
+ FL_complet.append(proper_round(FL))
3689
+ T_complet.append(theta * const.temp_0)
3690
+ p_complet.append(delta * const.p_0)
3691
+ rho_complet.append(sigma * const.rho_0)
3692
+ a_complet.append(a)
3693
+ TAS_complet.append(conv.ms2kt(tas))
3694
+ CAS_complet.append(conv.ms2kt(cas))
3695
+ M_complet.append(M)
3696
+ mass_complet.append(mass)
3697
+ Thrust_complet.append(Thrust)
3698
+ Drag_complet.append(Drag)
3699
+ ff_comlet.append(ff)
3700
+ ESF_complet.append(ESF)
3701
+ ROCD_complet.append(-1 * ROCD)
3702
+ TDC_complet.append(TDC)
3703
+ gamma_complet.append(gamma)
3704
+
3705
+ DESList = [
3706
+ FL_complet,
3707
+ T_complet,
3708
+ p_complet,
3709
+ rho_complet,
3710
+ a_complet,
3711
+ TAS_complet,
3712
+ CAS_complet,
3713
+ M_complet,
3714
+ mass_complet,
3715
+ Thrust_complet,
3716
+ Drag_complet,
3717
+ ff_comlet,
3718
+ ESF_complet,
3719
+ ROCD_complet,
3720
+ TDC_complet,
3721
+ gamma_complet,
3722
+ ]
3723
+
3724
+ return DESList
3725
+
3726
+
3727
+ class PTF(BADA3):
3728
+ """This class implements the PTD file creator for BADA3 aircraft following BADA3 manual.
3729
+
3730
+ :param AC: parsed aircraft.
3731
+ :type AC: bada3.Parse.
3732
+ """
3733
+
3734
+ def __init__(self, AC):
3735
+ super().__init__(AC)
3736
+
3737
+ self.flightEnvelope = FlightEnvelope(AC)
3738
+ self.ARPM = ARPM(AC)
3739
+
3740
+ def create(self, DeltaTemp, saveToPath):
3741
+ """This function creates the BADA3 PTF file
3742
+
3743
+ :param saveToPath: path to directory where PTF should be stored [-]
3744
+ :param DeltaTemp: deviation from ISA temperature [K]
3745
+ :type saveToPath: string.
3746
+ :type DeltaTemp: float.
3747
+ :returns: NONE
3748
+ """
3749
+
3750
+ # 3 different mass levels [kg]
3751
+ if 1.2 * self.AC.mass["minimum"] > self.AC.mass["reference"]:
3752
+ massLow = self.AC.mass["minimum"]
3753
+ else:
3754
+ massLow = 1.2 * self.AC.mass["minimum"]
3755
+
3756
+ massList = [massLow, self.AC.mass["reference"], self.AC.mass["maximum"]]
3757
+ max_alt_ft = self.AC.hmo
3758
+
3759
+ # original PTF altitude list
3760
+ altitudeList = list(range(0, 2000, 500))
3761
+ altitudeList.extend(range(2000, 4000, 1000))
3762
+
3763
+ if int(max_alt_ft) < 30000:
3764
+ altitudeList.extend(range(4000, int(max_alt_ft), 2000))
3765
+ altitudeList.append(max_alt_ft)
3766
+ else:
3767
+ altitudeList.extend(range(4000, 30000, 2000))
3768
+ altitudeList.extend(range(29000, int(max_alt_ft), 2000))
3769
+ altitudeList.append(max_alt_ft)
3770
+
3771
+ CRList = self.PTF_cruise(
3772
+ massList=massList, altitudeList=altitudeList, DeltaTemp=DeltaTemp
3773
+ )
3774
+ CLList = self.PTF_climb(
3775
+ massList=massList, altitudeList=altitudeList, DeltaTemp=DeltaTemp
3776
+ )
3777
+ DESList = self.PTF_descent(
3778
+ massList=massList, altitudeList=altitudeList, DeltaTemp=DeltaTemp
3779
+ )
3780
+
3781
+ self.save2PTF(
3782
+ saveToPath=saveToPath,
3783
+ altitudeList=altitudeList,
3784
+ massList=massList,
3785
+ CRList=CRList,
3786
+ CLList=CLList,
3787
+ DESList=DESList,
3788
+ DeltaTemp=DeltaTemp,
3789
+ )
3790
+
3791
+ def save2PTF(
3792
+ self, saveToPath, CRList, CLList, DESList, altitudeList, massList, DeltaTemp
3793
+ ):
3794
+ """This function saves data to PTF file
3795
+
3796
+ :param saveToPath: path to directory where PTF should be stored [-]
3797
+ :param altitudeList: aircraft altitude list [ft]
3798
+ :param massList: aircraft mass list [kg]
3799
+ :param CRList: list of PTF data in CRUISE [-].
3800
+ :param CLList: list of PTF data in CLIMB [-].
3801
+ :param DESList: list of PTF data in DESCENT [-].
3802
+ :param DeltaTemp: deviation from ISA temperature [K]
3803
+ :type saveToPath: string.
3804
+ :type altitudeList: list of int.
3805
+ :type massList: list of int.
3806
+ :type CRList: list.
3807
+ :type CLList: list.
3808
+ :type DESList: list.
3809
+ :type DeltaTemp: float.
3810
+ :returns: NONE
3811
+ """
3812
+
3813
+ def Nan2Zero(list):
3814
+ # replace NAN values by 0 for printing purposes
3815
+ for k in range(len(list)):
3816
+ for m in range(len(list[k])):
3817
+ if isinstance(list[k][m], float):
3818
+ if isnan(list[k][m]):
3819
+ list[k][m] = 0
3820
+ return list
3821
+
3822
+ newpath = saveToPath
3823
+ if not os.path.exists(newpath):
3824
+ os.makedirs(newpath)
3825
+
3826
+ if DeltaTemp == 0.0:
3827
+ ISA = ""
3828
+ elif DeltaTemp > 0.0:
3829
+ ISA = "+" + str(int(DeltaTemp))
3830
+ elif DeltaTemp < 0.0:
3831
+ ISA = str(int(DeltaTemp))
3832
+
3833
+ acName = self.AC.acName
3834
+
3835
+ while len(acName) < 6:
3836
+ acName = acName + "_"
3837
+ filename = saveToPath + acName + "_ISA" + ISA + ".PTF"
3838
+
3839
+ V1cl = min(250, conv.ms2kt(self.AC.V1["cl"]))
3840
+ V2cl = conv.ms2kt(self.AC.V2["cl"])
3841
+ Mcl = self.AC.M["cl"]
3842
+ V1des = min(250, conv.ms2kt(self.AC.V1["des"]))
3843
+ V2des = conv.ms2kt(self.AC.V2["des"])
3844
+ Mdes = self.AC.M["des"]
3845
+ V1cr = min(250, conv.ms2kt(self.AC.V1["cr"]))
3846
+ V2cr = conv.ms2kt(self.AC.V2["cr"])
3847
+ Mcr = self.AC.M["cr"]
3848
+
3849
+ today = date.today()
3850
+ d3 = today.strftime("%b %d %Y")
3851
+ OPFModDate = self.AC.modificationDateOPF
3852
+ APFModDate = self.AC.modificationDateAPF
3853
+
3854
+ file = open(filename, "w")
3855
+ file.write(
3856
+ "BADA PERFORMANCE FILE %s\n\n" % (d3)
3857
+ )
3858
+ file = open(filename, "a")
3859
+ file.write("AC/Type: %s\n" % (acName))
3860
+ file.write(
3861
+ " Source OPF File: %s\n"
3862
+ % (OPFModDate)
3863
+ )
3864
+ file.write(
3865
+ " Source APF file: %s\n\n"
3866
+ % (APFModDate)
3867
+ )
3868
+ file.write(
3869
+ " Speeds: CAS(LO/HI) Mach Mass Levels [kg] Temperature: ISA%s\n"
3870
+ % (ISA)
3871
+ )
3872
+ file.write(
3873
+ " climb - %3d/%3d %4.2f low - %.0f\n"
3874
+ % (V1cl, V2cl, Mcl, massList[0])
3875
+ )
3876
+ file.write(
3877
+ " cruise - %3d/%3d %4.2f nominal - %-6.0f Max Alt. [ft]:%7d\n"
3878
+ % (V1cr, V2cr, Mcr, massList[1], altitudeList[-1])
3879
+ )
3880
+ file.write(
3881
+ " descent - %3d/%3d %4.2f high - %0.f\n"
3882
+ % (V1des, V2des, Mdes, massList[2])
3883
+ )
3884
+ file.write(
3885
+ "==========================================================================================\n"
3886
+ )
3887
+ file.write(
3888
+ " FL | CRUISE | CLIMB | DESCENT \n"
3889
+ )
3890
+ file.write(
3891
+ " | TAS fuel | TAS ROCD fuel | TAS ROCD fuel \n"
3892
+ )
3893
+ file.write(
3894
+ " | [kts] [kg/min] | [kts] [fpm] [kg/min] | [kts] [fpm] [kg/min]\n"
3895
+ )
3896
+ file.write(
3897
+ " | lo nom hi | lo nom hi nom | nom nom \n"
3898
+ )
3899
+ file.write(
3900
+ "==========================================================================================\n"
3901
+ )
3902
+
3903
+ # replace NAN values by 0 for printing purposes
3904
+ CLList = Nan2Zero(CLList)
3905
+ DESList = Nan2Zero(DESList)
3906
+
3907
+ for k in range(0, len(altitudeList)):
3908
+ FL = proper_round(altitudeList[k] / 100)
3909
+ if FL < 30:
3910
+ file.write(
3911
+ "%3.0f | | %3.0f %5.0f %5.0f %5.0f %5.1f | %3.0f %5.0f %5.1f \n"
3912
+ % (
3913
+ FL,
3914
+ CLList[0][k],
3915
+ CLList[1][k],
3916
+ CLList[2][k],
3917
+ CLList[3][k],
3918
+ CLList[4][k],
3919
+ DESList[0][k],
3920
+ DESList[1][k],
3921
+ DESList[2][k],
3922
+ )
3923
+ )
3924
+ else:
3925
+ file.write(
3926
+ "%3.0f | %3.0f %5.1f %5.1f %5.1f | %3.0f %5.0f %5.0f %5.0f %5.1f | %3.0f %5.0f %5.1f \n"
3927
+ % (
3928
+ FL,
3929
+ CRList[0][k],
3930
+ CRList[1][k],
3931
+ CRList[2][k],
3932
+ CRList[3][k],
3933
+ CLList[0][k],
3934
+ CLList[1][k],
3935
+ CLList[2][k],
3936
+ CLList[3][k],
3937
+ CLList[4][k],
3938
+ DESList[0][k],
3939
+ DESList[1][k],
3940
+ DESList[2][k],
3941
+ )
3942
+ )
3943
+ file.write(
3944
+ " | | | \n"
3945
+ )
3946
+
3947
+ file.write(
3948
+ "==========================================================================================\n"
3949
+ )
3950
+
3951
+ def PTF_cruise(self, massList, altitudeList, DeltaTemp):
3952
+ """This function calculates the BADA3 PTF data in CRUISE
3953
+
3954
+ :param massList: list of aircraft mass [kg]
3955
+ :param altitudeList: aircraft altitude list [ft]
3956
+ :param DeltaTemp: deviation from ISA temperature [K]
3957
+ :type massList: list.
3958
+ :type altitudeList: list of int.
3959
+ :type DeltaTemp: float.
3960
+ :returns: list of PTF CRUISE data [-]
3961
+ :rtype: list
3962
+ """
3963
+
3964
+ TAS_CR_complet = []
3965
+ FF_CR_LO_complet = []
3966
+ FF_CR_NOM_complet = []
3967
+ FF_CR_HI_complet = []
3968
+
3969
+ phase = "cr"
3970
+ massNominal = massList[1]
3971
+
3972
+ Vcr1 = self.AC.V1[phase]
3973
+ Vcr2 = self.AC.V2[phase]
3974
+ Mcr = self.AC.M[phase]
3975
+
3976
+ Vcr1 = min(Vcr1, conv.kt2ms(250))
3977
+
3978
+ for h in altitudeList:
3979
+ H_m = conv.ft2m(h) # altitude [m]
3980
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
3981
+ [cas, speedUpdated] = self.ARPM.cruiseSpeed(
3982
+ theta=theta,
3983
+ delta=delta,
3984
+ h=H_m,
3985
+ mass=massNominal,
3986
+ DeltaTemp=DeltaTemp,
3987
+ speedSchedule_default=[Vcr1, Vcr2, Mcr],
3988
+ applyLimits=False,
3989
+ )
3990
+ tas_nominal = atm.cas2Tas(cas=cas, delta=delta, sigma=sigma)
3991
+ FL = h / 100
3992
+ ff = []
3993
+ for mass in massList:
3994
+ [cas, speedUpdated] = self.ARPM.cruiseSpeed(
3995
+ theta=theta,
3996
+ delta=delta,
3997
+ h=H_m,
3998
+ mass=mass,
3999
+ DeltaTemp=DeltaTemp,
4000
+ speedSchedule_default=[Vcr1, Vcr2, Mcr],
4001
+ applyLimits=False,
4002
+ )
4003
+ tas = atm.cas2Tas(cas=cas, delta=delta, sigma=sigma)
4004
+ CL = self.CL(tas=tas, sigma=sigma, mass=mass)
4005
+ CD = self.CD(CL=CL, config="CR")
4006
+ Drag = self.D(tas=tas, sigma=sigma, CD=CD)
4007
+ Thrust = Drag
4008
+ ff.append(self.ff(flightPhase="Cruise", v=tas, h=H_m, T=Thrust) * 60)
4009
+
4010
+ TAS_CR_complet.append(conv.ms2kt(tas_nominal))
4011
+ FF_CR_LO_complet.append(ff[0])
4012
+ FF_CR_NOM_complet.append(ff[1])
4013
+ FF_CR_HI_complet.append(ff[2])
4014
+
4015
+ CRList = [TAS_CR_complet, FF_CR_LO_complet, FF_CR_NOM_complet, FF_CR_HI_complet]
4016
+
4017
+ return CRList
4018
+
4019
+ def PTF_climb(self, massList, altitudeList, DeltaTemp):
4020
+ """This function calculates the BADA3 PTF data in CLIMB
4021
+
4022
+ :param massList: list of aircraft mass [kg]
4023
+ :param altitudeList: aircraft altitude list [ft]
4024
+ :param DeltaTemp: deviation from ISA temperature [K]
4025
+ :type massList: list.
4026
+ :type altitudeList: list of int.
4027
+ :type DeltaTemp: float.
4028
+ :returns: list of PTF CLIMB data [-]
4029
+ :rtype: list
4030
+ """
4031
+
4032
+ TAS_CL_complet = []
4033
+ ROCD_CL_LO_complet = []
4034
+ ROCD_CL_NOM_complet = []
4035
+ ROCD_CL_HI_complet = []
4036
+ FF_CL_NOM_complet = []
4037
+
4038
+ phase = "cl"
4039
+ massNominal = massList[1]
4040
+
4041
+ Vcl1 = self.AC.V1[phase]
4042
+ Vcl2 = self.AC.V2[phase]
4043
+ Mcl = self.AC.M[phase]
4044
+
4045
+ Vcl1 = min(Vcl1, conv.kt2ms(250))
4046
+ crossAlt = atm.crossOver(cas=Vcl2, Mach=Mcl)
4047
+
4048
+ for h in altitudeList:
4049
+ H_m = conv.ft2m(h) # altitude [m]
4050
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
4051
+ FL = h / 100
4052
+
4053
+ ROC = []
4054
+ tas_list = []
4055
+ ff_list = []
4056
+ for mass in massList:
4057
+ [cas, speedUpdated] = self.ARPM.climbSpeed(
4058
+ theta=theta,
4059
+ delta=delta,
4060
+ h=H_m,
4061
+ mass=mass,
4062
+ DeltaTemp=DeltaTemp,
4063
+ speedSchedule_default=[Vcl1, Vcl2, Mcl],
4064
+ applyLimits=False,
4065
+ )
4066
+ tas = atm.cas2Tas(cas=cas, delta=delta, sigma=sigma)
4067
+ M = atm.tas2Mach(v=tas, theta=theta)
4068
+ CL = self.CL(tas=tas, sigma=sigma, mass=mass)
4069
+ config = self.flightEnvelope.getConfig(
4070
+ h=H_m, phase="Climb", v=cas, mass=massNominal, DeltaTemp=DeltaTemp
4071
+ )
4072
+ CD = self.CD(CL=CL, config=config)
4073
+ Drag = self.D(tas=tas, sigma=sigma, CD=CD)
4074
+ Thrust = self.Thrust(
4075
+ rating="MCMB", v=tas, h=H_m, config=config, DeltaTemp=DeltaTemp
4076
+ )
4077
+ ff = self.ff(flightPhase="Climb", v=tas, h=H_m, T=Thrust) * 60
4078
+
4079
+ if H_m < crossAlt:
4080
+ ESF = self.esf(
4081
+ h=H_m, flightEvolution="constCAS", M=M, DeltaTemp=DeltaTemp
4082
+ )
4083
+ else:
4084
+ ESF = self.esf(
4085
+ h=H_m, flightEvolution="constM", M=M, DeltaTemp=DeltaTemp
4086
+ )
4087
+
4088
+ # I think this should use all config, not just for nominal weight
4089
+ ROC_val = (
4090
+ conv.m2ft(
4091
+ self.ROCD(
4092
+ h=H_m,
4093
+ T=Thrust,
4094
+ D=Drag,
4095
+ v=tas,
4096
+ mass=mass,
4097
+ ESF=ESF,
4098
+ DeltaTemp=DeltaTemp,
4099
+ reducedPower=True,
4100
+ )
4101
+ )
4102
+ * 60
4103
+ )
4104
+
4105
+ if ROC_val < 0:
4106
+ ROC_val = float("Nan")
4107
+
4108
+ ROC.append(ROC_val)
4109
+ tas_list.append(tas)
4110
+ ff_list.append(ff)
4111
+
4112
+ TAS_CL_complet.append(conv.ms2kt(tas_list[1]))
4113
+ ROCD_CL_LO_complet.append(ROC[0])
4114
+ ROCD_CL_NOM_complet.append(ROC[1])
4115
+ ROCD_CL_HI_complet.append(ROC[2])
4116
+ FF_CL_NOM_complet.append(ff_list[1])
4117
+
4118
+ CLList = [
4119
+ TAS_CL_complet,
4120
+ ROCD_CL_LO_complet,
4121
+ ROCD_CL_NOM_complet,
4122
+ ROCD_CL_HI_complet,
4123
+ FF_CL_NOM_complet,
4124
+ ]
4125
+
4126
+ return CLList
4127
+
4128
+ def PTF_descent(self, massList, altitudeList, DeltaTemp):
4129
+ """This function calculates the BADA3 PTF data in DESCENT
4130
+
4131
+ :param massList: list of aircraft mass [kg]
4132
+ :param altitudeList: aircraft altitude list [ft]
4133
+ :param DeltaTemp: deviation from ISA temperature [K]
4134
+ :type massList: list.
4135
+ :type altitudeList: list of int.
4136
+ :type DeltaTemp: float.
4137
+ :returns: list of PTF DESCENT data [-]
4138
+ :rtype: list
4139
+ """
4140
+
4141
+ TAS_DES_complet = []
4142
+ ROCD_DES_NOM_complet = []
4143
+ FF_DES_NOM_complet = []
4144
+
4145
+ phase = "des"
4146
+ massNominal = massList[1]
4147
+
4148
+ Vdes1 = self.AC.V1[phase]
4149
+ Vdes2 = self.AC.V2[phase]
4150
+ Mdes = self.AC.M[phase]
4151
+
4152
+ Vdes1 = min(Vdes1, conv.kt2ms(250))
4153
+ crossAlt = atm.crossOver(cas=Vdes2, Mach=Mdes)
4154
+
4155
+ for h in altitudeList:
4156
+ H_m = conv.ft2m(h) # altitude [m]
4157
+ [theta, delta, sigma] = atm.atmosphereProperties(h=H_m, DeltaTemp=DeltaTemp)
4158
+ [cas, speedUpdated] = self.ARPM.descentSpeed(
4159
+ theta=theta,
4160
+ delta=delta,
4161
+ h=H_m,
4162
+ mass=massNominal,
4163
+ DeltaTemp=DeltaTemp,
4164
+ speedSchedule_default=[Vdes1, Vdes2, Mdes],
4165
+ applyLimits=False,
4166
+ )
4167
+ tas_nominal = atm.cas2Tas(cas=cas, delta=delta, sigma=sigma)
4168
+ M = atm.tas2Mach(v=tas_nominal, theta=theta)
4169
+ FL = h / 100
4170
+
4171
+ config = self.flightEnvelope.getConfig(
4172
+ h=H_m, phase="Descent", v=cas, mass=massNominal, DeltaTemp=DeltaTemp
4173
+ )
4174
+
4175
+ CL = self.CL(tas=tas_nominal, sigma=sigma, mass=massNominal)
4176
+ CD = self.CD(CL=CL, config=config)
4177
+ Drag = self.D(tas=tas_nominal, sigma=sigma, CD=CD)
4178
+
4179
+ if self.AC.engineType == "PISTON" or self.AC.engineType == "ELECTRIC":
4180
+ # PISTON and ELECTRIC uses LIDL throughout the whole descent phase
4181
+ Thrust_nominal = self.Thrust(
4182
+ rating="LIDL",
4183
+ v=tas_nominal,
4184
+ h=H_m,
4185
+ config="CR",
4186
+ DeltaTemp=DeltaTemp,
4187
+ )
4188
+ ff_nominal = (
4189
+ self.ff(
4190
+ flightPhase="Descent",
4191
+ v=tas_nominal,
4192
+ h=H_m,
4193
+ T=Thrust_nominal,
4194
+ config="CR",
4195
+ adapted=False,
4196
+ )
4197
+ * 60
4198
+ )
4199
+ else:
4200
+ Thrust_nominal = self.Thrust(
4201
+ rating="LIDL",
4202
+ v=tas_nominal,
4203
+ h=H_m,
4204
+ config=config,
4205
+ DeltaTemp=DeltaTemp,
4206
+ )
4207
+ ff_nominal = (
4208
+ self.ff(
4209
+ flightPhase="Descent",
4210
+ v=tas_nominal,
4211
+ h=H_m,
4212
+ T=Thrust_nominal,
4213
+ config=config,
4214
+ adapted=False,
4215
+ )
4216
+ * 60
4217
+ )
4218
+
4219
+ if H_m < crossAlt:
4220
+ ESF = self.esf(
4221
+ h=H_m, flightEvolution="constCAS", M=M, DeltaTemp=DeltaTemp
4222
+ )
4223
+ else:
4224
+ ESF = self.esf(
4225
+ h=H_m, flightEvolution="constM", M=M, DeltaTemp=DeltaTemp
4226
+ )
4227
+
4228
+ ROCD = -1 * (
4229
+ conv.m2ft(
4230
+ self.ROCD(
4231
+ h=H_m,
4232
+ T=Thrust_nominal,
4233
+ D=Drag,
4234
+ v=tas_nominal,
4235
+ mass=massNominal,
4236
+ ESF=ESF,
4237
+ DeltaTemp=DeltaTemp,
4238
+ )
4239
+ )
4240
+ * 60
4241
+ )
4242
+
4243
+ TAS_DES_complet.append(conv.ms2kt(tas_nominal))
4244
+ ROCD_DES_NOM_complet.append(ROCD)
4245
+ FF_DES_NOM_complet.append(ff_nominal)
4246
+
4247
+ DESList = [TAS_DES_complet, ROCD_DES_NOM_complet, FF_DES_NOM_complet]
4248
+
4249
+ return DESList
4250
+
4251
+
4252
+ class Bada3Aircraft(BADA3):
4253
+ """This class implements the BADA3 performance model following the BADA3 manual.
4254
+
4255
+ :param filePath: path to the BADA3 ascii formatted file.
4256
+ :param acName: ICAO aircraft designation
4257
+ :type filePath: str.
4258
+ :type acName: str
4259
+ """
4260
+
4261
+ def __init__(self, badaVersion, acName, filePath=None, allData=None):
4262
+ super().__init__(self)
4263
+
4264
+ self.APFavailable = False
4265
+ self.OPFavailable = False
4266
+ self.ACModelAvailable = False
4267
+ self.ACinSynonymFile = False
4268
+
4269
+ self.BADAFamilyName = "BADA3"
4270
+ self.BADAFamily = BadaFamily(BADA3=True)
4271
+ self.BADAVersion = badaVersion
4272
+
4273
+ if filePath == None:
4274
+ self.filePath = configuration.getAircraftPath()
4275
+ else:
4276
+ self.filePath = filePath
4277
+
4278
+ # check if the aircraft is in the allData dataframe data
4279
+ if allData is not None and acName in allData["acName"].values:
4280
+ filtered_df = allData[allData["acName"] == acName]
4281
+
4282
+ self.acName = Parser.safe_get(filtered_df, "acName", None)
4283
+ self.xmlFiles = Parser.safe_get(filtered_df, "xmlFiles", None)
4284
+
4285
+ self.modificationDateOPF = Parser.safe_get(
4286
+ filtered_df, "modificationDateOPF", None
4287
+ )
4288
+ self.modificationDateAPF = Parser.safe_get(
4289
+ filtered_df, "modificationDateAPF", None
4290
+ )
4291
+
4292
+ self.ICAO = Parser.safe_get(filtered_df, "ICAO", None)
4293
+ self.numberOfEngines = Parser.safe_get(filtered_df, "numberOfEngines", None)
4294
+ self.engineType = Parser.safe_get(filtered_df, "engineType", None)
4295
+ self.engines = Parser.safe_get(filtered_df, "engines", None)
4296
+ self.WTC = Parser.safe_get(filtered_df, "WTC", None)
4297
+ self.mass = Parser.safe_get(filtered_df, "mass", None)
4298
+
4299
+ self.MTOW = Parser.safe_get(filtered_df, "MTOW", None)
4300
+ self.OEW = Parser.safe_get(filtered_df, "OEW", None)
4301
+ self.MPL = Parser.safe_get(filtered_df, "MPL", None)
4302
+ self.MREF = Parser.safe_get(filtered_df, "MREF", None)
4303
+ self.VMO = Parser.safe_get(filtered_df, "VMO", None)
4304
+ self.MMO = Parser.safe_get(filtered_df, "MMO", None)
4305
+ self.hmo = Parser.safe_get(filtered_df, "hmo", None)
4306
+ self.Hmax = Parser.safe_get(filtered_df, "Hmax", None)
4307
+ self.tempGrad = Parser.safe_get(filtered_df, "tempGrad", None)
4308
+
4309
+ self.S = Parser.safe_get(filtered_df, "S", None)
4310
+ self.Clbo = Parser.safe_get(filtered_df, "Clbo", None)
4311
+ self.k = Parser.safe_get(filtered_df, "k", None)
4312
+ self.Vstall = Parser.safe_get(filtered_df, "Vstall", None)
4313
+ self.CD0 = Parser.safe_get(filtered_df, "CD0", None)
4314
+ self.CD2 = Parser.safe_get(filtered_df, "CD2", None)
4315
+ self.HLids = Parser.safe_get(filtered_df, "HLids", None)
4316
+ self.Ct = Parser.safe_get(filtered_df, "Ct", None)
4317
+ self.CTdeslow = Parser.safe_get(filtered_df, "CTdeslow", None)
4318
+ self.CTdeshigh = Parser.safe_get(filtered_df, "CTdeshigh", None)
4319
+ self.CTdesapp = Parser.safe_get(filtered_df, "CTdesapp", None)
4320
+ self.CTdesld = Parser.safe_get(filtered_df, "CTdesld", None)
4321
+ self.HpDes = Parser.safe_get(filtered_df, "HpDes", None)
4322
+ self.Cf = Parser.safe_get(filtered_df, "Cf", None)
4323
+ self.CfDes = Parser.safe_get(filtered_df, "CfDes", None)
4324
+ self.CfCrz = Parser.safe_get(filtered_df, "CfCrz", None)
4325
+ self.TOL = Parser.safe_get(filtered_df, "TOL", None)
4326
+ self.LDL = Parser.safe_get(filtered_df, "LDL", None)
4327
+ self.span = Parser.safe_get(filtered_df, "span", None)
4328
+ self.length = Parser.safe_get(filtered_df, "length", None)
4329
+
4330
+ self.V1 = Parser.safe_get(filtered_df, "V1", None)
4331
+ self.V2 = Parser.safe_get(filtered_df, "V2", None)
4332
+ self.M = Parser.safe_get(filtered_df, "M", None)
4333
+
4334
+ self.GPFdata = Parser.safe_get(filtered_df, "GPFdata", None)
4335
+
4336
+ self.drone = Parser.safe_get(filtered_df, "drone", None)
4337
+
4338
+ self.DeltaCD = Parser.safe_get(filtered_df, "DeltaCD", None)
4339
+ self.speedSchedule = Parser.safe_get(filtered_df, "speedSchedule", None)
4340
+ self.aeroConfig = Parser.safe_get(filtered_df, "aeroConfig", None)
4341
+
4342
+ self.flightEnvelope = FlightEnvelope(self)
4343
+ self.ARPM = ARPM(self)
4344
+ self.PTD = PTD(self)
4345
+ self.PTF = PTF(self)
4346
+
4347
+ else:
4348
+ # read BADA3 GPF file
4349
+ GPFDataFrame = Parser.parseGPF(self.filePath, badaVersion)
4350
+
4351
+ # check if SYNONYM file exist
4352
+ synonymFile = os.path.join(
4353
+ self.filePath, "BADA3", badaVersion, "SYNONYM.NEW"
4354
+ )
4355
+ synonymFileXML = os.path.join(
4356
+ self.filePath, "BADA3", badaVersion, "SYNONYM.xml"
4357
+ )
4358
+ if os.path.isfile(synonymFile) or os.path.isfile(synonymFileXML):
4359
+ self.synonymFileAvailable = True
4360
+
4361
+ self.SearchedACName = Parser.parseSynonym(
4362
+ self.filePath, badaVersion, acName
4363
+ )
4364
+
4365
+ if self.SearchedACName == None:
4366
+ # look for file name directly, which consists of added "_" at the end of file
4367
+ fileName = acName
4368
+ while len(fileName) < 6:
4369
+ fileName += "_"
4370
+ self.SearchedACName = fileName
4371
+ else:
4372
+ self.ACinSynonymFile = True
4373
+ else:
4374
+ # if doesn't exist - look for full name based on acName (may not be ICAO designator)
4375
+ self.SearchedACName = acName
4376
+
4377
+ # look for either found synonym or original full BADA3 model name designator
4378
+ if self.SearchedACName is not None:
4379
+ # check for existence of OPF and APF files
4380
+ OPFfile = (
4381
+ os.path.join(
4382
+ self.filePath, "BADA3", badaVersion, self.SearchedACName
4383
+ )
4384
+ + ".OPF"
4385
+ )
4386
+ APFfile = (
4387
+ os.path.join(
4388
+ self.filePath, "BADA3", badaVersion, self.SearchedACName
4389
+ )
4390
+ + ".APF"
4391
+ )
4392
+ if os.path.isfile(OPFfile):
4393
+ self.OPFavailable = True
4394
+ if os.path.isfile(APFfile):
4395
+ self.APFavailable = True
4396
+
4397
+ if self.OPFavailable and self.APFavailable:
4398
+ self.ACModelAvailable = True
4399
+
4400
+ OPFDataFrame = Parser.parseOPF(
4401
+ self.filePath, badaVersion, self.SearchedACName
4402
+ )
4403
+ APFDataFrame = Parser.parseAPF(
4404
+ self.filePath, badaVersion, self.SearchedACName
4405
+ )
4406
+
4407
+ OPF_APF_combined_df = Parser.combineOPF_APF(
4408
+ OPFDataFrame, APFDataFrame
4409
+ )
4410
+ combined_df = Parser.combineACDATA_GPF(
4411
+ OPF_APF_combined_df, GPFDataFrame
4412
+ )
4413
+
4414
+ self.acName = Parser.safe_get(combined_df, "acName", None)
4415
+ self.xmlFiles = Parser.safe_get(combined_df, "xmlFiles", None)
4416
+
4417
+ self.modificationDateOPF = Parser.safe_get(
4418
+ combined_df, "modificationDateOPF", None
4419
+ )
4420
+ self.modificationDateAPF = Parser.safe_get(
4421
+ combined_df, "modificationDateAPF", None
4422
+ )
4423
+
4424
+ self.ICAO = Parser.safe_get(combined_df, "ICAO", None)
4425
+ self.numberOfEngines = Parser.safe_get(
4426
+ combined_df, "numberOfEngines", None
4427
+ )
4428
+ self.engineType = Parser.safe_get(combined_df, "engineType", None)
4429
+ self.engines = Parser.safe_get(combined_df, "engines", None)
4430
+ self.WTC = Parser.safe_get(combined_df, "WTC", None)
4431
+ self.mass = Parser.safe_get(combined_df, "mass", None)
4432
+
4433
+ self.MTOW = Parser.safe_get(combined_df, "MTOW", None)
4434
+ self.OEW = Parser.safe_get(combined_df, "OEW", None)
4435
+ self.MPL = Parser.safe_get(combined_df, "MPL", None)
4436
+ self.MREF = Parser.safe_get(combined_df, "MREF", None)
4437
+ self.VMO = Parser.safe_get(combined_df, "VMO", None)
4438
+ self.MMO = Parser.safe_get(combined_df, "MMO", None)
4439
+ self.hmo = Parser.safe_get(combined_df, "hmo", None)
4440
+ self.Hmax = Parser.safe_get(combined_df, "Hmax", None)
4441
+ self.tempGrad = Parser.safe_get(combined_df, "tempGrad", None)
4442
+
4443
+ self.S = Parser.safe_get(combined_df, "S", None)
4444
+ self.Clbo = Parser.safe_get(combined_df, "Clbo", None)
4445
+ self.k = Parser.safe_get(combined_df, "k", None)
4446
+ self.Vstall = Parser.safe_get(combined_df, "Vstall", None)
4447
+ self.CD0 = Parser.safe_get(combined_df, "CD0", None)
4448
+ self.CD2 = Parser.safe_get(combined_df, "CD2", None)
4449
+ self.HLids = Parser.safe_get(combined_df, "HLids", None)
4450
+ self.Ct = Parser.safe_get(combined_df, "Ct", None)
4451
+ self.CTdeslow = Parser.safe_get(combined_df, "CTdeslow", None)
4452
+ self.CTdeshigh = Parser.safe_get(combined_df, "CTdeshigh", None)
4453
+ self.CTdesapp = Parser.safe_get(combined_df, "CTdesapp", None)
4454
+ self.CTdesld = Parser.safe_get(combined_df, "CTdesld", None)
4455
+ self.HpDes = Parser.safe_get(combined_df, "HpDes", None)
4456
+ self.Cf = Parser.safe_get(combined_df, "Cf", None)
4457
+ self.CfDes = Parser.safe_get(combined_df, "CfDes", None)
4458
+ self.CfCrz = Parser.safe_get(combined_df, "CfCrz", None)
4459
+ self.TOL = Parser.safe_get(combined_df, "TOL", None)
4460
+ self.LDL = Parser.safe_get(combined_df, "LDL", None)
4461
+ self.span = Parser.safe_get(combined_df, "span", None)
4462
+ self.length = Parser.safe_get(combined_df, "length", None)
4463
+
4464
+ self.V1 = Parser.safe_get(combined_df, "V1", None)
4465
+ self.V2 = Parser.safe_get(combined_df, "V2", None)
4466
+ self.M = Parser.safe_get(combined_df, "M", None)
4467
+
4468
+ self.GPFdata = Parser.safe_get(combined_df, "GPFdata", None)
4469
+
4470
+ self.drone = Parser.safe_get(combined_df, "drone", None)
4471
+
4472
+ self.DeltaCD = Parser.safe_get(combined_df, "DeltaCD", None)
4473
+ self.speedSchedule = Parser.safe_get(
4474
+ combined_df, "speedSchedule", None
4475
+ )
4476
+ self.aeroConfig = Parser.safe_get(combined_df, "aeroConfig", None)
4477
+
4478
+ self.flightEnvelope = FlightEnvelope(self)
4479
+ self.ARPM = ARPM(self)
4480
+ self.PTD = PTD(self)
4481
+ self.PTF = PTF(self)
4482
+
4483
+ elif not self.OPFavailable and not self.APFavailable:
4484
+ # search for xml files
4485
+
4486
+ XMLDataFrame = Parser.parseXML(
4487
+ self.filePath, badaVersion, self.SearchedACName
4488
+ )
4489
+
4490
+ combined_df = Parser.combineACDATA_GPF(XMLDataFrame, GPFDataFrame)
4491
+
4492
+ self.acName = Parser.safe_get(combined_df, "acName", None)
4493
+ self.xmlFiles = Parser.safe_get(combined_df, "xmlFiles", None)
4494
+
4495
+ self.modificationDateOPF = Parser.safe_get(
4496
+ combined_df, "modificationDateOPF", None
4497
+ )
4498
+ self.modificationDateAPF = Parser.safe_get(
4499
+ combined_df, "modificationDateAPF", None
4500
+ )
4501
+
4502
+ self.ICAO = Parser.safe_get(combined_df, "ICAO", None)
4503
+ self.numberOfEngines = Parser.safe_get(
4504
+ combined_df, "numberOfEngines", None
4505
+ )
4506
+ self.engineType = Parser.safe_get(combined_df, "engineType", None)
4507
+ self.engines = Parser.safe_get(combined_df, "engines", None)
4508
+ self.WTC = Parser.safe_get(combined_df, "WTC", None)
4509
+ self.mass = Parser.safe_get(combined_df, "mass", None)
4510
+
4511
+ self.MTOW = Parser.safe_get(combined_df, "MTOW", None)
4512
+ self.OEW = Parser.safe_get(combined_df, "OEW", None)
4513
+ self.MPL = Parser.safe_get(combined_df, "MPL", None)
4514
+ self.MREF = Parser.safe_get(combined_df, "MREF", None)
4515
+ self.VMO = Parser.safe_get(combined_df, "VMO", None)
4516
+ self.MMO = Parser.safe_get(combined_df, "MMO", None)
4517
+ self.hmo = Parser.safe_get(combined_df, "hmo", None)
4518
+ self.Hmax = Parser.safe_get(combined_df, "Hmax", None)
4519
+ self.tempGrad = Parser.safe_get(combined_df, "tempGrad", None)
4520
+
4521
+ self.S = Parser.safe_get(combined_df, "S", None)
4522
+ self.Clbo = Parser.safe_get(combined_df, "Clbo", None)
4523
+ self.k = Parser.safe_get(combined_df, "k", None)
4524
+ self.Vstall = Parser.safe_get(combined_df, "Vstall", None)
4525
+ self.CD0 = Parser.safe_get(combined_df, "CD0", None)
4526
+ self.CD2 = Parser.safe_get(combined_df, "CD2", None)
4527
+ self.HLids = Parser.safe_get(combined_df, "HLids", None)
4528
+ self.Ct = Parser.safe_get(combined_df, "Ct", None)
4529
+ self.CTdeslow = Parser.safe_get(combined_df, "CTdeslow", None)
4530
+ self.CTdeshigh = Parser.safe_get(combined_df, "CTdeshigh", None)
4531
+ self.CTdesapp = Parser.safe_get(combined_df, "CTdesapp", None)
4532
+ self.CTdesld = Parser.safe_get(combined_df, "CTdesld", None)
4533
+ self.HpDes = Parser.safe_get(combined_df, "HpDes", None)
4534
+ self.Cf = Parser.safe_get(combined_df, "Cf", None)
4535
+ self.CfDes = Parser.safe_get(combined_df, "CfDes", None)
4536
+ self.CfCrz = Parser.safe_get(combined_df, "CfCrz", None)
4537
+ self.TOL = Parser.safe_get(combined_df, "TOL", None)
4538
+ self.LDL = Parser.safe_get(combined_df, "LDL", None)
4539
+ self.span = Parser.safe_get(combined_df, "span", None)
4540
+ self.length = Parser.safe_get(combined_df, "length", None)
4541
+
4542
+ self.V1 = Parser.safe_get(combined_df, "V1", None)
4543
+ self.V2 = Parser.safe_get(combined_df, "V2", None)
4544
+ self.M = Parser.safe_get(combined_df, "M", None)
4545
+
4546
+ self.GPFdata = Parser.safe_get(combined_df, "GPFdata", None)
4547
+
4548
+ self.drone = Parser.safe_get(combined_df, "drone", None)
4549
+
4550
+ self.DeltaCD = Parser.safe_get(combined_df, "DeltaCD", None)
4551
+ self.speedSchedule = Parser.safe_get(
4552
+ combined_df, "speedSchedule", None
4553
+ )
4554
+ self.aeroConfig = Parser.safe_get(combined_df, "aeroConfig", None)
4555
+
4556
+ self.flightEnvelope = FlightEnvelope(self)
4557
+ self.ARPM = ARPM(self)
4558
+ self.PTD = PTD(self)
4559
+ self.PTF = PTF(self)
4560
+
4561
+ else:
4562
+ # AC name cannot be found
4563
+ raise ValueError(acName + " Cannot be found")
4564
+
4565
+ def __str__(self):
4566
+ return f"(BADA3, AC_name: {self.acName}, searched_AC_name: {self.SearchedACName}, model_ICAO: {self.ICAO}, ID: {id(self.AC)})"