Protomaps Basemap 타일

줌 0부터 15까지의 전체 Protomaps Basemap을 일반 HTTP를 통해 단일 PMTiles 아카이브로 제공합니다. 어떤 PMTiles 클라이언트든 이 URL을 지정하면 작동합니다.

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

OpenStreetMap 타일이 차단되었거나 tile.openstreetmap.org에서 403 오류가 발생했나요? 여기서 시작하세요.

체험하기

동일한 아카이브를 세 가지 스타일로 제공합니다. 스타일을 바꾸면 이미 불러온 타일이 그대로 유지되므로 즉시 전환됩니다.

z0

직접 만들기

색상을 변경하거나 레이어를 끄면 지도에 바로 반영됩니다. 그런 다음 스타일 JSON을 복사하세요. 직접 호스팅할 수 있는 일반 MapLibre 스타일입니다. 이 타일을 가리키고 있으므로 어디에 두든 계속 작동합니다.

사용 방법

이 아카이브는 HTTP 범위 요청을 지원하므로 모든 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. 글꼴, 스프라이트 및 라이브러리

지도는 타일, 텍스트용 glyphs URL, 선택 사항인 아이콘용 sprite URL, 그리고 이를 그리는 라이브러리로 구성됩니다. 이 모든 것이 각 요소가 기대하는 경로에 준비되어 있으므로, 원한다면 단일 호스트 이름에서 지도를 전부 불러올 수 있습니다.

항목이 호스트의 URL라이선스

MapLibre 및 PMTiles 파일은 jsDelivr와 이곳에 모두 있습니다. 어느 URL을 사용해도 됩니다.

마지막 행은 이 페이지에서 사용하지 않습니다. 스크립트 태그 하나로 교체할 수 있도록 이곳에 함께 게시한 Leaflet 벡터 렌더러입니다. Leaflet 자체는 기존 CDN에서 불러옵니다.

Noto Sans Regular와 Noto Sans Medium 두 글꼴 스택은 MapLibre가 요청하는 방식대로 한 번에 256 코드포인트씩, 256개 범위로 나뉘어 있습니다. 라틴 문자뿐만 아니라 256개 범위 전체가 게시되어 있습니다. Protomaps는 지형지물에 현지어 이름을 라벨로 붙이므로 도쿄나 카이로를 표시하려면 수만 번대 범위가 필요합니다. 영어권 지도에서 쓰는 범위만 게시하면 오류 메시지도 없이 해당 라벨이 사라집니다.

업스트림 테마(flavour)별로 하나씩 총 다섯 개의 스프라이트 시트가 1x 및 2x 해상도로 제공됩니다. 아래 스타일들은 여기서 아이콘을 가져와 그립니다. 자체 스타일에 도시 지점(townspot), 고속도로 표지(highway shield) 또는 장소 아이콘을 넣으려면 sprite가 해당 테마의 시트를 가리키게 하세요. 시트에 포함된 아이콘 이름은 light.json에 나열되어 있습니다.

지도가 그리는 요소와 각 데이터의 출처

모든 레이어는 이 아카이브에 들어 있는 아홉 개 소스 레이어 중 하나를 사용하므로 두 번째 아카이브가 필요하지 않습니다. 그중 두 가지는 실제로는 누락된 것이 아닌데도 누락된 것처럼 보일 수 있어 짚어둘 만합니다:

레이어출처현재 빌드 포함 여부
지형, 도로, 건물, 수계, 육지earth, landcover, landuse, water, roads, buildings포함
장소, 도로 및 수계 이름해당 레이어의 name 필드포함
장소 아이콘, 도시 지점(townspot)pois.kind, places 및 스프라이트포함
노선 표지(route shield)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 필드를 읽는 심볼 레이어이며, 뒤에 후광(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가 없으므로 이를 요청하는 스타일은 모든 라벨을 조용히 잃게 됩니다.

어느 호스트가 더 빠른지, 그리고 어떻게 결정했는지. 추측하는 대신 직접 측정했습니다. 단일 연결에서 한 번에 하나씩 요청을 번갈아 보내고, 처음 세 샘플은 버린 뒤, 첫 바이트 도달 시간(TTFB)을 전송 시간과 분리해 측정했습니다. 이 테스트에서 모든 파일에 대해 CDN이 10~45ms 더 빠르게 응답했으며, 이는 15% 기준을 충족하고도 남는 뚜렷한 차이입니다. 라이브러리를 jsDelivr에서 불러오는 것은 이 때문입니다.

글꼴과 스프라이트는 같은 측정에서 CDN이 약 10ms 더 빠르게 응답하더라도 이곳에서 직접 제공합니다. 이는 측정이 아니라 결정의 문제입니다. 단일 호스트 이름에서 불러오는 지도는 외부 서버가 중단되어도 깨지지 않으며, 글리프 서버가 중단되면 지도에 라벨이 전혀 나오지 않기 때문입니다. 10ms 이득을 원한다면 glyphs가 protomaps.github.io/basemaps-assets/fonts를 가리키도록 지정하세요. 동일한 경로로 작동합니다. 이는 한 도시에서 측정한 수치이며, 비교 테스트는 명령어 하나로 다시 실행할 수 있습니다.

5. Leaflet 관련 안내

Leaflet의 L.tileLayer는 이미지를 그리지만, 이 타일은 벡터 타일입니다. 그래도 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, 모바일 네트워크는 주소를 공유하며, 차단은 앱뿐만 아니라 주소에도 적용됩니다. 사용자 측에서는 어느 쪽 때문인지 알 수 없습니다.

따라서 해당 서버에 요청하는 것을 멈추어야 합니다. 벗어나는 방법은 세 가지입니다.

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은 Leaflet의 타일 페인(tile pane) 안에 MapLibre 지도를 넣습니다. Leaflet 기반 앱을 유지하면서 위의 스타일이나 지형 및 음영기복도(hillshade)를 사용하고 싶을 때 적합합니다.

<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를 이미지로 그리는 pmtiles.leafletRasterLayer에서 발생합니다. 이 타일은 벡터 타일이므로 거부됩니다. 어떤 옵션을 바꾸어도 해결되지 않으며 이곳에는 래스터 사본이 없습니다. 위에 안내된 방법 중 하나를 사용하세요.

“download.openstreetmap.fr returned 403”

해당 서버는 타일 서버가 아니라 추출 및 복제 미러입니다. 2026년 10월에 과도한 다운로드 이후 주소들을 차단했으며, 운영진은 개별 검토를 통해 접근을 복구하겠다고 밝혔습니다. 이곳의 구성에는 해당 서버가 필요하지 않습니다. 이 아카이브는 Protomaps 릴리스에서 가져오므로 직접 .osm.pbf를 조립할 필요가 없습니다. OSM 데이터를 직접 받아야 한다면 분 단위 스트림 대신 정기 추출본을 사용하세요.

스타일

타일은 같고 색상만 다릅니다. 하나를 골라 복사하고 수정해서 자유롭게 사용하세요.

스타일색상 견본파일

타일에 들어 있는 데이터

페이지를 불러올 때 아카이브 자체 메타데이터에서 읽어온 내용입니다. 스타일에서 반드시 사용해야 하는 레이어 이름입니다.

소스 레이어줌필드
불러오는 중…

제공하지 않는 것

나중에 예상치 못한 문제가 생기지 않도록 명확히 밝혀둡니다.

얼마나 최신 데이터인가요?

이 타일은 한 달에 한 번 다시 빌드되는 Protomaps Basemap의 사본입니다.

스타일 JSON

open raw