Tile di Protomaps Basemap

L'intera Protomaps Basemap, dallo zoom 0 al 15, servita come un unico archivio PMTiles su normale HTTP. Punta qualsiasi client PMTiles all'URL e funziona.

https://tiles.jasontally.com/basemap.pmtiles

Una tile OpenStreetMap bloccata o un 403 da tile.openstreetmap.org? Inizia da qui.

Provalo

Lo stesso archivio in tre stili. Cambiando stile, le tile già caricate restano al loro posto: il passaggio è istantaneo.

z0

Crea il tuo stile

Cambia un colore o disattiva un layer e la mappa si aggiorna in tempo reale. Poi copia il JSON dello stile: è un normale stile MapLibre che puoi ospitare per conto tuo. Punta a queste tile, quindi continua a funzionare ovunque lo metti.

Come usarlo

L'archivio supporta le range request HTTP, quindi ogni client PMTiles può leggerlo.

Uno. Mettere una mappa nella pagina

Copia questo file. È l'intero esempio: tre tag, un URL di stile, una mappa. Entrambe le librerie hanno versione fissata e licenza BSD-3-Clause, e gli stessi file sono serviti anche da questo host se preferisci non usare una CDN.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Basemap</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css">
  <script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/pmtiles@3.2.1/dist/pmtiles.js"></script>
  <style>html, body, #map { margin: 0; height: 100%; }</style>
</head>
<body>
  <div id="map"></div>
  <script>
    // Teach MapLibre how to read a PMTiles archive.
    const protocol = new pmtiles.Protocol();
    maplibregl.addProtocol("pmtiles", protocol.tile);

    new maplibregl.Map({
      container: "map",
      style: "https://tiles.jasontally.com/styles/light.json",
      center: [2.35, 48.85],   // Paris
      zoom: 4
    });
  </script>
</body>
</html>

Due. Sostituire le tile in uno stile esistente

Ogni stile MapLibre legge le tile da una sorgente. Per usare queste tile, cambia quell'unico url. Nel resto dello stile non cambia nulla.

  "sources": {
    "protomaps": {
      "type": "vector",
-     "url": "pmtiles://https://demo-bucket.protomaps.com/v4.pmtiles",
+     "url": "pmtiles://https://tiles.jasontally.com/basemap.pmtiles",
      "attribution": "<a href=\"https://www.openstreetmap.org/copyright\">&copy; OpenStreetMap contributors</a>"
    }
  }

Oppure fallo via codice, utile se lo stile proviene da un server che non controlli:

// Rewrite every PMTiles source in a style to point at these tiles.
const style = await (await fetch("https://your-server/style.json")).json();

for (const source of Object.values(style.sources)) {
  if (source.url?.startsWith("pmtiles://")) {
    source.url = "pmtiles://https://tiles.jasontally.com/basemap.pmtiles";
  }
}

new maplibregl.Map({ container: "map", style, center: [2.35, 48.85], zoom: 4 });

Tre. Dove mettere lo stile che hai creato

Il pulsante JSON dello stile nell'editor qui sopra apre lo stile che la mappa sta disegnando in questo momento, modifiche incluse, con un pulsante per copiarlo. Ci sono due modi per usare il risultato, ed entrambi sono normali pagine web.

Salvalo come file accanto alla tua pagina e fai puntare style a quel file, proprio come fanno i tre stili su questo host. È la scelta giusta se lo stile è tuo e cambia insieme al tuo sito:

// Save the copied JSON as my-basemap.json beside this file.
new maplibregl.Map({
  container: "map",
  style: "my-basemap.json",           // relative, same origin
  center: [2.35, 48.85],
  zoom: 4
});

Oppure tienilo direttamente nell'HTML: un solo file da spostare e nulla da ospitare a parte. Lo stile è lo stesso, cambia solo dove si trova:

// The copied JSON, pasted straight into the page.
const myStyle = {
  "version": 8,
  "name": "Protomaps Basemap — custom",
  "glyphs": "https://tiles.jasontally.com/font/{fontstack}/{range}.pbf",
  "sprite": "https://tiles.jasontally.com/sprites/v4/light",
  "sources": { /* ... as copied ... */ },
  "layers":  [ /* ... as copied ... */ ]
};

new maplibregl.Map({ container: "map", style: myStyle, zoom: 4 });

Lascia invariate le righe glyphs e sprite. Servono a disegnare le etichette e le icone e puntano a questo host, così uno stile incollato nella tua pagina continua a funzionare da qualsiasi posto lo servi. Puntale invece a protomaps.github.io/basemaps-assets se preferisci non dipendere affatto da questo host.

