Protomaps Basemap タイル

ズーム 0 から 15 までの Protomaps Basemap 全体を、1 つの PMTiles アーカイブとして通常の HTTP で配信しています。任意の PMTiles クライアントに URL を指定すれば動作します。

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

OpenStreetMap タイルのブロックや、tile.openstreetmap.org からの 403 エラーでお困りですか? こちらから始めてください。

試してみる

同じアーカイブを 3 つのスタイルで表示できます。スタイルを切り替えても読み込み済みのタイルはそのまま使われるため、即座に反映されます。

z0

自分のスタイルを作る

色を変更したりレイヤーを非表示にしたりすると、地図がその場で更新されます。その後、スタイル JSON をコピーしてください。これは自前でホストできる通常の MapLibre スタイルです。このタイルを参照しているため、どこに配置してもそのまま動作します。

使い方

このアーカイブは HTTP 範囲リクエストに対応しているため、すべての PMTiles クライアントから読み取れます。

1. ページに地図を表示する

このファイルをコピーしてください。タグ 3 つ、スタイル URL 1 つ、地図 1 つだけの完全なサンプルです。両方のライブラリはバージョン固定で 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 スタイルはソースからタイルを読み取ります。このタイルを使うには、その 1 つの 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 ボタンを押すと、編集内容を含め、この地図が現在描画しているスタイルが開きます。ダイアログにはコピーボタンがあります。出力されたスタイルの使い方は 2 通りあり、どちらも通常のウェブページとして機能します。

このホストの 3 つのスタイルと同様に、ページの隣にファイルとして保存し、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 内に直接記述します。移動するファイルが 1 つで済み、別途ホストするものもありません。配置場所が違うだけで同じスタイルです:

// 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 です。別のスキーマ向けに書かれたスタイルは、読み込まれてもソースレイヤー名が一致しないため何も描画されません。9 つのレイヤー名と、コピーして使える完成済みの v3 スタイルを以下に掲載しています。

4. フォント、スプライト、ライブラリ

地図はタイル、テキスト用の glyphs URL、必要に応じたアイコン用の sprite URL、そしてそれらを描画するライブラリで構成されます。これらはすべて、それぞれが期待するパスでこのホスト上に用意されているため、必要であれば単一のホスト名から地図全体を配信できます。

項目このホストの URLライセンス

MapLibre と PMTiles のファイルは jsDelivr とこのホストの両方にあります。どちらの URL でも動作します。

最後の行はこのページでは使用されていません。これは Leaflet 用のベクターレンダラーで、スクリプトタグ 1 つで差し替えられるようここで公開しています。Leaflet 本体は公式の CDN のままにします。

Noto Sans Regular と Noto Sans Medium の 2 つのフォントスタックを、MapLibre が要求する 256 コードポイントずつの 256 範囲に分割しています。ラテン文字だけでなく 256 範囲すべてを公開しています。Protomaps は地物に現地の名称でラベルを付けるため、東京やカイロでは数万番台の範囲が必要になります。英語の地図が使う範囲だけを公開すると、エラーも出ないままそれらのラベルが消えてしまいます。

アップストリームのフレーバーごとに 1 つずつ、1x と 2x の 5 つのスプライトシートがあります。以下のスタイルはこれらからアイコンを描画するため、これらが取得されます。自作のスタイルで市街地マーク、高速道路の路線番号標識、POI アイコンを表示したい場合は、使用するフレーバーに対応するものを sprite に指定してください。シート内のアイコン名は light.json に記載されています。

地図が描画するものと、そのデータ元

すべてのレイヤーはこのアーカイブ内の 9 つのソースレイヤーのいずれかを使うため、別のアーカイブは不要です。そのうち 2 つは、不具合で欠けているように見えやすいため特記しておきます:

