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.
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.
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.
The runtime eats a pre-built binary graph as raw bytes and returns a polyline. It never opens a socket or touches a file.
The same algorithmic core compiles to WASM (webview), a native library via UniFFI (Android/Kotlin), and a command-line tool.
Plain A* over a compressed graph. A route across a city or island resolves in milliseconds — no contraction hierarchies needed.
Degree-2 collapse plus delta-compressed geometry. A whole country's road graph downloads like a single image.
One graph carries both. An access bitmask per edge; the profile filters at query time — a pedestrian never snaps onto a motorway.
Stable ordering and integer costs. Identical waypoints produce an identical line — reproducible builds, golden-testable.
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.
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.
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.
The expensive step — turning raw OpenStreetMap into a graph — happens once, ahead of time. The device only ever sees the finished, compact result.
A preprocessor reads a Geofabrik .osm.pbf, keeps the roads, and writes a compact versioned .graph — collapsed and delta-compressed.
The finished .graph ships as a release asset. The host app downloads it once and caches it — or bundles it — then works offline forever.
Load the graph from raw bytes, snap the waypoints onto real roads, run A*, and return the polyline plus its length in meters.
The same neutral contract everywhere — [lat, lon] in, { line, meters, fallback } out.
// 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
# 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
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.
Click the map to drop numbered waypoints — drag a marker to reroute.
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.