Bastiaan Breemer

Routing · 4 min read

Replacing a routing API with VROOM and Valhalla

Transport planning in Heyl's ERP used to work like this. Every customer went into an external planning tool, a planner adjusted the result by hand, and the tool exported a list that was imported back into the ERP. The routing itself was a commercial black box you paid per request, and the plan only existed as the file that came out at the end.

We set out to improve how the routes were calculated. The router turned out to be the smaller half of that.

Plan from a better selection

The first change was the input. Instead of pushing every customer through the planner, we build a base selection. It holds the customers that define a route, plus the customers likely to order in the period being planned.

That selection goes in together with a set of logistic hubs. The planner then answers one question per customer: which hub does it belong to, and which route on that hub does it fit best.

VROOM does the assignment. Time windows, service times and truck capacity are constraints in the request, so a vehicle that cannot carry the load or make the window does not get the stop. Valhalla supplies the travel times VROOM plans on, and afterwards the road geometry per leg so the plan can be drawn.

The matrix is the expensive part

VROOM is fast. The matrix is not. N stops means an N by N matrix, so the asking grows quadratically while the solving stays cheap. Almost all of the tuning went into the Valhalla side.

So there is a small Go service in front of it. It splits a large matrix request into batches Valhalla will accept, stitches the results back into one matrix, and caches the whole thing. The first request for a set of points pays the full cost. Every request after that is close to free, which matters because planning is iterative and the same customers get replanned many times a day.

Planning in the ERP

The planning screen is React and MapLibre, and it works on future transport plans rather than on one export at a time.

Planners can drag a customer from one vehicle to another. Each drop triggers a new VROOM plan, so the arrival times recalculate immediately and a vehicle that has been filled past its capacity or out of its windows says so. That is what replaced the manual adjustment step. The adjustment still happens, because planners know things the model does not, but now the consequence is visible and the plan stays inside the ERP.

When the map is wrong

Valhalla routes on OpenStreetMap, and in the rural parts of Germany we deliver to the coverage is patchy. An access road is missing, tagged as a track, or marked private with no note about who may pass. Valhalla applies the truck costing rules and either refuses to route the stop, or snaps it to the nearest road it will use, which might be the main road two hundred metres away or the wrong side of a canal. The plan looks fine until a driver is standing somewhere with no way in.

The service logs every stop that fails to route, and every stop whose snapped point lands further from the address than it should. The fix is then one of two things. If the map is wrong, we edit OSM. The edit survives the next import, which a local patch would not. If the map is right and our data is off, we store a delivery location on the customer record that Valhalla can route to, and use that instead of the geocoded address. A single database column absorbed most of the long tail.

The extract is reimported and the tiles rebuilt on a schedule, so the failure list runs against every new build. A stop that routed fine last month can quietly stop working.

Was it worth it

Yes, though self hosting moves the cost rather than removing it. There are tiles to build, disk to watch and a schedule to keep, and you own the truck profile now, which is a benefit right up to the moment something in it is wrong.

The optimizer was the easy part. Nearly everything since launch has been about the selection going in and the last two hundred metres between a coordinate and a gate.