レイヤーデータ元このビルドに含まれるか
地形、道路、建物、水域、陸地earth, landcover, landuse, water, roads, buildingsあり
地名、道路名、水域名同じレイヤーの name フィールドあり
POI アイコン、市街地マークpois.kind、places、およびスプライトあり
路線番号標識roads.shield_textなし。現在配信中のビルドにはこのフィールドがありません
住居番号buildings.kind=address および buildings.addr_housenumberなし(理由は同じ)

別の住所アーカイブはなく、取得すべき住所レイヤーもありません。アップストリームのプロジェクトでは、住所ポイントを buildings レイヤーの内部に、kind=address と addr_housenumber を持つ地物として格納しています。このフィールドはこの地図が配信しているものより後の Protomaps ビルドで追加されたため、住居番号レイヤーはすでに組み込まれており、次回の月次更新でデータが入ると自動的に有効になります。路線番号のテキストも同様です。現在のビルドには路線番号の文字数だけを表す shield_text_length しかなく文字列そのものがないため、これを描画するとすべての高速道路に空白の標識が表示されてしまいます。

自分でレイヤーを記述して色を着ける

レイヤーはフィルター、ズーム、形状、色で構成されます。フィルターには誰もが引っかかる 2 つの落とし穴があり、どちらもエラーを出さずに失敗します:

道路、その名称、および線を非表示にする切り替えの例です:

{
  "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 フィールドを読み取るシンボルレイヤーで、背景に縁取り(ハロー)を付けるか何も付けないかを指定します。グリフが見つからない場合もエラーは出ないため、指定するフォントスタックはこのホストにある 2 つのうちのいずれかでなければなりません:

{
  "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",

上記の 2 つ以外のフォントスタックをスタイルが要求した場合、テキストは何も描画されません。Protomaps 公式のスタイルは太字に Noto Sans Medium を使用しています。このセットには Noto Sans Bold がないため、それを要求するスタイルではエラーも出ないまますべてのラベルが消えてしまいます。

どちらのホストが速いか、そしてどう判断したか。推測ではなく実測しました。1 つの接続で 1 件ずつ交互にリクエストを送り、最初の 3 サンプルを除外し、最初のバイトが届くまでの時間(TTFB)を転送時間と分離して計測しました。このテストでは、すべてのファイルで CDN の方が 10〜45 ミリ秒速く応答し、15% ルールを明確に上回る差がつきました。そのため、ライブラリは jsDelivr から読み込んでいます。

同じ計測で CDN の方が約 10 ミリ秒速く応答するという結果が出ていますが、フォントとスプライトはこのホストから配信しています。これは計測結果ではなく設計上の判断です。単一のホスト名から配信される地図は他所の障害で壊れることがなく、グリフサーバーが落ちると地図からラベルが消えてしまうためです。10 ミリ秒の短縮を優先する場合は、glyphs に protomaps.github.io/basemaps-assets/fonts を指定すれば同じパスで動作します。これはある 1 都市からの計測値であり、比較の再実行は 1 つのコマンドで行えます。

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);

2 つのクラスに注意してください。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 アドレスが共有されており、ブロックはアプリだけでなくアドレスに対しても適用されます。利用者側からはどちらが原因かを判別できません。

そのため、それらのサーバーへのリクエストを止める必要があります。移行方法は 3 つあります。

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 のタイルペイン内に MapLibre の地図を配置します。アプリを 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 つ、アダプターは不要で、上記のスタイルがそのまま動作します。ページ全体のコードは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 データを取得する場合は、分単位のストリームではなく定期的な抽出データを使用してください。

スタイル

同じタイルで、配色が異なります。1 つ選んでコピーし、変更して自分のものとして使えます。

スタイル外観ファイル

タイルに含まれるデータ

ページ読み込み時にアーカイブ自身のメタデータから読み取った内容です。スタイルではこれらのレイヤー名を使用する必要があります。

ソースレイヤーズームフィールド
読み込み中…

このサービスが提供しないもの

後から想定外の事態にならないよう、あらかじめ明記しておきます。

データの鮮度について

このタイルは Protomaps Basemap のコピーであり、月 1 回再ビルドされます。

スタイル JSON

open raw