Tutorial 2: Travel time and carbon emission quantification for transit based trips#
Lesson objectives
This tutorial focuses on utilising r5py library to find shortest paths along the given street network based on travel times or distance by public transport. In addition, we will learn how to compute travel-related GHG emissions from transit-based travel.
Introduction to r5py#
In this tutorial, we will learn how to calculate travel times and detailed public transport travel itineraries with r5py between H3 hexgaons spread around the city center area of Helsinki, Finland. R5py is a Python library for routing and calculating travel time matrices on multimodal transport networks (walk, bike, public transport, and car).
It provides a simple and friendly interface to R5 (the Rapid Realistic Routing on Real-world and Reimagined networks) which is a routing engine developed by Conveyal. R5py is designed to interact with GeoPandas GeoDataFrames, and it is inspired by r5r which is a similar wrapper developed for R. R5py exposes some of R5’s functionality via its Python API, in a syntax similar to r5r’s. At the time of this writing, only the computation of travel time matrices has been fully implemented. Over time, r5py will be expanded to incorporate other functionalities from R5.
When calculating travel times with r5py, you typically need a couple of datasets:
A road network dataset from OpenStreetMap (OSM) in Protocolbuffer Binary (
.pbf) -format:This data is used for finding the fastest routes and calculating the travel times based on walking, cycling and driving. In addition, this data is used for walking/cycling legs between stops when routing with transit.
Hint: Sometimes you might need modify the OSM data beforehand, e.g. by cropping the data or adding special costs for travelling (e.g. for considering slope when cycling/walking). When doing this, you should follow the instructions at Conveyal website. For adding customized costs for pedestrian and cycling analyses, see this repository.
A transit schedule dataset in General Transit Feed Specification (GTFS.zip) -format (optional):
This data contains all the necessary information for calculating travel times based on public transport, such as stops, routes, trips and the schedules when the vehicles are passing a specific stop. You can read about GTFS standard from here.
Hint:
r5pycan also combine multiple GTFS files, as sometimes you might have different GTFS feeds representing e.g. the bus and metro connections.
Where to get these datasets?#
Here are a few places from where you can download the datasets for creating the routable network:
OpenStreetMap data in PBF-format:
pyrosm -library. Allows downloading data directly from Python (based on GeoFabrik and BBBike).
pydriosm -library. Allows downloading data directly from Python (based on GeoFabrik and BBBike).
GeoFabrik -website. Has data extracts for many pre-defined areas (countries, regions, etc).
BBBike -website. Has data extracts readily available for many cities across the world. Also supports downloading data by specifying your own area or interest.
Protomaps -website. Allows to download the data with custom extent by specifying your own area of interest.
GTFS data:
Transitfeeds -website. Easy to navigate and find GTFS data for different countries and cities. Includes current and historical GTFS data. Notice: The site will be depracated in the future.
Mobility Database -website. Will eventually replace TransitFeeds -website.
Transitland -website. Find data based on country, operator or feed name. Includes current and historical GTFS data.
import sys
sys.argv.append([
"--r5-classpath",
"https://github.com/DigitalGeographyLab/r5/releases/download/v6.9-post16-g1054c1e-20230619/r5-v6.9-post16-g1054c1e-20230619-all.jar"
])
import pandas as pd
import geopandas as gpd
import osmnx as ox
import r5py
import r5py.sampledata.helsinki
Load transport network using r5py#
Virtually all operations of r5py require a transport network. In this example, we use data from Helsinki metropolitan area, which you can easily obtain from the r5py.sampledata.helsinki library. The files will be downloaded automatically to a temporary folder on your computer when you call the variables *.osm_pbf and *.gtfs:
# Download OSM data
r5py.sampledata.helsinki.osm_pbf
DataSet('/Users/tenkanh2/.cache/r5py/kantakaupunki.osm.pbf')
# Download GTFS data
r5py.sampledata.helsinki.gtfs
DataSet('/Users/tenkanh2/.cache/r5py/helsinki_gtfs.zip')
To import the street and public transport networks, instantiate an r5py.TransportNetwork with the file paths to the OSM extract and the GTFS files:
from r5py import TransportNetwork
# Get the filepaths to sample data (OSM and GTFS)
helsinki_osm = r5py.sampledata.helsinki.osm_pbf
helsinki_gtfs = r5py.sampledata.helsinki.gtfs
transport_network = TransportNetwork(
# OSM data
helsinki_osm,
# A list of GTFS file(s)
[
helsinki_gtfs
]
)
At this stage, r5py has created the routable transport network and it is stored in the transport_network variable. We can now start using this network for doing the travel time calculations.
Load and prepare the origin and destination data (Locomizer)#
Let’s start by downloading a sample dataset into a geopandas GeoDataFrame that we can use as our destination locations. To make testing the library easier, we have prepared….
Recalling the single origin-destination locations from Tutorial 1#
We will first define the single origin and destination GeoDataFrame for theis tutorial. Let’s recall the origin and destination locations in our earlier tutorial and use those as our origin and destination locations here for demonstration purposes.
from shapely.geometry import Point, Polygon
import osmnx as ox
# Specify a couple of locations for origin and destination
# Origin
orig_y, orig_x = 60.16874416, 24.95721918
# Destination
dest_y, dest_x = 60.1622494, 24.9082137
# Create GeoDataFrames for the origin and destination
# In here it is important to give the locations an 'id' value
origin_Point = Point(orig_x, orig_y)
origin_Point_df = gpd.GeoDataFrame({"geometry": [origin_Point],
"name": "Origin",
"id": [0]},
index=[0],
crs="epsg:4326")
destination_Point = Point(dest_x, dest_y)
destination_Point_df = gpd.GeoDataFrame({"geometry": [destination_Point],
"name": "Destination",
"id": [1]},
index=[0],
crs="epsg:4326")
We can visualise the origin and the destination together:
# Plot the origin and destination
m = origin_Point_df.explore(color="red", marker_kwds={"radius": 12}, zoom_start=12)
m = destination_Point_df.explore(m=m,color="black", marker_kwds={"radius": 12})
m
The origin_Point_df and destination_Point_df GeoDataFrame contains a few columns, namely id, and geometry. The id column with unique values and geometry columns are required for r5py to work. If your input dataset does not have an id column with unique values, r5py will throw an error.
Compute travel time matrix from one to one location#
A travel time matrix is a dataset detailing the travel costs (e.g., time) between given locations (origins and destinations) in a study area. To compute a travel time matrix with r5py based on public transportation, we first need to initialize an r5py.TravelTimeMatrixComputer -object. As inputs, we pass following arguments for the TravelTimeMatrixComputer:
transport_network, which we created in the previous step representing the routable transport network.origins, which is a GeoDataFrame with one location that we created earlier (however, you can also use multiple locations as origins).destinations, which is a GeoDataFrame representing the destinations (in our case, thepointsGeoDataFrame).departure, which should be Python’sdatetime-object (in our case standing for “22nd of February 2022 at 08:30”) to tellr5pythat the schedules of this specific time and day should be used for doing the calculations.Note: By default,
r5pysummarizes and calculates a median travel time from all possible connections within 10 minutes from given depature time (with 1 minute frequency). It is possible to adjust this time window usingdeparture_time_window-parameter (see details here). For robust spatial accessibility assessment (e.g. in scientific works), we recommend to use 60 minutesdeparture_time_window.
transport_modes, which determines the travel modes that will be used in the calculations. These can be passed using the options from ther5py.TransportMode-class.Hint: To see all available options, run
help(r5py.TransportMode).
Note
In addition to these ones, the constructor also accepts many other parameters listed here, such as walking and cycling speed, maximum trip duration, maximum number of transit connections used during the trip, etc.
Now, we will first create a travel_time_matrix_computer instance as described above:
import datetime
from r5py import TravelTimeMatrixComputer, TransportMode
# Initialize the tool
travel_time_matrix_computer = TravelTimeMatrixComputer(
transport_network,
origins=origin_Point_df,
destinations= destination_Point_df,
departure=datetime.datetime(2022,2,22,8,30),
transport_modes=[TransportMode.TRANSIT, TransportMode.WALK]
)
# To see all available transport modes, uncomment following
# help(TransportMode)
Running this initializes the TravelTimeMatrixComputer, but any calculations were not done yet.
To actually run the computations, we need to call .compute_travel_times() on the instance, which will calculate the travel times between all points:
travel_time_matrix = travel_time_matrix_computer.compute_travel_times()
travel_time_matrix.head()
| from_id | to_id | travel_time | |
|---|---|---|---|
| 0 | 0 | 1 | 30 |
As a result, this returns a pandas.DataFrame which we stored in the travel_time_matrix -variable. The values in the travel_time column are travel times in minutes between the points identified by from_id and to_id. As you can see, the id value in the from_id column is the same for all rows because we only used one origin location as input. To get a better sense of the results, let’s create a travel time map based on the locomizer origin and destination data.
Compute travel times with a detailed information about the routing results#
Now that we are interested in more detailed routing results, it is possible to use DetailedItinerariesComputer. This will provide not only the same information as in the previous examples but also brings much more detailed information about the routings. When using this functionality, r5py produces information about the used routes for each origin-destination pair (with possibly multiple alternative routes), as well as individual trip segments and information about the used modes, public transport route-id information (e.g. bus-line number), distanes, waiting times and the actual geometry used.
Important
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.
from r5py import DetailedItinerariesComputer
# Initialize the Detailed Itineraries Computer object
detailed_itineraries_computer = DetailedItinerariesComputer(
transport_network,
origins=origin_Point_df,
destinations=destination_Point_df,
departure=datetime.datetime(2022,2,22,8,30),
transport_modes=[TransportMode.TRANSIT, TransportMode.WALK],
# With following attempts to snap all origin and destination points to the transport network before routing
snap_to_network=True,
)
travel_details = detailed_itineraries_computer.compute_travel_details()
# Convert the data into a metric CRS to get the distance in meters
travel_details = gpd.GeoDataFrame(travel_details).to_crs(3067)
travel_details.head(10)
/opt/miniconda3/envs/geo/lib/python3.11/site-packages/r5py/r5/detailed_itineraries_computer.py:135: RuntimeWarning: R5 has been compiled with `TransitLayer.SAVE_SHAPES = false` (the default). The geometries of public transport routes are inaccurate (straight lines between stops), and distances can not be computed.
warnings.warn(
| from_id | to_id | option | segment | transport_mode | departure_time | distance | travel_time | wait_time | route | geometry | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0 | TransportMode.WALK | NaT | 3074.137 | 0 days 00:52:32 | NaT | None | LINESTRING (386691.279 6671953.737, 386690.872... |
| 1 | 0 | 1 | 1 | 0 | TransportMode.WALK | 2022-02-22 08:32:12 | 906.231 | 0 days 00:15:28 | 0 days 00:00:00 | None | LINESTRING (386691.279 6671953.737, 386690.872... |
| 2 | 0 | 1 | 1 | 1 | TransportMode.SUBWAY | 2022-02-22 08:57:00 | NaN | 0 days 00:03:00 | 0 days 00:02:48 | M1 | LINESTRING (386142.898 6672338.745, 385693.166... |
| 3 | 0 | 1 | 1 | 2 | TransportMode.WALK | 2022-02-22 09:01:00 | 27.443 | 0 days 00:00:24 | 0 days 00:01:14 | None | LINESTRING (385208.247 6672004.446, 385223.129... |
| 4 | 0 | 1 | 1 | 3 | TransportMode.BUS | 2022-02-22 09:02:00 | NaN | 0 days 00:04:00 | 0 days 00:01:22 | 21 | LINESTRING (385223.018 6672024.655, 384942.332... |
| 5 | 0 | 1 | 1 | 4 | TransportMode.WALK | 2022-02-22 09:07:00 | 418.828 | 0 days 00:07:05 | 0 days 00:00:00 | None | LINESTRING (383911.537 6671322.991, 383909.804... |
| 6 | 0 | 1 | 2 | 0 | TransportMode.WALK | 2022-02-22 08:35:37 | 457.536 | 0 days 00:07:50 | 0 days 00:00:00 | None | LINESTRING (386691.279 6671953.737, 386690.872... |
| 7 | 0 | 1 | 2 | 1 | TransportMode.TRAM | 2022-02-22 08:45:00 | NaN | 0 days 00:05:00 | 0 days 00:01:47 | 7 | LINESTRING (386247.588 6672001.723, 386036.766... |
| 8 | 0 | 1 | 2 | 2 | TransportMode.WALK | 2022-02-22 08:51:00 | 52.393 | 0 days 00:00:52 | 0 days 00:01:04 | None | LINESTRING (385786.579 6672174.517, 385739.254... |
| 9 | 0 | 1 | 2 | 3 | TransportMode.SUBWAY | 2022-02-22 08:53:00 | NaN | 0 days 00:03:00 | 0 days 00:02:04 | M1 | LINESTRING (385693.166 6672170.209, 385207.314... |
As you can see, the result contains much more information than earlier, see the following table for explanations:
Column |
Description |
Data type |
|---|---|---|
from_id |
the origin of the trip this segment belongs to |
any, user defined |
to_id |
the destination of the trip this segment belongs to |
any, user defined |
option |
sequential number for different trip options found |
int |
segment |
sequential number for segments of the current trip options |
int |
transport_mode |
the transport mode used on the current segment |
r5py.TransportMode |
departure_time |
the transit departure date and time used for current segment |
datetime.datetime |
distance |
the travel distance in metres for the current segment |
float |
travel_time |
The travel time for the current segment |
datetime.timedelta |
wait_time |
The wait time between connections when using public transport |
datetime.timedelta |
route |
The route number or id for public transport route used on a segment |
str |
geometry |
The path travelled on a current segment (with transit, stops connected with straight lines by default) |
shapely.LineString |
Visualize the routes on a map#
In the following, we will make a nice interactive visualization out of the results, that shows the fastest routes and the mode of transport between the given origin-destination pairs (with multiple alternative trips/routes):
import folium
import folium.plugins
# Convert travel mode to string (from r5py.TransportMode object)
travel_details["mode"] = travel_details["transport_mode"].astype(str)
# Calculate travel time in minutes (from timedelta)
travel_details["travel time (min)"] = (travel_details["travel_time"].dt.total_seconds() / 60).round(2)
# Generate text for given trip ("origin" to "destination")
travel_details["trip"] = travel_details["from_id"].astype(str) + " to " + travel_details["to_id"].astype(str)
# Choose columns for visualization
selected_cols = ["geometry", "distance", "mode", "route", "travel time (min)", "trip", "from_id", "to_id", "option", "segment" ]
# Generate the map
m2 = travel_details[selected_cols].explore(
tooltip=["trip", "option", "segment", "mode", "route", "travel time (min)", "distance"],
column="mode",
tiles="CartoDB.Positron",
)
# Add marker for the origin
m2 = origin_Point_df.explore(m=m2, marker_type="marker", marker_kwds=dict(icon=folium.Icon(color="green", icon="train", prefix="fa", )))
# Add customized markers for destinations
destination_Point_df.apply(lambda row: (
# Marker with destination ID number attached to the icon
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 the marker to existing map
).add_to(m2)), axis=1,
)
m2
As a result, now we have a nice map that shows alternative routes between the origin and the given destinations in the study area.
If you hover over the lines, you can see details about the selected routes with useful information about the travel time, distance, route id (line number) etc.
Hence, as such, if you’re feeling nerdy (and happen to have Python installed to your phone 😛), you could replace your Google Maps navigator or other journey planners with r5py! 🤓😉
If we know the exact distances travelled in each leg of a PT journey in a particular travel mode, we can calculate the CO2 emissions per passenger-kilometre (g CO2/km) using the above table. The DetailedItinerariesComputer provides the details of the PT trips with the travel mode. But it can be observed that the travel distances are reported as NaN/None in the output. It is possible to get the exact routes by using a custom version of R5, but for the sake of simplicity we now use the straightline distances in our results.
Hence, we calculate the projected distances for different travel legs of the PT journey in the following way:
# Calculate simple Euclidian distances based on the geometry
travel_details["distance"] = travel_details.length
travel_details.head(8)
| from_id | to_id | option | segment | transport_mode | departure_time | distance | travel_time | wait_time | route | geometry | mode | travel time (min) | trip | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0 | TransportMode.WALK | NaT | 3145.528613 | 0 days 00:52:32 | NaT | None | LINESTRING (386691.279 6671953.737, 386690.872... | TransportMode.WALK | 52.53 | 0 to 1 |
| 1 | 0 | 1 | 1 | 0 | TransportMode.WALK | 2022-02-22 08:32:12 | 969.260680 | 0 days 00:15:28 | 0 days 00:00:00 | None | LINESTRING (386691.279 6671953.737, 386690.872... | TransportMode.WALK | 15.47 | 0 to 1 |
| 2 | 0 | 1 | 1 | 1 | TransportMode.SUBWAY | 2022-02-22 08:57:00 | 990.517475 | 0 days 00:03:00 | 0 days 00:02:48 | M1 | LINESTRING (386142.898 6672338.745, 385693.166... | TransportMode.SUBWAY | 3.00 | 0 to 1 |
| 3 | 0 | 1 | 1 | 2 | TransportMode.WALK | 2022-02-22 09:01:00 | 31.082850 | 0 days 00:00:24 | 0 days 00:01:14 | None | LINESTRING (385208.247 6672004.446, 385223.129... | TransportMode.WALK | 0.40 | 0 to 1 |
| 4 | 0 | 1 | 1 | 3 | TransportMode.BUS | 2022-02-22 09:02:00 | 1307.238420 | 0 days 00:04:00 | 0 days 00:01:22 | 21 | LINESTRING (385223.018 6672024.655, 384942.332... | TransportMode.BUS | 4.00 | 0 to 1 |
| 5 | 0 | 1 | 1 | 4 | TransportMode.WALK | 2022-02-22 09:07:00 | 711.547130 | 0 days 00:07:05 | 0 days 00:00:00 | None | LINESTRING (383911.537 6671322.991, 383909.804... | TransportMode.WALK | 7.08 | 0 to 1 |
| 6 | 0 | 1 | 2 | 0 | TransportMode.WALK | 2022-02-22 08:35:37 | 519.326152 | 0 days 00:07:50 | 0 days 00:00:00 | None | LINESTRING (386691.279 6671953.737, 386690.872... | TransportMode.WALK | 7.83 | 0 to 1 |
| 7 | 0 | 1 | 2 | 1 | TransportMode.TRAM | 2022-02-22 08:45:00 | 642.161116 | 0 days 00:05:00 | 0 days 00:01:47 | 7 | LINESTRING (386247.588 6672001.723, 386036.766... | TransportMode.TRAM | 5.00 | 0 to 1 |
Calculate the GHG emissions for the trips#
Let us now import the GHG emissions per passenger-kilometer (g CO2/pkm) by transport modes Data from the file “LCA_gCO2_per_pkm_by_transport_mode.csv”.
The explanations of the acronyms (in the transport modes names) are: BEV = battery electric vehicle; HEV = hybrid electric vehicle; ICE = internal combustion engine; FCEV = fuel cell electric vehicle; PHEV = plug-in hybrid electric vehicle.
import pandas as pd
ghg_factors = pd.read_csv("data/LCA_gCO2_per_pkm_by_transport_mode.csv",index_col=0)
ghg_factors.loc['Total_gCO2'] = ghg_factors.sum(axis=0)
ghg_factors.head()
| Private e-scooter | Shared e-scooter (1st gen.) | Shared e-scooter (new gen.) | Private bike | Shared bike | Private e-bike | Shared e-bike | Private moped - ICE | Private moped - BEV | Shared moped - ICE | ... | Ridesourcing - car - PHEV | Ridesourcing - car - BEV | Ridesourcing - car - BEV (two packs) | Ridesourcing - car - FCEV | Bus - ICE | Bus - HEV | Bus - BEV | Bus - BEV (two packs) | Bus - FCEV | Metro/urban train | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Vehicle component | 26 | 71 | 66 | 7 | 23 | 13 | 37 | 8 | 10 | 20 | ... | 29 | 39 | 62 | 32 | 8 | 8 | 14 | 17 | 11 | 2 |
| Fuel component | 1 | 1 | 2 | 0 | 0 | 3 | 3 | 54 | 5 | 54 | ... | 64 | 16 | 16 | 80 | 72 | 53 | 10 | 10 | 44 | 12 |
| Infrastructure componen | 9 | 9 | 9 | 9 | 9 | 9 | 10 | 11 | 11 | 11 | ... | 21 | 20 | 20 | 21 | 4 | 4 | 4 | 4 | 4 | 11 |
| Operational services | 0 | 35 | 25 | 0 | 25 | 0 | 25 | 0 | 0 | 0 | ... | 59 | 15 | 15 | 73 | 8 | 6 | 1 | 1 | 5 | 0 |
| Total_gCO2 | 36 | 116 | 102 | 16 | 57 | 25 | 75 | 73 | 26 | 85 | ... | 173 | 90 | 113 | 206 | 92 | 71 | 29 | 32 | 64 | 25 |
5 rows × 32 columns
# Extract the name of the travel mode from 'transport_mode' object
travel_details["transport_mode_name"] = travel_details["transport_mode"].apply(lambda obj: obj.name)
travel_details.head(5)
| from_id | to_id | option | segment | transport_mode | departure_time | distance | travel_time | wait_time | route | geometry | mode | travel time (min) | trip | transport_mode_name | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0 | TransportMode.WALK | NaT | 3145.528613 | 0 days 00:52:32 | NaT | None | LINESTRING (386691.279 6671953.737, 386690.872... | TransportMode.WALK | 52.53 | 0 to 1 | WALK |
| 1 | 0 | 1 | 1 | 0 | TransportMode.WALK | 2022-02-22 08:32:12 | 969.260680 | 0 days 00:15:28 | 0 days 00:00:00 | None | LINESTRING (386691.279 6671953.737, 386690.872... | TransportMode.WALK | 15.47 | 0 to 1 | WALK |
| 2 | 0 | 1 | 1 | 1 | TransportMode.SUBWAY | 2022-02-22 08:57:00 | 990.517475 | 0 days 00:03:00 | 0 days 00:02:48 | M1 | LINESTRING (386142.898 6672338.745, 385693.166... | TransportMode.SUBWAY | 3.00 | 0 to 1 | SUBWAY |
| 3 | 0 | 1 | 1 | 2 | TransportMode.WALK | 2022-02-22 09:01:00 | 31.082850 | 0 days 00:00:24 | 0 days 00:01:14 | None | LINESTRING (385208.247 6672004.446, 385223.129... | TransportMode.WALK | 0.40 | 0 to 1 | WALK |
| 4 | 0 | 1 | 1 | 3 | TransportMode.BUS | 2022-02-22 09:02:00 | 1307.238420 | 0 days 00:04:00 | 0 days 00:01:22 | 21 | LINESTRING (385223.018 6672024.655, 384942.332... | TransportMode.BUS | 4.00 | 0 to 1 | BUS |
# Find unique transit modes in the data
travel_details.transport_mode_name.unique()
array(['WALK', 'SUBWAY', 'BUS', 'TRAM'], dtype=object)
We need to map each unique transit modes to the relevant transit realted carbon emission factors. This is done by creating a simple convenience function as below:
# Create a function that returns travel mode specific co2 emission factors
def CO2_emission_factors(mode, ghg_factors):
"""
Convenience function that returns mode specific GHG emission factors (average)
based on International Transport Forum's LCA Emission estimates.
Parameters
----------
mode : str
Name of the travel mode.
ghg_factors : pd.DataFrame
A DataFrame containing information about the emissions of different types of vehicles.
"""
# Here, we don't assume walking produces emissions (although it does..due to eating)
if mode == "WALK":
co2_value = 0
elif mode in ["TRAM", "SUBWAY", "RAIL"]:
co2_value = ghg_factors.loc['Total_gCO2',['Metro/urban train']].mean()
elif mode == "BUS":
co2_value = ghg_factors.loc['Total_gCO2',['Bus - ICE', 'Bus - HEV', 'Bus - BEV','Bus - BEV (two packs)', 'Bus - FCEV']].mean()
else:
print(str(item))
raise ValueError("Unknown Transit mode found!")
return co2_value
# Parse the travel mode specific emission factors
travel_details['ghg_emission_factor'] = travel_details.apply(
lambda x: CO2_emission_factors(x['transport_mode_name'], ghg_factors),
axis=1)
# Calculate the emissions per travelled kilometer
travel_details["GHG_emissions_in_grams"] = travel_details["distance"]/1000 * travel_details["ghg_emission_factor"]
travel_details.head()
| from_id | to_id | option | segment | transport_mode | departure_time | distance | travel_time | wait_time | route | geometry | mode | travel time (min) | trip | transport_mode_name | ghg_emission_factor | GHG_emissions_in_grams | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0 | TransportMode.WALK | NaT | 3145.528613 | 0 days 00:52:32 | NaT | None | LINESTRING (386691.279 6671953.737, 386690.872... | TransportMode.WALK | 52.53 | 0 to 1 | WALK | 0.0 | 0.000000 |
| 1 | 0 | 1 | 1 | 0 | TransportMode.WALK | 2022-02-22 08:32:12 | 969.260680 | 0 days 00:15:28 | 0 days 00:00:00 | None | LINESTRING (386691.279 6671953.737, 386690.872... | TransportMode.WALK | 15.47 | 0 to 1 | WALK | 0.0 | 0.000000 |
| 2 | 0 | 1 | 1 | 1 | TransportMode.SUBWAY | 2022-02-22 08:57:00 | 990.517475 | 0 days 00:03:00 | 0 days 00:02:48 | M1 | LINESTRING (386142.898 6672338.745, 385693.166... | TransportMode.SUBWAY | 3.00 | 0 to 1 | SUBWAY | 25.0 | 24.762937 |
| 3 | 0 | 1 | 1 | 2 | TransportMode.WALK | 2022-02-22 09:01:00 | 31.082850 | 0 days 00:00:24 | 0 days 00:01:14 | None | LINESTRING (385208.247 6672004.446, 385223.129... | TransportMode.WALK | 0.40 | 0 to 1 | WALK | 0.0 | 0.000000 |
| 4 | 0 | 1 | 1 | 3 | TransportMode.BUS | 2022-02-22 09:02:00 | 1307.238420 | 0 days 00:04:00 | 0 days 00:01:22 | 21 | LINESTRING (385223.018 6672024.655, 384942.332... | TransportMode.BUS | 4.00 | 0 to 1 | BUS | 57.6 | 75.296933 |
Okay, now we know the emissions for each segment of the trips. Finally, we can calculate what are the total emissions and travel times for each trip option:
trip_emissions = travel_details.groupby("option")[["travel time (min)", "GHG_emissions_in_grams"]].sum().reset_index()
trip_emissions.head(4)
| option | travel time (min) | GHG_emissions_in_grams | |
|---|---|---|---|
| 0 | 0 | 52.53 | 0.000000 |
| 1 | 1 | 29.95 | 100.059870 |
| 2 | 2 | 27.78 | 86.928792 |
| 3 | 3 | 33.98 | 70.874764 |
Based on this information, we can see that the trip options 2 or 3 seem to be the optimal ones when considering the time it takes to travel as well as the GHG emissions produced.
Where to go next?#
In case you want to learn more about r5py, we recommend reading:
r5py documentation that provides much more details on how to use
r5py.A longer tutorial for spatial accessibility modelling with r5py