📝

maplibre-gl-opacityをMapbox GL JSで使ってみる

2024/01/09に公開

はじめに

現在、Software Designで連載中の「位置情報エンジニアリングのすすめ」を毎月楽しく拝読しております。第6回「防災マップの作成① 浸水想定区域と避難所を可視化する」ではMapLibre GL JSを用いて地理情報を表示していました。記事にも記載されている通り、MapLibre GL JSはMapbox GL JSからフォークされた地図ライブラリなので、使い方は非常に似通っています。そこで、ここでは紙面で紹介されていたMapLibre GL JS用のプラグインであるmaplibre-gl-opacityをMapbox GL JSで使ってみようと思います。

maplibre-gl-opacityとは

maplibre-gl-opacityはMapLibre GL JS用のプラグインで、スタイル上のレイヤーの表示・非表示設定や不透明度の設定を行うことができます。作者様の解説記事は以下です。

https://qiita.com/dayjournal/items/2200ce37472c3b4cd6f3

maplibre-gl-opacityの使い方

maplibre-gl-opacityはIControlとして実装されています。そのため、以下のコードのようにOpacityControlをインスタンス化し、Map#addControlで地図上に表示します。

const opacity = new OpacityControl({
  baseLayers: {
    'tokyo-st-green': 'GREEN',
    'tokyo-st-blue': 'BLUE',
  },
  overLayers: {
    satellite: 'Satellite',
  },
  opacityControl: true,
});

map.addControl(opacity, 'top-left');

上記のコードで以下のようなコントロールが表示されます。baseLayersで指定したレイヤーは択一式のラジオボタン、overLayersで指定したレイヤーは複数選択可能なチェックボックスとして表示されます。また、opacityControltrueとすることでoverLayersの不透明度を設定するためのスライダーが表示されます。

control

maplibre-gl-opacityのコードを読む

IControlMap#addControlのタイミングでonAdd関数が実行されます。つまり、IcontrolのエントリポイントはonAdd関数なので、ここからコードを読むと良いです。

https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L131-L136

_opacityControlAdd()関数が内部で呼ばれているようなので中を見ていきます。

baseLayersに関する処理

以下でbaseLayersに関する処理が行われています。baseLayersはキーがレイヤーIDなので、Object.keysでレイヤーIDの配列が取得されます。その配列に対してmapを作用させ、各レイヤーIDを引数として_radioButtonControlAdd関数を実行しています。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L99-L108

_radioButtonControlAddではラジオボタンを作成します。さらに、1つ目のレイヤーに対してはMap#setLayoutPropertyを使用し、visibilityプロパティをvisibleに、それ以外のレイヤーはnoneに設定しています。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L20-L30

さらに、ラジオボタン選択時に、選択されたレイヤーをvisibleにする処理が記述されています。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L33-L44

また、baseLayersの値はラベルとして利用されます。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L48

overLayersに関する処理

以下でoverLayersに関する処理が行われています。overLayersもキーがレイヤーIDなので、Object.keysでレイヤーIDの配列が取得されます。その配列に対してmapを作用させ、各レイヤーIDを引数として_checkBoxControlAdd関数を実行しています。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L114-L128

_checkBoxControlAdd関数ではチェックボックスを作成します。さらに、全てのレイヤーを非表示状態にします。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L54-L59

また、チェックボックス選択時に表示・非表示を切り替える処理が記述されています。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L62-L69

opacityControltrueのときには_rangeControlAdd関数も実行されます。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L123-L126

_rangeControlAdd関数ではスライダーを作成し、スライダー変更時にMap#setPaintPropertyraster-opacityを変更しています。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L86-L90

raster-opacityはラスターレイヤー専用のプロパティなので、opacityControlを使用する際にoverLayersに指定できるレイヤーはラスターレイヤーのみとなります。

maplibre-gl-opacityをMapbox GL JSで使う

上記ではmaplibre-gl-opacity内部で使用されるMapLibre GL JSの機能を見てきました。以下の表はMapLibre GL JSとMapbox GL JSの機能を比較です。これらの機能はMapbox GL JS v1.13.0以前に実装されたものなので、基本的に互換性があることが期待されます。

MapLibre GL JS Mapbox GL JS
IControl IControl
Map#addControl Map#addControl
Map#setLayoutProperty Map#setLayoutProperty
Map#setPaintProperty Map#setPaintProperty

サンプル全体

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

import { Map } 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'

import tokyoSt from './tokyo_st.json'

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

