PLATEAUのCityGML LOD2 を MVTに変換するツールを作ってみた

# 1. 概要
[ベクトルタイルAdvent Calendar 2021](https://qiita.com/advent-calendar/2021/vt)の@frogcatさんの記事『[PLATEAUのCityGML Building LOD2をMVTにしてみた](https://qiita.com/frogcat/items/84b413c7e08c00b78c6a)』は、[PLATEAU](https://www.mlit.go.jp/plateau/)のLOD2モデルを[mapbox-gl.js](https://docs.mapbox.com/jp/mapbox-gl-js/overview/)／[maplibre-gl-js](https://github.com/maplibre/maplibre-gl-js)の「[fill-extrusion](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#fill-extrusion)」で描画するというものです。このLOD2-MVTは[3DTiles](https://github.com/CesiumGS/3d-tiles)のような表現力はないですが、相対的に軽く扱いやすいので、非常に興味を持ちました。
また、東京都のLOD2-MVTタイルセットが[indigo-lab/plateau-lod2-mvt](https://github.com/indigo-lab/plateau-lod2-mvt)で公開されています。

https://qiita.com/frogcat/items/84b413c7e08c00b78c6a

https://github.com/indigo-lab/plateau-lod2-mvt

上記の記事ではCityGMLから地物のGeojsonを生成し、[tippecanoe](https://github.com/mapbox/tippecanoe)でMVTを生成していますが、ちょっと手間がかかるので、勉強がてら、[citygml4j](https://github.com/citygml4j)と[mapbox-vector-tile-java](https://github.com/wdtinc/mapbox-vector-tile-java)を使って、手っ取り早くLOD2-MVTを生成するツールを作ってみました。
![a006.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/a6a05571-cb48-d84f-e0fc-c73e55df8d2c.jpeg)

# 2. 作成物
MVTの仕様とか、色々と理解不足がありますが、とりあえず、CityGMLを読み込み、LOD2-MVTを出力するツールを作ってみました。
実行環境はJRE1.8以降です。
実行形式のJARファイルは『[ココ](https://www.termat.net/download/Lod2Mvt.jar)』からダウンロードできます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/f5761567-2b80-e119-52c1-c6d540bcff7a.jpeg" width="640"></img>
<font color="red">※変換するPlateau-CityGMLは、iurのXMLSchemaとロケーション属性が1.5版である必要があります。

https://www.chisou.go.jp/tiiki/toshisaisei/itoshisaisei/iur/index.html

# 3. 使い方
使い方は以下のとおりです。
<ol>
<li>入力先にGityGMLが入ったディレクトリを指定します。※D&D可能</li>
<li>出力先にMVTの出力先のディレクトリを設定します。※D&D可能</li>
<li>出力する最小・最大のズームレベルを設定します。</li>
<li>モデルの属性（GenericAttributeのみ）をMVTに設定するか選択します。</li>
<li>「処理開始」ボタンを押すと処理を開始します。</li>
</ol>
現在対応している都市モデルは、BUILDING、BRIDGE、WATER_BODYです。それぞれMVTの「BUILDINGレイヤー」、「BRIDGEレイヤー」、「WATER_BODYレイヤー」にデータが保持されます。同じ出力先を指定して、BUILDING、BRIDGE等の別モデルを処理した場合、複数レイヤーを持つMVTが生成されます。

# 4. 作成したクラス／ソースコード
作成したクラスは以下のとおりです。
今回はメモリを節約するため、1ファイルごとにMVTを生成し、同じタイル座標のMVTが存在する場合は地物又はレイヤーを追加してMVTを更新しています。
メモリに余裕があれば、すべてのファイルを読み込み、一括でMVTを生成した方が効率的だと思います。
<ul>
<li>Lod2MvtApp.java ---------- GUIを構築するクラス。GUI TookkitはSwingを使用しています。</li>
<li>TileMeshUtil.java --------- 緯度経度からタイル座標を算出するユーティリティクラス。</li>
<li>GMLToJsonUtil.java -------- CityGMLからGeojsonデータを生成するユーティリティクラス。</li>
<li>MVTBuilder.java ---------- GeojsonデータからMVTを生成するクラス。</li>
<li>JtsAdapterReverse.java --- JtsAdapterクラスをPLATEAU向けに修正したクラス。</li>
</ul>
参考として、以下にソースコードをアップしておきます。

https://github.com/termat/lod2mvt/tree/main

# 5. LOD2-MVTの使用例
mapbpx-gl.jsでの使用例を以下に示します。
なお、VS-CodeのLiveServerでHTML/JS及びタイルの動作を確認しました。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="utf-8">
    <title>テスト</title>
	<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
	<script src="https://api.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.js"></script>
	<link href="https://api.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.css" rel="stylesheet">
    <style type="text/css">
		html{height:100%; margin: 0px;}
		body { margin: 0; padding: 0; height:100%;}
		#main {
			position:relative;
			transition: margin-left .5s;
			padding: 16px;
			height:96%;
		}
		#map { 
   			position: absolute; 
   			top: 0; right: 0; 
   			bottom: 0; left: 0;
  			width:100%;
   			height:100%; 
		}
	</style> 
</head>
<body>
<div id="main">
 	<div id='map'></div>
</div>
<script>
	const data={
				"version": 8,
				"sources": {
					"t_pale": {
						"type": "raster",
							"tiles": ["https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg"],
							"tileSize": 256,
						}
					},
					"layers": [{
						"id": "t_pale",
						"type": "raster",
						"source": "t_pale",
						"minzoom": 9,
						"maxzoom": 18
					}]
				};

	mapboxgl.accessToken = 'アクセストークン';
	const map = new mapboxgl.Map({
		container: 'map',
		zoom: 13.1,
		maxZoom: 18,
		minZoom: 9,
		center: [139.7673068,35.6809591],
		pitch: 70,
		bearing: 0,
		antialias: true,
		style: data
	});

	map.on('load', () => {
 		map.addLayer({
			'id': 'sky',
			'type': 'sky',
			'paint': {
				'sky-type': 'atmosphere',
				'sky-atmosphere-sun': [0.0, 0.0],
				'sky-atmosphere-sun-intensity': 15
			}
		});
		map.addSource("mvt", {
			"type": "vector",
			"tiles": ["http://127.0.0.1:5500/mvt/{z}/{x}/{y}.pbf"],
			"minzoom":12,
			"maxzoom": 18
		});	
		map.addLayer({
			"id": "bldg",
			"type": "fill-extrusion",
			"source": "mvt",
			"source-layer": "BUILDING",
			"minzoom": 12,
			"maxzoom": 18,
			"paint": {
				"fill-extrusion-color": "#6666ff",
				"fill-extrusion-height": ["get", "height"]
			}
		});
		map.addLayer({
			"id": "brid",
			"type": "fill-extrusion",
			"source": "mvt",
			"source-layer": "BRIDGE",
			"minzoom": 12,
			"maxzoom": 18,
			"paint": {
				"fill-extrusion-color": "#66ff66",
				"fill-extrusion-height": ["get", "height"]
			}
		});
		map.addLayer({
			"id": "water",
			"type": "fill",
			"source": "mvt",
			"source-layer": "WATER_BODY",
			"minzoom": 12,
			"maxzoom": 18,
			"paint": {
				'fill-color': {
					property: 'height',
					stops: [
						[0, '#ffffff'],
						[0.5, '#ff0000'],
					]
				},
				'fill-opacity': 0.5,
			}
		});
		map.addSource("jgd", {
			"type": "vector",
			"tiles": ["https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf"],
			"minzoom":9,
			"maxzoom": 16
		});	
		map.addLayer({
			"id": "jgd",
			"type": "line",
			"source": "jgd",
			"source-layer": "road",
			"minzoom": 9,
			"maxzoom": 18,
			"paint": {
					'line-opacity': 1.0,
					'line-color': 'rgb(80, 80, 80)',
					'line-width': 2
				}
		});
		map.addControl(new mapboxgl.FullscreenControl());
		map.addControl(new mapboxgl.NavigationControl());
		map.addSource('mapbox-dem', {
			'type': 'raster-dem',
			'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
			'tileSize': 512,
			'maxzoom': 14
		});
		map.setTerrain({ 'source': 'mapbox-dem', 'exaggeration': 1.0 });
	});
</script>
</body>
</html>
```
■LOD2-MVT変換例（東京都港区付近）
![c01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/eb905cc2-b957-595f-8f29-d5c9e155bbcb.jpeg)
■LOD2-MVT変換例（東京タワー）※PLATEAUのbldgデータ
![c03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/46d75d50-9439-4a10-3484-6a1d2bedf1aa.jpeg)
■LOD2-MVT変換例（レインボーブリッジ） ※PLATEAUのbridデータ
![a002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/82076a0e-a639-afec-dbc2-9fb449162128.jpeg)
■LOD2-MVT変換例（荒川の浸水予測情報）　※PLATEAUのfldデータ
![a003.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/7db58839-ac26-ddc0-f72b-8800fbe01e32.jpeg)

# 6. その他
最近、3DTilesやオルソ画像のPLATEAU配信サービス（試験運用）が始まっており、色々と楽しみですが、データ量が大きいので現在はちょっと使い難い感じします。
LOD1ではちょっと味気ない、でも処理を軽くしたい時にLOD2-MVTを使用する場合があるかもと思っています。

https://github.com/Project-PLATEAU/plateau-streaming-tutorial


【出典など】
本エントリで使用した画像は、[3D都市モデル（Project PLATEAU）東京都23区（CityGML 2020年度）](https://www.geospatial.jp/ckan/dataset/plateau-tokyo23ku-citygml-2020)を加工して作成したものです。
 本ツールでのデータセットの使用・加工にあたっては、[PLATEAU Policy](https://www.mlit.go.jp/plateau/site-policy/)を確認し、権利者の権利を侵害しないように留意してください。
<ul>
【免責事項】
<li>明示、暗黙を問わず内容に関してはいかなる保証も適用しません。</li>
<li>ツール等の利用により、何らかのトラブルや損失・損害等が生じた場合、著者は損害、損失に対していかなる場合も一切の責任を負いません。</li>
<li>全ての情報について、内容の合法性・正確性・安全性等、あらゆる点において保証しません。</li>
</ul>