Тайлы Protomaps Basemap
Вся карта Protomaps Basemap (масштабы от 0 до 15) в одном архиве PMTiles по обычному HTTP. Укажите этот URL в любом клиенте PMTiles, и карта заработает.
https://tiles.jasontally.com/basemap.pmtiles
- Без API-ключаБез регистрации и без токенов в коде.
- Без лимита запросовБез замедления, квот и ограничений на пики.
- Раз в месяцПересобирается из свежего выпуска Protomaps раз в месяц.
Заблокирован тайл OpenStreetMap или приходит 403 от tile.openstreetmap.org? Начните отсюда.
Попробовать
Один и тот же архив в трёх стилях. При смене стиля уже загруженные тайлы остаются в памяти, поэтому переключение происходит мгновенно.
Свой стиль
Меняйте цвета или отключайте слои — карта обновляется сразу. Затем скопируйте JSON стиля: это обычный стиль MapLibre, который можно разместить у себя. Он ссылается на эти тайлы и будет работать где угодно.
Использование
Архив поддерживает HTTP Range-запросы, поэтому его читает любой клиент PMTiles.
1. Встроить карту на страницу
Скопируйте этот файл. Это полный пример: три тега, один URL стиля, одна карта. Обе библиотеки зафиксированы по версиям, распространяются под лицензией BSD-3-Clause и раздаются также с этого хоста, если вы не хотите использовать 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>
2. Заменить тайлы в готовом стиле
Каждый стиль MapLibre берёт тайлы из источника данных. Чтобы перейти на эти тайлы, замените один url. Остальная часть стиля не меняется.
"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>"
}
}
Или замените его в коде — это удобно, если стиль загружается со стороннего сервера:
// 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 });
3. Куда положить созданный стиль
Кнопка JSON стиля в конструкторе выше открывает текущий стиль карты со всеми правками, и в окне есть кнопка копирования. Использовать результат на обычной веб-странице можно двумя способами.
Сохраните его в файл рядом со страницей и укажите путь в style — так же устроены три стиля на этом хосте. Этот вариант подходит, если стиль ваш и меняется вместе с сайтом:
// 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
});
Или вставьте его прямо в HTML: тогда всё хранится в одном файле, и отдельный файл стиля размещать не нужно. Стиль тот же, отличается только место хранения:
// 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 });
Оставьте строки glyphs и sprite без изменений. Они отвечают за подписи и значки и указывают на этот хост, поэтому стиль, вставленный на вашу страницу, работает с любого сервера. Если вы не хотите зависеть от этого хоста, укажите вместо него protomaps.github.io/basemaps-assets.
Что проверить перед заменой. Этот архив использует схему Protomaps v3. Стиль, написанный под другую схему, загрузится, но ничего не отрисует, так как имена исходных слоёв не совпадут. Девять имён слоёв перечислены ниже, как и готовый стиль v3, который можно взять за образец.
4. Шрифты, спрайты и библиотеки
Карта состоит из тайлов, URL glyphs для текста, необязательного URL sprite для значков и библиотек для отрисовки. Всё это размещено здесь по ожидаемым путям, поэтому карту можно загружать с одного домена.
| Ресурс | URL на этом хосте | Лицензия |
|---|
Файлы MapLibre и PMTiles доступны на jsDelivr и здесь. Работает любой из двух URL.
Последняя строка на этой странице не используется. Это векторный рендерер для Leaflet, опубликованный здесь, чтобы переход сводился к одному тегу скрипта. Сам Leaflet остаётся на своём CDN.
Два набора шрифтов, Noto Sans Regular и Noto Sans Medium, разбиты на 256 диапазонов по 256 кодовых точек, как запрашивает MapLibre. Опубликованы все 256 диапазонов, а не только латиница: Protomaps подписывает объекты на местных языках, поэтому для Токио или Каира нужны диапазоны в десятках тысяч. Если опубликовать только символы для английской карты, такие подписи исчезнут без сообщений об ошибке.
Пять наборов спрайтов — по одному на каждый вариант оформления из исходного проекта, в разрешениях 1x и 2x. Приведённые ниже стили берут значки из них. Если в вашем стиле нужны отметки городов, дорожные щиты или значки объектов, укажите в sprite подходящий вариант. Имена значков перечислены в файле light.json соответствующего набора.
Что рисует карта и откуда берутся данные
Каждый слой берёт данные из одного из девяти исходных слоёв этого архива, поэтому второй архив не требуется. Два случая стоит отметить отдельно — оба выглядят как пропавшие данные, хотя это не так:
| Слой | Источник | Есть в этой сборке? |
|---|---|---|
| Контуры, дороги, здания, вода, суша | earth, landcover, landuse, water, roads, buildings | да |
| Названия мест, дорог и водоёмов | поле name в тех же слоях | да |
| Значки объектов, отметки населённых пунктов | pois.kind, places и спрайт | да |
| Дорожные щиты (номера трасс) | roads.shield_text | нет. В раздаваемой сборке этого поля нет |
| Номера домов | buildings.kind=address и buildings.addr_housenumber | нет, по той же причине |
Отдельного адресного архива или адресного слоя не существует. Исходный проект помещает адресные точки внутрь слоя buildings как объекты с kind=address и addr_housenumber. Это поле появилось в более поздней сборке Protomaps, чем та, что раздаётся сейчас, поэтому слой с номерами домов уже настроен и включится сам при следующем ежемесячном обновлении. То же самое с текстом дорожных щитов: в текущей сборке есть только поле shield_text_length (длина номера трассы в символах, а не сам номер), поэтому щиты на автомагистралях выглядели бы пустыми рамками.
Как написать и раскрасить свой слой
Слой задаёт фильтр, масштаб, геометрию и цвет. В фильтрах есть две частые ловушки, и обе не выдают ошибок:
kind— это общая категория. Для дорог это одно из значений:highway,major_road,minor_road,path,rail. Названия из документации исходного проекта —motorway,primary,residential— находятся в полеkind_detail; слой, фильтрующий по ним полеkind, ничего не нарисует и не сообщит об ошибке.- Слой не рисуется ниже своего
minzoom. Архив заканчивается на z15, поэтому слой с minzoom 16 не отобразится никогда. У большинства объектов также есть собственное полеmin_zoom— масштаб, с которого их имеет смысл показывать, и готовые стили проверяют его тоже.
Пример слоя дороги, её названия и отключения линии:
{
"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]
}
}
Название — это символьный слой, читающий поле name, с обводкой или без неё. Отсутствие глифов не вызывает ошибки, поэтому запрашиваемый шрифт должен быть одним из двух, имеющихся на этом хосте:
{
"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
}
}
Слой, который ничего не рисует, не считается ошибкой, и MapLibre не подскажет, какая проверка не прошла. Если слой пуст, проверяйте по порядку: имя поля, minzoom слоя, набор шрифтов, а затем имя значка по списку в light.json.
Если в вашем стиле глифы указывают на demotiles.maplibre.org, достаточно заменить имя хоста. Путь намеренно оставлен таким же.
"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",
Если стиль запрашивает шрифт, отличный от двух указанных выше, текст отрисован не будет. Собственные стили Protomaps используют Noto Sans Medium для полужирного начертания; Noto Sans Bold в этом наборе нет, поэтому стиль, запрашивающий его, без ошибок теряет все подписи.
Какой хост быстрее и какое решение принято. Проверено измерениями, а не догадками: чередующиеся запросы по одному в рамках одного соединения, первые три замера отброшены, время до первого байта отделено от времени передачи. В этом тесте CDN отвечал на 10–45 мс быстрее для каждого из файлов, что заведомо превышает порог в 15%. По этой причине библиотеки загружаются с jsDelivr.
Шрифты и спрайты раздаются отсюда, хотя по тем же замерам CDN отдал бы их примерно на 10 мс быстрее. Это осознанный выбор: карта, загружаемая с одного домена, не ломается при сбое стороннего сервиса, а недоступный сервер глифов оставляет карту без подписей. Если вам важнее сэкономить 10 мс, укажите в glyphs адрес protomaps.github.io/basemaps-assets/fonts — путь там такой же. Это замеры одного пользователя из одного города, а повторить сравнение можно одной командой.
5. О Leaflet
L.tileLayer в Leaflet рисует растровые изображения, а здесь — векторные тайлы. Карта на Leaflet всё равно может их использовать, сохранив ваши маркеры, всплывающие окна и элементы управления. Оба варианта перехода описаны в разделе Переход с тайлов OpenStreetMap.
6. Чтение архива библиотекой PMTiles
Без карты, только данные. Подходит для скриптов, тестов и задач, где не нужна отрисовка.
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);
Обратите внимание на два класса: FetchSource читает байты по HTTP, а PMTiles работает поверх него и разбирает формат архива. FileSource — аналог FetchSource для локальных файлов; со строкой URL он работать не будет.
Архив представляет собой срез данных, а не поток в реальном времени. График обновлений описан в разделе Насколько свежие данные?.
Переход с тайлов OpenStreetMap
Блокировка по политике использования тайлов, ошибка 403 и способы решения. Без ключей и квот.
Почему вместо карты видна заглушка о блокировке
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
Тайловые серверы намеренно прислали это изображение вместо карты в соответствии с политикой использования тайлов. Это не сбой, и изменение URL блокировку не снимет.
Что обычно пробуют в первую очередь:
- Замена поддоменов
a/b/cна канонический хост не снимает блокировку. Канонический URL обязателен, но блокировка привязана к приложению или заголовку Referer, а не к имени хоста. - Повторные запросы только ухудшают ситуацию. Высокий поток запросов — одна из причин блокировки.
- Никакие уловки с заголовками не спасут страницу, открытую с диска. Протокол
file://не отправляетReferer. ОтправкаX-Requested-Withпотребовала бы предварительного запроса CORS для каждого тайла, а тайловые серверы отклоняют и его. Firefox и Chromium отправляют разные заголовки, но правило одинаково для обоих. - Работает на
localhost, но заблокировано на рабочем сайте? Дело в источнике запроса. Блокируется Referer вашего домена, аlocalhostотправляет другой.
Офисы, учебные заведения, VPN и мобильные сети используют общие IP-адреса, а блокировки накладываются как на приложения, так и на адреса. Со своей стороны вы не сможете определить причину.
Поэтому перестаньте обращаться к тем серверам. Есть три способа уйти с них.
Остаться на Leaflet
Библиотека protomaps-leaflet рисует векторные тайлы в Leaflet. Она сама читает архив и подключается как обычный слой, поэтому маркеры, всплывающие окна, GeoJSON и элементы управления остаются на местах. Проверенная с этим архивом копия размещена здесь: https://tiles.jasontally.com/vendor/protomaps-leaflet.js.
Было:
<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>
Стало:
<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>
flavorпринимает значенияlight,dark,white,grayscaleилиblack. Это встроенные темы библиотеки, а не стили, показанные выше, поэтому цвета будут немного отличаться.- Подписи рисуются системным шрифтом на canvas. Не нужен URL глифов, и шрифт не может вернуть 404.
- z15 — максимальный масштаб тайлов в архиве. Крупнее этого уровня слой масштабирует уже загруженные данные, а параметр
maxDataZoom: 15включён по умолчанию.
Проект protomaps-leaflet находится в режиме поддержки: выходят только исправления, без новых функций. Этого достаточно для существующего приложения на Leaflet. Новые проекты лучше начинать на MapLibre GL.
Остаться на Leaflet, отрисовывать через MapLibre
Плагин @maplibre/maplibre-gl-leaflet помещает карту MapLibre в панель тайлов Leaflet. Используйте его, если вам нужен один из стилей выше либо рельеф и отмывка теней в приложении на 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>
- Сначала зарегистрируйте протокол. Без вызова
addProtocolв консоли появится ошибкаURL scheme "pmtiles" is not supported, а карта останется пустой. - MapLibre рисует на один уровень масштаба меньше, чем Leaflet. Его тайлы имеют размер 512 пикселей против 256 у Leaflet, поэтому слой с
minzoom12 появляется, когда Leaflet достигает z13. - Leaflet по-прежнему рисует поверх. Холст находится в панели тайлов, поэтому маркеры и наложения ведут себя так же, как поверх растрового слоя.
Перейти на MapLibre GL
Одна библиотека без адаптеров, и все стили выше работают напрямую. Полный код страницы приведён в разделе 1. Встроить карту на страницу. Переход сводится в основном к смене порядка координат:
| 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) | стиль с векторным источником; см. выше |
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 }), затем addLayer |
L.control.scale() | new maplibregl.ScaleControl() |
map.invalidateSize() | map.resize() |
setView([lat, lng]) принимает сначала широту, а все вызовы MapLibre принимают [lng, lat]. Если карта оказалась в другом полушарии, причина в этом, а не в тайлах. Источники и слои, добавленные до загрузки стиля, отбрасываются без ошибки, поэтому добавляйте их внутри map.on("load", …).
“archive contains MVT vector tiles, but leafletRasterLayer is for displaying raster tiles”
Это pmtiles.leafletRasterLayer, который рисует растровые архивы PMTiles как картинки. Здесь векторные тайлы, поэтому он их отвергает. Никакие настройки это не изменят, а растровой копии здесь нет. Используйте один из вариантов выше.
“download.openstreetmap.fr returned 403”
Это зеркало выгрузок и репликации, а не тайловый сервер. В октябре 2026 года оно заблокировало ряд адресов из-за чрезмерного скачивания, и администраторы сообщили, что будут восстанавливать доступ в индивидуальном порядке. Здесь оно не требуется: этот архив собирается из выпусков Protomaps, поэтому вам не нужно самостоятельно собирать файлы .osm.pbf. Если же вы скачиваете данные OSM напрямую, берите периодические выгрузки вместо поминутного потока.
Стили
Те же тайлы, другие цвета. Выберите стиль, скопируйте и измените под себя.
| Стиль | Вид | Файл |
|---|
Что внутри тайлов
Считывается из метаданных самого архива при загрузке страницы. Именно эти имена слоёв должен использовать стиль.
| Исходный слой | Масштаб | Поля |
|---|---|---|
| загрузка… | ||
Чем этот сервис не является
Прямые ответы, чтобы потом не было неожиданностей.
- Никаких гарантий уровня обслуживания. Это бесплатный сервис без договора и обязательств. Он может работать медленно, может стать недоступен, а URL и данные могут измениться или исчезнуть без предупреждения. Если от карты зависит ваш продукт, разместите копию тайлов у себя или оплатите тариф с поддержкой у стороннего провайдера.
- Данные не обновляются ежечасно. Эта копия — срез свежего выпуска Protomaps, а не поток в реальном времени. Сегодняшних правок здесь нет. Подробности — в разделе Насколько свежие данные?.
- Скачать весь файл целиком нельзя. Сервер отдаёт диапазоны размером не более 512 КиБ — клиенту PMTiles больше и не требуется. Запрос всего архива целиком вернёт код
416, и это штатное поведение. - Указание авторства обязательно. Тайлы созданы на основе данных OpenStreetMap. Сохраняйте строку об авторстве в стиле или на карте.
Насколько свежие данные?
Тайлы представляют собой копию Protomaps Basemap и пересобираются раз в месяц.
- Пересборка раз в месяц. Сборка запускается в первое воскресенье месяца между 00:30 и 07:00 UTC — это единственное окно, в которое исходный проект ни разу не публиковал релизы за три года истории выпусков. Обновление проводится раз в месяц, а не каждую неделю, потому что новый архив сбрасывает кэш на CDN и во всех браузерах, а каждая пересборка расходует ресурсы сборки.
- В большинстве месяцев ничего делать не нужно. Пересборка выполняется лишь тогда, когда в исходном проекте вышел новый выпуск.
- Какая сборка используется прямо сейчас? Нажмите кнопку Метаданные архива в верхней части страницы. Она читает заголовок самого архива, поэтому всегда показывает актуальную версию.
- Пересборка происходит не мгновенно. Она состоит из тринадцати отдельных сборок, которые выполняются по очереди и занимают около шести с половиной часов. Большую часть этого времени копия состоит из смеси двух выпусков. Запрошенный в этот момент тайл может прийти из новой сборки, а не из той, что указана в заголовке: это проявится как тайл от другого участка карты, а не как поломка всей карты.
- Пересборка может быть пропущена. Обновление выполняется по мере возможности, без гарантий. Если один из этапов завершится ошибкой, остальная часть копии останется на предыдущей сборке, а карта продолжит работать.