Every bike routing tool optimizes for speed. I built one that optimizes for not getting hit by a car.

How I turned Chicago crash data, bike infrastructure data, and OpenStreetMap edges and nodes into a routing tool that finds safer routes from A to B in Chicagoland.
data-engineering
cycling
loci
projects
Author

Matt Triano

Published

April 4, 2026

Modified

April 4, 2026

imports and setup
import sys
from pathlib import Path

module_dir = Path(".").resolve()
sys.path.append(str(module_dir))

import osmnx as ox
from network_explorer import BikeNetworkExplorer

Motivation

On a trip to Copenhagen, I biked in a city for the first time in a decade. It was delightful and liberating, and it made sense that the majority of Copenhagen residents primarily commuted by bike. Returning home to Chicago, I bought a bike and quickly felt the difference that bike infrastructure and culture makes.

In Copenhagen, bike safety and bike theft were basically solved problems. Extensive infrastructure made routes everywhere safe to bike and from the number of unlocked bikes, it was clear that bike theft wasn’t an issue. Back in Chicago, I was sharing lanes with cars going 40 mph and despite extensive locking and uglying up my bike, I still never knew if my bike would still be there. The gap between those two experiences stuck with me. Over time I’ve learned safer, more comfortable routes, and Chicago keeps building out new infrastructure, but you wouldn’t know about these better ways from the existing popular routing tools (see the images below). So I built a tool to help find the safer routes and find secure parking areas, the missing last mile issues that keep people from biking.

You can try it at bike-map.dev.missinglastmile.net.

Bike-Maps safety-optimized route using the Lakefront Trail
Google Maps route for the same start and end point

How routing algorithms work

You already have an intuition for how routing works.

Think about a route you ride or drive regularly. At each intersection, you know which way to go. You haven’t evaluated every possible path through the city, but you can tell which options move you toward your destination and which ones don’t. You don’t consider turning away from where you’re going unless you have a good reason, like avoiding a busy street or getting over a bridge.

In computer science jargon, a street grid is called a graph (or network); a set of intersections connected by roads. Each intersection is a node, each road segment between intersections is an edge, and each edge has a cost — whatever you’d “pay” by traveling down that segment. Cost could be distance, time, effort, risk, unpleasantness; it could be anything, you just have to be able to represent it with positive numbers.

The routing algorithm I use, A* (pronounced “A star”), works the same way. It starts at the starting intersection, looks at the road segments it could go down, and for each one asks: how much did it cost to get here, how much does it cost to go down this road segment, and how far would I be from the destination? It picks the option where that total is lowest and repeats the process. This means it naturally favors direct routes, but it’s willing to explore less direct paths if the cost of the direct route is high enough, which is exactly what you want when “cost” means danger instead of distance.

Here’s what a few blocks of Chicago’s actual bike network look like as a graph:

Examining the raw bike network
west = -87.6620; south = 41.8968; east = -87.6423; north = 41.9113
goose_island_graph = ox.graph_from_bbox(
    bbox=(west, south, east, north), network_type="bike", retain_all=True
)
goose_island_explorer = BikeNetworkExplorer(goose_island_graph)
goose_island_explorer.show()
Make this Notebook Trusted to load map: File -> Trust Notebook