Labs · Experiment 7 of 7 · LiDAR, Open data, The game
Rebuilding a city's ground from LiDAR
- Started
- Updated
- Status
- shipped
- Verdict
- Mixed
The game's ground was 3.5–6 m too high and inconsistent between districts. Can every height grid be rebuilt from the City's LiDAR ground returns, on one datum, without opening seams or leaving objects floating?
Why
Slopcouver's terrain came from global open terrain tiles (AWS Terrarium), stored relative to the lowest point in each area, in one grid for the core and one for each of 16 outer districts. Measured against the City's 2022 LiDAR ground returns, the core grid stood a median 5.6 m too high inland but only 3.8 m too high within 50 m of the water, and the district grids disagreed with each other by up to a metre. With the sea fixed at the game's 2.0 m, that showed up as low shores, bridge decks measured over their own ground sitting 4.4 m too high over the water, and water standing on plazas. It wasn't one shift that could be subtracted.
Source: each grid minus the LiDAR ground (median of 1 m ground cells within 5 m of each node), from the re-bake plan's measurements, 8 Nov 2026.
Hypothesis
Rebuilding every grid from the LiDAR's ground class, with one datum (game height = CGVD28 + D, the sea kept at 2.0), will:
- bring the ground to within 0.5 m of the LiDAR at the shore and inland;
- keep the seams between grids no worse than today, and under 0.3 m;
- move everything that stands on the ground with it, so that a census finds only a short, explainable list of objects that float or sink.
Method
- Ground: each grid node takes the median of LiDAR class-2 (ground) points within 5 m, with a measured/unmeasured mask; gaps (water, large buildings, dense canopy) are filled from the old grid, shifted by its local offset and blended over 50 m.
- Datum: D = 1.85 m from mean sea level at Point Atkinson. (The LiDAR's own water was flown at low tide, −0.5 to −1.1 m CGVD28, so it couldn't serve as the datum directly.)
- Trust rules, added after review: a measured node more than 10 m off the old grid's trend isn't trusted (decks classed as ground had made 70 m steps); a filled value is trusted only within 5 m of the old grid less its local offset; a gap two grids share carries one value at their seam; water connected to the sea takes the sea's level, while lakes keep their own.
- Rivers and decks: the river data is rebuilt from the new ground with the game's own code; the elevated viaducts, held at absolute heights, are moved with the regional ground change and pinned where they meet streets; the Lions Gate approach is anchored to the new ground.
- Census: build the whole city (core plus all 20 regions) on the old ground and on the new one, and compare, for every mesh, instanced item and collider, the change in its base with the change in the ground beneath it.
- Review: three rounds of independent review on the candidate outputs, read-only.
- Tools and hardware: Python with NumPy for the rebuild; a headless browser with software WebGL for the census and screenshots; one Debian desktop. No cost.
Results
Ground against LiDAR
| Distance from water | Old ground | New ground | LiDAR + D |
|---|---|---|---|
| 30–50 m | 3.53 | 2.46 | 2.65 |
| 60–100 m | 8.11 | 4.30 | 4.30 |
| 110–250 m | 12.64 | 8.74 | 8.84 |
| 500 m – 1 km | 56.0 | 52.6 | 52.6 |
Seams between grids
| Seam | Old worst step | Release 143 worst step |
|---|---|---|
| Southlands / Dunbar (the worst seam overall) | 1.79 m | 1.43 m |
| UBC / SW Marine (on a 45 m slope) | 1.57 m | 1.22 m |
| Point Grey | 0.55 m | 0.57 m |
| Every district edge | median step about 0.1 m, as before; a few 99th percentiles slightly above the old grids' | |
The float/sink census
Source: the whole-city census on Release 143's pack (core plus all 20 regions, software rendering), 8 Nov 2026.
Of 304,340 objects (298,553 matched between the two builds), 97.8% follow the ground within 0.5 m. Most of the rest are merged building chunks whose base is the lowest point of a whole chunk while the ground was sampled at its centre. The real items the census found, all now handled: the elevated viaducts (+5.9 m before they were moved), the Lions Gate approach (+3.9 to +4.3 m before anchoring), the Bloedel Conservatory dome poking through its plaza on the sharper LiDAR summit (now seated on the highest ground under it), and about 90 colliders at BC Place where the stadium came down 6–7 m with the flats while Beatty Street's edge stayed at 11.3 m: the real lie of the land.
Independent review
Round 1: fail. Fills that wrapped round a grid's edge were treated as measured ground and put false hills of up to 90 m on the Fraser foreshore. Round 2: fail, narrowly: one wrapped fill made a 6 m trench. Round 3: pass, with the known items. The final pass that makes overlapping district grids agree also passed review, with two medium findings recorded below. A separate tool, the one that moves the viaducts, failed its first review (a 0.78 m step at a three-way junction) and passed its second.

