How to estimate the fuel burnt by an aircraft?
The traffic library integrates the OpenAP aircraft performance and emission model.
We can demonstrate its use with an example flight extracted from a real flight data recorder of an Airbus A320-216 aircraft. Data is anonymised, so there is no geographical information about the trajectory and timestamps are just wrong.
About anonymisation
I may consider adding extra features to this dataset if it could help validate or illustrate a use case with the library. The bottom line is that anonymisation (tail number) should not be obviously broken.
If you find a way to break the anonymisation of this particular flight, good for you: nobody will confirm or deny your claim.
You may manage to find the city-pair associated with that flight: if you reconstruct the whole trajectory with a reasonable process, let’s write a tutorial page to illustrate it.
from traffic.data.samples import fuelflow_a320
There are enough features (and some more) in the provided data to estimate the fuel flow with OpenAP and compare the result with real data.
fuelflow_a320.data[['timestamp', 'altitude', 'groundspeed', 'CAS', 'vertical_acceleration', 'weight', 'fuelflow']]
timestamp | altitude | groundspeed | CAS | vertical_acceleration | weight | fuelflow | |
---|---|---|---|---|---|---|---|
602 | 2011-07-23 13:23:09+00:00 | 232 | 169 | 164.875 | 1.195312 | 69454.064722 | 7625.792472 |
603 | 2011-07-23 13:23:10+00:00 | 264 | 169 | 165.0 | 1.121094 | 69454.064722 | 7642.121792 |
604 | 2011-07-23 13:23:11+00:00 | 296 | 169 | 165.125 | 1.121094 | 69454.064722 | 7643.936161 |
605 | 2011-07-23 13:23:12+00:00 | 330 | 169 | 164.625 | 1.035156 | 69454.064722 | 7634.864316 |
606 | 2011-07-23 13:23:13+00:00 | 364 | 169 | 162.625 | 0.964844 | 69444.992874 | 7629.42121 |
... | ... | ... | ... | ... | ... | ... | ... |
12405 | 2011-07-23 16:39:52+00:00 | 156 | 135 | 128.125 | 0.976562 | 60926.52804 | 720.304452 |
12406 | 2011-07-23 16:39:53+00:00 | 156 | 134 | 126.875 | 1.015625 | 60926.52804 | 733.005034 |
12407 | 2011-07-23 16:39:54+00:00 | 164 | 132 | 124.375 | 0.929688 | 60926.52804 | 912.627555 |
12408 | 2011-07-23 16:39:55+00:00 | 172 | 130 | 123.75 | 1.1875 | 60917.456192 | 1043.262115 |
12409 | 2011-07-23 16:39:56+00:00 | 170 | 127 | 120.875 | 1.140625 | 60908.384344 | 1491.411233 |
11808 rows × 7 columns
User interface
The most direct use of the API, is with the fuelflow()
method:
- Flight.fuelflow(initial_mass=None, typecode=None, engine=None)
Estimates the fuel flow with OpenAP.
The OpenAP model is based on the aircraft type (actually, the most probable engine type) and on three features commonly available in ADS-B data:
altitude (in ft),
vertical rate (in ft/min), and
speed (in kts), in order of priority,
TAS
(true air speed),CAS
(computed air speed, used to compute TAS) andgroundspeed
, if no air speed is available.
- Parameters:
initial_mass (
Union
[None
,str
,float
]) – by default (None), 90% of the maximum take-off weight. You can also pass a value to initialise the mass. Wheninitial_mass > 1
, the mass is in kg. Wheninitial_mass <= 1
, it represents the fraction of the maximum take-off weight.typecode (
Optional
[str
]) – by default (None), use the typecode column if available, the provided aircraft database to infer the typecode based on theicao24
. Ignored if the engine parameter is not None.engine (
Optional
[str
]) – by default (None), use the default engine associated with the aircraft type.
- Return type:
- Returns:
the same instance enriched with three extra features: the mass, the fuel flow (in kg/s) and the total burnt fuel (in kg).
In this dataset:
- the vertical rate is not available as it is not directly measured on aircraft. Here, we consider the most simple approach and derive it from the altitude.In practice, the vertical acceleration is used to filter the vertical rate signal. It has been made available in this dataset (as a g-force)
the fuel flow (the ground truth) is provided in kg/h, we convert it in kg/s for compatibility reason with the output of the OpenAP interface.
f = fuelflow_a320.assign(
# the vertical_rate is not present in the data
vertical_rate=lambda df: df.altitude.diff().fillna(0) * 60,
# convert to kg/s
fuelflow=lambda df: df.fuelflow / 3600,
)
Sources of uncertainty
Let’s analyse the results produced with the following runs.
import altair as alt
alt.data_transformers.disable_max_rows()
def plot_flow(flight):
return flight.chart().encode(
alt.X(
"utchoursminutesseconds(timestamp)",
axis=alt.Axis(title=None, format="%H:%M"),
),
alt.Y("fuelflow", axis=alt.Axis(title="fuel flow (in kg/s)")),
alt.Color("legend", title=None),
)
def chart_flow(*flights):
return (
alt.layer(*(plot_flow(flight) for flight in flights))
.properties(width=600, height=250)
.configure_axis(
labelFontSize=14, titleFontSize=16,
titleAngle=0, titleY=-12, titleAnchor="start",
)
.configure_legend(
orient="bottom", columns=1,
labelFontSize=14, symbolSize=400, symbolStrokeWidth=3,
)
)
Default parameters
The default approach considers the default engine (which is the correct one for this particular aircraft), assumes the initial mass of the aircraft to be 90% of the initial take-off mass, and computes the TAS based on the available CAS.
The typecode="A320"
must be passed as a parameter because the icao24
parameter is not provided in this example.
resampled = f.resample("5s")
openap = resampled.fuelflow(typecode="A320")
chart_flow(
openap.assign(legend="OpenAP estimation"),
resampled.assign(legend="Real fuelflow")
)
real_fuel = resampled.weight_max - resampled.weight_min
estimated_fuel = openap.fuel_max
print(f"Total burnt fuel: {real_fuel:.0f}kg, OpenAP estimation: {estimated_fuel:.0f}kg")
print(f"Error: {abs(estimated_fuel - real_fuel) / real_fuel:.0%}")
Total burnt fuel: 8582kg, OpenAP estimation: 9448kg
Error: 10%
Impact of the take-off mass
As the weight of the aircraft is available along this particular trajectory—note that this is most likely an approximation too, based on the quantity of fuel loaded, the estimation of fuel burnt, cargo, number of embarked passengers, etc.—we can see that a better estimation of the mass slightly improves the estimation of the fuel flow.
resampled = f.resample("5s")
openap = resampled.fuelflow(typecode="A320", initial_mass=resampled.weight_max)
chart_flow(
openap.assign(legend="OpenAP estimation"),
resampled.assign(legend="Real fuelflow")
)
real_fuel = resampled.weight_max - resampled.weight_min
estimated_fuel = openap.fuel_max
print(f"Total burnt fuel: {real_fuel:.0f}kg, OpenAP estimation: {estimated_fuel:.0f}kg")
print(f"Error: {abs(estimated_fuel - real_fuel) / real_fuel:.0%}")
Total burnt fuel: 8582kg, OpenAP estimation: 9396kg
Error: 9%
Impact of the wind
resampled = f.resample("5s").drop(columns=["CAS"])
openap = resampled.fuelflow(typecode="A320")
chart_flow(
openap.assign(legend="OpenAP estimation"),
resampled.assign(legend="Real fuelflow")
)
real_fuel = resampled.weight_max - resampled.weight_min
estimated_fuel = openap.fuel_max
print(f"Total burnt fuel: {real_fuel:.0f}kg, OpenAP estimation: {estimated_fuel:.0f}kg")
print(f"Error: {abs(estimated_fuel - real_fuel) / real_fuel:.0%}")
Total burnt fuel: 8582kg, OpenAP estimation: 9570kg
Error: 12%
There are two ways to take wind into account when estimating fuel flow based on ADS-B data:
use information from extended Mode S in areas of the world where it is available (see
query_ehs()
);interpolate wind from GRIB files provided by Meteorological Agencies and use the information to compute the true air speed (TAS)
Influence of the sampling rate
The current Python implementation of fuel flow estimation is a bit slow, but changing the sampling rate of the trajectories in order to accelerate processing seems to have little impact on the final estimation.
resampled = f.resample("20s")
openap = resampled.fuelflow(typecode="A320")
chart_flow(
openap.assign(legend="OpenAP estimation"),
resampled.assign(legend="Real fuelflow")
)
real_fuel = resampled.weight_max - resampled.weight_min
estimated_fuel = openap.fuel_max
print(f"Total burnt fuel: {real_fuel:.0f}kg, OpenAP estimation: {estimated_fuel:.0f}kg")
print(f"Error: {abs(estimated_fuel - real_fuel) / real_fuel:.0%}")
Total burnt fuel: 8564kg, OpenAP estimation: 9453kg
Error: 10%
Influence of the engine type
The engine type has a serious impact on the fuel flow estimation even if the general trend looks similar. If you know the engine type for each aircraft, it may be more reasonable to specify it when running your estimation.
resampled = f.resample("5s")
openap = resampled.fuelflow(typecode="A320", engine="CFM56-5B5") # default/correct is CFM56-5B4
chart_flow(
openap.assign(legend="OpenAP estimation"),
resampled.assign(legend="Real fuelflow")
)
real_fuel = resampled.weight_max - resampled.weight_min
estimated_fuel = openap.fuel_max
print(f"Total burnt fuel: {real_fuel:.0f}kg, OpenAP estimation: {estimated_fuel:.0f}kg")
print(f"Error: {abs(estimated_fuel - real_fuel) / real_fuel:.0%}")
Total burnt fuel: 8582kg, OpenAP estimation: 10710kg
Error: 25%