🌄

「Software Design 2024年02月号 位置情報エンジニアリングのすすめ 第7回」をMapbox GL JSで実装してみる

2024/01/23に公開

はじめに

現在、Software Designで連載中の「位置情報エンジニアリングのすすめ」を毎月楽しく拝読しております。第7回「防災マップの作成② 地図の3D表現と土砂災害警戒区域の可視化」ではMapLibre GL JSを用いて地理情報を表示していました。今回の紙面の内容もMapbox GL JSで実装可能なので、試してみようと思います。

建物ポリゴンの3D表示

紙面ではソースとして国土地理院Vector、レイヤーとしてfill-extrusionを用いて建物を表示していました。Mapbox GL JSでも同じ手法で表示が可能です。ただし、Mapboxの場合はMapbox Streets v8タイルセットに建物のポリゴンおよびfill-extrusionレイヤーで使用する高さ情報が含まれているのでこちらを利用するのが便利です。

また紙面のようにコード上で実現してもよいですが、Mapbox Studioを使用すればボタン一つで表示可能です。

Mapbox Studio上での操作

  1. https://studio.mapbox.com/ にアクセス
  2. 「New style」をクリック
    stuodio01
  3. 「Classic template」をクリック
    stuodio02
  4. 「Streets」を選択し、「Customize Streets」をクリック
    stuodio03
  5. 編集画面が表示されたら「Layers」をクリック
    stuodio04
  6. Streetsスタイルのレイヤー一覧が表示されるので、「Buildings」をクリック
    stuodio05
  7. 「Building style」で「3D」を選択
    stuodio06

これで都市部をズームすると以下のように建物が3D表示されているのがわかります。
stuodio07

レイヤー一覧を再度確認すると、Buildingsに少し変化があり、buliding-extrusionというfill-extrusionレイヤーが作成されているのがわかります。つまり、紙面でコード上で作成していたのと同じことが実現できているということになります。
stuodio08

地形の3D表示

Mapbox GL JSではMap#setTerrainというメソッドを使って地形の3D表示を行います。また、MapLibre GL JSでもMap#setTerrainで設定するようです。ただし、Terrain(地形)の機能はMapbox GL JS v1.13.0にはなかった機能なので、それぞれが個別に実装した別機能と考えるのが良いかと思います(MapLibre GL JSはMapbox GL JS v1.13.0からフォークしたのでした)。

Mapbox GL JSのMap#setTerrainの使い方は以下のサンプルをご参照ください。

https://docs.mapbox.com/mapbox-gl-js/example/add-terrain/

また紙面で紹介されているようにMapLibre GL JSにはTerrainControlというMap#setTerrainを呼んでTerrainをオン・オフするコントロールがあります。しかし、残念ながらMapbox GL JSには同様のコントロールはデフォルトでは存在しません。

そこで、ここではTerrainを常に表示した状態にします。そして、この機能もMapbox Studioを使用すればボタン一つで表示可能です。

Mapbox Studio上での操作

  1. 「Global」をクリックし、「3D Terrain」をクリック
    stuodio09
  2. 「Enable terrain」を「on」にする
    stuodio10

これで山間部を表示すると、地形が3D表示されているのがわかります。Exaggerationの値を変更すると、高さを強調できます。
stuodio11

また、紙面ではhillshadeレイヤーを作成していました。Streetsスタイルでは以下のように最初からhillshadeレイヤーがあるので、改めて作成する必要はないです。
stuodio12

土砂災害区域の可視化

ここからは紙面と同じ様にレイヤーを作成していきます。紙面ではスタイルの中で定義していますが、ここではコード上でaddSourceaddLayerを使用します。Studio上でレイヤーを作成できると簡単で良いのですが、StudioはMapbox以外のサーバにあるソースを利用できないため、この方法を使用します。

Mapbox Studioで作成したスタイルをPublish

変更を加えたスタイルを使用するためにはPublishする必要があります。

  1. 画面右上の「Publish...」ボタンをクリック
    stuodio13
  2. ダイアログに変更前・後の地図が表示されているので、問題なければ「Publish」ボタンをクリック
    stuodio14
  3. 画面右上の「Share...」ボタンをクリック
    stuodio15
  4. ダイアログ上の「Style URL」をコピーしておく
    stuodio16

サンプル全体

まず今回作成したサンプルです。

import { Map, NavigationControl } from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
// @ts-ignore
import OpacityControl from 'maplibre-gl-opacity';
import 'maplibre-gl-opacity/dist/maplibre-gl-opacity.css'

const map = new Map({
    accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
    container: 'map',
    style: 'mapbox://styles/yochi/clrn88308001z01pud4930a9q',
    center: [139.768435, 35.681054],
    zoom: 15,
});

