Skip to content

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

Documentation menu
docs / clearance · raw .md

Measured clearance on a route

A truck router answers a height question from what the map records. Where the map records nothing, it has nothing to act on, so it returns the road as passable and the response looks exactly like one where every structure was checked. That failure is silent, and it points the dangerous way: a missing tag makes the answer look better than the ground.

POST /v1/clearance/along answers the other question. Given a route shape and a vehicle, it walks the corridor over a survey's own geometry and reports whether the vehicle passes, and if not, where, by how much, and with what uncertainty. It reads geometry rather than attributes, so it is independent of the tagging above.

Both answers stay true and neither replaces the other. The sentence our ADR reference has carried since it shipped is the shortest way to say it: an unchanged route is not a clearance. This page documents the measurement that sentence tells you to go and get.

Why the tagged answer is not enough

The numbers are our own, measured on one UK OpenStreetMap extract and published with their denominators on truck restriction data, measured:

Highway ways in the extract8,212,278
Ways carrying maxheight18,353
Ways carrying maxheight:physical107
Height-tagged ways in one Cumbria sample0 of 893 major bridge ways

Two consequences follow, and they are different from each other.

A tag can be absent. Absence of a recorded restriction is not evidence that a road was checked. On this data it is the ordinary case.

A tag describes a structure somebody signed. maxheight is a posted legal limit and maxheight:physical is a measured physical one, and there are 107 of the latter in the whole extract. In practice neither describes the branch of a mature street tree hanging over a lane, a temporary gantry, or a service run slung under a deck. Those are real obstructions and nothing posts a limit for them. Surveyed geometry finds them because it is not looking for a sign.

For context on why this matters: the Network Rail struck-bridge dataset runs to more than 5,000 structures, and 1,666 strikes were recorded in 2024/25, roughly one every five hours.

Turning survey data into gauging and clearance outputs is already bought at scale, as a batch service delivered weeks later. Two published procurement records, both about other organisations and neither one of ours: Network Rail paid Cordel £4,336,000 for point cloud data processing (Find a Tender notice 030479-2026), and National Highways paid IBI Group £556,560 for "online visualisation and interaction with the data outputs with driven imagery and LiDAR data" (Contracts Finder, expired February 2025). What is documented below is the same class of work answered per request, against a route.

What the answer is, and is not

It isIt is not
A physical measurement of the gap between a road surface and the lowest validated surface above itA signed, posted or legal height
Taken from one dated survey, with a stated uncertainty boundA certificate about the route
Reported per station along a corridor, with the limiting point namedA statement about signage, or about what you are permitted to do
Silent about ground the survey did not cover, and explicit that it is silentAn all-clear over ground nobody surveyed

A posted height carries a margin somebody chose and it carries legal force. A measurement carries neither. Every response therefore ships a clearance_enforcement block whose route_certified field is always false, and the field is a type rather than a boolean, so there is no value of it that serialises to true.

Where a posted height and a measured gap disagree, that is a matter for the road authority that posted the sign. It is not something a survey settles and it is not something this endpoint edits.

The two calls

Route acquisition is deliberately a separate call.

bash
# 1. A truck route, so the search already avoids the restrictions the map
#    records. `geometries=polyline6` gives the six-digit shape the clearance
#    call wants; it comes back as routes[0].geometry.
curl "$BASE/route/v1/truck/-0.1276,51.5072;-1.8904,52.4862?height=4.2&width=2.55&weight=40&geometries=polyline6" \
  -H "Authorization: Bearer $API_KEY"

# 2. That shape, measured against the survey.
curl -X POST "$BASE/v1/clearance/along" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "geometry_polyline6": "u{~vFvyys@fS]...",
        "vehicle_height_m": 4.2,
        "vehicle_width_m": 2.55,
        "margin_m": 0.0
      }'

Two calls rather than one, because they are two different claims with two different failure modes, and folding them together would produce one answer that reads like both. Agents get the composition packaged as the check_clearance_on_route MCP tool, which makes the same two calls and returns the measured answer with its bound.

Request fields

