Aus OpenStreetMap gebautes Vektor-Kachel-Overlay
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-10 15:35:11 +02:00
.gitignore update tiles with more features 2026-09-10 15:35:11 +02:00
germany-roads-places.yml update tiles with more features 2026-09-10 15:35:11 +02:00
README.md update tiles with more features 2026-09-10 15:35:11 +02:00

Beschriftungs-Kacheln für die Satellitenkarte (ff-operation)

Selbst gehostetes, aus OpenStreetMap gebautes Vektor-Kachel-Overlay ("nur Straßen + Orts-/Stadtnamen und Gewässer") für die Satellitenansicht ohne Beschriftung. Ohne Drittanbieter-API-Key und ohne dauerhaft laufenden Rendering-Server — nach dem Bauen genügt eine einzelne statische .pmtiles-Datei, ausgeliefert per nginx (HTTP-Range-Requests, kein Rendering zur Laufzeit).

Architektur

  1. Planetiler wandelt einen Geofabrik-OSM-Extrakt anhand des Profils germany-roads-places.yml in eine .pmtiles-Datei um (Vektor-Kacheln, die Layer roads, railways, places und water). Braucht keine Datenbank, läuft als einzelner Docker-Container. maxzoom liegt bei 14 (via args.maxzoom im Profil) — das Frontend überzoomt darüber hinaus clientseitig (maxDataZoom: 14 in MapView.vue), statt Kacheln für nicht vorhandene höhere Zoomstufen anzufragen.
  2. Die fertige .pmtiles-Datei wird auf dem Zielserver per nginx als statische Datei ausgeliefert (nginx unterstützt Range-Requests für statische Dateien nativ).
  3. Im Frontend liest protomaps-leaflet die Datei direkt per HTTP-Range-Requests und rendert sie als Canvas-Ebene über der Esri-Satellitenkarte — kein separater Kachel-Server, kein PostGIS.

Build ausführen

Auf einem Rechner/Server mit Docker, ausreichend Speicher (Faustregel: bei einem Deutschland-Extrakt sind ca. 8-16 GB RAM und ein paar Dutzend GB freier Plattenplatz realistisch) und einer stabilen Internetverbindung (der Geofabrik-Extrakt für Deutschland ist mehrere GB groß):

mkdir -p data
cp germany-roads-places.yml data/schema.yml

docker run -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:latest \
  generate-custom --schema=/data/schema.yml --output=/data/germany-roads-places.pmtiles

Das lädt automatisch den aktuellen Geofabrik-Deutschland-Extrakt herunter (per geofabrik:germany im Profil) und erzeugt data/germany-roads-places.pmtiles. Je nach Hardware kann das von einigen Minuten bis über eine Stunde dauern.

Aktualisierung: OSM-Daten ändern sich laufend (neue Straßen, umbenannte Orte). Der Befehl oben kann einfach erneut ausgeführt werden (lädt automatisch einen frischen Extrakt) — z. B. per Cronjob alle paar Wochen/Monate, je nach gewünschter Aktualität. Die neue Datei überschreibt dann einfach die alte auf dem Hosting-Server.

Build unter Windows

Läuft grundsätzlich genauso, ein paar Punkte vorher prüfen:

  • Docker Desktop mit WSL2-Backend (nicht Hyper-V) — deutlich schneller bei den vielen kleinen Dateizugriffen, die Planetiler beim Verarbeiten des Extrakts macht. Einstellung unter Docker Desktop → Settings → General → "Use the WSL 2 based engine".
  • Speicher-/CPU-Limit von Docker Desktop erhöhen: Docker Desktop deckelt der VM standardmäßig einen Teil des Host-Speichers, oft weniger als die empfohlenen 8-16 GB. Unter Settings → Resources (bzw. bei WSL2-Backend über %UserProfile%\.wslconfig, z. B. memory=16GB, processors=8) passend hochsetzen und Docker Desktop neu starten.
  • Befehl in einer WSL2-Shell ausführen (z. B. Ubuntu unter WSL), nicht in PowerShell oder Git Bash — der Befehl oben ist Bash-Syntax ($(pwd)), und WSL2 übergibt Pfade beim Volume-Mount 1:1 an Docker, ohne die Windows→Linux-Pfadübersetzung, die in PowerShell (${PWD} statt $(pwd)) bzw. Git Bash (bekannte MSYS-Pfad-Stolperfallen bei -v) sonst nötig wäre.
  • Genug freier Platz auf dem Laufwerk, auf dem WSL2/Docker Desktop seine virtuelle Festplatte hält (meist C:, unabhängig davon, wo data/ liegt) — mehrere Dutzend GB, siehe oben.
  • Energiesparen/Ruhezustand deaktivieren, falls auf einem Laptop gebaut wird — der Build kann je nach Hardware über eine Stunde dauern, ein zwischenzeitlicher Sleep bricht ihn ab.
  • Optional, nur für Geschwindigkeit: den WSL2-/Docker-Arbeitsordner von der Windows-Defender-Echtzeitprüfung ausnehmen (viele kleine Dateien, häufiger Scan-Overhead) — kein Muss, aber spürbar schneller.

