Skip to content

Repository files navigation

Open Reroute

A routing engine in Rust, API-compatible with openrouteservice, with public transport as a first-class citizen rather than a bolted-on module. openrouteservice inspired this project and its REST contract is the compatibility target; none of its code is used here — see NOTICE.

Compatibility is a property of the HTTP boundary only. The REST contract — paths, request fields, response fields, error codes — matches ORS so existing clients work unchanged. Everything behind it is chosen for speed and memory footprint: memory-mapped columnar graphs, cost arrays baked at build time, and RAPTOR for transit.

Status

Phase State
0 — scaffold, error contract, config, swagger done
1 — OSM import, street graph, directions endpoint done for driving-car and foot-walking
2 — GTFS import, RAPTOR, public-transport done
3 — matrix, isochrones, snap, export done, including additive transit variants
4–6 — profiles/options, performance, client/delivery planned

Measured against upstream openrouteservice 9.10.0 on the same Heidelberg extract, same machine:

Open Reroute openrouteservice 9.10.0
p50 directions (1.3 km, car) 0.49 ms 2.24 ms
p95 0.82 ms 3.03 ms
Resident memory, 2 profiles 17 MB 743 MB
Graph build 0.1 s minutes
Cold start with a prebuilt graph 13 ms minutes

orscompat reports 41/41 structural matches: 25 directions cases plus 16 matrix, isochrone, snap and export cases, including error codes. Four isochrone area samples are within 0.2% of the same openrouteservice 9.10.0 reference.

Bulk endpoints on a Stuttgart-sized graph: a 50×50 street matrix answers in 21 ms, a 15×15 transit matrix in 1.2 s, and a 900-second car isochrone in 11 ms (upstream: 30 ms).

Car durations are calibrated against upstream: median −1.2 % across 24 varied routes, with distances at +0.1 %. See docs/06-cost-models.md for the model and the measurements.

Public transport, built from the official VVS feed over a Stuttgart extract:

Feed 242,517 trips · 9,768 stops · 743 routes · 365 days of calendar
Timetable build 8.2 s (whole graph + timetable: 11 s)
Timetable artifact 48.6 MB, memory-mapped
Journey query Stuttgart Hbf → Vaihingen, S1 direct, answered in single-digit ms

Quickstart

Build a graph from an OSM extract:

cargo run --release -p or-cli -- build --source path/to/extract.osm.pbf --out data/graph.orrt --profiles driving-car,foot-walking,cycling-regular

Add a GTFS feed to serve public transport as well — the timetable is written next to the graph as data/graph.orrt.transit and picked up automatically when serving:

cargo run --release -p or-cli -- build --source path/to/extract.osm.pbf --out data/graph.orrt --profiles driving-car,foot-walking,cycling-regular --gtfs path/to/feed.zip

Serve the API:

cargo run --release -p or-cli -- serve --graph data/graph.orrt --port 8082

Ask for a route:

curl -X POST http://localhost:8082/ors/v2/directions/driving-car/json -H 'Content-Type: application/json' -d '{"coordinates":[[8.681495,49.41461],[8.687872,49.420318]]}'

Ask for a journey by public transport:

curl -X POST http://localhost:8082/ors/v2/directions/public-transport/json -H 'Content-Type: application/json' -d '{"coordinates":[[9.180229,48.783386],[9.1130,48.7250]],"departure":"2026-10-15T09:00:00"}'

Interactive docs are at http://localhost:8082/ors/swagger-ui, the OpenAPI document at /ors/v2/api-docs. The base path defaults to /ors, matching the upstream Docker image; set server.base_path to / to serve at the root like the public API.

Checking compatibility

Start the reference instance (upstream ORS on a Heidelberg extract, capped at 2 CPUs and 3 GB):

docker compose -f deploy/reference-ors/docker-compose.yml up -d

Then replay the corpus against both:

cargo run --release -p or-compat -- --ours http://localhost:8082/ors --reference http://localhost:8081/ors --verbose

The harness fails on structural differences — field names, types, error codes — and only reports distance and duration deltas, because the cost model is deliberately not upstream's.

Stop the reference when you are done:

docker compose -f deploy/reference-ors/docker-compose.yml down

Local development

Prerequisites

Tool Version Why
Rust 1.82+ (rustup) building everything
Docker + Compose any recent reference openrouteservice, Postgres/PostGIS
An OSM extract .osm.pbf the street network
A GTFS feed .zip public transport (phase 2)

No system libraries are required — no PROJ, no GEOS, no JVM. cargo build is the whole toolchain.

Small extracts to develop against, both shipped in the upstream openrouteservice repository:

