Tiles do Protomaps Basemap

Todo o Protomaps Basemap, do zoom 0 ao 15, servido como um único arquivo PMTiles via HTTP comum. Aponte qualquer cliente PMTiles para a URL e ele funciona.

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

Um tile do OpenStreetMap bloqueado ou erro 403 de tile.openstreetmap.org? Comece aqui.

Experimente

O mesmo arquivo em três estilos. Ao mudar o estilo, os tiles já carregados permanecem no lugar, então a troca é instantânea.

z0

Crie o seu

Mude uma cor ou desative uma camada e o mapa se atualiza na hora. Depois copie o JSON do estilo, que é um estilo MapLibre normal que você pode hospedar por conta própria. Ele aponta para estes tiles, então continua funcionando onde quer que você o coloque.

Como usar

O arquivo aceita requisições de intervalo HTTP (range requests), então todo cliente PMTiles consegue lê-lo.

Um. Coloque um mapa na página

Copie este arquivo. Ele é o exemplo completo: três tags, uma URL de estilo, um mapa. Ambas as bibliotecas têm versão fixa e licença BSD-3-Clause, e os mesmos arquivos são servidos a partir deste host caso prefira não usar uma 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>

Dois. Troque os tiles em um estilo que você já tem

Todo estilo MapLibre lê seus tiles de uma fonte de dados. Para usar estes tiles, altere apenas essa url. Nada mais no estilo muda.

  "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>"
    }
  }

Ou faça isso no código, o que é útil se o estilo vier de um servidor que você não controla:

// 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 });

Três. Onde colocar o estilo que você criou

O botão JSON do estilo no editor acima abre o estilo que este mapa está desenhando agora, incluindo as edições, e a janela tem um botão para copiar. Há duas formas de usar o resultado, e ambas são páginas web comuns.

Salve-o como um arquivo ao lado da sua página e aponte style para ele, do mesmo modo que os três estilos deste host são usados. Escolha esta opção se o estilo for seu e mudar junto com o seu site:

// 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
});

Ou mantenha-o dentro do HTML, o que significa um único arquivo para mover e nada extra para hospedar. É o mesmo estilo, só muda o lugar onde ele fica:

// 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 });

Deixe as linhas glyphs e sprite como estão. São elas que desenham os rótulos e os ícones, e apontam para este host; assim, um estilo colado na sua própria página continua funcionando de onde quer que você o sirva. Aponte-as para protomaps.github.io/basemaps-assets se preferir não depender deste host.

Um item para verificar antes de trocar. Este arquivo usa o esquema Protomaps v3. Um estilo escrito para outro esquema vai carregar e não desenhar nada, porque os nomes das camadas de origem não coincidem. Os nove nomes de camadas estão listados abaixo, assim como um estilo v3 pronto para copiar.

Quatro. Fontes, sprites e bibliotecas

Um mapa é composto pelos tiles, uma URL de glyphs para textos, opcionalmente uma URL de sprite para ícones e as bibliotecas para desenhar tudo isso. Tudo está aqui, nos caminhos esperados por cada parte, para que o mapa possa vir de um único hostname se você quiser.

O quêURL aquiLicença

Os arquivos do MapLibre e do PMTiles estão no jsDelivr e aqui. Qualquer uma das URLs funciona.

A última linha não é usada por esta página. Trata-se do renderizador vetorial para Leaflet, publicado aqui para que a troca exija apenas uma tag de script. O próprio Leaflet permanece na CDN dele.

Duas famílias de fontes, Noto Sans Regular e Noto Sans Medium, divididas nos 256 intervalos que o MapLibre solicita, 256 pontos de código por vez. Todos os 256 estão publicados, não apenas os latinos: o Protomaps rotula os elementos com o nome local, então Tóquio ou Cairo precisam de intervalos na casa das dezenas de milhares. Se você publicar apenas o que um mapa em inglês usa, esses rótulos somem sem nenhum erro que explique o motivo.

Cinco folhas de sprites, uma para cada variante original, em 1x e 2x. Os estilos abaixo desenham ícones a partir delas, por isso são buscadas. Se você quiser marcadores de cidades, escudos de rodovias ou pontos de interesse em um estilo próprio, aponte sprite para a folha da sua variante. Os nomes dos ícones de uma folha estão listados no light.json correspondente.

O que o mapa desenha e de onde vem cada elemento

Cada camada vem de uma das nove camadas de origem deste arquivo, então nada aqui precisa de um segundo arquivo. Vale destacar duas delas, porque ambas parecem estar faltando quando na verdade não estão:

CamadaVem deNesta compilação?
Formas, vias, edifícios, água, terraearth, landcover, landuse, water, roads, buildingssim
Nomes de lugares, vias e corpos d'águao campo name nessas mesmas camadassim
Ícones de pontos de interesse, marcadores de cidadespois.kind, places, mais o spritesim
Escudos de rodoviasroads.shield_textnão. A compilação servida atualmente não tem esse campo
Números de imóveisbuildings.kind=address e buildings.addr_housenumbernão, pelo mesmo motivo

Não existe um arquivo separado de endereços nem uma camada de endereços para buscar. O projeto original coloca os pontos de endereço dentro da camada buildings, como elementos com kind=address e addr_housenumber. Esse campo entrou em uma compilação do Protomaps posterior à que este mapa está servindo; por isso, a camada de números de imóveis já está pronta e se ativa sozinha quando a próxima atualização mensal trouxer esses dados. O mesmo vale para o texto dos escudos de rodovia: a compilação atual traz shield_text_length, que informa quantos caracteres tem o número da rota, mas não quais são, então um escudo desenhado com base nisso seria uma forma em branco em cada rodovia.

Como escrever e colorir sua própria camada

Uma camada é um filtro, um zoom, uma forma e uma cor. Dois detalhes no filtro costumam pegar todo mundo de surpresa, e ambos falham sem gerar erro:

Veja uma via, o nome dela e um botão para ocultar a linha:

{
  "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]
  }
}

Um nome é uma camada de símbolos que lê o campo name, com um halo ou sem nada atrás. Um glyph ausente falha em silêncio, então a família de fontes solicitada deve ser uma das duas disponíveis neste 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
  }
}

Uma camada que não desenha nada não é considerada erro, e o MapLibre não dirá qual verificação falhou. Se a sua ficar em branco, verifique nesta ordem: o nome do campo, o minzoom da camada, a família de fontes e, por fim, o nome do ícone no light.json da folha de sprites.

Se você tem um estilo que aponta os glyphs para demotiles.maplibre.org, basta substituir esse hostname. O caminho é propositalmente o mesmo.

"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 um estilo pedir uma família de fontes diferente das duas acima, nada será desenhado para ela. Os próprios estilos do Protomaps usam Noto Sans Medium para texto em negrito; não há Noto Sans Bold neste conjunto, e é por isso que um estilo que a solicite perde todos os rótulos sem avisar.

Qual host é mais rápido e o que foi decidido a respeito. Medido em vez de presumido: requisições intercaladas, uma por vez, em uma única conexão, descartando as três primeiras amostras e separando o tempo até o primeiro byte da transferência. Nesse teste, uma CDN respondeu de 10 a 45 ms mais rápido para cada um destes arquivos, o que atende com folga à regra dos 15%. Por esse motivo, as bibliotecas são carregadas do jsDelivr.

As fontes e os sprites são servidos daqui mesmo que a mesma medição indique que uma CDN responderia cerca de 10 ms mais rápido. Isso é uma decisão, não uma medição. Um mapa que vem de um único hostname não quebra quando o servidor de terceiros falha, e um servidor de glyphs fora do ar resulta em um mapa sem rótulos. Se você preferir ganhar esses 10 ms, aponte glyphs para protomaps.github.io/basemaps-assets/fonts, que funciona com o mesmo caminho. São números de um leitor em uma cidade, e refazer a comparação leva apenas um comando.

Cinco. Sobre o Leaflet

O L.tileLayer do Leaflet desenha imagens; estes são tiles vetoriais. Um mapa Leaflet ainda pode usá-los e, em qualquer uma das opções, seus marcadores, popups e controles são mantidos. As duas formas de troca estão em Migrando dos tiles do OpenStreetMap.

Seis. Leia o arquivo com a biblioteca PMTiles

Sem mapa, apenas dados. Útil para scripts, testes e qualquer coisa que não seja uma imagem.

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);

Repare nas duas classes. FetchSource é o leitor de bytes via HTTP, e PMTiles é o leitor que opera sobre ele e entende o formato. FileSource é o equivalente de FetchSource para arquivos locais; se receber uma string de URL, não funcionará.

O arquivo é uma captura pontual, não um fluxo em tempo real. Veja a frequência em Quão atualizados são os dados?.

Migrando dos tiles do OpenStreetMap

O bloqueio pela política de uso de tiles, o erro 403 e o que fazer a respeito. Sem chave, sem cota.

Por que o mapa mostra um bloqueio

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

Os servidores de tiles enviaram essa imagem no lugar do mapa de propósito, conforme a política de uso de tiles. Não é uma falha técnica, e corrigir a URL não remove o bloqueio.

O que costumam tentar primeiro:

Escritórios, escolas, VPNs e redes móveis compartilham o mesmo endereço IP, e os bloqueios se aplicam tanto a endereços quanto a aplicativos. Do seu lado, não há como saber qual dos dois casos ocorreu.

Portanto, pare de requisitar esses servidores. Há três caminhos para sair deles.

Manter o Leaflet

O protomaps-leaflet desenha tiles vetoriais no Leaflet. Ele mesmo lê o arquivo e se adiciona como uma camada normal, de modo que marcadores, popups, GeoJSON e controles continuam onde estão. Uma cópia testada com este arquivo está disponível aqui: https://tiles.jasontally.com/vendor/protomaps-leaflet.js.

Antes:

<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>

Depois:

<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>

O protomaps-leaflet está em modo de manutenção no projeto original: recebe correções, não recursos novos. Serve bem para um aplicativo Leaflet existente. Projetos novos devem começar no MapLibre GL.

Manter o Leaflet, renderizar com o MapLibre

O @maplibre/maplibre-gl-leaflet coloca um mapa MapLibre dentro do painel de tiles do Leaflet. Use-o quando quiser um dos estilos acima, ou terreno e sombreamento de relevo, em um aplicativo que continua em 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>

Migrar para o MapLibre GL

Uma biblioteca, nenhum adaptador, e os estilos acima funcionam como estão. A página completa está em Um. Coloque um mapa na página. A troca consiste basicamente em inverter a ordem das coordenadas:

LeafletMapLibre GL
L.map("map").setView([lat, lng], z)new maplibregl.Map({ container: "map", center: [lng, lat], zoom: z })
L.tileLayer(url).addTo(map)um estilo com uma fonte vetorial; veja acima
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 }), depois addLayer
L.control.scale()new maplibregl.ScaleControl()
map.invalidateSize()map.resize()

setView([lat, lng]) recebe a latitude primeiro; toda chamada do MapLibre recebe [lng, lat]. Um mapa no hemisfério errado é resultado disso, não dos tiles. Fontes e camadas adicionadas antes do carregamento do estilo são descartadas sem erro, portanto adicione-as dentro de map.on("load", …).

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

Isso vem de pmtiles.leafletRasterLayer, que desenha PMTiles raster como imagens. Estes são tiles vetoriais, por isso são recusados. Nenhuma opção muda isso, e não há cópia raster aqui. Use uma das opções acima.

“download.openstreetmap.fr returned 403”

Esse endereço é um espelho de extratos e replicação, não um servidor de tiles. Em outubro de 2026 ele bloqueou endereços após downloads abusivos, e os operadores informaram que restabeleceriam o acesso caso a caso. Nada aqui precisa dele: este arquivo vem dos lançamentos do Protomaps, então você mesmo não precisa montar arquivos .osm.pbf. Se você baixa dados do OSM, use um extrato periódico em vez do fluxo por minuto.

Estilos

Os mesmos tiles, cores diferentes. Escolha um, copie, modifique: ele é seu.

EstiloAparênciaArquivo

O que há nos tiles

Lido dos próprios metadados do arquivo ao carregar a página. Estes são os nomes de camadas que um estilo deve usar.

Camada de origemZoomCampos
carregando…

O que este serviço não é

Respostas diretas, para que nada aqui surpreenda você depois.

Quão atualizados são os dados?

Os tiles são uma cópia do Protomaps Basemap, reconstruída uma vez por mês.

JSON do estilo

open raw