Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
316 lines (224 sloc) 10.1 KB
---
output: github_document
---
# Tutorial 0: Getting started with MovingPandas
MovingPandas provides a trajectory datatype based on GeoPandas.
The project home is at https://github.com/anitagraser/movingpandas
This tutorial presents some of the trajectory manipulation and visualization functions implemented in MovingPandas.
After following this tutorial, you will have a basic understanding of what MovingPandas is and what it can be used for. You'll be ready to dive into application examples presented in the the follow-up tutorials:
* [Tutorial 1: Ship data analysis](1_ship_data_analysis.ipynb)
* [Tutorial 2: Bird migration analysis](2_bird_migration_analysis.ipynb)
## Introduction
MovingPandas follows the **trajectories = timeseries with geometries** approach of modeling movement data.
A MovingPandas trajectory can be interpreted as either a time series of points or a time series of line segments.
The line-based approach has many advantages for trajectory analysis and visualization. (For more detail, see e.g. Westermeier (2018))
![alt text](./data/trajectory_length.PNG "Trajectory length")
![alt text](./data/trajectory_context.PNG "Trajectory context")
![alt text](./data/trajectory_distance.PNG "Trajectory distance")
### References
* Graser, A. (2019). MovingPandas: Efficient Structures for Movement Data in Python. GI_Forum ‒ Journal of Geographic Information Science 2019, 1-2019, 54-68. doi:10.1553/giscience2019_01_s54. URL: https://www.austriaca.at/rootcollection?arp=0x003aba2b
* Westermeier, E.M. (2018). Contextual Trajectory Modeling and Analysis. Master Thesis, Interfaculty Department of Geoinformatics, University of Salzburg.
## Jupyter notebook setup
```{r, engine='python'}
import urllib
import os
import pandas as pd
import contextily as ctx
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from matplotlib import pyplot as plt
import sys
sys.path.append("..")
import movingpandas as mp
import warnings
warnings.simplefilter("ignore")
```
## Creating a trajectory from scratch
Trajectory objects consist of a trajectory ID and a GeoPandas GeoDataFrame with a DatetimeIndex. The data frame therefore represents the trajectory data as a Pandas time series with associated point locations (and optional further attributes).
Let's create a small toy trajectory to see how this works:
```{r, engine='python'}
df = pd.DataFrame([
{'geometry':Point(0,0), 't':datetime(2018,1,1,12,0,0)},
{'geometry':Point(6,0), 't':datetime(2018,1,1,12,6,0)},
{'geometry':Point(6,6), 't':datetime(2018,1,1,12,10,0)},
{'geometry':Point(9,9), 't':datetime(2018,1,1,12,15,0)}
]).set_index('t')
geo_df = GeoDataFrame(df, crs={'init': '31256'})
toy_traj = mp.Trajectory(1, geo_df)
toy_traj.df
```
We can access **key information** about our trajectory by looking at the print output:
```{r, engine='python'}
print(toy_traj)
```
We can also access the trajectories GeoDataFrame:
```{r, engine='python'}
toy_traj.df
```
## Visualizing trajectories
To **visualize the trajectory**, we can turn it into a linestring.
(The notebook environment automatically plots Shapely geometry objects like the LineString returned by to_linestring().)
```{r, engine='python'}
toy_traj.to_linestring()
```
We can **compute the speed** of movement along the trajectory (between consecutive points). The values are in meters per second:
```{r, engine='python'}
toy_traj.add_speed(overwrite=True)
toy_traj.df
```
We can also visualize the speed values:
```{r, engine='python'}
toy_traj.plot(column="speed", linewidth=5, capstyle='round', legend=True)
```
In contrast to the earlier example where we visualized the whole trajectory as one linestring, the trajectory plot() function draws each line segment individually and thus each can have a different color.
## Analyzing trajectories
MovingPandas provides many functions for trajectory analysis.
To see all available functions of the MovingPandas.Trajectory class use:
```{r, engine='python'}
dir(mp.Trajectory)
```
Functions that start with an underscore (e.g. ```__str__```) should not be called directly. All other functions are free to use.
### Extracting a moving object's position was at a certain time
For example, let's have a look at the get_position_at() function:
```{r, engine='python'}
help(mp.Trajectory.get_position_at)
```
When we call this method, the resulting point is directly rendered:
```{r, engine='python'}
toy_traj.get_position_at(datetime(2018,1,1,12,6,0), method="nearest")
```
To see its coordinates, we can look at the print output:
```{r, engine='python'}
print(toy_traj.get_position_at(datetime(2018,1,1,12,6,0), method="nearest"))
```
The method parameter describes what the function should do if there is no entry in the trajectory GeoDataFrame for the specified timestamp.
For example, there is no entry at 2018-01-01 12:07:00
```{r, engine='python'}
toy_traj.df
```
```{r, engine='python'}
print(toy_traj.get_position_at(datetime(2018,1,1,12,7,0), method="nearest"))
print(toy_traj.get_position_at(datetime(2018,1,1,12,7,0), method="interpolated"))
print(toy_traj.get_position_at(datetime(2018,1,1,12,7,0), method="ffill")) # from the previous row
print(toy_traj.get_position_at(datetime(2018,1,1,12,7,0), method="bfill")) # from the following row
```
### Extracting trajectory segments based on time or geometry (i.e. clipping)
First, let's extract the trajectory segment for a certain time period:
```{r, engine='python'}
segment = toy_traj.get_segment_between(datetime(2018,1,1,12,6,0),datetime(2018,1,1,12,12,0))
print(segment)
```
Now, let's extract the trajectory segment that intersects with a given polygon:
```{r, engine='python'}
xmin, xmax, ymin, ymax = 2, 8, -10, 5
polygon = Polygon([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin)])
polygon
```
```{r, engine='python'}
intersections = toy_traj.clip(polygon)
print(intersections[0])
```
```{r, engine='python'}
intersections[0].plot(linewidth=5, capstyle='round')
```
## Beyond toy trajectories: loading trajectory data from GeoPackage
The MovingPandas repository contains a demo GeoPackage file that can be loaded as follows:
```{r, engine='python'}
url = 'https://github.com/anitagraser/movingpandas/raw/master/tutorials/data/demodata_geolife.gpkg'
filename = url.split('/')[-1]
response = urllib.request.urlopen(url)
content = response.read()
with open(filename, 'wb' ) as f:
f.write( content )
```
```{r, engine='python'}
assert(os.path.exists(filename))
```
Now, we can use GeoPandas' read_file() function to read the GeoPackage and construct the Trajectories:
```{r, engine='python'}
t_start = datetime.now()
df = read_file(filename)
df['t'] = pd.to_datetime(df['t'])
df = df.set_index('t').tz_localize(None)
print("Finished reading {} rows in {}".format(len(df),datetime.now() - t_start))
t_start = datetime.now()
trajectories = []
for key, values in df.groupby(['trajectory_id']):
trajectory = mp.Trajectory(key, values)
print(trajectory)
trajectories.append(trajectory)
print("Finished creating {} trajectories in {}".format(len(trajectories),datetime.now() - t_start))
```
Let's look at one of those trajectories:
```{r, engine='python'}
trajectories[1].plot(column='speed', linewidth=5, capstyle='round', figsize=(9,3), legend=True)
```
```{r}
# trajectories[1].plot(with_basemap=True, linewidth=5.0, capstyle='round', figsize=(9,9))
```
### Finding intersections with a Shapely polygon
```{r, engine='python'}
xmin, xmax, ymin, ymax = 116.3685035,116.3702945,39.904675,39.907728
polygon = Polygon([(xmin,ymin), (xmin,ymax), (xmax,ymax), (xmax,ymin), (xmin,ymin)])
intersections = []
for traj in trajectories:
for intersection in traj.clip(polygon):
intersections.append(intersection)
print("Found {} intersections".format(len(intersections)))
```
```{r, engine='python'}
intersections[2].plot(linewidth=5.0, capstyle='round')
```
## 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:
```{r, engine='python'}
my_traj = trajectories[1]
print(my_traj)
my_traj.plot(linewidth=5.0, capstyle='round')
```
```{r, engine='python'}
split = my_traj.split_by_observation_gap(timedelta(minutes=5))
for traj in split:
print(traj)
```
```{r, engine='python'}
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')
```
## Generalizing trajectories
To reduce the size of trajectory objects, we can generalize them, for example, using the Douglas-Peucker algorithm:
```{r, engine='python'}
original_traj = trajectories[1]
print(original_traj)
```
```{r, engine='python'}
original_traj.plot(column='speed', linewidth=5, capstyle='round', figsize=(9,3), legend=True)
```
Try different tolerance settings and observe the results in line geometry and therefore also length:
```{r, engine='python'}
generalized_traj = original_traj.generalize(mode='douglas-peucker', tolerance=0.001)
generalized_traj.plot(column='speed', linewidth=5, capstyle='round', figsize=(9,3), legend=True)
```
```{r, engine='python'}
print('Original length: %s'%(original_traj.get_length()))
print('Generalized length: %s'%(generalized_traj.get_length()))
```
An alternative generalization method is to down-sample the trajectory to ensure a certain time delta between records:
```{r, engine='python'}
time_generalized = original_traj.generalize(mode='min-time-delta', tolerance=timedelta(minutes=1))
time_generalized.plot(column='speed', linewidth=5, capstyle='round', figsize=(9,3), legend=True)
```
```{r, engine='python'}
time_generalized.df.head(10)
```
```{r, engine='python'}
original_traj.df.head(10)
```
## Continue exploring MovingPandas
* [Tutorial 1: Ship data analysis](1_ship_data_analysis.ipynb)
* [Tutorial 2: Bird migration analysis](2_bird_migration_analysis.ipynb)
```{r, engine='python'}
```
You can’t perform that action at this time.