🌏

Mapbox GL JS v3と国土地理院 標高タイルで海面上昇シミュレータを作成してみました

2023/12/05に公開

はじめに

  • Mapbox GL JS v3で、ラスタータイルの色を変更して表示する機能(raster-color、raster-color-mix、raster-color-range)が実装されました。

https://github.com/mapbox/mapbox-gl-js/releases

https://docs.mapbox.com/style-spec/reference/layers/#paint-raster-raster-color

  • 今回は、Mapbox GL JS v3のラスタータイルの色を変更できる機能と国土地理院 標高タイル(Terrain-RGB形式)※を用いて、海面上昇シミュレータを作成してみました。
  • Terrain-RGB形式に変換済みの国土地理院 標高タイルを下記のGitHubで公開していますのでこちらを使用しました。

https://github.com/shi-works/gsi-dem-10b-terrain-rgb

アウトプットイメージ

https://x.com/shi__works/status/1728000842459230325?s=20

デモサイト

https://shi-works.github.io/japan-sea-level-rise-map-on-mapbox-gl-js/#8.26/35.828/140.048

標高タイルのRGB値の標高換算式(=エンコーディング)

  • 標高タイル(Terrain-RGB形式)のRGB値の標高換算式(=エンコーディング)は下記のとおりになります。
  • RGBピクセル値を3桁の256進数として捉え、横軸が縦軸を標高とした傾き0.1の単調増加関数になります。
Terrain-RGB形式の標高換算式
標高=-10000+((R値×256×256+G値×256+B値)×0.1)

(引用元:『現場のプロがわかりやすく教える 位置情報エンジニア養成講座』

全体のコードと解説

  • 全体のコードと解説は下記のとおりになります。
  • MapboxのAPIキーはご自身で用意してください。
  • raster-color:raster-valueに基づく色を定義します(初期値として透明にしています)。
  • raster-value:raster-color内でのみ利用できる設定で、raster-color-mixの設定により得られた値を取得できます。
  • raster-color-mix:RGB値を一つの値(raster-valueで取得できるもの)に変換するパラメータを指定します。[mix.r,mix.g,mix.b,mix.a]の4つのパラメータを指定します。4番目のパラメータであるmix.aは透明度ではなく、固定値のオフセットになります。上述の標高タイルのRGB値の標高換算式をraster-color-mixで定義すると下記のとおりになります。uは標高分解能(0.1)です。
          'raster-color-mix': [
            2 ** 16 * 255 * u,
            2 ** 8 * 255 * u,
            255 * u,
            -10000
          ],
  • raster-color-range:raster-color-mixで得られる値の範囲を指定します(スライダーの値を0~100mで設定しているので、0~128の範囲を指定しています)。なお、参考文献によれば、この範囲が1024に等分割されて、raster-colorで利用されるようです。
  • また、raster-colorは、下記でスライダー機能を用いて動的に変更できるようにしてあります(標高がスライダーの値以下のときに青に着色されます)。
        map.setPaintProperty('gsi-terrain-raster', 'raster-color', [
          'step',
          ['raster-value'],
          '#000098', elevation,   // 標高がelevation以下の場合の色
          'transparent'           // 標高がelevationより高い場合の色
        ]);
index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>海面上昇シミュレーション(地理院 標高タイル)</title>
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet">
  <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }

    .map-overlay {
      font: bold 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
      position: absolute;
      width: 260px;
      top: 5px;
      left: 0;
      padding: 10px;
    }

    .map-overlay .map-overlay-inner {
      background-color: #fff;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
      border-radius: 3px;
      padding: 10px;
      margin-bottom: 10px;
      line-height: 15px;
    }

    .map-overlay label {
      display: block;
      margin: 0 0 0px;
      font-size: 14px;
      top: 100px;
      left: 10px;
      display: block;
      margin-bottom: 5px;
    }

    .map-overlay input {
      background-color: transparent;
      display: inline-block;
      width: 100%;
      position: relative;
      margin: 0;
      cursor: ew-resize;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <div class="map-overlay top">
    <div class="map-overlay-inner">
      <h2>海面上昇シミュレーション</h2>
      <label>海面高さ: <span id="elevation-slider-value">0m</span></label>
      <input id="elevation-slider" type="range" min="0" max="100" step="1" value="0">
      <input id="disclaimer-button" type="button" value="免責事項" onclick="showDisclaimer();">
    </div>
  </div>
  <script>
    mapboxgl.accessToken = 'Your API KEY' 

    const map = new mapboxgl.Map({
      container: "map",
      style: 'https://tile.openstreetmap.jp/styles/maptiler-basic-ja/style.json',
      hash: true,
      center: {
        lat: 35.828,
        lng: 140.048
      },
      zoom: 8.26,
      language: 'ja',
      attributionControl: false
    });

    // ズーム・回転
    map.addControl(new mapboxgl.NavigationControl());

    // フルスクリーンモードのオンオフ
    map.addControl(new mapboxgl.FullscreenControl());

    // 現在位置表示
    map.addControl(new mapboxgl.GeolocateControl({
      positionOptions: {
        enableHighAccuracy: false
      },
      fitBoundsOptions: { maxZoom: 18 },
      trackUserLocation: true,
      showUserLocation: true
    }));

    // スケール表示
    map.addControl(new mapboxgl.ScaleControl({
      maxWidth: 200,
      unit: 'metric'
    }));

    // Attributionを折りたたみ表示
    map.addControl(new mapboxgl.AttributionControl({
      compact: true,
      customAttribution: '(<a href="https://twitter.com/shi__works" target="_blank">Twitter</a> | <a href="https://github.com/shi-works/japan-sea-level-rise-map-on-mapbox-gl-js" target="_blank">Github</a>) '
    }));

    // 免責事項ポップアップ表示
    function showDisclaimer() {
      let disclaimerText = `本シミュレータは地形データで一定の高さ以下の場所を青く塗りつぶす処理をするだけのものであり、ハザードマップとは異なります。 堤防等による防災対策や土砂の堆積等は考慮されていません。 実際の洪水や津波発生時の危険性は各自治体が公表するハザードマップでご確認ください。`
      alert(disclaimerText)
    }

    // 地図がロードされたら実行
    map.on('load', () => {
      // 標高タイルDEMソース
      map.addSource('gsi-terrain-dem', {
        "type": "raster-dem",
        "minzoom": 1,
        "maxzoom": 18,
        "tiles": ["https://xs489works.xsrv.jp/raster-tiles/gsi/gsi-dem-terrain-rgb/{z}/{x}/{y}.png"],
        "tileSize": 512,
        "attribution": "<a href='https://maps.gsi.go.jp/development/ichiran.html#dem' target='_blank'>地理院タイル(標高タイル)</a>"
      })

      // 標高タイルDEMセット
      map.setTerrain({ 'source': 'gsi-terrain-dem', 'exaggeration': 1 });

      // 標高タイルラスターソース
      map.addSource('gsi-terrain-raster', {
        "type": "raster",
        "minzoom": 1,
        "maxzoom": 18,
        "tiles": ["https://xs489works.xsrv.jp/raster-tiles/gsi/gsi-dem-terrain-rgb/{z}/{x}/{y}.png"],
        "tileSize": 512,
        "attribution": "<a href='https://maps.gsi.go.jp/development/ichiran.html#dem' target='_blank'>地理院タイル(標高タイル)</a>"
      })

      // 標高分解能
      const u = 0.1

      // 標高タイルラスターレイヤ
      map.addLayer({
        id: 'gsi-terrain-raster',
        source: 'gsi-terrain-raster',
        type: 'raster',
        paint: {
          // 初期状態で全ての標高に対して透明を指定
          'raster-color': [
            'interpolate',
            ['linear'],
            ['raster-value'],
            0, 'transparent',
            128, 'transparent'
          ],
          'raster-color-mix': [
            2 ** 16 * 255 * u,
            2 ** 8 * 255 * u,
            255 * u,
            -10000
          ],
          'raster-color-range': [0, 128],
          'raster-opacity': 0.7,
          'raster-resampling': 'nearest'
        }
      })

      // 陰影起伏図ソース
      map.addSource("hillshade", {
        type: 'raster',
        tiles: [
          'https://cyberjapandata.gsi.go.jp/xyz/hillshademap/{z}/{x}/{y}.png',
        ],
        attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html#hillshademap" target="_blank">地理院タイル(陰影起伏図)</a>',
        tileSize: 256
      });

      // 陰影起伏図レイヤ
      map.addLayer({
        id: 'hillshade',
        type: 'raster',
        source: 'hillshade',
        minzoom: 2,
        maxzoom: 18,
        paint: {
          'raster-opacity': 0.3
        }
      });

      // スライダーで標高に応じた色を制御
      document.getElementById('elevation-slider').addEventListener('input', function (e) {
        const elevation = parseFloat(e.target.value);
        document.getElementById('elevation-slider-value').innerText = elevation + 'm';

        // 標高タイルレイヤの色を更新
        map.setPaintProperty('gsi-terrain-raster', 'raster-color', [
          'step',
          ['raster-value'],
          '#000098', elevation,   // 標高がelevation以下の場合の色
          'transparent'           // 標高がelevationより高い場合の色
        ]);

      });

    })
  </script>

</body>

</html>

ソースコードとデモサイト

https://github.com/shi-works/japan-sea-level-rise-map-on-mapbox-gl-js

おまけ

  • 海面上昇シミュレータの全球バージョンのソースコードとデモサイトになります。
  • 標高タイルには、AWS(Mapzen) Terrain Tilesを使用しています。

https://github.com/shi-works/global-sea-level-rise-map-on-mapbox-gl-js

海外の事例

  • ありがたいことに、海面上昇シミュレータは海外でも展開されています。

https://aws-terrain-flood.netlify.app/#6/45/-123 (@dkwiens)

https://x.com/dkwiens/status/1730754717226352758?s=20

国内の事例

JAXA

  • 数値表層モデル(DSM:樹木や建物等の高さを含む地表面の高さ)を使った海面上昇のシミュレーション
  • Web地図ライブラリー:OpenLayers

https://data.earth.jaxa.jp/app/sea-level-rise/

産業技術総合研究所

  • ASTER GDEM Ver.2(30m空間分解能)と地理院タイル(10m空間分解能)に基づく海面上昇シミュレーション
  • Web地図ライブラリー:Leaflet

https://gbank.gsj.jp/sealevel/sealevel.html

参考文献

https://qiita.com/mg_kudo/items/978709ad67bce885bfe5

Discussion