Leaflet + OpenStreetMap で地図情報を扱うサンプルコード

# Leaflet とは

Leafletは、地図データを扱うためのJavaScript ライブラリ。Google Maps PlatoformやYOLPのようなメソッドで、地図情報の操作ができる。

![leaflet_logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/e24aa364-ed35-0219-3933-a292684dbfc3.png)


Leaflet のウェブサイト
https://leafletjs.com/

Leafletのレポジトリ 
https://github.com/Leaflet/Leaflet

[Vladimir Agafonkin](https://agafonkin.com/)というエンジニアによって開発された。

著作権は、Vladimir および各コントリビュータに帰属しているが、[BSDライセンス](https://github.com/Leaflet/Leaflet/blob/master/LICENSE)を採用しているため、著作権の表示と免責条項さえ明記しておけば、再利用も再配布も自由となる。

## Leafletを使えば、すぐに地図が使えるか

No、Leaflet自体は単なるJSライブラリで、地図データを持っていない。

## Leaflet で地図を表示するためにはどうすれば良いか

Leallet とは別に、地図サービスからデータを取得する必要がある。

例えば [MapBox](https://www.mapbox.com/) という有名な地図情報サービスと組み合わせ使うことができる。

この場合、MapBoxにアカウントを作成し、APIキーをあらかじめ取得する必要がある。
利用頻度に応じて、[MapBoxに利用料を払う](https://www.mapbox.com/pricing/)必要がある。

面白い例として、[Leaflet にGoogle Maps のデータを組み合わせて表示することも可能なようだ](https://stackoverflow.com/questions/50672846/how-to-load-a-google-maps-baselayer-in-leaflet-after-june-2018/50675567)(ただしライセンス違反になる可能性が高い)。

この場合、地図の画像データはGoogle Mapsを使い、描画するためのライブラリにLeafletを利用する形となる。

いずれにせよ、利用する地図データの規約に合わせて、APIキーを取得したり、費用を払う必要がある。

# OpenStreetMap とは

OpenStreatMap財団が「Open Data Commons Open Database License (ODbL)」 の下にライセンスするオープンデータ。OpenStreetMapとその協力者をクレジットすれば、データを自由にコピー、配布、送信、利用することができる。料金はかからない。

ライセンスはCreative Commons.
https://www.openstreetmap.org/copyright/ja


## Leaflet+ OpenStreetMap 基本的な使い方

[公式サイトのチュートリアル](https://leafletjs.com/examples/quick-start/)が詳しい。

Laefletのライブラリ(CSS、JS)を読み込み、地図データをOpenStreetMapから呼び出して使う。

Leafletを使う場合、必ずCSSを先に呼び出し、その後にJSライブラリを読み込む必要がある。

```html

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
   integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
   crossorigin=""/>
 <!-- Make sure you put this AFTER Leaflet's CSS -->
 <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
   integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
   crossorigin=""></script>
```

ライブラリ読み込み後、htmlの　body内に、地図表示のためのDOMを設定する。

```html
 <div id="mapid"></div>
```

地図表示用のDOMサイズを、個別にCSSで指定する。

```html
#mapid { height: 500px; }
```

DOMの準備ができたら、地図オブジェクトを生成してOpenStreetMapの画像タイルを読み込む。

```javascript

var map = L.map('mapid', {
  center: [35.66572, 139.73100],
  zoom: 17,
});
var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
});
tileLayer.addTo(map);
```

# サンプルコード




## 地図上にマーカーを表示する

DOMに地図を描画した後、マーカーの情報をオブジェクトに追加する。


```javascript
// 六本木を中心に地図描画

var map = L.map('mapid', {
  center: [35.66572, 139.73100],
  zoom: 17,
}); 

// OpenStreetMap から地図画像を読み込む

var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
});
tileLayer.addTo(map);

// マーカー画像の場所を指定する

L.marker([35.66572, 139.73100]).addTo(map); 

```

<img width="675" alt="スクリーンショット 2020-05-03 20.00.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/fa59a49d-31a0-4446-8fb2-e7447d5afbad.png">

### 動作サンプル

[Leaflet+OSM Sample01 マーカーを表示](https://codepen.io/TakeshiNickOsanai/pen/jObaqdO)


## 複数のマーカーを表示する

```javascript
// 六本木を中心に地図描画

var map = L.map('mapid', {
  center: [35.66572, 139.73100],
  zoom: 17,
}); 

// OpenStreetMap から地図画像を読み込む

var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
});
tileLayer.addTo(map);

// マーカー画像の場所を指定する

L.marker([35.66572, 139.73100]).addTo(map); //1つ目のマーカー
L.marker([35.6662186, 139.7303961]).addTo(map); //２つ目のマーカー

```
<img width="711" alt="スクリーンショット 2020-05-03 20.02.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/1ee01461-4eb0-f2f2-3bff-ceecb3e534bd.png">


### 動作サンプル

[Leaflet+OSM Sample02 マーカーを複数表示](https://codepen.io/TakeshiNickOsanai/pen/VwvrKXa)


## geoJson を利用して複数のマーカーを表示する

[geoJson](https://ja.wikipedia.org/wiki/GeoJSON) は、地理情報を扱うためのJSONフォーマット。
Leafletに用意されている [「geoJson」](https://leafletjs.com/reference-1.6.0.html#geojson) メソッドを利用する

```javascript
// 六本木を中心に地図描画

var map = L.map('mapid', {
  center: [35.66572, 139.73100],
  zoom: 17,
}); 

// OpenStreetMap から地図画像を読み込む

var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
});
tileLayer.addTo(map);


var features = [];
var place = [{
  "name": "東京ミッドタウン",
  "lat": "35.66572",
  "long": "139.73100"
}, {
  "name": "サントリー美術館",
  "lat": "35.6662186",
  "long": "139.7303961"
}, ]
// GeoJSON形式で複数個のマーカーを設定する
for (var i = 0; i < place.length; i++) {
  features.push({ // 1つのマーカーの情報を格納する
    "type": "Feature",
    "properties": {
      "name": place[i].name
    },
    "geometry": {
      "type": "Point",
      "coordinates": [place[i].long, place[i].lat]
    }
  });
}
L.geoJson(features).addTo(map);
```

### 注意点

geoJson でマーカーを表示する際は、「経度」→「緯度」で位置情報を指定する必要がある。

```javascript
      "coordinates": [place[i].long, place[i].lat]
```

「緯度」→「経度」で指定すると、期待通りの描画にならないため、要注意。

### 動作サンプル

[Leaflet+OSM Sample03 geoJson でマーカーを複数表示](https://codepen.io/TakeshiNickOsanai/pen/eYpedrx?editors=0010)



## マーカーをクリックするとポップアップを表示する

[「bindPopup」](https://leafletjs.com/reference-1.6.0.html#popup)でイベントをバインドする


```javascript
var map = L.map('mapid', {
  center: [35.66572, 139.73100],
  zoom: 17,
});
var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
});
tileLayer.addTo(map);
var features = [];
var place = [{
  "name": "東京ミッドタウン",
  "lat": "35.66572",
  "long": "139.73100"
}, {
  "name": "サントリー美術館",
  "lat": "35.6662186",
  "long": "139.7303961"
}, ]
// GeoJSON形式で複数個のマーカーを設定する
for (var i = 0; i < place.length; i++) {
  features.push({ 
    "type": "Feature",
    "properties": {
      "name": place[i].name
    },
    "geometry": {
      "type": "Point",
      "coordinates": [place[i].long, place[i].lat]
    }
  });
}
L.geoJson(features, {
  onEachFeature: function(features, layer) {
    if (features.properties && features.properties.name) {
      layer.bindPopup(features.properties.name);
    }
  }
}).addTo(map);
```

<img width="690" alt="スクリーンショット 2020-05-03 20.09.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/62169113-3377-5ebf-3730-d61351dc4ba4.png">




### 動作サンプル

[Leaflet+OSM Sample04 マーカーをクリックするとポップアップが表示される](https://codepen.io/TakeshiNickOsanai/pen/qBOVrWv?editors=0010)

## マーカーに MouseOver、Click イベントを仕掛ける

先にbindPopup をバインドし、MouseOver, click イベントを重ねる。

* マーカーの上にマウスを重ねると、ポップアップが表示
* マーカーをクリックすると、アラートが表示される

というユースケースで書いてみた。

```javascript
var map = L.map('mapid', {
  center: [35.66572, 139.73100],
  zoom: 17,
});
var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
});
tileLayer.addTo(map);
var features = [];
var place = [{
  "name": "東京ミッドタウン",
  "lat": "35.66572",
  "long": "139.73100"
}, {
  "name": "サントリー美術館",
  "lat": "35.6662186",
  "long": "139.7303961"
}, ]
// GeoJSON形式で複数個のマーカーを設定する
for (var i = 0; i < place.length; i++) {
  features.push({ 
    "type": "Feature",
    "properties": {
      "name": place[i].name
    },
    "geometry": {
      "type": "Point",
      "coordinates": [place[i].long, place[i].lat]
    }
  });
}
L.geoJson(features, {
  onEachFeature: function(features, layer) {
    if (features.properties && features.properties.name) {
      layer.bindPopup(features.properties.name);
      layer.on('mouseover', function(e) {
        this.openPopup();
      });
      layer.on('mouseout', function(e) {
        this.closePopup();
      });
      layer.on('click', function(e) {
        alert('Yay!');
      });
    }
  }
}).addTo(map);

```

<img width="950" alt="スクリーンショット 2020-05-03 20.17.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/083b9295-cf21-eece-8a3a-2e9fd078c415.png">


### 動作サンプル

[Leaflet+OSM Sample05 マーカーにonMouse、onClickそれぞれにイベントを仕掛ける](https://codepen.io/TakeshiNickOsanai/pen/PoPOpZG)


## 地図の中央に十字マークを表示する

[「あちあち情報局」](https://www.achiachi.net/blog/leaflet/cross)様のコードを参考にしました。
十字マークは、[国土地理院](https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html)より拝借。


```javascript

// 十字マークを地図中央に表示

var crossIcon = L.icon({
  iconUrl: 'https://maps.gsi.go.jp/image/map/crosshairs.png',
  iconSize: [32, 32], 
  iconAnchor: [16, 16] 
});

var crossMarker = L.marker( map.getCenter(),{
  icon:crossIcon,  
  zIndexOffset:1000, 
  interactive:false 
}).addTo(map);


map.on('move', function(e) {
  crossMarker.setLatLng(map.getCenter());
});


```

![スクリーンショット 2020-05-04 18.37.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/1145daa4-8005-3067-3183-4a00fb696403.png)

### 動作サンプル

[Leaflet+OSM Sample06 地図の中央に十字マークを表示する](https://codepen.io/TakeshiNickOsanai/pen/abvVPPe)

## 現在地の情報を取得して地図を表示する

[「locate」](https://leafletjs.com/reference-1.6.0.html#map-locate) メソッドを使う。geoLocation のラッパーのようで、ブラウザが geoLocation API 対応なら、現在地を取得して処理を行ってくれる。

locateメソッドとは別に、onLocationFound, onLocationError という2つのメソッドがある。

それぞれ、現在位置の取得の成功・失敗に応じてイベントの発火ができる。
より細かい処理が必要な場合に有用。


```javascript
// 現在地を取得して、地図を描画する

function getgeo() {
  map.on('locationerror', onLocationError);
  map.locate({
    setView: "true"
  });
}

function onLocationError(e) {
  alert(e.message);
}
```

### 動作サンプル

[Leaflet+OSM Sample07 現在地の地図を表示する](https://codepen.io/TakeshiNickOsanai/pen/rNOYPLB)

### 地図の縮尺について

PCの画面で locateメソッドを使うと、地図の描写がZoomOutされ、細かい地図の描写ができない。
その一方、スマートフォンで locate 機能を使うと、直前まで表示していた地図の縮尺レベルを保ったまま、現在地周辺の地図を描画する。

スマホで使う分には問題ないが、ちょっと気持ち悪い。どうすれば解決するか、調査中。

## 地図をドラッグ移動後、中心点の緯度経度を表示する

[「moveend」](https://leafletjs.com/reference-1.6.0.html#map-moveend) メソッドを使う。[getCenter](https://leafletjs.com/reference-1.6.0.html#map-moveend) と組み合わせて、

* 地図の移動終了を、moveend で検知し、イベント発火
* getCenter で中心地の緯度経度を取得する

という流れで処理する。

* 緯度は getCenter().lat
* 経度は getCenter().lng

で取得できる。

```javascript
map.on('moveend', function(e) {
  console.log("緯度: " + map.getCenter().lat);
  console.log("経度: " + map.getCenter().lng);   
});

``` 

![スクリーンショット 2020-05-05 12.03.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/c8f6b6df-144c-f0d0-e8e9-de8c7f242c77.png)


### 動作サンプル

[Leaflet+OSM Sample08 地図をドラッグ移動後、中心の緯度経度を表示](https://codepen.io/TakeshiNickOsanai/pen/ExVoZME)


## コントローラーの表示位置を調整する

* [zoomContorol](https://leafletjs.com/reference-1.6.0.html#map-zoomcontrol)
* [setPosition](https://leafletjs.com/reference-1.6.0.html#domutil-setposition) 

を使うと楽。

LeafletにはDOM操作用のメソッドがいくつか用意されており、それを使う。

[「右上」「右下」「左上」「左下」](https://leafletjs.com/reference-1.6.0.html#map-zoomcontrol) の指定ができる。

```javascript

map.zoomControl.setPosition("bottomleft");
```


[StackOverflow をみていると、別なやり方も見つけられるが](https://stackoverflow.com/questions/33614912/how-to-locate-leaflet-zoom-control-in-a-desired-position)、上記の方がシンプル。

![スクリーンショット 2020-05-05 12.45.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/6702ea9b-5320-a305-69f4-eb22a4b2e469.png)


### 動作サンプル


[Leaflet+OSM Sample09 Zoomコントローラーの位置を変える](https://codepen.io/TakeshiNickOsanai/pen/JjYMWbx)

### 実稼働例

個人で開発・運営しているウェブサービスに、Leaflet と OpenStreetMap で地図表示を行いました。こちらに反映してます。


* [天気の子 聖地ナビ] (https://www.tenkinokoseichi.link/)

![tenkinoseichi_main.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/6334b54e-eae1-4acc-d39c-4b93250af2e6.jpeg)