Our 3D Greenland terrain system. Clone the repo, follow the install instructions below, and start exploring — all terrain data (heightmaps and textures) is downloaded automatically as you fly around. No bulk downloads or pre-processing needed.
The front end in /webserver folder is running vite. The entry point is main.js → main.terrain.js. The SPA uses a heatmap priority approach to determine what tiles/textures to load or drop, and uses Takram three-geospatial for atmosphere and cloud effects. There is a map view (no clouds) to help with navigation and tile debugging.
The backend in /flaskserver manages terrain heightmaps and textures in a SQLite db (terrain.db). It always assumes the user starts from scratch and will rebuild all missing data as needed. FIRST VISIT TO ANY LOCATION WILL BE SLOW — heightmaps and textures are fetched on demand and cached in the DB. Subsequent visits are fast. All tile demand is driven from the frontend heatmap.
| Data | Source | Resolution | Notes |
|---|---|---|---|
| Heightmaps (primary) | ArcticDEM v4.1 mosaic via S3 | 10m | Cloud Optimized GeoTIFF, free, no auth |
| Heightmaps (fallback) | Copernicus GLO-30 via S3 | 30m | Used when ArcticDEM has no coverage |
| Textures | Dataforsyningen WMS | 0.2m–1.6m | SPOT 6/7 + aerial ortho, requires free API token |
| Labeling reference | Google satellite tiles | ~0.26 m/px (z18) | Classification ground truth only — measured, never shipped |
| System | EPSG | Used For |
|---|---|---|
| NSIDC Polar Stereographic North | 3413 | Internal tile grid, DEM, all spatial queries (meters) |
| WGS84 Lat/Lon | 4326 | API input/output, camera positions, user-facing coords |
| Greenland Transverse Mercator | 3184 | Dataforsyningen WMS requests only |
| WGS84 Ellipsoid + ECEF | — | Three.js 3D rendering, camera, lighting, ENU frames |
EPSG:3413 is the primary internal system. Everything else converts to/from it.
coords.pyhandlesto_stereo(lat, lon)(4326→3413) andto_wgs84(x, y)(3413→4326)- Dataforsyningen textures: 3413 → 3184 for the WMS request, then warped back to 3413 (Lanczos)
- Client-side: lat/lon →
Geodetic→ ECEF for Three.js scene placement
Real imagery down to depth 12, invented detail below it.
dataforsyningen (faithful, depth ≤ 12) → procedural synthesis (gorgeous + deterministic, depth ≥ 13)
- Bootstrap: On first run, the server seeds the tile grid as empty skeletons (no heightmaps, no textures). These define the quadtree structure up to the target depth.
- tex-worker fetches from Dataforsyningen WMS (SPOT 6/7 1.6m, EPSG:3184) on demand. If it fails (rate limit, timeout, no coverage), the tile stays uncached and an ancestor texture is cropped and served as a placeholder until the next request retries.
- Dataforsyningen WMS requires EPSG:3184 (not 3413). The fetch reprojects 3413→3184 for the request, then warps the result back to 3413 with Lanczos resampling.
- Dataforsyningen runs out of detail around depth 13 (SPOT is 1.6 m/px). Below that, accuracy vs reality stops mattering: depth 12 already tells us what goes where. A coarse class map at d12 scale (water / grey / dark slopes & shadows / green / white — see
flaskserver/classifier/storage.py) is the entire semantic contract. - Everything below depth 12 is procedural (work in progress): per-class texture and asset synthesis, seeded from absolute EPSG:3413 coordinates so every visit renders identical detail, color-anchored to the real d12 imagery so the transition doesn't pop. Judged on looks, not fidelity. Google imagery is a labeling/measurement reference only and never ships.
Retired approaches, kept in git history only: SUPIR/ComfyUI enhance (dataforsyningen_enhanced/upscaled sources — never worked right), bathymetry flattening, and learned recoloring from external reference imagery.
The eyeball harness is the tile inspector (pipeline.html?tile=<id>): a tile's progress through heightmap → southness → texture → procgen, with per-stage status and keyboard navigation across tiles and depths.
Classifier output lives in terrain.db's classifier_tiles table as a zlib-compressed, image-oriented uint8 label raster plus a class-schema name, dimensions, optional confidence raster, source/model identifier, and timestamp. A fresh database contains no classifier rows. In the 3D view, press C to toggle classifier presentation: missing tiles keep their satellite imagery but desaturate it, while available coarse_v1 tiles are painted as grey / green / dark / white / water classes. Grayscale elevation is used only while a satellite texture is still loading. Deeper terrain tiles inherit a nearest-neighbor crop from the closest classified ancestor.
- Tiles not loading? A hard browser reload (Cmd+Shift+R / Ctrl+Shift+R) may be needed to force the frontend to re-request tiles from the backend, especially after restarting the server or clearing the DB.
The camera starts facing north at Nuuk (64.18°N, 51.72°W). You can override this via URL params, e.g. ?lat=66.5&lon=-53.2. Press R to reset the view if shit gets crazy (e.g. the world suddenly turns into a blue ball).
Press M to toggle a 2D map view (no clouds/atmosphere) for navigation and tile debugging. Right-click on a tile to inspect its metadata or open it in the tile inspector.
Press H to swap the map canvas for the live tile-priority heatmap. Press H again to return to the regular map; both views retain the same heading, pan, and zoom. Regular map mode outlines the terrain meshes currently being rendered as a thin grey grid, with the tile under the pointer outlined in red.
In 3D mode, atmosphere sliders allow adjusting cloud density, coverage, and lighting parameters. (WIP — still working out some bugs.)
Press G to open Google Maps' satellite 3D view at the current camera position, heading, tilt, height above ground, and field of view. This is a debugging reference and opens in a new tab.
- Python 3.13+ with pip
- Node.js 18+
cd flaskserver
python -m venv venv
source venv/bin/activate
pip install -r requirements.txtCreate a .env file in flaskserver/ with your API token:
DATAFORSYNINGEN_TOKEN=<your-token>
To get a free token: create a user account (click "Log ind" → "Opret Profil"), confirm via email, then log in and go to your profile → "Administrer token til webservice og API'er" to generate a token.
The asset server manages vehicles, structures, and their placement via a SQLite database (assets.db). It must be running for vehicles and structures to load in the frontend. Still WIP — schema and endpoints may change.
cd assetserver
npm installThe frontend depends on a local clone of the Takram three-geospatial monorepo for atmosphere and cloud effects. Vite aliases @takram/* imports to this local source.
cd webserver
git clone https://github.com/takram-design-engineering/three-geospatial.git
cd three-geospatial && git checkout ab3d1cf5 && cd ..
npm install# Terminal 1 — asset server (assets.db)
./assetserver/runAssetServer
# Terminal 2 — terrain backend (terrain.db)
./flaskserver/runFlaskServer
# Terminal 3 — frontend (snazzy 3d ux)
./webserver/runViteServerBrowser asset calls go directly to http://127.0.0.1:8787 by default.
Override in the frontend URL with ?assetServer=http://<host>:<port>.
Scripts log to their respective directories (runAssetServer.log, runFlaskServer.log, runViteServer.log).
This project uses the following external data sources:
- Satellite orthophotos: Indeholder data fra Klimadatastyrelsen (formerly Styrelsen for Dataforsyning og Infrastruktur). Datasets: "Grønland Satellitfoto" (SPOT 6/7 1.6m regional orthophoto, 0.2m aerial orthophoto). Fetched on demand via Dataforsyningen WMS. Data is free for both commercial and non-commercial use with attribution. Terms of use.
- Heightmaps (primary): ArcticDEM v4.1 10m mosaic, provided by the Polar Geospatial Center under NSF-OPP awards 1043681, 1559691, and 1542736. CC-BY-4.0, free for commercial use with attribution. Fetched on demand via S3. Acknowledgement policy.
- Heightmaps (fallback): Copernicus GLO-30 DEM, provided by the European Space Agency. Free for commercial use with attribution. Fetched on demand via S3.
- Atmosphere & clouds: three-geospatial by Takram.
This project exists to promote the development of Greenland. Its incredibly harsh and remote terrain is almost like another planet — perfect for autonomous machinery, robotics, drones, and infrastructure like tunnels. A high-fidelity 3D terrain navigator is a first step toward making that landscape explorable and plannable for all Greenland enthusiasts!
