Mobility Analytics

goals of the tutorial

  • transform movement data into information
  • trajectory
  • map matching (look the solutions)

based on the data of Strava

requirements

  • python knowledge
  • geopandas
  • GPX format

status
“Where’s Wally?”


GNSS

Global Navigation Satellite System (GNSS) refers to a constellation of satellites providing signals from space that transmit positioning and timing data to GNSS receivers. The receivers then use this data to determine location. By definition, GNSS provides global coverage.

basic

with corrections


GPX Format

GPX, or GPS Exchange Format, is an XML schema designed as a common GPS data format for software applications. It can be used to describe waypoints, tracks, and routes.

units

position

latitude and longitude are expressed in decimal degrees, and elevation in meters, both using the WGS 84 datum (epsg 4326).

time

dates and times are not local time, but instead are Coordinated Universal Time (UTC) using ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)

data

(image by Berklas distributed under cc-by-sa 3.0)

  • wptType is an individual waypoint among a collection of points with no sequential relationship. It consists of the WGS 84 (GPS) coordinates of a point and possibly other descriptive information.
  • rteType is a route, an ordered list of routepoint (waypoints representing a series of significant turn or stage points) leading to a destination.
  • trkType is a track, made of at least one segment containing waypoints, that is, an ordered list of points describing a path. A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.

details about the format on fhe officiale website

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" creator="Oregon 400t" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">
  <metadata>
    <link href="http://www.garmin.com">
      <text>Garmin International</text>
    </link>
    <time>2009-10-17T22:58:43Z</time>
  </metadata>
  <trk>
    <name>Example GPX Document</name>
    <trkseg>
      <trkpt lat="47.644548" lon="-122.326897">
        <ele>4.46</ele>
        <time>2009-10-17T18:37:26Z</time>
      </trkpt>
      <trkpt lat="47.644548" lon="-122.326897">
        <ele>4.94</ele>
        <time>2009-10-17T18:37:31Z</time>
      </trkpt>
      <trkpt lat="47.644548" lon="-122.326897">
        <ele>6.87</ele>
        <time>2009-10-17T18:37:34Z</time>
      </trkpt>
    </trkseg>
  </trk>
</gpx>

information from wikipedia


Commmon problems

Missing Data

  • Temporal gaps
  • Spatial gaps

Incorrect data:

  • location or time is incorrect in the data frame

Outliers:

  • Too fast movements
  • Incorrect order of temporal information

Setup

try:
  import gpxpy
except ModuleNotFoundError as e:
  !pip install gpxpy==1.4.2
  import gpxpy

if gpxpy.__version__ != "1.4.2":
  !pip install -U gpxpy==1.4.2
try:
  import pygeos
except ModuleNotFoundError as e:
  !pip install pygeos==0.13
  import pygeos
  
if pygeos.__version__ != "0.13":
  !pip install -U pygeos==0.13
try:
  import mapclassify
except ModuleNotFoundError as e:
  !pip install mapclassify==2.4.3
  import mapclassify
  
if mapclassify.__version__ != "2.4.3":
  !pip install -U mapclassify==2.4.3
try:
  import movingpandas as mpd
except ModuleNotFoundError as e:
  !pip install movingpandas==0.7.rc1
  import movingpandas as mpd

if mpd.__version__ != "0.7.rc1":
  !pip install -U movingpandas==0.7.rc1
import os
os.environ["USE_PYGEOS"] = "0"
import gpxpy
import urllib.request
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
from datetime import datetime,timedelta

import matplotlib.pyplot as plt
from shapely.geometry import Point
import warnings
warnings.filterwarnings('ignore') # ignore warnings

Let’s start to play with GPX files

Download the data

If you are registered and the author permits the download you can download the gps track but and investigate with GPX Studio without timestamps

gpx-strava.png]

download file

or … you can use Strava To Gpx

gpx-full.png]

download file

investigate the file

gpx_url = "https://gist.githubusercontent.com/napo/4d0c14585da0ef96f263faa0e8d97ff5/raw/efba18f806459b462a1e485023cdd01c4d20e01a/strava_full.gpx"
gpx_file = "strava_full.gpx"
urllib.request.urlretrieve(gpx_url,gpx_file)
gpx_file = open(gpx_file, 'r')
gpx = gpxpy.parse(gpx_file)
gpx.creator
'https://www.mapstogpx.com/strava'
len(gpx.tracks)
1
len(gpx.tracks[0].segments)
1
segment = gpx.tracks[0].segments[0]
segment
GPXTrackSegment(points=[...])
type(segment.points)
list
segment.points[0].latitude
45.900222
segment.points[0].longitude
10.847826
segment.points[0].elevation
96.5

creation of a pandas DataFrame

data = []
for point_idx, point in enumerate(segment.points):
    data.append([point.longitude, point.latitude,
                point.elevation, point.time]) 

columns = ['longitude', 'latitude', 'altitude', 'time'] 
gpx_tracks = pd.DataFrame(data, columns=columns)
# Strava to GPX assign the date of the download of the file as timestamp and SimpleTZ("Z") as TimeZone.
# we can remove it
gpx_tracks['time'] = gpx_tracks['time'].apply(lambda x: x.replace(tzinfo=None))

# index creation needed by MovingPandas
df = gpx_tracks.set_index('time')

travel time

start_point = data[0]
end_point = data[-1]
# travel time
# steps to calcolate the difference of time from the timestamp in the first point to the last.
travel_time = end_point[3]-start_point[3]
travel_time_seconds = timedelta(seconds=travel_time.total_seconds())
travel_time_hours = str(timedelta(seconds=travel_time_seconds.seconds))
print_travel_time = travel_time_hours.split(':')
print("travel time %sh %s' %s''" % (print_travel_time[0], print_travel_time[1], print_travel_time[2]))
travel time 2h 34' 41''

reverse geocoding

from geopy.geocoders import Nominatim 

latlon = str(start_point[1]) + "," + str(start_point[0])
geolocator = Nominatim(user_agent="geospatial course unitn")
starting_place = geolocator.reverse(latlon)
starting_place
Location(Residenza Gardaverde, Via Luigi Pigarelli, Pasina, Varone, Riva del Garda, Comunità Alto Garda e Ledro, Provincia di Trento, Trentino-Alto Adige/Südtirol, 38066, Italia, (45.900411399999996, 10.847324175675148, 0.0))
latlon = str(end_point[1]) + "," + str(end_point[0])
geolocator = Nominatim(user_agent="geospatial course unitn")
arrival_place = geolocator.reverse(latlon)
arrival_place
Location(Pronto Soccorso Ospedale di Arco, Viale dei Capitelli, Chiarano, Arco, Comunità Alto Garda e Ledro, Provincia di Trento, Trentino-Alto Adige/Südtirol, 38062, Italia, (45.919077, 10.875114, 0.0))

Analyzing trajectories

# GeoDataFrame for MovingPandas
geo_df = gpd.GeoDataFrame(df, 
crs=4326,
geometry= gpd.points_from_xy(df.longitude, df.latitude, df.altitude))
geo_df.plot()
plt.show()

png

trajectory = mpd.Trajectory(geo_df, 0)
trajectory
Trajectory 0 (2021-10-12 12:01:38 to 2021-10-12 14:36:19) | Size: 5532 | Length: 40316.6m
Bounds: (10.731099, 45.861882, 10.875513, 45.919142)
LINESTRING Z (10.847826 45.900222 96.5, 10.84785 45.900264 96.5, 10.847843 45.900125 96.5, 10.847836
# object trajectory from MovingPandas
trajectory = mpd.Trajectory(geo_df, 1)
trajectory.plot()
plt.show()

png

trajectory.add_speed(overwrite=True)
trajectory.df
longitude latitude altitude geometry speed
time
2021-10-12 12:01:38 10.847826 45.900222 96.5 POINT Z (10.84783 45.90022 96.50000) 5.026084
2021-10-12 12:01:39 10.847850 45.900264 96.5 POINT Z (10.84785 45.90026 96.50000) 5.026084
2021-10-12 12:01:40 10.847843 45.900125 96.5 POINT Z (10.84784 45.90013 96.50000) 15.459309
2021-10-12 12:01:41 10.847836 45.899890 96.5 POINT Z (10.84784 45.89989 96.50000) 26.125749
2021-10-12 12:01:42 10.847835 45.899786 96.5 POINT Z (10.84783 45.89979 96.50000) 11.559794
... ... ... ... ... ...
2021-10-12 14:34:13 10.875184 45.919097 91.6 POINT Z (10.87518 45.91910 91.60000) 0.765202
2021-10-12 14:34:20 10.875213 45.919122 91.6 POINT Z (10.87521 45.91912 91.60000) 0.510754
2021-10-12 14:34:30 10.875186 45.919142 91.6 POINT Z (10.87519 45.91914 91.60000) 0.305432
2021-10-12 14:35:45 10.875151 45.919126 91.6 POINT Z (10.87515 45.91913 91.60000) 0.043276
2021-10-12 14:36:19 10.875091 45.919049 91.6 POINT Z (10.87509 45.91905 91.60000) 0.286540

5532 rows × 5 columns

#trajectory.hvplot(geo=True, tiles='OSM', line_width=5) #, frame_width=300, frame_height=300)
# conversion m/s to km/h
def ms_to_km(ms):
    v = (ms) * (60*60)/1000
    return v
# km/h column
trajectory.df['kmh'] = trajectory.df['speed'].apply(ms_to_km)
trajectory.plot(column="kmh", linewidth=5, capstyle='round', legend=True)
plt.show()

png

Extracting a moving object’s position was at a certain time

trajectory
Trajectory 1 (2021-10-12 12:01:38 to 2021-10-12 14:36:19) | Size: 5532 | Length: 40316.6m
Bounds: (10.731099, 45.861882, 10.875513, 45.919142)
LINESTRING Z (10.847826 45.900222 96.5, 10.84785 45.900264 96.5, 10.847843 45.900125 96.5, 10.847836
trajectory.get_position_at(datetime(2021,10,12,12,6,0), method="nearest")

svg

print(trajectory.get_position_at(datetime(2021,10,12,12,6,0), method="nearest"))
POINT Z (10.840998 45.888594 81.1)
print(trajectory.get_position_at(datetime(2021,10,12,12,6,0), method="nearest"))
print(trajectory.get_position_at(datetime(2021,10,12,12,6,0), method="interpolated"))
print(trajectory.get_position_at(datetime(2021,10,12,12,6,0), method="ffill")) # from the previous row
print(trajectory.get_position_at(datetime(2021,10,12,12,6,0), method="bfill")) # from the following row
POINT Z (10.840998 45.888594 81.1)
POINT Z (10.840998 45.888594 81.1)
POINT Z (10.840998 45.888594 81.1)
POINT Z (10.840998 45.888594 81.1)

Extracting trajectory segments based on time

segment_clip = trajectory.get_segment_between(datetime(2021,10,12,13,36,1),datetime(2021,10,12,13,38,51))
print(segment_clip)
Trajectory 1_2021-10-12 13:36:01 (2021-10-12 13:36:01 to 2021-10-12 13:38:49) | Size: 158 | Length: 1457.3m
Bounds: (10.832986, 45.870462, 10.839058, 45.879066)
LINESTRING Z (10.832986 45.870462 160.3, 10.833045 45.870534 159.9, 10.83311 45.870598 159.5, 10.833
segment_clip.plot()
plt.show()

png

Extracting trajectory segments based on geometry (i.e. clipping)

from shapely.geometry import Point, Polygon
latitude=45.88880
longitude=10.84269
p = Point(longitude, latitude)
point = gpd.GeoDataFrame(pd.DataFrame(), 
    crs=4326,
    geometry= [p])
distance = 10 # meters
# create a bounding box
xmin, ymax, xmax, ymin = point.to_crs(epsg=32632).buffer(distance).geometry.to_crs(epsg=4326).iloc[0].bounds
polygon = Polygon([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin)])
polygon

svg

intersections = trajectory.clip(polygon)
intersections
TrajectoryCollection with 3 trajectories
intersections.trajectories[0].plot(linewidth=5, capstyle='round')
plt.show()

png

intersections.trajectories[1].plot(linewidth=5, capstyle='round')
plt.show()

png

intersections.trajectories[2].plot(linewidth=5, capstyle='round')
plt.show()

png

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:

duration 15 minutes

split = mpd.ObservationGapSplitter(trajectory).split(gap=timedelta(minutes=15))
for traj in split:
    print(traj)
Trajectory 1_0 (2021-10-12 12:01:38 to 2021-10-12 13:47:11) | Size: 5070 | Length: 34131.1m
Bounds: (10.731099, 45.861882, 10.84785, 45.901398)
LINESTRING Z (10.847826 45.900222 96.5, 10.84785 45.900264 96.5, 10.847843 45.900125 96.5, 10.847836
Trajectory 1_2 (2021-10-12 14:24:10 to 2021-10-12 14:36:19) | Size: 461 | Length: 6182.5m
Bounds: (10.837456, 45.887391, 10.875513, 45.919142)
LINESTRING Z (10.842638 45.888938 79.4, 10.842624 45.888947 79.5, 10.842587 45.888957 79.5, 10.84259
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)

png

1km

split = mpd.StopSplitter(trajectory).split(min_duration=timedelta(minutes=15), max_diameter=30, min_length=1000)
for traj in split:
    print(traj)
Trajectory 1_2021-10-12 12:01:38 (2021-10-12 12:01:38 to 2021-10-12 13:47:11) | Size: 5070 | Length: 34131.1m
Bounds: (10.731099, 45.861882, 10.84785, 45.901398)
LINESTRING Z (10.847826 45.900222 96.5, 10.84785 45.900264 96.5, 10.847843 45.900125 96.5, 10.847836
Trajectory 1_2021-10-12 14:24:50 (2021-10-12 14:24:50 to 2021-10-12 14:36:19) | Size: 449 | Length: 6131.0m
Bounds: (10.837456, 45.887391, 10.875513, 45.919142)
LINESTRING Z (10.842435 45.889193 81.1, 10.842394 45.889205 81.2, 10.842355 45.889195 81.3, 10.84228
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)

png

Stop detection

detector = mpd.TrajectoryStopDetector(trajectory)
%time
CPU times: user 5 µs, sys: 0 ns, total: 5 µs
Wall time: 9.54 µs
#traj_plot = trajectory.hvplot(title='Trajectory {}'.format(trajectory.id), line_width=7.0, tiles='CartoLight', color='slategray', frame_width=350, frame_height=350) 
#traj_plot

stop duration

%%time
stop_durations = detector.get_stop_time_ranges(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: user 14.7 s, sys: 33.7 ms, total: 14.7 s
Wall time: 14.7 s
for x in stop_durations: 
    print(x)
Traj 1: 2021-10-12 12:40:59 - 2021-10-12 12:41:59 (duration: 0 days 00:01:00)
Traj 1: 2021-10-12 12:45:28 - 2021-10-12 12:46:40 (duration: 0 days 00:01:12)
Traj 1: 2021-10-12 12:46:44 - 2021-10-12 12:47:51 (duration: 0 days 00:01:07)
Traj 1: 2021-10-12 12:47:56 - 2021-10-12 12:49:26 (duration: 0 days 00:01:30)
Traj 1: 2021-10-12 12:50:03 - 2021-10-12 12:51:04 (duration: 0 days 00:01:01)
Traj 1: 2021-10-12 12:51:09 - 2021-10-12 12:52:47 (duration: 0 days 00:01:38)
Traj 1: 2021-10-12 13:41:59 - 2021-10-12 14:25:49 (duration: 0 days 00:43:50)
Traj 1: 2021-10-12 14:34:30 - 2021-10-12 14:36:19 (duration: 0 days 00:01:49)

stop points

%%time
stop_points = detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: user 14.6 s, sys: 24.5 ms, total: 14.6 s
Wall time: 14.6 s
stop_points
geometry start_time end_time traj_id duration_s
stop_id
1_2021-10-12 12:40:59 POINT Z (10.78989 45.86505 462.90000) 2021-10-12 12:40:59 2021-10-12 12:41:59 1 60.0
1_2021-10-12 12:45:28 POINT Z (10.78315 45.86742 518.50000) 2021-10-12 12:45:28 2021-10-12 12:46:40 1 72.0
1_2021-10-12 12:46:44 POINT Z (10.78217 45.86820 533.70000) 2021-10-12 12:46:44 2021-10-12 12:47:51 1 67.0
1_2021-10-12 12:47:56 POINT Z (10.78158 45.86913 550.40000) 2021-10-12 12:47:56 2021-10-12 12:49:26 1 90.0
1_2021-10-12 12:50:03 POINT Z (10.77957 45.86942 579.00000) 2021-10-12 12:50:03 2021-10-12 12:51:04 1 61.0
1_2021-10-12 12:51:09 POINT Z (10.77831 45.86930 592.90000) 2021-10-12 12:51:09 2021-10-12 12:52:47 1 98.0
1_2021-10-12 13:41:59 POINT Z (10.84269 45.88890 79.40000) 2021-10-12 13:41:59 2021-10-12 14:25:49 1 2630.0
1_2021-10-12 14:34:30 POINT Z (10.87519 45.91914 91.60000) 2021-10-12 14:34:30 2021-10-12 14:36:19 1 109.0
#stop_point_plot = traj_plot * stop_points.hvplot(geo=True, size='duration_s', color='deeppink')
#stop_point_plot

stop segments

%%time
stops = detector.get_stop_segments(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: user 14.6 s, sys: 16.5 ms, total: 14.6 s
Wall time: 14.6 s
stops
TrajectoryCollection with 8 trajectories
#stop_segment_plot = stop_point_plot * stops.hvplot( size=200, line_width=7.0, tiles=None, color='orange') 
#stop_segment_plot

split a stop

%%time
split = mpd.StopSplitter(trajectory).split(min_duration=timedelta(seconds=60), max_diameter=100)

CPU times: user 17.7 s, sys: 18.5 ms, total: 17.7 s
Wall time: 17.8 s
split
TrajectoryCollection with 8 trajectories
for segment in split:
    print(segment)

Trajectory 1_2021-10-12 12:01:38 (2021-10-12 12:01:38 to 2021-10-12 12:40:59) | Size: 1859 | Length: 10497.8m
Bounds: (10.789892, 45.861981, 10.84785, 45.901398)
LINESTRING Z (10.847826 45.900222 96.5, 10.84785 45.900264 96.5, 10.847843 45.900125 96.5, 10.847836
Trajectory 1_2021-10-12 12:41:59 (2021-10-12 12:41:59 to 2021-10-12 12:45:28) | Size: 150 | Length: 647.8m
Bounds: (10.78315, 45.865551, 10.788879, 45.867858)
LINESTRING Z (10.788848 45.865551 478.6, 10.788839 45.865583 478.8, 10.788834 45.865623 479.1, 10.78
Trajectory 1_2021-10-12 12:46:40 (2021-10-12 12:46:40 to 2021-10-12 12:46:44) | Size: 3 | Length: 8.3m
Bounds: (10.782169, 45.868145, 10.782239, 45.868201)
LINESTRING Z (10.782239 45.868145 533, 10.782199 45.868181 533.4, 10.782169 45.868201 533.7)
Trajectory 1_2021-10-12 12:47:51 (2021-10-12 12:47:51 to 2021-10-12 12:47:56) | Size: 3 | Length: 8.5m
Bounds: (10.781579, 45.869051, 10.781585, 45.869127)
LINESTRING Z (10.781579 45.869051 549, 10.781585 45.869089 549.7, 10.781585 45.869127 550.4)
Trajectory 1_2021-10-12 12:49:26 (2021-10-12 12:49:26 to 2021-10-12 12:50:03) | Size: 20 | Length: 81.9m
Bounds: (10.779572, 45.869424, 10.780497, 45.869678)
LINESTRING Z (10.780497 45.869637 571.2, 10.780459 45.869661 571.6, 10.780419 45.869664 572, 10.7803
Trajectory 1_2021-10-12 12:51:04 (2021-10-12 12:51:04 to 2021-10-12 12:51:09) | Size: 3 | Length: 8.4m
Bounds: (10.778313, 45.869304, 10.778396, 45.869351)
LINESTRING Z (10.778396 45.869351 592.1, 10.778364 45.869326 592.4, 10.778313 45.869304 592.9)
Trajectory 1_2021-10-12 12:52:47 (2021-10-12 12:52:47 to 2021-10-12 13:41:59) | Size: 2808 | Length: 21940.7m
Bounds: (10.731099, 45.861882, 10.842706, 45.888921)
LINESTRING Z (10.77706 45.869333 613.9, 10.77702 45.869337 614.4, 10.77698 45.869345 614.8, 10.77694
Trajectory 1_2021-10-12 14:25:49 (2021-10-12 14:25:49 to 2021-10-12 14:34:30) | Size: 423 | Length: 6002.9m
Bounds: (10.837456, 45.887391, 10.875513, 45.919142)
LINESTRING Z (10.842126 45.888566 79.1, 10.842008 45.888542 79.5, 10.841949 45.888514 79.7, 10.84182

Generalizing trajectories

Douglas-Peucker algorithm

trajectory.plot(column='speed', linewidth=5, capstyle='round', figsize=(9,3), legend=True, vmax=20)
plt.show()

png

generalized_trajectory = mpd.DouglasPeuckerGeneralizer(trajectory).generalize(tolerance=0.001)
generalized_trajectory.plot(column='speed', linewidth=5, capstyle='round', figsize=(9,3), legend=True, vmax=20)
plt.show()

png

print('Original length: %s'%(trajectory.get_length()))
print('Generalized length: %s'%(generalized_trajectory.get_length()))
Original length: 40316.62984544139
Generalized length: 37390.63448916281

alternative down-sample the trajectory to ensure a certain time delta between records

time_generalized = mpd.MinTimeDeltaGeneralizer(trajectory).generalize(tolerance=timedelta(minutes=10))
time_generalized.plot(column='speed', linewidth=5, capstyle='round', figsize=(9,3), legend=True, vmax=20)
plt.show()

png

time_generalized.to_point_gdf().head(10)
longitude latitude altitude geometry speed kmh
time
2021-10-12 12:01:38 10.847826 45.900222 96.5 POINT Z (10.84783 45.90022 96.50000) 5.026084 18.093904
2021-10-12 12:11:38 10.837393 45.874687 118.6 POINT Z (10.83739 45.87469 118.60000) 4.446633 16.007878
2021-10-12 12:21:38 10.830129 45.862466 244.0 POINT Z (10.83013 45.86247 244.00000) 3.863094 13.907137
2021-10-12 12:31:38 10.813936 45.864505 371.3 POINT Z (10.81394 45.86451 371.30000) 3.815148 13.734534
2021-10-12 12:41:38 10.788925 45.865013 477.9 POINT Z (10.78893 45.86501 477.90000) 3.525257 12.690925
2021-10-12 12:51:39 10.778118 45.869216 598.4 POINT Z (10.77812 45.86922 598.40000) 1.619121 5.828837
2021-10-12 13:01:39 10.746818 45.869637 657.8 POINT Z (10.74682 45.86964 657.80000) 8.849716 31.858976
2021-10-12 13:11:39 10.741853 45.885748 656.5 POINT Z (10.74185 45.88575 656.50000) 6.003111 21.611198
2021-10-12 13:21:39 10.774246 45.870862 637.8 POINT Z (10.77425 45.87086 637.80000) 10.406578 37.463682
2021-10-12 13:31:39 10.827858 45.863224 301.4 POINT Z (10.82786 45.86322 301.40000) 10.652206 38.347942
trajectory.to_point_gdf().head(10)
longitude latitude altitude geometry speed kmh
time
2021-10-12 12:01:38 10.847826 45.900222 96.5 POINT Z (10.84783 45.90022 96.50000) 5.026084 18.093904
2021-10-12 12:01:39 10.847850 45.900264 96.5 POINT Z (10.84785 45.90026 96.50000) 5.026084 18.093904
2021-10-12 12:01:40 10.847843 45.900125 96.5 POINT Z (10.84784 45.90013 96.50000) 15.459309 55.653513
2021-10-12 12:01:41 10.847836 45.899890 96.5 POINT Z (10.84784 45.89989 96.50000) 26.125749 94.052696
2021-10-12 12:01:42 10.847835 45.899786 96.5 POINT Z (10.84783 45.89979 96.50000) 11.559794 41.615259
2021-10-12 12:01:44 10.847798 45.899773 96.5 POINT Z (10.84780 45.89977 96.50000) 1.607194 5.785899
2021-10-12 12:01:54 10.847751 45.899764 96.5 POINT Z (10.84775 45.89976 96.50000) 0.378204 1.361533
2021-10-12 12:01:56 10.847786 45.899790 96.5 POINT Z (10.84779 45.89979 96.50000) 1.982969 7.138687
2021-10-12 12:01:57 10.847749 45.899800 96.5 POINT Z (10.84775 45.89980 96.50000) 3.078936 11.084170
2021-10-12 12:02:01 10.847702 45.899803 96.5 POINT Z (10.84770 45.89980 96.50000) 0.915638 3.296297

Exercise

the trips_truck.gpkg dataset contains the routes of a garbage vehicle in April 2018.

Based on this data:

  • identify the longest route carried out
  • identify the places of the daily departure and arrival points
  • show the km traveled day by day
  • identify the breaks carried out in the shortest route of the third week and in the longest one of the last
  • identify the longest route of the third week of the month on the OpenStreetMap road graph

Updated: