LoginSignup
3
3

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-05-15

1. 概要

ベクトルタイルAdvent Calendar 2021@frogcatさんの記事『PLATEAUのCityGML Building LOD2をMVTにしてみた』は、PLATEAUのLOD2モデルをmapbox-gl.jsmaplibre-gl-jsの「fill-extrusion」で描画するというものです。このLOD2-MVTは3DTilesのような表現力はないですが、相対的に軽く扱いやすいので、非常に興味を持ちました。
また、東京都のLOD2-MVTタイルセットがindigo-lab/plateau-lod2-mvtで公開されています。

上記の記事ではCityGMLから地物のGeojsonを生成し、tippecanoeでMVTを生成していますが、ちょっと手間がかかるので、勉強がてら、citygml4jmapbox-vector-tile-javaを使って、手っ取り早くLOD2-MVTを生成するツールを作ってみました。
a006.jpg

2. 作成物

MVTの仕様とか、色々と理解不足がありますが、とりあえず、CityGMLを読み込み、LOD2-MVTを出力するツールを作ってみました。
実行環境はJRE1.8以降です。
実行形式のJARファイルは『ココ』からダウンロードできます。

※変換するPlateau-CityGMLは、iurのXMLSchemaとロケーション属性が1.5版である必要があります。

3. 使い方

使い方は以下のとおりです。

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

4. 作成したクラス/ソースコード

作成したクラスは以下のとおりです。
今回はメモリを節約するため、1ファイルごとにMVTを生成し、同じタイル座標のMVTが存在する場合は地物又はレイヤーを追加してMVTを更新しています。
メモリに余裕があれば、すべてのファイルを読み込み、一括でMVTを生成した方が効率的だと思います。

  • Lod2MvtApp.java ---------- GUIを構築するクラス。GUI TookkitはSwingを使用しています。
  • TileMeshUtil.java --------- 緯度経度からタイル座標を算出するユーティリティクラス。
  • GMLToJsonUtil.java -------- CityGMLからGeojsonデータを生成するユーティリティクラス。
  • MVTBuilder.java ---------- GeojsonデータからMVTを生成するクラス。
  • JtsAdapterReverse.java --- JtsAdapterクラスをPLATEAU向けに修正したクラス。
参考として、以下にソースコードをアップしておきます。

5. LOD2-MVTの使用例

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

<!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
■LOD2-MVT変換例(東京タワー)※PLATEAUのbldgデータ
c03.jpg
■LOD2-MVT変換例(レインボーブリッジ) ※PLATEAUのbridデータ
a002.jpg
■LOD2-MVT変換例(荒川の浸水予測情報) ※PLATEAUのfldデータ
a003.jpg

6. その他

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

【出典など】
本エントリで使用した画像は、3D都市モデル(Project PLATEAU)東京都23区(CityGML 2020年度)を加工して作成したものです。
本ツールでのデータセットの使用・加工にあたっては、PLATEAU Policyを確認し、権利者の権利を侵害しないように留意してください。

    【免責事項】
  • 明示、暗黙を問わず内容に関してはいかなる保証も適用しません。
  • ツール等の利用により、何らかのトラブルや損失・損害等が生じた場合、著者は損害、損失に対していかなる場合も一切の責任を負いません。
  • 全ての情報について、内容の合法性・正確性・安全性等、あらゆる点において保証しません。
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3