Una cosa da verificare prima di sostituire le tile. Questo archivio usa Protomaps schema v3. Uno stile scritto per uno schema diverso verrà caricato ma non disegnerà nulla, perché i nomi dei layer sorgente non coincidono. I nove nomi dei layer sono elencati più avanti, insieme a uno stile v3 pronto da cui copiare.

Quattro. Font, sprite e librerie

Una mappa è fatta dalle tile, da un URL glyphs per il testo, facoltativamente da un URL sprite per le icone e dalle librerie che disegnano il tutto. Tutto questo si trova qui, ai percorsi che ciascun componente si aspetta, così l'intera mappa può provenire da un unico hostname se lo desideri.

CosaURL su questo hostLicenza

I file di MapLibre e PMTiles sono sia su jsDelivr sia qui. Entrambi gli URL funzionano.

L'ultima riga non è usata da questa pagina. È il renderer vettoriale per Leaflet, pubblicato qui in modo che la sostituzione richieda un solo tag script. Leaflet resta sulla propria CDN.

Due font, Noto Sans Regular e Noto Sans Medium, suddivisi nei 256 intervalli richiesti da MapLibre, 256 codepoint alla volta. Sono pubblicati tutti i 256 intervalli, non solo quelli latini: Protomaps etichetta gli elementi con il nome locale, quindi Tokyo o Il Cairo richiedono intervalli nell'ordine delle decine di migliaia. Se pubblichi solo ciò che serve a una mappa in inglese, quelle etichette spariscono senza alcun errore che ne spieghi il motivo.

Cinque fogli di sprite, uno per ciascuna variante upstream, a 1x e 2x. Gli stili qui sotto prendono le icone da questi file. Se vuoi indicatori di centri abitati, scudi autostradali o punti di interesse in un tuo stile, fai puntare sprite alla variante che preferisci. I nomi delle icone presenti in un foglio sono elencati nel relativo light.json.

Cosa disegna la mappa e da dove proviene ogni elemento

Ogni layer corrisponde a uno dei nove layer sorgente di questo archivio, quindi nulla richiede un secondo archivio. Vale la pena segnalarne due, perché entrambi sembrano dati mancanti quando in realtà non lo sono:

LayerProviene daIn questa build?
Geometrie, strade, edifici, acqua, suoloearth, landcover, landuse, water, roads, buildingssì
Nomi di luoghi, strade e corpi idriciil campo name di quegli stessi layersì
Icone dei punti di interesse, centri abitatipois.kind, places, più lo spritesì
Scudi stradaliroads.shield_textno. La build attualmente servita non contiene questo campo
Numeri civicibuildings.kind=address e buildings.addr_housenumberno, per lo stesso motivo

Non esiste un archivio separato per gli indirizzi e non c'è alcun layer di indirizzi da scaricare. Il progetto upstream inserisce i punti indirizzo dentro il layer buildings, come elementi con kind=address e addr_housenumber. Quel campo è stato introdotto in una build di Protomaps successiva a quella servita ora, quindi il layer dei numeri civici è già pronto e si attiverà da solo con il prossimo aggiornamento mensile. Lo stesso vale per il testo degli scudi stradali: la build attuale contiene shield_text_length, che indica quanti caratteri ha il numero di strada ma non quali sono; uno scudo disegnato così sarebbe una sagoma vuota su ogni autostrada.

Scrivere un layer da zero e colorarlo

Un layer è fatto da un filtro, uno zoom, una forma e un colore. Due dettagli nei filtri traggono in inganno quasi tutti, ed entrambi falliscono senza dare errore:

Ecco quindi una strada, il suo nome e il comando per nascondere la linea:

{
  "id": "road-major",
  "type": "line",
  "source": "protomaps",
  "source-layer": "roads",            // one of the nine
  "minzoom": 4,                                  // not drawn below this
  "filter": ["==", ["get", "kind"], "major_road"],
  "layout": {
    "line-cap": "round",
    "visibility": "visible"          // "none" to hide it
  },
  "paint": {
    "line-color": "#f7c44d",          // any CSS colour
    "line-width": ["interpolate", ["linear"], ["zoom"], 4, 1.7, 12, 6]
  }
}

Un nome è un layer di tipo symbol che legge il campo name, con o senza alone di contorno. Un glyph mancante non produce errori, quindi il font richiesto deve essere uno dei due presenti su questo host:

{
  "id": "road-label-major",
  "type": "symbol",
  "source": "protomaps",
  "source-layer": "roads",
  "minzoom": 11,
  "layout": {
    "text-field": ["coalesce", ["get", "name:en"], ["get", "name"]],
    "text-font": ["Noto Sans Medium"],  // or Noto Sans Regular
    "text-size": 10,
    "symbol-placement": "line"       // follow the road
  },
  "paint": {
    "text-color": "#2b2a26",
    "text-halo-color": "#ffffff",
    "text-halo-width": 1.1
  }
}

Un layer che non disegna nulla non è considerato un errore, e MapLibre non indica quale controllo è fallito. Se il tuo resta vuoto, verifica in quest'ordine: il nome del campo, il minzoom del layer, il font e infine il nome dell'icona confrontandolo con il light.json dello sprite.

Se hai uno stile in cui i glyph puntano a demotiles.maplibre.org, basta sostituire quell'hostname. Il percorso è identico di proposito.

"glyphs": "https://tiles.jasontally.com/font/{fontstack}/{range}.pbf",
"sprite": "https://tiles.jasontally.com/sprites/v4/light"

// before, in a style you already have
- "glyphs": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
+ "glyphs": "https://tiles.jasontally.com/font/{fontstack}/{range}.pbf",

Se uno stile richiede un font diverso dai due indicati sopra, il testo non verrà disegnato. Gli stili di Protomaps usano Noto Sans Medium per il grassetto; in questo set non c'è Noto Sans Bold, motivo per cui uno stile che lo richiede perde tutte le etichette senza errori.

Quale host è più veloce e cosa è stato scelto. Misurato invece che supposto: richieste alternate, una alla volta, su una sola connessione, primi tre campioni scartati, tempo al primo byte separato dal trasferimento. In quel test una CDN ha risposto da 10 a 45 ms prima per ciascuno di questi file, superando nettamente la soglia del 15%. Per questo motivo le librerie vengono caricate da jsDelivr.

I font e gli sprite sono invece serviti da qui, anche se la stessa misurazione indica che una CDN risponderebbe circa 10 ms prima. È una scelta progettuale, non di misura. Una mappa che dipende da un solo hostname non smette di funzionare quando cade il server di qualcun altro, e un server di glyph irraggiungibile lascia la mappa senza etichette. Se preferisci guadagnare quei 10 ms, fai puntare glyphs a protomaps.github.io/basemaps-assets/fonts: funziona con lo stesso percorso. Sono numeri rilevati da una singola città, e basta un comando per ripetere il confronto.

Cinque. Informazioni su Leaflet

L.tileLayer di Leaflet disegna immagini, mentre queste sono tile vettoriali. Una mappa Leaflet può comunque usarle mantenendo marker, popup e controlli. Entrambe le soluzioni sono descritte in Passare dalle tile di OpenStreetMap.

Sei. Leggere l'archivio con la libreria PMTiles

Nessuna mappa, solo dati. Utile per script, test e tutto ciò che non è un'immagine.

import { PMTiles, FetchSource } from "pmtiles";

const archive = new PMTiles(new FetchSource("https://tiles.jasontally.com/basemap.pmtiles"));

const header = await archive.getHeader();
console.log(header.minZoom, header.maxZoom);        // 0 15
console.log(header.numAddressedTiles);              // 1431655765

const metadata = await archive.getMetadata();
console.log(metadata.name);                          // "Protomaps Basemap"

const tile = await archive.getZxy(3, 4, 2);          // z, x, y
console.log(tile.data.byteLength);

Nota le due classi. FetchSource legge i byte via HTTP, mentre PMTiles è il lettore sovrastante che interpreta il formato. FileSource è l'equivalente di FetchSource per i file locali: se gli passi un URL come stringa non funziona.

L'archivio è un'istantanea, non un flusso in tempo reale. I dettagli sulle tempistiche sono in Quanto sono aggiornati i dati?.

Passare dalle tile di OpenStreetMap

Il blocco per la politica sull'uso delle tile, l'errore 403 e come risolvere. Nessuna chiave, nessuna quota.

Perché la mappa mostra un avviso di blocco

App is not following the tile usage policy of OpenStreetMap’s volunteer-run servers

General block: app is not following the tile usage policy of openstreetmap volunteer-run servers

403 Forbidden — tile.openstreetmap.org

I server di tile hanno inviato quell'immagine al posto della mappa di proposito, in base alla politica sull'uso delle tile. Non è un guasto, e correggere l'URL non risolve il problema.

I primi tentativi più comuni:

Uffici, scuole, VPN e reti mobili condividono lo stesso indirizzo IP, e i blocchi si applicano sia agli indirizzi sia alle applicazioni. Dal tuo lato non puoi sapere quale dei due casi ti riguarda.

Quindi smetti di interrogare quei server. Ci sono tre modi per sostituirli.

Mantenere Leaflet

protomaps-leaflet disegna tile vettoriali in Leaflet. Legge direttamente l'archivio e si aggiunge come un normale layer, così marker, popup, GeoJSON e controlli restano dove sono. Una copia testata con questo archivio si trova qui: https://tiles.jasontally.com/vendor/protomaps-leaflet.js.

