小縮尺地図におけるポイントデータのデザイン

## はじめに

Web 地図においてポイントデータを表示する際に、データが大量にあると、パフォーマンス上、小縮尺にすべて表示することはできない場合があります。その場合は、何らかの手段で表示するデータを削減しなくてはなりません。今回の記事では、小縮尺におけるポイントデータの表現方法（デザイン）の工夫点について、自分の知見を記載しておこうと思います。

:::note warn
なお、前提とする技術として、Web 地図ライブラリとしては [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js)/[Maplibre GL JS](https://github.com/maplibre/maplibre-gl-js)、データとしては Mapbox Vector Tile 形式のベクトルタイル、ベクトルタイル作成には [tippecanoe](https://github.com/felt/tippecanoe) の利用を想定して記述しています。他の GIS ソフトウェアを利用する等では、評価が異なる場合もありますのでご承知おきください。
:::

## 候補となる手法と例
ポイントデータの表示方法としては、以下の方法が考えられます。
1. 省略：そもそも一部のデータを表示しない（間引き）。
2. クラスタリング：複数のポイントをまとめて１つのポイントして表示する。
3. ヒートマップ：ポイントデータを密度として表示する。

これらの方法について概説すると、以下のようになるでしょう。

### 省略
* 考え方がシンプル。
* 省略していない個々のポイント特有の情報を保持できる。
* どのポイントを省略するか、優先度付けが必要。

単純に縮尺を大きくしていくと優先度の低いポイントが現れてくるというのは、地図作成のテクニックからしてもわかりやすくシンプルな方法です。優先度の高いポイントのデータはそのまま保持できます。一方、**どのポイントを表示しない（優先度を低くするか）は悩みどころ**かと思います。

パフォーマンスを考えると、データ作成の段階から、優先度の低いポイントデータは間引いておきたいものです。

たとえば、地理院地図の[基準点](https://maps.gsi.go.jp/development/ichiran.html#cp)では、この方法が使われています。基準点は、電子基準点、一等三角点、二等三角点、三等三角点……という形で、種類・等級が異なってきますので、小縮尺では電子基準点のみを表示（一等三角点以下は省略）し、ズームレベルを上げるごとに、表示する等級の三角点（その他の基準点）を増やしていくという方法が自然と採用できます。

https://maps.gsi.go.jp/#7/35.764343/139.191284/&base=std&ls=std%7Ccp&disp=11&lcd=cp&vs=c1g1j0h0k0l0u0t0z0r0s0m0f1&d=m

### クラスタリング
* 複数のポイントをまとめるため、ポイント数の情報を保持できる。
* 個々のポイント特有の情報（ID や名称）は失われる。
* クラスタリングのためのグループ分けをどのようにするのかが課題。
* クラスタリングしたポイントの表示場所も検討が必要。

クラスタリングでは、複数のポイントをまとめて一つのポイントとします。地図編集上の技法としては、「総描」の仲間かと思います。

今回念頭に置いている tippecanoe でもそうですが、クラスタリング結果のポイントデータに、元となるポイント数を保持することが多いです。そのため、クラスタリングした後も、ポイント数で重み付け処理を行うことが可能です。一方、各ポイント特有の情報（たとえば、ポイントの ID や施設名称など）は失われるでしょう。

なお、Mapbox GL JS/Maplibre GL JS では、ベクトルタイルの場合は、読み込んだ後のクラスタリング処理は対応していないようなので、ベクトルタイルの生成の段階からクラスタリング処理しておく必要があります。（ちなみに、GeoJSON の場合は、クラスタリング処理ができます。）

クラスタリングする際の検討事項としては、**どのような基準でまとめるか**という点が挙げられます。特に属性値は考慮せずに近傍のポイントをまとめるという方法もあるかと思いますが、何らか属性値に基づいてまとめることにより、（データ量削減効果は落ちるかもしれませんが）属性値もある程度有効に生かすことができるかもしれません。

また、単純に構成するポイントの重心などにクラスタリングした代表点を置くと、全く頓珍漢なところに表示される可能性もあるので、そこも注意が必要です。

この方法の使用例として、たとえば、内閣官房国民保護ポータルサイトの[避難施設](https://www.areamarker.com/kokuminhogo/map)の表現が挙げられます。この地図では、大縮尺では、施設ごとに、名称や所在地、収容人数といった情報を持つ個々のポイントデータを表示しています。少し縮尺を小さくすると、近傍の施設を1つのポイントにまとめるようなクラスタリングを行います。さらに縮尺を小さくすると、今度は、「市区町村」でグループ化し、市区町村ごとの代表点に、その市区町村に存在する避難施設数をクラスタリングして表示させます。この時点で、各施設の個々の情報は表示できなくなります。さらに縮尺を小さくすると、「都道府県」でグループ化されてクラスタリング表示されます。都道府県知事が、市町村と連携し、避難施設の指定を行っているという[制度上の趣旨](https://www.kokuminhogo.go.jp/hinan/index.html)からも、このクラスタリング構成は分かりやすいと思います。

https://www.areamarker.com/kokuminhogo/map

:::note
少し横道にそれますが、上記のアイデアは、コロプレス図でも同様に使えるアイデアだと思います。コロプレス図の場合は、さらに別のデータ（たとえば、自治体ごとのポリゴンデータ）等へ結びつける必要があります。[警視庁犯罪情報マップ](https://map.digipolice.jp/)は小縮尺でコロプレス図を利用しています。
:::

クラスタリングについては、tippecanoe の `--cluster-distance=` や `--cluster-maxzoom=`、`--accumulate-attribute=` 等のパラメータが用意されており、これらを駆使することで小縮尺のタイルについてクラスタリング処理を行うことができます。場合によっては、前処理が必要になるかもしれません。

### ヒートマップ
* データの密度を面的に表現可能。
* 各ポイントの属性値等を確認できない。
* 表現上の手法のため、そもそものデータ量の削減が難しい。

ヒートマップは、ポイントデータの密度を表現できる方法です。ヒートマップについては、パフォーマンス改善よりも、**「見た目の問題」を解決する手法**という性質が強いかもしれません。クラスタリングとは異なり、密度で表現するため、代表地点の位置による誤解などを招く可能性が小さくなることや、たくさんの点が重なり合って見づらい状況を改善することといった効果が考えられます。

Mapbox GL JS/Maplibre GL JS では、ポイントデータからヒートマップを表示できるスタイルのタイプ（[`heatmap`](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#heatmap)）が用意されています。

ヒートマップにすると、それだけでは個々のポイントの位置や属性値等の表示は難しくなります。Mapbox GL JS/Maplibre GL JS において、各ポイントをクリックした際にポップアップを出すようなインタラクティブな操作は、ヒートマップでは制約があります。ただし、個々のポイントの情報は、データ読み込みの段階では保持されていますから、属性値に応じたフィルタリングや重みづけ等で、動的にヒートマップの表現を変更することは可能です。

一方、入力データが個々のポイントデータのままである場合、データ削減という観点では、パフォーマンス改善のそこまで寄与しないと考えられます。うまくやれば、大量のデータをレンダリングするコストを回避できる可能性も想像できますが、データ転送によるパフォーマンス劣化には対応できません。あらかじめ、ヒートマップをラスタタイル等として作成しておけば、パフォーマンスは向上するかも知れませんが、逆に動的な表現の変更が難しくなるというトレードオフが発生します。

:::note warn
なお、自分が Mapbox GL JS v1 で少し試してみたところ、ポイントをそのまま表示するのとヒートマップとして表示するのでは、それほど表示時間が変わりませんでした。ポイント数の影響があるなど、パフォーマンスへの寄与については単純に測れなさそうですので、パフォーマンス向上を期待するなら、よくよく検討が必要だと思います。
:::

使用例として、たとえば、警視庁の[交通事故発生マップ](https://www2.wagmap.jp/jikomap/Map)では、交通事故の発生密度をヒートマップで表示しています。別途、個々の交通事故のポイントデータも表示できますが、大縮尺に限られているようなので、実質的にヒートマップが小縮尺用の表現として活用されていると言えるでしょう。なお、事故の種類によって、ヒートマップが複数用意されているようです。

https://www2.wagmap.jp/jikomap/Map

## 手法適用に関する方針（個人的考え）

自分としては、以下のような優先度で、各手法の適用を考えています。

1. データに優先度がある→ **省略**
2. データに階層がある→ **クラスタリング**
3. 見た目の問題（表示される位置が誤解を招きそう、点が集まりすぎててよくわからない）がある→ **ヒートマップ**

これらの手法は使えば使うほどデータから情報量が失われていきます。そのため、タイルのファイルサイズやユースケースをよく観察しながら、どの手法をどの程度使うか検討するのが良いと考えているところです。

### ケーススタディ
例えば、国土地理院が提供している[指定緊急避難場所](https://www.gsi.go.jp/bousaichiri/hinanbasho.html)のデータについて、小縮尺でどのように表現するか考えてみます。全部で11万件を超えるポイントデータですので、小縮尺での工夫は必須です。なお、指定緊急避難場所データは、地理院地図やハザードマップポータルサイトでも表示できますが、特に小縮尺で特別な表現を行っているわけではないようです（無理なく表示できる ZL から表示させています。）

データの属性値としては、都道府県・市町村、施設名・所在地、災害種別といったデータが含まれています。また、指定緊急避難場所は、災害対策基本法により、市町村長が災害種別（異常な現象の種類）ごとに指定することになっています。

上記より、次のような考えができるでしょう。

* まず、優先度を付けられるかという点について、これは難しそうなので、**省略は候補から外す**。
* 指定緊急避難場所は市町村長が災害種別ごとに指定するという趣旨から、「都道府県・市町村」と「災害種別」のいずれかまたは両方で**グループ化して、クラスタリングする**のが良いと思われる。集計値は災害種別ごとに集計するのも手（tippecanoe の `--accumulate-attribute=` が使えるかも）。どの程度クラスタリングするのかは、データサイズを観察しながら調整することが必要。
* もし、クラスタリングした際に、誤解を招くような位置（例えば、クラスタリング後の代表点が他の市町村に表示されてしまう等）があり、クラスタリング時点での対象が難しい場合は、**ヒートマップで大まかな位置や分布をつかめるように**しておき、「詳細を見たければ地図を拡大してください」という UI 設計とする。

### 参考：クラスタリング後のヒートマップ表示
クラスタリングしたポイントデータのヒートマップ化について、試しに、指定緊急避難場所（2023/1/5時点のデータ）について、属性値は無視して単純に位置関係のみでクラスタリングしたデータと、それをさらにヒートマップ（Mapbox GL JS の `heatmap` のスタイルレイヤ）で表示した例をお示ししておきます。いずれも背景地図は国土地理院最適化ベクトルタイルです。

クラスタリングの方は、まとめられたポイント数に合わせて円の大きさを変更しています。濃い赤は、クラスタリングされていない単独のポイントです。

クラスタリングする距離や、ヒートマップの重みづけや配色などをもう少し工夫するとより綺麗になるのではないかと思います。こういった点も試行錯誤が必要な箇所ですね。

![クラスタリング例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/b4643176-0b6a-b435-adbf-17e7b777ce06.png)
![ヒートマップ例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/4f970e9e-975c-e5f1-702b-6490f1b7a6a9.png)

なお、ヒートマップの上にクラスタリングしたポイントを重ねてみたところ、気が引けるような表現（ヒント：集合体恐怖症？）となったため、掲載は見送っています。ポイントデータの表現方法は気を付けなければならないかもしれません。そういう点では、 **ヒートマップ化の需要は結構大きい** かもしれません。