ls ../openrouteservice/ors-api/src/test/files/heidelberg.test.pbf ../openrouteservice/ors-api/src/test/files/vrn_gtfs_cut.zip

Build, run, test

cargo build --release
cargo run --release -p or-cli -- build --source path/to/extract.osm.pbf --out data/graph.orrt --profiles driving-car,foot-walking,cycling-regular
cargo run --release -p or-cli -- serve --graph data/graph.orrt --port 8082
cargo test
cargo clippy --all-targets -- -D warnings && cargo fmt --all --check

orctl --help lists every flag; orctl build --help and orctl serve --help cover the two subcommands.

Configuration

Values are layered: defaults, then a YAML file, then ORS_* environment variables, then CLI flags. The file is optional — the defaults run a local instance as-is.

# ors-config.yml
server:
  host: 0.0.0.0
  port: 8082
  base_path: /ors        # "/" serves at the root, like the public ORS API
engine:
  graph_path: data/graph.orrt
  source_file: path/to/extract.osm.pbf
  profiles: [driving-car, foot-walking]
limits:
  maximum_waypoints: 50
  maximum_distance_m: 100000
  maximum_snapping_radius_m: 350
Environment variable Overrides Default
ORS_CONFIG_LOCATION path of the config file none
ORS_SERVER_HOST server.host 0.0.0.0
ORS_SERVER_PORT server.port 8082
ORS_SERVER_BASE_PATH server.base_path /ors
ORS_ENGINE_GRAPH_PATH engine.graph_path data/graph.orrt
ORS_ENGINE_SOURCE_FILE engine.source_file none
ORS_ENGINE_PROFILES engine.profiles (comma-separated) driving-car,foot-walking
ORS_LOG_JSON structured log output false
RUST_LOG log filter, e.g. debug,or_graph=trace info

Public transport

public-transport needs two things a street profile does not: the foot-walking profile in the same graph (it powers access, egress and transfers) and a GTFS feed passed to orctl build.

cargo run --release -p or-cli -- build --source stuttgart.osm.pbf --out data/stuttgart.orrt --profiles driving-car,foot-walking --gtfs vvs_gtfs.zip

The example used throughout development is Stuttgart: the BBBike Stuttgart extract for the street network and the official VVS feed from opendata-oepnv.de (registration required; gtfs.mfdz.de mirrors it without one).

Parameter Meaning Default
departure local departure time, e.g. 2026-10-15T09:00:00 now
walking_time ISO-8601 budget for access, egress and transfers PT15M
ignore_transfers stop preferring fewer changes when arrival times tie false
schedule return several departures instead of one journey false
schedule_duration width of the schedule window PT30M
schedule_rows maximum journeys returned 3

Timetable times are read in the feed's own time zone (from agency.txt), so departure is local time and every timestamp in the response comes back with that offset.

When a journey looks wrong, check the data before the engine. orctl inspect reports what the graph and timetable actually contain at a point:

cargo run --release -p or-cli -- inspect --graph data/stuttgart.orrt --point 9.180229,48.783386 --stops

It prints where the coordinate snaps, how large the connected component around it is, and which stops are reachable on foot with the routes serving them. A stop that is missing here is a data or linking problem, not a routing one — for example a summer closure genuinely removes a line from the calendar, which is why a query in August and the same query in October give different answers.

Endpoints while developing

URL What
http://localhost:8082/ors/v2/directions/driving-car/json routing (POST)
http://localhost:8082/ors/v2/directions/public-transport/json transit journeys (POST)
http://localhost:8082/ors/v2/matrix/{profile} duration/distance matrix (POST)
http://localhost:8082/ors/v2/isochrones/{profile} reachability polygons (POST)
http://localhost:8082/ors/v2/snap/{profile} batch graph snapping (POST)
http://localhost:8082/ors/v2/export/{profile} bounded graph export (POST)
http://localhost:8082/ors/v2/health readiness
http://localhost:8082/ors/v2/status loaded profiles and versions
http://localhost:8082/ors/swagger-ui interactive docs
http://localhost:8082/ors/v2/api-docs OpenAPI document

OpenAPI document

The API description is generated from the same utoipa-annotated request/response structs that parse and render traffic in or-api — there is no hand-maintained spec to drift from the code.

  • Live, while orctl serve is running: http://localhost:8082/ors/swagger-ui (interactive) or http://localhost:8082/ors/v2/api-docs (raw JSON).

  • Static, without a server or graph: orctl openapi needs no config, graph, or timetable — the spec is pure compile-time annotation. Prints JSON to stdout by default:

    cargo run -p or-cli -- openapi                              # JSON to stdout
    cargo run -p or-cli -- openapi --format yaml --out spec.yaml
  • Checked-in copies live at docs/api/openapi.json and docs/api/openapi.yaml, for anything that wants a spec file without building the project — client generators, doc sites, another repo's CI. Regenerate them after touching a handler signature, DTO, or #[utoipa::path] annotation:

    make openapi          # regenerate docs/api/openapi.{json,yaml}
    make openapi-check    # fail if the checked-in copies are stale (wire into CI)

