Vue で地図を表示する無料で最短の道

# 概要

Vue で地図を表示するアプリを作ってみます。
地図ライブラリは leaflet を使います。

# 前提

## node と npm

[　ここからダウンロード　](https://nodejs.org/ja/)

## vue-cli
vue-cliのインストール

```console
$ npm i -g @vue/cli
$ vue -V
3.4.1
```

# 最短の道

## プロジェクトの作成

とりあえず vf という名前でプロジェクトを作成します。選択肢が出てきますが、デフォルトですすめます。

```console
$ vue create vf

Vue CLI v3.4.0
? Please pick a preset: 
❯ default (babel, eslint) 
  Manually select features 
```

## leaflet をインストール

vf という名前のディレクトリができているので、中に入って、leaflet をインストールします。

```console
$ cd vf
$ npm i leaflet --save
```

## App.vue を変更

App.vue を変更します。

```App.vue
<template>
	<div id=app>
	</div>
</template>

<script>
import	'leaflet/dist/leaflet.css'
import	L from 'leaflet'

export default {
	mounted() {
		L.map( 'app', { center: L.latLng( 35.6825, 139.752778 ), zoom: 15 } ).addLayer(
			L.tileLayer( 'http://{s}.tile.osm.org/{z}/{x}/{y}.png' )
		)
	}
}
</script>

<style>
html, body, #app { height: 100% }
body { margin: 0 }
</style>
```

## 起動と接続

vf ディレクトリでサービスを開始して、ブラウザから localhost:8080 に繋ぎます。

```console
$ npm run serve
```

![スクリーンショット 2019-03-04 15.03.26.png](https://qiita-image-store.s3.amazonaws.com/0/73493/52999b6c-32f2-fb2c-20f3-5bbd443d3967.png)

## 勘所

```js
import	'leaflet/dist/leaflet.css'
```
がないと、地図の表示が崩れます。

```css
html, body, #app { height: 100% }
```
で地図を囲む全ての要素の height を 100% にしないと地図が表示されません。

```css
body { margin: 0 }
```
がないと、地図の左上にちょっと余白ができて、右下が切れてしまいます。

```js
L.map( 'app', { center: L.latLng( 35.6825, 139.752778 ), zoom: 15 } )
```
で app という id の div に地図をつけています。
center と zoom の両方を指定しないと、地図が表示されません。

# マーカー

クリックされた場所にマーカーをつけてみます。

## デフォルトのマーカー

デフォルトのマーカーを使うにはちょっと面倒なんですがアイコンのリソースの位置を指定してやることが必要です。

```vue
<template>
	<div id=app>
	</div>
</template>

<script>

import	'leaflet/dist/leaflet.css'
import	L from 'leaflet'

delete	L.Icon.Default.prototype._getIconUrl

L.Icon.Default.mergeOptions(
	{	iconUrl			: require( 'leaflet/dist/images/marker-icon.png' )
	,	iconRetinaUrl	: require( 'leaflet/dist/images/marker-icon-2x.png' )
	,	shadowUrl		: require( 'leaflet/dist/images/marker-shadow.png' )
	}
)

export default {
	mounted() {
		const map = L.map( 'app', { center: L.latLng( 35.6825, 139.752778 ), zoom: 15 } ).addLayer(
			L.tileLayer( 'http://{s}.tile.osm.org/{z}/{x}/{y}.png' )
		).on(
			'click'
		,	p => map.addLayer( L.marker( p.latlng ) )
		)
	}
}
</script>

<style>
html, body, #app { height: 100% }
body { margin: 0 }
</style>
```

北の丸公園においてみました。
![スクリーンショット 2019-03-04 15.53.54.png](https://qiita-image-store.s3.amazonaws.com/0/73493/cdc3e0fd-c855-d165-5d6e-350d8d2231ed.png)

## divIcon を使ったマーカー

divIcon を使うと、マーカーのアイコンとして div を使えます。div なんでやりたい放題できます。

```App.vue
<template>
	<div id=app>
	</div>
</template>

<script>

import	'leaflet/dist/leaflet.css'
import	L from 'leaflet'

export default {
	mounted() {
		const map = L.map( 'app', { center: L.latLng( 35.6825, 139.752778 ), zoom: 15 } ).addLayer(
			L.tileLayer( 'http://{s}.tile.osm.org/{z}/{x}/{y}.png' )
		).on(
			'click'
		,	p => map.addLayer(
				L.marker(
					p.latlng
				,	{ icon: L.divIcon( { className: 'red marker', iconSize: [ 16, 16 ] } ) }
				)
			)
		)
	}
}
</script>


<style>

html, body, #app { height: 100% }
body { margin: 0 }

.marker {
	text-align		: center
;	color			: white
;	font-size		: 16
;	border-radius	: 8px
;	box-shadow		: 8px 8px 8px rgba( 0, 0, 0, 0.4 )
}
.red {
	background		: red
}

</style>
```

![スクリーンショット 2019-03-04 15.55.27.png](https://qiita-image-store.s3.amazonaws.com/0/73493/aa7405af-5192-55ae-1215-d356035130e7.png)

# 応用編

## マーカークラスターグループ

leaflet.markercluster をインストールすると、地図にたくさんマーカーを置くとき、近接するマーカーをまとめて表示するような機能を実現できます。

```sh
npm i leaflet.markercluster --save
```

5個のマーカーのクラスターグループを作ってみます。

```App.vue
<template>
	<div id=app>
	</div>
</template>

<script>

import	'leaflet/dist/leaflet.css'
import	L from 'leaflet'
require( 'leaflet.markercluster' )
import	'leaflet.markercluster/dist/MarkerCluster.css'
import	'leaflet.markercluster/dist/MarkerCluster.Default.css'

export default {
	mounted() {
		const	w = { icon: L.divIcon( { className: 'red marker', iconSize: [ 16, 16 ] } ) }
		L.map( 'app', { center: L.latLng( 35.6825, 139.752778 ), zoom: 15 } ).addLayer(
			L.tileLayer( 'http://{s}.tile.osm.org/{z}/{x}/{y}.png' )
		).addLayer(
			L.markerClusterGroup().addLayer(
				L.marker( [ 35.691, 139.752 ], w )
			).addLayer(
				L.marker( [ 35.692, 139.752 ], w )
			).addLayer(
				L.marker( [ 35.690, 139.752 ], w )
			).addLayer(
				L.marker( [ 35.691, 139.753 ], w )
			).addLayer(
				L.marker( [ 35.691, 139.751 ], w )
			)
		)
	}
}
</script>


<style>

html, body, #app { height: 100% }
body { margin: 0 }

.marker {
	text-align		: center
;	color			: white
;	font-size		: 16
;	border-radius	: 8px
;	box-shadow		: 8px 8px 8px rgba( 0, 0, 0, 0.4 )
}
.red {
	background		: red
}

</style>
```

5個まとめて表示されています。

![スクリーンショット 2019-03-04 16.37.04.png](https://qiita-image-store.s3.amazonaws.com/0/73493/4143cd55-e7a6-80f1-c9a0-d91e99b3cc4b.png)

クリックするとズームして別々に表示されます。

![スクリーンショット 2019-03-04 16.37.18.png](https://qiita-image-store.s3.amazonaws.com/0/73493/1dae765a-a721-6eed-91a5-73482d8a87ef.png)

# 最後に

leaflet は機能豊富なライブラリなので、いくらでもネタはあるんですが、キリがないのでこのへんにしておきます。最後までおつきあいいただいてありがとうございました。

# 余談

2020/5/18

同じようなテーマの[Electron で地図を表示する無料で最短の道](https://qiita.com/Satachito/items/7f2a063be7f945dd426a)を書きました。
Leaflet 1.6.0 に対応しているので、この記事とちょっと Leaflet の書き方が違います。参考にしていただければ幸いです。