Step 3: Using your Graph to Calculate Travel Times Along the Network#

In step 3 of this tutorial, we use our cleaned graph to create an Origin-Destination matrix (OD). Our setting remains Reykjavik, Iceland, as we look at travel times along the network to churches.

Import Libraries and Load the Graph#

We start by importing the necessary libraries and loading the graph we created in step 2.

Import Libraries#

We start by importing the necessary libraries:

  • os to set the working directory

  • networkx to work with the graph

  • geopandas to work with the geospatial data

  • pandas to work with the data

  • pickle to load the graph

  • shapely to work with the geometry

  • GOSTnets to apply custom functions to the network

import os
import networkx as nx
import geopandas as gpd
import pandas as pd
import pickle as pkl
from shapely.geometry import Point

# import GOSTnet library
import GOSTnets as gn
GDAL is not installed - OGR functionality not available

Read in the Graph#

We set the path and read in the graph from the result of the cleaning process (Step 2)

pth = "./"  # change this path to your working folder
G = pkl.load(
    open(os.path.join(pth, "tutorial_outputs", r"iceland_network_clean.pickle"), "rb")
)

Calculate Travel Times For Each Edge of the Graph#

At this stage each edge in the network has a property called ‘length’. This was actually computed during Step 1 when the generateRoadsGDF function was run. The units of this length are in kilometres.

gn.example_edge(G)
(1, 4644, {'Wkt': 'LINESTRING (-21.9985141 64.1536718, -21.9984767 64.1537859, -21.9984354 64.1538681, -21.9983764 64.1539657, -21.9982927 64.154069)', 'id': 328, 'infra_type': 'residential', 'one_way': False, 'osm_id': '5013949', 'key': 'edge_328', 'length': 0.04569859554162313, 'Type': 'legitimate'})

We want to convert length to time, so that we can conduct analysis on how long it takes to reach certain destinations.

We do this using the convert_network_to_time function. We have used a factor of 1000, because the function is expecting meters, so we need to convert the units of kilometers to meters.

The convert_network_to_time function uses a default speed dictionary that assigns speed limits to OSM highway types. However, it is possible to specify your own speed dictionary.

G_time = gn.convert_network_to_time(
    G, distance_tag="length", road_col="infra_type", factor=1000
)

We can now use the ‘time’ property for each edge to work out how long it takes to get from one node to another!

gn.example_edge(G_time, 1)
(1, 4644, {'Wkt': 'LINESTRING (-21.9985141 64.1536718, -21.9984767 64.1537859, -21.9984354 64.1538681, -21.9983764 64.1539657, -21.9982927 64.154069)', 'id': 328, 'infra_type': 'residential', 'one_way': False, 'osm_id': '5013949', 'key': 'edge_328', 'length': 45.69859554162313, 'Type': 'legitimate', 'time': 8.225747197492165, 'mode': 'drive'})

To do this for just one journey, we could call nx.shortest_path_length on any given origin or destination node. Let’s list 10 of our nodes using this networkX function:

