# JavaScript 3Dフレームワーク でマップを表示してみよう!!
JSの3Dフレームワーク [**babylon.js**](https://www.babylonjs.com/) を用いて3D空間上に地図を描画してみましょう :muscle:
```markdown
- この講座で学べること
1. babylon.js の基本的なオブジェクト生成〜テクスチャの設定、配置など
2. ブラウザの位置情報APIの初歩的な扱い方
3. あるフレームワークで「こういうことやりたい」となったときに
エンジニアはどんなふうに調べているのか
- この講座では学べないこと
1. babylon.jsのアニメーションや発展的な内容
2. 位置情報の更新に合わせて描画を更新する方法
3. おいしい打ち込みうどんのレシピ
```
<details>
<summary>TOC</summary>
1. babylon.jsに触れてみよう
a. 初めてのbabylon.js
b. メッシュを配置してテクスチャを当てよう
c. 自分のアバターを作ってみよう
2. 位置情報を取得しよう
a. MDNの位置情報APIのページを読んで、実際に使ってみよう
b. 地図タイルを取得してみよう
3. 地図をワールドに表示させよう
a. オブジェクトの配置〜テクスチャの適用までをやってみよう
b. 地図中の自分の位置にアバターを表示しよう
</details>
<details>
<summary>JavaScriptチョットワカル な人たちへ</summary>
本講座内では値の宣言に `var` を使い、アロー関数ではなく無名関数を使います。
JavaScriptチョットワカル な人は `var` は気持ち悪く、無名関数は冗長であると感じるかもしれません。
が、Babylon.js Playgroundの初期のコードではこれらが利用されているため、本講座内ではこれに習ってコードを書いています。
もちろん、変更されることがない値についてはconstで宣言される方が好ましいですし、必要ないタイミングではアロー関数のほうが有用なことが多いです。が、今回は記述が煩雑になることで混乱を招かないようにするためこの記法で統一します。
</details>
## 1. babylon.jsに触れてみよう
:star: [**babylon.js**](https://www.babylonjs.com/)とは
> ブラウザ(やネイティブアプリ)で
> - ゲーム作ったり
> - 3D グラフ描画したり
> - AR/VR 空間作ったり
> - 物理演算でピタゴラ作ったり
> - EC サイトの商品を 3D で見れたり
>
> できる TypeScript-based WebGL エンジン
>
> 引用: [Babylon.js 超入門](https://docs.google.com/presentation/d/1FPkVMku4fuLGhO6HkNzVRE3C7oRl8TEG3XxQYnkpolk/edit?pli=1#slide=id.g12240e6ccdb_0_80) / [やまゆ💝 (@akai_inu) / Twitter](https://twitter.com/akai_inu)
文章で見るだけではどんなものなのか想像がつかないですね... :thinking_face:
↓のリンクから実際の作品を見てみましょう!
- babylon.jsを利用した作品例
- https://playground.babylonjs.com/#3I55DK#0
- 
- https://playground.babylonjs.com/#ZHRWSL#50
- 
- https://playground.babylonjs.com/#LPTLZM
- 
### a. 初めての babylon.js
babylon.jsでできることが何となく想像がついてきたかと思います!
ではいざ "はじめての babylon.js" をやっていきましょう :smile:
今回の講座では [**Babylon.js Playground**](https://playground.babylonjs.com) を利用します。
↑のリンクからプレイグラウンドを開いてください。
↓のような画面が表示されれば OK です!

この画面でヘッダー・フッターが赤いことがあります。
その状態はTypeScriptというJavaScriptに型が追加された言語で記述するモードなのですが、いわばハードモードなのでヘッダーの `JS` からJavaScriptに切り替えてください。
まずはこの初期状態で書かれているコードの意味を追っていきます。
```javascript=
var createScene = function () {
// This creates a basic Babylon Scene object (non-mesh)
var scene = new BABYLON.Scene(engine);
// This creates and positions a free camera (non-mesh)
var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
// This targets the camera to scene origin
camera.setTarget(BABYLON.Vector3.Zero());
// This attaches the camera to the canvas
camera.attachControl(canvas, true);
// This creates a light, aiming 0,1,0 - to the sky (non-mesh)
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
// Default intensity is 1. Let's dim the light a small amount
light.intensity = 0.7;
// Our built-in 'sphere' shape.
var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2, segments: 32}, scene);
// Move the sphere upward 1/2 its height
sphere.position.y = 1;
// Our built-in 'ground' shape.
var ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 6, height: 6}, scene);
return scene;
};
```
さて、以降の処理に備えて L6 のカメラを書き換えておきましょう。一旦何も考えずに↓のコードで置き換えてください :pencil:
```javascript=6
var camera = new BABYLON.ArcRotateCamera("camera1", -Math.PI/4, Math.PI/2.5, 15, BABYLON.Vector3.Zero(), scene);
```
また、ライトの設定も変更しておきましょう。L15 のlightの宣言を置き換えます。
```javascript=15
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(-1, 1, 0), scene);
```

ここまでの作業が完了したら Ctrl + S を押して保存しましょう。

↑このようなダイアログが表示されるので適当なタイトルを入力して OK を押してください。

ブラウザのURLが書き換わっていれば保存成功です。
**<font color="red">注意</font>**
Babylon.js Playground のコードは通常保持されません。
変更が終わるたび、意識的に Ctrl + S を押してコードを保存するようにしましょう。
また、普段PCでジェスチャー機能 (3本指スワイプで画面移動や2本指スワイプでページを進む/戻す操作) をしている人は、誤爆しやすいので注意してください。(資料作成時6敗 :sob:)
なお、社内エンジニアの勉強会でテストプレイしたところ、ほぼ全エンジニアが敗北していました。リロードなどにも気をつけてください。
### b. メッシュを配置してテクスチャを当てよう
#### I. メッシュを生成して配置する
ではこれらの意味を一通り追ったところで、シーンに 2\*2\*2 の立方体と地面を描画するにはどうすれば良いか考えてみましょう :thinking_face:
上記コードの L24, L27 から推察するに、babylon.jsでは `BABYLON.Me
shBuilder.CreateXxxxx` で Xxxxx のメッシュを生成できそうです。立方体は箱、箱といえばBoxなので、CreateBoxという関数がありそうですね :bulb:
不要な L20 ~ L24 までは削除してしまって、box変数を宣言します。

↑のように、`BABYLON.MeshBuilder`まで入力したところで補完候補に`CreateBox`が出てきました。予想通り箱状のメッシュを作成する関数があるようなのでこれを使います。

補完を適用して `()` を入力すると今度は関数のドキュメントが展開されています。
展開されているドキュメントによると
- 第一引数 name(メッシュ名): string (必須、文字列型)
- 第二引数 options(オプション): object (任意、オブジェクト型)
- 第三引数 scene(生成先シーン): BABYLON.Scene (任意、シーン型)
のように引数が必要なようです。
ひとまず必須引数のnameのみ指定して確認してみます。
プログラムを実行して確認するために、画面上部のツールバーから三角の実行ボタンをクリックします :three_button_mouse:

立方体が表示されましたね! :tada:
しかし、これでは *2\*2\*2* を満たしていないのでoptionsでsizeプロパティを指定します。

大きくなりました!でも半分地面に埋まってますね...(マウスで画面を動かして確認してみてください。)
初期状態の L24 にsphere(球)の位置を動かしているコードがあります。これを参考に、boxのpositionプロパティのyを変更すればうまく位置を調整できそうです。
`box.position.y = 1;` を追加してみます。

これで完璧です :100:
ちなみに、optionsのプロパティには `width: number; height: number; depth: number;` も存在しています。
これらはそれぞれ 幅、高さ、奥行き の指定です。これらそれぞれに `2` を指定しても同様の挙動になります。
この指定を応用すれば任意の大きさの直方体を作れますね :building_construction:
#### II. テクスチャを設定する
テクスチャは初期状態のコードに参考にできるものがありません :thinking_face:
テクスチャはメッシュに対して指定したいですが、先程位置をboxメッシュに対して指定しましたね。これを参考に、テクスチャもメッシュに対して指定できるか確認してみましょう。
試しにgroundに芝生をイメージして緑色を設定できないかやってみます。
まず `ground.` とground宣言の次の行に入力して補完候補を見てみます。

materialプロパティが一番上に来ますね。textureプロパティは存在しないようなのでこれがそれっぽい気がします。
これだけでは指定方法がいまいち見えないのでGoogle先生に「babylon.js mesh material」で質問してみます 🔍
babylon.js公式ドキュメントの[Introduction to Materials](https://doc.babylonjs.com/features/featuresDeepDive/materials/using/materials_introduction)が先頭にあるのでこれを開いてみます。
[Colorセクション](https://doc.babylonjs.com/features/featuresDeepDive/materials/using/materials_introduction#color)にやりたいことが書いてありそうです!

```javascript
var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);
myMaterial.diffuseColor = new BABYLON.Color3(1, 0, 1);
myMaterial.specularColor = new BABYLON.Color3(0.5, 0.6, 0.87);
myMaterial.emissiveColor = new BABYLON.Color3(1, 1, 1);
myMaterial.ambientColor = new BABYLON.Color3(0.23, 0.98, 0.53);
mesh.material = myMaterial;
```
なるほど :thinking_face:
新しいマテリアルを宣言して、hogehogeColorプロパティに色を指定し、メッシュのmaterialプロパティに指定するのが一連の流れのようです。
今回の用途ではdiffuseColorを指定するのが良さそうです。
groundに設定するmaterialなので変数名は 'groundMat' などが適当でしょう。
追加するコードは↓のとおりです。
```javascript=26
var groundMat = new BABYLON.StandardMaterial("groundMat");
groundMat.diffuseColor = new BABYLON.Color3(0, 1, 0);
ground.material = groundMat;
```

これで地面に色が付きました〜〜!
箱の方にもテクスチャを当てたいですね :thinking_face:
今回は画像をテクスチャにしてみます。
ドキュメントの[Textureセクション](https://doc.babylonjs.com/features/featuresDeepDive/materials/using/materials_introduction#texture)が参考になりそうです。
追加するコードは↓のとおりです。
今回は任意の画像のパス(url)を `<Path to Image>` に置き換えて実行してみてください。
(スクリーンショットでは自分のGitHubアイコンを使用しています)
```javascript=22
var boxMat = new BABYLON.StandardMaterial("boxMat");
boxMat.diffuseTexture = new BABYLON.Texture("<Path to Image>");
box.material = boxMat;
```

ここまでのコードをまとめておきます。(コメントが適当でない部分を修正・削除してあります)
```javascript=
var createScene = function () {
// This creates a basic Babylon Scene object (non-mesh)
var scene = new BABYLON.Scene(engine);
// This creates and positions a arc rotate camera (non-mesh)
var camera = new BABYLON.ArcRotateCamera("camera1", -Math.PI/4, Math.PI/2.5, 15, new BABYLON.Vector3.Zero(), scene);
// This targets the camera to scene origin
camera.setTarget(BABYLON.Vector3.Zero());
// This attaches the camera to the canvas
camera.attachControl(canvas, true);
// This creates a light, aiming -1,1,0 - to the sky (non-mesh)
var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(-1, 1, 0), scene);
// Default intensity is 1. Let's dim the light a small amount
light.intensity = 0.7;
var box = new BABYLON.MeshBuilder.CreateBox("box", {size: 2});
box.position.y = 1;
var boxMat = new BABYLON.StandardMaterial("boxMat");
boxMat.diffuseTexture = new BABYLON.Texture("https://avatars.githubusercontent.com/u/24760583?v=4");
box.material = boxMat;
var ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 6, height: 6}, scene);
var groundMat = new BABYLON.StandardMaterial("groundMat");
groundMat.diffuseColor = new BABYLON.Color3(0, 1, 0);
ground.material = groundMat;
return scene;
};
```
これでメッシュの生成〜配置、テクスチャのかんたんな設定方法までが何となく身についたかと思います。
では、今回使用するアバターを作ってください!
### c. 自分のアバターを作ってみよう
ここで`MergeMeshes`というメッシュを統合する関数を説明します。
自分のアバターを生成して、統合したメッシュを返すような関数を用意することでコードの可読性(読みやすさ)を意識してみましょう。
`createAvatar` という関数を作成して自分のアバターを生成する記述を関数内に書きます。
ここでは試しに三角錐に球が乗ったチェスのポーンのような形をつくってみます。
```javascript=
var createAvatar = function () {
var body = BABYLON.MeshBuilder.CreateCylinder("body", {
height: 0.7,
diameterTop: 0,
diameterBottom: 0.5,
});
var head = BABYLON.MeshBuilder.CreateSphere("head", {diameter: 0.4});
head.position.y = 0.3;
};
```
```javascript
var CreateScene = function () {
...
createAvatar();
...
};
```

ここに地面を追加してみます。

このように半分埋まってしまいます。`position.y`を指定して上に持っていきたいですが、残念ながら`createScene`内には現状でかけません。
これでは不便なので `MergeMeshes` で body と head を統合したメッシュを返却してposition指定を一括でできるようにします。
```javascript
createAvatar = function () {
...
return BABYLON.Mesh.MergeMeshes([body, head], true, false, null, false, true);
};
```
第一引数に統合したいメッシュのリストを、以降はそれぞれ意味があるのですがひとまず↑と同じように渡して貰えれば問題ないです。
これで`createScene`中の`createAvatar`の返り値を`avatar`変数に格納することで、一括で位置指定が可能になるはずです。
(このとき、yにはbodyのcylinderのheightの半分の値を代入しています。)

これでy軸座標を設定して任意の位置に配置できるようになりました。同様にx,z座標も設定可能です。
では、createAvatar関数を自由に書き換えて自分だけのアバターを作成してみましょう!
サンプルで帽子をかぶったようなモデルのスクリーンショットを置いておきます。
先程までのマテリアル設定などを応用してカラーなどを設定してみてください :+1:

## 2. 位置情報を取得しよう
### a. MDNの位置情報APIのページを読んで、実際に使ってみよう
では少しBabylon.jsから離れて、ブラウザの標準APIで実装されている位置情報APIの利用に触れてみましょう🔍
ここではMDN web docsの[位置情報API](https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API)を参考にします。
> 開発者は、いくつかの異なる方法でこの位置情報にアクセスできるようになりました。
>
> Geolocation.getCurrentPosition(): 端末の現在の位置を受け取ります。
> Geolocation.watchPosition(): 端末の位置が変化するごとに自動的に呼び出され、更新された位置情報を返すハンドラー関数を登録します。
とあるので、この2つの関数を用いて位置情報を取得することができるようです。
試しに、1つ目の `Geolocation.getCurrentPosition()` を使って現在地を一度だけ取得するやり方でやってみましょう。
[インターフェース](https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%82%A4%E3%82%B9)に以下の記述があります :memo:
> Navigator.geolocation
> API のエントリーポイント。 Geolocation オブジェクトのインスタンスを返し、そこから他のすべての機能にアクセスすることができます。
実際利用する際にはこのエントリーポイントを使うようです。
では、ブラウザの開発者ツールを開いてコンソールタブに移動します。
新しいタブを作って移動してF12キーやCtrl + Shift + Iで開けるはずです。
ページの任意の場所を右クリックして「検証」をクリックしても開発者ツールが開けます。

ここで以下のコードを実行してみてください。
```javascript
navigator.geolocation.getCurrentPosition(function (position) {
console.table(position);
});
```
すると以下のようにブラウザから位置情報の利用に関するダイアログが表示されます。
「許可する」を選択して位置情報APIを利用できるようにします。

許可した時点で↓のようにコードの実行結果がコンソールに表示されるかと思います。

先程の[インターフェース](https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%82%A4%E3%82%B9)に以下のように記載があります :memo:
> GeolocationPosition
> ユーザーの位置を表します。 GeolocationPosition インスタンスは、 Geolocation に含まれるメソッドのいずれかの呼び出しが成功した場合に、成功コールバックの内部で返され、タイムスタンプと GeolocationCoordinates オブジェクトのインスタンスが含まれます。
また、[GeolocationCoordinates](https://developer.mozilla.org/ja/docs/Web/API/GeolocationCoordinates)も記述があり、プロパティには以下のものがあるようです。
- GeolocationCoordinates.latitude
- GeolocationCoordinates.longitude
- GeolocationCoordinates.altitude
- GeolocationCoordinates.accuracy
- GeolocationCoordinates.altitudeAccuracy
- GeolocationCoordinates.heading
- GeolocationCoordinates.speed
この中で利用するものは緯度`GeolocationCoordinates.latitude`、経度`GeolocationCoordinates.longitude`くらいでしょうか? :thinking_face:
スマートフォンからの利用を想定するなら方角`GeolocationCoordinates.heading`も利用できそうです。
ここでは、位置情報APIで現在地を取得して、現在の緯度経度を返却する関数を用意しておきましょう。
### b. 地図タイルを取得してみよう
さて、実際にワールドに地図を表示するにはマテリアルのテクスチャに地図の画像を適用する必要があります。
今回の講座では国土地理院の[地理院タイル](https://maps.gsi.go.jp/development/siyou.html)を利用します。
↑のページのURLの項目を確認すると以下のような記述があります。
> 地理院タイル1枚1枚のURLは、ズームレベルとタイル座標に基づいて、原則として以下のように命名されています。
>
> https://cyberjapandata.gsi.go.jp/xyz/{t}/{z}/{x}/{y}.{ext}
>
> ここで、{t},{x},{y},{z},{ext}の意味は次の通りです。
>
> {t}:データID
> {x}:タイル座標のX値
> {y}:タイル座標のY値
> {z}:ズームレベル
> {ext}:拡張子
なるほど、現在地の地図タイルを取得するにはタイル座標が必要そうです。
まずはかんたんに国土交通省の[タイル座標確認ページ](https://maps.gsi.go.jp/development/tileCoordCheck.html#17/35.9435542/136.2003804)で先程コンソールで取得した緯度経度を入力して確認してみましょう。
(リンク先はjig.jp鯖江開発センター付近になっています。)
どうやら弊社の近くはズームレベル17だとx:115125, y:51495が妥当そうです。
tには標準タイルを示す`std`、拡張子には`png`を指定します。
アクセスするURLは [`https://cyberjapandata.gsi.go.jp/xyz/std/17/115125/51495.png`](https://cyberjapandata.gsi.go.jp/xyz/std/17/115125/51495.png) になります。

開けました〜!
この `セ` のあたりにオフィスがあるのであってそうです。
ではこのURLを自動で生成する関数を...といいたいところですが、この生成式は非常に難しいです。(3日間くらい悩んだ)
おもに緯度方向のタイル座標の計算が難しいのですが、ありがたいことに計算式を公開してくれているページがあります。
:page_facing_up: [TrailNote : 座標の変換(世界座標、ピクセル座標、タイル座標、緯度・経度)](https://www.trail-note.net/tech/coordinate/)
こちらのページを参考に緯度、経度からタイル座標を生成する関数を考えます。
- 経度 `lon` から タイル座標 `x` を計算する式
$$ x = \lfloor\frac{2^{z+7}(\frac{lon}{180} + 1)}{256}\rfloor
$$
- 緯度 `lat` から タイル座標 `y` を計算する式
$$ y = \lfloor\frac{\frac{2^{z+7}}{\pi}(-tanh^{-1}(sin(\frac{\pi \cdot lat}{180})) + tanh^{-1}(sin(\frac{\pi \cdot L}{180}))}{256}\rfloor\\
ただし、 L = \frac{180}{\pi}sin^{-1}(tanh(\pi)) \approx 85.05112878
$$
これをプログラムに書き起こしたものが↓です。
``` javascript
var zoomLevel = 17;
var l = 85.05112878;
var getTileCoordinate = function (lon, lat) {
var x = (2 ** (zoomLevel + 7)) * (lon / 180 + 1);
var y = ((2 ** (zoomLevel + 7)) / Math.PI )
* ((
- Math.atanh(Math.sin((Math.PI * lat) / 180)))
+ Math.atanh(Math.sin((Math.PI * l) / 180))
);
return {x: Math.floor(x / 256), y: Math.floor(y / 256)};
};
```
これをコンソールに入力して、先程実行した位置情報取得の結果を渡して `getTileCoordinate` を実行してみましょう。

結果は `{x: 115125, y: 51495}` となりました。これはタイル座標確認ページで確認したものと等しいので、問題なく使用できそうに思います。
では、この返却値を利用してURLを生成する関数も準備します。
```javascript
var createUrl = function (coords) {
return `https://cyberjapandata.gsi.go.jp/xyz/std/${zoomLevel}/${coords.x}/${coords.y}.png`;
};
```
実行してみると、無事URLが生成できました。

では、これらのコードをまとめて、Babylon.js Playgroundで利用できるようにしましょう。
1.で作ったコードの先頭に以下のコードを追加してください。
```javascript=
var zoomLevel = 17;
var l = 85.05112878;
var getTileCoordinate = function (lon, lat) {
var x = (2 ** (zoomLevel + 7)) * (lon / 180 + 1);
var y = ((2 ** (zoomLevel + 7)) / Math.PI )
* ((
- Math.atanh(Math.sin((Math.PI * lat) / 180)))
+ Math.atanh(Math.sin((Math.PI * l) / 180))
);
return {x: Math.floor(x / 256), y: Math.floor(y / 256)};
};
var createUrl = function (coords) {
return `https://cyberjapandata.gsi.go.jp/xyz/std/${zoomLevel}/${coords.x}/${coords.y}.png`;
};
var createAvatar() = function () {
...
```
これでBabylon.jsのシーン作成時に位置情報を取得すれば簡単に地図タイルURLを生成できるようになりました〜!
## 3. 地図をワールドに表示させよう
### a. 現在地の位置情報取得〜地図タイルテクスチャの適用までをやってみよう
さて、この講座ではここまでに以下の内容をお話してきました。
- Babylon.js
- メッシュの作成
- メッシュの移動
- マテリアルの作成
- マテリアルに色を設定する方法
- マテリアルに画像(テクスチャ)を設定する方法
- メッシュにマテリアルを適用する方法
- メッシュを統合する方法
- 位置情報API
- 現在の位置情報を取得する方法
- 取得できる情報の内容
- 地図タイルのURL形式
- 地図タイルのタイル座標の計算式とコード
ここまでの内容を踏まえて、`ground`の大きさを`幅10, 奥行き10`に設定して、現在地の地図タイルを適用してその上に自分が作ったアバターを表示させてください!
出来上がりイメージは下のスクリーンショットの感じです。

### b. 【発展】地図中の自分の位置にアバターを表示しよう
:bulb: ヒント: タイル座標生成関数では最後に256で割っています。
この256は地図タイル一枚のピクセル幅なのですが、このあまりを使うと位置がうまくわかりそうです。
また、地図タイルをテクスチャにしている `ground` メッシュは 10\*10 のサイズになっていることに注意しましょう。
### c. 【発展】現在地タイルの周囲も表示しよう
:bulb: ヒント: forループは負の値からも回せます。タイル座標は必ず整数なので...?
---
###### 参考文献
- 地理院タイルについて/国土交通省国土地理院
- url: https://maps.gsi.go.jp/development/siyou.html
- タイル座標確認ページ/国土交通省国土交通省
- url: https://maps.gsi.go.jp/development/tileCoordCheck.html#3/60.02/136.05
- 地理情報API/mdn web docs
- url: https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API
- Babylon.js Tips集/babylon.js 日本コミュニティ
- url: https://scrapbox.io/babylonjs/