MapLibre GL JS で条件によって色を変動させる


この記事は [MapLibre Advent Calendar 2023](https://qiita.com/advent-calendar/2023/maplibre) 22日目の記事です。

## はじめに 

MapLibre GL JS で線形やポリゴンに対して色を指定するとき、 [Layersの形式に従って](https://maplibre.org/maplibre-style-spec/layers/) で指定します。線形やポリゴンなどに塗りつぶしの色を指定するときは、Layerの `paint` の中で指定します。例えば、React で円 (Circle) の塗りつぶし色を指定する際には、以下のようにレイヤーを定義します。

```tsx
map.addLayer({
  id: 'temperature_circle_layer',
  type: 'circle',
  source: 'temperature_data',
  layout: {},
  paint: {
    "circle-color": '#84919e',
    'circle-radius': 10
  }
})
```

`addSource` は省略していますが、それも書いて実際にローカルでサーバを立ち上げて見てみると、以下のように気象通報でお知らせされる観測地点に灰色の円が表示されます。

![気象通報でお知らせされる観測地点](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93706/6d591456-0ef1-1542-a06a-fda13e4c51a9.png)

## 条件によって色を変える

今回のサンプルデータは、2023年12月16日の気象通報でお知らせされた気温の情報が記された以下のようなGeoJSONを用いています。このようなデータがあるとき、気温の値によって円の色を変更したいということもあると思います。このとき [Expressions](https://maplibre.org/maplibre-style-spec/expressions/) を活用して気温の値によって色を動的に変更することができます。

```json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          125.27833,
          24.79333
        ]
      },
      "properties": {
        "name": "石垣島",
        "kana": "イシガキジマ",
        "temperature": 20
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          127.68667,
          26.20667
        ]
      },
      "properties": {
        "name": "那覇",
        "kana": "ナハ",
        "temperature": 21
      }
    }
  ]
}
```

### RGB で色を補間しながら変える

[公式ドキュメント](https://maplibre.org/maplibre-style-spec/expressions/) では、サンプルとして [RGB](https://maplibre.org/maplibre-style-spec/expressions/#rgb) を使った動的の色制御が紹介されています。ただし、サンプルでは華氏を前提にしているため、日本は摂氏を採用しているため少し工夫が必要です。

例えば、摂氏30度の時を赤色、摂氏-30度のときを青色のグラデーションで表現したい場合、以下のように `circle-color` を指定します。

```tsx
map.addLayer({
  id: 'temperature_circle_layer',
  type: 'circle',
  source: 'temperature_data',
  layout: {},
  paint: {
    "circle-color": [
      // RGBを指定
      "rgb",
      // Rの値: 255 * ((気温のデータ + 30) / 60)
      ["*", 255, ["/", ["+", 30, ["get", "temperature"]], 60]],
      // Gの値: 0
      0,
      // Bの値: 255 * (1 - (気温のデータ + 30) / 60)
      ["*", 255, ["-", 1, ["/", ["+", 30, ["get", "temperature"]], 60]]],
    ],
    'circle-radius': 18
  }
})
```

RGBによる指定は、配列の最初の要素に `rgb` と指定して、その後「赤の値」「緑の値」「青の値」の順番で値を指定します。

MapLibreで数式を書くときは、配列で書きます。例えば `40-20` を定義したい場合は、 `["-", 40, 20]` と定義します。GeoJSONの `properties` の値を取得したい場合は `["get", "temperature"]` と書くことで取得することができます。これらの式に関する書き方について、詳しくは [Expressionsのドキュメント](https://maplibre.org/maplibre-style-spec/expressions/#expressions) を御覧ください。 



実際にローカルでサーバを立ち上げて見てみると、以下の画像のように青と赤のグラデーションで円の色を定義することができます（気温の値がわかるように [Symbol](https://maplibre.org/maplibre-style-spec/layers/#symbol) で気温値をラベリングしています）。

![気温の値によって円の色が変わる](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93706/50d333cb-dee3-ac57-ba7b-a4d4f019da4a.png)

## interpolate で色を補間しながら変える

RGBによる色の制御は、2色でのグラデーションを表現するには有効は手法ですが、多色のグラデーションで表現したいとなると、式が複雑になり大変なことになります。そこで利用するのが [interpolate](https://maplibre.org/maplibre-style-spec/expressions/#interpolate) です。この式は、配列形式で２つ以上の入力値と出力値を定義すると、それぞれの入力値で値を補間して、連続的な出力値の値を生成してくれます。出力値は色の値だけではなく、円の大きさなどにも応用ができます。

以下のように `circle-color` に気温（入力値）を10度ごとに区切りながら色（出力値）を定義すると、気温の値を補間して連続的な色を出力してくれます。

```tsx
map.addLayer({
  id: 'temperature_circle_layer',
  type: 'circle',
  source: 'temperature_data',
  layout: {},
  paint: {
    "circle-color": [
      // interpolateを指定
      "interpolate",
      // 補間形式を指定（配列）
      ["linear"],
      // 基準値の要素を指定
      ["get", "temperature"],
      // 基準値（入力）と色（出力）を交互に指定
      // -30℃のときに黒
      -30,
      "#000000",
      // -20℃のときに青
      -20,
      "#005AFF",
      // -10℃のときに水色
      -10,
      "#4DC4FF",
      // 0℃のときに白
      0,
      "#FFFFFF",
      // 10℃のときに黄
      10,
      "#FFF100",
      // 20℃のときに赤
      20,
      "#FF4B00",
      // 30℃のときに紫
      30,
      "#990099"
    ],
    'circle-radius': 18
  }
})
```

実際にローカルでサーバを立ち上げて見てみると、気温の値ごとに円の色が変化します。気温の値を補間しているので、きれいなグラデーションで表現してくれます。

![気温の値によって色が変化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93706/d5441066-ca73-cd08-8773-46731d0ba464.png)

### 補間形式

`interpolate` を指定するときに、補間する手法を３つの中から選択します。

* `linear` : 線形補間。始点から終点まで直線的に補間します。
* `exponential` : 指数関数。指数を入力値として与えて、始点から終点まで指数関数的に補間します。
* `cubic-bezier` : ３次 [ベジェ曲線](https://ja.wikipedia.org/wiki/%E3%83%99%E3%82%B8%E3%82%A7%E6%9B%B2%E7%B7%9A)。制御点を入力値として与えて、始点から終点まで滑らかな曲線で補間します。

実際にどのように色が変化するのか視覚的に見たいため、LineStringの色のグラデーションで見てみます。以下のように Line の Layer を用意して、それぞれの補間手法でLineStringの色の変化を見てみます。

```tsx
// 線形補間
map.addLayer({
  id: 'linear_layer',
  type: 'line',
  source: 'linear_data',
  paint: {
    "line-color": "red",
    "line-width": 20,
    "line-gradient": [
      "interpolate",
      // 線形補間
      ["linear"],
      ["line-progress"],
      0,
      "#000000",
      1,
      "#FFFFFF"
    ]
  },
  layout: {
    'line-cap': 'round',
    'line-join': 'round'
  }
})

// 指数関数
map.addLayer({
  id: 'exponential_layer',
  type: 'line',
  source: 'exponential_data',
  paint: {
    "line-color": "red",
    "line-width": 20,
    "line-gradient": [
      "interpolate",
      // 指数関数 指数は3を設定
      ["exponential", 3.0],
      ["line-progress"],
      0,
      "#000000",
      1,
      "#FFFFFF"
    ]
  },
  layout: {
    'line-cap': 'round',
    'line-join': 'round'
  }
})

// 3次ベジェ曲線
map.addLayer({
  id: 'cubic_bezier_layer',
  type: 'line',
  source: 'cubic_bezier_data',
  paint: {
    "line-color": "red",
    "line-width": 20,
    "line-gradient": [
      "interpolate",
      // 3次ベジェ曲線 (x1, y1, x2, y2) = (0.5, 0.0, 0.5, 1.0)
      ["cubic-bezier", 0.5, 0.0, 0.5, 1.0],
      ["line-progress"],
      0,
      "#000000",
      1,
      "#FFFFFF"
    ]
  },
  layout: {
    'line-cap': 'round',
    'line-join': 'round'
  }
})

```

それぞれの補間手法で描画すると、以下のような画像となります。左から「線形補間」「指数関数」「３次ベジェ曲線」です。

![それぞれの補間手法の変化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93706/e8eab47e-f717-8592-e8f4-380ff2d58a8d.png)

指数関数は指数を３で設定しているため、線形補間と比較して黒く変化していくのがわかります。３次ベジェ曲線は、線形補間と比較して滑らかに色が変化しているのがわかります。

## おわりに

MapLibreのスタイル定義は、少し独特なものとなっています。 [Styleに関する公式ドキュメントが公開されている](https://maplibre.org/maplibre-style-spec/) ので、こちらも合わせてご覧ください。