Smoothing trajectories¶

No description has been provided for this image

Binder IPYNB HTML

To smooth trajectories, we can use a Kalman filter. The implemented KalmanSmootherCV is based on the assumption of a nearly-constant velocity (CV) model. To use KalmanSmootherCV, the optional dependency StoneSoup needs to be installed.

Documentation

A closely related type of operation is trajectory generalization which is covered in a separate notebook.

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, dim

import warnings

warnings.filterwarnings("ignore")

plot_defaults = {"linewidth": 5, "capstyle": "round", "figsize": (9, 3), "legend": True}
opts.defaults(opts.Overlay(active_tools=["wheel_zoom"]))
hvplot_defaults = {
    "tiles": "CartoLight",
    "frame_height": 320,
    "frame_width": 320,
    "cmap": "Viridis",
    "colorbar": True,
}

mpd.show_versions()
MovingPandas 0.22.4

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-107-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]:
split = mpd.ObservationGapSplitter(tc).split(gap=timedelta(minutes=15))

KalmanSmootherCV¶

This smoother operates on the assumption of a nearly-constant velocity (CV) model. The process_noise_std and measurement_noise_std parameters can be used to tune the smoother:

  • process_noise_std governs the uncertainty associated with the adherence of the new (smooth) trajectories to the CV model assumption; higher values relax the assumption, therefore leading to less-smooth trajectories, and vice-versa.
  • measurement_noise_std controls the assumed error in the original trajectories; higher values dictate that the original trajectories are expected to be noisier (and therefore, less reliable), thus leading to smoother trajectories, and vice-versa.

Try tuning these parameters and observe the resulting trajectories:

In [4]:
smooth = mpd.KalmanSmootherCV(split).smooth(
    process_noise_std=0.1, measurement_noise_std=10
)
print(smooth)
TrajectoryCollection with 11 trajectories
In [5]:
kwargs = {**hvplot_defaults, "line_width": 4}
(
    split.hvplot(title="Original Trajectories", **kwargs)
    + smooth.hvplot(title="Smooth Trajectories", **kwargs)
)
Out[5]:
In [6]:
kwargs = {**hvplot_defaults, "c": "speed", "line_width": 7, "clim": (0, 20)}
smooth.add_speed()
(
    split.trajectories[2].hvplot(title="Original Trajectory", **kwargs)
    + smooth.trajectories[2].hvplot(title="Smooth Trajectory", **kwargs)
)
Out[6]:

OutlierCleaner¶

In [7]:
traj = split.trajectories[8]

cleaned = traj.copy()
cleaned = mpd.OutlierCleaner(cleaned).clean(alpha=2)

smoothed = mpd.KalmanSmootherCV(cleaned).smooth(
    process_noise_std=0.1, measurement_noise_std=10
)

(
    traj.hvplot(title="Original Trajectory", **kwargs)
    + cleaned.hvplot(title="Cleaned Trajectory", **kwargs)
    + smoothed.hvplot(title="Cleaned & Smoothed Trajectory", **kwargs)
)
Out[7]:
In [ ]: