map2stl

Schweizer Landeskarte → druckbares 3D-Modell · swissALTI3D · swissBUILDINGS3D · swissTLMRegio · Bambu Lab AMS-ready
Kein Bereich ausgewählt

3D-Vorschau

Maus: Drag = Rotieren · Rad = Zoom · Rechte Maustaste = Pan
1 Finger: Rotieren · 2 Finger: Pinch-Zoom + Verschieben

ℹ Browser-Vorschau vs Server-Build

⚡ Browser-Vorschau
  • Max ~360 × 360 Pixel
  • Gebäude als einfache Klötze
  • Wald als Box-Säulen
  • geglättetes Terrain
  • direkt im Browser
  • gratis, ohne Login
  • ~1–8 Sekunden
🛠 Server-Build
  • Bis 25 Mio Pixel
  • Echte 3D-Dachgeometrie
  • Vegetation als Säulen
  • volle Heightmap-Auflösung
  • Multi-Color Filament-Trennung
  • Bambu AMS-ready (.3mf)
  • ~30–90 Sekunden

Vorschau und Server-Rendering nutzen dieselbe Geometrie — der Hauptunterschied ist die Auflösung. Der Browser schafft nur begrenzte Auflösung flüssig; für feinere Modelle nutze das GPU-Server-Rendering.

📦 Der Server-Build liefert eine .3mf-Datei mit Farb- & Filament-Informationen (kompatibel mit Bambu Studio, OrcaSlicer, PrusaSlicer u.a.) sowie einzelne .stl-Dateien für maximale Kompatibilität.

🚀 Schnellstart

  1. 1
    📐 Bereich zeichnen
    Klick auf „Bereich zeichnen" und zieh auf der Karte ein Rechteck auf — oder such oben rechts einen Ort. Kleiner Bereich = mehr Details & schnellerer Build.
  2. 2
    🎨 Stil anpassen optional
    Maße, Höhenüberhöhung, Farben/Elemente & Filament-Sparen einstellen. Die Voreinstellungen passen für die meisten Modelle — du kannst auch direkt weiter.
  3. 3
    ⚡ Vorschau gratis
    Mit „Vorschau" bekommst du in Sekunden eine grobe Browser-3D-Ansicht, um die Auswahl zu prüfen — ohne Login.
  4. 4
    🛠 Generieren & herunterladen
    Klick „Generieren" für den vollen Server-Build. Ergebnis: eine .3mf-Datei (Farben & Filament — für Bambu Studio, OrcaSlicer, PrusaSlicer) plus einzelne .stl-Dateien. Ab damit in den Slicer!

Login

Willkommen zurück! Logge dich ein.

Noch kein Account? Registrieren → · Passwort vergessen?

Auswahl speichern

Speichere die aktuelle BBox + Settings unter einem Namen.

Account-Einstellungen

Email ändern, Passwort wechseln oder Account löschen.

📧 Email ändern
🔒 Passwort ändern
⚠ Account löschen
Löscht deinen Account und ALLE gespeicherten Auswahlen permanent. Diese Aktion kann nicht rückgängig gemacht werden.

🇨🇭 Verwendete swisstopo-Datenquellen

Alle Daten kostenlos unter CC-BY-Lizenz vom Bundesamt für Landestopografie (swisstopo).

⚙️ Funktionsweise

🚀 GPU-Beschleunigung

→ Mehr Info auf swisstopo.admin.ch

📘 Funktionsweise swissmap2stl

Wie aus deiner BBox auf der Karte ein druckbares 3D-Modell wird — alle Berechnungs-Schritte im Detail.

Übersicht

Du zeichnest eine Bounding Box auf der Schweiz-Karte. Das Backend lädt parallel alle nötigen Daten-Layer von swisstopo (Geländemodell, Oberflächenmodell, 3D-Gebäude, Vektor-Wasser, Vektor-Strassen, Bodenbedeckung), komponiert daraus eine Heightmap mit aufgesetzten 3D-Gebäuden und Vegetation, klassifiziert jeden Pixel für Multi-Color-Print, und generiert pro Filament-Farbgruppe eine STL-Datei plus ein .3mf-Archiv. Die DEFLATE-Compression des finalen 3MF läuft auf einer NVIDIA-GPU (RTX PRO 6000) statt CPU — 10-30× schneller. Bei nicht erreichbarem GPU-Service automatischer CPU-Fallback.

