← Alle Praxisartikel

Aktuell freie Kapazitäten — Projektstart ab sofort möglich.

OpenStreetMap-Karte selbst hosten: Ausschnitt laden, einfärben, mit Leaflet einbinden

Eine Karte auf Ihrer Website muss keine Daten an Dritte senden. Dieser Artikel zeigt, wie Sie einen Ausschnitt aus OpenStreetMap-Daten herunterladen, auf dem eigenen Server ablegen und mit Leaflet in den Farben Ihrer Seite zeichnen.

Von · Cloud-Entwicklung · Stand · Lesezeit ca. 8 Minuten

Vektordaten ausschneiden, selbst hosten und im Browser zeichnen

Eine OpenStreetMap-Karte lässt sich vollständig selbst hosten, wenn Sie nicht die fertigen Bildkacheln kopieren, sondern einen kleinen Ausschnitt der Vektordaten. Der Weg hat vier Schritte: Mit pmtiles extract ziehen Sie einen Ausschnitt aus der Protomaps-Basiskarte in eine einzige Datei. Die Datei legen Sie auf Ihren eigenen Server, der Byte-Bereiche ausliefern muss. Im Browser zeichnet protomaps-leaflet die Daten in den Farben Ihrer Seite. Leaflet bindet das Ganze ein, ohne ein CDN.

Das Ergebnis sehen Sie als Karte auf meiner Kontaktseite: eine verschiebbare Karte um meine Anschrift in Hamburg-Schnelsen, in Grautönen der Seite, hell und dunkel. Die Seite stellt dafür keine einzige Anfrage an einen fremden Server. Kein Dritter erhält also die IP-Adresse eines Besuchers. Die Kartendatei ist 2,7 MB groß, der Browser lädt davon nur die Stücke, die gerade sichtbar sind.

Dieser Artikel richtet sich an Entwicklerinnen und Entwickler, die eine Karte auf einer Firmenseite brauchen und keine Daten an Dritte senden wollen. Er zeigt die Befehle und den Code, die ich dafür verwendet habe, und zeigt auf, wo die Grenzen liegen.

Warum nicht einfach die Kacheln von OpenStreetMap?

Die übliche Leaflet-Einbindung lädt Bildkacheln live von tile.openstreetmap.org. Das ist in zehn Zeilen erledigt, hat aber zwei Haken.

Erstens gehen Daten Ihrer Besucher an einen Dritten. Jede Kachelanfrage überträgt die IP-Adresse des Besuchers an die Server der OpenStreetMap Foundation. Eine IP-Adresse ist für den Betreiber einer Website ein personenbezogenes Datum, sobald er über Mittel verfügt, den Nutzer mit Hilfe Dritter zu identifizieren. So hat es der Europäische Gerichtshof 2016 in der Rechtssache Breyer entschieden. EuGH, Pressemitteilung zum Urteil C‑582/14 (PDF). Sie brauchen also eine Rechtsgrundlage und einen entsprechenden Abschnitt in der Datenschutzerklärung.

Zweitens dürfen Sie die Kacheln nicht auf Vorrat herunterladen. Die naheliegende Lösung, alle Kacheln eines Ausschnitts für mehrere Zoomstufen einmal zu laden und auf den eigenen Server zu kopieren, verbietet die Kachel-Richtlinie der OpenStreetMap Foundation. Sie zählt zum verbotenen Massendownload jedes vorausschauende Laden von Kacheln, die ein Nutzer nicht gerade ansieht, ausdrücklich auch das Anlegen von Kachelarchiven. Solche Aktivitäten werden ohne Vorwarnung gesperrt.

Die Kachelserver sind von der Gemeinschaft finanziert und für die Darstellung auf openstreetmap.org gedacht. Die Daten dahinter sind dagegen frei: Sie stehen unter der Open Database License (ODbL) und dürfen kopiert, verändert und weitergegeben werden, solange Sie OpenStreetMap nennen. Dieser Artikel beschreibt, wie Sie die Daten herunterladen und für Ihre Website aufbereiten können.