Troubleshooting

  • graph has no costs for profile 'x' — the artifact was built without that profile. Rebuild with --profiles, which is what /v2/status lists.
  • 2010 point not found — the coordinate is outside the extract or further from the network than the snapping radius. Pass radiuses, or check you are sending [longitude, latitude].
  • 2004 exceeds server limit — straight-line extent above limits.maximum_distance_m (100 km by default, matching upstream).
  • 2013/2014/2015 — no stop is reachable on foot from the start, the destination, or either. Raise walking_time, or check with orctl inspect --stops that stops nearby were linked to the street network at build time.
  • 2016 route not found — both ends reach the network but nothing connects them within the six-hour search horizon. Usually a calendar question: is the line running on that date?
  • Port already in use — a previous orctl serve is still running: pkill -f "orctl serve".

Frontend

A React + Vite + Leaflet client lives in frontend/, with one screen per backend capability — Directions (incl. a transit-itinerary breakdown), Isochrones, Matrix, and a Tools screen bundling Snap and Export. It talks to or-api directly (CORS is already permissive on the server); nothing about the frontend requires a database or a second service.

Prerequisites

Node 20+ and npm. The backend must be running somewhere reachable (see Quickstart above).

Run it

cd frontend
npm install
npm run generate:api-types   # generates src/api/schema.ts from docs/api/openapi.json
npm run dev                  # http://localhost:5173

Environment variables

Variable Default What
VITE_API_BASE_URL http://localhost:8082/ors Base URL of the or-api instance to call
VITE_TRACESTRACK_API_KEY unset Optional free key from tracestrack.com; enables the "Tracestrack Topo" basemap in the map style switcher. Every other basemap (Street, Satellite, Terrain via OpenTopoMap, Cycle Map via CyclOSM, Transport Map via ÖPNVKarte) needs no key and works out of the box.

Copy frontend/.env.example to frontend/.env and edit as needed.

Geocoding

Address search in the location fields calls Nominatim (OpenStreetMap's public geocoder) — the one call the frontend makes to a service other than or-api, since the backend only accepts coordinates. Clicking the map or typing lat, lon directly works without it.

Deploying to Vercel

frontend/vercel.json is already configured (Vite framework preset, SPA rewrite). Set the project's Root Directory to frontend and add VITE_API_BASE_URL (and optionally VITE_TRACESTRACK_API_KEY) as environment variables pointing at wherever or-api is deployed.

Scripts

npm run dev, npm run build, npm run lint (oxlint), npm test (vitest) — see frontend/README.md.

Layout

crates/    or-domain  entities and ports, zero sibling dependencies
           or-app     use cases, validation, service limits
           or-api     the only crate that knows ORS field names
           or-graph   CSR graph, snapping, search, instructions
           or-osm     PBF import
           or-store   mmap artifact format
           or-geo     geometry, polyline, simplification
           or-cli     orctl: composition root (build / serve)
           or-compat  orscompat: the compatibility harness
pkg/       ors-errors, ors-config, ors-telemetry   cross-cutting, business-free
frontend/  React + Vite + Leaflet client — directions, isochrones, matrix, tools
charts/    open-reroute Helm chart
deploy/    reference-ors: the compatibility oracle
docs/      design documents

See docs/README.md for the design and roadmap.

Deployment

The backend ships as a container image and a Helm chart, deployed to Kubernetes by GitHub Actions. Full detail — including every repository secret and variable the pipelines need — is in docs/08-deployment.md.

make docker-build     # the image CI builds
make deploy-check     # chart lint + render + OpenAPI drift

The engine serves from a memory-mapped artifact (190 MB for Stuttgart, 42 MB compressed) that an init container downloads onto a volume, verified by sha256. That digest is also the cache key, so restarts skip the download and refreshing the data is a one-line change rather than an image rebuild.

License

Apache-2.0. See LICENSE.

open-reRoute is API-compatible with openrouteservice and was inspired by it, but contains none of its code — it is an independent Rust implementation, and compatibility is a property of the HTTP boundary only. NOTICE sets this out in full.

Note that the data is licensed separately from the software: OpenStreetMap is © OpenStreetMap contributors under the ODbL, GTFS feeds under their agencies' own terms, and a routing artifact built from them is a derived database with its own attribution and share-alike obligations.

About

A routing engine in Rust API-compatible with openrouteservice

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages