Flight#
Data structure for working with flight trajectories.
[1]:
import pandas as pd
import numpy as np
from pycontrails import Flight
from pycontrails.datalib.ecmwf import ERA5
Create Flight#
From Numpy Arrays#
[2]:
# waypoints
longitude = np.linspace(0, 50, 100)
latitude = np.linspace(0, 10, 100)
altitude = np.linspace(11000, 11500, 100)
time = pd.date_range("2022-03-01 00:00:00", "2022-03-01 02:00:00", periods=100)
fl = Flight(longitude=longitude, latitude=latitude, altitude=altitude, time=time, flight_id="id")
fl
[2]:
Flight [4 keys x 100 length, 2 attributes]
Attributes | |
---|---|
time | [2022-03-01 00:00:00, 2022-03-01 02:00:00] |
longitude | [0.0, 50.0] |
latitude | [0.0, 10.0] |
altitude | [11000.0, 11500.0] |
flight_id | id |
crs | EPSG:4326 |
longitude | latitude | time | altitude | |
---|---|---|---|---|
0 | 0.000000 | 0.00000 | 2022-03-01 00:00:00.000000000 | 11000.000000 |
1 | 0.505051 | 0.10101 | 2022-03-01 00:01:12.727272727 | 11005.050505 |
2 | 1.010101 | 0.20202 | 2022-03-01 00:02:25.454545454 | 11010.101010 |
3 | 1.515152 | 0.30303 | 2022-03-01 00:03:38.181818181 | 11015.151515 |
4 | 2.020202 | 0.40404 | 2022-03-01 00:04:50.909090909 | 11020.202020 |
... | ... | ... | ... | ... |
95 | 47.979798 | 9.59596 | 2022-03-01 01:55:09.090909090 | 11479.797980 |
96 | 48.484848 | 9.69697 | 2022-03-01 01:56:21.818181818 | 11484.848485 |
97 | 48.989899 | 9.79798 | 2022-03-01 01:57:34.545454545 | 11489.898990 |
98 | 49.494949 | 9.89899 | 2022-03-01 01:58:47.272727272 | 11494.949495 |
99 | 50.000000 | 10.00000 | 2022-03-01 02:00:00.000000000 | 11500.000000 |
100 rows × 4 columns
From Pandas DataFrame#
[3]:
# Example flight
df = pd.DataFrame()
df["longitude"] = np.linspace(0, 50, 100)
df["latitude"] = np.linspace(0, 10, 100)
df["altitude"] = 11000
df["time"] = pd.date_range("2022-03-01 00:00:00", "2022-03-01 02:00:00", periods=100)
fl = Flight(data=df, flight_id="ABC")
fl
[3]:
Flight [4 keys x 100 length, 2 attributes]
Attributes | |
---|---|
time | [2022-03-01 00:00:00, 2022-03-01 02:00:00] |
longitude | [0.0, 50.0] |
latitude | [0.0, 10.0] |
altitude | [11000.0, 11000.0] |
flight_id | ABC |
crs | EPSG:4326 |
longitude | latitude | altitude | time | |
---|---|---|---|---|
0 | 0.000000 | 0.00000 | 11000.0 | 2022-03-01 00:00:00.000000000 |
1 | 0.505051 | 0.10101 | 11000.0 | 2022-03-01 00:01:12.727272727 |
2 | 1.010101 | 0.20202 | 11000.0 | 2022-03-01 00:02:25.454545454 |
3 | 1.515152 | 0.30303 | 11000.0 | 2022-03-01 00:03:38.181818181 |
4 | 2.020202 | 0.40404 | 11000.0 | 2022-03-01 00:04:50.909090909 |
... | ... | ... | ... | ... |
95 | 47.979798 | 9.59596 | 11000.0 | 2022-03-01 01:55:09.090909090 |
96 | 48.484848 | 9.69697 | 11000.0 | 2022-03-01 01:56:21.818181818 |
97 | 48.989899 | 9.79798 | 11000.0 | 2022-03-01 01:57:34.545454545 |
98 | 49.494949 | 9.89899 | 11000.0 | 2022-03-01 01:58:47.272727272 |
99 | 50.000000 | 10.00000 | 11000.0 | 2022-03-01 02:00:00.000000000 |
100 rows × 4 columns
Create Flight without Waypoints#
[4]:
# Example flight
attrs = dict(flight_id="1234", equip="A532")
fl = Flight.create_empty(attrs=attrs)
fl
[4]:
Flight [4 keys x 0 length, 3 attributes]
Attributes | |
---|---|
flight_id | 1234 |
equip | A532 |
crs | EPSG:4326 |
altitude | latitude | time | longitude |
---|
Create from CSV file#
[5]:
# load flight
df = pd.read_csv("flight.csv")
fl = Flight(data=df, flight_id="csv")
fl
[5]:
Flight [4 keys x 175 length, 2 attributes]
Attributes | |
---|---|
time | [2022-03-01 00:50:00, 2022-03-01 03:47:00] |
longitude | [-97.02592499402108, -77.03594970703125] |
latitude | [32.93064880371094, 38.854248046875] |
altitude | [190.5, 11582.4] |
flight_id | csv |
crs | EPSG:4326 |
longitude | latitude | altitude | time | |
---|---|---|---|---|
0 | -77.035950 | 38.829315 | 236.22 | 2022-03-01 00:50:00 |
1 | -77.038223 | 38.772675 | 708.66 | 2022-03-01 00:51:00 |
2 | -77.114231 | 38.744568 | 9471.66 | 2022-03-01 00:52:00 |
3 | -77.201965 | 38.739888 | 2019.30 | 2022-03-01 00:53:00 |
4 | -77.286191 | 38.745117 | 3032.76 | 2022-03-01 00:54:00 |
... | ... | ... | ... | ... |
170 | -97.025925 | 32.931379 | 190.50 | 2022-03-01 03:43:00 |
171 | -97.025922 | 32.930649 | 190.50 | 2022-03-01 03:44:00 |
172 | -97.025922 | 32.930649 | 190.50 | 2022-03-01 03:45:00 |
173 | -97.025922 | 32.930649 | 190.50 | 2022-03-01 03:46:00 |
174 | -97.025922 | 32.930649 | 190.50 | 2022-03-01 03:47:00 |
175 rows × 4 columns
Using the Flight class#
The flight.data
attribute is a dictionary with np.ndarray
values
[6]:
# waypoints
longitude = np.linspace(0, 50, 10)
latitude = np.linspace(0, 10, 10)
altitude = np.linspace(11000, 11500, 10)
time = pd.date_range("2022-03-01 00:00:00", "2022-03-01 02:00:00", periods=10)
attrs = {"flight_id": "ABC123"}
fl = Flight(longitude=longitude, latitude=latitude, altitude=altitude, time=time, attrs=attrs)
[7]:
fl.data.keys()
[7]:
dict_keys(['longitude', 'latitude', 'time', 'altitude'])
Flight
attributes are stored in a dictionary on the attrs
attribute. The crs
attribute is always added by default, if not specified.
[8]:
fl.attrs
[8]:
{'flight_id': 'ABC123', 'crs': 'EPSG:4326'}
Data can be set / get from the Flight
like a dictionary
[9]:
# get
lat = fl["latitude"]
# set
lat[5] = 20
fl["latitude"] = lat
# get updated
fl["latitude"][5]
[9]:
20.0
The Flight
class contains the following convenience properties
[10]:
# Pressure altitude, in hPa
fl.level
[10]:
array([226.3170091 , 224.34300442, 222.3862176 , 220.44649846,
218.52369813, 216.61766904, 214.7282649 , 212.85534072,
210.99875274, 209.15835847])
[11]:
# Altitude, in ft
fl.altitude_ft
[11]:
array([36089.23884514, 36271.5077282 , 36453.77661126, 36636.04549431,
36818.31437737, 37000.58326043, 37182.85214348, 37365.12102654,
37547.38990959, 37729.65879265])
[12]:
# Values that are constant along the flight path
# set constant value along flight waypoints
fl["constant"] = np.full(shape=fl.shape, fill_value=100)
fl.constants
[12]:
{'constant': 100, 'flight_id': 'ABC123', 'crs': 'EPSG:4326'}
[13]:
# Flight distance, in meters
fl.length
[13]:
7818835.115366629
[14]:
# Time start/end
print(fl.time_start)
print(fl.time_end)
2022-03-01 00:00:00
2022-03-01 02:00:00
[15]:
# Flight duration, as a pandas Timedelta
fl.duration
[15]:
Timedelta('0 days 02:00:00')
[16]:
# Max time gap between waypoints, as a pandas Timedelta
fl.max_time_gap
[16]:
Timedelta('0 days 00:13:20')
[17]:
# Max distance gap between waypoints, in meters
fl.max_distance_gap
[17]:
1831403.3492360476
Intersect with Met data#
[18]:
# waypoints
longitude = np.linspace(0, 50, 50)
latitude = np.linspace(0, 10, 50)
altitude = np.linspace(11000, 11500, 50)
time = pd.date_range("2022-03-01 00:00:00", "2022-03-01 02:00:00", periods=50)
fl = Flight(longitude=longitude, latitude=latitude, altitude=altitude, time=time, flight_id="ABC")
[19]:
# domain
time = ("2022-03-01 00:00:00", "2022-03-01 03:00:00")
variables = ["t", "q", "u", "v", "w", "ciwc", "z", "cc"]
pressure_levels = [300, 250, 200]
# get met data
era5 = ERA5(time=time, variables=variables, pressure_levels=pressure_levels)
met = era5.open_metdataset()
[20]:
# interpolate to nearest grid member
fl.intersect_met(met["air_temperature"], method="nearest")
[20]:
array([231.6297 , 231.5037 , 231.73001, 231.08833, 218.8252 , 218.8393 ,
219.00676, 218.07823, 218.66353, 219.61859, 219.48346, 219.66501,
219.8988 , 219.86067, 219.95186, 220.16989, 220.15166, 220.06793,
219.71309, 219.5191 , 219.5647 , 219.54646, 219.39972, 219.1908 ,
219.0192 , 219.02084, 219.36572, 219.42708, 219.39308, 219.49672,
219.63931, 219.63351, 219.80347, 219.72304, 218.88406, 218.68758,
219.5904 , 219.66667, 219.75786, 219.57713, 219.53734, 219.87642,
219.74045, 219.47931, 219.345 , 219.28697, 219.14354, 219.04074,
218.90147, 218.93463], dtype=float32)
[21]:
# linear interpolation
fl.intersect_met(met["air_temperature"], method="linear")
[21]:
array([225.77794546, 225.3907536 , 225.21886695, 224.88386925,
225.13017366, 224.7533235 , 224.69847888, 224.51217578,
224.6564141 , 225.06851445, 225.2778995 , 225.23736206,
225.27412295, 225.21636444, 225.02274627, 225.16204077,
225.0248815 , 224.83874578, 224.5083198 , 224.36187816,
224.27049915, 224.02780713, 223.71685602, 223.55300043,
223.37676381, 223.35237865, 223.48899599, 223.45066473,
223.46031406, 223.51036424, 223.47455769, 223.33977131,
223.33706289, 223.19572504, 222.68132145, 222.43277286,
222.99376949, 222.90991434, 222.94348612, 222.70607751,
222.66989441, 222.84316583, 222.59155016, 222.31973459,
222.06893404, 221.91065999, 221.70156579, 221.52470254,
221.33912935, 221.26541793])
Get Lengths#
[22]:
# total flight length in meters
fl.length
[22]:
5642421.5973290345
[23]:
# intersect flight with air temperature
fl["temp"] = fl.intersect_met(met["air_temperature"], method="nearest")
# get the length of the flight where ambient temperature is > 226 K
fl.length_met("temp", threshold=226)
[23]:
462850.7958842697
Plot and Resample#
[24]:
fl.dataframe.plot.scatter(x="longitude", y="latitude", figsize=(12, 8))
fl.max_distance_gap
[24]:
115715.16936791067

[25]:
# resample with 10 minute waypoints
fl = fl.resample_and_fill("10T")
fl.dataframe.plot.scatter(x="longitude", y="latitude", figsize=(12, 8))
fl.max_distance_gap
[25]:
520697.0929684143

[26]:
# resample with 10 second waypoints
fl = fl.resample_and_fill("10S")
fl.dataframe.plot.scatter(x="longitude", y="latitude", figsize=(12, 8))
fl.max_distance_gap
[26]:
8678.45194022453