Prima:

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

<script>
  const map = L.map("map").setView([48.85, 2.35], 12);

  L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
    maxZoom: 19,
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }).addTo(map);
</script>

Dopo:

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
+ <script src="https://tiles.jasontally.com/vendor/protomaps-leaflet.js"></script>

<script>
  const map = L.map("map").setView([48.85, 2.35], 12);

-  L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { ... }).addTo(map);
+  protomapsL.leafletLayer({
+    url: "https://tiles.jasontally.com/basemap.pmtiles",
+    flavor: "light",
+    lang: "en"
+  }).addTo(map);
</script>

protomaps-leaflet è in modalità di sola manutenzione upstream: correzioni, niente nuove funzioni. Va bene per un'applicazione Leaflet già esistente. I nuovi progetti dovrebbero partire con MapLibre GL.

Mantenere Leaflet, disegnare con MapLibre

@maplibre/maplibre-gl-leaflet inserisce una mappa MapLibre nel riquadro delle tile di Leaflet. Usalo quando vuoi uno degli stili qui sopra, oppure rilievo e ombreggiatura, in un'applicazione che resta basata su Leaflet.

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<link rel="stylesheet" href="https://tiles.jasontally.com/vendor/maplibre-gl.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://tiles.jasontally.com/vendor/maplibre-gl.js"></script>
<script src="https://tiles.jasontally.com/vendor/pmtiles.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@maplibre/maplibre-gl-leaflet@0.1.4/dist/leaflet-maplibre-gl.js"></script>

<script>
  // The style's source is pmtiles://, so MapLibre needs the protocol first.
  maplibregl.addProtocol("pmtiles", new pmtiles.Protocol().tile);

  const map = L.map("map", { minZoom: 1 }).setView([48.85, 2.35], 12);

  const gl = L.maplibreGL({
    style: "https://tiles.jasontally.com/styles/light.json"
  }).addTo(map);

  // gl.getMaplibreMap() is the MapLibre map, for sources and layers.
</script>

Passare a MapLibre GL

Una sola libreria, nessun adattatore, e gli stili qui sopra funzionano così come sono. La pagina completa è in Uno. Mettere una mappa nella pagina. La migrazione consiste soprattutto nell'invertire l'ordine delle coordinate:

LeafletMapLibre GL
L.map("map").setView([lat, lng], z)new maplibregl.Map({ container: "map", center: [lng, lat], zoom: z })
L.tileLayer(url).addTo(map)uno stile con sorgente vettoriale; vedi sopra
L.marker([lat, lng]).bindPopup(html)new maplibregl.Marker().setLngLat([lng, lat]).setPopup(new maplibregl.Popup().setHTML(html))
L.geoJSON(data)addSource(id, { type: "geojson", data }), poi addLayer
L.control.scale()new maplibregl.ScaleControl()
map.invalidateSize()map.resize()

setView([lat, lng]) accetta prima la latitudine; ogni chiamata MapLibre accetta invece [lng, lat]. Se la mappa finisce nell'emisfero sbagliato il motivo è questo, non le tile. Le sorgenti e i layer aggiunti prima del caricamento dello stile vengono ignorati senza errori, quindi aggiungili dentro map.on("load", …).

“archive contains MVT vector tiles, but leafletRasterLayer is for displaying raster tiles”

Questo messaggio proviene da pmtiles.leafletRasterLayer, che disegna archivi PMTiles raster come immagini. Queste sono tile vettoriali, quindi vengono rifiutate. Nessuna opzione può cambiarlo e qui non c'è una copia raster. Usa una delle soluzioni sopra.

“download.openstreetmap.fr returned 403”

Quello è un mirror di estratti e replica, non un server di tile. Nell'ottobre 2026 ha bloccato diversi indirizzi in seguito a download abusivi, e gli operatori hanno comunicato che avrebbero ripristinato l'accesso caso per caso. Nulla qui ne ha bisogno: questo archivio deriva dalle release di Protomaps, quindi non devi assemblare file .osm.pbf per conto tuo. Se scarichi dati OSM, usa un estratto periodico anziché il flusso al minuto.

Stili

Stesse tile, colori diversi. Scegline uno, copialo, modificalo: è tuo.

StileAspettoFile

Cosa c'è nelle tile

Letto direttamente dai metadati dell'archivio al caricamento della pagina. Questi sono i nomi dei layer che uno stile deve usare.

Layer sorgenteZoomCampi
caricamento…

Cosa non è questo servizio

Risposte chiare, per evitare sorprese più avanti.

Quanto sono aggiornati i dati?

Le tile sono una copia di Protomaps Basemap, ricostruita una volta al mese.

JSON dello stile

open raw