JSライブラリ

Webサイトに地図を表示してカスタマイズする(Leaflet, OpenStreetMap)

復習も兼ねてLeafletの使い方を記事にします。

JS弱弱な自分にとっては、使い方が非常に難しいLeaflet…
ChatGPT先生のお力を借りつつ、サンプルを残しておきます。

目次

実装例

※ レスポンシブを考慮してCSSは書いていないのでご了承を。

See the Pen OSM_custom by Masayuki / ウェブ制作者 (@pazfinder) on CodePen.

Leafletとは

モバイル対応の地図をWebページに簡単に追加できるようにするための、オープンソースで作成されたJavaScriptのライブラリです。

ライセンスはこちら

Leafletは、あくまで【地図をWeb上に表示してカスタマイズできる機能】であって、Leafletだけで地図を表示することは出来ません。
そこで必要になってくるのが地図の画像データです。

OpenStreetMapとは

世界中の地図データをオープンデータとして無料で利用できる地図サービスです。
非営利の団体であるOpenStreetMapFoundationが運営されていて、誰でも自由にデータを利用できます。(ありがたいですね!)

このLeafletとOpenStreetMapを組み合わせることで、Web上に地図を表示できる。というわけです。

ライセンスはこちら

使い方

HTML

地図を使いたいページの<head>内に、公式ページに用意されているCSSとJSを読み込みます。

<body>内の地図を表示させたい場所に、ID付きのdivを書いて準備完了です。

<head>

〜省略〜

<!-- CSSを先に書く -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
     integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
     crossorigin=""/>

<!-- 続けてJSを書く -->
 <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
     integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM="
     crossorigin=""></script>
</head>

<body>

〜省略〜

<!-- 表示させたい場所にID付きのdiv -->
<div id="map"></div>

</body>

JavaScript

(function () {

  const name = '松江城';
  const description = 'ここは島根県にある松江城です。<br>城の最上階から松江市を一望できます!';
  const lat = 35.47523182098407; // 緯度
  const lng = 133.05066804411982; // 経度

  // 地図表示
  const map = L.map('ID', { // divに書いたIDを記述
    center: [lat, lng], // 初期表示座標(緯度と経度を)
    zoom: 17, // 初期拡大率
  });

  // OpenStreetMapから地図画像を読み込む & ライセンスの表示
  L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }).addTo(map); // .addTo(map)でマップに追加

  // マーカー表示
  L.marker([lat, lng], { alt: name }).addTo(map) // マーカーの位置とalt属性
    .bindPopup(description) // ポップアップ文章
    .openPopup(); // ポップアップを表示しておく

}());

コード内の【 L.〇〇(L.mapや L.tileLayerなど)】 がLeaflet用のプログラムとなっていて、
9行目の L.mapは、地図を表示するための処理、
15行目の L.tileLayerは、地図の画像データを表示する処理(今回はOpenStreetMap)、
20行目の L.markerは、地図上にピンを表示するための処理です。
コード内のコメントも参考にしてください。

これでWeb上に地図を表示することが出来ます。

実装例の説明(JavaScript)

ここからは実装例にある、カスタマイズしたマップの説明です。

その1(地図情報と表示場所)

// 地図情報
const place = {
    tourism: [{
      name: '松江城',
      description: 'ここは島根県にある松江城です。<br>城の最上階から松江市を一望できます!(画像はテスト用)',
      lat: 35.47523182098407,
      lng: 133.05066804411982,
    }],
    guid: [{
      name: 'ぶらっと松江観光案内所',
      description: '松江の観光案内所の1つ。<br>観光パンフレットやチラシ、お土産物の販売、手荷物の一時預かりもOK。(画像はテスト用)',
      lat: 35.47443834390742,
      lng: 133.0522209367023,
    }]
  };

 // 地図の表示場所
  const map = L.map('map_2', {
    center: [place.tourism[0].lat, place.tourism[0].lng], // 松江城を中心に表示
    zoom: 17,
  });

2行目にはポップアップしたい地図情報を、
18行目には地図の表示場所を書いています。

その2(地図画像の取得)

// 地図画像の取得
const createBaseMaps = function () {

    // OpenStreetMapから地図画像(タイルレイヤー)を読み込む & ライセンスの表示
    const osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
    }).addTo(map)

    const gsiStdLayer = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {
      attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html" target="_blank">地理院タイル</a>',
    })

    const gsiOrtLayer = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg', {
      attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html" target="_blank">地理院タイル</a>, Images on 世界衛星モザイク画像 obtained from site https://lpdaac.usgs.gov/data_access maintained by the NASA Land Processes Distributed Active Archive Center (LP DAAC), USGS/Earth Resources Observation and Science (EROS) Center, Sioux Falls, South Dakota, (Year). Source of image data product.',
    })

    const gsiOrt1961_1969 = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/ort_old10/{z}/{x}/{y}.png', {
      attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html" target="_blank">地理院タイル</a>',
    })

    const baseMaps = {
      'OpenStreetMap': osm,
      '国土地理院(標準)': gsiStdLayer,
      '国土地理院(写真)': gsiOrtLayer,
      '国土地理院(1961年~1969年)': gsiOrt1961_1969,
    }

    return baseMaps;
  };

L.tileLayerで地図画像を読み込み、
関数createBaseMapsの値として、オブジェクトのbaseMapsを返します。
この関数は、後述する L.geoJSON関数に記述します。

その3(地図情報の準備)

// 地図情報をleaflet用に取得 + ポップアップ用のHTML作成
const createFeatures = function () {

// leaflet用の情報を格納
    const someFeatures = [];

    Object.keys(place).forEach(function (type) {
      for (let i = 0; i < place[type].length; i++) {
        someFeatures.push({
          "type": "Feature",
          "properties": {
            "type": type,
            "name": place[type][i].name,
            "popup":
              '<dl class="definition-wrap">' +
              '<dt class="term">' + place[type][i].name + '</dt>' +
              '<dd class="description">' + place[type][i].description + '</dd>' +
              '</dl>' +
              '<div class="img__wrap"><img src="https://picsum.photos/200/100" alt="テスト画像です" width="200" height="100"></div>',
          },
          "geometry": {
            "type": "Point",
            "coordinates": [place[type][i].lng, place[type][i].lat] // 経度 → 緯度の順で書かないと描画されない
          }
        });
      }
    });

    return someFeatures;
  };

その1に記述していた地図情報のplaceを参照して、Leaflet用の値にして返します。
(console.log(createFeatures());と記述すると、どんな要素が返ってきてるのか分かります。)

