☁️

ヒートマップレイヤーの勘所

2023/07/31に公開

はじめに

Mapbox Newsletter WEEKLY TIPSの解説 -「ヒートマップレイヤーの作成」ではサンプルを用いてヒートマップレイヤーの使い方を確認しました。この記事ではヒートマップが内部的にどのような処理をしているのかについて見ていきます。また、「手持ちのデータで試してみたけど全体的に01の色になってしまってうまくいかない」という場合にも参考にもなるかと思います。

こちらのブログで詳細が解説されているので合わせてご参照ください。

カーネル密度推定

ヒートマップは密度を色で表現するレイヤーでした。ここで言う密度とは「ポイントデータがどれぐらい密に存在するか」ということです。ただし、ポイントデータそのままでは離散値なのでポイント自体は密度1, ポイントとポイントの間の地点が密度0のような状態になってしまいます。ヒートマップレイヤーとしては、ポイント間もそれぞれのポイントからの影響に応じて密度を決めていい感じの色を出力したいです。そこで利用されるのがカーネル密度推定です。

カーネル密度推定はポイントデータの地点を中心とする正規分布を仮定します。例えば以下の図のように、あるポイントが正規分布の中心に来ます。そしてその周辺になだらかな山状に値が分布します。ポイント地点での値を1とすると、ポイントの近くであれば0.9だったり、遠くであれば0.1だったりします。これによりポイントの影響を周囲に反映させることができますね!

Normal 01

複数のポイントが存在するときは正規分布が重ね合わされます。以下の図ではポイント1による正規分布の山が青線、ポイント2による正規分布の山が赤線、それらの重ね合わせがオレンジ色の線です。重ね合わせると値が1を超えることがありますが、ヒートマップでは1以上の値は1にクリップされます。

Normal 02

実際の値を確認

さて、heatmap-weightの値が1のときにポイントデータの地点の密度の値も1になるのでしょうか?実はheatmap-weight1heatmap-intensity1のとき、密度は0.3989422804014327になります。中途半端な値に見えますが、これは標準正規分布の山の頂上の値です。

Normal 03

以下のようなヒートマップレイヤーを作って挙動を確かめてみました。値が0.3989422804014327に近づくと色が青色(rgb(0,0,255))になります。少しでも超えると(0.3989422804014327)緑色(rgb(0,255,0))になります。そこから0.9999999999999999に近づくにつれ赤色(rgb(255,0,0))になり、1で白色(rgb(255,255,255))となります。

map.addLayer({
  id: "layer",
  type: "heatmap",
  source: "source",
  paint: {
    "heatmap-weight": 1,
    "heatmap-intensity": 1,
    "heatmap-color": [
      "interpolate",
      ["linear"],
      ["heatmap-density"],
      0.39,
      "rgba(0,0,0,0.1)",
      0.3989422804014327,
      "rgb(0,0,255)",
      0.3989422804014328,
      "rgb(0,255,0)",
      0.9999999999999999,
      "rgb(255,0,0)",
      1,
      "rgb(255,255,255)"
    ]
  }
});

具体的に挙動を見てみましょう。

少し見にくいですが、東京駅の東西に1個ずつ、神田駅に1個の合計3個のポイントが表示されてます。各々離れているので0.3989422804014327近辺の青色で表示されています。
Heatmap 01

次に少しズームアウトします。すると東京駅東西の2個のポイントが接近し、緑色になります。2個の正規分布が合わさっている様子がわかりますね。
Heatmap 02

更にズームアウトすると東京駅は赤色に近づいていきます。さらに、神田駅のポイントも緑色になっているので東京駅の2個のポイントの影響を受けていることがわかります。
Heatmap 03

もっとズームアウトすると3個のポイントが重なり合い、中心が白色になります。3個の正規分布が重ね合わされて1になることがわかります。0.3989422804014327 * 3 = 1.1968268412042981なので、1にクリップされているということですね。
Heatmap 04

以下にサンプルを置いているので、ぜひズームアウトして動きを確認してください。

heatmap-intensityheatmap-radiusを変更すると?

heatmap-intensityは密度の値に直接乗算されます。つまりheatmap-intensity2に変更すると、密度の値も2倍になります。以下はheatmap-intensity2にしたときの結果です。中心の値が0.3989422804014327より大きくなっているのがわかります。
intensity

heatmap-radiusは正規分布の裾の長さを調整します。数学的には正規分布は無限に広がりますが、そうするとある地点の値はすべてのポイントからの正規分布の値を合算する必要があり大変です。そこでデフォルトでは30ピクセルまで計算します。以下はheatmap-radius60にしたときの結果です。青色の領域が少し広がっているので影響範囲が広がっているのがわかります。
radius

まとめ

heatmap-weight1heatmap-intensity1のとき値はだいたい0.4、つまりポイントが3個重なると1を超えるということを覚えておくと、各種パラメータを変更するときに検討しやすいのではないかと思います。

おまけ

ヒートマップレイヤーは正規分布の重ね合わせをシェーダーで処理しています。以下のコードが該当箇所ですのでご参照ください。0.3989422804014327というマジックナンバーもここで定義されています。

https://github.com/mapbox/mapbox-gl-js/blob/v2.15.0/src/shaders/heatmap.vertex.glsl
https://github.com/mapbox/mapbox-gl-js/blob/v2.15.0/src/shaders/heatmap.fragment.glsl

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

Discussion