Splitting trajectories¶
Gaps are quite common in trajectories. For example, GPS tracks may contain gaps if moving objects enter tunnels where GPS reception is lost. In other use cases, moving objects may leave the observation area for longer time before returning and continuing their recorded track.
Depending on the use case, we therefore might want to split trajectories at observation gaps that exceed a certain minimum duration:
In [1]:
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas
import matplotlib.pyplot as plt
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts
import warnings
warnings.filterwarnings("ignore")
plot_defaults = {"linewidth": 5, "capstyle": "round", "figsize": (9, 3), "legend": True}
opts.defaults(
opts.Overlay(active_tools=["wheel_zoom"], frame_width=500, frame_height=400)
)
mpd.show_versions()
MovingPandas 0.20.0 SYSTEM INFO ----------- python : 3.10.15 | packaged by conda-forge | (main, Oct 16 2024, 01:15:49) [MSC v.1941 64 bit (AMD64)] executable : c:\Users\Agarkovam\AppData\Local\miniforge3\envs\mpd-ex\python.exe machine : Windows-10-10.0.19045-SP0 GEOS, GDAL, PROJ INFO --------------------- GEOS : None GEOS lib : None GDAL : None GDAL data dir: None PROJ : 9.5.0 PROJ data dir: C:\Users\Agarkovam\AppData\Local\miniforge3\envs\mpd-ex\Library\share\proj PYTHON DEPENDENCIES ------------------- geopandas : 1.0.1 pandas : 2.2.3 fiona : None numpy : 1.23.1 shapely : 2.0.6 pyproj : 3.7.0 matplotlib : 3.9.2 mapclassify: 2.8.1 geopy : 2.4.1 holoviews : 1.20.0 hvplot : 0.11.1 geoviews : 1.13.0 stonesoup : 1.4
In [2]:
gdf = read_file("../data/geolife_small.gpkg")
tc = mpd.TrajectoryCollection(gdf, "trajectory_id", t="t")
In [3]:
my_traj = tc.trajectories[1]
print(my_traj)
Trajectory 2 (2009-06-29 07:02:25 to 2009-06-29 11:13:12) | Size: 897 | Length: 38764.6m Bounds: (116.319212, 39.971703, 116.592616, 40.082514) LINESTRING (116.590957 40.071961, 116.590905 40.072007, 116.590879 40.072027, 116.590915 40.072004,
In [4]:
my_traj.plot(column="speed", vmax=20, **plot_defaults)
Out[4]:
<Axes: >
ObservationGapSplitter¶
Split the trajectory where then are no observations for at least two minutes:
In [5]:
split = mpd.ObservationGapSplitter(my_traj).split(gap=timedelta(minutes=2))
split
Out[5]:
TrajectoryCollection with 5 trajectories
In [6]:
split.to_traj_gdf()
Out[6]:
trajectory_id | start_t | end_t | geometry | length | direction | |
---|---|---|---|---|---|---|
0 | 2_0 | 2009-06-29 07:02:25 | 2009-06-29 07:13:55 | LINESTRING (116.59096 40.07196, 116.5909 40.07... | 2367.642888 | 345.820618 |
1 | 2_1 | 2009-06-29 07:16:55 | 2009-06-29 07:17:05 | LINESTRING (116.5869 40.07961, 116.58689 40.07... | 36.188090 | 188.079866 |
2 | 2_2 | 2009-06-29 07:29:35 | 2009-06-29 08:20:15 | LINESTRING (116.58703 40.07951, 116.58704 40.0... | 33766.853732 | 250.896081 |
3 | 2_3 | 2009-06-29 10:57:17 | 2009-06-29 11:06:52 | LINESTRING (116.3197 40.00751, 116.31971 40.00... | 1613.488553 | 140.473858 |
4 | 2_4 | 2009-06-29 11:09:12 | 2009-06-29 11:10:07 | LINESTRING (116.32636 40.00025, 116.32349 40.0... | 574.481893 | 40.696636 |
In [7]:
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19, 4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle="round", column="speed", vmax=20)
StopSplitter¶
Split the trajectory where observations stay within 30 meters for at least 1 minute. Discard created trajectories that are shorter than 500 meters long:
In [8]:
split = mpd.StopSplitter(my_traj).split(
max_diameter=30, min_duration=timedelta(minutes=1), min_length=500
)
split
Out[8]:
TrajectoryCollection with 4 trajectories
In [9]:
split.to_traj_gdf()
Out[9]:
trajectory_id | start_t | end_t | geometry | length | direction | |
---|---|---|---|---|---|---|
0 | 2_2009-06-29 07:06:55 | 2009-06-29 07:06:55 | 2009-06-29 07:12:55 | LINESTRING (116.59258 40.0742, 116.59254 40.07... | 1862.857529 | 332.413430 |
1 | 2_2009-06-29 07:29:40 | 2009-06-29 07:29:40 | 2009-06-29 08:02:30 | LINESTRING (116.58704 40.07951, 116.58703 40.0... | 29770.503003 | 244.569703 |
2 | 2_2009-06-29 08:06:55 | 2009-06-29 08:06:55 | 2009-06-29 11:04:22 | LINESTRING (116.32311 39.98477, 116.32328 39.9... | 5026.886838 | 11.661788 |
3 | 2_2009-06-29 11:06:17 | 2009-06-29 11:06:17 | 2009-06-29 11:13:12 | LINESTRING (116.32744 39.99997, 116.32746 40.0... | 777.883748 | 1.430923 |
In [10]:
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19, 4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle="round", column="speed", vmax=20)
SpeedSplitter¶
Split the trajectory where the speed is below one meters per second for at least five minutes:
In [11]:
split = mpd.SpeedSplitter(my_traj).split(speed=1, duration=timedelta(minutes=5))
split
Out[11]:
TrajectoryCollection with 3 trajectories
In [12]:
split.to_traj_gdf()
Out[12]:
trajectory_id | start_t | end_t | geometry | length | direction | |
---|---|---|---|---|---|---|
0 | 2_0 | 2009-06-29 07:02:25 | 2009-06-29 07:17:05 | LINESTRING (116.59096 40.07196, 116.5909 40.07... | 2521.655180 | 336.756427 |
1 | 2_1 | 2009-06-29 07:29:55 | 2009-06-29 08:20:15 | LINESTRING (116.58704 40.07947, 116.58705 40.0... | 33662.308829 | 250.906244 |
2 | 2_2 | 2009-06-29 10:57:22 | 2009-06-29 11:10:07 | LINESTRING (116.31971 40.00759, 116.31964 40.0... | 2231.112048 | 138.778458 |
In [13]:
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19, 4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle="round", column="speed", vmax=20)
In [ ]: