Compute travel times with a detailed breakdown of the routing results#
Detailed itineraries#
In case you are interested in more detailed routing results, you can use
DetailedItineraries. In contrast to
TravelTimeMatrix, it reports individual trip
segments, and possibly multiple alternative routes for each trip.
As such, DetailedItineraries are structured
in a different way, too. It provides one row per trip segment, multiple trip
segments together constitute a trip option, of which there might be several per
from_id/to_id pair. The results also include information on the public
transport routes (e.g., bus line numbers) used on the trip, as well as a
shapely.geometry for each segment.
Detailed itineraries are computationally expensive
Computing detailed itineraries is significantly more time-consuming than calculating simple travel times. As such, think twice whether you actually need the detailed information output from this function, and how you might be able to limit the number of origins and destinations you need to compute.
For the examples below, to reduce computation effort, we use a sample of 3 origin points and one single destination (the railway station) in our sample data of Helsinki.
import geopandas
import r5py
import r5py.sampledata.helsinki
import shapely
population_grid = geopandas.read_file(r5py.sampledata.helsinki.population_grid)
RAILWAY_STATION = shapely.Point(24.941521, 60.170666)
transport_network = r5py.TransportNetwork(
r5py.sampledata.helsinki.osm_pbf,
[
r5py.sampledata.helsinki.gtfs,
]
)
import datetime
import r5py
origins = population_grid.sample(3).copy()
origins.geometry = origins.geometry.centroid
destinations = geopandas.GeoDataFrame(
{
"id": [1],
"geometry": [RAILWAY_STATION]
},
crs="EPSG:4326",
)
detailed_itineraries = r5py.DetailedItineraries(
transport_network,
origins=origins,
destinations=destinations,
departure=datetime.datetime(2022, 2, 22, 8, 30),
transport_modes=[r5py.TransportMode.TRANSIT, r5py.TransportMode.WALK],
snap_to_network=True,
)
Snap to network
If you read the code block above especially carefully, you may have noticed that
we added an option snap_to_network=True to
DetailedItineraries. This
option does exactly what it says on the outside: it attempts to snap all origin
and destination points to the transport network before routing. This can help
with points that come to lie in an otherwise inaccessible area, such as a fenced
area, a swamp, or the middle of a lake.
For a detailed description of the functionality, see the Advanced use page.
detailed_itineraries
| from_id | to_id | option | segment | transport_mode | departure_time | distance | travel_time | wait_time | feed | agency_id | route_id | start_stop_id | end_stop_id | geometry | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 44 | 1 | 0 | 0 | TransportMode.WALK | NaT | 469.066000 | 0 days 00:07:59 | 0 days 00:00:00 | NaN | NaN | NaN | NaN | NaN | LINESTRING (24.93415 60.16899, 24.93441 60.168... |
| 1 | 44 | 1 | 1 | 0 | TransportMode.WALK | 2022-02-22 08:32:12 | 86.922000 | 0 days 00:01:32 | 0 days 00:00:00 | NaN | NaN | NaN | NaN | NaN | LINESTRING (24.93415 60.16899, 24.93441 60.168... |
| 2 | 44 | 1 | 1 | 1 | TransportMode.TRAM | 2022-02-22 08:42:00 | 368.596683 | 0 days 00:02:00 | 0 days 00:01:44 | helsinki_gtfs | HSL | 1009 | 1040410 | 1020453 | LINESTRING (24.93566 60.16884, 24.9385 60.1698... |
| 3 | 44 | 1 | 1 | 2 | TransportMode.WALK | 2022-02-22 08:45:00 | 51.910000 | 0 days 00:00:55 | 0 days 00:00:00 | NaN | NaN | NaN | NaN | NaN | LINESTRING (24.9413 60.17036, 24.9413 60.17036... |
| 4 | 44 | 1 | 2 | 0 | TransportMode.WALK | 2022-02-22 08:32:12 | 86.922000 | 0 days 00:01:32 | 0 days 00:00:00 | NaN | NaN | NaN | NaN | NaN | LINESTRING (24.93415 60.16899, 24.93441 60.168... |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 538 | 86 | 1 | 59 | 1 | TransportMode.BUS | 2022-02-22 08:50:00 | 328.874077 | 0 days 00:01:00 | 0 days 00:08:16 | helsinki_gtfs | HSL | 1016 | 1010108 | 1010125 | LINESTRING (24.95798 60.17422, 24.95628 60.174... |
| 539 | 86 | 1 | 59 | 2 | TransportMode.WALK | 2022-02-22 08:52:00 | 802.496000 | 0 days 00:13:42 | 0 days 00:00:00 | NaN | NaN | NaN | NaN | NaN | LINESTRING (24.95208 60.17407, 24.95208 60.174... |
| 540 | 86 | 1 | 60 | 0 | TransportMode.WALK | 2022-02-22 08:32:12 | 594.075000 | 0 days 00:10:09 | 0 days 00:00:00 | NaN | NaN | NaN | NaN | NaN | LINESTRING (24.95722 60.17325, 24.95722 60.173... |
| 541 | 86 | 1 | 60 | 1 | TransportMode.BUS | 2022-02-22 08:44:00 | 391.396595 | 0 days 00:02:00 | 0 days 00:01:39 | helsinki_gtfs | HSL | 1071 | 1020106 | 1020201 | LINESTRING (24.94919 60.17345, 24.94914 60.173... |
| 542 | 86 | 1 | 60 | 2 | TransportMode.WALK | 2022-02-22 08:47:00 | 254.463000 | 0 days 00:04:28 | 0 days 00:00:00 | NaN | NaN | NaN | NaN | NaN | LINESTRING (24.94376 60.17192, 24.94374 60.171... |
543 rows × 15 columns
As you can see, the result contains much more information than earlier. Depending on your screen size, you might even have to scroll further right to see all columns.
For public transport routes or when a variety of
transport_modes are used, the
structure of the results is more complex: For each origin-destination pair, one
or more possible option is reported, which in turn can consist of one or more
segments. Both options and segments are numbered sequentially, starting at
0.
Each segment, then, represents one row in the results table, and provides information about the transport mode used for a segment, time travelled, possible wait time (before the departure of a public transport vehicle), information about the feed and agency, the route identifier, the starting and ending stop used, and finally a line geometry representing the travelled path.
See the following table for a complete list of columns contained in
DetailedItineraries:
from_id(same type asorigins["id"])the origin of the trip this segment belongs to
to_id(same type asdestinations["id"])the destination of the trip this segment belongs to
option(int)sequential number enumerating the the different trip options found. Each trip option consists of one or more trip segments. (starts with
0)segment(int)sequential number enumerating the segments the current trip option consists of. (starts with
0)transport_mode(r5py.TransportMode)the transport mode used on the current segment
departure_time(datetime.datetime)the departure date and time of the public transport vehicle used for the current segment;
NaTin case of other modes of transportdistance(float)the distance travelled on the current segment, in metres.
travel_time(datetime.timedelta)The time spent travelling on the current segment
wait_time(datetime.timedelta)if the current segment is a public transport vehicle: wait time between the arrival of the previous trip segment and the departure of the current segment.
feed(str)if the current segment is a public transport vehicle: the GTFS feed identifier used for this trip, which should match the filename provided. This is useful when a given transport network consists of multiple GTFS feeds.
agency_id(str)if the current segment is a public transport vehicle: the GTFS agency identifier found in the
agency.txtfile in the provided GTFS feed. Most feeds have just one agency, but multiple are possible.route_id(str)if the current segment is a public transport vehicle: the GTFS route id found in the
routes.txtfile in the provided GTFS feed.start_stop_id(str)if the current segment is a public transport vehicle: the GTFS stop id found in the
stops.txtwhich was used as the boarding stop for that vehicle.end_stop_id(str)if the current segment is a public transport vehicle: the GTFS stop id found in the
stops.txtwhich was used as the alighting stop for that vehicle.geometry(shapely.LineString)the path travelled on the current segment.
Visualise travel details#
It’s not difficult to plot the detailed routes in a map, however, a couple more
steps are needed than with simple travel times.
GeoDataFrame.explore() cannot handle
the column types r5py.TransportMode and datetime.timedelta -
the conversion is quick and easy, though:
detailed_itineraries["mode"] = detailed_itineraries.transport_mode.astype(str)
detailed_itineraries["travel time (min)"] = detailed_itineraries.travel_time.apply(
lambda t: round(t.total_seconds() / 60.0, 2)
)
detailed_itineraries["trip"] = detailed_itineraries.apply(
lambda row: f"{row.from_id} → railway station",
axis=1
)
detailed_routes_map = (
detailed_itineraries[
[
"geometry",
"distance",
"mode",
"travel time (min)",
"from_id",
"to_id",
"trip",
"option",
"segment",
]
]
.explore(
tooltip=["trip", "option", "segment", "mode", "travel time (min)", "distance"],
column="mode",
tiles="CartoDB.Positron",
style_kwds={
"weight": 3,
"opacity": 0.8,
},
highlight_kwds={
"weight": 6,
"opacity": 1,
},
)
)
Let’s also add the origins and the destination to the map:
import folium
import folium.plugins
import pandas
folium.Marker(
(RAILWAY_STATION.y, RAILWAY_STATION.x),
icon=folium.Icon(
color="green",
icon="train",
prefix="fa",
)
).add_to(detailed_routes_map)
points = geopandas.GeoDataFrame(
pandas.DataFrame(
{"id": detailed_itineraries.od_pairs["id_origin"].unique()}
)
.set_index("id")
.join(population_grid.set_index("id"))
.reset_index()
)
points.geometry = points.geometry.to_crs("EPSG:3875").centroid.to_crs("EPSG:4326")
points.apply(
lambda row: (
folium.Marker(
(row["geometry"].y, row["geometry"].x),
icon=folium.plugins.BeautifyIcon(
icon_shape="marker",
number=row["id"],
border_color="#728224",
text_color="#728224",
),
).add_to(detailed_routes_map)
),
axis=1,
)
detailed_routes_map
Export the detailed routes#
If you want to further analyse the resulting routes, for instance, in a desktop
GIS, you can export the
GeoDataFrame to a wide range of file
formats,
using the to_file() method.
Note that many geospatial file formats do not support
datetime.timedelta columns, or columns with custom objects, such as the
r5py.TransportMode data. Similar to the above example, with a few
simple steps we can convert the values accordingly:
detailed_itineraries["transport_mode"] = detailed_itineraries.transport_mode.astype(str)
detailed_itineraries["travel time (min)"] = detailed_itineraries.travel_time.apply(
lambda t: round(t.total_seconds() / 60.0, 2)
)
detailed_itineraries["wait time (min)"] = detailed_itineraries.wait_time.apply(
lambda t: round(t.total_seconds() / 60.0, 2)
)
# keep all columns except travel time and wait time (which we renamed to
# reflect the unit of measurement)
detailed_itineraries = detailed_itineraries[
[
"from_id",
"to_id",
"option",
"segment",
"transport_mode",
"departure_time",
"distance",
"travel time (min)",
"wait time (min)",
"feed",
"agency_id",
"route_id",
"geometry",
]
]
detailed_itineraries.to_file("detailed_itineraries.gpkg")