1. Datenerfassung (parallel)

  • STAC-API auf data.geo.admin.ch/api/stac/v0.9 listet alle GeoTIFF-Tiles für deine BBox.
  • COG-Tiles (Cloud-Optimized GeoTIFFs) für ALTI3D + SURFACE3D werden via HTTP-Range-Reads gelesen — kein voller Download, nur die nötige Sub-Region. Pro km²-Kachel gecached unter /cache/swissdata/*.bin (LRU 50 GB).
  • GDB-Tiles (swissBUILDINGS3D 3.0) sind ZIP-komprimierte ESRI File Geodatabases (~16 MB pro Tile). Werden mit ogr2ogr -nlt MULTIPOLYGONZ in GeoJSON gewandelt, dann in JS geparst, und als .v6gdb.json persistiert.
  • TLM3D-Vektoren (Wasser, Strassen, Bahnen) via api3.geo.admin.ch / MapServer/identify als GeoJSON-LineStrings.
  • swissTLMRegio Landcover (Wald, Fels, Geröll, Sumpf, Gletscher) als 218 MB Komplett-Schweiz-Index, einmal pro Container-Boot extrahiert und in einer RBush-Spatial-Index für O(log n) BBox-Queries.
  • Alle Quellen werden parallel geholt; einzelne Fails (Timeout, 5xx) lassen den Build mit den restlichen Daten weiterlaufen.

2. Heightmap-Komposition

  • ALTI3D-Tiles werden im Sample-Step-Raster (z. B. 2 m) zu einer zusammenhängenden Float32-Heightmap kombiniert: heightmap.data[W×H], jeder Wert = Bodenhöhe in Meter über Meer.
  • Vegetations-Maske aus SURFACE3D - ALTI3D > 4 m (oder der User-konfigurierten Min-Höhe).
  • Gebäude-Maske: Polygone werden mit Sutherland-Hodgman-Clipping an der User-BBox zurechtgeschnitten und ins Grid rasterisiert.
  • Bilineare Interpolation zwischen Grid-Punkten (oder Nearest-Neighbor für treppigen Look) für sub-Pixel-Lookups.

3. Gebäude-Extrusion (3D-TIN, echte Dachformen)

Aus swissBUILDINGS3D 3.0 kommt pro Gebäude das komplette 3D-Triangle-Mesh (Floor + Walls + Roof). Das wird direkt verwendet — keine flache Box-Extrusion mehr.

Z-Anchoring (kritisch — sonst „flying roofs"):

  • Floor-Vertices (z ≈ GELAENDEPUNKT): an lokales Terrain verankert via heightAt(E, N). → Boden warpt mit Hang, Gebäude sitzt bündig auf dem Boden.
  • Wand-/Dach-Vertices: an die Gebäude-Referenz-Höhe verankert: realToMmZ(GELAENDEPUNKT) + (z − GELAENDEPUNKT) × mmPerMeter × buildingScale. → Roof ist rigid, kippt nicht mit dem Hang.

Hohlbau-Modus (wallThicknessMm > 0):

  • Footprint-Polygon nach innen versetzt um wallThicknessMm (Bisektor-Inset)
  • Hohle Box-Wände vom Terrain bis zur Eave-Höhe (DACH_MIN-Attribut aus GDB)
  • Eave-Donut zwischen Aussen- und Innen-Ring auf Eave-Höhe
  • Roof-TIN-Triangles (Z ≥ Eave) werden obendrauf gesetzt und am Wand-Top verankert
  • Mesh-Inset im Hybrid-Modus → echte Dachformen UND Material-Ersparnis

Per-Building SHRINK_MM = 0.05 zur Centroid hin: verhindert non-manifold-Edges bei Reihenhäusern, die exakt dieselbe Wand teilen.

4. Ribbon-Geometrie (Strassen, Wasser, Bahn)

  • Linien aus TLM3D als LineStrings in LV95-Koordinaten
  • Subdivision auf max 0.5 × Sample-Step pro Segment — sonst driftet die Ribbon über/unter das Terrain-Mesh
  • Pro Linien-Vertex eine Miter-Senkrechte (90° auf der Tangenten-Mittelung der zwei angrenzenden Segmente)
  • Pro Segment 8 Triangles: Top, Bottom, Linke Wand, Rechte Wand (je 2 Tri pro Quad) + End-Caps am LineString-Anfang/Ende
  • Top-Z = terrainHeightAt(E, N) + stampHeightMm — Höhe konfigurierbar pro Layer (water-/road-/railway-height)
  • Triangle-aware Terrain-Lookup: identische Triangulation wie die Heightmap-Mesh selbst → keine Z-Fighting-Lücken

5. Klassifikation für Multi-Color

Pro Pixel im Heightmap-Grid wird eine Klassen-ID (0–11) vergeben:

IDKlasseQuelle
0base (Wiese)Default — alles nicht-klassifiziert
1forestswissTLMRegio-Polygon
2waterTLM3D-Polygon
3building (Wohnhaus)swissBUILDINGS3D 3.0 Polygon
4rock (Fels)swissTLMRegio-Polygon
5vegetation (freistehende Bäume)SURFACE3D-Delta > 4 m, ausserhalb Wald-Polygon
6highriseswissBUILDINGS3D Klasse: Hochhaus, Turm
7sacredswissBUILDINGS3D Klasse: Kirche, Kapelle
8industrialswissBUILDINGS3D Klasse: Lagertank, Treibhaus
9specialswissBUILDINGS3D Klasse: Vordach, Brücke
10roadTLM3D-Strassen-Lines (Ribbon)
11railwayTLM3D-Bahn-Lines (Ribbon)

Prioritäts-Reihenfolge: Gebäude/Wasser sind autoritär (überschreiben alles), dann TLMRegio Wald/Fels, dann Vegetation/Strassen/Bahn nur auf base-Pixeln.

Das Frontend-Preset gruppiert die 12 Klassen in 1-12 Filament-Gruppen (siehe „Anzahl Farben"-Dialog).

6. STL-Generierung pro Filament-Gruppe (Worker-Pool)

Backend hat einen LayerWorkerPool mit 4 parallelen Node-Worker-Threads. Pro Filament-Gruppe werden parallel generiert:

  • Terrain-STL: Triangle-Mesh aus Heightmap-Grid (2 Triangles pro Pixel)
  • Buildings-STL: TIN-Dump oder Hybrid-Box+Roof (je nach Hohlbau-Setting)
  • Vegetations-Säulen: volumetrische Boxen pro Vegetations-Pixel (Top-Z = Terrain + Vegetationshöhe)
  • Ribbons: Strassen, Wasser, Bahn (siehe Sektion 4)
  • Color-Stamps: dünne Schicht aufs Terrain für Klassen ohne eigene Geometrie (z. B. Fels)

SharedArrayBuffer: die Heightmap-Daten werden via SAB an alle Worker übergeben — keine Kopie pro Dispatch (bei 14M-Cell Heightmap = 56 MB × 4 Workers würde sonst Multi-GB-Allocation produzieren).

7. 3MF-Build (Multi-Material-Container)

  • Alle generierten STLs werden in ein 3MF-Archiv verpackt (im Grunde ein ZIP mit XML-Manifest + 3D-Model-Files)
  • XML beschreibt Filament-Mapping pro Part und Bambu-AMS-Slot-Zuordnung
  • Vertex-Dedup pro Part: Map<x|y|z-Hash → index> reduziert Daten 50-70 % (identische Vertices verschiedener Triangles werden zu einem Index)
  • Finale DEFLATE-Compression auf GPU (siehe nächste Sektion)

🚀 CUDA-Integration (GPU-Beschleunigung)

Hardware-Stack:

  • NVIDIA RTX PRO 6000 (Blackwell, 96 GB GDDR7, 24'064 CUDA-Cores)
  • Docker-Container mit nvidia/cuda:12.8.2-devel (Devel-Variante damit JIT-Compilation für sm_120 funktioniert)
  • CuPy 13.6 für elementwise + Reduktionen, nvCOMP 4.1 für DEFLATE
  • HTTP-API: cuda-compute (FastAPI), Bearer-Token-Auth, GPU-Concurrency-Semaphore, Response-Gzip für grosse Outputs

Vertex-Dedup-Pfad (Threshold: ~100k Triangles pro Layer):

  1. Worker-Thread sammelt alle Vertices eines Parts → flat Float32Array
  2. POST /v1/mesh/dedup-vertices mit Body = raw Vertices, Header X-Vertex-Count
  3. GPU: Quantisiert auf 0.001 mm Grid → cp.unique → first-occurrence-Index pro Bucket
  4. Response: [u32 unique_count][f32 unique_verts][u32 indices], gzip-komprimiert

DEFLATE-Pfad:

  1. Health-Check des GPU-Service (für 30 s gecached). Nicht erreichbar → CPU-zlib.
  2. POST /v1/compress/deflate?level=6 mit raw binary Body
  3. GPU: nvCOMP DEFLATE — massiv parallel über die Cores
  4. Backend persistiert das Ergebnis unter /cache/results/{jobId}/

Real-World-Messungen (Production-Logs):

OperationGPU computeRoundtrip totalCPU-Fallback (V8)
Vertex-Dedup 3 M V12 ms339 ms~150 ms
Vertex-Dedup 7 M V24 ms570 ms~350 ms
Vertex-Dedup 30 M V98 ms2.4 s~1.5 s
Vertex-Dedup 63 M V1.4 s12.9 s~3 s (V8 OOM-Risiko)
DEFLATE 853 MB236 ms2.8 s~10 s

End-zu-End-Speedup gegenüber reinem CPU: typische 80 km² Builds: 3–5× (Speerr 80 km²: 172 s → 49 s). Bei sehr grossen Workloads (60M+ V) wird GPU-Compute selbst zum Bottleneck.

Robustheit:

  • Health-Cache 30 s: spart Network-Round-Trips bei Burst von kleinen Builds
  • Bei HTTP-Error, Timeout, oder Health-Failure → automatischer Fallback auf CPU. Build läuft weiter, einfach langsamer.
  • Per-Call-Logging mit Backend (gpu/cpu) + Compute-Zeit + Roundtrip im Backend-Log
  • Stats via /api/admin/status: cuda.gpuCalls, cuda.cpuFallbacks

Weitere GPU-Pfade (vorhanden, aktuell selten genutzt):

  • Heightmap → STL Triangle-Generation (/v1/mesh/heightmap-to-stl): Threshold 50M Tris — bei kleineren Builds ist die JS-V8 Triangulation single-threaded so schnell, dass GPU-Overhead nicht lohnt. Bei riesigen Builds (>240 km²) wird's aktiv.
  • Polygon-Rasterisierung für Building-Masken (geplant — potentiell 10× bei dichten Städten)

⚡ Caching (3 Ebenen)

CachePfadInhaltPolicy
COG-Tiles/cache/swissdata/*.binALTI3D/SURFACE3D Float32-Pixels pro km²-KachelLRU 50 GB
GDB-Parses/cache/*.v6gdb.jsonFootprints + TIN + Heights aus geparseten GDBsLRU 50 GB
Build-Hash/cache/results/.by-hash/*.jsonIdentische Param-Hashes → fertige Builds 1:1 wieder ausliefern3 h TTL
TLMRegio-Index/cache/tlmregio/218 MB Landcover-Polygone, RBush-IndexNie evicted

Job-Queue mit MAX_CONCURRENT_BUILDS=2: weitere Builds bekommen ein „queue"-Event mit Position + ETA. Verhindert OOM bei vielen parallelen Usern auf einem 40-GB-Container.

🛠 Tech-Stack

  • Frontend: Vite + OpenLayers (Karte) + Three.js (3D-Vorschau) + Vanilla-JS für UI
  • Backend: Node.js 24 (Express, Worker-Threads, native-Modules better-sqlite3 + bcrypt)
  • GDB-Parsing: GDAL 3.11 mit OpenFileGDB-Treiber (apk add gdal gdal-tools im Alpine-Container)
  • Persistence: SQLite für User-Accounts + Saved-Selections + User-Settings
  • GPU-Compute: Externer NVIDIA-Service via REST-API (RTX PRO 6000, 96 GB)
  • Deployment: Docker Compose hinter nginx-Reverse-Proxy

🔗 Auswahl teilen

Mit diesem Link kann jeder ohne Account deine Auswahl öffnen.