Routing street networks: Find your way with Python
Tags: Geospatial, Python, Data Science, Towards Data Science, Data Visualization

We use routing services almost every day to get directions from one location to another. In this tutorial, we will learn how to make our routing animation to get from one place to another, using Python’s excellent libraries, OMSNx, Geopandas and Plotly Express.

Set up Network Graphs

First of all, we need to get the data for the location we want. So, let us download first and visualize street network data for a place. In this tutorial, I will use Stockholm, Sweden, as an example. Feel free to pick up any other locations.

First, let us import the necessary libraries.

import pandas as pd

import geopandas as gpd

from shapely.geometry import Point, LineString

import plotly_express as px

import networkx as nx

import osmnx as ox

ox.config(use_cache=True, log_console=True)

We need to set up the network graphs. OSMNx has plenty of ways to get street networks from all over the world using the excellent opensource service of OpenStreetMap. I have created here a function that can generate a graph by providing addresses or coordinates.

def create_graph(loc, dist, transport_mode, loc_type="address"): """Transport mode = ‘walk’, ‘bike’, ‘drive’, ‘drive_service’, ‘all’, ‘all_private’, ‘none’""" if loc_type == "address":

G = ox.graph_from_address(loc, dist=dist, network_type=transport_mode) elif loc_type == "points":

G = ox.graph_from_point(loc, dist=dist, network_type=transport_mode ) return G

Here, we create street networks for Gothenburg city in Sweden using the function above. We can also plot the street networks using plot_graph.

G = create_graph(“Gothenburg”, 2500, “drive”)

ox.plot_graph(G)

The street networks plot is shown below.

Street network graph — Gothenburg, Sweden.

Routing

To get from one point to another, you can use coordinates as well as addresses with OSMnX. Let us see how we can create nodes from coordinates. You can pick any coordinates within the network streets we have created above. Before calculating the routes, we also need to impute missing edge speeds and add travel times to the Graph.

G = ox.add_edge_speeds(G) #Impute

G = ox.add_edge_travel_times(G) #Travel time start = (57.715495, 12.004210)

end = (57.707166, 11.978388) start_node = ox.get_nearest_node(G, start)

end_node = ox.get_nearest_node(G, end) # Calculate the shortest path

route = nx.shortest_path(G, start_node, end_node, weight='travel_time') #Plot the route and street networks

ox.plot_graph_route(G, route, route_linewidth=6, node_size=0, bgcolor='k',fig_width=12, fig_height=12 );

In the above code, we calculate the shortest path with the provided coordinates for origin point and destination. We plot the route from the origin to destination below. The route uses travel time weight to minimize the travel time.

Route Animations

To create route animations, we will use Geopandas to create a LineString Geometry and PlotlyExpress to animate the route. Before that, we need to derive nodes, coordinates and travel time from the route and graph network. Here we will create a list that holds these values and loop through the route.

node_start = []

node_end = []

X_to = []

Y_to = []

X_from = []

Y_from = []

length = []

travel_time = [] for u, v in zip(route[:-1], route[1:]):

node_start.append(u)

node_end.append(v)

length.append(round(G.edges[(u, v, 0)]['length']))

travel_time.append(round(G.edges[(u, v, 0)]['travel_time']))

X_from.append(G.nodes[u]['x'])

Y_from.append(G.nodes[u]['y'])

X_to.append(G.nodes[v]['x'])

Y_to.append(G.nodes[v]['y'])

Now, we can create a data frame out of the lists from the above calculations. We end up with a data frame that holds these values, like origin coordinates of each node in the route, length of the path between nodes and travel time between the nodes.

df = pd.DataFrame(list(zip(node_start, node_end, X_from, Y_from, X_to, Y_to, length, travel_time)), columns =[“node_start”, “node_end”, “X_from”, “Y_from”, “X_to”, “Y_to”, “length”, “travel_time”]) df.head()

Route nodes data frame

Next, we create a LineString Geodataframe that connects all these nodes coordinates.

def create_line_gdf(df):

gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.X_from, df.Y_from)) gdf[“geometry_to”] = [Point(xy) for xy in zip(gdf.X_to, gdf.Y_to)] gdf[‘line’] = gdf.apply(lambda row: LineString([row[‘geometry_to’], row[‘geometry’]]), axis=1) line_gdf = gdf[[“node_start”,”node_end”,”length”,”travel_time”, “line”]].set_geometry(‘line’) return line_gdf line_gdf = create_line_gdf(df)

Just to mark the starting point and destination different from other nodes, we create separate Dataframes for both the starting point and ending point.

start = df[df[“node_start”] == start_node] end = df[df[“node_end”] == end_node]

We will use Plotly Express to animate the route. Let us first plot all the nodes in the Dataframe with Plotly.

px.scatter_mapbox(df, lon= “X_from”, lat=”Y_from”, zoom=12)

The map below shows all the nodes, but the good thing is it is straightforward to animate with PlotlyExpress.