Zum Hauptinhalt springen

Karten mit PMTiles

·1067 Wörter·6 min
Alpine

Schon als ich das erste Mal von PMTiles gehört hatte war ich fasziniert: Ich kann Millionen von Karten-Kacheln in einer Datei speichern und brauche nur einen regulären Webserver um die Karten-Ebenen zum Web-Browser zu liefern. Für alle, für die das jetzt ziemlich abgespaced klingt: Das Ding rockt!

Ich habe mich schon früher mit Karten im Web und in Apps beschäftigt und so meine Kämpfe ausgefochten. Seit 2012 habe ich eigene Karten-Kacheln erstellt und zum Download für die snowhow-freeride-app angeboten, oder in verschiedenen Online Karten verwendet (zum Beispiel: https://snowhow.info/map). Dabei traten unterschiedliche Probleme und Herausforderungen auf, wie etwa den Tile-Cache teilweise zu aktualisieren, oder das Tile-Format zu konvertieren.

snowhow map
Die snowhow-Freeride-Karte, mit lokal vorgehaltenen PNG-Kacheln bis Zoomstufe 18

Meine Bestrebungen beschränkten sich dabei auf den Alpenraum (innerhalb der von der Alpenkonvention festgelegten Grenzen). Der Tile-Cache, also die im Voraus berechneten PNG-Dateien waren ziemlich schwierig zu verwalten, immerhin hatten wir bis zur Zoom-Stufe 18 alle Tiles vorrätig. An einen Tile-Cache der gesamten Welt war gar nicht zu denken: Grobe Schätzungen gehen bei einer Zoom-Stufe von 16 (nicht 18!) von 5,7 Milliarden Kacheln und 85 TB Speicherplatz aus.

Was macht PMTiles nun so viel besser? Zuerst einmal werden die Milliarden Karten-Kacheln in eine Datei verpackt und können durch HTTP-Range-Requests blitzschnell abgefragt werden. Dazu braucht man nur einen Webserver, keine weitere Konfiguration.

Gerendert wird im Browser mit MapLibre GL JS, dem Open-Source-Fork von Mapbox GL JS. Das PMTiles-SDK klinkt sich dort einfach als eigenes URL-Protokoll ein, sodass MapLibre die Vektor-Kacheln direkt aus der einen Datei liest, ganz ohne Tile-Server dazwischen.

maps.webman.at zeigt alpines Gelände im Ultental in Südtirol
Alpines Gelände im hinteren Ultental, Südtirol

Das war der Ausgangspunkt im Frühjahr: ein weltweiter Vektorkachel-Datensatz aus OpenStreetMap-Daten, direkt als PMTiles aus dem Browser geladen, mit ein paar Kartenstilen zum Durchklicken. Seither ist maps.webman.at deutlich gewachsen – aus der Techdemo ist eine Alpenkarte geworden, die ich inzwischen selbst immer häufiger verwende.

Der “Weltkacheldatensatz” reicht nicht für die Alpen #

Der weltweite PMTiles-Datensatz (von Protomaps, eine Datei mit 126 GB) ist grandios für einen globalen Überblick, aber im Gebirge zeigt er kaum etwas: Gipfel ohne Höhenangabe, fast keine Lifte, Gletscher nur bis Zoomstufe 7, Wanderwege ohne Schwierigkeitsgrad. Genau die Details, wegen denen man in den Bergen überhaupt eine Karte braucht.

Also habe ich Claude Code angewiesen, die fehlenden Layer selbst zu bauen: Aus den rohen OpenStreetMap-Daten, zugeschnitten auf ein Rechteck, das den gesamten Alpenbogen abdeckt (grob 5,5°O–16,5°O, 43,5°N–48,5°N), werden folgende Daten extrahiert.

  • ~554.000 durchsuchbare Orte und POIs – Gipfel, Hütten, Pässe, Seen, Lifte, Unterkünfte, Skirouten, Quellen, Höhlen, Burgen
  • Jeder benannte Gipfel im Alpenraum mit Höhe, sofern OSM sie kennt – gestaffelt nach Zoomstufe, damit bei z11 nur die 2500er auftauchen und erst ab z14 auch der namenlose Hügel um die Ecke
  • ~12.700 Lifte, in vier Kategorien (Seilbahnen, Sessellifte, Schlepplifte, Materialseilbahnen) einzeln zu- und abschaltbar
  • ~3.300 Gletscherflächen, weil die Weltkarte sie ab Zoomstufe 8 einfach nicht mehr zeigt
  • ~397.000 graded Wanderwege und Klettersteige, eingefärbt nach SAC-Schwierigkeitsskala (T1–T6) statt einheitlich als graue Linie

Eine direkte Overpass-Abfrage für so eine Fläche ist keine Option – ein gleich großer Testausschnitt anderswo in den Alpen brachte mehr passende Wanderweg-Features zurück, als der ganze Tirol-Extrakt vorher hatte, weit jenseits dessen, was die öffentliche Overpass-API an Fair-Use verträgt. Die Pipeline lädt deshalb einmal die Geofabrik-Regionalextrakte für den gesamten Alpenraum herunter, schneidet sie mit osmium extract auf die Bounding-Box zu, merged sie zu einem ~3,3-GB-PBF und filtert daraus mit osmium tags-filter/osmium export – lokal, gecacht, ohne fremde API zu strapazieren. Jeder der Layer hat sein eigenes Build-Skript (build-peaks.sh, build-lifts.sh, build-glaciers.sh, build-trails.sh), verpackt als eigenes topo-builder-Docker-Image, das ich bei Bedarf neu laufen lasse, wenn sich OSM oder die Stile ändern.

Eine eigene Suche statt eines Geocoders #

Ein allgemeiner Geocoder ist für alpine Ziele fast nutzlos: Ein Gipfel oder eine Hütte verliert dort gegen jeden Ort oder jede Straße mit ähnlichem Namen – und genau die alpinen Details sind ja der Grund für diese Karte. Also gibt es jetzt einen eigenen Such-Index, gebaut aus derselben OSM-Extraktion wie die Layer oben: rund 554.000 Features, gewichtet nach Fundqualität, Feature-Art und Distanz zum aktuellen Kartenmittelpunkt.

Bewusst kein Adress-Geocoder – Straßen und Hausnummern hätten den Index locker verdoppelt, für etwas, das die Karte nirgends anzeigt. Auch Geschäfte, Restaurants, Haltestellen oder Parkplätze fehlen absichtlich: Bei einer Testabfrage über denselben Alpenausschnitt lieferten allein diese vier Kategorien 744.000 zusätzliche Treffer – mehr als eine Verdopplung, und sie hätten die Gipfel und Hütten, um die es hier eigentlich geht, im Ranking begraben.

Technisch steckt dahinter PostGIS mit pg_trgm für Trigram-Suche, eine einzige SQL-Rankingfunktion, ausgeliefert über PostgREST und von nginx gecacht. Alternativnamen sind als eigene Zeilen indiziert – Bozen findet also auch Bolzano, Wien auch Vienna – und Umlaute/ß werden in beide Richtungen normalisiert (Großglockner = grossglockner = großglockner).

GPX-Tracks und Fotos direkt im Browser #

Über das Track-Symbol lassen sich GPX-Dateien und geotaggte Fotos laden – per Dialog oder einfach per Drag & Drop auf die Karte. Alles passiert im Browser, es wird nichts hochgeladen. Tracks werden nach Geschwindigkeit eingefärbt, überstehen einen Wechsel des Kartenstils, und jeder zeigt Distanz, Fahrzeit und Durchschnitts-/Höchstgeschwindigkeit. Ausreißer über 50 km/h – GPS-Aussetzer – werden zwar noch gezeichnet, damit die Linie durchgängig bleibt, fließen aber nicht in die Statistik ein.

Fotos mit GPS-Tag im EXIF landen als Thumbnail-Marker auf der Karte, mit Popup für Vollbild, Aufnahmezeit, Kamera und Koordinaten. Und für alle, die schon einen ?config=-Link vom alten webman/gpx-viewer verwenden: Das Format ist unverändert kompatibel.

Drumherum: basemap.at, Deployment #

Für Österreich lässt sich zusätzlich auf basemap.at umschalten – die offizielle Basiskarte der öffentlichen Hand, detaillierter als die Weltkarte, inklusive 30-cm-Orthofoto als Luftbild.

Ausgeliefert werden die Kacheln primär direkt als PMTiles per Range-Request, wie eingangs beschrieben. Für Clients, die das nicht unterstützen, steht ergänzend Martin als klassischer Tile-Server bereit. Dazu nginx für das statische Frontend, PostGIS/PostgREST für die Suche – alles in Docker Compose hinter meinem Traefik-Proxy. Der Quellcode ist auf GitLab öffentlich einsehbar, die CI-Pipeline baut das Image und deployt per SSH auf unseren Server.

Claude Code macht’s möglich #

Der allermeiste Code hier stammt nicht von mir. topo-builder, der Suchindex, das Track-Panel, die CI-Pipeline, große Teile von main.js – das hat Claude Code geschrieben, Anthropics KI-Coding-Agent, über unzählige Sessions in den letzten Monaten. Meine Rolle war die Planung: entscheiden, was als Nächstes gemacht wird, Architektur und Datenquellen festlegen, das Ergebnis in echt ausprobieren und nachjustieren, wenn etwas nicht passt. Von den aktuell 72 Commits im Repo tragen 59 entsprechend einen Co-Authored-By: Claude-Trailer.

Ausprobieren: maps.webman.at – für die volle Werkzeugkiste (Stil-Tabs, Quellen-Umschalter, Layer-Toggles, Feature-Inspector, Pitch-Slider) einfach ?showcase an die URL hängen.