list(G_time.nodes)[:10]
[1, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Now we will calculate the travel time between the first and tenth node in the list.

A = list(G_time.nodes)[0]  # first node in list
B = list(G_time.nodes)[10]  # 10th node in list
travel_time = nx.shortest_path_length(G_time, A, B, weight="time")
print(
    "The travel time between A and B is: %d seconds, or %d minutes!"
    % (travel_time, travel_time / 60)
)
The travel time between A and B is: 1563 seconds, or 26 minutes!

Calculate Travel Times to Churches#

In our example, we want to use our network for Reykjavik to work out the travel time to local churches.

Here, we import a shapefile for Reykjavik, and reproject it to WGS 84:

rek = gpd.read_file(os.path.join(pth, "tutorial_data", "rek2.shp"))
rek = rek.to_crs("epsg:4326")

Next, we set a variable poly equal to just the geometry

poly = rek.geometry.iloc[0]

We can visualize this in-line by just calling it:

poly
../_images/c215179840c767af32035240966a9b139cda2d9e3ec210575031b7034bdbb921.svg

With this in hand, we can read in a shapefile of destinations - here, the churches in Iceland. We use Shapely’s ‘within’ command to select just those in the Reykjavik area:

churches = gpd.read_file(os.path.join(pth, "tutorial_data", "churches.shp"))
churches = churches.loc[churches.within(poly)]

In order to perform network analysis we want to know the closest network node to each church. For this, we use the GOSTnets pandana_snap function to snap the church locations to the road network:

churches
osm_id code fclass name Pop geometry
0 59983337 3104 christian_lutheran Grafarvogskirkja 5057 POINT (-21.79968 64.13241)
2 319768632 3300 muslim Félag Múslima á Íslandi 5979 POINT (-21.87575 64.13430)
11 1866767163 3100 christian Betanía 3678 POINT (-21.81463 64.12343)
13 3030668954 3103 christian_evangelical Biskup Íslands 9586 POINT (-21.92745 64.14541)
14 3030668955 3103 christian_evangelical Biskupsstofa 7988 POINT (-21.92743 64.14541)
15 3030668961 3103 christian_evangelical Kirkjuráð 5139 POINT (-21.92744 64.14541)
16 3030668966 3103 christian_evangelical Skálholtsútgáfan 1035 POINT (-21.92743 64.14541)
19 3761680070 3100 christian Alþjóðleg kirkja Guðs og embætti Jesú Krists 4016 POINT (-21.90527 64.14133)
20 3975889251 3100 christian Parohia Ortodoxă Română Reykjavik 429 POINT (-21.95329 64.15002)
# the crs of churches
churches.crs
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich
# view the pandana_snap doc string
gn.pandana_snap?

We want the nearest node distance (NN_dist) to be measured in meters, so that is why we include the target_crs parameter specifying the correct UTM zone.

churches = gn.pandana_snap_c(
    G_time,
    churches,
    source_crs="epsg:4326",
    target_crs="epsg:32627",
    add_dist_to_node_col=True,
)

As we can see from the NN_dist column, our church locations are very close to a node on the network in all cases

churches
osm_id code fclass name Pop geometry NN NN_dist
0 59983337 3104 christian_lutheran Grafarvogskirkja 5057 POINT (-21.79968 64.13241) 12032 24.416940
2 319768632 3300 muslim Félag Múslima á Íslandi 5979 POINT (-21.87575 64.13430) 6557 26.170603
11 1866767163 3100 christian Betanía 3678 POINT (-21.81463 64.12343) 11157 16.852778
13 3030668954 3103 christian_evangelical Biskup Íslands 9586 POINT (-21.92745 64.14541) 13257 13.190325
14 3030668955 3103 christian_evangelical Biskupsstofa 7988 POINT (-21.92743 64.14541) 13257 12.559162
15 3030668961 3103 christian_evangelical Kirkjuráð 5139 POINT (-21.92744 64.14541) 13257 12.976799
16 3030668966 3103 christian_evangelical Skálholtsútgáfan 1035 POINT (-21.92743 64.14541) 13257 12.751436
19 3761680070 3100 christian Alþjóðleg kirkja Guðs og embætti Jesú Krists 4016 POINT (-21.90527 64.14133) 9540 39.976706
20 3975889251 3100 christian Parohia Ortodoxă Română Reykjavik 429 POINT (-21.95329 64.15002) 8370 21.453451

When calculating an OD-Matrix, we can only use the node IDs as inputs. So, we convert this column of our dataframe over to a list of unique values:

destinations = list(set(churches.NN))
destinations
[12032, 9540, 13257, 8370, 11157, 6557]

Further Analysis#

We would like to make an OD matrix where the origin is the cottage we are renting in the city, and the destinations are the churches in Reykjavik. This will help us work out how many churches we can see today! First, we need to create the origin, it has coordinates: 64.152215, -22.002099 (Lat,Lon), so we make a shapely.geometry.Point:

# A list with a single Shapely Point object is created with (x,y)
my_house = [Point(-22.002099, 64.152215)]

Next, we load it into a GeoDataFrame and snap it to the network:

mini_gdf = gpd.GeoDataFrame(
    {"geometry": my_house}, crs="epsg:4326", geometry="geometry", index=[1]
)
mini_gdf
geometry
1 POINT (-22.00210 64.15221)
origin_gdf = gn.pandana_snap_c(
    G_time, mini_gdf, source_crs="epsg:4326", target_crs="epsg:32627"
)
origin_gdf
geometry NN NN_dist
1 POINT (-22.00210 64.15221) 2302 79.027786
# This is the nearest node (NN)
origin_gdf.iloc[0].NN
2302

Now, we can calculate the OD matrix using the GOSTNets calculate_OD function. Bear in mind it takes list objects as inputs:

origin = [origin_gdf.iloc[0].NN]
OD = gn.calculate_OD(G_time, origin, destinations, fail_value=9999999)

The OD matrix displays the time in seconds to reach each church

OD
array([[1335.75240603,  635.47968749,  489.87692026,  348.95959429,
        1066.23418002,  817.05710822]])

We can use minutes as the measure by dividing every value in the OD Matrix by 60. Then we can convert the array nicely into a pandas Dataframe,

OD = OD / 60
OD_df = pd.DataFrame(OD, columns=destinations, index=origin)
OD_df
12032 9540 13257 8370 11157 6557
2302 22.26254 10.591328 8.164615 5.815993 17.77057 13.617618

It appears nearly all of the churches less than twenty minutes away.

Now you are up to speed on the basics!