Splitting trajectories¶

No description has been provided for this image

Binder IPYNB HTML

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.23.0

SYSTEM INFO
-----------
python     : 3.10.19 | packaged by conda-forge | (main, Jan 26 2026, 23:45:08) [GCC 14.3.0]
executable : /home/anita/miniforge3/envs/mpd-ex/bin/python
machine    : Linux-6.8.0-134-generic-x86_64-with-glibc2.39

PROJ INFO
-----------
PROJ       : 9.6.2
PROJ data dir: /home/anita/miniforge3/envs/mpd-ex/share/proj

PYTHON DEPENDENCIES
-------------------
numpy      : 1.23.1
geopandas  : 1.0.1
geopy      : 2.4.1
geoviews   : 1.15.1
holoviews  : 1.22.1
hvplot     : 0.12.2
mapclassify: 2.8.1
matplotlib : 3.10.8
pandas     : 2.3.3
pyproj     : 3.7.1
shapely    : 2.1.2
stonesoup  : 1.8
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: 38.8 km
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: >
No description has been provided for this image

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

Trajectory 2_0 (No. rows: 149 | Length: 2.4 km)

Start: 2009-06-29 07:02:25End: 2009-06-29 07:13:55Duration: 0:11:30
Bounds: (116.586342, 40.07196, 116.592616, 40.082514)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 07:02:25 1556 1090 2_0 0 POINT (116.59096 40.07196)
2009-06-29 07:02:30 1557 1091 2_0 0 POINT (116.5909 40.07201)
2009-06-29 07:02:35 1558 1092 2_0 0 POINT (116.59088 40.07203)
2009-06-29 07:02:40 1559 1093 2_0 0 POINT (116.59092 40.072)
2009-06-29 07:02:45 1560 1094 2_0 0 POINT (116.59096 40.07198)

Trajectory 2_1 (No. rows: 3 | Length: 36.2 m)

Start: 2009-06-29 07:16:55End: 2009-06-29 07:17:05Duration: 0:00:10
Bounds: (116.586843, 40.07929, 116.586902, 40.079608)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 07:16:55 1705 1239 2_1 0 POINT (116.5869 40.07961)
2009-06-29 07:17:00 1706 1240 2_1 0 POINT (116.58689 40.07936)
2009-06-29 07:17:05 1707 1241 2_1 0 POINT (116.58684 40.07929)

Trajectory 2_2 (No. rows: 619 | Length: 33.8 km)

Start: 2009-06-29 07:29:35End: 2009-06-29 08:20:15Duration: 0:50:40
Bounds: (116.319212, 39.971703, 116.587085, 40.079854)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 07:29:35 1708 1242 2_2 0 POINT (116.58703 40.07951)
2009-06-29 07:29:40 1709 1243 2_2 0 POINT (116.58704 40.07951)
2009-06-29 07:29:45 1710 1244 2_2 0 POINT (116.58703 40.07952)
2009-06-29 07:29:50 1711 1245 2_2 0 POINT (116.587 40.07953)
2009-06-29 07:29:55 1712 1246 2_2 0 POINT (116.58704 40.07947)

Trajectory 2_3 (No. rows: 120 | Length: 1.6 km)

Start: 2009-06-29 10:57:17End: 2009-06-29 11:06:52Duration: 0:09:35
Bounds: (116.319598, 39.999772, 116.327485, 40.007696)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 10:57:17 2327 1861 2_3 0 POINT (116.3197 40.00751)
2009-06-29 10:57:22 2328 1862 2_3 0 POINT (116.31971 40.00759)
2009-06-29 10:57:27 2329 1863 2_3 0 POINT (116.31964 40.00769)
2009-06-29 10:57:32 2330 1864 2_3 0 POINT (116.3196 40.0077)
2009-06-29 10:57:37 2331 1865 2_3 0 POINT (116.31965 40.00764)

Trajectory 2_4 (No. rows: 5 | Length: 574.5 m)

Start: 2009-06-29 11:09:12End: 2009-06-29 11:10:07Duration: 0:00:55
Bounds: (116.323486, 40.000248, 116.327222, 40.001014)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 11:09:12 2447 1981 2_4 0 POINT (116.32636 40.00025)
2009-06-29 11:09:47 2448 1982 2_4 0 POINT (116.32349 40.00037)
2009-06-29 11:09:57 2449 1983 2_4 0 POINT (116.32513 40.00057)
2009-06-29 11:10:02 2450 1984 2_4 0 POINT (116.32688 40.00087)
2009-06-29 11:10:07 2451 1985 2_4 0 POINT (116.32722 40.00101)
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)
No description has been provided for this image

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

Trajectory 2_2009-06-29 07:06:55 (No. rows: 75 | Length: 1.9 km)

Start: 2009-06-29 07:06:55End: 2009-06-29 07:12:55Duration: 0:06:00
Bounds: (116.586342, 40.074197, 116.592583, 40.082514)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 07:06:55 1617 1151 2_2009-06-29 07:06:55 0 POINT (116.59258 40.0742)
2009-06-29 07:07:00 1618 1152 2_2009-06-29 07:06:55 0 POINT (116.59254 40.0743)
2009-06-29 07:07:05 1619 1153 2_2009-06-29 07:06:55 0 POINT (116.59249 40.07455)
2009-06-29 07:07:10 1620 1154 2_2009-06-29 07:06:55 0 POINT (116.59246 40.07488)
2009-06-29 07:07:15 1621 1155 2_2009-06-29 07:06:55 0 POINT (116.59244 40.07526)

Trajectory 2_2009-06-29 07:29:40 (No. rows: 400 | Length: 29.8 km)

