from openap import FuelFlow
# create a fuel flow model for A320
= FuelFlow(ac='A320')
fuelflow
# estimate fuel flow during cruise
= fuelflow.enroute(mass=60000, tas=230, alt=32000)
FF
# estimate fuel flow at climb, with vertical speed (feet/min)
= fuelflow.enroute(mass=60000, tas=200, alt=20000, vs=1000)
FF
# estimate fuel flow at with a given thrust (e.g., derived from drag model)
= fuelflow.at_thrust(acthr=50000, alt=30000)
FF
# estimate fuel flow at takeoff
= fuelflow.takeoff(tas=100, alt=0, throttle=1) FF
7 🔥 Fuel and Emission
OpenAP provides estimations of fuel consumption and emissions based on actual flight trajectory data. The models are based on the following work:
- ICAO Aircraft Engine Emissions Databank link
- Polynomial models derived from the acropole model
7.1 Accuracy of the fuel model
The accuracy of the fuel estimation is greatly improved in the new OpenAP (v2), which is comparable to the BADA 3 fuel model. This is due to the better tuning using data-driven model, acropole, shared by @JarryGabriel.
In best cases, we see errors below 3%, and in the following A320 example shows an accuracy of 1% compared to the QAR data.
For the in-depth analysis of the accuracy affected by mass, wind, and other parameters for this flight, please see the example from the traffic library.
7.2 Basic usage of the fuel and emission modules
7.2.1 Compute aircraft fuel flow:
To estimate fuel flow, you need to provide the aircraft type (e.g., ‘A320’) through the openap.FuelFlow
object. The fuel flow model is based on: - aircraft’s mass (in kg
), - true airspeed (TAS, in kts
), - altitude (in ft
), - and vertical speed (optional, in ft/min
).
7.2.2 Compute aircraft emissions:
The emission model is based on the fuel flow and aircraft’s true airspeed (TAS) and altitude. The input fuel flow is in kg/s
The emissions include CO2, H2O, NOx, CO, and HC, with units in g/s
.
from openap import FuelFlow, Emission
= FuelFlow(ac="A320")
fuelflow = Emission(ac="A320")
emission
= 350
TAS = 30000
ALT
= fuelflow.enroute(mass=60000, tas=TAS, alt=ALT, vs=0) # kg/s
FF
= emission.co2(FF) # g/s
CO2 = emission.h2o(FF) # g/s
H2O = emission.nox(FF, tas=TAS, alt=ALT) # g/s
NOx = emission.co(FF, tas=TAS, alt=ALT) # g/s
CO = emission.hc(FF, tas=TAS, alt=ALT) # g/s HC
7.3 Estimate fuel and emission from flight data
In the following example, we estimate the fuel consumption for a given flight trajectory data obtained from the OpenSky Network. The sample data can be downloaded from https://github.com/junzis/openap/tree/master/examples.
The following code snippets show how to estimate fuel flow and emissions for this example flight trajectory data.
7.3.1 Data exploration
First, we need to import openap, pandas, and matplotlib libraries.
import pandas as pd
import openap
import matplotlib.pyplot as plt
We also need to define aircraft parameters and import data.
= 66300 # kg
mass_takeoff_assumed
= openap.FuelFlow("A319")
fuelflow
# Load the data
= pd.read_csv(
df "assets/data/flight_a319_opensky.csv",
=["timestamp"],
parse_dates={"icao24": str},
dtype
)
# Calculate seconds between each timestamp
= df.assign(d_ts=lambda d: d.timestamp.diff().dt.total_seconds().bfill()) df
Let’s see what are the features in this flight dataframe:
df
timestamp | icao24 | typecode | callsign | origin | destination | latitude | longitude | altitude | groundspeed | track | vertical_rate | d_ts | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2018-01-02 19:53:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 49.085861 | 2.349666 | 8200.0 | 255.0 | 327.804266 | 1920.0 | 60.0 |
1 | 2018-01-02 19:54:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 49.153427 | 2.310861 | 9475.0 | 290.0 | 9.722018 | 960.0 | 60.0 |
2 | 2018-01-02 19:55:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 49.233367 | 2.361121 | 9975.0 | 332.0 | 23.243919 | 640.0 | 60.0 |
3 | 2018-01-02 19:56:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 49.319916 | 2.418471 | 11225.0 | 353.0 | 23.517962 | 2752.0 | 60.0 |
4 | 2018-01-02 19:57:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 49.411652 | 2.478896 | 14175.0 | 368.0 | 23.566915 | 3584.0 | 60.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
111 | 2018-01-02 21:44:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 58.978659 | 17.706646 | 13250.0 | 343.0 | 24.471621 | -1408.0 | 60.0 |
112 | 2018-01-02 21:45:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 59.062826 | 17.781088 | 11800.0 | 326.0 | 24.443955 | -1344.0 | 60.0 |
113 | 2018-01-02 21:46:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 59.142609 | 17.852051 | 10400.0 | 307.0 | 24.397686 | -1216.0 | 60.0 |
114 | 2018-01-02 21:47:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 59.217286 | 17.918701 | 9125.0 | 291.0 | 24.541618 | -1216.0 | 60.0 |
115 | 2018-01-02 21:48:00+00:00 | 3946e9 | a319 | AFR91HL | LBG | BMA | 59.289505 | 17.990112 | 8075.0 | 291.0 | 28.500577 | -960.0 | 60.0 |
116 rows × 13 columns
Let’s plot the altitude profile of the flight. I will also make the plots more visually appealing.
from matplotlib import dates
import matplotlib
"font", size=11)
matplotlib.rc("font", family="Ubuntu")
matplotlib.rc("lines", linewidth=2, markersize=8)
matplotlib.rc("grid", color="darkgray", linestyle=":")
matplotlib.rc(
def format_ax(ax):
"%H:%M"))
ax.xaxis.set_major_formatter(dates.DateFormatter("right"].set_visible(False)
ax.spines["top"].set_visible(False)
ax.spines[-0.1, 1.03)
ax.yaxis.set_label_coords(0)
ax.yaxis.label.set_rotation("left")
ax.yaxis.label.set_ha(
ax.grid()
= plt.subplots(3, 1, figsize=(5, 5), sharex=True)
fig, (ax1, ax2, ax3)
ax1.plot(df.timestamp, df.altitude)
ax2.plot(df.timestamp, df.groundspeed)
ax3.plot(df.timestamp, df.vertical_rate)
"altitude (ft)")
ax1.set_ylabel("groundspeed (kts)")
ax2.set_ylabel("vertical rate (ft/min)")
ax3.set_ylabel(
for ax in (ax1, ax2, ax3):
format_ax(ax)
plt.tight_layout() plt.show()
7.3.2 Fuel flow calculation
Next, we iterate over the timestamp to calculate fuel flow and mass during the flight.
= mass_takeoff_assumed
mass_current
= []
fuelflow_every_step = []
fuel_every_step
for i, row in df.iterrows():
= fuelflow.enroute(
ff =mass_current,
mass=row.groundspeed,
tas=row.altitude,
alt=row.vertical_rate,
vs
)= ff * row.d_ts
fuel
fuelflow_every_step.append(ff)* row.d_ts)
fuel_every_step.append(ff -= fuel
mass_current
= df.assign(fuel_flow=fuelflow_every_step, fuel=fuel_every_step) df
Then, we can visualize the fuel flow during the flight.
=(7, 2))
plt.figure(figsize="tab:red")
plt.plot(df.timestamp, df.fuel_flow, color"fuel flow (kg/s)")
plt.ylabel(
format_ax(plt.gca()) plt.show()
With the new dataframe, we can calculate total fuel consumption by summing the fuel consumption at overall time steps:
= df.fuel.sum().astype(int)
total_fuel print(f"Total fuel: {total_fuel} kg")
Total fuel: 5052 kg
7.3.3 Emission calculation
The emission calculations are based on the fuel consumption using the openap.Emission
class.
We can calculate the emissions for the entire flight and append them as new columns to the dataframe as follows:
= openap.Emission(ac="A319")
emission
= df.assign(
df =lambda d: emission.co2(d.fuel_flow),
co2_flow=lambda d: emission.h2o(d.fuel_flow),
h2o_flow=lambda d: emission.soot(d.fuel_flow),
soot_flow=lambda d: emission.sox(d.fuel_flow),
sox_flow=lambda d: emission.nox(d.fuel_flow, tas=d.groundspeed, alt=d.altitude),
nox_flow=lambda d: emission.co(d.fuel_flow, tas=d.groundspeed, alt=d.altitude),
co_flow=lambda d: emission.hc(d.fuel_flow, tas=d.groundspeed, alt=d.altitude),
hc_flow )
Let’s visualize the emission flows:
= plt.subplots(7, 1, figsize=(5, 7), sharex=True)
fig, axes
= axes.flatten()
axes
= dict(
labels ="CO2 (g/s)",
co2_flow="H2O (g/s)",
h2o_flow="Soot (g/s)",
soot_flow="SOx (g/s)",
sox_flow="NOx (g/s)",
nox_flow="CO (g/s)",
co_flow="HC (g/s)",
hc_flow
)
for i, (k, v) in enumerate(labels.items()):
="tab:brown")
axes[i].plot(df.timestamp, df[k], color
axes[i].set_ylabel(v)
format_ax(axes[i])
plt.tight_layout() plt.show()
Note that CO2, H2O, Soot, and SOx are linear correlated to the fuel flow, following the coefficients from the paper Global Mortality Attributable to Aircraft Cruise Emissions.
7.3.4 Final data
The emissions at each time step are also calculated as follows. Note that we divide values by 1000 to obtain the emissions in kg
.
= df.eval(
df """
co2 = co2_flow * d_ts / 1000
h2o = h2o_flow * d_ts / 1000
soot = soot_flow * d_ts / 1000
sox = sox_flow * d_ts / 1000
nox = nox_flow * d_ts / 1000
co = co_flow * d_ts / 1000
hc = hc_flow * d_ts / 1000
"""
)
We can take a look at the final data with fuel flow and emissions columns.
"timestamp", "fuel", "co2", "h2o", "soot", "sox", "nox", "co", "hc"]].round(4) df[[
timestamp | fuel | co2 | h2o | soot | sox | nox | co | hc | |
---|---|---|---|---|---|---|---|---|---|
0 | 2018-01-02 19:53:00+00:00 | 81.3691 | 257.1265 | 100.0840 | 0.0024 | 0.0976 | 1.2232 | 0.0993 | 0.0027 |
1 | 2018-01-02 19:54:00+00:00 | 61.1136 | 193.1191 | 75.1698 | 0.0018 | 0.0733 | 0.7674 | 0.1380 | 0.0027 |
2 | 2018-01-02 19:55:00+00:00 | 57.3222 | 181.1380 | 70.5063 | 0.0017 | 0.0688 | 0.6956 | 0.1402 | 0.0026 |
3 | 2018-01-02 19:56:00+00:00 | 88.5218 | 279.7288 | 108.8818 | 0.0027 | 0.1062 | 1.4456 | 0.0554 | 0.0025 |
4 | 2018-01-02 19:57:00+00:00 | 85.7259 | 270.8939 | 105.4429 | 0.0026 | 0.1029 | 1.3834 | 0.0540 | 0.0025 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
111 | 2018-01-02 21:44:00+00:00 | 27.3882 | 86.5467 | 33.6875 | 0.0008 | 0.0329 | 0.2163 | 0.3043 | 0.0143 |
112 | 2018-01-02 21:45:00+00:00 | 26.7537 | 84.5418 | 32.9071 | 0.0008 | 0.0321 | 0.2061 | 0.3411 | 0.0170 |
113 | 2018-01-02 21:46:00+00:00 | 26.5316 | 83.8399 | 32.6339 | 0.0008 | 0.0318 | 0.2016 | 0.3610 | 0.0184 |
114 | 2018-01-02 21:47:00+00:00 | 25.4723 | 80.4924 | 31.3309 | 0.0008 | 0.0306 | 0.1865 | 0.3963 | 0.0210 |
115 | 2018-01-02 21:48:00+00:00 | 28.2236 | 89.1865 | 34.7150 | 0.0008 | 0.0339 | 0.2214 | 0.3331 | 0.0165 |
116 rows × 9 columns
Finally, we can obtain the total fuel consumption and emissions for the entire flight (unit in kg
).
"fuel", "co2", "h2o", "soot", "sox", "nox", "co", "hc"]].sum().round(2) df[[
fuel 5052.25
co2 15965.10
h2o 6214.27
soot 0.15
sox 6.06
nox 55.09
co 18.27
hc 0.46
dtype: float64