Anleitung

Vektorkacheln in OpenLayers

OpenLayers zeichnet Vektorkacheln selbst, und das Paket ol-mapbox-style macht aus einem MapLibre-Stil OpenLayers-Layer. Mit Script-Tags und mit Vite, Marker und GeoJSON darüber, welche unserer Stile OpenLayers zeichnet, und MapLibre als OpenLayers-Layer für die übrigen. Getestet mit OpenLayers 10.11.0 und ol-mapbox-style 13.9.0.

Mit Script-Tags, OpenLayers und ol-mapbox-style:
<link rel="stylesheet" href="https://unpkg.com/ol@10.11.0/ol.css">
<script src="https://unpkg.com/ol@10.11.0/dist/ol.js"></script>
<script src="https://unpkg.com/ol-mapbox-style@13.9.0/dist/olms.js"></script>

<style>#map { height: 480px; }</style>
<div id="map"></div>
<script>
const base = new ol.layer.Group();
olms.apply(base, "https://api.overpass.ch/v1/IHR_SCHLÜSSEL/maps/styles/light.json?lang=de");

const map = new ol.Map({
  target: "map",
  layers: [base],
  view: new ol.View({ center: ol.proj.fromLonLat([8.54, 47.37]), zoom: 13 }),
  controls: ol.control.defaults.defaults({ attributionOptions: { collapsible: false } })
});
</script>
Gehe zu:
Die Karte wird geladen…
OpenLayers 10.11 mit ol-mapbox-style und dem Code dieser Anleitung. Klicken Sie auf einen roten Punkt.

Mit Script-Tags

Der vollständige Build von OpenLayers, dist/ol.js, legt die globale Variable ol an, und dist/olms.js von ol-mapbox-style die Variable olms. olms.apply lädt den Stil und füllt die Layer-Gruppe mit OpenLayers-Layern für seine Quellen und Layer. Legen Sie die Grundkarte in eine Layer-Gruppe: Layer, die Sie später zur Karte hinzufügen, bleiben dann darüber.

OpenLayers zählt Zoomstufen in Kacheln von 256 Pixeln, wie Leaflet. Zoomstufe 13 in OpenLayers zeigt deshalb dasselbe Gebiet wie Zoomstufe 12 in MapLibre. Die Ansicht rechnet in Web-Mercator-Koordinaten; ol.proj.fromLonLat rechnet Länge und Breite um.

Lassen Sie die Klassen in einem klassischen Script im Namensraum ol:

const { Map, View } = ol; // die Karte bleibt leer

Ein const auf der obersten Ebene eines klassischen Scripts gilt für alle Scripts der Seite. Dieses verdeckt das Map des Browsers, das ol-mapbox-style braucht, und die Konsole zeigt «TypeError: seen.has is not a function». Schreiben Sie ol.Map, oder setzen Sie Ihren Code in eine Funktion oder ein Modul.

Marker, Popups und GeoJSON

Marker sind Objekte in einem Vektor-Layer, ein Popup ist ein Overlay, und GeoJSON lädt in eine Vektorquelle. Fügen Sie sie nach der Grundkarte hinzu:

<style>
  .popup { background: #fff; padding: 4px 8px; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,.3); }
</style>
<div id="popup" class="popup"></div>
<script>
// nach dem Code der Karte oben
const red = new ol.style.Style({
  image: new ol.style.Circle({
    radius: 7,
    fill: new ol.style.Fill({ color: "#d0312d" }),
    stroke: new ol.style.Stroke({ color: "#fff", width: 2 })
  })
});
const places = new ol.layer.Vector({
  source: new ol.source.Vector({
    features: [
      new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([8.5402, 47.3782])), name: "Zürich HB" })
    ]
  }),
  style: red
});
const shops = new ol.layer.Vector({
  source: new ol.source.Vector({ url: "shops.geojson", format: new ol.format.GeoJSON() }),
  style: red
});
map.addLayer(places);
map.addLayer(shops);

