How to simplify or resample a trajectory?๏
ADS-B trajectories consist of a series of geographical coordinates and other physical features relevant to the dynamics of the aircraft. Such time series may be unnecessarily precise in some cases (e.g. straight lines or static aircraft on ground), or contain gaps because of lack of coverage in terms of data receivers.
The traffic library contains functions to deal with such issues, illustrated here with the following sample trajectory:
from traffic.data.samples import texas_longhorn
texas_longhorn
Flight
- callsign: N56821 (KIWS to KIWS)
- aircraft:
a7475a
ยท ๐บ๐ธ N56821 (P28A) - start: 2017-09-17 21:38:21+00:00
- stop: 2017-09-18 00:16:44+00:00
- duration: 0 days 02:38:23
- sampling rate: 10 second(s)
Trajectory simplification with Douglas-Peucker algorithm๏
Trajectory simplification is particularly relevant for the ground track of trajectories. The two most famous methods are the Ramer-Douglas-Peucker algorithm and the Visvalingam-Whyatt algorithm.
- Flight.simplify(tolerance, altitude=None, z_factor=3.048)๏
Simplifies a trajectory with Douglas-Peucker algorithm.
The method uses latitude and longitude, projects the trajectory to a conformal projection and applies the algorithm. If x and y features are already present in the DataFrame (after a call to compute_xy for instance) then this projection is taken into account.
The tolerance parameter must be defined in meters. :rtype:
Flight
By default, a 2D version of the algorithm is called, unless you pass a column name for
altitude
.You may scale the z-axis for more relevance (
z_factor
). The default value works well in most situations.
The method returns a
Flight
or a 1D mask if you specifyreturn_mask=True
.
texas_longhorn.simplify(1e3) | texas_longhorn.simplify(1e4)
Flight
- callsign: N56821 (KIWS to KIWS)
- aircraft:
a7475a
ยท ๐บ๐ธ N56821 (P28A) - start: 2017-09-17 21:38:21+00:00
- stop: 2017-09-18 00:16:44+00:00
- duration: 0 days 02:38:23
- sampling rate: 190 second(s)
Flight
- callsign: N56821 (KIWS to KIWS)
- aircraft:
a7475a
ยท ๐บ๐ธ N56821 (P28A) - start: 2017-09-17 21:38:21+00:00
- stop: 2017-09-18 00:16:44+00:00
- duration: 0 days 02:38:23
- sampling rate: 1056 second(s)
Warning
A basic 2D simplification may result in an undesired oversimplification on
another channel, esp. the altitude. An extra parameter altitude
is
available to help to control the simplification for that other feature. The
z_factor
is designed to give more or less weight to that feature with
respect to the 2D ground track.
import altair as alt
encoding = [
alt.X(
"utcyearmonthdatehoursminutesseconds(timestamp):T",
axis=alt.Axis(format="%H:%M", title=None),
),
alt.Y("altitude", title="altitude (in ft)"),
alt.Color("name:N", title="Simplification"),
]
base = texas_longhorn.chart().transform_calculate(name="'original'")
chart = (
alt.vconcat(
alt.layer(
base.encode(*encoding),
texas_longhorn.simplify(1e4)
.chart()
.transform_calculate(name="'simplify (2D)'")
.encode(*encoding),
).properties(height=200, width=450),
alt.layer(
base.encode(*encoding),
# specify a parameter to take the altitude into account
texas_longhorn.simplify(1e4, altitude="altitude", z_factor=30.48)
.chart()
.transform_calculate(name="'simplify (3D)'")
.encode(*encoding),
).properties(height=200, width=450),
)
.configure_axis(
titleAngle=0,
titleY=-15,
titleX=0,
titleAnchor="start",
titleFont="Lato",
titleFontSize=16,
labelFontSize=12,
)
.configure_legend(
orient="bottom",
labelFont="Lato",
labelFontSize=14,
titleFont="Lato",
titleFontSize=14,
)
)
chart
Trajectory resampling with interpolation๏
Trajectory resampling is a useful tool in the following situations:
reducing the number of samples and size of the resulting object;
filling missing data in trajectory (interpolation);
fitting many trajectories into vectors of equal length.
- Flight.resample(rule='1s', how='interpolate', interpolate_kw={}, projection=None)๏
Resample the trajectory at a given frequency or for a target number of samples.
- Parameters:
rule (str | int) โ
If the rule is a string representing Offset aliases for time frequencies is passed, then the data is resampled along the timestamp axis, then interpolated (according to the
how
parameter).If the rule is an integer, the trajectory is resampled to the given number of evenly distributed points per trajectory.
how (None | str | dict[str, Iterable[str]]) โ
(default:
"interpolate"
)When the parameter is a string, the method applies to all columns
When the parameter is a dictionary with keys as methods (e.g.
"interpolate"
,"ffill"
) and names of columns as values. Columns not included in any value are left as is.
interpolate_kw (dict[str, Any]) โ
(default:
{}
)A dictionary with keyword arguments that will be passed to the pandas :py:method:`pandas.Series.interpolate` method.
Example usage: To specify a fifth-degree polynomial interpolation, you can pass the following dictionary:
interpolate_kw = {โmethodโ: โpolynomialโ, โorderโ: 5}
projection (None | str | pyproj.Proj | โcrs.Projection) โ
(default:
None
)By default, lat/lon are resampled with a linear interpolation;
If a projection is passed, the linear interpolation is applied on the x and y dimensions, then lat/lon are reprojected back;
If the projection is a string parameter, e.g.
"lcc"
, a projection is created on the fly, centred on the trajectory. This approach is helpful to fill gaps along a great circle.
- Return type:
Flight
texas_longhorn.resample("1 min") | texas_longhorn.resample("3 min")
Flight
- callsign: N56821 (KIWS to KIWS)
- aircraft:
a7475a
ยท ๐บ๐ธ N56821 (P28A) - start: 2017-09-17 21:38:00+00:00
- stop: 2017-09-18 00:16:00+00:00
- duration: 0 days 02:38:00
- sampling rate: 60 second(s)
Flight
- callsign: N56821 (KIWS to KIWS)
- aircraft:
a7475a
ยท ๐บ๐ธ N56821 (P28A) - start: 2017-09-17 21:36:00+00:00
- stop: 2017-09-18 00:15:00+00:00
- duration: 0 days 02:39:00
- sampling rate: 180 second(s)
Unlike with simplification, resampling is not designed to respect the topology of a trajectory. However, trends in other features (like altitude) are better preserved.
encoding = [
alt.X(
"utcyearmonthdatehoursminutesseconds(timestamp):T",
axis=alt.Axis(format="%H:%M", title=None),
),
alt.Y("altitude", title="altitude (in ft)"),
alt.Color("name:N", title="Resampling"),
]
base = texas_longhorn.chart().transform_calculate(name="'original'")
chart = (
alt.vconcat(
alt.layer(
base.encode(*encoding),
texas_longhorn.resample("1 min")
.chart()
.transform_calculate(name="'resample (1 min)'")
.encode(*encoding),
).properties(height=200, width=450),
alt.layer(
base.encode(*encoding),
texas_longhorn.resample("5 min")
.chart()
.transform_calculate(name="'resample (5 min)'")
.encode(*encoding),
).properties(height=200, width=450),
)
.configure_axis(
titleAngle=0,
titleY=-15,
titleX=0,
titleAnchor="start",
titleFont="Lato",
titleFontSize=16,
labelFontSize=12,
)
.configure_legend(
orient="bottom",
labelFont="Lato",
labelFontSize=14,
titleFont="Lato",
titleFontSize=14,
)
)
chart
Tip
In many use cases, it is necessary to align trajectories in a specific frame (e.g. final approach) and get the same number of samples per trajectory.
In the following example, we resample many final approaches to Paris-Orly airport with 30 samples per trajectory. The x-axis displays the distance to runway threshold; because of different ground speeds, all samples are not perfectly aligned on the x-axis, but they are all equally distributed along the time axis.
from traffic.data.samples import quickstart
landing_lfpo = (
quickstart.next("aligned_on_ils('LFPO')")
.cumulative_distance(reverse=True)
.query("cumdist < 10")
.resample(30)
.eval()
)
chart = (
alt.layer(
*list(
flight.chart()
.mark_line(point=alt.OverlayMarkDef(color="#f58518"))
.encode(
alt.X(
"cumdist",
scale=alt.Scale(reverse=True),
title="Cumulative distance to runway threshold (in nm)",
),
alt.Y(
"altitude",
scale=alt.Scale(domain=(0, 4000)),
axis=alt.Axis(
title="Altitude (in ft)",
titleAngle=0,
titleY=-15,
titleX=0,
titleAnchor="start",
),
),
)
for flight in landing_lfpo
)
)
.properties(height=250)
.configure_axis(
titleFont="Lato",
titleFontSize=16,
labelFontSize=12,
)
)
chart