50,000 free calls a month, card-free. Get an API key →

news / truck-routing-dimensions · raw .md
Routing8 min readMapMap engineering
An articulated lorry reduced to a wireframe box, ringed by height, width and length measurement lines beneath a low bridge with nested clearance envelopes.

Truck routing with dimensions enforced in costing

MapMap routes trucks against real dimensional constraints: height, width, length and weight go into the cost model itself, so a 4.2 m trailer is never planned under a 4.0 m bridge and a 44 t artic is never sent down a lane posted at 7.5 t. There are two ways to send those numbers. The compatible URL endpoint GET /route/v1/{profile}/{coordinates} takes them as vendor query parameters, and the native POST /route takes them as costing_options.truck. Both return a route the vehicle can legally drive rather than a route you then have to check. Truck is the premium price class at 1p per call, selected automatically from the request.

Why dimensional limits have to be enforced in costing

Because a post-filter can only reject a route, it cannot find the legal one. If height is checked after planning, the engine returns the fastest path, your check finds a 3.9 m bridge on it at kilometre 140, and now you have nothing. You are holding a rejection, not a route.

The tempting fix is to delete the offending edge and re-plan. That gives you a loop with no termination guarantee, and each iteration only proves the route is legal against the specific constraints you happened to test on the specific edges you happened to look at. It is also usually wrong about the shape of the answer. The legal route for a 4.5 m vehicle is often not a small detour around one bridge. It is a different motorway junction 30 km earlier, because the low bridge sits on the only link off that corridor.

In costing, a dimension is an edge filter evaluated during the graph search. An edge is unusable when its posted legal limit is below the vehicle attribute:

Vehicle attributeOSM edge limitEdge blocked when
height_mmaxheightedge max height < vehicle height
width_mmaxwidthedge max width < vehicle width
length_mmaxlengthedge max length < vehicle length
gross_weight_tmaxweightedge max weight < vehicle gross weight
axle_load_t (when known)maxaxleloadedge max axle load < vehicle axle load

The search never expands a blocked edge, so the first route that comes back is already legal, and it is the cheapest legal route rather than the cheapest route that happened to survive a filter. The same argument applies to ADR tunnel categories, which we make at greater length in ADR tunnel compliance, enforced in costing.

How do I send dimensions on the compatible URL endpoint?

As vendor query parameters on GET /route/v1/truck/{coordinates}, alongside the standard ones. Dover to Birmingham, 44 t artic at 4.0 m:

bash
export BASE=https://api.mapmap.ai
export API_KEY=snk_...

curl -fsS -G "$BASE/route/v1/truck/1.3134,51.1279;-1.8904,52.4862" \
  -H "Authorization: Bearer $API_KEY" \
  --data-urlencode "overview=full" \
  --data-urlencode "height=4.0" \
  --data-urlencode "width=2.55" \
  --data-urlencode "length=16.5" \
  --data-urlencode "weight=44.0"

The vendor parameters, in full:

ParamUnitExample
heightmetresheight=4.0
widthmetreswidth=2.55
lengthmetreslength=16.5
weighttonnesweight=44.0
hazmatbooleanhazmat=true
tunnel_codeADR 8.6.4 codetunnel_code=D

The response is the OSRM shape: distance in metres, duration in seconds, location arrays as [lon, lat], and geometry as an encoded polyline at precision 5 unless you ask for geometries=geojson.

json
{
  "code": "Ok",
  "routes": [
    {
      "distance": 362514.3,
      "duration": 16043.9,
      "weight": 16043.9,
      "weight_name": "duration",
      "geometry": "u{~vHmgg@…",
      "legs": [
        { "distance": 362514.3, "duration": 16043.9, "summary": "M25, M40" }
      ]
    }
  ],
  "waypoints": [
    { "name": "A20", "location": [1.3134, 51.1279] },
    { "name": "", "location": [-1.8904, 52.4862] }
  ]
}

This is the point of the compatible endpoint. If your stack already speaks to an OSRM or Valhalla-style routing client, the change is a base URL, an Authorization: Bearer snk_… header and the truck parameters appended to a request you are already building. Coordinate order, units and response parsing stay exactly as they are.

Three things bite people:

  • Truck parameters require the truck profile. Send height on driving and you get a 400 InvalidValue, not a silently ignored parameter.
  • Omitted dimensions are not unlimited. Send any one truck parameter and the rest fill from the EU 96/53/EC maxima for a standard artic: 4.0 m height, 2.55 m width, 16.5 m length, 40 t gross. Set height but omit weight and you route as a 40 t vehicle, which is the safe default but is not your 3.5 t van.
  • Slashed tunnel codes need URL encoding. tunnel_code=B/D must be encoded, which --data-urlencode handles. Setting tunnel_code also implies hazmat=true unless you override it explicitly.

How do I send dimensions on the native JSON endpoint?

As costing_options.truck on POST /route, either directly or through the top-level adr object that the gateway merges into it. The adr form is the one to reach for, because it carries the tunnel semantics as well:

