Improved routing for OSMnx with Taxicab

Nathan Rooy | March 12, 2021

Introduction

If you’ve ever worked on the geospatial side of data science or just have a passion for maps and code, you’ve probably used OSMnx. It’s a solid Python package that fills the void between OpenStreetMap and NetworkX. I’ve used it for both work and personal projects, and I really like it a lot. The one thing though that I’ve always found lacking however is the routing functionality… Let’s work through a simple example that demonstrates what I mean. When routing between two longitude/latitude pairs, one first needs to find the nearest network nodes like so:

import osmnx as ox

# download osm graph
xmin, xmax = -84.323, -84.305
ymin, ymax =  39.084,  39.092
G = ox.graph_from_bbox(
    ymax, ymin, xmin, xmax, network_type='drive', simplify=True)

# set origin/destination
orig = (39.08710, -84.31050)
dest = (39.08800, -84.32000)

# find nearest nodes
n_orig = ox.distance.get_nearest_node(G, orig)
n_dest = ox.distance.get_nearest_node(G, dest)

At which point, you can calculate the shortest route and its distance:

import networkx as nx
osmnx_route = nx.shortest_path(G, n_orig, n_dest, 'length')

edge_lengths = ox.utils_graph.get_route_edge_attributes(
    G, osmnx_route, 'length')
route_len_m = sum(edge_lengths)

Which yields a route length of 1665.48 meters. This appears to be all well and good until you do a quick visual inspection.

import matplotlib.pyplot as plt
fig, ax = ox.plot_graph_route(
    G, osmnx_route, route_color='darkorange', show=False, close=False)

ax.scatter(
    G.nodes[n_orig]['x'], G.nodes[n_orig]['y'], 
    c='lime', s=100, label='orig')

ax.scatter(
    G.nodes[n_dest]['x'], G.nodes[n_dest]['y'],
    c='red', s=100, label='dest')

ax.scatter(
    orig[1], orig[0],
    color='lime', marker='x', s=100, label='orig-point')

ax.scatter(
    dest[1], dest[0],
    color='red', marker='x', s=100, label='dest-point')

plt.legend()
plt.show()
initial route create with osmnx

Ouch, not what we really wanted… I’ve ran into this problem enough times in the past that I decided to finally write my own routing module. I call it Taxicab because it picks you up and drops you off pretty close to where you specify. Under the hood, Taxicab is using a combination of the OpenStreetMap graph (which is what OSMnx uses) as well as the actual road/sidewalk geometry which are represented by Shapely LineString objects. Additionally, I’ve repurposed my old Vincenty distance function and tried to maintain the same interface as OSMnx.

Now, let’s try routing with Taxicab. First install it with the following:

pip install taxicab

Next, instead of finding the nearest nodes like previously, we’ll pass in the actual longitude/latitude pairs.

import taxicab as tc
taxi_route = tc.distance.shortest_path(G, orig, dest)

Which yields a distance of 669.05 meters. A bit different, so let’s validate with a quick visual inspection.

fig, ax = tc.plot.plot_graph_route(
    G, taxi_route, figsize=(10,10), show=False, close=False)

ax.scatter(orig[1], orig[0],
    color='lime', marker='x', s=100, label='orig-point')

ax.scatter(dest[1], dest[0],
    color='red', marker='x', s=100, label='dest-point')

plt.legend()
plt.show()
improved route create with taxicab module

Hey, not too bad! There is still room for improvement, but I’ll deal with that later…

Final Thoughts

The default routing functionality that ships with OSMnx is great and I’m guessing works just fine for the vast majority of what people are doing. However if you need something a little more accurate and are fine with the computational burden of a Vincenty based distance function, than give Taxicab a try! For further documentation and usage examples, see the Github repo here [link].

- Nathan