Start: 2009-06-29 07:29:40End: 2009-06-29 08:02:30Duration: 0:32:50
Bounds: (116.328591, 39.971703, 116.587085, 40.079854)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 07:29:40 1709 1243 2_2009-06-29 07:29:40 0 POINT (116.58704 40.07951)
2009-06-29 07:29:45 1710 1244 2_2009-06-29 07:29:40 0 POINT (116.58703 40.07952)
2009-06-29 07:29:50 1711 1245 2_2009-06-29 07:29:40 0 POINT (116.587 40.07953)
2009-06-29 07:29:55 1712 1246 2_2009-06-29 07:29:40 0 POINT (116.58704 40.07947)
2009-06-29 07:30:00 1713 1247 2_2009-06-29 07:29:40 0 POINT (116.58704 40.07945)

Trajectory 2_2009-06-29 08:06:55 (No. rows: 246 | Length: 5.0 km)

Start: 2009-06-29 08:06:55End: 2009-06-29 11:04:22Duration: 2:57:27
Bounds: (116.319212, 39.984768, 116.327176, 40.008284)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 08:06:55 2167 1701 2_2009-06-29 08:06:55 0 POINT (116.32311 39.98477)
2009-06-29 08:07:00 2168 1702 2_2009-06-29 08:06:55 0 POINT (116.32328 39.98481)
2009-06-29 08:07:05 2169 1703 2_2009-06-29 08:06:55 0 POINT (116.32345 39.98481)
2009-06-29 08:07:10 2170 1704 2_2009-06-29 08:06:55 0 POINT (116.32362 39.98481)
2009-06-29 08:07:15 2171 1705 2_2009-06-29 08:06:55 0 POINT (116.32381 39.9848)

Trajectory 2_2009-06-29 11:06:17 (No. rows: 14 | Length: 777.9 m)

Start: 2009-06-29 11:06:17End: 2009-06-29 11:13:12Duration: 0:06:55
Bounds: (116.323486, 39.99997, 116.327485, 40.001014)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32)
Data preview
id sequence trajectory_id tracker geometry
t
2009-06-29 11:06:17 2439 1973 2_2009-06-29 11:06:17 0 POINT (116.32744 39.99997)
2009-06-29 11:06:22 2440 1974 2_2009-06-29 11:06:17 0 POINT (116.32746 40.00004)
2009-06-29 11:06:27 2441 1975 2_2009-06-29 11:06:17 0 POINT (116.32748 40.00009)
2009-06-29 11:06:32 2442 1976 2_2009-06-29 11:06:17 0 POINT (116.3274 40.0002)
2009-06-29 11:06:37 2443 1977 2_2009-06-29 11:06:17 0 POINT (116.32727 40.00035)
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)
No description has been provided for this image

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

Trajectory 2_0 (No. rows: 85 | Length: 2.5 km)

Start: 2009-06-29 07:02:25End: 2009-06-29 07:17:05Duration: 0:14:40
Bounds: (116.586342, 40.07196, 116.592604, 40.082514)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32), speed (float64)
Data preview
id sequence trajectory_id tracker geometry speed
t
2009-06-29 07:02:25 1556 1090 2_0 0 POINT (116.59096 40.07196) 1.352991
2009-06-29 07:02:30 1557 1091 2_0 0 POINT (116.5909 40.07201) 1.352991
2009-06-29 07:02:50 1561 1095 2_0 0 POINT (116.59101 40.07196) 1.008940
2009-06-29 07:03:20 1568 1102 2_0 0 POINT (116.59087 40.0721) 2.927362
2009-06-29 07:03:25 1569 1103 2_0 0 POINT (116.59069 40.07225) 4.457305

Trajectory 2_1 (No. rows: 536 | Length: 33.7 km)

Start: 2009-06-29 07:29:55End: 2009-06-29 08:20:15Duration: 0:50:20
Bounds: (116.319212, 39.971703, 116.587085, 40.079854)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32), speed (float64)
Data preview
id sequence trajectory_id tracker geometry speed
t
2009-06-29 07:29:55 1712 1246 2_1 0 POINT (116.58704 40.07947) 1.443824
2009-06-29 07:30:20 1718 1252 2_1 0 POINT (116.58705 40.0795) 1.334180
2009-06-29 07:30:25 1719 1253 2_1 0 POINT (116.58707 40.07957) 1.646357
2009-06-29 07:30:30 1720 1254 2_1 0 POINT (116.58708 40.07964) 1.521706
2009-06-29 07:30:35 1721 1255 2_1 0 POINT (116.58704 40.07976) 2.782868

Trajectory 2_2 (No. rows: 98 | Length: 2.2 km)

Start: 2009-06-29 10:57:22End: 2009-06-29 11:10:07Duration: 0:12:45
Bounds: (116.319636, 39.999772, 116.327485, 40.007687)CRS: EPSG:4326
Columns: id (int64), sequence (int64), trajectory_id (object), tracker (int32), speed (float64)
Data preview
id sequence trajectory_id tracker geometry speed
t
2009-06-29 10:57:22 2328 1862 2_2 0 POINT (116.31971 40.00759) 1.628377
2009-06-29 10:57:27 2329 1863 2_2 0 POINT (116.31964 40.00769) 2.541564
2009-06-29 10:57:37 2331 1865 2_2 0 POINT (116.31965 40.00764) 1.436148
2009-06-29 10:57:47 2333 1867 2_2 0 POINT (116.31968 40.00756) 1.222813
2009-06-29 10:57:52 2334 1868 2_2 0 POINT (116.3199 40.00748) 4.295232
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)
No description has been provided for this image
In [ ]: