MapLibre GL JS の expression 事例

# 背景
MapLibre GL JS を使って地図描画をしていると、複雑な条件を使って何かを描画したいことにちょくちょく直面。悲しいことに、MapLibre GL JSのドキュメントを見てもイマイチ使い方が分からないことも多々💦
ということで、それぞれ使い方を試しつつ、まとめてみました。

MapLibre GL JS の細かい書き方については記載していないので、ご了承ください。

   
# 事例
## その前に、単純なケース
通常、GeoJSONのプロパティ値が以下のようなデータであった場合、
```JavaScript:データ
{ "properties": { "data": 20 } }
```
その値に応じて円の色を変えるといったときは、このように記述する。

```JavaScript:円を着色
"circle-color": [
  "step",
  ["get", "data"], // properties の data の値を取得
  "#ffffff", 5, // 5 まで白
  "#ffe100", 10, // 10 まで黄色
  "#76ff00", 15, // 15 まで緑
  "#00fffa", 20, // 20 まで水色
  "#7700ff" // 20 以上 紫
],
```


## オブジェクトの値を取得
データが上記のように単純だと話は早いが、そうもいかないケースもある。
以下のように階層構造を持つオブジェクトになっている場合。
```JavaScript:データ
{ "properties": { "data": { "2020": 20, "2021": 15, "2022": 10, "2023": 5 } } }
```

このようなケースでは、以下のように `get` を繰り返すことで値を取得することができる。

```JavaScript: 取得方法
"circle-color": [
  "step",
  ["get", "2020", ["get", "data"]], // get を何度も繰り返す
  "#ffffff", 5,
   <略>
],
```
`["get", "data"]` で まずは `{ "2020": 20, .... }` をオブジェクトとして取得して、そのオブジェクトから、再度 `["get", "2020"]` で値を取り出している。
分かれば単純だけど、いきなりこのケースに直面すると、結構思考が停止する（した）。

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="zYebELd" data-user="humo-tech" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/humo-tech/pen/zYebELd">
  maplibre template</a> by humo (<a href="https://codepen.io/humo-tech">@humo-tech</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

## 配列の値を取得（at）
オブジェクトとくれば、配列。
```JavaScript:データ
{ "properties": { "data": [ 20, 15, 10, 5 ] } }
```

配列の取得に使うのは `at`。
`["get", "data"]` で まずは `[ 20, 15, 10, 5 ]` を配列として取得して、その配列からのN番目の値を `["at", 2]` といった形で取得する。
```JavaScript:取得方法
"circle-color": [
  "step",
  ["at", 2, ["get", "data"]], // at で 配列の値を取得
  "#ffffff", 5,
   <略>
],
```

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="eYxXGaE" data-user="humo-tech" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/humo-tech/pen/eYxXGaE">
  maplibre expressions (object)</a> by humo (<a href="https://codepen.io/humo-tech">@humo-tech</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

### 配列がproperties に直接書かれている場合（properties）
そんなケースないかもしれないけれど「propertiesに配列が直接定義されている場合」って、値取れるんだろうか？と思って実験してみた。
```JavaScript:データ
{ "properties": [ 20, 15, 10, 5 ] }
```
結論としては、以下の方法で取れました（知らなかった）。
```JavaScript:取得方法
["get", "2", ["properties"]
```
`properties` は「プロパティをオブジェクトで返してくれる」。配列で返ってきてくれれば `at` で取得するのだけれど、オブジェクトで返ってくるので、`get` で取得。
何かほかにも方法知ってる方いれば、教えてくださいｍ＿＿ｍ


## 文字列の整形 (format)
地図上に文字を表示したい場合に、ちょっと工夫を加えるケース。

通常は、こんな感じで記述
```JavaScript: データ
{ "properties": { "name": "東京", "density": 6409.29 } }
```
```JavaScript: 通常の方法
{
  "text-field": ["get", "name"]
}
```
```JavaScript: テキストつなげる
{
  "text-field": ["concat", 
    ["get", "name"], 
    "\n", 
    ["get", "density"]
  ]
}
```

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="xxMBPRy" data-user="humo-tech" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/humo-tech/pen/xxMBPRy">
  maplibre expressions (concat)</a> by humo (<a href="https://codepen.io/humo-tech">@humo-tech</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>


それでも十分色々表現できるけれど、2つの項目（この場合 name と density）でフォントサイズ変えたり、色変えたりといったこともしたいケースが出てくる。
そんなときに役立つのが `format`。

```JavaScript: 記述方法
"text-field":[
    "format",
    ["get", "name"], {"text-color": "#000"}, // name は黒で表記
    "\n", // 改行を繋げて
    ["get", "density"], {"text-color": "#f00", "font-scale": 0.8}, // density は 赤文字で少し小さめに

]
```

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="JjxzOMz" data-user="humo-tech" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/humo-tech/pen/JjxzOMz">
  maplibre expressions (concat)</a> by humo (<a href="https://codepen.io/humo-tech">@humo-tech</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

## 数値の整形 (number-format)
上の例で、3桁カンマが欲しいな、小数第1位まででいいな、と思うケースでは、`number-format` で整形ができる。
```JavaScript: 記述方法
[ "number-format", // number-format を使うと、3桁カンマが入る
  [ "get", "density" ],
  { "min-fraction-digits": 1, "max-fraction-digits": 1 } // min/max-fraction-digits で桁数調整
],
```
なお、`{ "min-fraction-digits": 3  }`  のように指定すると、データが `29.12` だった場合に `29.120` とゼロ埋め表示してくれるみたいです。

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="WNPmXyE" data-user="humo-tech" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/humo-tech/pen/WNPmXyE">
  maplibre expressions (format)</a> by humo (<a href="https://codepen.io/humo-tech">@humo-tech</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

## 代替データ（coalesce）
`symbol` 機能で、マップ上に画像を表示する際に、あらかじめ用意した画像以外を呼び出してしまったときの fallback として、`coalesce` の機能が使えます。
なお、発音難しいこの `coalesce`は（こあれす？）、 **与えられた値を順番に評価していって、最初にnullじゃない値が返ってきたものを使用してくれる** ものになります。

```JavaScript: データ
properties: { "image": 'kitten1' }
```
`coalesce` を使わない普通の書き方↓。この場合、存在しない画像は何も表示されない。
```JavaScript: 記述方法
"icon-image": ["get", "image"]
```
`coalesce` を使ってフォールバック画像を表示する書き方↓。こう書くと、存在しない画像を指定してしまった場合にダミーの画像を替わりに表示してくれます。
```JavaScript: 記述方法
"icon-image": [
  "coalesce", 
  ["image", ["get", "image"]], 
  "dummy"
]
```
なお、ここで突如出てくる `image` は、**画像があるかどうかを判定して、あれば画像パスを、なければ null を返してくれる**ものとなっています。このことにより「存在しない画像（null）の替わりにdummy画像を表示する」という動きをしてくれます。

以下の例で石川県あたりにある「ダミー」画像は、そのようにして表示されたものになっています。

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="YzBgYBr" data-user="humo-tech" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/humo-tech/pen/YzBgYBr">
  maplibre expressions (coalesce)</a> by humo (<a href="https://codepen.io/humo-tech">@humo-tech</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>


## ポリゴン内判定（within）
「特定のポリゴン内にあるかどうか」の判定をしてくれるもの。
これを使うと「ポリゴン内にあるもの」と「そうでないもの」でスタイルを分けることができるようになる。

```JavaScript:例
'circle-color': [
  'case',
  ['within', areaPolygon],
  '#f0f',
  '#0ff'
],
```

<details>
  <summary>areaPolygon の値</summary>

  ```JavaScript
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "coordinates": [
          [
            [
              139.44336833463933,
              35.989952696688704
            ],
            [
              139.5366426753509,
              35.57883425456254
            ],
            [
              140.02011467470146,
              35.588948481200916
            ],
            [
              140.29838312448936,
              35.90185508166421
            ],
            [
              139.6268078713709,
              36.12442624848467
            ],
            [
              139.44336833463933,
              35.989952696688704
            ]
          ]
        ],
        "type": "Polygon"
      }
    }
  ]
}
  ```
</details>

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="dyardRm" data-user="humo-tech" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/humo-tech/pen/dyardRm">
  maplibre expressions (at (Array))</a> by humo (<a href="https://codepen.io/humo-tech">@humo-tech</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>

例はあげないですけれど、地名表記の言語設定に `within` を用いれば、例えば「北方領土の地名は必ず日本語で表示する」ということも、できたりします（日本語表記の定義があることは前提）。

## 変数（let/var）
properties から計算で求めた値を、何度も使いまわしたいときに、let/var を使うことができます。
MapLibre公式サイトからの抜粋になりますが、こんな感じです。
```JavaScript:let/var 実装例
'fill-color': [
  // 人口（population）と面積（sq-km）から 人口密度（density）を計算
  'let',
  'density',
  ['/', ['get', 'population'], ['get', 'sq-km']],
  [
    'interpolate', ['linear'], ['zoom'], 
    // zoomレベル8まで
    8,
    [
      'interpolate',
      ['linear'],
      ['var', 'density'], // 人口密度の値で色を変える
      274,
      ['to-color', '#edf8e9'],
      1551,
      ['to-color', '#006d2c']
    ],
    // zoomレベル10まで
    10,
    [
      'interpolate',
      ['linear'],
      ['var', 'density'], // 人口密度の値で色を変える
      274,
      ['to-color', '#eff3ff'],
      1551,
      ['to-color', '#08519c']
    ]
  ]
],
```

https://maplibre.org/maplibre-gl-js/docs/examples/visualize-population-density/


## 距離（distance）
MapLibre GL JS では、未サポートだった、、、。
（MapBox GL JS ver3 ではサポートされた）
距離に応じた着色とかができるんだと期待しています。

# まとめ
MapLibre GL JS の Expressionを色々試してみました。
知らなかった機能もチラホラ。
何かのお役に立てば幸いです。

<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>