【Leaflet.js】住所から緯度経度を取得しその地点にピンを立てる

Leaflet.js はシンプルで拡張性の高いオープンソースの地図描画ライブラリです。
今回は Leaflet 地図に住所検索機能を追加してみたいと思います。

# 作りたいもの

- 入力された住所から緯度経度を求める
- 目的地点に地図の中心を移動
- その地点にピンを立てて情報を表示
- こんな感じのイメージ ↓

![68747470733a2f2f692e696d6775722e636f6d2f376f58534a4d722e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274026/3cf2c003-885e-2a84-61bf-60341dcb51bf.png)

# Leaflet 地図を準備

## ファイル構成

```terminal
.
├── index.html
└── main.js
```

### index.html

- leaflet.css と leaflet.js の読み込み順序に注意
- 地図を描画する div 要素に対して width 属性を指定しないと表示されないので注意

```html:index.html
<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>住所検索</title>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.7.1/dist/leaflet.min.css">
	<script src="https://cdn.jsdelivr.net/npm/leaflet@1.7.1/dist/leaflet.min.js"></script>
	<style>
		#mymap {
			height: 500px;
			width: 500px;
		}
	</style>
</head>

<body>
	<input type="text" id="address" />
	<button id="search">検索</button>
	<div id="mymap"></div>
	<script type="module" src="./main.js"></script>
</body>

</html>
```

### main.js

```javascript:main.js
const map = L.map("mymap", {
	center: [35.021028, 135.755583],
	zoom: 15
});
const baseLayer = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
	attribution: "&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors'"
});
baseLayer.addTo(map);
```

# 住所の入力を受け付け緯度経度を求める

## geolonia/normalize-japanese-addresses の紹介

住所から緯度経度を求めるのにはオープンソースの住所正規化ライブラリ[geolonia/normalize-japanese-addresses](https://github.com/geolonia/normalize-japanese-addresses)を用います。  
このライブラリは引数に与えた住所をパース・正規化した後、[Geolonia 住所データ](https://geolonia.github.io/japanese-addresses/)に基づいてその地域の代表点の緯度経度を返します。  
バージョン 2.3.0 から ES Modules 形式に対応されたようで、import 句を使って読み込むことができるようになりました。使用例を下に示します。

```javascript:使用例
import { normalize } from 'https://cdn.skypack.dev/@geolonia/normalize-japanese-addresses';

normalize("滋賀県大津市御陵町3-1").then(result => {
	console.log(result);
});
```

```terminal:出力
{
	pref: "滋賀県",
	city: "大津市",
	town: "御陵町",
	addr: "3-1",
	lat: 35.018665,
	lng: 135.855868,
	level: 3
}
```

# Leaflet 地図にピンを立てる方法

`bindPopup`の引数には HTML タグを入れたりすることもできるのですが、詳細は割愛します。[公式ドキュメント](https://leafletjs.com/reference-1.7.1.html#marker)をご覧ください。

```javascript
const latitude = 35.018665; // 緯度
const longitude = 135.855868; // 経度
const marker = L.marker([latitude, longitude]); // markerオブジェクトを作成
marker.bindPopup("大津市役所"); // markerをクリックしたとき"大津市役所"と表示されるようにする
marker.addTo(map); // markerをLeaflet地図に追加
```

![Leaflet地図にピンを立ててみた](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274026/e1bb9ec8-2741-ec74-46f7-8d114cfc2226.png)

あと、ピンを立てても表示範囲になければ変化がわかりませんので、ピンを立てると同時に地図の表示範囲もその地点に移動するようにしましょう。

```javascript
map.flyTo([latitude, longitude]);
```

# コードを組み立てる

最初に準備したコードを次のように書き換えます。

## index.html

変更なし

## main.js

```javascript:main.js
import { normalize } from 'https://cdn.skypack.dev/@geolonia/normalize-japanese-addresses'

const map = L.map("mymap", {
	center: [35.021028, 135.755583],
	zoom: 15
});
const baseLayer = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
	attribution: "&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors'"
});
baseLayer.addTo(map);

// 以下を追加
document.getElementById("search").addEventListener("click", () => {
	const address = document.getElementById("address").value;
	normalize(address).then(result => {
		map.flyTo([result.lat, result.lng]); // 住所の地点に移動
		const marker = L.marker([result.lat, result.lng]); // ピンを作成
		marker.bindPopup(address); // ピンをクリックすると住所が表示されるようにする
		marker.addTo(map); // 地図にピンを立てる
	});
});
```

## 完成！

これでとりあえず動くものができました！

![demo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274026/8436287e-b551-4ce1-c922-c18113064d24.gif)

# 宣伝

本記事で作成したプログラムにエラー処理の追加やインターフェースの改良を施したものを Leaflet プラグインとして公開しました！

[Cocon/Leaflet.CommunityGeoCoder](https://github.com/Cocon/Leaflet.CommunityGeoCoder)

実際に触って動かせるデモページも作ってみました。ぜひご活用いただき、バグや機能提案などあればご気軽にお願いします。

[Demo | Cocon/Leaflet.CommunityGeoCoder](https://cocon.github.io/Leaflet.CommunityGeoCoder/demo/)