map.on('style.load', () => {
  map.addSource('tokyo-st', {
    type: 'geojson',
    data: tokyoSt as any,
  });

  map.addLayer({
    'id': 'tokyo-st-green',
    'type': 'fill',
    'source': 'tokyo-st',
    'paint': {
      'fill-color': '#00ff00',
    }
  });

  map.addLayer({
    'id': 'tokyo-st-blue',
    'type': 'fill',
    'source': 'tokyo-st',
    'paint': {
      'fill-color': '#0000ff',
    }
  });

  map.addSource('satellite', {
    'type': 'raster',
    'url': 'mapbox://mapbox.satellite',
  });

  map.addLayer({
    'id': 'satellite',
    'type': 'raster',
    'source': 'satellite',
  });

  const opacity = new OpacityControl({
    baseLayers: {
      'tokyo-st-green': 'GREEN',
      'tokyo-st-blue': 'BLUE',
    },
    overLayers: {
      satellite: 'Satellite',
    },
    opacityControl: true,
  });

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

以下で各部分の動作を確認します。

地図の作成

以下ではMapをインスタンス化しています。

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

ソース・レイヤーの作成

ソース・レイヤーの作成はmap.on('style.load', () => { /* ここ */})の部分で行います。

以下は東京駅のポリゴンをGeoJSONから読み込んでいます。また、ポリゴンを緑色で塗りつぶしたtokyo-st-greenレイヤーとポリゴンを青色で塗りつぶしたtokyo-st-blueレイヤーを作成しています。これらのレイヤーはbaseLayersで使用します。

  map.addSource('tokyo-st', {
    type: 'geojson',
    data: tokyoSt as any,
  });

  map.addLayer({
    'id': 'tokyo-st-green',
    'type': 'fill',
    'source': 'tokyo-st',
    'paint': {
      'fill-color': '#00ff00',
    }
  });

  map.addLayer({
    'id': 'tokyo-st-blue',
    'type': 'fill',
    'source': 'tokyo-st',
    'paint': {
      'fill-color': '#0000ff',
    }
  });

以下はMapboxのSatellite Tilesetをソースとして使用します。また、このタイルセットはラスターなので、ラスターレイヤーとして追加します。このレイヤーはoverLayersで使用します。

  map.addSource('satellite', {
    'type': 'raster',
    'url': 'mapbox://mapbox.satellite',
  });

  map.addLayer({
    'id': 'satellite',
    'type': 'raster',
    'source': 'satellite',
  });

maplibre-gl-opacityの作成

maplibre-gl-opacityの使い方で見た通り、OpacityControlをインスタンス化し、addControlします。

  const opacity = new OpacityControl({
    baseLayers: {
      'tokyo-st-green': 'GREEN',
      'tokyo-st-blue': 'BLUE',
    },
    overLayers: {
      satellite: 'Satellite',
    },
    opacityControl: true,
  });

  map.addControl(opacity, 'top-left');

最後にちょっとしたハックです。Mapbox GL JSで使用する際に必要なクラス名の指定を行います。

  opacity._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';

この指定の背景は以下のとおりです。

maplibre-gl-opacityコントロールのHTMLエレメントに対し、maplibregl-ctrlおよびmaplibregl-ctrl-groupというクラスが割り当てられています。
https://github.com/mug-jp/maplibre-gl-opacity/blob/v1.6.0/src/maplibre-gl-opacity.js#L93-L98

これらのクラスは以下の様にMapLibre GL JSのCSSで定義されています。IControlで作成するコントロールに対してこのクラスを割り当てることで、いい感じに表示されます。
https://github.com/maplibre/maplibre-gl-js/blob/v2.4.0/src/css/maplibre-gl.css#L87-L94
https://github.com/maplibre/maplibre-gl-js/blob/v2.4.0/src/css/maplibre-gl.css#L108-L112

同様のクラスがMapbox GL JSでは以下のように定義されています。
https://github.com/mapbox/mapbox-gl-js/blob/v3.0.1/src/css/mapbox-gl.css#L63-L69
https://github.com/mapbox/mapbox-gl-js/blob/v3.0.1/src/css/mapbox-gl.css#L63-L69

そこで、Mapbox GL JSで使用する場合にはmaplibregl-ctrlmapboxgl-ctrlに、 maplibregl-ctrl-groupmapboxgl-ctrl-groupに変更する必要があります。

結果

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

デモは以下のサイトで試せます。
https://sd202312.netlify.app/opacity

まとめ

ちょっとしたハックは必要でしたが、maplibre-gl-opacity(v1.6.0)がMapbox GL JS(v3.0.1)でも使用できることがわかりました。ただし、今後MapLibre GL JSの独自機能を使う変更が加えられたり、MapLibre Gl JSまたはMapbox GL GLの既存機能に破壊的変更があった場合には使用できなくなる可能性もあります。

OpacityControlの設定に関して、baseLayersという名称からベースマップとして表示するラスターレイヤーの使用がライブラリとしての期待値かもしれません。しかし、今回のような任意のレイヤーの表示の選択にも利用可能です。

また、紙面ではOpacityControlを2つ作成していましたが、一つはベースマップの選択用、もう一つは浸水想定区域の選択用として作成していると推察されます。一つのOpacityControlに対し、後者をoverLayersとして表示する使い方もあるかと思います。

おまけ

紙面で以下のような脚注がありました。

注5) MapLibre GL JSはv3から一部のプラグインが機能しない問題があるため、本連載では最新バージョンではなく動作が安定しているv2.4.0を使います。
https://github.com/maplibre/maplibre-gl-js/blob/main/CHANGELOG.md#potentially-breaking-changes

リンク先を確認するとPotentially breaking changesとして以下の項目があります。

⚠️ Remove deprecated mapboxgl- css classes (use maplibregl- instead) (#1575)

maplibre-gl-opacityはv1.5.0でクラスをmaplibregl-ctrlmaplibregl-ctrl-group変更しているので問題ないですが、Mapbox GL JS用に開発されたプラグインは使えなくなっていると考えられます。

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

Discussion