Old groundLiDAR ground
Old groundLiDAR ground
Old groundLiDAR ground
Old groundLiDAR ground
Old groundLiDAR groundLimitations and threats to validity
- D is a single number taken from a tide gauge, not from the survey's own water (flown at low tide).
- Trust rules are heuristics. At cliffs and deck approaches some measured nodes stay more than 5 m from the LiDAR (3,586 at UBC's cliffs, 1,663 in the core); one ledge at the Southlands escarpment is lumpy.
- The census compares bases with the ground under an object's centre, which flags slope noise as well as real errors; the 2,273 downtown colliders flagged have a median of 0.54 m, which we read as noise.
- Correctness was checked in software rendering, not on a phone; timing wasn't measured (grid sizes are unchanged, so memory shouldn't change).
- Making overlapping grids agree has a cost: it changed 24,115 rendered nodes in narrow bands along the overlaps (290 by more than 1 m), and raised the mean error against the LiDAR by 0.03–0.16 m per district. On bluffs, one grid copies the other's edge row: at SW Marine one spot is 4.77 m further off the LiDAR, and Point Grey has a 3.86 m step where the LiDAR's is 1.13 m. Both have known fixes, not yet made.
- Four viaduct ramps come out steeper than before (worst 17.8%). The LiDAR shows the real decks still elevated at their mapped ends, so the true touchdown is further along the next street; the old ground hid this.
Verdict
Supported (1): the ground follows the LiDAR to within 0.1 m from 30 m inland, and within 0.2 m at 30–50 m from the water.
Not supported (2) as stated: the 0.3 m gate wasn't met (the old grids never met it either). The worst seams did improve (1.79 → 1.43 m and 1.57 → 1.22 m), while Point Grey and a few 99th percentiles came out slightly worse.
Supported (3): 97.8% of objects follow the ground within 0.5 m, and every real outlier was explained and fixed.
Practical application
- For the game: shores, beaches, bridges and hills at their real heights; the five bridges can now be raised onto their measured decks. Live since Release 143 (8 Nov 2026).
- For anyone building terrain from open LiDAR: don't shift old grids; rebuild from the ground class, distrust nodes far off the old trend (decks and cliffs are classed as ground surprisingly often), handle fills separately from measurements, and run a census of everything placed on the ground before shipping.
- For flood and shoreline work: the same pipeline gives a consistent, sea-level-referenced surface across districts; the corrected shore heights are what freeboard and sea-level-rise visualisations need. (Our grids are for a game, at game resolution; they aren't a survey product.)
- What remains: a slope-tolerant trust test for cliffs; fixing the Science World plaza water; checking piers and bridge towers on the new ground.
Reproducibility
In the Slopcouver repository (private for now): app/tools/lidar/rebake-ground.py (all grids, D as one parameter; writes only the changed files plus a receipt), app/tools/lidar/reseat-decks.py (moves the viaducts; refuses to run twice), raise-decks.py (the five bridges), and the census scripts.
python3 tools/lidar/rebake-ground.py <pack dir> <rasters dir> <out dir> --D 1.85
python3 tools/lidar/reseat-decks.py <old pack> <new pack> <decks.json> <out decks.json> --D 1.85
Data and credits: City of Vancouver LiDAR 2022, OGL–Vancouver. The old ground: Terrarium elevation tiles (Mapzen / AWS Open Data; sources include USGS, NRCan CDEM and others). Mean sea level at Point Atkinson: Fisheries and Oceans Canada, Canadian Hydrographic Service. Street and bridge data: OpenStreetMap contributors, ODbL.