Build a routing service: a practical guide to road-network routing
A routing service converts geographic waypoints into paths through a transportation network and returns distance, duration, geometry, and often turn-by-turn instructions. Building one requires more than drawing a line between coordinates: you must acquire and license road data, select a routing engine, preprocess a graph, expose an API, validate coordinate order and snapping behavior, and plan for map updates, traffic, observability, and capacity. This guide uses Project OSRM as a concrete, self-hostable implementation while distinguishing routing computation from geocoding, map rendering, and broader geographic analysis.
The central design decision is whether you need to operate the routing engine yourself or consume a hosted directions API. Self-hosting gives you control over data versions, profiles, request limits, and network behavior, but it also makes you responsible for preprocessing, storage, upgrades, monitoring, and data licensing. A practical starting point is Project OSRM with an OpenStreetMap extract. OSRM's documented pipeline uses a Lua profile during extraction, then prepares the resulting graph with either Multi-Level Dijkstra (MLD) or Contraction Hierarchies (CH). Its HTTP API supports route, nearest, table, match, trip, and tile services. (github.com)
How EDK fits
For: Software engineers, GIS developers, platform teams, and technical analysts building applications that need driving, walking, cycling, or other network-based travel calculations.
Routing is a direct example of computing about the physical world. A route is not merely a visual line: it is a constrained computation over roads, paths, turn restrictions, access rules, travel speeds, and transportation modes. The routing engine supplies one specialized computation layer for a transportation network, while a broader world model can provide the surrounding geographic context: roads and service roads, footways and cycleways, buildings, schools, parks, waterways, charging stations, hydrants, construction activity, or public-service locations. That distinction matters. A routing service answers questions such as “what path connects these points under this profile?” A world-model interface can additionally help find the relevant physical features, relate them to the route, filter nearby assets, compare regions, or compute what lies within a distance or along a corridor. E2's connection is therefore strongest when routing is combined with discovery and spatial computation—not when a routing engine is presented as a replacement for every GIS component.
- •Route-aware asset analysis: compute a route between facilities, then find nearby roads, service locations, charging stations, schools, parks, or other physical features around the resulting corridor.
- •Operational territory planning: compare travel times from depots to customers or public assets, aggregate destinations by reachable area, and rank locations using both network travel and geographic context.
- •Change and constraint analysis: combine route results with current geographic features such as construction activity, road access, waterways, or pedestrian infrastructure to identify where a planned route may require additional review. The routing engine itself does not establish that those features exist or that they are current.
The EDK outcome
Before you start
Basic command-line and Docker familiarity.
A machine with enough disk, memory, and CPU for the geographic extract and preprocessing job; exact requirements depend on region size, engine version, profile, and hardware.
A licensed road-network data source in PBF or another format supported by the selected engine.
A clear definition of supported travel modes, geographic coverage, response format, waypoint limits, and freshness expectations.
A test set of known routes and edge cases for every supported profile.
Learning objectives
Explain the difference between road-network data, a prepared routing graph, and an application-facing routing API.
Prepare a small OSRM graph from an OpenStreetMap PBF extract using Docker.
Issue and validate a route request with explicit coordinate order and response options.
Design operational boundaries for profiles, updates, rate limits, testing, and geographic context.
Relate route geometries to broader physical-world analysis without confusing routing with geocoding or general GIS computation.
Verification
A direct route request returns a successful response for a known pair of points.
The service rejects reversed or out-of-range coordinates instead of silently producing an unexpected route.
Returned geometry endpoints are close to the requested waypoints.
Known one-way, access-restricted, and disconnected-network tests produce the expected behavior.
The deployed graph, profile, and data-extract versions are recorded and reproducible.
A route can be stored and queried against nearby geographic features using explicit distance units.
Troubleshooting
No route found: verify coordinate order, profile compatibility, network connectivity, extract coverage, and whether the points are too far from routable edges.
The container starts but requests fail: confirm that all prepared `.osrm.*` files are mounted, that the algorithm matches preprocessing, and that the graph base path is correct.
Unexpected route choice: inspect the selected profile, road access tags, turn restrictions, ferry behavior, data age, and any custom speed or penalty updates.
Profile changes have no effect: rebuild extraction and graph preparation; OSRM profiles are applied during preprocessing rather than selected arbitrarily at request time. (github.com)
Slow matrix requests: cap the number of sources and destinations, separate matrix traffic from interactive route traffic, and evaluate whether CH is more appropriate for the workload.
Nearby-feature results look wrong: check geometry validity, SRIDs, geography versus geometry casts, distance units, and whether the query is measuring to the route line or only to its endpoints.
Next steps
Add a geocoder or place-resolution component so users can submit addresses and place names rather than only coordinates.
Add a route-matching workflow for GPS traces when the application needs to infer which roads were traveled.
Introduce authoritative closure, restriction, or traffic feeds only after defining how they map to the prepared graph and how conflicts are resolved.
Build route-corridor analysis that combines network paths with nearby roads, buildings, facilities, environmental features, or public-service records.
Compare OSRM and GraphHopper on the same fixed test suite, region, profiles, and workload before making an engine decision.
Prerequisites and architecture
Before writing an endpoint, define the service contract. Decide which travel modes you support, whether coordinates or place identifiers are accepted, whether alternatives and turn-by-turn steps are required, and whether responses must include GeoJSON, encoded polylines, or only distance and duration. Also define what “current” means. A route computed from a static OpenStreetMap extract is not the same as a route using live traffic, temporary closures, or agency-maintained restrictions.
For a self-hosted implementation, the main components are an input-data pipeline, a routing engine, an HTTP service, and an application-facing adapter. OpenStreetMap is a common source for road-network data, but it is community-maintained and subject to the Open Database License. The OpenStreetMap Foundation requires attribution and makes clear that derived databases can carry additional licensing obligations. Review the license for your exact distribution model before shipping a service or redistributing processed data. (openstreetmap.org)
- •Runtime: Docker or a native OSRM installation.
- •Network data: an extract in PBF format from a source such as Geofabrik or another licensed distributor.
- •Preparation: an OSRM Lua profile for car, bicycle, foot, or another supported mode.
- •API layer: a web service that validates and limits requests before calling OSRM.
- •Testing data: fixed origin-destination pairs covering motorways, one-way streets, turn restrictions, disconnected roads, ferries, bridges, and pedestrian paths.
Step 1: download and preprocess the road network
The following example uses a small Berlin extract to keep the first build manageable. Replace the filename with an extract whose license and geographic coverage fit your application. Project OSRM's documented Docker workflow downloads an OSM PBF file, runs `osrm-extract` with a profile, partitions the graph, customizes it, and starts `osrm-routed`. (github.com)
```bash mkdir routing-data cd routing-data wget https://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf export OSRM_IMAGE=ghcr.io/project-osrm/osrm-backend export PBF=berlin-latest.osm.pbf # Convert OSM data into the OSRM graph using the vehicle profile. docker run --rm -t -v "$PWD:/data" \ "$OSRM_IMAGE" \ osrm-extract -p /opt/car.lua "/data/$PBF" # Prepare the graph for Multi-Level Dijkstra routing. docker run --rm -t -v "$PWD:/data" \ "$OSRM_IMAGE" osrm-partition "/data/$PBF.osrm" docker run --rm -t -v "$PWD:/data" \ "$OSRM_IMAGE" osrm-customize "/data/$PBF.osrm" ```
- •Pin the container image by a tested release tag rather than relying on an unqualified moving tag.
- •Record the extract timestamp, download source, profile revision, engine version, and preprocessing command.
- •Use separate output basenames when preparing the same PBF for car, bicycle, and foot profiles.
- •Expect preprocessing time and memory requirements to grow substantially with geographic coverage.
Step 2: start the engine and expose a minimal route endpoint
Start the prepared graph with MLD and bind the container's port 5000 to a private host port. The OSRM HTTP API uses a path such as `/route/v1/driving/{coordinates}`. Coordinates are semicolon-separated and expressed as longitude,latitude pairs. The response can include route geometry, steps, annotations, and waypoints. (project-osrm.org)
```bash docker run --rm -it \ -p 5000:5000 \ -v "$PWD:/data" \ "$OSRM_IMAGE" \ osrm-routed --algorithm mld "/data/$PBF.osrm" ```
- •Use `steps=true` only when turn-by-turn instructions are needed; detailed responses are larger.
- •Use GeoJSON when the client already works with geographic features; use a compact encoded geometry when payload size matters.
- •Cap waypoint counts and matrix dimensions to prevent expensive requests from exhausting the service.
- •Keep the engine endpoint off the public internet unless it is protected by an API gateway or equivalent controls.
Step 3: model profiles, updates, and geographic context
A routing profile determines which network edges are usable and how they are weighted. In OSRM, profiles are Lua scripts applied during extraction, so changing the profile requires rebuilding the prepared data and reloading the service. This is an important operational constraint: profile changes are deployment events, not ordinary request parameters. (github.com)
Use profiles for stable differences such as car versus bicycle or foot access. Do not treat a profile as a substitute for a complete live-traffic system. OSRM's MLD pipeline can apply segment-speed and turn-penalty updates during customization, but you need a reliable mapping between external observations and the prepared graph's edges. The project documentation describes these inputs as CSV updates and notes that customization can be repeated without repartitioning. (github.com)
- •Version profiles and graph artifacts together.
- •Use a blue/green or shadow deployment when rebuilding a regional graph.
- •Never infer live closures solely from an old static extract.
- •For corridor analysis, preserve the route geometry and query nearby features with an explicitly chosen distance and coordinate system.
Step 4: validate, operate, and scale the service
Routing errors are often plausible rather than obvious. A request may return a route that is technically valid but operationally wrong because a coordinate snapped to the wrong carriageway, a private road was treated as accessible, a ferry was selected, or the underlying map was stale. Build a regression suite with known expected properties rather than comparing every vertex exactly.
At minimum, test that routes stay within the intended region, do not cross prohibited barriers, honor one-way behavior, produce non-negative distance and duration, and return a geometry whose endpoints are near the requested points. Test each supported profile independently. If your application depends on emergency access, truck restrictions, loading zones, seasonal roads, or private facilities, obtain authoritative supplemental data and document how it is merged; the base routing engine cannot establish those facts automatically.
- •Load-test realistic coordinate distributions, not only short routes in a city center.
- •Use timeouts and cancellation when upstream clients disconnect.
- •Publish a health check that confirms the graph is loaded, not merely that the process is alive.
- •Retain enough metadata to reproduce a disputed route.
Workflow
1. Define the service contract
Specify modes, waypoint limits, geometry formats, steps, alternatives, matrix behavior, freshness, error semantics, and licensing responsibilities before choosing an engine. Decide which concerns belong outside routing: address search, map display, traffic, closures, and nearby-feature analysis.
Example request contract: POST /v1/routes with {"profile":"driving","coordinates":[[13.388860,52.517037],[13.385983,52.496891]],"steps":true,"geometry":"geojson"}2. Download an appropriately licensed extract
Obtain a regional PBF extract from a suitable distributor, record its timestamp and source, and preserve the attribution and license information required for your use. Start with a small region while developing.
wget https://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf3. Build the prepared routing graph
Run the selected OSRM profile during extraction, then prepare the graph with MLD partitioning and customization. Treat the resulting group of files as one versioned deployment artifact.
docker run --rm -t -v "$PWD:/data" ghcr.io/project-osrm/osrm-backend osrm-extract -p /opt/car.lua /data/berlin-latest.osm.pbf
docker run --rm -t -v "$PWD:/data" ghcr.io/project-osrm/osrm-backend osrm-partition /data/berlin-latest.osrm
docker run --rm -t -v "$PWD:/data" ghcr.io/project-osrm/osrm-backend osrm-customize /data/berlin-latest.osrm4. Start and query the engine
Run osrm-routed with the matching algorithm, then issue a small direct request. Confirm that coordinates, profile, response geometry, distance, duration, and steps match expectations before adding an application adapter.
docker run --rm -it -p 5000:5000 -v "$PWD:/data" ghcr.io/project-osrm/osrm-backend osrm-routed --algorithm mld /data/berlin-latest.osrm
curl 'http://127.0.0.1:5000/route/v1/driving/13.388860,52.517037;13.385983,52.496891?steps=true&geometries=geojson&overview=full'5. Add validation and a stable API layer
Validate numeric ranges, coordinate order, waypoint counts, profile names, request body size, and timeouts. Normalize upstream errors, protect the engine with authentication and rate limits, and include graph/profile versions in logs and responses.
Reject: latitude outside [-90,90], longitude outside [-180,180], empty coordinates, unsupported profiles, excessive waypoints, and requests that exceed the configured timeout.6. Test route behavior and surrounding analysis
Build regression cases for one-way roads, disconnected networks, bridges, ferries, private roads, pedestrian paths, and profile-specific access. When routes must be related to physical assets, store their geometries and run explicit spatial queries in a GIS or spatial database.
SELECT id FROM assets WHERE ST_DWithin(geom::geography, ST_GeomFromGeoJSON(:route)::geography, 100);7. Deploy with versioning and observability
Version the extract, profile, engine image, preprocessing commands, and configuration. Use health checks that confirm the graph is loaded, monitor latency and errors, separate interactive routes from large matrix jobs, and use blue/green replacement when refreshing graph artifacts.
Record per request: request_id, profile, graph_version, engine_version, waypoint_count, duration_ms, response_code, and route_error_code.Questions teams ask
Can I build a routing service directly from GeoJSON road lines?
Not reliably without substantial additional work. A routing engine needs a connected graph with nodes, directed edges, access rules, turn relationships, speeds or costs, and often restrictions. Raw line features may be useful source geometry, but they do not automatically encode a routable network. Use a routing engine's supported preprocessing pipeline or build and validate an equivalent graph explicitly.
Should I use OSRM, GraphHopper, or a hosted directions API?
Use OSRM when you want a focused, self-hosted engine and are comfortable preparing graph artifacts. Consider GraphHopper when profile and custom-model controls are central to the application; its documentation describes customizable profiles and routing behavior. Use a hosted API when operational simplicity, global coverage, managed updates, or vendor-provided traffic matter more than infrastructure control. Compare licensing, geographic coverage, commercial terms, latency, request limits, and the exact features you need rather than choosing by benchmark claims alone. (github.com)
Why does my route fail even though both points are on a map?
The points may be outside the prepared network, too far from a routable edge, located on disconnected components, assigned to an incompatible profile, or affected by access restrictions. Coordinate-order mistakes are also common. Verify longitude,latitude order, test the engine's nearest behavior, inspect the selected extract, and compare the failing points with the actual routable network.
How do I find physical features near a computed route?
Store the returned route geometry in a spatially enabled database and use a corridor or proximity query. With PostGIS, `ST_DWithin` can test whether features fall within a specified distance, while `ST_LineLocatePoint` can provide a relative position along a line for ordering or linear referencing. Make the coordinate system and distance units explicit. (postgis.net)
Keep exploring
build a geocoder
Routing usually begins after an address or place description has been converted into coordinates; this guide covers address search, reverse lookup, normalization, ranking, and geographic filtering.
analyze spatial data with geopandas
After obtaining route geometries, GeoPandas provides a practical Python workflow for spatial joins, overlays, measurements, and summarizing features along or near routes.
build a fleet tracking dashboard
A live fleet application can use routing to relate vehicle positions to roads, facilities, service areas, and other physical features while keeping current state separate from historical observations.
Sources
Project OSRM: OSRM backend README and Docker quick start
Authoritative project documentation for downloading an OSM extract, running OSRM extraction, partitioning and customization, starting the routing daemon, and issuing a route request.
Open sourceProject OSRM HTTP API documentation
Documents route URL structure, longitude-latitude coordinate order, response fields, geometry formats, steps, alternatives, and service error codes.
Open sourceProject OSRM profiles documentation
Explains Lua profiles, preprocessing behavior, multiple profiles, and the requirement to rebuild prepared data after profile changes.
Open sourceOpenStreetMap Copyright and License
Authoritative licensing and attribution guidance for OpenStreetMap data, including ODbL obligations.
Open sourcePostGIS ST_DWithin documentation
Documents proximity queries, distance units, coordinate-system requirements, and index-assisted bounding-box filtering.
Open sourcePostGIS ST_LineLocatePoint documentation
Documents locating the closest point on a line as a fractional position, useful for route ordering and linear referencing.
Open sourceGraphHopper profiles documentation
Provides an authoritative comparison point for vehicle profiles, turn costs, custom models, and route-priority adjustments.
Open source