Routing street networks: Find your way with Python

Tutorial — Getting Direction routes with Python and route animations

Photo by Isaac Mehegan on Unsplash

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”)

The street networks plot is shown below.

Photo by Isaac Mehegan on Unsplash


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.

Photo by Isaac Mehegan on Unsplash

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:]):
length.append(round(G.edges[(u, v, 0)]['length']))
travel_time.append(round(G.edges[(u, v, 0)]['travel_time']))

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”])
Photo by Isaac Mehegan on Unsplash

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.

Photo by Isaac Mehegan on Unsplash

We can animate the route nodes by passing animation frame into Plotly Express scatter Mapbox function in the first line. We also add other datasets, like the starting and endpoints as well as the route line Geodataframe.

fig = px.scatter_mapbox(df, lon= “X_from”, lat=”Y_from”, zoom=13, width=1000, height=800, animation_frame=”index”,mapbox_style=”dark”)[0].marker = dict(size = 12, color=”black”)
fig.add_trace(px.scatter_mapbox(start, lon= “X_from”, lat=”Y_from”).data[0])[1].marker = dict(size = 15, color=”red”)
fig.add_trace(px.scatter_mapbox(end, lon= “X_from”, lat=”Y_from”).data[0])[2].marker = dict(size = 15, color=”green”)
fig.add_trace(px.line_mapbox(df, lon= “X_from”, lat=”Y_from”).data[0])

Now, we have a very simple routing animated over a map, as shown below.

Photo by Isaac Mehegan on Unsplash


In this tutorial, we have seen how to create a route between two locations to get directions using OSMNx Python library. We have also created an animation of route nodes over a map using PlotlyExpress and Geopandas.

The code for this tutorial is available in this Google Colab Notebook.

Leave a Reply

Your email address will not be published. Required fields are marked *