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はカスタマイズが豊富で、自分も把握しきれてはいない状態ですが、
今回のサンプルがお役に立てれば幸いです。
参考リンク
公式の実装例(チュートリアル)ページ
公式の説明書
参考にしたサイト様