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
- Sem chave de APISem cadastro, sem token no seu código.
- Sem limite de requisiçõesSem limitação de taxa, sem cota, sem teto de pico.
- MensalReconstruído uma vez por mês a partir do lançamento mais recente do Protomaps.
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.
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\">© 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 aqui | Licenç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:
| Camada | Vem de | Nesta compilação? |
|---|---|---|
| Formas, vias, edifícios, água, terra | earth, landcover, landuse, water, roads, buildings | sim |
| Nomes de lugares, vias e corpos d'água | o campo name nessas mesmas camadas | sim |
| Ícones de pontos de interesse, marcadores de cidades | pois.kind, places, mais o sprite | sim |
| Escudos de rodovias | roads.shield_text | não. A compilação servida atualmente não tem esse campo |
| Números de imóveis | buildings.kind=address e buildings.addr_housenumber | nã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:
kindé a categoria geral. Para vias, o valor é um destes:highway,major_road,minor_road,path,rail. Os nomes na documentação original —motorway,primary,residential— são valores dekind_detail, e uma camada que filtrekindpor eles não desenha nada nem avisa em lugar algum.- Uma camada não é desenhada abaixo do seu próprio
minzoom. O arquivo vai até z15, então uma camada com minzoom 16 nunca é desenhada. A maioria dos elementos também traz seu própriomin_zoom, o nível de zoom a partir do qual os dados indicam que vale a pena exibi-los, e os estilos fornecidos também testam esse campo.
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:
- Trocar
a/b/cpelo host canônico não desbloqueia o acesso. A URL canônica é obrigatória, mas o bloqueio está vinculado ao aplicativo ou ao Referer identificado, não ao hostname. - Tentar novamente só piora. O volume de requisições faz parte do motivo do bloqueio.
- Nenhum truque de cabeçalho salva uma página aberta do disco.
file://não enviaReferer. EnviarX-Requested-Withno lugar exigiria um preflight CORS para cada tile, e os servidores de tiles também recusam esse preflight. Firefox e Chromium diferem nos cabeçalhos que enviam, mas a regra é a mesma para ambos. - Funciona em
localhost, mas é bloqueado no site no ar? A causa é a origem. O que é bloqueado é o Referer do seu domínio, elocalhostenvia outro.
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: '© <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>
flavorpode serlight,dark,white,grayscaleoublack. Esses são os temas da biblioteca, não os estilos mostrados acima, então as cores não serão idênticas.- Os rótulos são texto em canvas na fonte do sistema. Sem URL de glyphs, sem fonte que possa retornar 404.
- z15 é o tile mais detalhado. Acima disso a camada amplia os dados existentes (overzoom), e
maxDataZoom: 15já é o padrão.
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>
- Registre o protocolo primeiro. Sem a linha
addProtocol, o console informaURL scheme "pmtiles" is not supportede o mapa fica em branco. - O MapLibre desenha um nível de zoom abaixo do Leaflet. Os tiles dele têm 512 px contra 256 do Leaflet, então uma camada com
minzoom12 aparece quando o Leaflet chega ao z13. - O Leaflet continua desenhando por cima. O canvas fica no painel de tiles, então marcadores e sobreposições se comportam como sobre uma camada raster.
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:
| Leaflet | MapLibre 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.
| Estilo | Aparência | Arquivo |
|---|
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 origem | Zoom | Campos |
|---|---|---|
| carregando… | ||
O que este serviço não é
Respostas diretas, para que nada aqui surpreenda você depois.
- Não há garantia de nível de serviço. Este é um serviço gratuito e sem contrato. Ele pode ficar lento, pode sair do ar, e a URL e os dados podem mudar ou desaparecer sem aviso. Se o seu produto depende disso, hospede sua própria cópia dos tiles ou contrate um plano com suporte de algum provedor.
- Nunca está atualizado à última hora. A cópia é uma captura pontual do lançamento mais recente do Protomaps, não um fluxo em tempo real. Edições feitas hoje não estão nela. Veja a frequência em Quão atualizados são os dados?.
- Sem download do arquivo inteiro. As requisições são respondidas em intervalos de no máximo 512 KiB, que é tudo o que um cliente PMTiles pede. Solicitar o arquivo inteiro em uma única requisição retorna
416, o que é o comportamento esperado. - A atribuição é obrigatória. Os tiles são derivados do OpenStreetMap. Mantenha a linha de atribuição no seu estilo ou no seu mapa.
Quão atualizados são os dados?
Os tiles são uma cópia do Protomaps Basemap, reconstruída uma vez por mês.
- Reconstruído uma vez por mês. A compilação roda no primeiro domingo do mês, entre 00:30 e 07:00 UTC, a única janela em que o projeto original nunca publicou em três anos de histórico de lançamentos. É mensal e não semanal porque um novo arquivo invalida as partes em cache na CDN e em todos os navegadores que já as têm, além do tempo gasto em cada reconstrução.
- Nada a fazer na maioria dos meses. Uma reconstrução só acontece quando o projeto original publica um novo lançamento.
- Qual compilação original está em uso agora? Use o botão Metadados do arquivo no topo desta página. Ele lê o próprio cabeçalho do arquivo, então a resposta está sempre atualizada em vez de depender de uma informação estática nesta página.
- A reconstrução não é instantânea. São treze reconstruções separadas distribuídas ao longo de cerca de seis horas e meia, uma de cada vez. Durante a maior parte dessa janela, a cópia fica dividida entre duas compilações originais. Um tile solicitado nesse período pode vir da compilação mais nova em vez da informada pelo cabeçalho, o que aparece como um tile do lugar errado, e não como um mapa quebrado.
- Uma reconstrução pode falhar. É uma tarefa feita sem garantia de execução. Se uma etapa falhar, o restante da cópia permanece na compilação original anterior. O mapa continua funcionando.