leaflet + leaflet-kmlで表示されるpopupに緯度経度座標を表示させる

[Leaflet](https://leafletjs.com/)を使った地図アプリを作る上で試行錯誤したことをメモとして書いています。その機能あるよなど、なにかあればぜひコメントください。

## 概要
### やりたいこと
- leafletで表示中の地図に、KMLファイルの内容を表示したい。
- KMLファイルで表示した各要素をカスタマイズしたい。まずはポップアップの内容を編集したい。

### 結論
- `leaflet-kml`の中に使われている関数を一部コピーして、カスタマイズ、`leaflet-kml`を上書きする。


## 使用するもの
- [Leaflet](https://leafletjs.com/) 
- leafletjsのKML表示プラグイン[leaflet-kml](https://github.com/windycom/leaflet-kml)
- KMLの作成には[Google マイマップ](https://www.google.com/maps/)、[Google Earth]()を利用しています。

## 0,KMLファイルを作成する
`スタート`と`ゴール`の2つのマーカーを立てたものを作りました。

|Google Mymap|Google MymapからKMLファイルを書き出し|
|--|--|
|![スクリーンショット 2023-10-28 9.26.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73246/e91f6ec4-eaaa-ac30-f60a-ccc7b170d63b.png)|![スクリーンショット 2023-10-28 9.26.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73246/a4cbbb2d-43e5-7cad-0bdd-5c5adfe26ec4.png)|

sample.kmlが完成。

## 1,KMLを表示する
単純にKMLファイルを読み込んで表示する

```html:index.html
<!DOCTYPE html>
<html>
<head>
    <title>Leaflet KML Example</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Leaflet CSS -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />

    <!-- Leaflet JavaScript -->
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>

    <!-- Leaflet-KML Plugin -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-plugins/3.4.0/layer/vector/KML.js"></script>
</head>
<body>
    <div id="map" style="height: 400px;"></div>

    <script>
        // Create a Leaflet map
        var map = L.map('map').setView([0, 0], 2); // Set the initial map center and zoom level.

        // Add a base layer (e.g., OpenStreetMap)
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
        }).addTo(map);

        // Load and display a KML file on the map
        var kmlURL = 'sample.kml';
        var kmlLayer = new L.KML(kmlURL, {
            async: true,
        }).addTo(map);
    </script>
</body>
</html>
```

表示したもの。

![スクリーンショット 2023-10-28 9.27.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73246/b00b182f-5b3b-93d7-479b-8aeb686b4020.png)

## 2,ポップアップをカスタマイズ
マーカーをクリックした際に表示するポップアップに緯度経度座標を表示させます。
|as-is|to-be|
|--|--|
|![スクリーンショット 2023-10-28 9.27.27のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73246/022cc55d-4ed8-7f57-8407-11a00adc9367.png)|![スクリーンショット 2023-10-28 9.27.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73246/b407ab4e-cd82-7554-0925-1e516bfcbf38.png)|

#### ポップアップを表示している関数(parsePlacemark)をコピー

Leaflet-KML Pluginのファイル、KML.jsの中で`bindPopup`を検索。出てきた箇所を含む関数`parsePlacemark`をごっそりコピー、自分で作った`index.html`の`Leaflet-KML Plugin`を読み込んだ直後に貼り付ける。

貼り付けの際、parsePlacemark:functionの部分をL.KML.parsePlacemark=functionに書き換える。
```js: index.htmlの一部分を抜粋
// ↓こうなっていたものを
parsePlacemark:function (place, xml, style, options) { ...

// ↓ こうする
L.KML.parsePlacemark = function (place, xml, style, options) { ...
```

#### layer.bindPopupをカスタマイズ

layer.bindPopupの周辺にてGPS座標を取得、また、それらを表示するようにする。

```js:index.htmlの一部を抜粋
var pointLatLang = layer.getLatLng(); // 追記： レイヤーから緯度経度座標を取得
var pointlatLngStr = "GPS: " + pointLatLang.lat + ", " + pointLatLang.lng; // 追記: 表示用の文字列にする

if (name) {
  layer.on('add', function () {
     layer.bindPopup('<h2>' + name + '</h2>' + descr + "<br>" + pointlatLngStr);// 編集：　表示用の文字列を文字列をポップアップに追記する
     });
}
```

## 最終形

```html:index.html
<!DOCTYPE html>
<html>
<head>
    <title>Leaflet KML Example</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Leaflet CSS -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />

    <!-- Leaflet JavaScript -->
    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>

    <!-- Leaflet-KML Plugin -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-plugins/3.4.0/layer/vector/KML.js"></script>
    <script>
        L.KML.parsePlacemark = function (place, xml, style, options) {
		var h, i, j, k, el, il, opts = options || {};

		el = place.getElementsByTagName('styleUrl');
		for (i = 0; i < el.length; i++) {
			var url = el[i].childNodes[0].nodeValue;
			for (var a in style[url]) {
				opts[a] = style[url][a];
			}
		}
		
		il = place.getElementsByTagName('Style')[0];
		if (il) {
			var inlineStyle = this.parseStyle(place);
			if (inlineStyle) {
				for (k in inlineStyle) {
					opts[k] = inlineStyle[k];
				}
			}
		}

		var multi = ['MultiGeometry', 'MultiTrack', 'gx:MultiTrack'];
		for (h in multi) {
			el = place.getElementsByTagName(multi[h]);
			for (i = 0; i < el.length; i++) {
				return this.parsePlacemark(el[i], xml, style, opts);
			}
		}
		
		var layers = [];

		var parse = ['LineString', 'Polygon', 'Point', 'Track', 'gx:Track'];
		for (j in parse) {
			var tag = parse[j];
			el = place.getElementsByTagName(tag);
			for (i = 0; i < el.length; i++) {
				var l = this['parse' + tag.replace(/gx:/, '')](el[i], xml, opts);
				if (l) { layers.push(l); }
			}
		}

		if (!layers.length) {
			return;
		}
		var layer = layers[0];
		if (layers.length > 1) {
			layer = new L.FeatureGroup(layers);
		}

		var name, descr = '';
		el = place.getElementsByTagName('name');
		if (el.length && el[0].childNodes.length) {
			name = el[0].childNodes[0].nodeValue;
		}
		el = place.getElementsByTagName('description');
		for (i = 0; i < el.length; i++) {
			for (j = 0; j < el[i].childNodes.length; j++) {
				descr = descr + el[i].childNodes[j].nodeValue;
			}
		}

        var pointLatLang = layer.getLatLng();
        var pointlatLngStr = "GPS: " + pointLatLang.lat + ", " + pointLatLang.lng;

		if (name) {
			layer.on('add', function () {
				layer.bindPopup('<h2>' + name + '</h2>' + descr + "<br>" + pointlatLngStr);
			});
		}

		return layer;
	}
    </script>
</head>
<body>
    <div id="map" style="height: 400px;"></div>
    <script>
        // Create a Leaflet map
        var map = L.map('map').setView([0, 0], 2); // Set the initial map center and zoom level.

        // Add a base layer (e.g., OpenStreetMap)
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
        }).addTo(map);

        // Load and display a KML file on the map
        var kmlURL = 'sample.kml'; // Replace with the path to your KML file
        var kmlLayer = new L.KML(kmlURL, {
            async: true,
        }).addTo(map);
    </script>
</body>
</html>
```

スクリーンショット
![スクリーンショット 2023-10-28 10.18.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73246/39380b01-0984-2000-59b1-c58d998ac6cd.png)