const popup = new ol.Overlay({ element: document.getElementById("popup"), positioning: "bottom-center", offset: [0, -12] });
map.addOverlay(popup);
map.on("click", (e) => {
  const f = map.forEachFeatureAtPixel(e.pixel, (f) => f, { layerFilter: (l) => l === places || l === shops });
  popup.setPosition(f ? f.getGeometry().getCoordinates() : undefined);
  if (f) popup.getElement().textContent = f.get("name");
});
</script>
  • ol.format.GeoJSON liest Länge und Breite und rechnet sie in die Projektion der Karte um.
  • Auch die Grundkarte hat Objekte. Ohne den Layer-Filter findet ein Klick auf eine Strasse eines davon, und der Code bricht mit «TypeError: f.getGeometry(...).getCoordinates is not a function» ab. Der Filter erhält jeden Layer in der Gruppe einzeln, nie die Gruppe selbst; nennen Sie darum die Layer, die Sie wollen.

Mit npm und Vite

npm install ol ol-mapbox-style @fontsource/noto-sans

Das lief in einem Build mit Vite 8.3:

import "ol/ol.css";
import "@fontsource/noto-sans/400.css";
import "@fontsource/noto-sans/500.css";
import "@fontsource/noto-sans/400-italic.css";
import Map from "ol/Map.js";
import View from "ol/View.js";
import LayerGroup from "ol/layer/Group.js";
import { fromLonLat } from "ol/proj.js";
import { defaults as defaultControls } from "ol/control/defaults.js";
import { apply } from "ol-mapbox-style";

const KEY = import.meta.env.VITE_OVERPASS_KEY;

const base = new LayerGroup();
apply(base, `https://api.overpass.ch/v1/${KEY}/maps/styles/light.json?lang=de`);

const map = new Map({
  target: "map",
  layers: [base],
  view: new View({ center: fromLonLat([8.54, 47.37]), zoom: 13 }),
  controls: defaultControls({ attributionOptions: { collapsible: false } }),
});

Vite liest VITE_OVERPASS_KEY aus einer Datei .env neben package.json. Der Schlüssel landet trotzdem im JavaScript der Seite; beschränken Sie ihn deshalb auf Ihre Website (unten). In einem Modul ist Map nur ein Name dieses Moduls, das Problem mit const { Map } tritt dort nicht auf.

Schriften

OpenLayers zeichnet die Beschriftung mit den Schriften des Browsers. Unsere Stile verwenden Noto Sans in drei Schnitten: normal, medium und kursiv. Hat die Seite sie nicht, lädt ol-mapbox-style sie von cdn.jsdelivr.net, in unseren Tests 18 bis 27 Anfragen bei der ersten Karte.

Damit die Browser Ihrer Besucher diesen Server nicht kontaktieren, laden Sie die Schrift vor der Karte. Mit Vite erledigen das die drei Importe von @fontsource/noto-sans oben. Mit Script-Tags kopieren Sie 400.css, 500.css, 400-italic.css und den Ordner files aus dem npm-Paket @fontsource/noto-sans auf Ihren Server und geben ol-mapbox-style die Adresse an:

olms.apply(base, "https://api.overpass.ch/v1/IHR_SCHLÜSSEL/maps/styles/light.json?lang=de", {
  webfonts: "/fonts/{font-family}/{fontweight}{-fontstyle}.css"
});

ol-mapbox-style setzt noto-sans, 400 oder 500 und -italic ein; die Dateien gehören also nach /fonts/noto-sans/400.css und so weiter.

Welche Stile OpenLayers zeichnet

StilMit ol-mapbox-style
light, dark, white, grayscale, blackJa
outdoorJa, mit Wanderwegen nach Schwierigkeit, Hütten, Gipfeln, Höhenlinien und Schummerung
drivingMit schmalen Strassen: Es fehlen die Ränder, die sie breit machen
marineDas Meer in einem Blau, ohne die Farben nach Tiefe; Seezeichen und Tiefenlinien wie in MapLibre
reliefNur graue Schummerung, ohne die Farben nach Höhe

ol-mapbox-style 13.9 fehlen drei Teile der MapLibre-Stilspezifikation, die unsere Stile verwenden:

  • Layer vom Typ color-relief, die das Land nach Höhe und das Meer nach Tiefe färben. Die Konsole zeigt «layers[2].type "color-relief" not supported».
  • line-gap-width, das die Ränder der Strassen zeichnet.
  • text-variable-anchor; manche Beschriftungen liegen darum auf ihrem Symbol.

