roughroute
Offline OSM router · Rust

Points in.
A road-following line out.

A small, fast router that turns ordered waypoints into a polyline that follows real OpenStreetMap roads — fully offline at runtime. Built to be embedded: WASM in a webview, a native library on Android, or a CLI.

34.7071, 33.0226 ──▶ 34.6841, 33.0379 · car · 2143.7 m · connected
View on GitHub Download region graphs ↓
What it is

One job, done small and fast.

roughroute isn't a navigation engine. It's a route source: give it A→B→C and a profile, get back a dense line that hugs the road network. No servers, no network at runtime.

OFFLINE

No network at runtime

The runtime eats a pre-built binary graph as raw bytes and returns a polyline. It never opens a socket or touches a file.

EMBEDDABLE

One core, three shells

The same algorithmic core compiles to WASM (webview), a native library via UniFFI (Android/Kotlin), and a command-line tool.

FAST

Single-digit millisecond queries

Plain A* over a compressed graph. A route across a city or island resolves in milliseconds — no contraction hierarchies needed.

COMPACT

Graphs measured in megabytes

Degree-2 collapse plus delta-compressed geometry. A whole country's road graph downloads like a single image.

PROFILES

Car and foot

One graph carries both. An access bitmask per edge; the profile filters at query time — a pedestrian never snaps onto a motorway.

DETERMINISTIC

Same input, same bytes

Stable ordering and integer costs. Identical waypoints produce an identical line — reproducible builds, golden-testable.

Why it exists

When you need a line, not turn-by-turn truth.

roughroute was built as a swappable route provider for a location-simulation tool — one that plays a route back as mock GPS. For that, "some road-following line between A and B" matters more than a legally perfect maneuver.

The trade it makes

It deliberately ignores one-ways, turn restrictions, and access tags. The maneuver isn't guaranteed legal, and travel time is coarse — cost is just distance, not a speed model. That's the simplification that keeps it tiny and instant.

So the trajectory is plausible, not always correct: it may run the wrong way down a one-way street or take a turn a car couldn't. For mock-GPS playback that's fine; for real navigation it isn't.

If no path is found, it doesn't fail — it drops a straight segment and flags fallback: true. "Some route" beats an honest refusal when you're feeding a playback pipeline.

vs. OSRM & Valhalla — technically

Production engines bake in the whole truth: one-ways, turn restrictions, speed-based costing, maneuver narrative, and multi-level contraction hierarchies. That correctness is exactly what makes their graphs and binaries heavy, and why on-device they lean on tiled, partially-loaded data.

roughroute drops all of that. Its graph carries only geometry and a car/foot access bit per edge — collapsed degree-2 chains and delta-encoded coordinates — so a whole country fits in tens of megabytes and loads as one flat blob. A tiny WASM core, no C++ toolchain, no tiles.

Net: far smaller and simpler, deliberately less correct. Use OSRM or Valhalla for honest routing — roughroute plugs in alongside them behind a shared contract, as the offline fallback when the network is gone or a demo server is throttled.

How it works

Heavy work offline. Featherweight at runtime.

The expensive step — turning raw OpenStreetMap into a graph — happens once, ahead of time. The device only ever sees the finished, compact result.

STEP 01

Build the graph

A preprocessor reads a Geofabrik .osm.pbf, keeps the roads, and writes a compact versioned .graph — collapsed and delta-compressed.

dev machine / CI · once per region
STEP 02

Deliver the bytes

The finished .graph ships as a release asset. The host app downloads it once and caches it — or bundles it — then works offline forever.

CDN / release · optional, one time
STEP 03

Route on device

Load the graph from raw bytes, snap the waypoints onto real roads, run A*, and return the polyline plus its length in meters.

phone / webview · fully offline
Using it

Two calls: load a graph, ask for a route.

The same neutral contract everywhere — [lat, lon] in, { line, meters, fallback } out.

WASM · TypeScript
// load a region graph (from bundle or cache — the app's job)
const bytes = await loadRegionGraph();
const router = new WasmRouter(new Uint8Array(bytes));

// waypoints as [lat, lon]; profile is "car" or "foot"
const res = router.route({
  waypoints: [[34.7071, 33.0226], [34.6841, 33.0379]],
  profile: "car",
});

res.line     // → dense polyline following the roads
res.meters   // → 2143.7
res.fallback // → false
CLI
# build a graph once, offline
roughroute build --pbf cyprus.osm.pbf --out cyprus.graph --profiles car,foot

# route, exporting to json / geojson / gpx
roughroute route --graph cyprus.graph --profile car \
  --via 34.7071,33.0226 --via 34.6841,33.0379 \
  --format gpx > route.gpx
Live demo

Drop two points. A* runs in your browser.

The Cyprus graph (~20 MB) loads once into WASM. After that every route below is computed on your device in milliseconds — no routing server, no per-route network call. Only the map tiles come from the network.

Region

One-time download, cached for repeat visits. Map tiles © OpenStreetMap contributors © CARTO — the routing itself runs locally.

Pre-built graphs

Grab a region, or build your own.

Region graphs are built by CI from a manifest and published as release assets, each with a recorded checksum. Add a line, push, and the pipeline bakes just that region.

Browse all region graphs on GitHub ↗