FieldTypeMeaning
geometry_polyline6string, requiredThe corridor as a six-digit-precision encoded polyline, typically straight out of a prior route call: routes[0].geometry with ?geometries=polyline6 on the compatible URL endpoint, or geometry_polyline6 from the route MCP tool. Capped at 10,000 vertices; fewer than two decoded points is a 400.
vehicle_height_mnumber, requiredThere is no default vehicle.
vehicle_width_mnumber, optionalAsks the width axis as well. Without it the report answers for height and says why the other axis was not assessed.
margin_mnumber, default 0Your operating margin, added to the vehicle before the verdict and echoed back. No compliance policy is baked in here: both figures are reported.
datasetsstring array, optionalNarrows the search to named datasets, up to 16. It can never widen it. A name this key may not see, and a name that does not exist, are dropped identically, so the endpoint is not an existence oracle for anybody else's data.

The report

json
{
  "basis": "surveyed_geometry_not_signage",
  "clearance_enforcement": {
    "basis": "surveyed_pointcloud_within_survey_difference",
    "route_certified": false,
    "vertical_datum": "WGS84 ellipsoidal heights, no undulation applied.",
    "caveat": "Headroom figures are measured from a dated survey's own geometry as a within-survey difference, with the stated uncertainty bound. They are not signed or legal clearances, the route is not certified, and ground without survey coverage is reported as not surveyed rather than clear. Verify signed restrictions before dispatching a load."
  },
  "route": { "distance_m": 1200.0, "geometry_polyline6": "u{~vFvyys@fS]..." },
  "vehicle": { "height_m": 4.2, "width_m": 2.55, "margin_m": 0.0 },
  "height": {
    "verdict": "pass",
    "coverage": { "kind": "complete", "assessed_m": 1200.0 },
    "tightest": {
      "location": { "lng": -0.1276, "lat": 51.5072 },
      "route_distance_m": 412.0,
      "clearance": {
        "headroom_m": 4.9,
        "sigma_m": 0.03,
        "sampling_gap_m": 0.11,
        "safe_headroom_m": 4.73,
        "overhead_class": "structure",
        "support_m2": 12.0,
        "surveyed_on": "2026-01-14"
      },
      "dataset": "acme-survey",
      "view_url": "https://mapmap.ai/demo/lidar/acme-survey?cam=...&pin=...&panel=clearance"
    }
  },
  "width": { "verdict": "pass", "coverage": { "kind": "complete", "assessed_m": 1200.0 } },
  "datasets": [
    { "dataset": "acme-survey", "survey_dates": "2026-01-12/2026-01-14", "stale": false }
  ],
  "advisories": [],
  "explanation": "The survey covers all 1200 m of this corridor."
}

basis and clearance_enforcement.basis are fixed strings, not free text. caveat is a single constant defined once in the arithmetic crate and read by the gateway, the MCP tool and this page, so the three cannot describe the guarantee differently.

view_url is a deep link into the survey viewer, posed at the limiting point. It is site-relative unless the deployment has been told its own public viewer origin, because a safety report should not carry a guessed host.

advisories are notes attached to stretches of route: a vegetation-limited reading, a survey age worth knowing about, a seam between two datasets. explanation is one honest sentence for a human, generated rather than written, and asserted in tests to be free of the words this product does not get to use.

The four verdicts

Both axes use the same ladder.

verdictWhat it says
passEvery assessed station clears the vehicle by more than the stated bound, over coverage with no gaps. tightest names the closest point, and is absent where the survey found nothing above the corridor at all.
failAt least one station does not clear the vehicle on the measured figure. limiting is the worst point and all_failures is every one of them, worst first, with all_failures[0] being limiting itself.
indeterminateThe vehicle clears the measurement but not the bound on it, or the deciding surface is foliage on a survey too old to trust. reasons carries machine-readable codes and resolution_hint says in one sentence what would settle it.
no_verdictCoverage gaps prevented an answer. Not a fail, and emphatically not a pass.

reasons on an indeterminate is one or more of inside_uncertainty_band, vegetation_age_exceeded, artefact_stale and artefact_freshness_unchecked. no_verdict carries no_survey_coverage or insufficient_data. Branch on the codes and read the English to a person.

The ladder may over-restrict. It is built never to under-restrict.

Every figure carries its bound

A clearance measurement has four numbers and none of them is optional.

FieldWhat it is
headroom_mWhat the survey measured: the lowest validated surface above the corridor, less the road surface under it.
sigma_mCombined one-sigma measurement uncertainty, applied at two sigma.
sampling_gap_mA one-sided bias bound. A gap between samples on the underside of a structure means the true low point can hang below the lowest sample anybody took, by roughly that much.
safe_headroom_mheadroom_m less two sigma less the sampling gap. This is the verdict input, and it is the figure to plan against.

