Writing a map engine from scratch · 1
Tokyo, drawn live by code written for this page and two small shaders, with no map library underneath. It reads a PMTiles file by byte range, decodes the vector tiles, fills polygons through the stencil buffer and extrudes lines on the GPU. Drag, zoom, and click anything.
© OpenStreetMap · Protomaps Basemap 4.15.2 · drawn by Tilework
Six steps between a file on a server and the map above. Each is a few hundred lines at most, and each can be watched in the panel on the map.
The map lives in one 20.1 MB PMTiles file, cut out of Protomaps' 139 GB planet build by Tilework's own reader and writer. A 127-byte header says where the root directory is; the directory maps each tile's ID on a Hilbert curve to a byte range. Every tile is one HTTP Range request. There is no tile server.
14/14552/6451 → TileID 293554628 first request 16 KB (header + root) → leaf directory → 84 KB of tile
A tile is a protobuf of layers. Features carry their properties as indexes into shared key and value tables, and their geometry as a stream of integer commands on a 4096 grid, each coordinate a zigzag-encoded step from the last. Click a feature to read its stream.
command = id | count << 3 MoveTo 1 · LineTo 2 · ClosePath 7 zigzag 0, −1, 1, −2 → 0, 1, 2, 3
Polygons are not triangulated. Each ring is drawn as a fan of triangles into the stencil buffer, adding one for front-facing triangles and subtracting one for back-facing ones; a single quad then colours every pixel whose count is not zero. Holes cancel out, overlaps stay filled. Turn on fill fans to see them.
Each line vertex carries a miter direction, the sum of the normals of its two segments, capped where the corner is sharp. The vertex shader pushes it out by half the width in screen pixels, so roads keep their width while you zoom; dashes are cut from the distance along the line.
Every tile is drawn only inside its own square, with the scissor test. A tile that hasn't arrived borrows its nearest loaded ancestor, scaled up and clipped to its square, so the map never shows a gap. Past z15, the deepest level in the file, every tile is a z15 tile enlarged.
Names go on a 2D canvas above the map. Candidates are ranked, towns first, then districts, stations and neighbourhoods, and placed greedily, skipping any that would overlap one already placed. The text is the tile's own name property in the chosen language.
Map data © OpenStreetMap contributors, ODbL. Tiles from the Protomaps basemap, build of 19 October 2026. The file covers Kanto at z0–9 and narrows to Marunouchi and Ginza at z15; beyond what it holds, the engine enlarges the deepest tile it has.