Skip to content

How a map is drawn

About what roadstyle makes, what MapLibre draws, the two forms of road data, and why the page uses one tile zoom. This page is the same for the Python engine and the Rust engine.

roadstyle makes the map, MapLibre draws it

roadstyle does not draw pixels. It turns the road edges into a map for one drawing library, and that library draws. You select the library with the keyword backend of render_edges:

backend The library that draws What render_edges gives
"web" (the default) MapLibre GL JS, in the browser one HTML file
"folium" Leaflet, in a notebook a folium.Map
"lonboard" deck.gl, in a notebook a lonboard.Map

The rest of this page is about "web", the backend of almost all maps. The Rust engine makes only this backend.

flowchart TB
    roads["Road edges + look"]
    rsty["roadstyle<br/>makes the style<br/>and the road data"]
    ml["MapLibre GL JS<br/>draws the pixels"]
    roads --> rsty
    rsty -->|"style + GeoJSON<br/>or vector tiles"| ml

The "web" backend makes:

  1. A MapLibre style: the layers and their paint expressions (the widths for each zoom, the colors, the casing under the fill, the drawing order of bridges and tunnels), and the roadstyle:* metadata for the dashboard.
  2. The road data, as GeoJSON or as vector tiles (the next section).
  3. With render_edges: one HTML page that holds 1 and 2, MapLibre and the dashboard.

MapLibre reads the style and the data and draws them with WebGL. It draws a new frame each time that the view or a filter changes.

Three choices for the road data

MapLibre draws only from tiles: small squares of the map, one grid for each zoom. Three choices decide who makes these tiles and how they get to the browser. Each choice comes after the one before it.

Choice 1: GeoJSON or vector tiles

GeoJSON (the default) Vector tiles (MVT)
Who cuts the roads into tiles MapLibre, in the browser, for all roads when the page opens roadstyle, before the browser
Calls render_edges(edges), export_inline, makeStyle tiles=True, export_style, Tiler
Good for up to about 10⁴ edges about 10⁵ edges

With GeoJSON, roadstyle makes no tiles. The page gives MapLibre all the roads, and MapLibre cuts them; data_maxzoom and data_tolerance control that cut. Choices 2 and 3 do not apply.

After the cut, MapLibre does the same work with both forms: it turns each tile into shapes for the GPU and draws them. The map looks the same, and the dashboard works on both.

Choice 2: how the vector tiles get to the browser

Way Call Where the tiles are Server What the browser loads
A. In one HTML file render_edges(edges, tiles=True) one PMTiles archive inside the page not necessary: the file opens from disk the whole file at the start, with all tiles; MapLibre decodes only the tiles in view
B. On a server export_style(...) files on a web server necessary style.json, then only the tiles in view, one request for each
C. Made in the browser new Tiler(...) (WebAssembly) nowhere: the engine makes each tile when MapLibre asks for it only for the page and the roads file the roads file one time; the tiles exist only in memory
  • Way A is one file for everything. Send it to a person or open it from disk.
  • Way B is for a large area, and for other map apps (GeoLibre, QGIS) that open style.json by its URL.
  • Way C is for a page that makes its maps itself, as duckosm-rust's page does.

Choice 3: the tile files on the server (way B only)

XYZ folder (format="xyz") One PMTiles file (format="pmtiles")
The files one file for each tile: tiles/<z>/<x>/<y>.pbf one file, roads.pmtiles; an index at its start tells where each tile is
How the map gets one tile one request for that tile's file one request for a part of the file (an HTTP range request)

In both forms the map downloads only the tiles of its view. The difference is the number of files on the server. Make tiles and PMTiles shows the calls.

One tile zoom or several

When the map is flat, all tiles on the screen have the same zoom. When the map is tilted, MapLibre loads the near tiles at a higher zoom than the far tiles, because the near part of the view is larger on the screen.

zoom 14zoom 14 15151515 a step 15151515 15151515 Several tile zooms: the road changes width One tile zoom: the road has one width

roadstyle bakes a road's width into each tile for the zoom of that tile. Where a near tile (zoom 15) meets a far tile (zoom 14), the same road gets two widths, and the map shows a line across all roads at that place.

To stop this, the page makes MapLibre use one tile zoom on the whole screen. The page template sets it for each source that is not a base map:

source.calculateTileZoom = (z) => z;      // web_template.html, _flatTiles

The cost:

  • Up to 60° of tilt, MapLibre already uses one tile zoom, so the rule changes nothing.
  • Above 60°, the page loads 2 to 3.6 times as many tiles (66° to 70°).
  • max_pitch=60 keeps every view in the range where the rule costs nothing. The default is 70.

A page of your own that adds roadstyle's style to its own MapLibre map does not have this rule. Add the same line for each roads source, as the tile example of Use roadstyle in the browser does.

See also: Big networks · Make tiles and PMTiles · Architecture of the Rust version