Alternative ohne Docker (direkt mit Java)

Planetiler ist im Kern ein Java-Programm — Docker ist nur eine bequeme Verpackung davon. Ohne Docker geht es auf Windows sogar einfacher, weil die ganze WSL2-/Pfadübersetzungs-Thematik oben komplett entfällt (kein Container, kein Volume-Mount, der Build schreibt direkt ins Windows-Dateisystem):

  1. Java 21 oder neuer installieren (JDK, nicht nur JRE), z. B. per winget:
    winget install EclipseAdoptium.Temurin.21.JDK
    
  2. planetiler.jar herunterladen von der Releases-Seite (direkter Link: .../releases/latest/download/planetiler.jar).
  3. Build starten (PowerShell, im Ordner mit planetiler.jar und germany-roads-places.yml):
    java -Xmx12g -jar planetiler.jar generate-custom --schema=germany-roads-places.yml --output=germany-roads-places.pmtiles
    
    -Xmx12g reserviert dem Build 12 GB RAM — an den tatsächlich verfügbaren Hauptspeicher anpassen (Faustregel: Gesamt-RAM minus 2-4 GB für Windows selbst). Lädt wie beim Docker-Weg automatisch den Geofabrik-Deutschland-Extrakt herunter (steht bereits im Profil).

Zu beachten bleibt: genug freier Plattenplatz auf dem Laufwerk, auf dem der Befehl ausgeführt wird, Energiesparen/Ruhezustand deaktivieren bei langer Laufzeit. Die Windows-Defender-Ausnahme ist hier sogar eher relevanter als beim Docker/WSL2-Weg, da die Zwischendateien jetzt direkt im normalen (von Defender in Echtzeit überwachten) Windows-Dateisystem statt im von Defender standardmäßig ausgenommenen WSL2-Dateisystem liegen.

Über einen CI-Runner (Forgejo Actions)

Automatisiert statt manuell, z. B. für die regelmäßige Aktualisierung. Wichtig dabei: die Pipeline baut und lädt direkt hoch statt das Ergebnis als Forgejo-Artefakt oder Registry-Image zu speichern — sonst sammelt sich bei jedem Lauf ein mehrere GB großer Blob an, ohne sinnvolles Layer-Diffing (die Datei ändert sich bei jedem Build komplett). So bleibt nie mehr als eine Kopie im Umlauf: die aktuell auf dem Tile-Server liegende.

Voraussetzung ist ein eigener (self-hosted) Forgejo-Runner mit Docker-Zugriff und genug RAM/Plattenplatz (siehe oben) — Forgejo Actions kennt keine geteilten Cloud-Runner wie GitLab.com, der Runner muss also entsprechend dimensioniert selbst betrieben werden.

.forgejo/workflows/build-tiles.yml:

name: Build & Deploy Map Tiles

on:
  workflow_dispatch: {} # manuell anstoßbar
  schedule:
    - cron: "0 3 1 * *" # zusätzlich automatisch monatlich am 1. um 03:00 Uhr

jobs:
  build:
    runs-on: self-hosted # eigener Runner mit ausreichend RAM/Platz + Docker
    steps:
      - uses: actions/checkout@v4

      - name: .pmtiles mit Planetiler bauen
        run: |
          docker run --rm -v "$PWD":/data ghcr.io/onthegomap/planetiler:latest \
            generate-custom --schema=/data/germany-roads-places.yml --output=/data/germany-roads-places.pmtiles

      - name: SSH-Key für den Upload einrichten
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.TILES_DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan tiles.ff-admin.de >> ~/.ssh/known_hosts

      - name: Auf den Tile-Server hochladen (überschreibt die bestehende Datei)
        run: |
          rsync -avz germany-roads-places.pmtiles deploy@tiles.ff-admin.de:/var/www/tiles/germany-roads-places.pmtiles

