A static web app (GitHub Pages, no backend) that builds architectural site models and drawings for anywhere in England, using Environment Agency LIDAR and OpenStreetMap. Tuned for Bath.
- 3D model, one object per thing: terrain, buildings, roads, paths, railways, rivers and water, parks, woodland, squares, bridges, walls and fences, and trees. Each is a separate layer, and each building, tree and bridge is its own named object → OBJ (Rhino/Blender), per-layer STL (3D printing), polyface-mesh DXF (AutoCAD/Rhino).
- You choose the detail: every feature can be switched off; buildings can be blocks (LOD1), fitted pitched roofs (LOD1.5) or the LIDAR roof surface (LOD2-raw); terrain and feature meshes can be coarsened to a triangle budget.
- 2D: contours, footprints, road kerb lines, paths, rail, rivers and water as polygons, bridge decks, walls, roof ridges/hips, trees at crown radius → SVG, PNG, layered DXF.
- Physical models: laser-cut contour stack DXF in mm at model scale; print-ready closed solids.
- Data: the aligned dataset as GeoJSON (EPSG:27700) plus DTM/DSM ESRI ASCII grids.
Everything runs in the browser. Data is fetched live from public APIs that allow browser access (CORS).
| Dataset | What it contains | How it's used |
|---|---|---|
| EA LIDAR Composite DTM 1 m (WCS) | Bare-earth elevation grid, buildings and vegetation removed. Metres above Ordnance Datum Newlyn (ODN), on British National Grid (BNG, EPSG:27700). Float32 GeoTIFF. | Terrain mesh, contours, hillshade, building ground levels |
| EA LIDAR Composite DSM 1 m (first return, WCS) | Top-of-surface elevation, including roofs and tree canopy. Same grid. | Building heights from DSM − DTM, and OSM alignment |
| OpenStreetMap (Overpass API) | Building footprints (+ building:levels, height, roof:*, heritage tags), roads by class, paths, rail, waterways, water areas, parks, woodland, squares, bridges, individual trees |
All vector layers. In Bath, under 1 % of buildings have a height tag, so heights come from LIDAR. |
| OSTN15 (Ordnance Survey) | Official WGS84/ETRS89 ↔ BNG transformation | Baked into src/geo/ostn15-correction.json by tools/build_ostn15_correction.py; cm-level accuracy |
| OSM raster tiles, EA hillshade WMS | Basemap and LIDAR coverage overlay | Area selection only |
- Area: a BNG-aligned rectangle (integer metres, ≤ 4 km²) drawn on the map, picked from a preset, or found by postcode or grid reference.
- LIDAR: DTM and DSM are fetched as 1 km WCS chunks (4 in parallel, with retries) and stitched into one grid. Nodata holes are filled by interpolating from their edges.
- OSM:
- A single Overpass query fetches everything, with mirror fallback, backoff and a 90 s timeout per request.
- osmtogeojson converts it, then features are projected to BNG through OSTN15 and classified.
- If OSM is unavailable, you still get the terrain.
- Alignment:
- OSM is traced from aerial imagery that can be 1–3 m out (about 1.75 m east in central Bath).
- The app searches shifts of up to ±3 m for the one where OSM footprints best match LIDAR roofs (nDSM > 2.5 m), then shifts all OSM features.
- Polygons are clipped to the site exactly with
polygon-clipping.
- Buildings:
- Ground = lowest DTM value under the footprint. Top = a chosen DSM percentile inside it (typical roof 70th, eaves 40th, ridge 95th). Fallbacks: OSM
height, thenbuilding:levels, then 7 m. - LOD1 is a closed block sunk 0.5 m into the terrain.
- LOD1.5 fits a flat, gable or hip roof to the footprint's oriented box by least squares against the DSM, and keeps it only if the RMS error is ≤ 1.2 m and the pitch is plausible. Stepped terraces on a hillside, and complex roofs, stay as blocks.
- LOD2-fit splits the footprint into parts (one per house in a terrace, found from roofline steps along the long axis; L/T/U shapes bisected into rectangles) and fits flat, shed, gable or hip roofs to each by least squares against the DSM, ignoring edge samples. Each part is its own closed solid (
building_<id>_<n>), so walls and floor join the roof and ground by construction. - With LOD2-fit, chimneys (small DSM clusters 0.9–4.5 m above the fitted roof, rejected if they cover > 25 % of a roof) and parapets (a rim 0.35–1.8 m above a flat roof's interior) become separate objects on the
ROOF-DETAILlayer. - LOD2-raw cuts the DSM grid to the footprint exactly (the roof edge follows the outline) with walls from the DSM. It looks like the real roofs but is noisy and not guaranteed watertight, so use it for visualisation.
- Ground = lowest DTM value under the footprint. Top = a chosen DSM percentile inside it (typical roof 70th, eaves 40th, ridge 95th). Fallbacks: OSM
- Context features (
src/process/context.ts,buffer.ts,scene.ts):- Roads, paths and rail are offset from their centrelines (Clipper):
widthtag, thenlanes× 3.25 m, then a class default; merged per class so junctions become single outlines (these give the drawing kerb lines). - Water = OSM water polygons + river centrelines that no polygon covers. The terrain is carved below the banks, and the water surface sits just under bank level.
- Roads, paths, rail, water and parks are draped on the terrain by cutting their triangles along the terrain grid, then closed into thin solids (
reliefmm at print scale) or left as lighter open surfaces. - Bridge decks follow the lowest DSM across the deck, smoothed and kept within a gentle arch over the abutments. Walls and fences are thin solids. Tunnels are left out of 3D (dashed in drawings).
- Trees are local maxima of the canopy height model (DSM − DTM, minus buildings and bridges), with OSM tree points taking priority.
- Roads, paths and rail are offset from their centrelines (Clipper):
- Terrain mesh: either a regular grid, averaged (not just point-sampled) when coarsened, or adaptive (right-triangle bintree): the DTM is resampled onto a 2ᵏ+1 grid spanning the site and triangles split only where the vertical error exceeds your tolerance (±5 cm to ±1 m), with a triangle cap that raises the tolerance if needed. Pulteney: 318 k triangles on the 1 m grid → 54 k at ±10 cm. Draped features follow the full-resolution surface and are raised above the tolerance. The step is either set by you or the finest one within a triangle budget. It can be closed with walls and a base for a watertight solid.
- Contours: marching squares on a smoothed grid of about 1.5 M cells, joined into polylines and simplified (Douglas–Peucker). Laser stacks use d3-contour's filled regions.
- OBJ: local origin at the site centre rounded to 10 m (
origin.txt; add it back to get BNG), metres, Z-up or Y-up. One group (g) and material per layer, one object (o) per building/tree/bridge/wall. Either one .obj per layer (best for Rhino/CAD) or one combined file. - STL: one binary file per layer in a zip (or one merged file), mm at the chosen scale with vertical exaggeration and a base. All files share one origin and register when loaded together. Each layer is built from closed solids.
- 3D DXF: AutoCAD R12 polyface meshes, one entity per building/tree/bridge, terrain in tiles under AutoCAD's 32,767-vertex limit. Layers
TERRAIN,BUILDINGS,ROADS,PATHS,RAIL,WATER,GREEN,WOODLAND,PAVED,BRIDGES,WALLS,TREES. - 2D DXF: AutoCAD R12 with named layers (
ROAD-SURFACE,BUILDINGS,ROOF,WATER,BRIDGE,CONTOUR-MAJOR, …). Contours are polylines at their elevation; footprints have thickness = building height; trees are INSERTs of aTREEblock scaled to crown radius, so you can redefine the symbol. True BNG by default. - SVG/PNG: a sheet at the chosen paper scale with scale bar, north arrow and attribution. The SVG layers are named groups (Illustrator/Inkscape).
- Laser stack DXF: one layer per level in mm at model scale, either laid out sheet by sheet or stacked at height.
- Four numbered steps (Site, Data, Model, Analysis, Export) with collapsible panels; quick-start presets (Presentation, 3D print, CAD context, Terrain only); live triangle estimates; a per-layer legend in the 3D view.
- One-click packs (Export): Planning drawings (PDF pack + R2000 DXF + SVG), CAD context model (3DM + 3D DXF + 2D DXF +
buildings.csv), 3D print (STL + 3MF), Laser cut — each a single zip with a README. - Click a building in the 3D view for its data (OSM id, name, address, storeys, levels, height source, roof, listing) and to override its height (saved in your browser per site; press Update model). 3D shortcuts:
Tplan,Iisometric,Ssouth,Eeast,?help. - Copy link reopens the same site, custom shape, red line, section lines and settings (state lives in the URL hash; no server).
- Your own imagery (Data step): GeoTIFF (BNG, lat/lon or Web Mercator) or JPG/PNG + world file → underlay for drawings and/or texture for the 3D terrain; the licence note you enter is written into sheets and README files.
- Undo for site shapes, settings remembered per browser, friendlier error messages with suggested fixes.
- GLB (glTF 2.0 binary, Y-up, metres): a node per layer, a child node per building/tree/bridge, a material per layer. 3MF: one coloured object per layer in mm at your model scale (slicers keep names and colours). Rhino 3DM: Rhino layers with colours, one named mesh object per building/tree/…, building user text (OSM id, heights, levels, roof types, listing), origin and CRS in document strings; the 2.5 MB WASM loads only when you export. Verified by reading each file back (trimesh for GLB/3MF, rhino3dm for 3DM).
- Manifold booleans (manifold-3d WASM, loaded on demand): Union overlapping buildings merges OSM's overlapping building solids into clean ones (per-building objects are lost, so it's an option); fuse buildings into the terrain solid (STL/3MF) gives one printable body. Parts that aren't closed solids are skipped and reported.
- Facade detail (off by default, procedural, not surveyed): string courses at every floor line, a window per bay (default 1.2 m) per storey on every free wall, doors at OSM/inferred entrances; walls shared with a neighbour get nothing; a box budget keeps it light. Storeys come from
building:levelsor height / 3.2 m. LayersFACADE-BANDS,FACADE-WINDOWS,FACADE-DOORS. There is no open elevation imagery, so this adds data-driven detail, not photographic detail; Historic England listing text is not parsed.
- Drawing types: Site plan, Site location plan (defaults to 1:1250, red-line boundary you draw on the map, else the site outline), Roof plan (footprints, ridges/hips, pitch angles, heights), and sections / elevations from a cut line drawn on the map (several allowed, A–A, B–B).
- Sheet engine: smallest A-series paper that fits at the scale (portrait or landscape; falls back to a scale that fits), map frame with BNG grid ticks, north arrow, scale bar, a key generated from the layers drawn, notes, revision table and title block (project, drawing no., scale, date, drawn by, status). Output SVG, PNG and a multi-page vector PDF pack (location plan, site plan, roof plan, every section; text stays selectable).
- Section / elevation profile: looks to the left of the line A→B. Ground line (DTM) with soil poché, buildings the line cuts as solid poché using their fitted roof shapes, buildings beyond (to a depth you set) in lighter elevation, trees, water level, 5 m level grid and eaves/ridge levels, with a stated vertical exaggeration. Elevation mode cuts nothing and draws everything on the viewing side. Sections export as SVG, in the PDF pack, and as DXF (chainage along X, level × exaggeration along Y).
- DXF R2000 (default): entity handles,
LWPOLYLINE, solidHATCHfills, ISO 128 lineweights (0.13–0.70 mm) and true colour per layer, aTREEblock with INSERTs at crown radius, and XDATA (UKDTM: id, ground/top levels, height source, levels, roof types, listing) on every building. Read back with ezdxf's strict reader and audited with 0 errors. R12 remains available for the widest compatibility. - Layer standards: Simple names, or NCS/AIA style (
C-ROAD-KERB,A-SITE-BLDG,C-TOPO-MAJR,A-ROOF-RIDG, …), plus an optional prefix (project code). Real BS EN ISO 13567 layer codes are firm- and project-specific, so use the prefix or rename on import rather than expect a universal mapping. - Spot levels (junction levels,
C-TOPO-SPOT), roof pitches, listed buildings, entrances, markings and the red line are separate layers. - Shadow study sheet (Analysis → Shadow): 21 Mar / Jun / Dec at 09:00, 12:00, 15:00 on one sheet, saved as PDF.
- Not done: DXF paper-space layouts/viewports and a CAD-native title block (the title block is in SVG/PDF); axonometric line drawings; hatch patterns (solid fills only).
All analyses run client-side in a Web Worker (cancellable) and render over a hillshade/buildings/roads base; each can be saved as an image and as data (ESRI ASCII grid or CSV). They are screening tools, labelled as such in the app.
| Analysis | Method | Data | Limits |
|---|---|---|---|
| Shadow & sun hours | NOAA solar position (tested against published day lengths and noon altitudes); heightfield ray-march on the 1 m DSM; sun-hours = shadows every 15–60 min from sunrise to sunset; BRE 209 "≥ 2 h on 21 March" share; planning-date buttons; UK clock time with BST | LIDAR DSM | trees are opaque; 1 m surface |
| Wind comfort | Pedestrian-level speed ratio: open-terrain log law reduced by upwind frontal-area index, wakes ≈ 8 obstacle heights, channelling along open streets between tall buildings; Lawson-style classes. "Use local climate" sets direction/speed from the last year of Open-Meteo hourly wind (prevailing direction, 95th percentile) | DSM, DTM, Open-Meteo | empirical, not CFD |
| Flooding | Bathtub fill from water on site (or the lowest point) to connected ground below a chosen level, EA defences as walls of a chosen height, per-building depth; or rain ponding (priority-flood depressions). Overlays EA Flood Zones 2/3 and flood defences (live) | DTM, EA | indicative extent only |
| Crime | data.police.uk street-level crime for the site outline, 3–12 months, category filter, kernel-density heatmap, top categories/streets | Police API | anonymised locations, counts not rates |
| Entrances & activity | OSM entrance nodes snapped to walls, doors inferred facing the nearest street/path; gravity-weighted shortest-path flows from entrances (weighted by floor area) to shops, cafés, attractions, stops and commercial buildings, with sliders for homes / retail / transit |
OSM | a model of likely activity, not counted footfall |
- Custom site shapes: rectangle, hexagon, circle, or a free polygon (click corners; double-click/Enter to finish; Backspace removes a corner; Esc cancels; Undo restores the previous area). Data is fetched for the bounding box and clipped exactly to the outline; the terrain is cut to the outline (edge, skirt and base follow it), drawings and laser sheets are clipped to it. The 4 km² limit applies to the polygon's area.
- Listed buildings: Historic England's National Heritage List (points matched to the containing/nearest footprint, highest grade wins) →
LISTEDdrawing layer,listed_*GeoJSON properties,buildings.csv. buildings.csv(in the OBJ, STL and data zips): id, object name, name, address, levels, material, heights, height source, roof parts and types, eaves/ridge, listing.- Road markings (
ROAD-MARKINGSlayer,ROAD-MARK*drawing layers) are generated from OSM tags: centre and lane lines (2 m dash / 4 m gap), zebra crossings, signal crossings, stop and give-way lines, cycle lanes, double yellows. There is no open imagery to detect markings from, so only what OSM tags support is drawn. - Entrances (OSM
entrancenodes) appear on anENTRANCEdrawing layer. - Fetchers for EA flood zones/defences, police crime and Open-Meteo wind (used by the analysis panel) live in
src/data/with an IndexedDB response cache (data/cache.ts).
| Control | Effect |
|---|---|
| Terrain resolution / triangle budget | Grid step in metres, or the finest step within 0.1–4 M triangles. Coarser terrain is box-averaged. |
| Roads, water & parks detail | Cell size (1–8 m) used to follow the terrain. |
| LIDAR-roof detail | Downsamples the DSM for LOD2-raw roofs. |
| Closed solids | Uncheck for open surfaces: roughly half the triangles for roads, water and parks. |
The 3D view lists triangles per layer and warns above 4 M.
- LOD2.2 (clean roof faces, dormers, chimneys) is not available in the browser. It needs the EA LIDAR point clouds (multi-GB LAZ downloads, not served by the WCS) and a C++ reconstructor such as roofer. A desktop app is the way to get it.
- Georgian terraces step down hillsides, so most central-Bath footprints do not fit a single gable/hip and stay as blocks at LOD1.5; use LOD2-raw for their real roofline.
npm install
npm run dev # http://localhost:5173
npm test # unit tests (geometry, CRS, exporters, watertightness)
npm run build # static site in dist/Deploys to GitHub Pages from main via .github/workflows/deploy.yml. In the repo, set Settings → Pages → Source to GitHub Actions.
- Contains Environment Agency LIDAR data © Environment Agency copyright and/or database right, Open Government Licence v3.
- Map data © OpenStreetMap contributors, ODbL.
- OSTN15 © Ordnance Survey.
Exports include a README.txt with these notices.