Offline route calculations of San Francisco in browser using Rust (live demo)

punnerud1 pts0 comments

MPEE β€” Offline route calculations & optimization

What it is

MPEE is built from two Rust crates β€” dijeng<br>(contraction-hierarchy routing) and brooom (the VRP<br>solver). Together they solve a 50,000-customer vehicle-routing problem on<br>a laptop without ever materialising a full distance matrix ,<br>and in head-to-head tests on a Mac they produced shorter routes than<br>VROOM at equal runtime,<br>using less memory. The engine binary is under ~50 MB; the map cache<br>scales with the area you download.

🧭<br>Point-to-point routes<br>Driving distance + time between two coordinates, with optional geometry.

🚚<br>Multi-vehicle VRP<br>Best routes for N vehicles over your own stops β€” capacities, skills, time windows, mixed fleets.

πŸ”Ž<br>Offline geocoding<br>Street ⇄ coordinate and street-crossing lookup, reusing the same cache. No separate index.

πŸ—ΊοΈ<br>Bring your own area<br>Any OpenStreetMap extract sized to where you operate. One downloaded area, one cache.

Scope: MPEE covers a single downloaded area β€” it is not<br>a route-anywhere-on-Earth offline map. There is no global tiling, by<br>design: within your area, one cache is simpler and faster.

How it compares (measured, Apple M3 Pro)

A 50,000-customer fleet implies a 50,000 Γ— 50,000 distance<br>matrix β€” ~10 GB if you store it. The classic<br>OSRM + VROOM split builds and ships that matrix<br>between two processes; MPEE streams it in one process and never<br>materialises it β€” which is where the speed and memory wins come from.

Routing β€” NΓ—N duration+distance matrix Β· dijeng vs OSRM, Greater London CH (n = 1.16 M)

MatrixMPEE β€” timeMPEE β€” peak RAMOSRM

10k Γ— 10k4.3 sstreamedimpractical β€” no chunked many-to-many<br>50k Γ— 50k 94 s ≀ 500 MB OOM β€” the matrix alone is ~10 GB

Honest caveat: OSRM is ~3Γ— faster on a single point-to-point query<br>(β‰ˆ30 Β΅s vs β‰ˆ88 Β΅s). MPEE wins decisively the moment you need a fleet-sized<br>matrix β€” the case VRP actually requires.

Optimisation β€” VRP solver Β· brooom vs PyVRP / VROOM / OR-Tools, Solomon-style

ScaleResult

N = 1,000beats the next-best solver (PyVRP) on 17 / 20 seeds, p β‰ˆ 10⁻⁷<br>N = 50,000the only tested solver that converges on a laptop<br>Inner loopthe entire local search (2-opt, relocate, swap-star, Or-opt, ILS-kick, regret-3) as one GPU megakernel β€” Metal on Mac, Vulkan/DX12 elsewhere; sub-ms per iteration

End-to-end on this machine: 2,000 jobs / 50 vehicles in ~2 min (matrix 0.32 s);<br>5,000 / 100 in ~9 min (matrix 4.10 s), both β‰₯99 % assigned. Full numbers in the<br>dijeng and<br>brooom READMEs.

Install

pip install mpee

Prebuilt wheel for macOS (arm64); Linux/Windows install from the source<br>distribution (needs a Rust toolchain) until multi-platform wheels are<br>published. Python 3.8+. No Rust toolchain needed for the macOS wheel.

Quick start (CLI)

1. Install & get a map once

# MPEE β€” offline routing, VRP & street geocoding for one downloaded area.<br># Docs: https://punnerud.github.io/mpee/ Source: https://github.com/punnerud/mpee<br>pip install mpee

# Download an OpenStreetMap extract (Geofabrik) …<br>mpee download europe/great-britain/england/greater-london

# … and preprocess it into a routable cache (.pp + .ch + .names).<br>mpee build data/greater-london-latest.osm.pbf # car (default)<br># mpee build data/greater-london-latest.osm.pbf bicycle # or: car | bicycle | foot<br>After this you are fully offline. The cache is reusable; a re-run reuses it instantly (--force rebuilds, --quiet hushes progress).

2. Route from A to B

mpee route 51.5080,-0.1281 51.5138,-0.0984 --cache data/greater-london-latest.osm.pbf<br># distance: 2.38 km<br># duration: 4.4 min

3. Optimize a delivery run over many stops

# stops.txt: one "lat,lon" per line (or a JSON [[lat,lon], …])<br>mpee optimize --stops stops.txt --vehicles 5 --capacity 20 \<br>--cache data/greater-london-latest.osm.pbf<br># stops: 50 vehicles used: 3/5 unassigned: 0<br># total: 60.0 km, 115 min (solved in 4.6s)

4. Geocode within the area (offline)

mpee reverse 51.5080,-0.1281 --cache data/greater-london-latest.osm.pbf # β†’ Trafalgar Square<br>mpee geocode "Baker Street" --cache data/greater-london-latest.osm.pbf # β†’ 51.522072,-0.157497<br>mpee crossing "Oxford Street" "Regent Street" --cache ... # β†’ Oxford Circus (LAT,LON)

Use it from Python

Coordinate order. The simple helpers take<br>(lat, lon). The VROOM-style solve(problem)<br>accepts a coordinate as {"lat": …, "lon": …} (recommended),<br>[lon, lat], or {"coord": [lon, lat]}.

import mpee

# Open a prebuilt cache (built once via `mpee build`).<br>r = mpee.Router("data/greater-london-latest.osm.pbf.pp",<br>"data/greater-london-latest.osm.pbf.ch")

# Distance + time between two points.<br>leg = r.route(51.5080, -0.1281, 51.5138, -0.0984)<br>print(leg["distance_km"], "km,", leg["duration_min"], "min")

# Optimize 50 deliveries across 5 vehicles.<br>stops = [(51.51, -0.12), (51.49, -0.10)] # your (lat, lon) list<br>plan = r.optimize(stops, vehicles=5, capacity=20, time_limit_s=5.0)

# Geocoding (needs the .names sidecar built by `mpee build`).<br>r.reverse(51.5080, -0.1281) # β†’ "Trafalgar Square"<br>r.geocode("Baker Street") # β†’ {"name", "lat",...

mpee cache greater london matrix stops

Related Articles