MapLibre　レイヤーから特定の市区町村の町丁目だけを抜き出して表示したいときのExpression


別の記事で、日本全国の町丁目ポリゴンのベクトルタイルから特定の市区町村の町丁目だけを色塗りする場面がありました。
その時のやり方があっているのか疑問だったので、振り返りつつ他の方法も考えてみます。
そのときの記事はこちら↓

https://qiita.com/4gou/items/0bd5dd6217d0fef64757#%E7%94%BA%E4%B8%81%E7%9B%AE%E3%83%AC%E3%83%99%E3%83%AB%E3%81%A7%E5%B9%B3%E5%9D%87%E5%B9%B4%E5%8F%8E%E3%82%92%E8%A6%8B%E3%82%8B

# index-ofでできけど、それでいいの？
その記事で使用した方法は以下のような感じです。（記事で使用したコードを少し簡略化しています）
ソースとして町丁目ポリゴンのベクトルタイルを指定し、レイヤーに町丁目ポリゴンの塗りつぶしレイヤーとラインレイヤーをセットしました。
```js
import './style.css'
// MapLibreの読み込み
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

const map = new maplibregl.Map({
    container: 'map',
    style: {
        version: 8,
        sources: {
            chomokuTile: {
                type: 'vector',
                tiles: [
                    'http://localhost:5173/nenshu2020_layer2023_chomoku/tiles/{z}/{x}/{y}.pbf'
                ],
            }
        },
        layers: [
            {
                id: 'chomokuPolygonLayer',
                type: 'fill',
                source: 'chomokuTile',
                'source-layer': 'nenshu2020_layer2023_chomoku',
                paint: {
                    'fill-color': '#d7352b',
                    'fill-opacity': 0.6,
                },
                //別海町のジオコード（01691）で始まる町丁目のみ表示されるようにフィルターをセット
                filter: ['==', ['index-of', '01691', ['get', 'geocode']], 0]
            },
            {
                id: 'chomokuLineLayer',
                type: 'line',
                source: 'chomokuTile',
                'source-layer': 'nenshu2020_layer2023_chomoku',
                paint: {
                'line-color': '#666',
                'line-opacity': 0.5,
                'line-width': 0.1
                },
            },
        ],
    },
    center: [144.95707, 43.41482],
    zoom: 9
});
```
実行結果
![別海町の町丁目ポリゴンをindex-ofで抽出](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/96ecf7f0-687b-2494-0304-d988957aa195.png "別海町の町丁目ポリゴン")


町丁目ポリゴンの塗りつぶしレイヤーでfilterをセットし別海町の町丁目だけを抜き出しました。
```js
//この部分が本記事の主題
//別海町のジオコード（01691）で始まる町丁目のみ表示されるようにフィルターをセット
filter: ['==', ['index-of', '01691', ['get', 'geocode']], 0]
```
別海町のジオコード（この場合は市区町村コードと同義）である「01691」で始まっている町丁目のジオコードだけを表示対象としてfilterしました。
[index-of](https://maplibre.org/maplibre-style-spec/expressions/#index-of)を使ってみたけどなんだかしっくりこないなと思ったのがこの記事を書くきっかけです。
紹介するサンプルコードはMapLibre GL JSを対象としていますが、おそらくMapbox GL JSでも動作すると思います。

## 前提事項
他の方法をあげる前に、前提となる知識や条件を少し整理します。
### ジオコードについて
まず初めにジオコードについて軽く触れます。
ジオコードとは特定の場所を表すためのコードです。特定の場所を識別するための符号であったり座標情報だったりします。この記事では行政界のコードをジオコードとしている場面を想定しています。
行政界の場合、都道府県は数字2桁の都道府県コードがジオコードとして使われることがほとんどです。市区町村は5桁または6桁の市区町村コードや全国地方公共団体コードが使われます。

|  | ジオコード |
|:------------|:------------|
| 北海道 | 01 |
| 東京都 | 13 |
| 北海道札幌市中央区 | 01101 / 011011|
| 千代田区 | 13101 / 131016 |

表を見てもらうと市区町村のジオコードの上2桁は都道府県コードと一致します。行政界のジオコードは上位となるレイヤー（市区町村であれば都道府県）の中に含まれる構造になっています。
行政界には市区町村より細かい単位として町丁目があります。町丁目のジオコードは上5桁/6桁が市区町村コードになることがほとんどだと思います。

#### ジオコーディング
ジオコーディングは住所をインプットとして座標を求める機能や行為を指します。逆に座標から住所を求めるのはリバースジオコーディング/逆ジオコーディングと言われます。

### 特定の市区町村の町丁目だけを抜き出す
"特定の市区町村の町丁目だけを抜き出す"が何を指すのかと言うと、例えば千代田区の町丁目だけを表示させたり、色付けをするということです。
千代田区の町丁目のジオコードは13101で始まっていますので、ジオコードの上5桁が13101に一致するものを抜き出せば実現できるだろうと考えました。
上のコード例では別海町のジオコード01691で始まっている町丁目だけを抜き出しています。

#### 補足
この記事で取り上げているケースは町丁目ポリゴンのベクトルタイルのプロパティに市区町村コードを持っていない場合です。もしプロパティに市区町村コードを持っていたら、単純にその市区町村コードがマッチするか判定すれば済みます。

# sliceを使え
いきなりですが、[slice](https://maplibre.org/maplibre-style-spec/expressions/#slice)を使うのが正解だと思います。今回のような特定の文字列で始まっているプロパティの値を見つけるにはindex-ofよりsliceの方が適していますね。
上の例のコードを書いたとき、substringみたいなことができるexpressionがないかなと探していたのに見つけられなくてindex-ofを使用しました。改めてドキュメントを見てみたらsliceの説明に思いっきりsubstringって書いてありました。ちゃんと読め自分です。
レイヤーにセットしたfilterの条件を以下のようにsliceに変えます。
ちょっとした違いですがindex-ofと比べて目的がはっきりして読みやすくなりました。
```js
{
    id: 'chomokuPolygonLayer',
    type: 'fill',
    source: 'chomokuTile',
    'source-layer': 'nenshu2020_layer2023_chomoku',
    paint: {
        'fill-color': '#d7352b',
        'fill-opacity': 0.6,
    },
    // 別海町のジオコード（01691）で始まる町丁目のみ表示されるようにフィルターをセット
    // geocodeの0～5桁をsliceして別海町のジオコード（01691）と一致するか判定
    'filter': ['==', ['slice', ['get', 'geocode'], 0, 5], '01691']
},
```
## 条件に該当する町丁目と該当しない町丁目で塗りつぶし色を変えたい場合
filterで該当するものだけを抜き出すのではなく、条件に該当する町丁目と該当しない町丁目で塗りつぶし色を変えたい場合は、filterではなくfill-colorに[case](https://maplibre.org/maplibre-style-spec/expressions/#case)を指定して実現できます。（layerを2つ作って別々のfilterをセットしても実現できますが面倒ですよね）
```js
{
    id: 'chomokuPolygonLayer',
    type: 'fill',
    source: 'chomokuTile',
    'source-layer': 'nenshu2020_layer2023_chomoku',
    paint: {
        // 別海町のジオコード（01691）で始まる町丁目とそうじゃない町丁目で色を変える
        'fill-color': [
            'case',
            ['==', ['slice', ['get', 'geocode'], 0, 5], '01691'],
            '#d7352b',
            '#ccc'
        ],
        'fill-opacity': 0.6,
    },
},
```
![fill-colorにcaseで条件指定して町丁目ポリゴンを色分け](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/8658e22d-6f76-8f70-d08d-45d31b6fa33c.png "fill-colorにcaseで条件指定して町丁目ポリゴンを色分け")

## 複数の市区町村を指定したいときはanyやin
複数の市区町村を指定したいときはanyやinなどのexpressionで実現できます。
### anyを使う場合
[any](https://maplibre.org/maplibre-style-spec/expressions/#any)を使って複数の市区町村を指定する例です。
```js
// anyの例
{
    id: 'chomokuPolygonLayer',
    type: 'fill',
    source: 'chomokuTile',
    'source-layer': 'nenshu2020_layer2023_chomoku',
    paint: {
        'fill-color': '#d7352b',
        'fill-opacity': 0.6,
    },
    // anyで複数の市区町村コードを指定
    'filter': [
        'any', 
        ['==', ['slice', ['get', 'geocode'], 0, 5], '13103'], // 港区
        ['==', ['slice', ['get', 'geocode'], 0, 5], '13104'], // 新宿区
        ['==', ['slice', ['get', 'geocode'], 0, 5], '13105'], // 文京区
        ['==', ['slice', ['get', 'geocode'], 0, 5], '13108'], // 江東区
    ]
},
```
何度もsliceしなくていいように、letやvarを使って[variable binding](https://maplibre.org/maplibre-style-spec/expressions/#variable-binding)を行うこともできます。
```js
// sliceの結果を変数に入れる場合
{
    id: 'chomokuPolygonLayer',
    type: 'fill',
    source: 'chomokuTile',
    'source-layer': 'nenshu2020_layer2023_chomoku',
    paint: {
        'fill-color': '#d7352b',
        'fill-opacity': 0.6,
    },
    'filter': [
        // letでsliceした結果をcityにセット
        'let',
        'city',
        ['slice', ['get', 'geocode'], 0, 5],
        // varでcityを呼び出し
        [
            'any', 
            ['==', ['var', 'city'], '13103'],
            ['==', ['var', 'city'], '13104'],
            ['==', ['var', 'city'], '13105'],
            ['==', ['var', 'city'], '13108'],
        ]
    ]
},
```
[in](https://maplibre.org/maplibre-style-spec/expressions/#in)を使用する場合です。
```js
{
    id: 'chomokuPolygonLayer',
    type: 'fill',
    source: 'chomokuTile',
    'source-layer': 'nenshu2020_layer2023_chomoku',
    paint: {
        'fill-color': '#d7352b',
        'fill-opacity': 0.6,
    },
    // inを使用して抽出
    'filter': [
        'in', 
        ['slice', ['get', 'geocode'], 0, 5],
        ['literal', ['13103', '13104', '13105', '13108']],
    ]
},
```
複数の市区町村を指定して町丁目を抜き出した結果です。港区、新宿区、文京区、江東区の町丁目を抜き出してみました。
![anyやinで町丁目ポリゴンを抽出](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/21bbc7c9-9f77-3558-200c-9e78a5b3e07d.png "anyやinで町丁目ポリゴンを抽出")