[GIS]MapLibre GL JS を使用したWebマップ上での距離測定について(備忘録)

2025/02/24に公開

1.はじめに

GISとは地理情報システム(Geographic Information System)の略称です。
・地球上に存在する地形物や事象をコンピュータ上の地図に可視化し、空間データの管理・検索・分析等を可能にします。

2.サンプルプログラム

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="style.css" rel="stylesheet" />
    <!-- MapLibre GL JS -->
    <link rel='stylesheet' href='https://unpkg.com/maplibre-gl@5.1.0/dist/maplibre-gl.css' />
    <script src='https://unpkg.com/maplibre-gl@5.1.0/dist/maplibre-gl.js'></script>
</head>
<body>
    <!-- 地図を表示する要素 -->
    <div id="map"></div>
    <!-- 距離を表示する要素 -->
    <div id="distance" class="distance-container"></div>
    <!-- turfライブラリをインポート -->
    <script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
    <script src="main.js"></script>
</body>
style.css
body { 
  margin: 0;
  padding: 0;
}

html,
body,
#map {
  height: 100%;
}

.distance-container {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
}

.distance-container > * {
  background-color: rgba(0, 0, 0, 0.5);
  color: #fff;
  font-size: 11px;
  line-height: 18px;
  display: block;
  margin: 0;
  padding: 5px 10px;
  border-radius: 3px;
}
main.js
// 地図を初期化
const map = new maplibregl.Map({
  container: 'map',  // 地図を表示するHTMLの要素idを指定
  style: 'https://api.maptiler.com/maps/jp-mierune-streets/style.json?key=xxxxx',  // 地図のスタイル
  center: [139.767066, 35.6813],  // 中心座標(経度, 緯度)
  zoom: 15  // ズームレベル
});

// 距離を表示するHTML要素を取得
const distanceContainer = document.getElementById('distance');

// 測定機能を保持するためのGeoJSONオブジェクト
const geojson = {
  'type': 'FeatureCollection',
  'features': []
};

// 座標間の線を描画するために使用
const linestring = {
  'type': 'Feature',
  'geometry': {
      'type': 'LineString',
      'coordinates': []
  }
};

map.on('load', () => {
  map.addSource('geojson', {
      'type': 'geojson',
      'data': geojson
  });

  // 地図上でクリックした地点に描画される円(circle)のレイヤ―を定義
  map.addLayer({
      id: 'measure-points',
      type: 'circle',  // レイヤーの種類
      source: 'geojson',
      paint: {
          'circle-radius': 5,
          'circle-color': '#000'
      },
      filter: ['in', '$type', 'Point']
  });

  // 地図上で線を描画するためのレイヤー
  map.addLayer({
      id: 'measure-lines',
      type: 'line',
      source: 'geojson',
      layout: {
          'line-cap': 'round',
          'line-join': 'round'
      },
      paint: {
          'line-color': '#000',
          'line-width': 2.5
      },
      filter: ['in', '$type', 'LineString']
  });

  // クリックした地点のmeasure-pointsレイヤーに関するフィーチャーを取得
  map.on('click', (e) => {
      const features = map.queryRenderedFeatures(e.point, {
          layers: ['measure-points']
      });

      if (geojson.features.length > 1) geojson.features.pop();

      // 距離を表示するHTML要素をクリア
      distanceContainer.innerHTML = '';

      // クリックして描画された円を削除
      if (features.length) {
          const id = features[0].properties ? features[0].properties.id : null;
          if (id) {
              geojson.features = geojson.features.filter((point) => {
                  return point.properties.id !== id;
              });
          }
      } else {
          // クリックして描画された円をGeoJSONのfeaturesに追加
          const point = {
              'type': 'Feature',
              'geometry': {
                  'type': 'Point',
                  'coordinates': [e.lngLat.lng, e.lngLat.lat]
              },
              'properties': {
                  'id': String(new Date().getTime())
              }
          };

          geojson.features.push(point);
      }

      // featuresが2個以上(つまり円が2個以上)のとき、円の間に線を描画し、距離を計測
      if (geojson.features.length > 1) {
          linestring.geometry.coordinates = geojson.features.map(
              (point) => {
                  return point.geometry.coordinates;
              }
          );

          geojson.features.push(linestring);

          // 距離表示HTMLエレメントに総距離を表示
          const value = document.createElement('pre');
          value.textContent =
              `Total distance: ${
                  turf.length(linestring).toLocaleString()
              } km`;
          distanceContainer.appendChild(value);
      }

      map.getSource('geojson').setData(geojson);
  });
});

// マウス操作時の挙動
map.on('mousemove', (e) => {
  // 円が描画されているか検出
  const features = map.queryRenderedFeatures(e.point, {
      layers: ['measure-points']
  });
  map.getCanvas().style.cursor = features.length ?
      'pointer' :  // 地図上に描画された円にカーソルを重ねた際、カーソルをポインターに変更
      'crosshair';  // それ以外では、カーソルは十字
});

・表示画面

3.実行環境

・OS: Windows 11(バージョン 24H2)
・ブラウザ: Microsoft Edge(バージョン 133.0.3065.82)
・エディタ: Visual Studio Code(バージョン 1.97)
・MapLibre GL JS: バージョン 5.1.0

4.参考

GISとは?メリットや活用例をわかりやすく解説
用語解説
地図表示
Measure distances
Mapbox Newsletter WEEKLY TIPSの解説 -「距離を測定」
第9回「防災マップの作成④空間演算による距離計測と避難所到達圏の可視化」

Discussion