TILES_DEPLOY_SSH_KEY als Forgejo-Actions-Secret hinterlegen (Repo → Settings → Actions → Secrets) — der private SSH-Key eines Deploy-Users auf dem Tile-Server, der nur Schreibrechte auf /var/www/tiles/ braucht. Kein docker push, kein actions/upload-artifact — das Ergebnis verlässt den Runner ausschließlich per rsync direkt zum Zielserver.

Hosting (Docker + nginx)

Die .pmtiles-Datei wird zentral unter tiles.ff-admin.de gehostet und von jeder ff-operation-Installation gemeinsam genutzt, unabhängig von deren eigener Domain — kein Selbstbau oder eigenes Hosting pro Kunde/Deployment nötig. Weil dadurch im Voraus nicht feststeht, von welcher Origin aus zugegriffen wird, ist Access-Control-Allow-Origin bewusst offen (*) statt auf eine einzelne Frontend-Domain beschränkt — unproblematisch, weil es eine rein öffentliche, statische Datei ohne Zugriffsschutz/Credentials ist.

docker-compose.yml:

services:
  tiles:
    image: nginx:alpine
    container_name: ff-operation-tiles
    restart: unless-stopped
    ports:
      - "8080:80" # dahinter ein vorhandener Reverse Proxy für TLS auf tiles.ff-admin.de
    volumes:
      - ./data:/usr/share/nginx/html:ro # enthält germany-roads-places.pmtiles
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

nginx.conf (daneben liegend, wird oben ins Image gemountet):

server {
    listen 80;
    server_name tiles.ff-admin.de;
    root /usr/share/nginx/html;

    location ~ \.pmtiles$ {
        # nginx unterstützt Range-Requests für statische Dateien automatisch (sendfile) -
        # keine zusätzliche Konfiguration dafür nötig, nur die CORS-Header für den
        # Cross-Origin-Zugriff von beliebigen ff-operation-Domains aus.
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Range" always;
        add_header Access-Control-Expose-Headers "Content-Length, Content-Range, Accept-Ranges" always;
        add_header Cache-Control "public, max-age=86400" always;

        # Manche Browser schicken vor der eigentlichen Range-Anfrage eine CORS-Preflight-
        # OPTIONS-Anfrage - die hier direkt beantworten, statt sie durchfallen zu lassen.
        if ($request_method = OPTIONS) {
            return 204;
        }
    }
}

Start/Update:

docker compose up -d          # einmalig starten
docker compose restart tiles  # nach einer neuen .pmtiles-Datei in ./data (kein Rebuild nötig,
                               # nginx liefert einfach die neue Datei; restart nur falls die
                               # alte noch im Cache eines Clients/Proxys hängt)

Der Container hört auf Port 8080 im internen Netz - TLS-Terminierung für tiles.ff-admin.de übernimmt der bereits vorhandene Reverse Proxy des Servers (dorthin 8080 weiterleiten), analog zu den anderen *.ff-admin.de-Diensten.

Frontend-Anbindung

Die URL ist fest in ff-operation unter src/constants/mapTypes.ts (Konstante LABEL_TILES_URL) hinterlegt — keine Umgebungsvariable, keine Pro-Deployment-Konfiguration, da die Datei zentral für alle Installationen gehostet wird. MapView.vue rendert sie via protomaps-leaflet (Styling in src/constants/labelTileStyle.ts). Ändert sich Dateiname oder Hosting-URL, muss nur diese eine Konstante angepasst werden.

Bekannte Einschränkung

Das Beschriftungs-Overlay ist nicht Teil des bestehenden Offline-Kachel-Caches (IndexedDB, "Bereich offline verfügbar machen") — der PMTiles-Reader macht sein eigenes HTTP-Range-Caching, das aber nicht dauerhaft/explizit für Offline-Nutzung vorgesorgt ist. Für echte Offline-Nutzung der Beschriftung wäre ein zusätzlicher Schritt nötig (z. B. die gesamte .pmtiles-Datei oder einen Ausschnitt davon in den Service Worker/Cache Storage vorladen) — bislang nicht umgesetzt, da noch nicht angefragt.

Upload

scp germany-roads-places.pmtiles USER@tiles.ff-admin.de:/pfad/zu/ff-operation-tiles/data/ rsync -avz --progress germany-roads-places.pmtiles USER@tiles.ff-admin.de:/pfad/zu/ff-operation-tiles/data/