Detecting stops¶

No description has been provided for this image

Binder IPYNB HTML

There are no definitive answers when it comes to detecting / extracting stops from movement trajectories. Due to tracking inaccuracies, movement speed rarely goes to true zero. GPS tracks, for example, tend to keep moving around the object's stop location.

Suitable stop definitions are also highly application dependent. For example, an application may be interested in analyzing trip purposes. To do so, analysts would be interested in stops that are longer than, for example, 5 minutes and may try to infer the purpose of the stop from the stop location and time. Shorter stops, such as delays at traffic lights, however would not be relevant for this appication.

In the MovingPandas TrajectoryStopDetector implementation, a stop is detected if the movement stays within an area of specified size for at least the specified 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.17.0

SYSTEM INFO
-----------
python     : 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:34:57) [MSC v.1936 64 bit (AMD64)]
executable : H:\miniconda3\envs\mpd-ex\python.exe
machine    : Windows-10-10.0.19045-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : 3.7.0
GDAL data dir: None
PROJ       : 9.2.1
PROJ data dir: H:\miniconda3\pkgs\proj-9.0.0-h1cfcee9_1\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 0.13.2
pandas     : 2.0.3
fiona      : 1.9.4
numpy      : 1.24.4
shapely    : 2.0.1
rtree      : 1.0.1
pyproj     : 3.6.0
matplotlib : 3.7.2
mapclassify: 2.5.0
geopy      : 2.3.0
holoviews  : 1.17.0
hvplot     : 0.8.3
geoviews   : 1.9.6
stonesoup  : 1.0
In [2]:
gdf = read_file('../data/geolife_small.gpkg')
tc = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')

Stop detection with a single Trajectory¶

