Тайли базової карти Protomaps

Уся базова карта Protomaps, масштаби від 0 до 15, як один архів PMTiles через звичайний HTTP. Вкажіть цю URL-адресу будь-якому клієнту PMTiles, і все працюватиме.

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

Заблоковано тайл OpenStreetMap або помилка 403 від tile.openstreetmap.org? Почніть тут.

Спробувати

Один і той самий архів у трьох стилях. Під час зміни стилю вже завантажені тайли залишаються на місці, тому перемикання відбувається миттєво.

z0

Створіть власний стиль

Змініть колір або вимкніть шар — карта оновлюється одразу. Потім скопіюйте JSON стилю: це звичайний стиль MapLibre, який ви можете розмістити у себе. Він посилається на ці тайли, тому працюватиме будь-де.

Використання

Архів підтримує HTTP-запити діапазонів (range requests), тому його може читати будь-який клієнт PMTiles.

Перше. Додайте карту на сторінку

Скопіюйте цей файл. Це повний приклад: три теги, одна 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>

Друге. Замініть тайли в наявному стилі

Кожен стиль MapLibre читає тайли з джерела (source). Щоб використовувати ці тайли, змініть лише одне поле 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 });

Третє. Де розмістити створений стиль

Кнопка 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, який можна взяти за основу.

Четверте. Шрифти, спрайти та бібліотеки

Карта складається з тайлів, URL-адреси glyphs для тексту, необов'язкової URL-адреси sprite для значків і бібліотек, які все це малюють. Усе це є тут за стандартними шляхами, тож за бажанням усю карту можна завантажувати з одного домену.

Що самеURL тутЛіцензія

Файли MapLibre та PMTiles доступні на jsDelivr і тут. Працює будь-яка з адрес.

Останній рядок на цій сторінці не використовується. Це векторний візуалізатор для Leaflet, опублікований тут, щоб заміна зводилася до одного тегу script. Сам Leaflet залишається на власному CDN.

Два набори шрифтів, Noto Sans Regular і Noto Sans Medium, розбиті на 256 діапазонів по 256 кодових точок, як того вимагає MapLibre. Опубліковано всі 256 діапазонів, а не лише латиницю: Protomaps підписує об'єкти місцевими назвами, тому для Токіо чи Каїра потрібні діапазони в десятках тисяч. Якщо опублікувати лише діапазони для англійської карти, ці підписи зникнуть без жодного повідомлення про помилку.

П'ять таблиць спрайтів, по одній на кожен варіант оформлення вихідного проєкту, у роздільній здатності 1x та 2x. Наведені нижче стилі беруть значки з них. Якщо у власному стилі вам потрібні позначки міст, дорожні щити чи значки об'єктів (POI), вкажіть у sprite потрібний варіант. Назви значків у таблиці перелічені у відповідному файлі light.json.

Що малює карта і звідки береться кожен елемент

Кожен шар стилю бере дані з одного з дев'яти вихідних шарів цього архіву, тому другий архів не потрібен. Варто окремо згадати два з них, бо обидва виглядають так, ніби чогось бракує, хоча це не так:

ШарДжерело данихЄ в цій збірці?
Контури, дороги, будівлі, вода, сушаearth, landcover, landuse, water, roads, buildingsтак
Назви населених пунктів, доріг і водоймполе name у тих самих шарахтак
Значки об'єктів (POI), позначки містpois.kind, places, а також спрайттак
Номери трас на щитахroads.shield_textні. У поточній збірці цього поля немає
Номери будинківbuildings.kind=address і buildings.addr_housenumberні, з тієї самої причини

Окремого адресного архіву не існує, і завантажувати окремий шар адрес не потрібно. Основний проєкт Protomaps розміщує адресні точки всередині шару 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]
  }
}

Назва — це символьний шар (symbol), який читає поле name, з обведенням (halo) або без нього. Якщо гліфа бракує, помилка не виводиться, тому шрифт має бути одним із двох, наявних на цьому вузлі:

{
  "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 — шлях там такий самий. Це показники одного користувача в одному місті, а повторити порівняння можна однією командою.

П'яте. Про Leaflet

L.tileLayer у Leaflet малює готові зображення, а тут — векторні тайли. Проте карта на Leaflet все одно може їх використовувати, зберігаючи ваші маркери, спливаючі вікна та елементи керування. Обидва варіанти заміни описано в розділі Перехід із тайлів OpenStreetMap.

Шосте. Читання архіву бібліотекою 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

Одна бібліотека, жодних адаптерів, а наведені вище стилі працюють як є. Повний код сторінки наведено в розділі Перше. Додайте карту на сторінку. Перехід зводиться здебільшого до зміни порядку координат:

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, яка перезбирається раз на місяць.

JSON стилю

open raw