Тайлы Protomaps Basemap

Вся карта Protomaps Basemap (масштабы от 0 до 15) в одном архиве PMTiles по обычному HTTP. Укажите этот URL в любом клиенте PMTiles, и карта заработает.

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

Заблокирован тайл OpenStreetMap или приходит 403 от tile.openstreetmap.org? Начните отсюда.

Попробовать

Один и тот же архив в трёх стилях. При смене стиля уже загруженные тайлы остаются в памяти, поэтому переключение происходит мгновенно.

z0

Свой стиль

Меняйте цвета или отключайте слои — карта обновляется сразу. Затем скопируйте 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\">&copy; 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 (длина номера трассы в символах, а не сам номер), поэтому щиты на автомагистралях выглядели бы пустыми рамками.

Как написать и раскрасить свой слой

Слой задаёт фильтр, масштаб, геометрию и цвет. В фильтрах есть две частые ловушки, и обе не выдают ошибок:

Пример слоя дороги, её названия и отключения линии:

{
  "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 блокировку не снимет.

Что обычно пробуют в первую очередь:

Офисы, учебные заведения, 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: '&copy; <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>

Проект 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>

Перейти на MapLibre GL

Одна библиотека без адаптеров, и все стили выше работают напрямую. Полный код страницы приведён в разделе 1. Встроить карту на страницу. Переход сводится в основном к смене порядка координат:

LeafletMapLibre 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 напрямую, берите периодические выгрузки вместо поминутного потока.

Стили

Те же тайлы, другие цвета. Выберите стиль, скопируйте и измените под себя.

СтильВидФайл

Что внутри тайлов

Считывается из метаданных самого архива при загрузке страницы. Именно эти имена слоёв должен использовать стиль.

Исходный слойМасштабПоля
загрузка…

Чем этот сервис не является

Прямые ответы, чтобы потом не было неожиданностей.

Насколько свежие данные?

Тайлы представляют собой копию Protomaps Basemap и пересобираются раз в месяц.

JSON стиля

open raw