Anleitung

MapLibre: «Unable to parse the tile» und «Unimplemented type: 3»

Die Vektorkacheln kommen im Browser an, aber MapLibre kann sie nicht lesen, und die Karte bleibt leer. Fast immer sind die Kacheln gzip-komprimiert, und der Server sagt es nicht. Wie Sie die Ursachen unterscheiden, und die Lösung für nginx, Apache, Caddy, S3, GitHub Pages und PMTiles.

Die Meldungen

Der Wortlaut hängt von der Version ab. Wir haben jeden Fall in Chromium mit MapLibre GL JS 3.3.0 und 6.13.0 nachgestellt:

Der Server sendetMapLibre 3.4.0 und neuerMapLibre vor 3.4.0, Mapbox GL JS
gzip-Daten ohne Content-Encoding: gzipUnable to parse the tile at …, please make sure the data is not gzipped and that you have configured the relevant header in the serverUnimplemented type: 3
doppelt komprimierte Daten, Header einmal gesendetwie bei gzip obenUnimplemented type: 3
eine HTML-Seite (Fehlerseite, Anmeldeseite, index.html)Unable to parse the tile at …, got error: Unimplemented type: 4Unimplemented type: 4
ein PNG-Bild (eine Rasterkachel)Unable to parse the tile at …, got error: Unimplemented type: 3Unimplemented type: 3

Die Zahl ist der Feldtyp, den der Protocol-Buffer-Decoder in den ersten Bytes gefunden hat; sie deutet also an, was die Bytes sind. Ein Fehlercode des Kachelservers ist sie nicht. Die Meldung wiederholt sich für jede Kachel, eine Karte kann also Dutzende davon auf einmal ausgeben.

Eine Kachel ansehen

Kopieren Sie eine Kachel-URL aus dem Netzwerk-Tab des Browsers, und fragen Sie sie so an wie der Browser:

curl -s -D - -o tile.bin -H 'Accept-Encoding: gzip, br' 'https://tiles.example.com/14/8580/5737.pbf' \
  | grep -i -e '^HTTP' -e content-type -e content-encoding
head -c 4 tile.bin | od -An -tx1
Erste BytesDer Inhalt istDer Header muss sein
1aeine Vektorkachel, unkomprimiertkein Content-Encoding
1f 8bgzipContent-Encoding: gzip, einmal
3c («<»)HTML oder XML: eine Fehler- oder ErsatzseiteURL oder Routing korrigieren
7b («{»)JSON: meist eine Fehlermeldung der APIlesen, sie nennt das Problem
89 50 4e 47ein PNG-Bildeine Quelle vom Typ raster verwenden

Beginnt der Inhalt mit 1f 8b und sagt der Header Content-Encoding: gzip, entpacken Sie ihn einmal (gzip -dc tile.bin | head -c 4 | od -An -tx1). Beginnt er wieder mit 1f 8b, wurde die Kachel doppelt komprimiert.

Warum Kacheln gzip-komprimiert sind

Vektorkacheln lassen sich gut komprimieren, und die meisten Werkzeuge speichern sie deshalb komprimiert: MBTiles-Dateien von tippecanoe und planetiler, PMTiles-Archive und Ordner, die mb-util oder tippecanoe -e schreiben. Ein Kachelserver wie tileserver-gl oder martin weiss das und sendet den Header. Ein einfacher Webserver, ein Bucket oder ein CDN sieht nur Dateien, sendet sie ohne den Header, und der Browser gibt komprimierte Bytes an MapLibre weiter.

Lösung: den Header senden

Jede dieser Einstellungen wurde mit gzip-Kacheln in einem Ordner tiles/ getestet, mit eingeschalteter Kompression für den Rest der Website, und curl bestätigte ein einziges Content-Encoding: gzip und einen Inhalt, der sich zu einer Vektorkachel entpacken lässt:

location /tiles/ {
    gzip off;                          # die Dateien sind schon komprimiert
    add_header Content-Encoding gzip;
    add_header Access-Control-Allow-Origin *;
    types { application/vnd.mapbox-vector-tile pbf mvt; }
}
# add_header in einer location ersetzt die add_header-Zeilen des server-Blocks

Laden Sie die Seite danach mit geleertem Cache neu (oder öffnen Sie ein privates Fenster): Der Browser behält die kaputten Kacheln so lange, wie die alten Cache-Header es erlauben.

Lösung: die Kacheln entpacken

Wo Sie Header nicht pro Pfad setzen können, etwa auf GitHub Pages, speichern Sie die Kacheln unkomprimiert. Auf dem Weg zum Browser sind sie dann grösser, es sei denn, der Host komprimiert sie unterwegs.

# einen Ordner mit gzip-Kacheln an Ort und Stelle entpacken
find tiles -name '*.pbf' -exec sh -c 'gzip -dc "$1" > "$1.tmp" && mv "$1.tmp" "$1"' _ {} \;

# oder sie gleich unkomprimiert schreiben
tippecanoe -e tiles --no-tile-compression -zg data.geojson

Doppelt komprimiert

Sind die Dateien schon gzip und komprimiert der Server oder ein CDN sie noch einmal, entfernt der Browser eine Schicht, und MapLibre erhält die andere. Schalten Sie die Kompression unterwegs für den Pfad der Kacheln aus (gzip off in nginx, SetEnv no-gzip in Apache), und prüfen Sie die Einstellungen des CDN, das den Typ application/x-protobuf womöglich komprimiert.

Eine HTML-Seite statt einer Kachel

«got error: Unimplemented type: 4» und erste Bytes 3c heissen, dass der Server eine Webseite geschickt hat. Häufige Gründe:

  • Eine Single-Page-App liefert für jeden unbekannten Pfad index.html (try_files $uri /index.html, Rewrites auf Netlify oder Vercel); fehlende Kacheln kommen also als App mit Status 200 zurück. Nehmen Sie den Pfad der Kacheln davon aus, und beantworten Sie fehlende Kacheln mit 404 oder 204.
  • Eine Anmeldeseite, eine Fehlerseite eines Proxys oder eine Seite «nicht gefunden» mit Status 200.
  • Eine falsche URL-Vorlage: ein fehlender Teil des Pfads, oder {z}/{y}/{x} in falscher Reihenfolge.

PMTiles

Entpacken Sie ein PMTiles-Archiv nicht in einen Ordner des Webservers, um das zu beheben. Liefern Sie das Archiv als eine Datei mit HTTP-Range-Requests aus, und lassen Sie es von der Bibliothek pmtiles lesen; sie entpackt die Kacheln selbst:

import { Protocol } from "pmtiles";
maplibregl.addProtocol("pmtiles", new Protocol().tile);

// im Stil:
// "sources": { "world": { "type": "vector", "url": "pmtiles://https://example.com/world.pmtiles" } }

Eine Rasterkachel in einer Vektorquelle

Ist die Kachel ein PNG oder JPEG, muss die Quelle im Stil "type": "raster" sein, bei den meisten Rasterdiensten mit "tileSize": 256. Ein Satz Rasterkacheln lässt sich nur als Bilder zeigen; er hat keine Layer, die man gestalten könnte.

Oder lassen Sie jemand anderen die Kacheln liefern

overpass.ch liefert Vektorkacheln der ganzen Welt mit den richtigen Headern, ETags und CORS, in neun fertigen Stilen für MapLibre, mit Beschriftung in 41 Sprachen und 3D-Gelände. 100'000 Kacheln im Monat sind kostenlos. Fehler kommen als JSON mit einem Code zurück.