map.on('style.load', () => {
  map.addSource('doseki', {
      type: 'raster',
      tiles: ['https://disaportaldata.gsi.go.jp/raster/05_dosekiryukeikaikuiki/{z}/{x}/{y}.png'],
      minzoom: 2,
      maxzoom: 17,
      tileSize: 256,
      attribution: '<a href="https://disaportaldata.gsi.go.jp/hazardmap/copyright/opendata.html">ハザードマップポータルサイト</a>',
  });

  map.addSource('kyukeisha', {
      type: 'raster',
      tiles: ['https://disaportaldata.gsi.go.jp/raster/05_kyukeishakeikaikuiki/{z}/{x}/{y}.png'],
      minzoom: 2,
      maxzoom: 17,
      tileSize: 256,
      attribution: '<a href="https://disaportaldata.gsi.go.jp/hazardmap/copyright/opendata.html">ハザードマップポータルサイト</a>',
  });

  map.addSource('jisuberi', {
      type: 'raster',
      tiles: ['https://disaportaldata.gsi.go.jp/raster/05_jisuberikeikaikuiki/{z}/{x}/{y}.png'],
      minzoom: 2,
      maxzoom: 17,
      tileSize: 256,
      attribution: '<a href="https://disaportaldata.gsi.go.jp/hazardmap/copyright/opendata.html">ハザードマップポータルサイト</a>',
  });

  map.addLayer({
      id: 'doseki_layer',
      source: 'doseki',
      type: 'raster',
      paint: { 'raster-opacity': 0.8 },
  });

  map.addLayer({
      id: 'kyukeisha_layer',
      source: 'kyukeisha',
      type: 'raster',
      paint: { 'raster-opacity': 0.8 },
  });

  map.addLayer({
      id: 'jisuberi_layer',
      source: 'jisuberi',
      type: 'raster',
      paint: { 'raster-opacity': 0.8 },
  });

  const hazardLayers = new OpacityControl({
    baseLayers: {
      doseki_layer: '土石流',
      kyukeisha_layer: '急傾斜',
      jisuberi_layer: '地滑り',
    },
  });

  map.addControl(hazardLayers, 'top-left');
  hazardLayers._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';

  map.addControl(new NavigationControl());
});

Mapオブジェクトの作成

基本的にいつも通りですが、styleだけ異なります。自分で編集したスタイルを使用するので、先程コピーしたものを指定してください。

const map = new Map({
    accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
    container: 'map',
    style: 'mapbox://styles/yochi/clrn88308001z01pud4930a9q',
    center: [139.768435, 35.681054],
    zoom: 15,
});

ソース・レイヤーの作成

Map#addSourceMap#addLayerは地図の読み込み後に実行する必要があります。そこで、map.on('style.load', () => {/* ここ */}の「ここ」の部分に以下のように記述します。

  map.addSource('doseki', {
      type: 'raster',
      tiles: ['https://disaportaldata.gsi.go.jp/raster/05_dosekiryukeikaikuiki/{z}/{x}/{y}.png'],
      minzoom: 2,
      maxzoom: 17,
      tileSize: 256,
      attribution: '<a href="https://disaportaldata.gsi.go.jp/hazardmap/copyright/opendata.html">ハザードマップポータルサイト</a>',
  });

中略

  map.addLayer({
      id: 'doseki_layer',
      source: 'doseki',
      type: 'raster',
      paint: { 'raster-opacity': 0.8 },
  });
以下略

maplibre-gl-opacityの作成

前回確認した通り、ちょっとしたハックでmaplibre-gl-opacityが使えます。作成したレイヤーをbaseLayersに指定します。

  const hazardLayers = new OpacityControl({
    baseLayers: {
      doseki_layer: '土石流',
      kyukeisha_layer: '急傾斜',
      jisuberi_layer: '地滑り',
    },
  });

  map.addControl(hazardLayers, 'top-left');
  hazardLayers._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';

紙面ではレイヤーの初期状態を非表示にするコード(リスト9)がありますが、load.styleのタイミングでOpacityControlを作成すればbaseLayersのレイヤーが一瞬描画されることはありません。

カメラコントロール機能の追加

MapLibre GL JSと同じ感じですね。

  map.addControl(new NavigationControl());

結果

以下のように表示されます。

result

デモは以下のサイトで試せます。

https://sd202312.netlify.app/202402

おまけ

スタイル作成時に「Start with Standard」をクリックすると、最新のStandardスタイルが使用されます。
studio_17

Standardスタイルはデフォルトで建物が3D表示されています。地形の3D表示はStreetsのときと同じ様に「Global」「3D Terrain」「Enable terrain」で設定します。
studio_18

ExaggerationがStreets v12とは異なる設定なっています。ここでは同じ設定にします。
studio_19

このスタイルを用いた場合のデモは以下のサイトで試せます。

https://sd202312.netlify.app/202402-standard

おまけ2

ぜひ過去の記事もご参照ください。
https://zenn.dev/ottylab/articles/0cf786c002a7a5
https://zenn.dev/ottylab/articles/4b6ed508f2e24f
https://zenn.dev/ottylab/articles/335915a261d6e9

GitHubで編集を提案
マップボックス・ジャパン合同会社

Discussion