Schritt 1: Den Ausschnitt herunterladen

Protomaps stellt täglich eine Basiskarte aus OpenStreetMap-Daten als eine Datei im Format PMTiles bereit. Die vollständige Weltkarte umfasst die Zoomstufen 0 bis 15 und ist rund 120 GB groß. Sie brauchen davon nur ein kleines Rechteck. Protomaps empfiehlt selbst, die Daten nicht von den Build-Servern zu verlinken, sondern auf den eigenen Speicher zu kopieren, weil sich die Adressen ändern können. Protomaps: Basemap Downloads.

Das Werkzeug dafür ist pmtiles, ein einzelnes Programm ohne weitere Abhängigkeiten. pmtiles kann für für alle gängigen Betriebssysteme [heruntergeladen werdenhttps://docs.protomaps.com/pmtiles/cli) und steht auch als Docker-Image zur Verfügung. Der Befehl extract erzeugt aus einem großen Archiv ein kleines. Als Quelle reicht die Adresse des Tagesbuilds; das Programm lädt per Range-Request nur die Teile, die in dem von Ihnen spezifizierten Kartenausschnitt liegen.

Um beispielsweise den auf der CODEGY Kontaktseite dargestellten Kartenausschnitt zu erzeugen, kann folgender Befehl verwendet werden:

pmtiles extract https://build.protomaps.com/20261019.pmtiles \
  static/karte/hamburg-schnelsen-20261019.pmtiles \
  --bbox=9.85,53.60,9.95,53.65 --minzoom=13 --maxzoom=15

Die einzelnen Parameter im Detail:

  • --bbox ist das Rechteck in der Reihenfolge MIN_LON,MIN_LAT,MAX_LON,MAX_LAT, also westlicher Längengrad, südlicher Breitengrad, östlicher Längengrad, nördlicher Breitengrad. In meinem Fall misst das Rechteck etwa 6,6 km mal 5,6 km.
  • --minzoom und --maxzoom begrenzen die Zoomstufen. 15 ist die höchste Stufe der Protomaps-Basiskarte. Webbrowser können damit sogar 18 Zoomstufen abbilden, weil sie die Vektordaten der Stufe 15 größer zeichnen können.
  • Der Dateiname enthält das Datum des Builds. Ein neuer Ausschnitt bekommt einen neuen Namen, weil Browser und CDN die alte Datei noch im Cache halten können.

Die Datei aus meinem Beispiel ist 2,7 MB groß. Zum Vergleich: Ein Ausschnitt über das ganze Hamburger Stadtgebiet wäre nach demselben Verfahren etwa 11 MB groß. Jede weitere Zoomstufe verdoppelt die Größe ungefähr. Mit pmtiles show datei.pmtiles prüfen Sie verfügbare Zoomstufen, Kartenausschnitt und die in der Datei enthaltenen Metadaten auf Korrektheit.

Die Koordinaten des Rechtecks lesen Sie am einfachsten auf openstreetmap.org ab: Den gewünschten Ausschnitt einstellen, „Export“ wählen, und die vier Werte der Auswahl übernehmen.

Schritt 2: Die Datei selbst hosten

PMTiles ist ein Archivformat für Kartenkacheln in einer einzigen Datei. Der Browser holt sich per HTTP-Range-Request nur den Kopf, dann das Verzeichnis, dann die Kacheln, die er gerade braucht. Es läuft kein Kartenserver (Tileserver); eine statische Datei genügt. Protomaps: PMTiles.

Daraus folgt die einzige harte Anforderung an den Server: Er muss Byte-Ranges ausliefern können. Der Browser sendet dafür den Header Range, der Server antwortet mit Status 206 (Partial Content) und dem Header Content-Range. Dass ein Server das kann, zeigt er mit Accept-Ranges: bytes an. Amazon S3 und CloudFront beherrschen dies, ebenso Cloudflare R2, Google Cloud Storage, nginx und Caddy. Weitere Details hierzu erhalten Sie in der Protomaps Cloud Storage Dokumentation.

Um zu prüfen, ob Ihr Server die erforderlichen HTTP-Header unterstützt, können Sie einen Range-Request via curl absetzen:

curl -sI -r 0-16383 https://example.com/hamburg-schnelsen-20261019.pmtiles

In der Antwort müssen HTTP/2 206, accept-ranges: bytes und content-range: bytes 0-16383/2746513 enthalten sein. Antwortet der Server mit Status 200 und der ganzen Datei, lädt die Karte zwar, aber jede Kachel zieht die komplette Datei nach.

Zwei Stolpersteine aus der Praxis:

  • Der lokale Entwicklungsserver. Viele einfache Vorschauserver beantworten keine Range-Requests. Dann bleibt die Karte lokal leer, obwohl sie auf dem Server läuft. Über entsprechende Hooks lässt sich der Range Suppport ggf. nachrüsten.
  • CORS. Liegt die Kartendatei auf einer anderen Domain als die Seite, muss der Speicher die Methoden GET und HEAD, die Anfrage-Header Range und If-Match sowie den Antwort-Header ETag für Ihre Domain freigeben.

Schritt 3: Die Karte einfärben

Weil der Browser Vektordaten erhält, bestimmt er die Farben selbst. Das ist der eigentliche Gewinn gegenüber Bildkacheln: Die Karte kann aussehen wie der Rest der Seite, und ein Wechsel auf das dunkle Farbschema zeichnet sie sofort um.

Die Farbregeln liefert das Paket @protomaps/basemaps. Es enthält fünf fertige Farbschemata, dort „Flavors“ genannt: light, dark, white, grayscale und black. Ein Flavor ist ein einfaches Objekt aus Farbwerten und einigen Eigenschaften wie Schriftnamen. Eigene Farben entstehen, indem Sie ein fertiges Schema kopieren und einzelne Schlüssel überschreiben.

Schritt 4: Leaflet lokal einbinden, ohne CDN

Leaflet lässt sich per npm installieren und in den eigenen Code integrieren. So entfällt der Download von einem CDN beim Öffnen der Website und keinerlei Nutzerdaten werden an Dritte weitergereicht.

npm install leaflet pmtiles protomaps-leaflet @protomaps/basemaps

Die Integration der Karte ist anschließend mit wenigen Zeilen Code erledigt. Das Markup enthält die Karte als figure mit den Daten, die der Code braucht, und den OpenStreetMap Pflichtnachweis:

<figure class="location-map"
        data-lat="53.6256" data-lon="9.89896" data-zoom="15"
        data-min-zoom="14" data-max-zoom="18"
        data-bounds="53.6,9.85,53.65,9.95"
        data-tiles="/karte/hamburg-schnelsen-20261019.pmtiles">
  <img src="/img/karte/hell.webp" alt="Kartenausschnitt: Halstenbeker Straße 60b, 22457 Hamburg" loading="lazy">
  <figcaption>
    <a href="https://leafletjs.com/">Leaflet</a> ·
    © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende
  </figcaption>
</figure>

Das hinterlegte Bild dient dabei als Fallback für Besucher, die JavaScript deaktiviert haben.

Damit die Karte erst dann lädt, wenn die figure in die Nähe des sichtbaren Bereichs kommt, können Leaflet und der Vektorrenderer nachgeladen werden:

const observer = new IntersectionObserver((entries) => {
  if (!entries.some((entry) => entry.isIntersecting)) return;
  observer.disconnect();
  import("./location-map-leaflet.ts")
    .then(({ mountLocationMap }) => mountLocationMap(figure))
    .catch(() => { /* Fallback-Bild bleibt stehen */ });
}, { rootMargin: "400px 0px" });
observer.observe(figure);

Und das Herzstück, gekürzt:

import * as L from "leaflet";
import { PMTiles } from "pmtiles";

export async function mountLocationMap(figure: HTMLElement) {
  const data = figure.dataset;
  const [south, west, north, east] = data.bounds!.split(",").map(Number);
  const canvas = document.createElement("div");
  figure.prepend(canvas);

  const map = L.map(canvas, {
    center: [Number(data.lat), Number(data.lon)],
    zoom: Number(data.zoom),
    minZoom: Number(data.minZoom),
    maxZoom: Number(data.maxZoom),
    maxBounds: L.latLngBounds([south, west], [north, east]),
    maxBoundsViscosity: 1,
    scrollWheelZoom: false,
  });

  const tiles = new PMTiles(data.tiles!);
  vectorLayer(figure, tiles).addTo(map);
}

maxBounds hält die Karte im Rechteck des Ausschnitts. Ohne diese Grenze könnte ein Besucher aus dem Ausschnitt rausscrollen und sähe dort nur die Hintergrundfarbe. scrollWheelZoom: false verhindert die “Scroll-Falle”: Das Mausrad scrollt die Seite weiter, gezoomt wird über Knöpfe und Doppelklick. Auf dem Telefon scrollt ein Finger die Seite und zwei Finger zoomen.

Die Lizenz: Namensnennung ist Pflicht

Die Protomaps-Basiskarte ist ein aus OpenStreetMap-Daten erzeugtes Werk unter der ODbL. Die Namensnennung von OpenStreetMap ist verpflichtend. Die Richtlinien der OpenStreetMap Foundation verlangen, dass der Nachweis für jeden sichtbar ist, der die Karte sieht, ohne dass er dafür mit der Karte interagieren muss.

Leaflet steht unter der BSD-2-Clause-Lizenz, protomaps-leaflet unter der BSD-3-Clause-Lizenz. Beide verlangen, dass Sie den Lizenztext mitliefern, etwa in der Lizenzdatei Ihres Builds.

Grenzen

Unter bestimmten Bedingungen kann ein anderer Weg sinnvoller sein, als der geschilderte:

  • Fester Ausschnitt. Die Karte zeigt nur das Rechteck, das Sie extrahiert haben. Für eine frei scrollbare Deutschlandkarte wächst die Datei auf viele Gigabyte. Dies ist zwar keine Limitierung im eigentlichen Sinn, aber es macht dann Sinn, einen Storage Service wie AWS S3 einzusetzen.
  • Eingefrorener Datenstand. Der Ausschnitt ist so aktuell wie der Tagesbuild, aus dem er stammt. Eine neue Straße erscheint erst, wenn Sie den Befehl erneut ausführen und die Datei ersetzen.
  • protomaps-leaflet wird nur noch gepflegt, nicht weiterentwickelt. Das Projekt empfiehlt es für bestehende Leaflet-Systeme und verweist neue Projekte auf MapLibre GL JS. protomaps-leaflet: README. Für eine Karte mit Pin ist das in Ordnung. Wer viele Karten oder gedrehte und geneigte Ansichten braucht, sollte gleich mit MapLibre beginnen.
  • Keine Suche, kein Routing. Ein Vektorausschnitt zeigt eine Karte. Adresssuche, Routenplanung oder Geocoding sind eigene Dienste mit eigenen Datenschutzfragen.

Nächster Schritt

Soll Ihre Website oder Anwendung ohne fremde Server auskommen?

Beschreiben Sie mir im kostenlosen, unverbindlichen 30-minütigen Erstgespräch, welche Bausteine Ihrer Seite oder Anwendung heute an Dritte senden. Ich sage Ihnen, welche sich wie die Karte auf den eigenen Server holen lassen und wo der Aufwand nicht lohnt.

Erstgespräch vereinbaren30 Minuten · kostenlos und unverbindlich