bash
curl -fsS -X POST "$BASE/route" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "locations": [
      { "lat": 51.5072, "lon": -0.1276 },
      { "lat": 52.4862, "lon": -1.8904 }
    ],
    "costing": "truck",
    "adr": {
      "dimensions": {
        "height_m": 4.0, "width_m": 2.55, "length_m": 16.5,
        "gross_weight_t": 44.0, "axle_load_t": 11.5, "axle_count": 5
      },
      "tunnel_code": "C/E",
      "hazmat": true
    }
  }'

dimensions, tunnel_code (nullable) and hazmat are all required keys of the adr object. The gateway turns that into exactly this, which is what the routing engine sees:

json
{
  "truck": {
    "height": 4.0,
    "width": 2.55,
    "length": 16.5,
    "weight": 44.0,
    "axle_load": 11.5,
    "axle_count": 5,
    "hazmat": true,
    "adr_tunnel_code": "C/E"
  }
}

height, width and length are metres, weight and axle_load are metric tonnes, axle_count is an integer, hazmat is a boolean. axle_load and axle_count are emitted only when you supply them, so the engine's own defaults apply otherwise. You may set costing_options.truck yourself instead, but if your values contradict what the adr profile implies, the request is rejected with a 400 costing-conflict whose conflicting_fields names them. Set them in one place.

The native response is the Valhalla shape, with time in seconds, length in the request's units (kilometres unless you ask for miles) and shape as an encoded polyline at precision 6:

json
{
  "trip": {
    "legs": [
      { "summary": { "time": 8611.0, "length": 190.8 }, "shape": "_y|jbB_kiFc[?a[?…" }
    ],
    "summary": { "time": 8611.0, "length": 190.8 },
    "status": 0,
    "status_message": "Found route between points",
    "units": "kilometers",
    "language": "en-US"
  }
}

What does a truck route cost?

1p per call in the first paid band, because truck is the premium price class. Prices are tiered and marginal, so each band prices only the calls that fall inside it:

Monthly volumePremium (truck and ADR)Standard (everything else)
Beyond the free tier, to 1M1p per call0.05p per call
1M to 10M0.7p per call0.03p per call
Beyond 10M0.4p per call0.02p per call

The class is decided from each request automatically, so there is no flag to set and no separate truck plan to buy. A call is premium when it uses "costing": "truck", a top-level adr object, "hazmat": true, POST /adr/check, or the truck profile or truck query parameters on the compatible endpoint. Everything else, including car and van routing, geocoding, matrix and isochrone, bills at the standard rate.

On the free tier, 50,000 included calls a month land after you verify an email, and commercial use is allowed. Included volume is denominated in standard calls, and a premium call draws 20 of them, matching the price ratio. All-premium free usage is therefore 2,500 truck calls a month. Above 25M calls a month we quote. Pay-as-you-go is prepaid credit only, a 4xx response is never charged, and no credit means a 402 or a 429 rather than a surprise invoice.

What this does not do

Dimensional enforcement is only as good as the map, and our map is OpenStreetMap. An edge with no limit tag is unrestricted for that dimension, so an unsurveyed low bridge carrying no maxheight will not block anything. This is a data-coverage limit rather than an engine limit, and we would rather you heard it from us than from a driver. If a bridge matters to your fleet, check it is tagged before you trust the route.

Four more limits worth knowing before you build against this:

  • The compatible URL endpoint carries no axle detail. axle_load and axle_count are native-only. A request through /route/v1/truck/... leaves both unset, so axle-load gates do not apply. Use POST /route when they matter.
  • ADR tunnel-category enforcement needs an ADR-capable routing backend. Against a backend without ADR costing the dimensions still apply, but the tunnel restriction code is not enforced and there is no per-response warning field to tell you. POST /adr/check is always authoritative.
  • The analysis endpoints apply no ADR restrictions. /isochrone, /matrix, /trace_route and /trace_attributes ignore an adr object sent to them. Do not use them for dangerous-goods compliance.
  • The compatible endpoint uses its own error envelope. Routing errors there are OSRM-shaped {"code": ..., "message": ...} bodies (InvalidQuery, InvalidValue, NoRoute, NoSegment, always 400), not RFC 9457 problem+json, because compatible clients dispatch on code. Auth, quota and payment errors keep the shared formats even on that path: 401 and 429 are problem+json, and beyond-tier 402s use the x402 body. If your client dispatches on urn:sn-gateway:problem: type URNs, it needs one extra branch here.

Try it

Run a truck route from your browser with no install at the playground, or issue a key with one card-free call and paste the curl above. The truck parameters, error slugs and units on this page are specified in the API reference, and the cross-cutting rules (coordinate order, units, quotas, the two 402s and the full ADR tunnel-code table) live in the API conventions.

Routing derives from OpenStreetMap, so credit "© OpenStreetMap contributors" when you render or republish it.