CHAPTER 22
Routing on road networks
Graph representation of road networks, Dijkstra's algorithm, A* with a Haversine heuristic, and how turn restrictions change the model.
Every navigation app is running some form of shortest-path search on a graph of roads. The specific algorithm depends on the scale, but the underlying model is always the same.
Dijkstra shortest path
A → C → E → F · cost 10
Start node
End node
Click on a node to set it as start. Golden edges = shortest path.
Roads as a graph
A road network is a directed weighted graph:
- Nodes: intersections, dead ends, POIs
- Edges: road segments with direction and weight (travel time or distance)
- Weights: computed from speed limits, road type, traffic, turn costs
const graph = {
"A": [{ to: "B", weight: 5 }, { to: "C", weight: 10 }],
"B": [{ to: "D", weight: 3 }],
"C": [{ to: "D", weight: 2 }],
"D": [],
};
One-way streets become single-direction edges. A two-way road is two edges with the same weight.
Dijkstra's algorithm
Dijkstra's algorithm finds the shortest path from a source node to all other nodes in a weighted graph. It works by greedily expanding the cheapest-so-far node, maintaining a priority queue. It guarantees the optimal path as long as all edge weights are non-negative. Invented by Edsger Dijkstra in 1956 — originally solved by hand in about 20 minutes.
Finds the shortest path from a source to all other nodes:
function dijkstra(graph, start, end) {
const dist = {}, prev = {}, visited = new Set();
const queue = new MinHeap();
dist[start] = 0;
queue.push({ node: start, cost: 0 });
while (!queue.isEmpty()) {
const { node, cost } = queue.pop();
if (visited.has(node)) continue;
visited.add(node);
if (node === end) break;
for (const { to, weight } of graph[node] ?? []) {
const newCost = cost + weight;
if (newCost < (dist[to] ?? Infinity)) {
dist[to] = newCost;
prev[to] = node;
queue.push({ node: to, cost: newCost });
}
}
}
// Reconstruct path
const path = [];
let cur = end;
while (cur) { path.unshift(cur); cur = prev[cur]; }
return path;
}
Time complexity: with a min-heap.
A* — Dijkstra with direction
A* adds a heuristic — an estimate of the remaining cost from node to the goal. For geographic routing, the straight-line distance (Haversine) is a perfect admissible heuristic:
function heuristic(nodeA, nodeB) {
return haversine(nodeA.lat, nodeA.lon, nodeB.lat, nodeB.lon);
}
// In the main loop, replace cost with f = g + h:
queue.push({ node: to, cost: newCost + heuristic(nodes[to], nodes[end]) });
A* explores far fewer nodes than Dijkstra — typically 5–10× faster for geographic routing because it focuses the search toward the destination.
Continue reading "Routing on road networks"
You've reached the end of the free preview. Unlock all 22 paid chapters, including distance math, bearings, polygons, spatial indexing, and 3D map rendering — plus a downloadable PDF and the companion code repo.
- All 22 paid chapters with worked examples
- Downloadable PDF for offline reading
- Companion GitHub repo (JavaScript + Python)
- Free updates for life
Multiple payment options including Wise, PayPal, and bank transfer.
Related chapters
- Spatial indexing — Geohash, Quadtrees, S2, H3 — indices accelerate routing queries
- Bearings, headings, and azimuths — bearing as an A* heuristic