Quote headroom_m beside safe_headroom_m rather than on its own. A headroom figure without its bound is a number somebody will treat as exact, which is why the SDK's formatClearance takes the whole measurement rather than a value and returns a branded ClearanceText a hand-built label cannot be substituted for.

overhead_class is structure, vegetation, wire or unknown. Foliage and wires are labelled, never dropped: a branch across a lane is a real obstruction. Labelling them lets a reader know that a figure is seasonal and compressible, or measured off a thin target, which is a different thing from a figure off concrete.

margin_m is yours. It defaults to zero, it is added to the vehicle before the verdict, and it is echoed back. It is applied to the measurement and not to the routing step, where the map's posted heights already carry somebody else's margin.

Coverage is a type, not a caveat

Three states, and keeping the last two apart is the whole point.

  • Measured open sky. The survey covered this ground, looked, and found no surface above the corridor. That is a measurement, and it supports a pass.
  • Not surveyed. No survey covers this ground. It arrives in coverage.not_surveyed as located runs of route, and no verdict is drawn over it.
  • Insufficient data. A survey reaches this ground but not well enough to decide on. It arrives separately, in coverage.insufficient_data, because the two have different remedies: one wants a survey and the other wants a better one.

"We measured no obstruction here" and "we have no data here" are different answers, so coverage is two distinct shapes. The complete shape has no field for gaps at all, and a pass verdict accepts only that shape. A route that passes over unsurveyed ground is therefore a state the wire format cannot represent, in TypeScript and in Rust alike, rather than a rule somebody remembered to apply.

A refusal is not always thin coverage, either. The commonest reason a station under a structure has no usable cell is occlusion: the survey could not see through the thing above you. Neither the report nor this page words that as sparse survey.

Width is a different quantity

Supply vehicle_width_m and the report answers a second question: how wide the clear corridor is. That answer arrives as clear_width_m and safe_clear_width_m on a separate measurement type, never in a headroom field, because a lateral distance read as a headroom is the same conflation as a sign read as a measurement, turned through ninety degrees. The SDK keeps them apart the same way: MeasuredLateral has no headroom field and formatLateral will not take a MeasuredClearance.

The width axis answers not_assessed, with the reason spelled out, when no vehicle_width_m was supplied, and again when the height axis could not be assessed either. Where a width was supplied and the survey is too thin at the corridor edges, the width axis runs the same ladder as the height axis and comes back indeterminate or no_verdict over those stretches, while the height axis can still answer for the same ground.

Swept path is out of scope. A route shape is a centreline approximation, lane position is not recoverable from it, and overhang arithmetic on an approximate centreline would manufacture precision. The corridor is sampled across the vehicle's width plus a lateral allowance for the registration error between a route shape and the carriageway it stands for. That allowance covers the ordinary bow of the shape between its vertices. It does not cover which lane a vehicle is in, and under an arch the outer lane is the direction of danger, because a soffit is lowest at its edges. Lane-resolved geometry fixes that and it is a later phase.

The vertical datum, and why it cancels

headroom_m is overhead_z - road_z: two heights from the same cloud, the same epoch and the same processing. A constant vertical offset, whether a geoid undulation or a reheighting constant, cancels exactly in that difference, so the absolute vertical basis never enters the arithmetic. What is left is the differential survey error over the few metres between the road and the structure above it.

A terrain model contributes nothing at any point. Not as a road surface, not as a gap filler, not as an adjustment. A survey published as height above a DEM is refused rather than caveated, because the disagreement between a terrain model and a survey's own road surface is metre scale and is worst exactly under the flyovers a clearance product exists to measure.

Every response names the frame its figures live in, in clearance_enforcement.vertical_datum, spelled for a reader rather than as an EPSG code.

What it refuses to answer

Each of these is attempted by a test that asserts the refusal, rather than being a promise on a page.

  1. A pass over unsurveyed or insufficient ground. Structurally impossible, as above.
  2. A clearance without its uncertainty. The measurement types require both bound fields.
  3. Legal or signed clearance questions. Out of scope by type. The response text is swept for "legal", "compliant", "permitted" and "certified", with the enforcement caveat excluded by identity.
  4. Verdicts inside the uncertainty band. indeterminate, with the shortfall stated and a hint at what would settle it.
  5. Vegetation-limited verdicts on stale surveys. Past the vegetation age limit they degrade to indeterminate with the age given.
  6. Swept-path claims. As above.
  7. Anything from a survey without a complete datum block. Refused when the artefact is baked, so the runtime never sees it, and refused again at load if the block is doctored afterwards.