7行,8行目のObject.keys(place)で、placeのキーを配列で取得し、
forEach(function (type)で、取得した配列の要素名(ここではtype)を1つづつ参照し、
for (let i = 0; i < place[type].length; i++)で、type内の要素数だけforループで繰り返し、オブジェクトを作成しています。
なので、type内(tourismやguid内)の要素が増えても機能するように作ってあります。

9行目のsomeFeatures.pushで、5行目の空の配列に格納しています。

その4(ポップアップ機能)

// 各ポップアップを表示する関数
  const onEachFeatureFunc = function (feature, layer) {
    if (feature.properties.popup) {

      // ポップアップ表示・html
      layer.bindPopup(feature.properties.popup);

      // // クリックで表示
      // layer.on('click', function (e) {
      //   this.openPopup();
      // });

      // ホバーで表示
      layer.on('mouseover', function (e) {
        this.openPopup();
      });

      // ホバー外しで非表示
      layer.on('mouseout', function (e) {
        this.closePopup();
      });
    }
  };

その3で格納した情報を元に、ポップアップを表示する関数です。
後述する L.geoJSONに記述することで、地図情報をポップアップさせることが出来ます。

今回はマウスホバー時にポップアップするようにしていますが、
マウスクリック時にポップアップさせることも可能です。(上記コードのコメントアウトを切り替えてください。)

その5(地図マーカー作成)

// マーカーをグループ化するための変数
const markerGroup_1 = L.layerGroup();
const markerGroup_2 = L.layerGroup();

// 地図に指すピン(マーカー)を作成する関数
  const pointToLayerFunc = function (feature, latlng) {

    const myIcon_1 = L.icon({ // オリジナルアイコン
      iconUrl: './assets/img/map_pin_1.png', // アイコン画像パス
      iconSize: [40, 40], // アイコン画像サイズ
      iconAnchor: [20, 40], // アイコン画像位置
      popupAnchor: [-115, -40] // ポップアップの表示位置
    });

    const myIcon_2 = L.icon({
      iconUrl: './assets/img/map_pin_2.png',
      iconSize: [70, 70],
      iconAnchor: [35, 50],
      popupAnchor: [-115, -40]
    });

    if (feature.properties.type === 'tourism') { // 観光名所のマーカー

      const marker_1 = L.marker(latlng, { icon: myIcon_1 });
      // マーカーをレイヤーとマップに追加
      markerGroup_1.addLayer(marker_1).addTo(map);

      return marker_1

    } else if (feature.properties.type === 'guid') { // 案内所のマーカー

      const marker_2 = L.marker(latlng, { icon: myIcon_2 });

      markerGroup_2.addLayer(marker_2).addTo(map);

      return marker_2
    };
  };

その3で格納した情報を元に、地図に指すピン(マーカー)を作成する関数です。
後述する L.geoJSON関数に記述することで、地図に指すピンを表示させることが出来ます。

26行,34行目は、作成した各マーカーをLeafletの関数の L.layerGroupに、markerGroup_1とmarkerGroup_2のグループ分けをして追加しています。

その6(レイヤー切り替えUIの作成)

// マーカーをグループ化した変数の表示名
const overlayMaps = {
    '観光名所': markerGroup_1,
    '案内所': markerGroup_2
  };

  // レイヤーコントロールの作成(チェックボックスの作成)
  L.control.layers(createBaseMaps(), overlayMaps).addTo(map);

8行目の L.control.layersで、
その2の地図画像データ [ createBaseMaps() ]と、マーカーグループ [ overlayMaps ] を、
切り替え可能なUIとして地図に表示しています。

その7(マップに表示する情報の作成)

// マップに表示する情報の作成
  L.geoJSON(createFeatures(), {
    onEachFeature: onEachFeatureFunc,
    pointToLayer: pointToLayerFunc
  }).addTo(map); // マップに追加

L.geoJSON関数に、その3で用意したcreateFeatures()の情報を元に、
ポップアップ機能のonEachFeatureFuncと、
マーカー機能のpointToLayerFuncを表示しています。

その8(オリジナルのUI作成・表示)

/* 画面左下のUI -------------------------------------------------------------------------------- */
  const info = new L.Control({ position: 'bottomleft' }); // 画面左下にUIの作成
  const div = L.DomUtil.create('div', 'info'); // divをclass名infoで作る

  // divの中身HTML
  info.update = function () {
    div.innerHTML =
      '<h3 class="heading__tertiary">何らかの情報見出し</h3>' +
      '<div class="text">ほげほげ</div>' +
      '<div class="text">げほげほ</div>';
  };

  // divをマップに追加
  info.onAdd = function (map) {
    this.update();
    return div;
  };

  // 地図に表示
  info.addTo(map);

地図画面の左下に表示しているUIです。
コメントを見てもらえると大体わかると思います。

まとめ

Leafletはカスタマイズが豊富で、自分も把握しきれてはいない状態ですが、
今回のサンプルがお役に立てれば幸いです。

参考リンク

公式の実装例(チュートリアル)ページ

公式の説明書

参考にしたサイト様

シェアしてもいいかも…と、思ったら

記事 一覧へ

コメントを書いてみる

CAPTCHA