Mapbox GL JSで色別標高図

Mapbox GL JSで、地理院地図の「自分で作る色別標高図」を実装してみる試みです。

# 自分で作る色別標高図とは
今年、地理院地図に追加された、自由に色を設定して色別標高図を作ることができるすごい機能です。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">【地理院地図改良（その２）】自由に標高を色分けして、土地の高低差をわかりやすく表示できるようになりました。意外なところに地形の凸凹が潜んでいるかもしれません。オリジナルの地図を作って、新しい発見をしてみよう！<a href="https://t.co/CoGLjpTvxl">https://t.co/CoGLjpTvxl</a> <a href="https://t.co/cJ7ir0RSRU">pic.twitter.com/cJ7ir0RSRU</a></p>&mdash; 地理院地図 (@gsi_cyberjapan) <a href="https://twitter.com/gsi_cyberjapan/status/971655048072593409?ref_src=twsrc%5Etfw">2018年3月8日</a></blockquote>

Leaflet.jsの[TileLayer](https://leafletjs.com/reference-1.3.4.html#tilelayer)を拡張して、canvas上で標高に応じた色をつけるように実装されています。

同じことをMapbox GL JSで実現することを考えます。
ですが、現時点では色別標高図を作る機能はないため、自分で実装してみます。


# 地理院標高タイル
まず、[地理院標高タイル](https://maps.gsi.go.jp/development/demtile.html)をMapbox GL JSで扱えるようにします。
これは以前、[Mapbox GL JSで地理院標高タイル](https://qiita.com/tattii88/items/3d88907c7116d1aeb028)という記事を書きました。


# Relief Layer
[hillshade](https://www.mapbox.com/mapbox-gl-js/example/hillshade/)のように、色別標高図をRelief Layerとして追加できるようにします。


## WebGLで標高に応じた色をつける

```glsl:relief.fragment.glsl
#ifdef GL_ES
precision highp float;
#endif

uniform sampler2D u_image;
uniform sampler2D u_table;
varying vec2 v_pos;

uniform float u_color_len;
uniform float u_opacity;

float getElevation(sampler2D u_tex, vec2 coord, float bias) {
    // Convert encoded elevation value to meters
    vec4 data = texture2D(u_tex, coord) * 255.0;
    return (data.r + data.g * 256.0 + data.b * 256.0 * 256.0) / 4.0;
}

float getIndex(float el) {
    float prev;
    for (float i = 0.0; i < 128.0; i++){
        if (i >= u_color_len) return u_color_len;
        float v = getElevation(u_table, vec2((i + 0.5) / u_color_len, 1), 0.0);
        if (v >= el){
            if (i == 0.0) return i;
            return i + (el - prev) / (v - prev);
        }
        prev = v;
    }
}

void main() {
    float v = getElevation(u_image, v_pos, 0.0);
    float i = getIndex(v);

    vec4 color = texture2D(u_table, vec2(i / u_color_len, 0), 0.0);
    gl_FragColor = v > 0.0 ? vec4(color.rgb, u_opacity) : vec4(0.0, 0.0, 0.0, 0.0);
}
```
ざっくりと説明すると、
1. WebGLで標高値`getElevation`に応じた階級`getIndex`を計算する
2. 設定色のTextureから、階級に応じた色を取得して返す
という感じです。

区分の標高値と設定色をTexture `u_table`として渡すところがポイントでしょうか。
このフラグメントシェーダーを実行するために、いろいろとコードを書きます。
https://github.com/tattii/mapbox-gl-js/pull/2

参考: [Data-driven Raster Layer with Mapbox GL](https://medium.com/@Scarysize/data-driven-raster-layer-with-mapbox-gl-bdb3b747ae22)


## Example
<img width="1434" alt="スクリーンショット 2018-12-26 6.16.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/103755/1d576c76-711e-5d5a-df00-db4d8ec9a1a9.png">
https://tattii.github.io/terrain-japan/relief/#9.06/35.511/139.1993

```js
map.addLayer({
    "id": "relief",
    "source": "gsi-dem",
    "type": "relief",
    "paint": {
        "relief-opacity": 0.7,
        "relief-gradation": true,
        "relief-colors": [
            0, "#2db4b4",
            100, "#71b42d",
            300, "#b4a72d",
            1000, "#b4562d",
            2000, "#b4491b",
            4000, "#b43d09",
            null, "#b43d09"
        ]
    }
}, 'waterway-river-canal-shadow');
```
`relief-colors`で標高と色を指定します。この設定例で、自分で作る色別標高図の初期状態と同じです。
![スクリーンショット 2018-12-26 6.43.19.png](https://qiita-image-store.s3.amazonaws.com/0/103755/70d5105f-4817-b3e4-40f7-420f6a386a8c.png)



# WIP: 自由に色を設定する
簡易的に、[dat.gui](https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage)を使ってリアルタイムに色の編集をできるようにしました。
<img width="1436" alt="スクリーンショット 2018-12-26 7.26.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/103755/232fce79-5ae7-b706-0c7a-ebf3461c29c1.png">
https://tattii.github.io/terrain-japan/relief/color.html#8.85/35.352/139.1404



標高の編集や区分数を増やすことができるようなアプリケーションとして作成中です...。


# Mapbox GL JSで実装してみて
描画が速いため、リアルタイムに色の編集をできるのが良いところです。
また、Mapbox GL JSの陰影起伏`hillshade`は重ねても暗くなりません。

一方、Mapbox GL JSをforkして独自の拡張をしているため、本体のバージョンアップについていくのは大変です。


# まとめ
WebGLは難しいです。綺麗な色別標高図を作るのも難しいです。
地理院地図にある、URLでの共有や断面図といった機能も実装してみたいです。