PMTiles化した筆ポリゴンデータをLeafletで表示させる
農水省筆ポリゴンをLeafletで表示させるのに苦労する
ベクトルデータについては、最近FlatGeoBufとPMTilesという新しいフォーマットが出てきているようです。
(PMTilesはベクトルデータに加えラスターデータも取り扱えるようです)
今回はPMTilesを選択して、クライアントPCでの表示を行おうと思いました。
PMTilesデータのクライアント表示については、MapLibre GLやMapbox GL JSでの事例が数多く上がっていますが、Leafletでの事例はあまり上がっていません。
ここで今回は苦労したので、情報を共有しておこうと思っています。
<script src="https://unpkg.com/protomaps-leaflet@latest/dist/protomaps-leaflet.min.js"></script>
<script>
const map = L.map('map')
var layer = protomapsL.leafletLayer({url:'FILE.pmtiles OR ENDPOINT/{z}/{x}/{y}.pbf'})
layer.addTo(map)
</script>
基本的にはprotomaps-leafletのコードを参照するのですが、それだけだとちょっと情報が足りません。
例えば上に挙げたサンプルスクリプトだとPMTilesの読み込み形式ではありませんね。
今回は筆ポリゴンの表示、とポリゴンに含まれているデータのポップアップ表示までしてみます。
因みにVue3+Quasar Frameworkでの環境となります。
表示自体はそれほど難しくない、けど
筆ポリゴンを表示するだけであれば、以下のようなコードで書いていくと表示できると思います。
protomaps-leafletで筆ポリゴンを表示する場合、重要な個所としてはprotomapsL.leafletLayerでのインスタンス作成時に与えるpaint_rulesと、paint_rules定義時のsymbolizerの定義となります。
import * as protomapsL from "protomaps-leaflet";
// map定義は各々やりたいように
const map = L.map("map_canvas")
let PAINT_RULES = [
{
dataLayer: "target_fude_year", // PMTiles作成時に定義したレイヤー名
symbolizer: new protomapsL.PolygonSymbolizer({
fill: "steelblue",
do_stroke: true,
opacity: { value: 0.3, per_feature: true },
stroke: { str: "black", per_feature: true },
}),
minzoom: 12,
maxzoom: 18,
},
];
const fudeSource = protomapsL.leafletLayer({
url: "fude_url" + ".pmtiles", // サーバに展開しているPMTilesのアドレス
attribution:
"<a href='http://www.maff.go.jp/j/tokei/porigon/index.html' target='_blank'>筆ポリゴンデータ(農林水産省)を加工して作成</a>",
paint_rules: PAINT_RULES,
// debug: true, // debugをtrueにすると、タイル番号などを画面上に出力します
});
fudeSource.addTo(map)
ただsymbolizerで上記のように定義した場合、表示は出来ますが筆ポリゴンの情報をクライアント側から何も引っ張って来れませんし、透明度についても定義しても反映してくれませんでした。
この状態のままだとあまりメリットが無いので何とか透明度や筆ポリゴン情報を表示させたいですね。
ここが今回最も苦労したところです。
ちなみに、ここぐらいまでのコードであればChatGPTなどで出せると思います。自分はBingのGPTで作成してみました。
paint_rulesとsymbolizerの定義が特に重要
結論を言うと、custom symbolizerを作り、そこに筆ポリゴンの情報を表示させるコードを展開します。
class FarmSymbolizer {
draw(context, geom, z, feature) {
// "feature" contains the properties of the vector tile feature
let attributes = feature.props;
context.globalAlpha = 0.5;
context.lineWidth = z / 15;
// 塗り色をランドタイプで選択
if (attributes.land_type == 100) {
context.fillStyle = "yellowgreen";
context.strokeStyle = "teal";
} else {
context.fillStyle = "lightyellow";
context.strokeStyle = "saddlebrown";
}
context.beginPath();
for (let poly of geom) {
let p0;
for (let p = 0; p < poly.length - 1; p++) {
let pt = poly[p];
if (p == 0) {
context.moveTo(pt.x, pt.y);
p0 = pt;
} else {
context.lineTo(pt.x, pt.y);
}
}
context.lineTo(p0.x, p0.y);
}
// Now you can access the attributes
// Add a circular marker to the map at the feature's location
if (z > 15) {
let marker = L.circleMarker(
[attributes.point_lat, attributes.point_lng],
{
radius: 3,
color: "seagreen",
fillColor: "seagreen",
fillOpacity: 1,
}
).addTo(markerLayer);
// When the marker is clicked, output the feature's properties
let contents =
"<table><tr><td style='width:40%'>ID</td><td>" +
attributes.polygon_uuid +
"</td></tr>";
if (attributes.land_type == 100) {
contents += "<tr><td>田畑区分</td><td>田</td></tr>";
} else {
contents += "<tr><td>田畑区分</td><td>畑</td></tr>";
}
contents += "</table>";
marker.bindPopup(contents);
} else {
map.removeLayer(markerLayer);
}
// Rest of your drawing code here...
context.stroke();
context.fill();
}
let PAINT_RULES = [
{
dataLayer: "target_fude_year", // PMTiles作成時に定義したレイヤー名
symbolizer: new FarmSymbolizer(),
minzoom: 12,
maxzoom: 18,
},
];
上記はFarmSymbolizerというカスタムクラスを作成しています。
ここでは田畑の区分でポリゴンを色分けし、ズームレベル15より大きい時、サークルマーカーを表示して、それをクリックすると筆ポリゴンの情報を表示する、というコードとなります。
そして、PAINT_RULESのsymbolizerの部分をこのカスタムクラスに置き換える事で、Leaflet上で、PMTiles化した筆ポリゴンの情報を表示できるようになりました。
筆ポリゴンの情報をクライアントに表示させる場合、例えばポリゴンを選択すると表示する、という事が出来れば良いのですが、protomaps-leafletでは、ポリゴン自体にクリックイベントを紐づける事は出来ませんでした。
そのため、CircleMakerを使うという事になりましたが、他に良いアイデアがあるかもしれませんね。
と言う訳で、地図や衛星データの情報をまとめるべくnoteのマガジンを作りました!
私が共同代表を務めているGREEN OFFSHOREでは農業向けのIoTサービスを展開しています。
そちらにも様々な情報を展開しているので、ぜひ上記リンクをクリックしてみて下さい。