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:
@@ -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 ~50–150 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 0–28
|
||||
(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 30–50 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
|
||||
`(E−shiftE, N−shiftN, Z−shiftZ)`, 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)
|
||||
Reference in New Issue
Block a user