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.
- pyBADA/TCL.py +8731 -0
- pyBADA/__init__.py +0 -0
- pyBADA/aircraft/BADA3/DUMMY/BADA.GPF +113 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTD +139 -0
- pyBADA/aircraft/BADA3/DUMMY/BZJT__.PTF +73 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.PTD +71 -0
- pyBADA/aircraft/BADA3/DUMMY/GA____.PTF +39 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.PTD +131 -0
- pyBADA/aircraft/BADA3/DUMMY/J2H___.PTF +69 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.PTD +123 -0
- pyBADA/aircraft/BADA3/DUMMY/J2M___.PTF +65 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.PTD +139 -0
- pyBADA/aircraft/BADA3/DUMMY/J4H___.PTF +73 -0
- pyBADA/aircraft/BADA3/DUMMY/ReleaseSummary +35 -0
- pyBADA/aircraft/BADA3/DUMMY/SYNONYM.NEW +155 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.APF +25 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.OPF +61 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTD +99 -0
- pyBADA/aircraft/BADA3/DUMMY/TP2M__.PTF +53 -0
- pyBADA/aircraft/BADA4/DUMMY/ACM_BADA4.xsd +556 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.ATF +106 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST.xml +244 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTD +182 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA+20.PTF +41 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTD +182 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-PST/Dummy-PST_ISA.PTF +41 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.ATF +191 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP.xml +540 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTD +218 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA+20.PTF +49 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTD +218 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/Dummy-TBP_ISA.PTF +49 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/LRC.dat +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MEC.dat +58 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/MRC.dat +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TBP/OPTALT.dat +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.ATF +204 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN.xml +648 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA+20.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/Dummy-TWIN_ISA.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/ECON.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/LRC.OPT +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MEC.OPT +58 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/MRC.OPT +38 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN/OPTALT.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.ATF +238 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus.xml +737 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA+20.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTD +281 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/Dummy-TWIN-plus_ISA.PTF +63 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/ECON.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/LRC.OPT +36 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MEC.OPT +56 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/MRC.OPT +36 -0
- pyBADA/aircraft/BADA4/DUMMY/Dummy-TWIN-plus/OPTALT.OPT +37 -0
- pyBADA/aircraft/BADA4/DUMMY/GPF.xml +130 -0
- pyBADA/aircraft/BADA4/DUMMY/GPF_BADA4.xsd +75 -0
- pyBADA/aircraft/BADA4/DUMMY/aircraft_model_default.xml +311 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.ATF +97 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH.xml +82 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTD +632 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA+20.PTF +71 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTD +632 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/DUMH_ISA.PTF +71 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/LRC.OPT +142 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/MEC.OPT +142 -0
- pyBADA/aircraft/BADAH/DUMMY/DUMH/MRC.OPT +142 -0
- pyBADA/aircraft.py +414 -0
- pyBADA/atmosphere.py +345 -0
- pyBADA/bada3.py +4566 -0
- pyBADA/bada4.py +5327 -0
- pyBADA/badaE.py +3317 -0
- pyBADA/badaH.py +3632 -0
- pyBADA/configuration.py +98 -0
- pyBADA/constants.py +64 -0
- pyBADA/conversions.py +197 -0
- pyBADA/data/magneticDeclinationGridData.json +247067 -0
- pyBADA/flightTrajectory.py +929 -0
- pyBADA/geodesic.py +760 -0
- pyBADA/magnetic.py +119 -0
- pyBADA/trajectoryPrediction.py +175 -0
- pybada-0.1.0.dist-info/METADATA +57 -0
- pybada-0.1.0.dist-info/RECORD +99 -0
- pybada-0.1.0.dist-info/WHEEL +4 -0
- pybada-0.1.0.dist-info/licenses/AUTHORS +2 -0
- 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)})"
|