3dcelの都市3DモデルをThree.jsで表示してみる。

## 概要
「[3D City Experience lab.](https://3dcel.com/)」では、渋谷駅周辺の都市3Dモデルがオープンデータで公開されています。
サイトではデータ閲覧ソフトとして[MeshLab](http://www.meshlab.net/)が紹介されているのですが、「これはやっぱりWebGLで表示したいよね」と思い、[Three.js](https://threejs.org/)で表示してみました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">都市3Dデータがオープンデータで公開されるとか、すごい時代になったもんだなあ。<br>渋谷の多様性が3Dデータによって浮かび上がる都市の新しい姿を捉えるビジュアライゼーション <a href="https://t.co/66XfC7ZBP0">https://t.co/66XfC7ZBP0</a></p>&mdash; t_mat (@t_mat) <a href="https://twitter.com/t_mat/status/1142767025481064449?ref_src=twsrc%5Etfw">June 23, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 使用したデータ・ライブラリ等
・渋谷駅周辺3Dデータ：https://3dcel.com/opendata/
・Three.js boilerplate：https://github.com/learnthreejs/three-js-boilerplate
※boilerplateはちょっと古いのですが、このまま使いました。

## ファイル構成
ファイル構成は以下のとおりとしました。
なお、SizeLディレクトリ配下のobj等は[3dcel](https://3dcel.com/opendata/)よりダウンロードした渋谷駅周辺の都市3Dデータです。

- index.html
- script.js
- assets
    - MTLLoader.js
    - OBJLoader.js
    - OrbitControls.js
    - SizeL
        - Tile_173078_LD_010_017_L22.mtl
        - Tile_173078_LD_010_017_L22.obj
        - Tile_173078_LD_010_017_L22_0.jpg

## ソースコード
index.htmlは、ほぼboilerplateをそのまま使っています。

```html:index.html
<html>
	<head>
		<title>渋谷駅周辺3Dデータ</title>
		<style>
			body { margin: 0; }
			canvas { width: 100%; height: 100% }
		</style>
	</head>
	<body>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
		<script src="assets/OrbitControls.js"></script>
		<script src="assets/OBJLoader.js"></script>
		<script src="assets/MTLLoader.js"></script>
		<script src="scripts.js"></script>
	</body>
</html>
```
script.jsは以下のとおりとしました。
モデルは平面直角第9系の座標になっているので、モデル読み込み後、中心を原点に移動させています。

```javascript:script.js
let scene = new THREE.Scene();

let camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 100000 );
camera.position.z = 300;
camera.position.x = 0;
camera.position.y = 0;
camera.lookAt(new THREE.Vector3(0, 0, 0));

let renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

let controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = false;
controls.dampingFactor = 0.1;
controls.enableZoom = true;

let keyLight = new THREE.DirectionalLight(new THREE.Color('hsl(30, 100%, 75%)'), 1.0);
keyLight.position.set(-100, 0, 100);
let fillLight = new THREE.DirectionalLight(new THREE.Color('hsl(240, 100%, 75%)'), 0.75);
fillLight.position.set(100, 0, 100);
let backLight = new THREE.DirectionalLight(0xffffff, 1.0);
backLight.position.set(100, 0, -100).normalize();

scene.add(keyLight);
scene.add(fillLight);
scene.add(backLight);

let axis = new THREE.AxisHelper(10000);
axis.position.set(0,0,0);
scene.add(axis);

let mtlLoader = new THREE.MTLLoader();
mtlLoader.setTexturePath('assets/SizeL/');
mtlLoader.setPath('assets/SizeL/');
mtlLoader.load('Tile_173078_LD_010_017_L22.mtl', function (materials) {
    materials.preload();
    let objLoader = new THREE.OBJLoader();
    objLoader.setMaterials(materials);
    objLoader.setPath('assets/SizeL/');
    objLoader.load('Tile_173078_LD_010_017_L22.obj', function (object) {
        scene.add(object);   
        fitCamera(object,camera);
    });

});

let animate = function () {
	requestAnimationFrame( animate );
	controls.update();
	renderer.render(scene, camera);
};

let getCompoundBoundingBox=function(object3D) {
    var box = null;
    object3D.traverse(function (obj3D) {
        var geometry = obj3D.geometry;
        if (geometry === undefined) return;
        geometry.computeBoundingBox();
        if (box === null) {
            box = geometry.boundingBox;
        } else {
            box.union(geometry.boundingBox);
        }
    });
    return box;
}

var fitCamera=function(object, camera) {
    let bbox=getCompoundBoundingBox(object);
    let xx=(bbox.max.x+bbox.min.x)/2;
    let yy=(bbox.max.y+bbox.min.y)/2;
    let zz=(bbox.max.z+bbox.min.z)/2;
    object.position.set(-xx,-yy,-zz);
}
animate();
```

## 実行結果
VSCodeのliveServer等でindex.htmlを立ち上げると、しばらく待つとモデルが表示されます。

![渋谷駅周辺3Dデータ - Google Chrome 2019_06_26 1_14_07 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/d83c44f0-bef4-4c07-dd54-55abcc7e8097.png)
渋谷駅周辺3Dデータ©PASCO CORPORATION クリエイティブ・コモンズ・ライセンス（表示4.0 国際）
https://creativecommons.org/licenses/by/4.0/

## GoogleMapと比べてみる
表示したモデルをGoogleMapの3Dモデル、ストリートビューと比較してみました。
あたり前ですが、一致していました。

[GoogleMap3D](https://www.google.com/maps/@35.6589333,139.7045826,222a,35y,39.45t/data=!3m1!1e3)
[Street View](https://www.google.com/maps/@35.660283,139.705937,3a,60y,346.42h,95.72t/data=!3m6!1e1!3m4!1sp7ksOJ7ouX9XkD8_SUfYXQ!2e0!7i16384!8i8192)

![0022.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55735/f42098ae-e1ba-d9f5-9d63-d36c278886ba.png)

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/Efm7p3Yhbw">https://t.co/Efm7p3Yhbw</a>の渋谷駅周辺3DデータをThree.jsに読み込んで閲覧中。当たりまえですが、GoogleMapと一致しました。<a href="https://t.co/Uz7kTvRi3R">https://t.co/Uz7kTvRi3R</a><a href="https://t.co/e4CR2Zee4g">https://t.co/e4CR2Zee4g</a> <a href="https://t.co/xZ8ptwhSkS">pic.twitter.com/xZ8ptwhSkS</a></p>&mdash; t_mat (@t_mat) <a href="https://twitter.com/t_mat/status/1143530506497097729?ref_src=twsrc%5Etfw">June 25, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 感想
Three.jsはあまり使い慣れていないのですが、シーングラフでの3D構築は分かり易くて良いですね。Java3Dを思い出します。
しかし、都市3Dデータがオープンデータで公開されるとか、すごい時代になったものです。