Beschriftungen in anderen Sprachen funktionieren mit ?lang=de, und &hillshade=1 fügt eine Schummerung hinzu, wie in MapLibre.

Alle neun Stile: MapLibre als Layer

Damit Relief, Marine und Driving so aussehen wie in MapLibre, lassen Sie MapLibre die Grundkarte in OpenLayers zeichnen. Das Paket @geoblocks/ol-maplibre-layer setzt eine MapLibre-Karte in einen OpenLayers-Layer; Ihre OpenLayers-Layer, Overlays und Bedienelemente bleiben, wie sie sind. Es braucht einen Bundler. Das lief in einem Build mit Vite 8.3 und MapLibre 6.16.0:

npm install ol @geoblocks/ol-maplibre-layer maplibre-gl
import "ol/ol.css";
import "maplibre-gl/dist/maplibre-gl.css";
import Map from "ol/Map.js";
import View from "ol/View.js";
import { fromLonLat } from "ol/proj.js";
import { defaults as defaultControls } from "ol/control/defaults.js";
import { MapLibreLayer } from "@geoblocks/ol-maplibre-layer";
import { setWorkerUrl } from "maplibre-gl";
import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";

setWorkerUrl(workerUrl);

const KEY = import.meta.env.VITE_OVERPASS_KEY;

const base = new MapLibreLayer({
  mapLibreOptions: { style: `https://api.overpass.ch/v1/${KEY}/maps/styles/relief.json?lang=de` },
});

const map = new Map({
  target: "map",
  layers: [base],
  view: new View({ center: fromLonLat([7.62, 46.12]), zoom: 10.6 }),
  controls: defaultControls({ attributionOptions: { collapsible: false } }),
});
  • MapLibre 6 braucht setWorkerUrl mit jedem Bundler; für webpack siehe die Anleitung zum Umstieg auf MapLibre.
  • Die MapLibre-Karte ist base.mapLibreMap, sobald der Layer auf einer Karte liegt. Das README von Version 2.0.0 nennt sie maplibreMap, das ist undefined. Der Layer löst load aus, wenn der Stil geladen ist.
  • MapLibre zeichnet die Beschriftung mit den Schriften der API; der Abschnitt über Schriften oben gilt hier nicht.

Downloadgrössen

VarianteJavaScript, gzip
Script-Tags: ol.js und olms.js290 KB + 40 KB
Vite: OpenLayers und ol-mapbox-style, der Code oben151 KB
Vite: OpenLayers, ol-maplibre-layer und MapLibre332 KB, und 143 KB für den Worker von MapLibre

Vite nimmt nur die Teile von OpenLayers, die Sie importieren; der vollständige Build enthält alle.

Ihr Schlüssel und was zählt

  • Ihr Schlüssel ist in der Seite sichtbar. Erstellen Sie in Ihrem Dashboard einen Schlüssel vom Typ «Nur Karten», und tragen Sie Ihre Website als erlaubten Referer ein, dazu localhost:5173 für den Entwicklungsserver von Vite.
  • Jede Vektorkachel zählt als eine Kartenkachel, wie bei MapLibre. Jedes Konto enthält 100'000 Kartenkacheln im Monat.
  • collapsible: false hält die Quellenangabe für OpenStreetMap in der Ecke der Karte sichtbar, wie es die Lizenz verlangt. Ohne die Option klappt OpenLayers die Quellenangaben in eine Schaltfläche «i» ein.

Grenzen

  • OpenLayers kann die Karte nicht neigen, terrain=1 und 3D-Gebäude nützen hier also nichts. Drehen geht: mit gedrückten Tasten Alt und Shift ziehen, oder mit zwei Fingern.
  • OpenLayers platziert Beschriftungen auf seine eigene Art und zeigt weniger Strassennamen als MapLibre.
  • Dieselbe Karte in Leaflet: Vektorkacheln in Leaflet; wie sich die Bibliotheken unterscheiden: MapLibre vs. Leaflet vs. OpenLayers.