本文へスキップ

開発ノート

When the map has no sign: inferring speed limits under Circular 31/2019

The OpenMapTiles schema carries no maxspeed tag. With free tiles, close to 100% of limits must be inferred — and here is where that inference can be wrong.

The original plan was simple: read the maxspeed tag from OpenStreetMap and show it. That plan died at the first step.

Public tiles do not carry maxspeed

The OpenMapTiles schema — the standard OpenFreeMap and most free tile providers use — has no maxspeed field in its transportation layer. Even a road fully tagged in raw OSM does not carry that information into the tile. It is not missing data; the schema omits it deliberately, to keep tiles small.

And there is a second problem underneath: Vietnamese roads in OSM are very often missing the maxspeed tag in the first place. So even building your own tiles leaves most roads with nothing to read.

Inferring from the law

Circular 31/2019/TT-BGTVT sets maximum speeds by area and road type. For cars and trucks up to 3.5 t:

  • Inside a built-up area — 60 km/h on a divided road or a one-way road with two or more lanes, 50 km/h on an undivided two-way road or a single-lane one-way road.
  • Outside a built-up area — 90 km/h on a divided road, 80 km/h on an undivided two-way road.

VietnamSpeedRules implements exactly this table. But applying it requires two things the map does not state outright.

First inference: the built-up area

On the road, a built-up area is delimited by the R.420 and R.421 signs. OSM does not mark those systematically. The app infers it from the tile's landuse layer — residential landuse means built-up. It is an approximation, and it is wrong at the edges of towns.

Second inference: the road-class ceiling

Read literally, the law allows 80 km/h on every undivided road outside an urban area — including a three-metre concrete village lane. The app does not do that: it caps by the road class in the map, so small roads get a lower limit than the law permits.

That is deliberate. Warning earlier than necessary is irritating; warning later than necessary is useless at the exact moment it matters.

Why every inferred value carries a tilde

On the limit sign, an inferred value always carries the words ~ estimated. A driver needs to be able to tell 'the map says this road is 60' from 'the app guesses this road is 60', because those two claims deserve different amounts of trust. Dropping the distinction would mean presenting a guess as a fact.

The real fix

V1.5 will build tiles with Planetiler under a custom profile that carries maxspeed. That does not fill the gaps where OSM itself has no data, but it moves every already-tagged road out of 'must be guessed' and into 'can be read' — and since OBD-II is out of scope, the accuracy of the speed warning is the entire core of this app.