How we built London in 3D
The London corridor in our labs demo carries 10,473 buildings whose heights come from Environment Agency 1 m LIDAR rather than OSM tags, 1,292 trees joined to the Greater London Authority street-tree survey, three Blender-baked landmarks totalling 83.5 KB of glTF, and facades generated procedurally at runtime so the whole scene ships without a single image asset. This is how each of those was built, and where each one is still wrong.
Why not use the building heights already in OSM?
Because along this corridor they barely exist. OSM height or level tags cover roughly 4% of the buildings in the bounding box, which produces a city of a few tall towers standing in a field of flat grey footprints. So the heights are derived instead from the Environment Agency's LIDAR Composite at 1 m, as the 90th percentile of (surface model minus terrain model) inside each OSM footprint, joined offline. The data is Open Government Licence v3.0, and the demo credits it on screen.
The 90th percentile is the important choice. A mean is dragged down by courtyards and light wells; a maximum is dragged up by a single lift overrun or aerial. The p90 lands on the bulk roof, which is what you want a building's mass to be. Where a spire or tower genuinely exceeds that bulk by more than 8 m, the bake also stores a separate peak, and 370 of the 10,473 buildings carry one.
Of the total, 1,427 are near-detail buildings that get full facade treatment and 9,046 are background massing. Tests hold the join honest: Victoria Tower has to land within 6 m of 98 m, Elizabeth Tower above 70 m, nothing taller than 250 m, and the median between 8 m and 30 m.
LIDAR also fails in ways that are obvious once you see them. An unnamed range of the Palace of Westminster sampled the clock tower standing behind it and came back as a 65 m black slab parked in front of Big Ben, which is why the bake carries an explicit height override for exactly one OSM way. And the Blender Elizabeth Tower is modelled to its surveyed 96 m rather than to LIDAR, because a 1 m composite reads it at 79.4 m p90 and 90.4 m maximum: it cannot see the finial.
How are the facades made without shipping textures?
They are generated at runtime, one tileable texture per facade family, and there is no atlas. Each building is assigned to one of five families (stone, brick, office, glass or civic) from its OSM tag, then by an explicit name pattern for the civic set, then by height as a fallback: above 70 m glass, above 34 m office, otherwise stone. Storeys are 3.4 m, window bays 3.1 m, the ground band 4.5 m, the parapet 0.7 m, and each family's texture is an 8 by 8 cell grid at 32 px per cell.
A shared atlas was the obvious first attempt and it was wrong. Under repeating wrap, an atlas column bleeds into its neighbour, so brick terraces started sampling the glass column. One texture per family costs a handful of extra materials and removes the whole class of bug.
Each family also carries its own lit fraction at night, from 3% for civic buildings to 10% for glass, because a government building at 2 am is not an office block at 2 am. Both of those knobs are exposed: ?lit= scales the lit fraction and ?warmth= shifts the colour temperature of lit windows, and the same values can be carried in a hosted Studio theme so a style can drive the 3D scene.
Shopfronts are the ground-floor pass. Wall segments that face the drive within 35 m and are not civic get three merged meshes floated proud of the facade: a glazed band from 0.4 m to 3.6 m, a fascia above it, and abstract sign quads at roughly one per 18 m of frontage, coloured from a fixed palette. The rule we set before writing any of it was that no sign ever spells anything: no invented brands, no invented shops.
How real are the trees?
Real where the survey covers them, and honestly generic where it does not. The GLA street-tree dataset holds 1.14M trees under Open Government Licence v3.0, of which 15,373 fall in our bounding box and 1,292 sit in the corridor we render. Of those, 733 carry a surveyed genus, 479 a surveyed height and 437 a surveyed canopy width. 513 of them are London planes, which is exactly the answer anyone who has walked the Embankment would expect.
Each tree is a five-sided tapered trunk plus two icosahedron canopy lobes, instanced, with the surveyed canopy width driving spread where the join matched and a 7.5 m to 15 m range where it did not. Wind is about ten injected lines of vertex shader, with per-tree phase derived from the instance matrix so no two sway together.
The honest gap is the silhouette table. There are 40 distinct genera in the data and only six shapes plus a default, so a plane, a lime, a maple, a cherry, a hawthorn and a rowan are distinguishable, and the other 34 genera share a generic crown. That is a real limit, not a rounding.
What went through Blender, and what did it cost?
Three landmarks: the London Eye at 49,828 bytes, Nelson's Column at 19,904 and Elizabeth Tower at 13,784. All three are glTF binary compressed with gltfpack's meshopt encoder, which comes to roughly 18 KB and 5 KB over the wire for the two originals.
Meshopt rather than Draco was a constraint, not a preference. Draco needs an external decoder fetched at runtime, and the demo's promise is that it is self-hosted and clean under a strict content security policy. Meshopt's decoder ships in the bundle.
Two things do not survive glTF export intact, and both are handled on load. Blender's emission strength is one, so imported materials are replaced wholesale by matching on the Blender material names. Load failure is the other: each landmark falls back to procedural geometry, so a missing file gives you a cruder London Eye rather than a hole in the skyline.
What does the camera have to do differently?
Run a wider lens than a map. The scene uses a 55 degree vertical field of view against MapLibre's default of about 37, because with the default lens the horizon only enters frame above roughly 71.6 degrees of pitch, and the whole point of a driving view is that you can see where the road goes. At 55 degrees the sky band appears from about 62.5 degrees, which is inside the range a chase camera actually uses.
Fog is exponential-squared per time-of-day preset, and live weather multiplies its density: cloud fraction scales it by up to nearly double, rain adds more. With no ?time= parameter the scene picks night, dawn, day or dusk from a solar-elevation calculation for the actual coordinates, so the demo is dark at midnight without anyone configuring it.
What this does not cover
- It is one corridor, not a planet. Outside the bounding box the scene falls back to the standard map. The demo is labelled an experiment on our labs index for exactly this reason.
- The bake pipeline is not in the repository. The LIDAR join, the Blender scripts, the road export and the tree join all run offline and their code lives outside the demo. You can read what the scene does with the results; you cannot read how the results were made.
- Manhattan is a different city with different data. It has no landmark models yet, and its heights do not come from the same source. Anything in this post about LIDAR or the tree survey applies to London only.
- Photographic facade textures were tried and cut. They were generated, they looked plausible in stills, and they were dropped in favour of the procedural set on 27 July.
Try it
Open the London demo and press the credits toggle first: every dataset in this post is named there with its licence. Then drive it with ?time=night&lit=2 to see the lit-fraction knob move, or ?lowfx to see what it does on a weaker GPU. The demos documentation covers every URL parameter and the theme block that carries them, hosted vector tiles and styles explains the basemap underneath, and Studio is where the styles that drive it are made.
Map data © OpenStreetMap contributors, ODbL. Building heights derived from Environment Agency LIDAR Composite 1 m DSM and DTM, Open Government Licence v3.0. Street tree data from the Greater London Authority street-tree survey, Open Government Licence v3.0.
