PMTilesのデータをS3に設置して、ブラウザから表示する
こんにちは
Fusicの石橋です
最近、GIS関連のデータを扱うことが増えてきてPMTilesという面白いデータを知りました
なんとタイルサーバ無しで(S3などのクラウドストレージをタイルサーバにできるって表現の方が正しいかも)ラスタ、ベクタデータを配信できるとのことでした
フロントエンド側では少し特殊な実装が必要っぽいですが、プラグインなども整備されてきているので手軽に使えるようになってきている印象もあります
今回は、「国土数値情報ダウンロードサイト」
の行政区画の情報(geojson)をPMTilesに変換し、S3に設置してから、JSで表示するところまでを書いていきます
データの準備
国土数値情報ダウンロードサイトからデータをダウンロードします
任意の都道府県だけでもいいでしょうが、部分リクエストをしていい感じにできることを実感するためには全国のデータをダウンロードした方がおもしろいので今回は全国版のデータをダウンロードします
zipファイルの中身を確認するとこのようになっています
今回はこの中でgeojsonファイルを使います
geojsonをPMTilesに変換
変換ツールとして今回はtippecanoeを使います
インストール方法はMIERUNEの井口さんが分かりやすい記事を書いてくださっていたのでこちらを参考にインストールしていただければと(私もこの方法でWSLにインストールした)
tippecanoeが使えるようになったら以下のコマンドでpmtilesを作ります
tippecanoe --no-tile-compression -z12 -Z2 -o output.pmtiles N03-23_230101.geojson
tippecanoeのコマンドでややこしいのが
小文字のzが最大ズームレベル
大文字のXが最小ズームレベル
らしいですw
作成したPMTilesをS3に設置する
こちらのドキュメントに書いてある通りです
今回はS3を使うので、S3の項目を参考にしながら
- バケットポリシー
- CORS Config
の設定します
バケットの作成ができたら先ほど作成したPMTilesのデータを設置しましょう
js側の実装
React + typescriptのテンプレートを使っています
map関連のライブラリで
- mapbox-gl-js
- maplibre-gl-js
がありますが、PMTilesを読み込もうと思うと
- mapbox-gl-js
- maplibre-gl-js
が見つかりました。mapbox-gl-jsでmapbox-pmtilesを使おうと思うとなぜかnode_module配下のコードでエラーが発生してうまく使えませんでした
何かしらうまくやる方法があるのかもしれないですが、まだ解決できていないので詳しい方がいたら教えて欲しいです
maplibre-gl-js + pmtilesのパターンでは成功したので、今回はこちらの構成で表示までやります
App.tsx
import './App.css';
import MapComponent from './MapComponent'
function App() {
return (
<div className="App">
<MapComponent />
</div>
);
}
export default App;
MapComponent.tsx
import React, { useRef, useEffect, useState } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import { Protocol, PMTiles } from 'pmtiles';
const PMTILES_URL = ""; // アップロードしたS3のURL
const MapComponent: React.FC<{}> = () => {
const mapContainer = useRef(null);
const map = useRef<any>(null);
const [lat, setLat] = useState(33.5676);
const [lon, setLon] = useState(130.4102);
const [zoom, setZoom] = useState(9);
useEffect(() => {
if (map.current) return; // initialize map only once
const protocol = new Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
const p = new PMTiles(PMTILES_URL);
protocol.add(p);
map.current = new maplibregl.Map({
container: mapContainer.current!,
center: [lon, lat],
zoom: zoom,
style: {
version: 8,
sources: {
"osm": {
"type": "raster",
"tiles": ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"],
"tileSize": 256,
"attribution": '© <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'
},
"gyousei-kukaku": {
"type": "vector",
"url": "pmtiles://" + PMTILES_URL,
"attribution": '© <a href="https://openstreetmap.org">OpenStreetMap</a>'
}
},
layers: [
{
"id": "osm-tiles",
"type": "raster",
"source": "osm",
"minzoom": 0,
"maxzoom": 19,
},
{
"id": "sample-layer",
"source": "gyousei-kukaku",
"source-layer": "N0323_230101",
"type": "fill",
"paint": {
"fill-color": "#00ffff",
"fill-opacity": 0.4,
"fill-outline-color": "#ff0000",
}
},
]
}
});
}, [lat, lon, zoom]);
return (
<div className="App">
<div ref={mapContainer} className="map-container" style={{ height: '100vh', width: '100%' }} />
</div>
);
};
export default MapComponent;
こんな感じにしてあげると
いい感じに地図の上に行政区画が表示されました!
開発者コンソールでネットワークを監視しながら地図をぐりぐり動かしてみると、output.pmtilesからデータを取ってきているのがわかりますね
ステータスコードをみてみると普段あまり見かけない
Status Code: 206 Partial Content
とあります
これはPMTilesからデータを取得する際にHTTP range requestsという手法をつかうからだそうです
詳しく知りたい方はこちらを見ると良いかと
おさらい
PMTilesをつかうと、タイルサーバをS3などのクラウドストレージで代用することができる
→ サーバ費用をめっちゃ抑えられる可能性があるかも
PMTilesの中身を更新するのはちょっと難しそう(おそらくgeojson側を更新して、再度PMTilesを作り直してS3に設置しなおすって感じになるのかな?)
更新頻度が高いデータの場合は運用の仕方を事前にちゃんと考えて導入するかしないかは検討しないと行けなさそう
とはいえ、大量のデータを1つのファイルにまとめて、そのファイルの1部分だけを取得していい感じに表示できるなんてすごいなぁ
Discussion