Staleness, and what an operator sees

A clearance artefact records the sources it was baked from and their hashes. The dataset manifest records what the dataset holds now. An artefact is fresh only when every source still matches.

An artefact nobody has checked is treated exactly as one known to lag, so a deployment that has not declared what its dataset holds gets the cautious answer rather than the trusting one. Staleness never hides the artefact: the report still carries its measurements, marks stale: true on the dataset entry, and refuses pass.

GET /admin/clearance (admin token) is the operator view: every published artefact, whether it still matches its sources, the bake's own quality statistics, and the reason for any artefact that would not open. An artefact that silently answers "not surveyed" because it is corrupt or oversized is exactly what an operator needs to see.

Accuracy, and why no headline figure appears here

There is no accuracy number on this page, and that is deliberate.

The bound is computed per measurement rather than assumed from a datasheet, and it is computed from things that genuinely vary between one structure and the next: the fit residual of the road estimate, the fit residual of the overhead cluster, the differential georeferencing error of the survey, and the sample spacing on the surface that is doing the limiting. A single published figure would be read as a guarantee across surfaces, densities, structure types and capture geometries it was never measured on, and the one place it would be quoted is the place it would be wrong.

What is validated, and how, is worth stating plainly:

  • Synthetic scenes with known geometry. A slab soffit at exactly 4.800 m, an arch whose minimum is off the centreline, a gantry with a catenary below it, a porous canopy, a density ramp down to the refusal thresholds, and injected birds and strays. The assertions are on the value, the bound, the class and every verdict transition.
  • Property tests. More noise never moves an answer towards pass. Less density never moves one out of a refusal towards pass. Adding a constant to every height changes nothing at all, which is the datum argument above, mechanised.
  • Deliberate failure canaries. Every check is also run against artefacts that are wrong on purpose, and each of those must fail. If one of them passes, the suite goes red on that alone, because at that moment the check has stopped being evidence. A clean log proves nothing on its own.
  • Independent re-measurement on real survey data, by a different estimator that shares no code path with the bake. That bounds the estimator. It says nothing about the survey, because both readings come from the same returns, and the harness says so in as many words.

One known open question, stated because it is the kind of thing a page like this usually leaves out. On a ragged underside, the overhead estimator can read optimistically compared with an independent reading of the same points, and how far is under active measurement rather than settled. That is one reason the verdict ladder refuses rather than narrows, and one reason no headline accuracy figure appears above.

Each baked field publishes its own quality block, and GET /admin/clearance surfaces it. That block, and the checks above, are what a validation conversation about a particular survey is held over. Ask and we will walk you through them.

Availability

Clearance artefacts live inside the hosted point-cloud dataset layout, so the dataset manifest stays the single authority for who may see what:

text
SN_POINTCLOUDS_DIR/
  acme-survey/
    dataset.json              manifest: access, plus the clearance block
    clearance/
      downtown.clr.bin        the field
      downtown.clr.json       its sidecar
VariableDefaultWhat it does
SN_POINTCLOUDS_DIRunsetThe dataset directory. Unset, or set to a directory holding no clearance block anywhere, and POST /v1/clearance/along answers 501.
SN_CLEARANCE_VIEWER_URLunsetPublic origin of the survey viewer, used to build view_url. Unset means site-relative links rather than a guessed host.
SN_CLEARANCE_MAX_ARTEFACT_MIB512Largest field the gateway will decode. A bigger artefact is refused rather than loaded: the corridor over it answers "not surveyed", which is never a pass, and the admin inventory names the file and its size.

The 501 is computed across the whole deployment before any caller's visibility is considered, so it can never become an oracle for whether somebody else's data exists. Feature-detect on it, the same way clients already do for elevation.

A clearance field is baked per dataset, offline, from the survey's own point data, and it is separate work from a payload bake: it needs a declared vertical datum, a class map it can trust, and a review of what the bake refused. It is not part of the published point cloud bake packages. Talk to us about the corridor or the network you want measured and we will scope it.

In the SDK

@mapmap/points carries the wire types and two pure helpers, so a client can hold a report without re-implementing anything safety critical: RouteClearanceReport, RouteVerdict, WidthVerdict, Coverage, MeasuredClearance, MeasuredLateral, ClearanceEnforcement, formatClearance, formatLateral and reportToGeoJson. Nothing there computes a clearance. Full list on the SDKs page.

Next steps