PMTilesに複数のレイヤを格納して、maplibre-gl-jsで重ねて表示する
こんにちは
Fusicの石橋です
前回の記事でgeojsonからPMTilesを作成してS3に設置
その後、maplibre-gl-jsから可視化するところまで実装しました
今回は、複数のレイヤ(複数のgeojsonファイル)をPMTilesに格納し
maplibre-gl-js側でそれぞれを重ねて表示する実装をしていきます
データ準備
前回は行政区画だけでしたが、今回はそれに追加してハザードマップを追加してみます
- https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v3_1.html
- https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A31-v3_0.html
ハザードマップは全国データは無く、各都道府県レベルのデータでした
今回は福岡県のデータを使います
展開した後のデータを見てみると、各市レベルでファイルになっていることが分かりますね
今回はとりあえず一つのファイルにまとめちゃいます
こんな感じでパッと結合してfukuoka-hazard-map.geojsonというファイルにしちゃいます
(ハザードマップってかなり細かいレベルで作られてて結構大きなデータ量になるんですね。ファイル構成の理由が分かった気がする)
import json
import pandas as pd
import geopandas as gpd
from pathlib import Path
# 入力ディレクトリと出力ファイルパスを設定
input_directory = Path("../data/hazard-map/A31-21_40_GML/03_浸水継続時間/GEOJSON")
output_file = Path("fukuoka-hazard-map.geojson")
# GeoJSONファイルのリストを取得
geojson_files = list(input_directory.glob("*.geojson"))
# すべてのGeoJSONファイルを読み込んでリストに追加
gdfs = [gpd.read_file(geojson_file) for geojson_file in geojson_files]
# リスト内のすべてのGeoDataFrameを結合
merged_gdf = gpd.GeoDataFrame(pd.concat(gdfs, ignore_index=True))
# 結合したデータを新しいGeoJSONファイルとして保存
merged_gdf.to_file(output_file, driver="GeoJSON")
print(f"結合したファイルを {output_file} に保存しました。")
PMTilesの作成
前回同様geojsonからPMTilesを作成します
tippecanoe --no-tile-compression -z15 -Z2 -o output.pmtiles gyousei-kukaku/japan/N03-23_230101.geojson hazard-map/fukuoka-hazard-map.geojson
- 上限のズームレベルを前回よりちょっと上げました
- ファイルを2つ指定している
というのが前回のコマンドとの差分になるかと
このように複数ファイルの入力を受け付けてくれます
オプションコマンドでレイヤ名を指定することもできるそうですが、特に何も指定しなければファイル名がそのままレイヤ名になるそうです
また、複数ファイルを一つのレイヤにまとめることとかもできるそうです
作成したpmtilesのファイルは前回同様S3に設置しておきます
js側での実装
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 = "[PMTIlesのS3パス]";
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>'
},
"fukuoka-hazardmap": {
"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",
},
},
{
"id": "hazard-map",
"source": "fukuoka-hazardmap",
"source-layer": "fukuokahazardmap",
"type": "fill",
"paint": {
"fill-color": "#ff0000",
"fill-opacity": 0.4,
"fill-outline-color": "#0000ff",
},
},
]
}
});
}, [lat, lon, zoom]);
return (
<div className="App">
<div ref={mapContainer} className="map-container" style={{ height: '70vh', width: '100%' }} />
</div>
);
};
export default MapComponent;
こんな感じで表示できました!
ただ結構重いですねw
ハザードマップはプロパティにレベルが格納されているので、そのレベルごとに前処理をして分けておくといいかもしれませんね
(浸水リスク、低、中、高とかのそれぞれのレイヤを作っておくと表示非表示とかが簡単そうだし)
とまあ、こんな感じでできました!
Discussion