Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

Mapbox GL JS で大量のデータを可視化する

はじめに

駅メモ!チームでエンジニアをしている id:wgg00sh です.

駅メモ!では2021年11月に「未取得の駅を地図で確認できる機能」をリリースしました. 今回はこの機能を実現するにあたって発生した問題の一例と,その問題をどのように解決したかについて,使用した地図ライブラリである Mapbox GL JS の使い方と合わせて紹介していきます

地図上に全ての駅を表示する

駅メモ!には,9300を超える駅が使用されているので,まずはその全てを表示してみます

↓のような駅情報を持っていて,

export const stationList = [
  [1,'函館',140.xxxxxx,41.yyyyyy],
  [2,'五稜郭',140.xxxxxx,41.yyyyyy],
  ...
]

このデータを使って地図上に全ての駅を表示してみます

const stationGeoJSON = convertGeoJSON (stationList) // 座標データを GeoJSON に変換する処理

map.addSource('station', {
  type: 'geojson',
  data: stationGeoJSON,
})

map.addLayer({
  id: 'station',
  type: 'circle',
  source: 'station',
  paint: {
    'circle-color': '#FF0000',
    'circle-radius': 6,
  }
})

// クリックイベントの追加
map.on('click', 'station', (e) => {
  new mapboxgl.Popup({anchor: 'bottom',})
    .setLngLat(e.lngLat)
    .setHTML(e.features[0].properties.name)
    .addTo(map);
})
遠くから見た場合 近くで見た場合 クリック

問題点

クリックして駅の詳しい情報などを見ることを考えると,遠くから見た場合は駅数が多すぎてまともに操作できないです.

また,これだけの量を描画するのは,パフォーマンスの観点から見てもよく無さそうです

解決法

そこで,複数の駅が密集している場合は,それらを纏めて別の表示にするクラスタ化を行います

Mapbox GL JS では, addSource のオプションに cluster: true をつけることで,クラスタ化を行ってくれます

クラスタ化に関するオプションとして, clusterRadius clusterMinPoints clusterMaxZoom がありますが,これらは実際に使用するデータによって良い感じに見えるパラメータを模索するのが良いと思います

// 駅データの投入
map.addSource('station', {
  type: 'geojson',
  data: stationGeoJSON,
  cluster: true,  // クラスタ化を行うオプション
  clusterRadius: 120, // クラスタ化を行う半径
  clusterMinPoints: 30, // クラスタ化に必要な最小の要素数
})

// クラスタ化されていない座標データの描画
map.addLayer({
  id: 'station',
  type: 'circle',
  source: 'station',
  filter: ['!', ['has', 'point_count']], 
  paint: {
    'circle-color': '#FF0000',
    'circle-radius': 6,
  }
})

// クラスタ化されたデータの描画 (円部分)
map.addLayer({
  id: 'station_cluster',
  type: 'circle',
  source: 'station',
  filter: ['has', 'point_count'], // クラスタ化されている要素かのチェック方法
  paint: {
    'circle-color': [
      'step',
      ['get', 'point_count'],  // クラスタに含まれる要素数に応じて色を変更
      '#FF4000', 30,
      '#FF8000', 50,
      '#FFB000', 100,
      '#FFFF00', 300,
      '#FFFFFF',
    ],
    'circle-radius': [
      'step',
      ['get', 'point_count'], // クラスタに含まれる要素数に応じて円の大きさを変更
      24, 30,
      36, 50,
      48, 100,
      60, 300,
      72,
    ],
  }
})

// クラスタ化されたデータの描画 (テキスト部分)
map.addLayer({
  id: 'station_cluster_label',
  type: 'symbol',
  source: 'station',
  filter: ['has', 'point_count'],
  layout: {
    'text-field': '{point_count}',
    'text-size': [
      'step',
      ['get', 'point_count'],
      14, 30,
      18, 50,
      24, 100,
      30, 300,
      36,
    ]
  }
})

このようにして,地図上に描画した多数のデータから,目的の場所を探しやすくする機能を実現することができました

参考

Sources | Style Specification | Mapbox GL JS | Mapbox

Create and style clusters | Mapbox GL JS | Mapbox