This tutorial uses data published on Movebank, specifically: Navigation experiments in lesser black-backed gulls (data from Wikelski et al. 2015)-gps.csv
This tutorial covers:
%matplotlib inline
from IPython.core.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))
import urllib
import os
import pandas as pd
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import sys
sys.path.append("..")
import movingpandas as mpd
import warnings
warnings.simplefilter("ignore")
%%time
df = read_file('data/demodata_gulls.gpkg')
wgs84 = df.crs
df['t'] = pd.to_datetime(df['timestamp'])
df = df.set_index('t')
print("Finished reading {}".format(len(df)))
This is what the data looks like:
df.head()
df.plot()
Let's see how many individuals we have in the dataset:
df['individual-local-identifier'].unique()
The records per individual are not evenly distributed:
df['individual-local-identifier'].value_counts().plot(kind='bar', figsize=(17,3))
Finally, let's create trajectories:
MIN_LENGTH = 100 # meters
traj_collection = mpd.TrajectoryCollection(df, 'individual-local-identifier', min_length=MIN_LENGTH)
all_trajectories = traj_collection.trajectories
print("Finished creating {} trajectories".format(len(all_trajectories)))
Let's pick out a specific individual. For example, '91916A' is the individual with most records in our dataset:
filtered = traj_collection.filter('individual-local-identifier', '91916A')
my_traj = filtered.trajectories[0].copy()
my_traj.df.head()
my_traj.hvplot(line_width=2)
This individual has been travelling back and forth for quite a few years!
One way to take a closer look at this individual's travels is to split the overall track into yearly trips:
trips_by_year = mpd.TemporalSplitter(filtered).split(mode='year')
for trip in trips_by_year:
print(trip.id)
Now we can explore individual years:
one_year = trips_by_year.get_trajectory('91916A_2010-12-31 00:00:00')
print(one_year)
one_year.hvplot(line_width=5.0, c='speed', cmap='RdYlGn', colorbar=True, clim=(0,15))
Let's see where this individual was on a specific day:
def plot_location_at_timestamp(traj, t, fig_size=300):
loc = GeoDataFrame([traj.get_row_at(t)])
return (loc.hvplot(geo=True, tiles='OSM', size=200, color='red', width=fig_size, height=fig_size) *
traj.hvplot(line_width=1.0, color='black', tiles=False, width=fig_size, height=fig_size))
( plot_location_at_timestamp(one_year, datetime(2010,9,1)) +
plot_location_at_timestamp(one_year, datetime(2010,10,1)) +
plot_location_at_timestamp(one_year, datetime(2010,11,1)) )
Of course, it might also be of interest to see the different locations on a certain day each year:
def plot_location_at_day_of_year(traj, month, day, ax=None):
ts = [datetime(year, month, day) for year in traj.df.index.year.unique()]
return plot_locations_at_timestamps(traj, ts, ax=ax)
def plot_locations_at_timestamps(traj, ts, ax=None):
loc = GeoDataFrame([traj.get_row_at(t) for t in ts])
loc['date_label'] = loc.index.strftime('%Y-%m-%d')
return (loc.hvplot(c='date_label', size=200, geo=True, tiles='OSM') *
traj.hvplot(line_width=1.0, color='black', geo=True, tiles=False) )
plot_location_at_day_of_year(my_traj, month=10, day=1)
It's pretty clear that this individual does not follow the same schedule and route every year. However, it seems to always be heading to the same area Red Sea coast to spend the winter there.
Let's find its arrival times in this area:
area_of_interest = Polygon([(30, 25), (50, 25), (50, 15), (30, 15), (30, 25)])
plotted_area_of_interest = GeoDataFrame(pd.DataFrame([{'geometry': area_of_interest, 'id': 1}]), crs=wgs84).hvplot(geo=True, color='yellow', alpha=0.5)
arrivals = [traj for traj in my_traj.clip(area_of_interest)]
print("Found {} arrivals".format(len(arrivals)))
for traj in arrivals:
print("Individual '{}' arrived at {}".format(traj.df['individual-local-identifier'].iloc[0], traj.get_start_time()))
( plot_locations_at_timestamps(my_traj, [traj.get_start_time() for traj in arrivals]) * plotted_area_of_interest )
Multiple individuals travel to this area every year. Let's have a closer look:
def get_trajectories_by_year(trajs, year):
result = []
for traj in trajs:
if traj.get_start_time().year <= year_of_interest and traj.get_end_time().year >= year_of_interest:
result.append(traj)
return result
year_of_interest = 2010
arrivals = [ traj.clip(area_of_interest) for traj in all_trajectories ] # list of lists
arrivals = [ item for sublist in arrivals for item in sublist ] # flat list
relevant = get_trajectories_by_year(arrivals, year_of_interest)
print("Found {} arrivals".format(len(relevant)))
for traj in relevant:
print("Individual '{}' arrived at {} (duration: {})".format(
traj.df['individual-local-identifier'].iloc[0], traj.get_start_time().date(),
traj.get_end_time()-traj.get_start_time()))
Based on the duration of the individuals' trajectory segments within our area of interest, it looks like some individuals spend the winter here while others only pass through.
For example, Individual '91761A' passed through twice? What has it been up to?
def get_individual_traj_for_year(trajs, id, year):
individual = traj_collection.get_trajectory(id)
return individual.get_segment_between(datetime(year,1,1), datetime(year,12,31))
plotted_trajectory = get_individual_traj_for_year(all_trajectories, '91761A', year_of_interest).hvplot(color='black', line_width=1.0)
( plotted_trajectory * plotted_area_of_interest )
Turns out that this individual does not stay at the Red Sea but continues its journey into Africa.