In [3]:
my_traj = tc.trajectories[0]
my_traj
Out[3]:
Trajectory 1 (2008-12-11 04:42:14 to 2008-12-11 05:15:46) | Size: 466 | Length: 6207.0m
Bounds: (116.385602, 39.862378, 116.393553, 39.898723)
LINESTRING (116.391305 39.898573, 116.391317 39.898617, 116.390928 39.898613, 116.390833 39.898635, 
In [4]:
traj_plot = my_traj.hvplot(title='Trajectory {}'.format(my_traj.id), line_width=7.0, tiles='CartoLight', color='slategray') 
traj_plot
Out[4]:
In [5]:
detector = mpd.TrajectoryStopDetector(my_traj)

Stop duration¶

In [6]:
%%time
stop_time_ranges = detector.get_stop_time_ranges(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 234 ms
Wall time: 225 ms
In [7]:
for x in stop_time_ranges: 
    print(x)
Traj 1: 2008-12-11 04:42:14 - 2008-12-11 04:43:32 (duration: 0 days 00:01:18)
Traj 1: 2008-12-11 04:43:52 - 2008-12-11 04:47:38 (duration: 0 days 00:03:46)
Traj 1: 2008-12-11 04:50:06 - 2008-12-11 04:51:23 (duration: 0 days 00:01:17)
Traj 1: 2008-12-11 04:54:50 - 2008-12-11 04:55:55 (duration: 0 days 00:01:05)
Traj 1: 2008-12-11 05:02:03 - 2008-12-11 05:06:34 (duration: 0 days 00:04:31)
Traj 1: 2008-12-11 05:07:19 - 2008-12-11 05:08:31 (duration: 0 days 00:01:12)
Traj 1: 2008-12-11 05:11:17 - 2008-12-11 05:13:38 (duration: 0 days 00:02:21)
Traj 1: 2008-12-11 05:13:51 - 2008-12-11 05:15:46 (duration: 0 days 00:01:55)

Stop points¶

In [8]:
%%time
stop_points = detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 266 ms
Wall time: 271 ms
In [9]:
stop_points
Out[9]:
geometry start_time end_time traj_id duration_s
stop_id
1_2008-12-11 04:42:14 POINT (116.39112 39.89861) 2008-12-11 04:42:14 2008-12-11 04:43:32 1 78.0
1_2008-12-11 04:43:52 POINT (116.39071 39.89837) 2008-12-11 04:43:52 2008-12-11 04:47:38 1 226.0
1_2008-12-11 04:50:06 POINT (116.38932 39.88923) 2008-12-11 04:50:06 2008-12-11 04:51:23 1 77.0
1_2008-12-11 04:54:50 POINT (116.39232 39.87849) 2008-12-11 04:54:50 2008-12-11 04:55:55 1 65.0
1_2008-12-11 05:02:03 POINT (116.39293 39.86384) 2008-12-11 05:02:03 2008-12-11 05:06:34 1 271.0
1_2008-12-11 05:07:19 POINT (116.39024 39.86383) 2008-12-11 05:07:19 2008-12-11 05:08:31 1 72.0
1_2008-12-11 05:11:17 POINT (116.38596 39.86519) 2008-12-11 05:11:17 2008-12-11 05:13:38 1 141.0
1_2008-12-11 05:13:51 POINT (116.38603 39.86538) 2008-12-11 05:13:51 2008-12-11 05:15:46 1 115.0
In [10]:
stop_point_plot = traj_plot * stop_points.hvplot(geo=True, size='duration_s', color='deeppink')
stop_point_plot
Out[10]:

Stop segments¶

In [11]:
%%time
stops = detector.get_stop_segments(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 266 ms
Wall time: 282 ms
In [12]:
stops
Out[12]:
TrajectoryCollection with 8 trajectories
In [13]:
stop_segment_plot = stop_point_plot * stops.hvplot(line_width=7.0, tiles=None, color='orange') 
stop_segment_plot
Out[13]:

Split at stops¶

In [14]:
%%time
split = mpd.StopSplitter(my_traj).split(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 344 ms
Wall time: 393 ms
In [15]:
split
Out[15]:
TrajectoryCollection with 7 trajectories
In [16]:
split.to_traj_gdf()
Out[16]:
trajectory_id start_t end_t geometry length direction
0 1_2008-12-11 04:43:32 2008-12-11 04:43:32 2008-12-11 04:43:52 LINESTRING (116.39083 39.89863, 116.38941 39.8... 264.335588 192.205758
1 1_2008-12-11 04:47:38 2008-12-11 04:47:38 2008-12-11 04:50:06 LINESTRING (116.39030 39.89843, 116.39019 39.8... 1080.164181 186.476476
2 1_2008-12-11 04:51:23 2008-12-11 04:51:23 2008-12-11 04:54:50 LINESTRING (116.39000 39.88924, 116.39010 39.8... 1378.618493 169.324962
3 1_2008-12-11 04:55:55 2008-12-11 04:55:55 2008-12-11 05:02:03 LINESTRING (116.39183 39.87814, 116.39174 39.8... 1909.066931 176.111541
4 1_2008-12-11 05:06:34 2008-12-11 05:06:34 2008-12-11 05:07:19 LINESTRING (116.39251 39.86398, 116.39239 39.8... 145.517971 262.952795
5 1_2008-12-11 05:08:31 2008-12-11 05:08:31 2008-12-11 05:11:17 LINESTRING (116.38974 39.86382, 116.38967 39.8... 439.686682 300.319621
6 1_2008-12-11 05:13:38 2008-12-11 05:13:38 2008-12-11 05:13:51 LINESTRING (116.38587 39.86544, 116.38584 39.8... 16.921893 349.846256
In [17]:
stop_segment_plot + split.hvplot(title='Trajectory {} split at stops'.format(my_traj.id), line_width=7.0, tiles='CartoLight')
Out[17]:

Stop Detection for TrajectoryCollections¶

The process is the same as for individual trajectories.

In [18]:
%%time
detector = mpd.TrajectoryStopDetector(tc)
stop_points = detector.get_stop_points(min_duration=timedelta(seconds=120), max_diameter=100)
len(stop_points)
CPU times: total: 3.05 s
Wall time: 3.19 s
Out[18]:
31
In [19]:
ax = tc.plot(figsize=(7,7))
stop_points.plot(ax=ax, color='red')
Out[19]:
<Axes: >
No description has been provided for this image
In [ ]: