Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge

Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit
Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem,
dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en)
sowie Projektdokumentation und Probe-Harness.
This commit is contained in:
2026-06-30 20:52:27 +02:00
commit ca859c4aa4
157 changed files with 37921 additions and 0 deletions
+566
View File
@@ -0,0 +1,566 @@
# Swisstopo-Geodaten & SIA-Flächenstandards im Browser-BIM
> Stand: 2026-06-29 · Recherche für das Standalone-Browser-BIM (React + TS + Three.js),
> Port von **DOSSIER** (Rhino-Plugin). Ziel: (A) Schweizer Geodaten (Höhenmodell,
> Orthofoto, 3D-Gebäude, Parzellen) direkt im Browser laden, (B) Standort-Kontext
> (Gelände + Parzelle + Nachbargebäude) für ein Projekt importieren, (C) SIA-416-
> Flächen/Volumen + Raumschemata berechnen wie in DOSSIER.
>
> Bezug zur ROADMAP: Swisstopo/Terrain/OSM = **Phase 4** (Kontext/Daten); SIA-416-
> Räume + Bilanz-CSV = **Phase 2**. Beide sind dort bereits als ⭐-Features gelistet.
**Alle in diesem Dokument genannten geo.admin.ch-Endpunkte wurden am 2026-06-29 live
gegen die echte API getestet** (curl + CORS-Header-Check). Wo „verifiziert" steht,
liegt eine echte Antwort vor.
---
## Teil A — Swisstopo-APIs & Dienste aus dem Browser
### A.0 Das Wichtigste vorweg: CORS & Lizenz
Zwei Fragen entscheiden, ob ein Dienst *ohne Backend-Proxy* aus einer reinen
Browser-App nutzbar ist: CORS und Lizenz. Beide sind hier günstig.
**CORS (live verifiziert):** Alle relevanten Hosts senden `access-control-allow-origin: *`:
| Host | Dienst | CORS | Range-Requests |
|---|---|---|---|
| `api3.geo.admin.ch` | REST (height, profile, identify, find, search) | ✅ `*` | — |
| `data.geo.admin.ch` | STAC-API + Daten-Assets (COG-GeoTIFF, XYZ.zip) | ✅ `*` | ✅ `206 Partial Content`, `accept-ranges`/`content-range` vorhanden |
| `wmts.geo.admin.ch` | WMTS-Kacheln | ✅ `*` | — |
| `3d.geo.admin.ch` | 3D-Tiles (`tileset.json` + glTF) | ✅ `*` | — |
**Konsequenz:** Höhenabfrage, Geocoding, Parzellen-Identify, Karten-/Orthofoto-
Kacheln, **COG-GeoTIFF-Höhenmodell per Range-Request** und 3D-Tiles sind **direkt aus
dem Browser ohne eigenen Proxy** abrufbar. Das ist ein großer Vorteil gegenüber vielen
anderen nationalen Geodiensten.
**Lizenz:** swisstopo/geo.admin.ch ist **Open Government Data**: „The acquisition and
use of data or services is free of charge, subject to the provisions on fair use."
Kommerzielle Nutzung ist erlaubt, Einbindung in (auch kommerzielle) Web-Apps explizit
gedeckt. Pflicht-Attribution: **`© swisstopo`** (bzw. „© Data: swisstopo"). „Fair use"
= z.B. Web-App mit Ø 20'000 Nutzern/Tag ok; aggressives Bot-Scraping vermeiden. Haftung
ausgeschlossen, ~98% Verfügbarkeit. [Terms of use FSDI](https://www.geo.admin.ch/en/general-terms-of-use-fsdi)
> ⚠️ **Korrektur zu DOSSIER & zur Doku:** Die offizielle REST-Doku notiert beim
> *Height*-Service „This service is not freely accessible (fee required)". Das ist
> **in der Praxis falsch / veraltet**: Der Endpunkt antwortet anonym, ohne Key, mit
> `200` und CORS `*` (verifiziert, siehe A.1). DOSSIERs Aussage „alle APIs offen, ohne
> Auth, ohne Key" deckt sich mit der gemessenen Realität. Wir verlassen uns aber nicht
> blind darauf, sondern behandeln 402/429 defensiv (Retry/Backoff, Cache).
### A.1 Höhenabfrage — Height-Service (Einzelpunkt)
Punkt-Höhe (DTM) aus swissALTI3D/DTM. **Verifiziert:**
```
GET https://api3.geo.admin.ch/rest/services/height?easting=2600000&northing=1200000&sr=2056
→ {"height":"555.5"}
```
Parameter:
- `easting`, `northing` — LV95 (`sr=2056`) oder LV03 (`sr=21781`). **Pflicht.**
- `sr``2056` (LV95) angeben, sonst Default `21781`.
- `elevation_model``DTM2` (= swissALTI3D, 2 m), `DTM25` (Default), `COMB`.
(Im Tal lieferten DTM2/DTM25/COMB denselben Wert; im Steilgelände kann DTM2 genauer sein.)
- `callback` — JSONP (brauchen wir wegen CORS nicht).
Nutzung im Tool: **Projekt-Nullpunkt-Z** bzw. „Gebäude auf Gelände setzen" — eine
einzelne Höhe an der Projekt-Koordinate. Antwortzeit ~50150 ms. Quelle:
[GeoAdmin REST Height](https://geoadmin.readthedocs.io/en/latest/services/sdiservices.html)
### A.2 Höhenprofil — Profile-Service (Schnittlinie)
Höhen entlang einer Polylinie — ideal für **Geländeschnitt** unter einem
Gebäude-Schnitt. **Verifiziert** (echte Werte zurück):
```
GET https://api3.geo.admin.ch/rest/services/profile.json
?geom={"type":"LineString","coordinates":[[2600000,1200000],[2600200,1200000]]}
&sr=2056&nb_points=3
→ [{"alts":{"COMB":555.5,"DTM2":555.5,"DTM25":555.5},"dist":0,"easting":2600000,"northing":1200000},
{"alts":{...},"dist":100,...}, {"dist":200,...}]
```
Parameter: `geom` (GeoJSON-LineString, max 6'000 Punkte), `sr`, `nb_points` (Anzahl
Stützpunkte, Default 200), `elevation_models`, `offset` (Glättung). Auch als
`profile.csv`. → Für 2D-Geländeschnitte **ohne** Mesh-Download. Quelle:
[GeoAdmin REST Profile](https://geoadmin.readthedocs.io/en/latest/services/sdiservices.html)
### A.3 swissALTI3D — Höhenmodell als COG-GeoTIFF (Mesh-Quelle) ⭐
Das ist der **Schlüssel für das Gelände-Mesh im Browser**. swissALTI3D ist das präzise
DTM der Schweiz (ohne Vegetation/Bebauung), Auflösung 0.5 m / 2 m, alle 6 Jahre
aktualisiert ([swissALTI3D](https://www.swisstopo.admin.ch/en/height-model-swissalti3d)).
Bezug über die **STAC-API** (verifiziert — Tile `swissalti3d_2019_2599-1198`):
```
GET https://data.geo.admin.ch/api/stac/v1/collections/ch.swisstopo.swissalti3d/items
?bbox=<lonMin,latMin,lonMax,latMax>&limit=...
```
Jedes 1×1-km-Tile liefert pro Auflösung **zwei** Asset-Typen:
| Asset | Typ | Browser-tauglich? |
|---|---|---|
| `..._0.5_2056_5728.tif` / `..._2_2056_5728.tif` | **Cloud-Optimized GeoTIFF**, **EPSG:2056** | ✅ **direkt** via `geotiff.js` + Range |
| `..._0.5_2056_5728.xyz.zip` / `..._2_..._xyz.zip` | ASCII-XYZ (E N Z) in ZIP | ✅ via `fflate` entpacken (so macht es DOSSIER) |
**Verifiziert:** `data.geo.admin.ch` liefert auf das `.tif` ein `206 Partial Content`
mit `content-range` bei `Range:`-Header und CORS `*`. Das bedeutet: **`geotiff.js`
liest nur den benötigten Ausschnitt eines COG per HTTP-Range, ohne das ganze File zu
laden** — perfekt für eine Browser-App. Bbox in WGS84 für STAC, Tile-Daten dann in
LV95-Metern (kein Reprojizieren der Z-Werte nötig). Quelle:
[STAC tech docs](https://docs.geo.admin.ch/) · COG-Tile live geprüft.
### A.4 SWISSIMAGE / Karten — WMTS-Kacheln
Orthofoto (10 cm) und Landeskarten als Kacheln. RESTful-URL-Template:
```
https://wmts.geo.admin.ch/1.0.0/<Layer>/default/<Time>/<TileMatrixSet>/<z>/<TileCol>/<TileRow>.<ext>
```
Beispiel-Layer:
- `ch.swisstopo.swissimage` — Orthofoto, `.jpeg`
- `ch.swisstopo.pixelkarte-farbe` — Landeskarte farbig, `.jpeg`
- `ch.kantone.cadastralwebmap-farbe`**Katasterplan (AV)**, `.png`
TileMatrixSets: **`2056` (LV95)**, `21781`, `3857` (Web-Mercator), `4326`. Zoom 028
(4000 m → 0.1 m); Zoom 27/28 nur für wenige Layer (swissimage, Kataster). Für 3D in
Three.js am einfachsten **`3857`** (Standard-Slippy-Map-Schema, z/x/y), z.B.
```
https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.swissimage/default/current/3857/{z}/{x}/{y}.jpeg
```
Für planimetrisch exakte 2D-Arbeit besser **`2056`**. CORS `*` (verifiziert). Quellen:
[WMTS docs](https://docs.geo.admin.ch/visualize-data/wmts.html) ·
[WMTS service](https://wmts.geo.admin.ch/) ·
[WMTS EPSG:2056 CodePen](https://codepen.io/geoadmin/pen/GZKEam).
> Es gibt zusätzlich klassisches **WMS** (`https://wms.geo.admin.ch/`, GetMap mit
> beliebiger BBox/Größe, ebenfalls EPSG:2056). Für ein einzelnes georeferenziertes
> Orthofoto-Rechteck unter dem Modell ist ein WMS-GetMap manchmal praktischer als
> WMTS-Kacheln zu stitchen. [WMS docs](https://docs.geo.admin.ch/visualize-data/wms.html)
### A.5 swissBUILDINGS3D — Nachbargebäude (3D)
Zwei Wege:
**(a) 3D-Tiles (Streaming, Cesium-Format)** — `glTF`/`tileset.json`, für große Gebiete:
```
https://3d.geo.admin.ch/<Layer>/<Version>/<Time>/tileset.json
```
Layer u.a. `ch.swisstopo.swissbuildings3d.3d`, `ch.swisstopo.swisstlm3d.3d`,
`ch.swisstopo.swissnames3d.3d`, `ch.swisstopo.vegetation.3d`. `Version` = `v1`, `Time`
optional (ISO `YYYYMMDD`, weglassen = aktuellste). CORS `*` (verifiziert auf
`tileset.json`). Direkt für CesiumJS gedacht; in **reinem Three.js** über
`@loaders.gl/3d-tiles` oder den `3DTilesRendererJS` (NASA-AMMOS/`three.js`-Community)
ladbar. [3D-Tiles docs](https://docs.geo.admin.ch/visualize-data/3d-tiles.html) ·
[Switzerland in 3D](https://www.swisstopo.admin.ch/en/switzerland-in-3d)
**(b) STAC-Tiles als CAD/Mesh-Datei (Download pro Tile)** — so macht es DOSSIER:
Collections `ch.swisstopo.swissbuildings3d_3_0` (neu; in Städten z.T. >700 MB Tiles)
und `ch.swisstopo.swissbuildings3d_2` (1-km-Tiles, ~50 MB, stabil). Assets in
`.dxf/.dwg/.obj/.ifc` (+ `.zip`), Varianten `solid`/`separated`. Für den **Import als
echte, editierbare Massen** ins eigene Modell ist der OBJ/IFC-Tile-Weg besser als
3D-Tiles (die sind read-only Visualisierung). swissBUILDINGS3D: >3 Mio Gebäude,
Lage-/Höhengenauigkeit 3050 cm.
**Empfehlung:** Für „Nachbarschaft als Kontext anzeigen" (Phase 4 Start) **3D-Tiles
streamen** (kein Download, kein Parsing). Wenn der Nutzer Nachbargebäude als Geometrie
*braucht* (Verschattung, Abstand), **STAC-OBJ-Tile** laden und als Mesh importieren.
### A.6 Parzelle / Kataster (AV) — Identify-Service ⭐
**Verifiziert** — Parzellen-Polygon aus einer Koordinate, in LV95:
```
GET https://api3.geo.admin.ch/rest/services/api/MapServer/identify
?geometry=2600423,1199521&geometryType=esriGeometryPoint
&imageDisplay=100,100,96&mapExtent=2600323,1199421,2600523,1199621
&tolerance=2&layers=all:ch.kantone.cadastralwebmap-farbe
&returnGeometry=true&geometryFormat=geojson&sr=2056
→ {"results":[{"type":"Feature","bbox":[...],
"geometry":{"type":"Polygon","coordinates":[[ [2600377.1,1199523.7], ... ]]},
"attributes":{"number":"698","egris_egrid":"CH507635214670","ak":"BE", ...}}]}
```
- Parzellen-Layer: **`ch.kantone.cadastralwebmap-farbe`** (liefert `number`,
`egris_egrid` = EGRID, Kanton; mit `returnGeometry=true&geometryFormat=geojson` das
**Parzellen-Polygon in LV95-Metern** → direkt als Grundstücksgrenze importierbar).
- Gebäudeadressen: `ch.swisstopo.amtliches-gebaeudeadressverzeichnis`; Gebäude-/
Wohnungsregister `ch.bfs.gebaeude_wohnungs_register` (EGID).
- Pflichtparameter: `geometry`, `geometryType` (`esriGeometryPoint|...Polygon|...Envelope`),
`mapExtent`, `imageDisplay`, `tolerance`; `sr=2056`; max 50 Features/Request.
Quellen: [Identify features](https://docs.geo.admin.ch/access-data/identify-features.html) ·
[GeoAdmin REST Identify/Find](https://geoadmin.readthedocs.io/en/latest/services/sdiservices.html) ·
Live-Antwort oben.
### A.7 Geocoding — SearchServer (Adresse → LV95)
**Verifiziert** (Adresse → Koordinate, deckt sich mit DOSSIERs `geocode()`):
```
GET https://api3.geo.admin.ch/rest/services/api/SearchServer
?searchText=Bundesplatz 3 Bern&type=locations&origins=address&sr=2056&limit=1
→ results[0].attrs: { label:"Bundesplatz 3 <b>3011 Bern</b>", lat:46.94677, lon:7.44419,
geom_st_box2d:"BOX(2600423.26 1199521.11, ...)", origin:"address", ... }
```
- `type=locations`, `origins` aus `{address, parcel, gg25, gazetteer, zipcode, district,
kantone}`, `sr=2056`. **Im LV95-Modus liefert die Geo-Admin-Konvention `y`=East,
`x`=North** (DOSSIER liest genau so: `e=attrs.y`, `n=attrs.x`). Labels enthalten
`<b>`-Tags (strippen).
- `type=featuresearch` + `features=<layer>` durchsucht Attribute (z.B. Parzellennummer).
Quelle: [Search](https://docs.geo.admin.ch/access-data/search.html) · Live-Antwort oben.
### A.8 Koordinatensystem LV95 / EPSG:2056 & Transformationen
Intern rechnet das BIM-Tool in **Metern** (ROADMAP-Konvention) und verschiebt den
Projekt-Ursprung nahe (0,0,0); LV95-Koordinaten sind ~2.6 Mio / 1.2 Mio Meter groß und
würden bei `float32` (Three.js) zu **Jitter** führen → **Origin-Shift Pflicht** (siehe B.3).
DOSSIER macht genau das (`origin_shift`/`shift_lv95`, typ. bbox-Center → 0/0/0).
Transformations-Optionen:
1. **`proj4` (npm `proj4@2.20.9`)** — universell, exakt. EPSG:2056-Definition:
```js
proj4.defs("EPSG:2056",
"+proj=somerc +lat_0=46.9524055555556 +lon_0=7.43958333333333 +k_0=1 "+
"+x_0=2600000 +y_0=1200000 +ellps=bessel "+
"+towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs +type=crs");
const [e,n] = proj4("EPSG:4326","EPSG:2056",[lon,lat]); // WGS84→LV95
```
Genauigkeit mit dieser 3-Parameter-`towgs84` ~1 m (für Kontext-Import völlig
ausreichend). Types: `@types/proj4`. Quellen:
[epsg.io/2056](https://epsg.io/2056) · [proj4js](https://github.com/proj4js/proj4js).
2. **Näherungsformeln (CH1903→WGS84, swisstopo)** — DOSSIERs Ansatz, ~1 m genau,
**0 Dependencies** (zwei kleine Funktionen `lv95_to_wgs84`/`wgs84_to_lv95`).
1:1 nach TS portierbar; gut, wenn man `proj4` nicht ziehen will. Reicht, weil
STAC-Queries ohnehin nur eine grobe WGS84-Bbox brauchen und alle *Daten* schon in
LV95 kommen.
3. **swisstopo REFRAME Web-API** — cm-genaue offizielle Umrechnung (LV95↔WGS84,
LN02↔Bessel). Nur nötig, wenn Vermessungs-Genauigkeit verlangt wird. REST, online.
[REFRAME Web](https://www.swisstopo.admin.ch/en/rest-api-geoservices-reframe-web)
**Empfehlung:** `proj4` mit fester EPSG:2056-Def (eine Abhängigkeit, exakt genug,
wartungsarm) — oder, wenn Dependency-Geiz, DOSSIERs Formeln portieren. REFRAME nur bei
Bedarf nachrüsten.
### A.9 Endpoint-Übersicht (Spickzettel)
| Zweck | Endpoint | Frei/CORS | Format |
|---|---|---|---|
| Punkt-Höhe | `api3…/rest/services/height` | ✅ ✅ | JSON |
| Höhenprofil (Schnitt) | `api3…/rest/services/profile.json` | ✅ ✅ | JSON/CSV |
| Gelände-Mesh (COG) | STAC `…/swissalti3d/items` → `.tif` (COG, 2056) | ✅ ✅ Range | GeoTIFF |
| Gelände (ASCII) | STAC `…/swissalti3d` → `.xyz.zip` | ✅ ✅ | XYZ in ZIP |
| Orthofoto/Karte | `wmts…/1.0.0/<layer>/…/{z}/{x}/{y}.jpeg` | ✅ ✅ | Kacheln |
| 3D-Nachbargebäude (stream) | `3d…/ch.swisstopo.swissbuildings3d.3d/v1/tileset.json` | ✅ ✅ | 3D-Tiles/glTF |
| 3D-Gebäude (Datei) | STAC `…/swissbuildings3d_2` → `.obj/.ifc` | ✅ ✅ | OBJ/IFC |
| Parzelle/Kataster | `api3…/MapServer/identify` `layers=all:ch.kantone.cadastralwebmap-farbe` | ✅ ✅ | GeoJSON |
| Geocoding | `api3…/SearchServer?type=locations` | ✅ ✅ | JSON |
---
## Teil B — Standort-Kontext importieren (Terrain + Parzelle + Nachbargebäude)
So bekommt ein Projekt seinen realen Kontext „auf Knopfdruck". Der Ablauf folgt
DOSSIER (`rhino/swisstopo.py`), übersetzt auf Browser-Libs.
### B.1 Pipeline (End-to-End)
```
Adresse/Parzelle ──SearchServer──▶ Zentrum (E,N) in LV95
├─ radius r ──▶ bbox_LV95 (E±r, N±r) ──proj4/Formeln──▶ bbox_WGS84
├─[Parzelle] identify(cadastralwebmap, point) ─▶ Polygon (LV95) ─▶ Grundstücksgrenze (Ebene 01 Vermessung)
├─[Gelände] STAC(swissalti3d, bbox_WGS84) ─▶ COG .tif(2056)
│ └─ geotiff.js readRasters(window) ─▶ Höhen-Grid (E,N,Z, m)
│ └─ Three.js BufferGeometry (Grid→Mesh) [optional: TIN, Höhenlinien, Volumen]
├─[Orthofoto] WMTS swissimage ─▶ Textur auf Gelände-Mesh ODER georef. Plane
└─[Nachbarn] 3D-Tiles streamen (Anzeige) ODER STAC swissbuildings3d_2 .obj ─▶ Mesh-Import
(Weltweit/ausserhalb CH: OSM-Overpass als Fallback, siehe B.5)
──▶ alle Geometrien um origin_shift (bbox-Center→0/0/0) verschoben, Z aus ALTI3D
```
### B.2 Gelände-Mesh aus swissALTI3D (Kern, Phase 4)
**Empfohlener Browser-Weg (COG + geotiff.js):**
1. STAC-Query mit `bbox_WGS84` → Liste der überlappenden Tiles; pro Tile das
gewünschte COG-Asset (`_2_2056_` für 2 m, `_0.5_2056_` für 0.5 m).
2. `geotiff.js`: `const tiff = await fromUrl(href)` → COG; `image.readRasters({window})`
liest **nur den Ausschnitt** (Range-Requests, da CORS+Range bestätigt). Ergebnis ist
ein reguläres Z-Raster mit bekanntem Origin/PixelScale (LV95-Meter) aus den
GeoKeys/`image.getOrigin()`/`image.getResolution()`.
3. Raster → **`THREE.BufferGeometry`**: ein Vertex pro Rasterpunkt
`(EshiftE, NshiftN, ZshiftZ)`, Faces als zwei Dreiecke pro Zelle (DOSSIER:
`mesh_from_grid`, gleiche Logik), `computeVertexNormals()`. Bei 0.5 m wird das Mesh
groß → bei Bedarf raumräumlich sub-samplen (DOSSIER macht ganzzahliges Sub-Sampling
auf dem **globalen** LV95-Raster, damit Nachbar-Tiles nahtlos zusammenpassen).
4. Mehrere Tiles: erst zu **einem** Grid mergen (gemeinsamer Origin/Step), dann meshen —
sonst entstehen Nähte (DOSSIER: `merge_grids`).
**Alternativweg (XYZ, exakt wie DOSSIER):** `.xyz.zip` laden → mit **`fflate`**
(npm, schnellster Inflate im Browser) entpacken → ASCII `E N Z` parsen → gleiches Grid.
Robust, aber überträgt mehr Bytes als der COG-Range-Weg. Für den Port empfehle ich
**COG primär, XYZ als Fallback**.
Bibliotheken: `geotiff@3.0.5`, `fflate` (XYZ-Variante), `three@0.185.0`.
[geotiff.js](https://github.com/geotiffjs/geotiff.js/) ·
[3D-Terrain aus GeoTIFF mit Three.js](https://spatial-dev.guru/2024/11/30/creating-3d-terrain-maps-from-geotiff-files-with-three-js/).
**Ableitungen wie in DOSSIER** (alle aus dem Grid, in Phase 4 portierbar):
- **Höhenlinien** (Marching-Squares auf dem Grid; npm `d3-contour` oder
`marchingsquares`) — für 2D-Plan.
- **TIN / Patch** (Delaunay aus den Punkten; npm `delaunator`) — alternatives Mesh.
- **Geschlossenes Gelände-Volumen** (Boden N m unter tiefstem Punkt) → gefüllte
Querschnitte beim Schnitt-Cut (DOSSIER `terrainVolume`/`terrainVolumeDepth`).
### B.3 Origin-Shift (Pflicht)
LV95-Koordinaten (~2.6e6) sprengen `float32`. Beim Import einmal
`shift = (eCenter, nCenter, zRef)` festlegen, **alle** Geometrien `shift` rechnen, und
`shift` am Projekt persistieren (für Re-Import / Geo-Referenz / Norden). DOSSIER:
`origin_shift`/`shift_lv95`, plus „Auto-Zoom auf Import" (ROADMAP §11). Damit bleibt das
Modell metergenau und der Rückweg in echte LV95-Koordinaten (Export, weitere
swisstopo-Abfragen) ist `+ shift`.
### B.4 Parzelle + Orthofoto
- **Parzelle:** `identify(...cadastralwebmap..., returnGeometry=true, geometryFormat=geojson)`
→ Polygon (LV95) → `shift` → als geschlossene Polylinie auf Ebene **`01 Vermessung`**.
Attribute `number`/`egris_egrid` am Objekt/Projekt speichern.
- **Orthofoto:** WMTS `swissimage`-Kacheln über die Modell-Bbox stitchen → eine Textur,
als Material auf das Gelände-Mesh **oder** auf eine georeferenzierte Plane (DOSSIER:
`add_ortho_plane`, mit UV-Shift gegen Tile-Nähte). Für 3D ist `3857` einfacher, für
exakte 2D-Lage `2056`.
### B.5 OSM-Overpass als weltweiter Fallback (Phase 4)
Ausserhalb der Schweiz (oder wenn nur 2D-Footprints/Straßen reichen): DOSSIER hat einen
**Overpass-Importer** (`https://overpass-api.de/api/interpreter`, POST) mit 7 Kategorien
(Straßen/Gebäude/Wasser/Wasserläufe/Grün/Wege). Liefert OSM-Ways → Polylinien. Im
Browser identisch nutzbar (`fetch` POST). Overpass koordiniert in WGS84 → mit
`proj4`→LV95→`shift`. Hinweis: Overpass-CORS ist beim Haupt-Server meist offen, kann
aber je nach Mirror variieren; ggf. anderen Mirror wählen.
[Overpass API](https://overpass-api.de/).
### B.6 Was sich von DOSSIER **nicht** 1:1 portieren lässt
- **Rhino-`_-Import`** für DXF/DWG/OBJ und **`_-MeshPatch`/Delaunay**-Commands gibt es im
Browser nicht → ersetzen durch JS-Parser/Algorithmen (OBJ: `three`-`OBJLoader`;
Delaunay: `delaunator`; Contours: `d3-contour`).
- **Filesystem-Cache neben der `.3dm`** → Browser: **IndexedDB**-Cache (Cache-API für
Kacheln). Passt zur ROADMAP-Phase 5 (IndexedDB-Persistenz).
- IFC-Import von swissBUILDINGS3D 3.0 → über **web-ifc** (ohnehin im Stack, Phase 4).
---
## Teil C — SIA 416 (Flächen/Volumen) & SIA 421 + DOSSIER-Logik
### C.1 SIA 416 — Flächen- und Volumenhierarchie
**SIA 416:2003** „Flächen und Volumen von Gebäuden" ist die in der CH gültige Norm und
Berechnungsbasis für Kostenplanung/Flächennachweise. Sie kennt vier Bereiche:
**GSF** (Grundstück), **GF** (Geschossflächen), **AGF** (Aussengeschossflächen), **GV**
(Volumen). Maßgeblich ist die effektive Geometrie (keine fiktiven Zuschläge mehr).
Quellen: [SIA 416 Übersicht (siworks/DBV)](https://diebauherrenvertretung.ch/sia146/) ·
[SIA-Shop 416/2003](https://shop.sia.ch/normenwerk/architekt/sia%20416/dfi/D/Product) ·
[Flächenkennzahlen SIA 416 (Ginesta, PDF)](https://www.ginesta.ch/resources/public/lava3/media/kcfinder/files/Fl%C3%A4chenkennzahlen%20SIA%20416%20Norm.pdf).
**Hierarchie & Formeln (verifiziert):**
```
GSF Grundstücksfläche
GF Geschossfläche = KF + NGF
├─ KF Konstruktionsfläche (Wände/Stützen; tragend KFT + nicht tragend KFN)
└─ NGF Nettogeschossfläche = NF + VF + FF
├─ NF Nutzfläche = HNF + NNF
│ ├─ HNF Hauptnutzfläche (zweckbestimmte Hauptnutzung: Wohnen, Büro …)
│ └─ NNF Nebennutzfläche (Lager, Bad/WC, Abstell-, Nebenräume)
├─ VF Verkehrsfläche (Erschließung: Flure, Treppen, Lifte)
└─ FF Funktionsfläche (Gebäudetechnik: Heizung, Lüftung, Technik)
AGF Aussengeschossfläche (Balkone, Terrassen, gedeckte Aussenflächen)
GV Gebäudevolumen [m³]
```
| Abk. | Deutsch | Inhalt (Kurz) |
|---|---|---|
| GSF | Grundstücksfläche | Parzellenfläche (aus Kataster, Teil A.6) |
| GF | Geschossfläche | allseits umschlossene + überdeckte Grundrissflächen, geschossweise |
| KF | Konstruktionsfläche | Bauteile (Wände/Stützen), nicht begehbar; KFT tragend / KFN nicht tragend |
| NGF | Nettogeschossfläche | begehbare Fläche innerhalb der Umschließung = NF+VF+FF |
| NF | Nutzfläche | tatsächlich nutzbar = HNF+NNF |
| HNF | Hauptnutzfläche | zweckbestimmte Hauptnutzung |
| NNF | Nebennutzfläche | dienende Nebenräume (Lager, Bad, WC) |
| VF | Verkehrsfläche | horizontale/vertikale Erschließung |
| FF | Funktionsfläche | Gebäudetechnik |
| AGF | Aussengeschossfläche | Balkone/Terrassen u.ä. |
| GV | Gebäudevolumen | umbauter Raum [m³] |
> **Versionshinweis:** Es kursiert eine Revision **SIA 416:2017** mit präzisierten
> Begriffen, in der Praxis wird aber breit weiter **416:2003** referenziert. Für unser
> Tool reicht die Klassifikation **HNF/NNF/VF/FF/GF/AGF + Bilanz**; die genaue Auflage
> nur als Label dokumentieren. Verbindliche Definitionen stehen im kostenpflichtigen
> Normtext (SIA-Shop) — die hier zitierten freien Quellen stimmen in der Struktur überein.
### C.2 SIA 421 — Flächengliederung für Bewirtschaftung/Vermietung
**SIA 421:2006** „Flächengliederung und Mengenangaben" (Korrigenda C1:2014) baut auf der
SIA-416-Systematik auf und **gliedert Flächen für Immobilien-Bewirtschaftung und
Vermietung** (Mietflächen, Nutzungseinheiten, Zuordnung von VF/FF zu Mietern). Relevant,
sobald wir **Mietflächen-/Bewirtschaftungs-Auswertungen** wollen (über den reinen
Architektur-Nachweis hinaus). Für den ersten Wurf **nicht zwingend** — SIA 416 reicht
für Flächennachweis und Raumschema. SIA 421 ist die natürliche Erweiterung, wenn
Property-Management-Features kommen. Quellen:
[SIA 421:2006 (PDF Inhalt)](https://shop.sia.ch/d475e3f9-10c9-48aa-9be8-657784ea519b/F/DownloadAnhang) ·
[Korrigenda C1:2014 (PDF)](https://www.sia.ch/fileadmin/content/download/sia-norm/korrigenda_sn/421-C1_2014_d.pdf).
### C.3 Wie DOSSIER SIA macht (Vorlage für den Port)
Quellcode: `rhino/elemente.py` (Räume) + `rhino/elemente_uebersicht.py` (Bilanz/CSV).
Kernpunkte, die wir 1:1 übernehmen:
**Datenmodell pro Raum.** Ein Raum ist eine **geschlossene Outline-Curve** + ein
**Text-Stempel**. Klassifikation über das Feld `dossier_raum_sia` mit Werten
`{"", hnf, nnf, vf, ff, gf, agf}` (`_RAUM_SIA_KINDS`). Weitere Felder: `name`, `nummer`,
`funktion` (`wohnen|schlafen|bad|kueche|essen|flur|…`), `personen` (für
Personenbelegung/Brandschutz), Rundung, Stempel-Layout.
**Flächen-/Umfangsberechnung** (`_raum_amp`): Fläche aus `AreaMassProperties` (≙ in JS
**Shoelace-Formel** über das Polygon), Umfang = Kurvenlänge, plus Zentroid für den
Stempel. Im Browser: Polygon-Fläche selbst rechnen (Shoelace), kein Mesh nötig — exakt
und schnell. Rundungsstufen (`_format_area`): `exakt|0.01|0.1|0.5|1` (z.B. `0.5` =
`round(a*2)/2`).
**SIA-Bilanz** (`compute_sia_bilanz`, `scope = total | geschoss:<id>`): summiert
Raumflächen je Klasse, dann:
```
NF = HNF + NNF
NGF = NF + VF + FF (GF/AGF separat aggregiert, zählen nicht in NGF)
```
(genau die Formeln aus C.1). Ergebnis je Geschoss + Total.
**Farb-/Darstellungs-Konvention** (`_SIA_COLORS_HEX`, Pastell): HNF rot `#e8a8a8`,
NNF orange `#e8c498`, VF gelb `#e8d878`, FF hellblau `#a8c8e0`, GF grau `#d0d0d0`,
AGF hellgrün `#c0d8c0`. Umgesetzt als **regelbasierte Overrides** (`_build_sia_preset_rules`,
Preset „SIA-Raeume"): Bedingung `user_string == code` → Outline-Farbe + Solid-Hatch.
→ Passt 1:1 zur geplanten **Overrides-Engine** (ROADMAP §2c/§11).
**Export** (`_cmd_export_raeume`, `_export_bilanz`): CSV, **Semikolon + UTF-8-BOM**
(CH/DE-Excel), Dezimal-Komma. Raumliste: Nummer; Name; Geschoss; Funktion; SIA; Fläche;
Fläche gerundet; Umfang. Bilanz: eine Spalte je Geschoss + Total, Zeilen je Kategorie.
→ Im Browser: Blob + Download (kein SaveFileDialog), gleiche CSV-Struktur. Optional
direkt `.xlsx` via `sheetjs`/`exceljs`.
**Layer-Routing:** GF→`61_GF`, AGF→`62_AGF`, Rest→`60_RAEUME` (`_layer_path_for_raum_sia`)
— damit Geschossflächen-Outlines getrennt schalt-/exportierbar sind. Übersetzt sich auf
unsere Ebenen-Codes (`60 Räume`).
**Was wir im Port besser/anders machen:**
- Fläche per **Shoelace** statt Rhino-Mass-Props (0 Deps).
- Bilanz **reaktiv** aus dem semantischen Modell (Zustand-Store) statt Doc-Scan.
- **Space = Slab-/Raum-Polygon mit `siaClass`** im Datenmodell (ROADMAP:
`Space { boundary, name }`), Bilanz als abgeleitete Sicht.
---
## Teil D — Umsetzungsplan (Endpunkte · Libs · Phase)
Reihenfolge orientiert sich an der ROADMAP (SIA = Phase 2, Geo = Phase 4) und an „größter
Nutzen zuerst, geringste Abhängigkeit zuerst".
### Phase 2 — SIA-Räume (kein Netz, reine Logik) ⭐
**Endpunkte:** keine. **Libs:** keine (Shoelace selbst), optional `exceljs`/`sheetjs` für
.xlsx.
1. `Space`-Modell: `{ boundary[], geschossId, name, nummer, funktion, siaClass∈{hnf,nnf,vf,ff,gf,agf}, personen }`.
2. `computeArea` (Shoelace) + Umfang; Rundungsstufen (`exakt|0.01|0.1|0.5|1`) wie DOSSIER `_format_area`.
3. `computeSiaBilanz(scope)` → `{hnf,nnf,nf,vf,ff,ngf,gf,agf,count,personen}` mit
`nf=hnf+nnf`, `ngf=nf+vf+ff`.
4. SIA-Farbpalette + Stil-Override (Outline-Farbe/Solid-Fill) in der Overrides-Engine.
5. Raumstempel-Renderer (Felder-Layout) + **CSV-Export** (Semikolon, UTF-8-BOM, Komma)
für Raumliste **und** Bilanz.
*Ergebnis:* SIA-416-Flächennachweis + Raumschema, Excel-kompatibel — vor jeder Geo-Arbeit nutzbar.
### Phase 4a — Geo-Grundlage: Koordinaten + Standortabfrage
**Endpunkte:** `SearchServer` (Geocoding), `height` (Punkt-Z), `identify`
(Parzelle, `cadastralwebmap-farbe`). **Libs:** `proj4@2.20.9` (+ `@types/proj4`).
1. `proj4`-EPSG:2056-Def + Helfer `lv95↔wgs84`, `bboxLv95→bboxWgs84` (DOSSIER-Logik).
2. **Origin-Shift**-Mechanik + Persistenz am Projekt (`shift = bbox-Center`), Auto-Zoom.
3. Adresssuche → Zentrum; „Gelände-Höhe holen" (height); „Parzelle holen" → Polygon auf
Ebene `01 Vermessung` (+ EGRID/Nummer am Projekt).
*Ergebnis:* Projekt ist georeferenziert; Parzelle + Adresse + Geländehöhe vorhanden.
### Phase 4b — Gelände-Mesh + Orthofoto
**Endpunkte:** STAC `swissalti3d` (COG `.tif`, 2056) + `profile.json`; WMTS `swissimage`.
**Libs:** `geotiff@3.0.5`, `three@0.185.0`, optional `fflate` (XYZ-Fallback),
`d3-contour`/`marchingsquares` (Höhenlinien), `delaunator` (TIN).
1. STAC-Query (bbox) → COG-Tiles; `geotiff.js` Range-Read → Grid; Tiles mergen.
2. Grid → `THREE.BufferGeometry` (DOSSIER `mesh_from_grid`/`merge_grids`); Sub-Sampling
auf globalem LV95-Raster; Normalen.
3. Optional: Höhenlinien (2D-Plan), TIN, geschlossenes Volumen (Schnitt-Füllung).
4. WMTS-`swissimage`-Kacheln → Textur auf Mesh/Plane (DOSSIER `add_ortho_plane`).
5. Geländeschnitt im 2D-Plan via `profile.json` entlang der Schnittlinie.
*Ergebnis:* echtes Gelände mit Orthofoto unter dem Gebäude; Geländeschnitte.
### Phase 4c — Nachbargebäude + weltweiter Fallback
**Endpunkte:** 3D-Tiles `ch.swisstopo.swissbuildings3d.3d/v1/tileset.json` (Anzeige)
**oder** STAC `swissbuildings3d_2` `.obj/.ifc` (Import); OSM `overpass-api.de`.
**Libs:** `@loaders.gl/3d-tiles@4.4.3` **oder** `3DTilesRendererJS`; `three`-`OBJLoader`;
`web-ifc` (für 3.0-IFC); `proj4`.
1. Kontext-Anzeige: 3D-Tiles in den Three-Scenegraph streamen (kein Download).
2. Bedarf an echter Geometrie (Verschattung/Abstand): STAC-OBJ-Tile → Mesh-Import → `shift`.
3. Ausserhalb CH / nur 2D: Overpass-POST → Ways → Polylinien (Ebene `70 OSM`).
*Ergebnis:* Nachbarschaftskontext (CH 3D, weltweit OSM).
### Querschnitt (alle Geo-Phasen)
- **Caching:** IndexedDB für STAC-Antworten + COG-Bytes + Kacheln (ersetzt DOSSIERs
Disk-Cache); Cache-Schlüssel = Tile-ID/URL.
- **Defensive HTTP:** Timeouts, Retry/Backoff, 402/429 abfangen, Größen-Limit pro Tile
(DOSSIER: 200-MB-Guard).
- **Attribution:** „© swisstopo" sichtbar einblenden, „© OpenStreetMap-Mitwirkende" bei OSM.
- **Worker:** GeoTIFF-Parsing + Mesh-Bau im Web-Worker (Comlink, ROADMAP-Stack), UI bleibt flüssig.
### Empfohlener Library-Satz (npm, aktuell)
`proj4@2.20.9` · `geotiff@3.0.5` · `three@0.185.0` · `fflate` (XYZ) ·
`@loaders.gl/3d-tiles@4.4.3` *oder* `3DTilesRendererJS` · `delaunator` ·
`d3-contour` · `web-ifc` (im Stack) · `exceljs`/`sheetjs` (optional, .xlsx).
---
## Quellen
- GeoAdmin REST (height/profile/identify/find/search): https://geoadmin.readthedocs.io/en/latest/services/sdiservices.html
- GeoAdmin Tech-Docs (Hub): https://docs.geo.admin.ch/
- Identify Features: https://docs.geo.admin.ch/access-data/identify-features.html
- Search: https://docs.geo.admin.ch/access-data/search.html
- WMTS: https://docs.geo.admin.ch/visualize-data/wmts.html · https://wmts.geo.admin.ch/
- WMS: https://docs.geo.admin.ch/visualize-data/wms.html
- 3D-Tiles: https://docs.geo.admin.ch/visualize-data/3d-tiles.html
- swissALTI3D: https://www.swisstopo.admin.ch/en/height-model-swissalti3d
- Switzerland in 3D / swissBUILDINGS3D: https://www.swisstopo.admin.ch/en/switzerland-in-3d
- Terms of use (FSDI / OGD): https://www.geo.admin.ch/en/general-terms-of-use-fsdi
- REFRAME Web-API: https://www.swisstopo.admin.ch/en/rest-api-geoservices-reframe-web
- EPSG:2056 Definition: https://epsg.io/2056
- proj4js: https://github.com/proj4js/proj4js
- geotiff.js: https://github.com/geotiffjs/geotiff.js/
- Three.js-Terrain aus GeoTIFF: https://spatial-dev.guru/2024/11/30/creating-3d-terrain-maps-from-geotiff-files-with-three-js/
- WMTS EPSG:2056 Beispiel: https://codepen.io/geoadmin/pen/GZKEam
- Overpass API: https://overpass-api.de/
- SIA 416 (Übersicht): https://diebauherrenvertretung.ch/sia146/ · https://shop.sia.ch/normenwerk/architekt/sia%20416/dfi/D/Product
- SIA 416 Flächenkennzahlen (PDF): https://www.ginesta.ch/resources/public/lava3/media/kcfinder/files/Fl%C3%A4chenkennzahlen%20SIA%20416%20Norm.pdf
- SIA 421:2006 (PDF) + Korrigenda C1:2014: https://shop.sia.ch/d475e3f9-10c9-48aa-9be8-657784ea519b/F/DownloadAnhang · https://www.sia.ch/fileadmin/content/download/sia-norm/korrigenda_sn/421-C1_2014_d.pdf
- DOSSIER-Quellcode (Vorlage): `rhino/swisstopo.py`, `rhino/osm.py`, `rhino/elemente.py` (Räume/SIA), `rhino/elemente_uebersicht.py` (Bilanz/CSV)