Convert Swiss cadastral parcels into comprehensive 3D IFC (Industry Foundation Classes) models with terrain, site boundaries, and buildings. Fetches data from Swiss geo.admin.ch APIs and generates georeferenced IFC files for BIM applications.
- Cadastral boundaries - Fetch Swiss parcels via EGRID from geo.admin.ch
- 3D terrain generation - Circular terrain mesh with precise site cutout
- Building footprints & 3D models - Load buildings from Swiss APIs (CityGML, Vector 25k)
- Road network data - Load Swiss road and transportation network from swissTLM3D
- Railway & train tracks - Load railway data from OpenStreetMap (rail, tram, metro, funicular)
- Vegetation & forest data - Load trees, forests, and vegetation from swissTLM3D
- Complete BIM models - Site + terrain + buildings + roads + vegetation in georeferenced IFC
- Satellite imagery textures - Optional SWISSIMAGE orthophoto textures for terrain and buildings
- glTF/GLB export - Web-ready 3D models with textures for Three.js visualization
- IFC4 compliance - Proper georeferencing (EPSG:2056), property sets, and schema compliance
- FastAPI service - RESTful API for terrain and building generation
- Multiple workflows - Choose terrain-only, site-only, or complete models
# Clone and navigate to the project
cd site-boundaries-geom
# Create virtual environment
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txtUsing the unified CLI (recommended):
python src/cli.py \
--address "Bundesplatz 3, 3003 Bern" \
--all \
--include-satellite-overlay \
--output complete_site.ifcThis generates:
- Georeferenced IFC file with terrain, site boundary, buildings, roads, water, and vegetation
- GLB file with satellite imagery textures for web visualization
- Satellite imagery automatically fetched and mapped
Using legacy scripts:
python -m src.terrain_with_buildings \
--egrid CH999979659148 \
--radius 500 \
--include-buildings \
--output complete_site.ifcThis generates a georeferenced IFC file with:
- Circular terrain mesh (500m radius)
- Site boundary solid with cadastral metadata
- 3D building models from Swiss CityGML data
Script: src/terrain_with_buildings.py
Generates the most comprehensive output: terrain mesh, site solid, and 3D buildings.
python -m src.terrain_with_buildings \
--egrid CH999979659148 \
--radius 500 \
--resolution 10 \
--include-buildings \
--building-buffer 10 \
--output complete.ifcKey Options:
--egrid- Swiss EGRID identifier (required)--radius- Terrain radius in meters (default: 500)--resolution- Grid resolution in meters (default: 10, lower = more detail)--include-buildings- Include 3D buildings from CityGML--building-buffer- Buffer around parcel for buildings (meters)--buildings-full-radius- Include all buildings in terrain radius (not just on site)--attach-to-solid- Attach terrain to smoothed site edges (smoother transitions)
Script: src/terrain_with_site.py
Creates terrain mesh with site boundary solid, without buildings.
python -m src.terrain_with_site \
--egrid CH999979659148 \
--radius 500 \
--resolution 10 \
--output terrain_site.ifcKey Options:
--egrid- Swiss EGRID identifier (required)--center-x,--center-y- Override terrain center (EPSG:2056)--radius- Terrain radius in meters (default: 500)--resolution- Grid resolution (default: 10m)--densify- Site boundary densification interval (default: 0.5m)--attach-to-solid- Attach terrain to site edges
Script: src/site_solid.py
Generates only the site boundary solid with elevation, no terrain or buildings.
python -m src.site_solid \
--egrid CH999979659148 \
--output site.ifcKey Options:
--egrid- Swiss EGRID identifier--cadastral- Path to local cadastral file (GeoPackage/Shapefile)--dem- Path to local DEM file (GeoTIFF)--densify- Boundary densification interval (default: 0.5m)
The tool supports multiple Swiss building data sources:
-
CityGML (3D) - Complete 3D building geometry with walls, roofs, and ground surfaces
- Best for: Complete 3D models with accurate heights
- Source: Swiss Buildings 3D (STAC API)
- Format: LOD2 solids with detailed geometry
-
Vector 25k (2D) - Building footprints from Swiss topographic maps
- Best for: Fast footprint queries
- Source: swisstopo Vector 25 (REST API)
- Format: 2D polygons
Load buildings programmatically:
from src.building_loader import SwissBuildingLoader, get_buildings_around_egrid
# Method 1: Load buildings by EGRID
buildings, stats = get_buildings_around_egrid(
egrid="CH999979659148",
buffer_m=10 # Include buildings within 10m of parcel
)
print(f"Found {stats['count']} buildings")
print(f"Total footprint area: {stats['total_footprint_area_m2']:.1f} m²")
# Method 2: Load buildings by bounding box
from src.building_loader import get_buildings_in_bbox
bbox = (2682500, 1247500, 2683000, 1248000) # EPSG:2056
buildings, stats = get_buildings_in_bbox(bbox, method="rest")
# Method 3: Load 3D buildings from CityGML
loader = SwissBuildingLoader()
buildings_3d = loader.get_buildings_3d(bbox, max_tiles=1)
for building in buildings_3d:
print(f"Building {building.id}:")
print(f" Height: {building.height:.1f}m")
print(f" Footprint area: {building.geometry.area:.1f}m²")The loader provides comprehensive statistics:
stats = loader.get_building_statistics(buildings)
# Returns:
# {
# 'count': 42,
# 'total_footprint_area_m2': 12543.2,
# 'avg_footprint_area_m2': 298.6,
# 'avg_height_m': 8.5,
# 'max_height_m': 24.3,
# 'min_height_m': 3.2,
# 'buildings_with_height': 38
# }The tool supports Swiss road and transportation network data:
-
swissTLM3D Roads - Complete road and path network
- Source: Swiss Topographic Landscape Model
- Format: Line geometry with classification and attributes
- Coverage: Switzerland and Liechtenstein
-
Vector 25k Roads - Topographic road representation
- Source: swisstopo Vector 25
- Format: Simplified road network
-
Main Roads Network - Highway and main road network
- Source: ASTRA (Federal Roads Office)
- Format: Major road corridors
Load roads programmatically:
from src.loaders.road import SwissRoadLoader, get_roads_around_egrid
# Method 1: Load roads by EGRID
roads, stats = get_roads_around_egrid(
egrid="CH999979659148",
buffer_m=10 # Include roads within 10m of parcel
)
print(f"Found {stats['count']} roads")
print(f"Total length: {stats['total_length_m']:.1f} m")
print(f"Road classes: {stats['road_classes']}")
# Method 2: Load roads by bounding box
from src.loaders.road import get_roads_in_bbox
bbox = (2682500, 1247500, 2683000, 1248000) # EPSG:2056
roads, stats = get_roads_in_bbox(bbox)
# Method 3: Load roads around a point
loader = SwissRoadLoader()
roads = loader.get_roads_around_point(x=2683000, y=1248000, radius=500)
for road in roads:
print(f"Road {road.id}:")
print(f" Class: {road.road_class}")
print(f" Name: {road.name or 'Unnamed'}")
print(f" Length: {road.geometry.length:.1f}m")Swiss roads are classified into categories:
- Autobahn - Highway/Motorway
- Autostrasse - Expressway
- Hauptstrasse - Main road
- Nebenstrasse - Secondary road
- Verbindungsstrasse - Connecting road
- Gemeindestrasse - Local road
- Privatstrasse - Private road
- Weg - Path/Track
- Fussweg - Footpath
The tool supports optional satellite imagery textures from Swiss SWISSIMAGE orthophoto data (geo.admin.ch). When enabled, textures are applied to terrain and optionally to buildings for realistic visualization.
- Terrain textures - Satellite imagery automatically mapped to terrain mesh
- Building textures - Optional satellite imagery textures on building roofs and walls
- glTF/GLB export - Web-ready 3D models with embedded textures for Three.js
- IFC textures - Texture mapping in IFC files for BIM viewers
- Historical imagery - Support for historical imagery by year
- Configurable resolution - Control imagery resolution (0.25m to 2.0m per pixel)
Basic usage with satellite imagery:
python src/cli.py \
--address "Paradeplatz, 8001 Zürich" \
--include-satellite-overlay \
--include-buildings \
--output site_with_textures.ifcThis generates:
- IFC file with terrain textures
- GLB file (glTF) with textures for web visualization
- Satellite imagery automatically fetched and mapped
With building textures (default):
python src/cli.py \
--address "Paradeplatz, 8001 Zürich" \
--include-satellite-overlay \
--include-buildings \
--apply-texture-to-buildings \
--output site.ifcTerrain textures only (buildings use default color):
python src/cli.py \
--address "Paradeplatz, 8001 Zürich" \
--include-satellite-overlay \
--include-buildings \
--no-texture-buildings \
--output site.ifcHigh-resolution imagery:
python src/cli.py \
--address "Paradeplatz, 8001 Zürich" \
--include-satellite-overlay \
--imagery-resolution 0.25 \
--output detailed.ifcHistorical imagery:
python src/cli.py \
--address "Paradeplatz, 8001 Zürich" \
--include-satellite-overlay \
--imagery-year 2020 \
--output historical.ifc--include-satellite-overlay- Enable satellite imagery textures--apply-texture-to-buildings- Apply textures to buildings (default: enabled when imagery enabled)--no-texture-buildings- Use default color for buildings instead of satellite textures--imagery-resolution- Resolution in meters per pixel (default: 0.5m)--imagery-year- Year for historical imagery (e.g., "2020"), default: current--export-gltf- Export glTF/GLB file (default: auto-enabled when imagery enabled)--no-export-gltf- Disable glTF export even when imagery is enabled--embed-imagery- Embed imagery in IFC (default: True)--no-embed-imagery- Use external URL references for imagery
When satellite imagery is enabled:
- Terrain always gets satellite textures
- Buildings can optionally get satellite textures or use default beige color
- Buildings are always included in the 3D model regardless of texture setting
- The
--no-texture-buildingsflag only controls coloring, not geometry inclusion
When satellite imagery is enabled, the tool automatically exports a GLB file alongside the IFC:
- Same name as IFC file but with
.glbextension - Includes terrain, roads, water, railways, and buildings
- Textures embedded in GLB for easy web deployment
- Compatible with Three.js and other web 3D viewers
Example output:
site.ifc # IFC file with textures
site.glb # glTF/GLB file with textures
site_texture.jpg # Satellite imagery texture file
from src.site_model import run_combined_terrain_workflow
# With building textures (default)
result = run_combined_terrain_workflow(
address="Paradeplatz, 8001 Zürich",
radius=500,
include_satellite_overlay=True,
include_buildings=True,
apply_texture_to_buildings=True, # Buildings get satellite textures
imagery_resolution=0.5,
export_gltf=True,
output_path="site.ifc"
)
# Without building textures
result = run_combined_terrain_workflow(
address="Paradeplatz, 8001 Zürich",
radius=500,
include_satellite_overlay=True,
include_buildings=True,
apply_texture_to_buildings=False, # Buildings use default color
imagery_resolution=0.5,
export_gltf=True,
output_path="site.ifc"
)- UV coordinates - Automatically calculated based on imagery bounding box
- Coordinate system - EPSG:2056 (Swiss LV95) for both geometry and imagery
- Terrain mapping - Full terrain mesh gets UV coordinates mapped to imagery bounds
- Building mapping - Each building face gets UV coordinates based on its world position
- Image format - JPEG format from geo.admin.ch WMS service
- Imagery download - ~1-5 seconds depending on area size and resolution
- Texture processing - Minimal overhead for UV coordinate calculation
- GLB file size - Increases with imagery resolution (0.25m = larger files)
- Recommendation - Use 0.5m resolution for good quality/size balance
The tool supports railway and train track data from OpenStreetMap:
- OpenStreetMap Railways - Complete railway network
- Best for: Train tracks, tram lines, metro, funicular
- Source: OpenStreetMap Overpass API
- Format: Line geometry with classification and attributes
- Coverage: Worldwide (including Switzerland)
Swiss railways include various track types:
- rail - Standard railway tracks (SBB, regional trains)
- tram - Tram/streetcar lines
- light_rail - Light rail systems
- subway - Metro/underground lines
- narrow_gauge - Narrow gauge railways (mountain railways)
- funicular - Funicular railways
Include railways in site model:
python src/cli.py \
--address "Kasernenstrasse 97, 8004 Zürich" \
--include-railways \
--include-buildings \
--output station_area.ifcExample output: Railway tracks at Zürich Hauptbahnhof visualized in BlenderBIM with surrounding buildings
All features including railways:
python src/cli.py \
--address "Bern Hauptbahnhof" \
--all \
--output complete.ifcNote: --all includes railways by default.
Load railways programmatically:
from src.loaders.railway import SwissRailwayLoader, RailwayFeature
# Load railways in a bounding box
loader = SwissRailwayLoader()
bbox = (2682500, 1247500, 2683000, 1248000) # EPSG:2056
railways = loader.get_railways_in_bbox(bbox)
for railway in railways:
print(f"Railway {railway.id}:")
print(f" Type: {railway.railway_type}")
print(f" Name: {railway.name or 'Unnamed'}")
print(f" Electrified: {railway.electrified}")
print(f" Gauge: {railway.gauge}mm" if railway.gauge else "")
print(f" Tracks: {railway.tracks}")Each railway feature includes:
id- Unique identifiergeometry- LineString geometry (EPSG:2056)railway_type- Type classification (rail, tram, etc.)name- Railway line name (if available)electrified- Electrification type (contact_line, rail, yes, no)gauge- Track gauge in mm (e.g., 1435 standard, 1000 meter gauge)tracks- Number of tracksservice- Service type (main, branch, spur, yard, siding)usage- Usage type (main, branch, industrial, tourism)
- Railway data is fetched from OpenStreetMap Overpass API
- Rate limiting is applied to prevent API overload
- Data is converted from WGS84 to EPSG:2056 automatically
- Railways are clipped to the specified radius
The tool supports Swiss vegetation and forest data:
-
swissTLM3D Forest - Forest polygons with classification
- Best for: Forest areas, tree cover analysis
- Source: Swiss Topographic Landscape Model
- Format: Polygon geometry with type classification
- Types: Forest, Sparse forest, Bush forest
-
Vegetation 3D - 3D vegetation objects
- Best for: Individual trees and vegetation features
- Source: swisstopo 3D data
- Format: Point/polygon with height data
-
Vegetation Health Index - Satellite-based vegetation monitoring
- Best for: Current vegetation health status
- Source: swissEO satellite observations
- Format: Raster with vegetation indices
Load vegetation programmatically:
from src.loaders.forest import SwissForestLoader, get_trees_around_egrid
# Method 1: Load trees/forest by EGRID
trees, stats = get_trees_around_egrid(
egrid="CH999979659148",
buffer_m=10 # Include trees within 10m of parcel
)
print(f"Found {stats['count']} tree/forest features")
print(f"Total canopy area: {stats['total_canopy_area_m2']:.1f} m²")
print(f"Average height: {stats['avg_height_m']:.1f} m")
# Method 2: Load trees by bounding box
from src.loaders.forest import get_trees_in_bbox
bbox = (2682500, 1247500, 2683000, 1248000) # EPSG:2056
trees, stats = get_trees_in_bbox(bbox)
# Method 3: Load trees around a point
loader = SwissForestLoader()
trees = loader.get_trees_around_point(x=2683000, y=1248000, radius=500)
for tree in trees:
print(f"Tree {tree.id}:")
print(f" Type: {tree.tree_type}")
print(f" Height: {tree.height:.1f}m" if tree.height else " Height: Unknown")
print(f" Canopy area: {tree.canopy_area:.1f}m²" if tree.canopy_area else "")Swiss vegetation is classified into types:
- Forest (Wald) - Dense forest areas
- Sparse forest (Wald_offen) - Open forest with lower density
- Bush forest (Buschwald) - Shrubland and bushes
- Individual tree (Einzelbaum) - Single trees
- Row of trees (Baumreihe) - Linear tree arrangements
- Hedge (Hecke) - Hedgerows
- Scrubland (Gebueschwald) - Mixed scrub vegetation
Run the terrain and building generation service as a REST API:
uvicorn src.rest_api:app --host 0.0.0.0 --port 8000GET /health- Service health checkPOST /generate- Generate and stream IFC file immediatelyPOST /jobs- Start background generation jobGET /jobs/{job_id}- Check job statusGET /jobs/{job_id}/download- Download completed IFC file
Immediate generation:
curl -X POST http://localhost:8000/generate \
-H "Content-Type: application/json" \
-o site.ifc \
-d '{
"egrid": "CH999979659148",
"radius": 500,
"resolution": 10,
"include_buildings": true
}'Background job:
# Start job
JOB_ID=$(curl -s -X POST http://localhost:8000/jobs \
-H "Content-Type: application/json" \
-d '{
"egrid": "CH999979659148",
"radius": 500,
"include_buildings": true
}' | jq -r .job_id)
# Check status
curl http://localhost:8000/jobs/$JOB_ID
# Download when ready
curl -o site.ifc http://localhost:8000/jobs/$JOB_ID/download-
Boundary Fetching
- Fetches cadastral polygon via geo.admin.ch API using EGRID
- Extracts metadata (parcel number, canton, area, perimeter)
- Calculates site centroid
-
Terrain Generation
- Creates circular grid around site centroid
- Fetches elevation data from Swiss height API
- Generates Delaunay triangulation
- Creates precise cutout for site boundary
-
Site Solid Creation
- Samples elevations along site boundary
- Applies smoothing algorithm (best-fit plane + circular mean filter)
- Creates closed solid with triangulated surfaces
-
Building Integration (if enabled)
- Queries building APIs within site bounds or terrain radius
- Downloads CityGML tiles with complete 3D geometry
- Filters buildings to search area
- Converts to IFC building elements
-
IFC Generation
- Creates georeferenced IFC4 file (EPSG:2056)
- Adds terrain mesh as IfcGeographicElement
- Adds site solid with property sets
- Adds buildings as IfcBuilding with 3D geometry
- Maps all metadata to IFC schema
Multi-step smoothing process to reduce noise while preserving slope:
- Calculate best-fit plane using least squares
- Apply circular mean filter to elevations (window: 9)
- Compute residuals (smoothed - plane)
- Smooth residuals with circular mean filter
- Attenuate residuals to 20%
- Final elevation:
plane + 0.2 * smoothed_residuals
- Input/Output: EPSG:2056 (Swiss LV95 / CH1903+)
- Vertical Datum: LN02 (Swiss height system)
- Units: Meters (SI)
- Project Origin: Site centroid rounded to nearest 100m
Pset_LandRegistration:
LandID- Parcel numberLandTitleID- EGRID identifierIsPermanentID- EGRID permanence flag
Pset_SiteCommon:
Reference- Local identifierTotalArea- Site area in m²BuildableArea- Maximum buildable area in m²
Pset_BuildingCommon (for buildings):
Reference- Building identifierYearOfConstruction- Construction yearGrossPlannedArea- Building footprint area
Qto_SiteBaseQuantities:
GrossArea- Total site areaGrossPerimeter- Site perimeter
CPset_SwissCadastre (custom):
GeoportalURL- Canton geoportal linkCanton- Canton abbreviationParcelNumber- Parcel number
IfcProject
└── IfcSite
├── Representation: FootPrint (2D polyline)
├── Property Sets: Pset_LandRegistration, Pset_SiteCommon, etc.
├── IfcGeographicElement (Surrounding_Terrain)
│ ├── PredefinedType: TERRAIN
│ └── Representation: Body (ShellBasedSurfaceModel)
├── IfcGeographicElement (Site_Solid)
│ ├── PredefinedType: TERRAIN
│ └── Representation: Body (FacetedBrep)
└── IfcBuilding (for each building)
├── Representation: Body (FacetedBrep or Tessellation)
├── Representation: FootPrint (2D polygon)
└── Property Sets: Pset_BuildingCommon
- 500m radius @ 10m resolution: ~2000 points, ~3-4 minutes
- 500m radius @ 20m resolution: ~500 points, ~1 minute
- Recommendation: Use 15-25m resolution for faster processing
- Detail: Use 2-5m resolution for high-detail terrain
CityGML 3D (Recommended):
- Download: ~14MB per tile, 10-30 seconds
- Processing: Fast (Fiona/GDAL)
- Output: Complete LOD2 geometry with walls and roofs
Vector 25k (Fast):
- Download: Instant (REST API)
- Processing: Very fast
- Output: 2D footprints only
Tip: Use --buildings-on-site-only to load only buildings on the parcel for faster processing.
The building loader includes automatic rate limiting (5 requests/second) to prevent API abuse.
If you encounter elevation API rate limiting:
- Use larger
--resolutionvalues (20-30m) - Increase
--densifyinterval (2.0m or higher) - Reduce terrain
--radius - Use local DEM file with
--demflag (site_solid.py only)
If building download fails:
- Check network connectivity
- Verify EGRID exists in cadastral database
- Try without buildings: remove
--include-buildingsflag - Check logs for specific error messages
If you see geometry warnings:
- The tool attempts automatic fixes with
buffer(0) - Check source cadastral data for self-intersections
- Increase
--densifyvalue for smoother boundaries
Ensure Python 3.9 or higher:
python --version- Cadastral boundaries: geo.admin.ch REST API
- Elevation data: Swiss height API (LN02)
- Satellite imagery: SWISSIMAGE orthophoto (WMS service)
- Buildings (Vector 25k): swisstopo topographic maps
- Buildings (3D): Swiss Buildings 3D STAC API
- CityGML: LOD2 building models with complete geometry
- Roads (swissTLM3D): Swiss Topographic Landscape Model road network
- Roads (Vector 25k): Topographic road representation
- Roads (Main network): ASTRA main roads network
- Railways (OpenStreetMap): Railway tracks, tram lines, metro, funicular
- Vegetation (swissTLM3D): Forest polygons and vegetation areas
- Vegetation (3D): 3D vegetation objects and individual trees
- Vegetation Health: SwissEO satellite-based vegetation monitoring
- Cadastral:
https://api3.geo.admin.ch/rest/services/api/MapServer/identify - Height:
https://api3.geo.admin.ch/rest/services/height - Satellite Imagery:
https://wms.geo.admin.ch(WMS GetMap) - Buildings REST:
https://api3.geo.admin.ch/rest/services/api/MapServer/identify - Buildings STAC:
https://data.geo.admin.ch/api/stac/v1 - Roads:
https://api3.geo.admin.ch/rest/services/api/MapServer/identify - Railways:
https://overpass-api.de/api/interpreter(OpenStreetMap) - Vegetation:
https://api3.geo.admin.ch/rest/services/api/MapServer/identify
ch.swisstopo.swissimage- SWISSIMAGE orthophoto (WMS)ch.swisstopo.swissbuildings3d_3_0- 3D building models (STAC)ch.swisstopo.vec25-gebaeude- Building footprints (REST)ch.swisstopo.swisstlm3d-strassen- Complete road network (REST)ch.swisstopo.vec25-strassennetz- Vector 25k roads (REST)ch.astra.hauptstrassennetz- Main roads network (REST)ch.swisstopo.swisstlm3d-wald- Forest areas (REST)ch.swisstopo.vegetation.3d- 3D vegetation objects (3D Tiles)ch.swisstopo.swisseo_vhi_v100- Vegetation Health Index (Raster)- OpenStreetMap
railway=*- Railway tracks (via Overpass API)
See docs/DEPLOYMENT.md for containerized deployment instructions.
Run the test suite:
# Install test dependencies (included in requirements.txt)
pip install -r requirements.txt
# Run all tests
pytest
# Run with coverage
pytest --cov=src tests/
# Run specific test module
pytest tests/test_api.pyGenerated IFC files contain:
- Georeferenced site boundaries (EPSG:2056)
- 3D terrain geometry (surface mesh with cutout)
- 3D building models (complete LOD2 solids)
- Optional satellite imagery textures (SWISSIMAGE orthophoto)
- Comprehensive metadata (cadastral, building attributes)
- IFC4 compliance for major viewers (BlenderBIM, Solibri, etc.)
When satellite imagery is enabled, additional files are generated:
- GLB file (glTF) with textures for web visualization
- Texture image file (JPEG) with satellite imagery
- geo.admin.ch API
- IFC4 Schema
- Swiss LV95 Coordinate System
- Swiss Buildings 3D
- swissTLM3D Topographic Landscape Model
- swissTLM3D Roads and Tracks
- swissTLM3D Forest
- buildingSMART Property Sets
See LICENSE file for details.

