-
Notifications
You must be signed in to change notification settings - Fork 0
/
flight.py
315 lines (274 loc) · 11 KB
/
flight.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
from math import radians, sin, cos, sqrt, atan2
from src.models.airport import Airport
from src.models.aircraft import Aircraft
import json
from typing import Any
class Flight:
"""
Represents a flight from a departure airport to
an arrival airport using a specific aircraft.
The class calculates various flight parameters based on the
provided ICAO codes of the departure and arrival airports,
as well as the aircraft. These parameters include distance,
block fuel, payload, and weights (ZFW, TOW, and LW).
Args:
dep_icao (str): ICAO code of the departure airport.
arr_icao (str): ICAO code of the arrival airport.
aircraft_icao (str): ICAO code of the
aircraft being used for the flight.
"""
def __init__(self, dep_icao: str, arr_icao: str, aircraft_icao: str):
self.aircraft = Aircraft(aircraft_icao)
self.aircraft_data = self.aircraft.data[self.aircraft.aircraft_icao]
self.dep_airport = Airport(dep_icao)
self.arr_airport = Airport(arr_icao)
self.distance_km: float = 0.0
self.block_fuel: float = 0.0
self.payload: int = 0
self.cargo: float = 0.0
self.passengers_count: int = 0
self.empty_weight: int = 0
self.estimated_zfw: int = 0
self.max_zfw: int = 0
self.estimated_tow: int = 0
self.max_tow: int = 0
self.estimated_lw: int = 0
self.max_lw: int = 0
self.calculate_flight_params()
def calculate_flight_params(self) -> None:
"""Calculates the flight parameters."""
self.distance_km = self.calculate_distance_km()
self.block_fuel = self.calculate_block_fuel()
self.payload = self.calculate_payload()
self.cargo = self.calculate_cargo()
self.estimated_zfw = self.calculate_zfw()
self.estimated_tow = self.calculate_tow()
self.estimated_lw = self.calculate_lw()
def _to_dict(self) -> dict[str, Any]:
"""Returns flight calculations as a dictionary."""
return {
"aircraft": self.aircraft.aircraft_icao,
"departure": {
"icao": self.dep_airport.icao_code,
"latitude": self.dep_airport.latitude,
"longitude": self.dep_airport.longitude,
},
"arrival": {
"icao": self.arr_airport.icao_code,
"latitude": self.arr_airport.latitude,
"longitude": self.arr_airport.longitude,
},
"parameters": {
"distance_km": int(self.distance_km),
"passengers_max": self.passengers_count,
"block_fuel_kg": int(self.block_fuel),
"payload_kg": int(self.payload),
"cargo_kg": int(self.cargo),
"zfw": {
"est": int(self.estimated_zfw),
"max": int(self.max_zfw)
},
"tow": {
"est": int(self.estimated_tow),
"max": int(self.max_tow)
},
"lw": {
"est": int(self.estimated_lw),
"max": int(self.max_lw)
},
},
}
def save_to_json(self) -> None:
"""Saves flight calculations to a JSON file."""
filename = (
f"route-{self.aircraft.aircraft_icao}"
+ f"-{self.dep_airport.icao_code}-"
+ f"to-{self.arr_airport.icao_code}.json"
)
try:
with open(filename, "w") as file:
json.dump(self._to_dict(), file, indent=4)
except IOError as e:
raise IOError(f"Error saving JSON file: {e}")
def print_flight_params(self) -> None:
"""Prints the flight parameters."""
print(
f"\nAircraft: {self.aircraft.aircraft_icao}",
f"\n{self.dep_airport.icao_code}"
+ f" lat:{self.dep_airport.latitude},"
+ f" lon:{self.dep_airport.longitude}",
f"\n{self.arr_airport.icao_code}"
+ f" lat:{self.arr_airport.latitude},"
+ f" lon:{self.arr_airport.longitude}",
f"\nDistance: {self.distance_km:.0f} km\n",
f"\nPassengers [max]: {self.passengers_count}",
f"\nBlock Fuel: {self.block_fuel:.0f} kg",
f"\nPayload: {self.payload} kg",
f"\nCargo: {self.cargo:.0f} kg\n",
f"\nZFW est:{self.estimated_zfw}, max:{self.max_zfw}",
f"\nTOW est:{self.estimated_tow}, max:{self.max_tow}",
f"\nLW est:{self.estimated_lw}, max:{self.max_lw}\n",
)
def _haversine_distance(
self, lat1: float, lon1: float, lat2: float, lon2: float
) -> float:
"""
Calculates the distance between
two points using the Haversine formula.
"""
if not (-90 <= lat1 <= 90 and -90 <= lat2 <= 90):
raise ValueError(
"Latitude must be between -90 and 90 degrees."
)
if not (-180 <= lon1 <= 180 and -180 <= lon2 <= 180):
raise ValueError(
"Longitude must be between -180 and 180 degrees."
)
R = 6371.0
(lat1_rad, lon1_rad, lat2_rad, lon2_rad) = map(
radians, [lat1, lon1, lat2, lon2]
)
dlat, dlon = lat2_rad - lat1_rad, lon2_rad - lon1_rad
a = (
sin(dlat / 2) ** 2
+ cos(lat1_rad) * cos(lat2_rad) * sin(dlon / 2) ** 2
)
c = 2 * atan2(sqrt(a), sqrt(1 - a))
return R * c
def _distance_100km(self) -> float:
"""
Calculates the distance normalized per 100 km.
"""
base_coefficient = 1.5
additional_coefficient = (self.distance_km // 100) * 0.3
total_coefficient = base_coefficient + additional_coefficient
if total_coefficient == 0:
raise ValueError("Total coefficient cannot be zero.")
return self.distance_km / 100 / total_coefficient
def calculate_block_fuel(self) -> float:
"""
Calculates the block fuel required for the flight.
"""
try:
fuel_on_100km = int(self.aircraft_data["FuelOn100km"]["MAX"])
distance_100km = self._distance_100km()
block_fuel = fuel_on_100km * distance_100km
return block_fuel
except ZeroDivisionError:
raise ValueError("Distance calculation resulted in zero.")
except KeyError:
raise ValueError("Fuel data for aircraft is missing.")
def calculate_distance_km(self) -> float:
"""
Calculates the distance between two airports.
"""
try:
distance_km = self._haversine_distance(
self.dep_airport.latitude,
self.dep_airport.longitude,
self.arr_airport.latitude,
self.arr_airport.longitude,
)
return distance_km
except TypeError:
raise ValueError(
"Invalid coordinates for departure or arrival airports."
)
def calculate_payload(self) -> int:
"""
Calculates the total payload on
board based on the number of passengers.
"""
try:
self.passengers_count = self.aircraft_data["Passengers"]["MAX"]
if not isinstance(
self.passengers_count, int
) or self.passengers_count < 0:
raise ValueError("Invalid passenger count data.")
passenger = 104
payload = self.passengers_count * passenger
return payload
except KeyError:
raise ValueError("Passenger data for aircraft is missing.")
def calculate_cargo(self) -> float:
"""
Calculates the total cargo weight
on board based on the number of passengers.
"""
try:
cargo_per_passenger = 3.5
cargo = self.payload * cargo_per_passenger / 14
return cargo
except ZeroDivisionError:
raise ValueError(
"Cargo calculation encountered division by zero."
)
def calculate_zfw(self) -> int:
"""
Calculates the estimated Zero Fuel Weight (ZFW).
ZFW is the total weight of the aircraft without any fuel on board.
It is calculated as the sum of the empty weight and the payload.
Returns:
int: Estimated ZFW in kilograms.
"""
try:
self.empty_weight = self.aircraft_data["ZWF"]["EMP"]
self.max_zfw = self.aircraft_data["ZWF"]["MAX"]
if (
not isinstance(self.empty_weight, int)
or not isinstance(self.max_zfw, int)
or self.empty_weight < 0
or self.max_zfw < 0
):
raise ValueError("Invalid count data.")
estimated_zfw = self.payload + self.empty_weight
if estimated_zfw > self.max_zfw:
raise ValueError(
"Estimated ZFW exceeds maximum allowable ZFW."
)
return estimated_zfw
except KeyError:
raise ValueError("ZFW data for aircraft is missing.")
def calculate_tow(self) -> int:
"""
Calculates the estimated Takeoff Weight (TOW).
TOW is the total weight of the aircraft at the time of takeoff.
It includes the empty weight, block fuel, and payload.
Returns:
int: Estimated TOW in kilograms.
"""
try:
self.max_tow = self.aircraft_data["TOW"]["MAX"]
if not isinstance(self.max_tow, int) or self.max_tow < 0:
raise ValueError("Invalid count data.")
estimated_tow = self.empty_weight + self.block_fuel + self.payload
estimated_tow = int(estimated_tow)
if estimated_tow > self.max_tow:
raise ValueError(
"Estimated TOW exceeds maximum allowable TOW."
)
return estimated_tow
except KeyError:
raise ValueError("TOW data for aircraft is missing.")
def calculate_lw(self) -> int:
"""
Calculates the estimated Landing Weight (LW).
LW is the total weight of the aircraft upon landing.
It is calculated as the estimated TOW minus the block fuel
and includes the cargo weight.
Returns:
int: Estimated LW in kilograms.
"""
try:
self.max_lw = self.aircraft_data["LW"]["MAX"]
if not isinstance(self.max_lw, int) or self.max_lw < 0:
raise ValueError("Invalid count data.")
estimated_lw = self.estimated_tow - self.block_fuel + self.cargo
estimated_lw = int(estimated_lw)
if estimated_lw > self.max_lw:
raise ValueError(
"Estimated LW exceeds maximum allowable LW."
)
return estimated_lw
except KeyError:
raise ValueError("LW data for aircraft is missing.")