地理院タイルとdeck.glで3D地図をつくろう

![deckgl-gsi-terrain-layer.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/1211b977-64b5-a450-c080-750413731e18.gif)

この記事は[MIERUNEアドベントカレンダー](https://qiita.com/advent-calendar/2020/mierune)2日目の記事です！

1日目は最近弊社にジョインした@JinIgarashiさんが[FOSS4GHokkaidoでの登壇](https://docs.google.com/presentation/d/e/2PACX-1vQRyhbW_jbsu7B0Xuta61O5jWLUAbMqbiakdIFflXJlFFkUSxnelSaMixwTW2SADlrcS2k6hrYQdW8G/pub?start=false&loop=false&delayms=3000&slide=id.g953cd9280a_0_0)から引き続き、とてもアツい記事を投稿されましたのでMIERUNE的なインパクトは十分、2日目はゆるふわに富士山でも見て涼んでいきましょう。

## TL;DR

国土地理院の標高タイルで3D表現をするためのGsiTerrainLayerをつくりました。
https://github.com/Kanahiro/deckgl-gsi-terrain-layer

npmモジュールとして公開済みです、以下でインストール出来ます。

```
npm install deckgl-gsi-terrain-layer
```

## はじめに

QGISやウェブ問わず、GISの使い始めは、まずはOSM地図や地理院タイルを表示するところから始める方が多いのではないでしょうか。今回は地理院タイルにフォーカスしていく訳ですが、国土地理院は空中写真だけではなく、様々なデータをラスタータイルとして配信しています。

https://maps.gsi.go.jp/development/ichiran.html
こちらがその一覧です。ベースマップなどは定番ですね。

この中に標高タイルなるデータがあります。標高データ、DEMと言えば標高値をグレースケール画像に変換した[白黒のアレ](https://www.google.com/search?q=DEM&tbm=isch)をイメージしますが、標高タイルでは何やらカラフルな画像が配信されているようです（下記画像）。
![スクリーンショット 2020-11-08 17.51.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/6c68f0f0-503b-ff7a-4002-2943569645cd.png)
※地理院タイルより10mDEM、東京都と富士山のまわり

10mレベルのデータが既に配信されていて無料で使える訳ですから、これを用いてウェブ地図で3D表示出来たら便利ですよね。ということで色々やったのが本記事です。

## RGB値の標高換算

先ほども言ったとおりDEMと言えばグレイスケールですが、標高値をRGB画像に変換するというのは、標高タイル以外にもやっている例があります。

https://docs.mapbox.com/help/troubleshooting/access-elevation-data/
たとえばMapbox GL JSではterrain-RGBとして、以下の計算式によりRGB値を標高値に換算しています（もともとはMapzenという会社が定義した規格のよう）。

```
height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)
```

何やら難しく見えますが、RGBそれぞれが0-255の値をとるので、RGB値を3桁の256進数として見立ててRGBと標高をやり取りしている訳です（傾き0.1の単調増加関数と言えますね）。

先ほどの標高タイルと同じエリアの標高を、この式から得られるterrain-RGB画像にすると以下のような画像になります。見た目がずいぶん違う事がわかりますね。
![スクリーンショット 2020-11-19 20.12.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/e556a2b9-9251-f896-9aad-f27ea3852fc3.png)

地理院の標高タイルも、ベースの考え方は全く同じで、計算式が異なり以下です（仕様を解釈して式になおしています）。

```shell

height = (R * 256 * 256 + G * 256 + B) * 0.01
# ただし[R,G,B] = [128,0,0]を無効値とする
# ただし[128,0,1]以上の場合2^24を引き去る
```
ここでサラッと書いた但し書きがミソで、これらのせいで、RGB値→標高値の関係が、単調増加と言えなくなっており、Mapboxなどが採用するterrain-RGBの仕組みに乗っかれなくなってしまっています。

参考：[国土地理院-標高タイルの詳細仕様](https://maps.gsi.go.jp/development/demtile.html)

### Mapzen Terrain-RGBと地理院標高タイルの考え方の違い

（こきたない落書きですみません…）
※とくに標高タイルの方、間違いがありましたらご指摘ください

![スケッチ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/92148481-6df3-bafd-643a-8939dc0adbce.png)


## それでも標高タイルを使いたい理由

<b>既にホスティングされていて、自由に使えること</b>に尽きます。

terrain-RGBを配信しているサービスはいくつかありますが、API-keyが必要だったり、アクセス数がそれなりにあれば当然費用が発生したりと、ライトに使えるものはありません。
また、標高タイルの元データである10mDEMは、基盤地図情報から取得可能なので自前で変換してホスティングする事も不可能ではありませんが、それを全国分やるのは骨です。

## 標高タイルをdeck.glで3D表示してみた
Mapbox GL JSでは陰影図は表示出来ますが3D表示は出来ません。terrain-RGBの表示には、deck.glのTerrainLayerが良い感じです。deck.glのTerrainLayerをベースに、標高タイルに準拠するよう拡張したGsiTerrainLayerをつくってみました。

> ちなみにTerrainLayerでは任意の標高画像に対応していて、グレースケール画像でも標高表現が可能ですが、RGB値と標高値の関係が単調増加である必要があり、標高タイルはどうやっても読ませる事ができません。

[サンプルページ](https://kanahiro.github.io/deckgl-gsi-terrain-layer/)
![スクリーンショット 2020-11-10 23.06.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/34804bbb-e125-72e8-39ee-e721b3ccc7e4.png)
※羊蹄山

### deckgl-gsi-terrain-layer

で、GsiTerrainLayerを使うためのモジュールはnpmで既に公開済みです。
https://github.com/Kanahiro/deckgl-gsi-terrain-layer

```shell

npm install deckgl-gsi-terrain-layer
```

これでインストール可能です。詳細な使い方は本記事では割愛します、上記リポジトリのREADMEを参照してください。

## 終わりに

いかがだったでしょうか？
グレースケールPNGでは分解能は16bitが最大でしょうか。その点、24bitの分解能を持つRGB-PNGが標高表現で有利なのは明らかでしょう。本記事をきっかけにterrain-RGBの利用が少しでも広がったらうれしいですね。
ちなみに、過去に[Mapbox GL JSで同じ様な事をされた方](https://qiita.com/tattii88/items/3d88907c7116d1aeb028)がいらっしゃったみたいですね。

そんな訳でMIERUNEアドベントカレンダー2日目でした。3日目は@mits003さんです、ぜひご覧ください:)