| title | Terrology |
|---|---|
| emoji | 🗺️ |
| colorFrom | green |
| colorTo | blue |
| sdk | docker |
| app_port | 8000 |
| pinned | false |
Generate 3D-printable terrain and building models from OpenStreetMap and elevation data.
Urban — Canary Wharf, London |
Mountain — Snowdon with 50 m contours |
Two-point — Edinburgh Castle to Arthur's Seat |
Route — Beacon Fell Country Park GPX |
Coastal — St Ives, Cornwall |
|
Install uv, then:
uv tool install git+https://github.com/twigley/terrology
terrology "Snowdon" --radius 500Or clone and run directly:
git clone https://github.com/twigley/terrology
cd terrology
terrology "Snowdon" --radius 500The default elevation source (glo30) requires no API key. The srtm and aw3d30 sources require a free OpenTopography API key:
export OPENTOPOGRAPHY_API_KEY=your_keyOr save it once:
terrology --save-api-key <your_key>terrology <location> [options]Single location — square map centred on a point:
terrology "Canary Wharf, London" --radius 500
terrology 51.5074,-0.1278 --radius 600 --scale 4000Two locations — rectangular map spanning both points (each near an edge):
terrology "51.5074,-0.1278" --to "51.5155,-0.0753"
terrology "Edinburgh Castle" --to "Arthur's Seat, Edinburgh" --buffer 0.08Area (GeoJSON) — map clipped to a polygon boundary; everything outside is void:
terrology --area central_park.geojson
terrology --area manhattan.geojson --dem srtmDraw or export a polygon from geojson.io, QGIS, or any GIS tool. The first polygon in the file is used. No location argument is needed — the polygon provides the extent.
Route (GPX) — terrain-only map with the GPX track painted as a coloured line:
terrology --route my_ride.gpx
terrology --route trail.gpx --route-width 2.0 --terrain-exag 3A browser-based UI is included. It provides a map for drawing or searching a location, all the key options, a 3D preview of the result, and a ZIP download.
Try it live: https://huggingface.co/spaces/twigley/terrology
Run locally (installed):
uv tool install "git+https://github.com/twigley/terrology[web]"
terrology-webRun locally (cloned repo):
uv run uvicorn web.app:app --host 0.0.0.0 --port 8000Then open http://localhost:8000.
Run with Docker:
docker build -t terrology .
docker run -p 8000:8000 -e OPENTOPOGRAPHY_API_KEY=your_key terrologyEach job runs in its own process so memory is fully released when it finishes — the server returns to ~50 MB idle between jobs. The minimum spec for a public instance is 1 GB RAM.
| Platform | RAM | Sleep policy | Credit card | Notes |
|---|---|---|---|---|
| Oracle Cloud Always Free | 24 GB (ARM VM) | Never | Required (verification only, never charged) | Best free option; full VM with Docker. ARM instances are often at capacity in popular regions — may take retries to provision |
| Hugging Face Spaces | 16 GB | After 48 h inactivity | Not required | Run as a Docker Space. Terrology's poll-based job API avoids the platform's HTTP timeout limits |
Render, Koyeb, and Northflank free tiers top out at 256–512 MB RAM — not enough for a map generation job. Fly.io removed its free tier for new users in late 2024.
| Platform | Plan | RAM | Cost |
|---|---|---|---|
| Hetzner Cloud | CX22 | 4 GB | ~€4/mo |
| Railway | Hobby | up to 8 GB | ~$5/mo |
| Fly.io | shared-cpu-1x | 1 GB | ~$5/mo |
| Self-hosted VPS | — | 1 GB+ | varies |
Fly.io quickstart:
fly launch # detects the Dockerfile, creates fly.toml
fly secrets set OPENTOPOGRAPHY_API_KEY=your_key
fly deployEnvironment variables:
| Variable | Default | Description |
|---|---|---|
OPENTOPOGRAPHY_API_KEY |
— | Required for srtm and aw3d30 DEM sources |
TERROLOGY_NO_RATE_LIMIT |
unset | Set to 1 to disable the 5 jobs/hour/IP rate limit |
MAX_CONCURRENT_JOBS |
1 |
Max simultaneous jobs — increase on larger instances (allow ~600 MB RAM per job) |
TERROLOGY_JOB_DIR |
/tmp/terrology |
Directory for job output files |
| File | Description |
|---|---|
terrain.stl |
Full terrain solid — use for mono-colour printing |
buildings.stl |
Building extrusions only |
water.stl |
Water surface patch (lakes, rivers, sea) |
parks.stl |
Parks/woodland surface patch |
roads.stl |
Roads/paths surface patch |
model.obj + model.mtl |
Combined coloured model for multi-colour slicers |
model.3mf |
Bambu Studio 3MF with per-face colour metadata |
Import model.obj into Bambu Studio — it reads the MTL colours and lets you assign each material to a filament in the Filament panel.
The per-colour STLs (water.stl, parks.stl, roads.stl) are surface patches of the top layer only. Import them alongside terrain.stl into any slicer that handles multi-material via separate objects. Only files for colours present in the map are written.
| Material | Used for |
|---|---|
terrain |
Terrain surface and buildings |
water |
Lakes, rivers, sea |
parks |
Parks, woodland, grassland |
roads |
Roads, paths |
route |
GPX route line (route mode only) |
Use --colors to limit the number of materials (e.g. --colors 2 for a two-colour printer).
| Flag | Default | Description |
|---|---|---|
location |
— | Place name or "lat,lon" — omit when using --area or --route |
--to |
— | Second location for a two-point map |
--area |
— | GeoJSON file whose first polygon defines the map boundary |
--route |
— | GPX file (route mode) |
--radius |
500 |
Radius in metres (single-point mode) |
--buffer |
0.05 |
Edge buffer as a fraction of span (two-point / area / route mode) |
| Flag | Default | Description |
|---|---|---|
--size |
190 |
Longest model dimension in mm |
--scale |
auto | Scale denominator — overrides --size (e.g. 3000 → 1:3000) |
--terrain-exag |
2.0 |
Vertical exaggeration of terrain height |
| Flag | Default | Description |
|---|---|---|
--grid-size |
200 |
Terrain base mesh resolution N×N |
--color-grid-size |
800 |
Colour surface mesh resolution N×N — higher gives finer roads/paths |
--color-depth |
1.5 |
Depth (mm) colour features project into terrain — limits filament changes to surface layers |
--nozzle |
0.4 |
Nozzle diameter in mm. Both grid sizes are capped at model_size ÷ (2 × nozzle) so no cell is finer than the minimum printable feature. |
--dem |
glo30 |
Elevation dataset — see table below |
| Value | Resolution | Coverage | Notes |
|---|---|---|---|
glo30 (default) |
30 m | Global | Copernicus GLO-30 via public S3 — no API key needed |
srtm |
30 m | Global (±60°) | SRTM GL1 via OpenTopography — smoother in dense urban areas |
aw3d30 |
30 m | Global | ALOS AW3D30 via OpenTopography — often sharper in mountains; better bridge deck elevation |
All three are Digital Surface Models (DSM) — they measure the top of the surface including buildings and trees, which adds height to dense urban areas. srtm tends to be slightly smoother than glo30 in cities. Both srtm and aw3d30 require a free OpenTopography API key.
| Flag | Default | Description |
|---|---|---|
--colors |
4 |
Number of materials — priority order: terrain+buildings, water, parks, roads. Route mode always uses 2 (terrain + route). |
--route-width |
1.5 |
Route line width on the printed model in mm |
--contour-interval |
— | Draw elevation contour lines every N real-world metres (e.g. --contour-interval 50). Uses a contrasting colour from the existing palette — no extra filament needed. |
| Flag | Default | Description |
|---|---|---|
--output |
./output |
Output directory |
--save-api-key |
— | Save an OpenTopography API key to ~/.config/terrology/config and exit |
--no-terrain |
— | Skip terrain — buildings and features only |
--no-buildings |
— | Skip building extrusion — terrain and features only |
--no-cache |
— | Ignore cached downloads |
--smooth-boundary |
0 |
Smooth the --area polygon outline with N iterations of Chaikin corner-cutting (e.g. 3–5). Rounds sharp corners between GeoJSON vertices. |
API key — set once as an environment variable so you don't have to pass it every run:
export OPENTOPOGRAPHY_API_KEY=your_keyCaching — OSM and elevation data are cached in ~/.cache/3dmap/. Re-runs with the same area are fast. Use --no-cache to force a fresh download. If features look unexpectedly missing, try --no-cache — a failed fetch is cached as empty.
Area / polygon maps — draw your boundary at geojson.io and save as a .geojson file. Useful for irregular shapes (a river valley, a city district, a national park) where a rectangular bbox would include unwanted terrain. Everything outside the polygon is removed entirely.
terrology --area my_area.geojson --smooth-boundary 4Smooth polygon outlines — if your GeoJSON polygon has few vertices, the map outline will have angular corners. Use --smooth-boundary 3 to 5 to round them. More iterations pull the outline inward, so don't exceed ~6.
Multi-colour printing — import model.obj into Bambu Studio and assign each material name to a filament in the Filament panel. Buildings and terrain share the same material, so a 4-colour printer covers terrain, water, parks, and roads.
Nozzle & triangle count — the --nozzle default (0.4 mm) automatically caps grid resolutions so you never generate triangles the printer can't resolve. With a 0.6 mm nozzle the cap is tighter and files are smaller with no visible quality loss.
Vertical exaggeration — flat areas benefit from a higher --terrain-exag (try 3–5). Mountainous areas may look better at 1.5.
Route maps — the GPX bounding box is used automatically. Adjust --buffer to add more space around the track edges (default 5%). The route line width (--route-width) is in printed mm, not real-world metres, so it stays the same visual size regardless of map scale.
Coastal maps — coastlines and sea are detected automatically from OSM coastline data. Use --dem srtm for dense coastal cities to reduce building spikes in the terrain.
Contour lines — --contour-interval paints elevation contours using a colour already in your palette — no extra filament required. Choose an interval that matches the relief: 10–25 m for gentle hills, 50–100 m for mountains. Contours are invisible in 1–2 colour mode.
terrology "Zermatt, Switzerland" --radius 1500 --contour-interval 50
terrology "Peak District" --radius 3000 --contour-interval 25 --terrain-exag 3Bridges — road and railway segments tagged bridge=yes in OSM sit at the correct elevated position rather than being depressed into the hillside.
Slicers without OBJ support — import terrain.stl, water.stl, parks.stl, and roads.stl together and assign each a filament. Only files for features present in the map are written.
Map data © OpenStreetMap contributors, licensed under the Open Database Licence.
Elevation data provided by OpenTopography. Datasets used:
- GLO-30 (glo30) — © DLR/ESA, distributed under CC BY 4.0
- SRTM GL1 (srtm) — NASA/USGS, public domain
- AW3D30 (aw3d30) — © JAXA, distributed under CC BY 4.0