leafletでウェブページに地図を表示する

# leafletとは

javascriptで地図表示をやってくれるライブラリです。
https://leafletjs.com/index.html


# タイルについて

ウェブで一般的な地図アプリケーションでは地図の画像をタイル状に敷き詰めて表示しています。leafletも同様に地図タイルを取得して表示するように作られていますので、無償または有償で提供されているタイルを利用して表示することができます。

Open Street Mapなど、無料で利用可能なタイル一覧を以下で確認することが出来ます
https://leaflet-extras.github.io/leaflet-providers/preview/

また、mapboxなどの地図APIサービスのタイルを利用することも出来ます。mapboxを使用する場合はアカウントを作ってAPI-keyを生成して使う必要があります。
https://docs.mapbox.com/vector-tiles/reference/


# 1. 地図を表示する

以下の4つを記載するだけです。

#### 1. leafletのCSSを読み込む

```
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
   integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
   crossorigin=""/>
```

#### 2. leafletのjavascriptコードを読み込む

これは上記のCSSの読み込みの後に書かないといけないようです

```
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
   integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
   crossorigin=""></script>
```

#### 3. 地図を表示する場所にタグを書く

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

#### 4. 地図の表示幅を設定する

```
#mapid { height: 180px; }
```

#### まとめて書くとこんな感じ

以下ではOSMのタイルを利用しています

```html:index.html
<!DOCTYPE html>
<html lang="ja" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Leafletデモ</title>
</head>
  <body>
    <div id="mapid"></div>
  </body>
</html>

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" integrity="sha512Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js" integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw==" crossorigin=""></script>

<style media="screen">
  #mapid {
      height: 500px;
  }
</style>

<script type="text/javascript">
  //地図の中心地を定義
  var mymap = L.map('mapid').setView([37.85, 138.8], 9);

  //タイルレイヤーを取得
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: 'Map data &copy; <a href="https://openstreetmap.org">OpenStreetMap</a>',
    maxZoom: 18}).addTo(mymap);

</script>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/cb3bd834-1996-9581-7110-de1ad96ff2f2.png)


# 2. 要素を足す

### マーカーを置く

```javascript
var marker = L.marker([37.85, 138.8]).addTo(mymap);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/33c57fb9-f593-d19d-7b61-c693264f137a.png)


### サークルを描く

```javascript
  var circle = L.circle([37.85, 139.1], {
    color: 'red',
    fillColor: '#f03',
    fillOpacity: 0.5,
    radius: 6000
  }).addTo(mymap);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/be19b3d3-c996-0a35-f6b0-1e18ee7e6df8.png)


### ポリゴンを描く

```javascript
geo_array = [[37.95, 138.6], [38.05, 138.3], [37.80, 138.4]]
var polygon = L.polygon(geo_array, {
          color: 'green',
          fillColor: '#25dc25',
          fillOpacity: 0.5}).addTo(mymap);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/3b4915af-273d-9672-2d0f-5e75e63ecbd0.png)


# 3. ポップアップを付ける

### 配置した部品に付ける

```javascript
marker.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup();
circle.bindPopup("I am a circle.");
polygon.bindPopup("I am a polygon.");
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/165d9402-4f87-6062-52f5-40b40aaf8d05.png)

### マップ上の座標に付ける

```javascript
var popup = L.popup()
  .setLatLng([37.4, 138.9])
  .setContent("I am a standalone popup.")
  .openOn(mymap);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/0f84d93e-2178-0e9d-f5ce-222e5afe84c4.png)

### イベントに対応して付ける

以下のようにクリックイベントが発生したらポップアップを呼ぶこともできます

```javascript
var popup = L.popup();
function onMapClick(e) {
    popup
        .setLatLng(e.latlng)
        .setContent("You clicked the map at " + e.latlng.toString())
        .openOn(mymap);
}
mymap.on('click', onMapClick);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/31aebf20-7822-a977-b399-b3a32b7efbbc.png)

# 4. GeoJSONを使う

ポリゴン座標をJSONでまとめて渡すことも出来ます。
GeoJSONという書式があって、その通りに書いて渡すだけです。
https://leafletjs.com/examples/geojson/

GeoJSON Specification (RFC 7946)
https://tools.ietf.org/html/rfc7946

```javascript
var myLines = [{
    "type": "LineString",
    "coordinates": [[138.6, 37.95], [138.3, 38.05], [138.4, 37.80], [138.6, 37.95]]
}, {
    "type": "LineString",
    "coordinates": [[139.1, 38.05], [138.8, 38.15], [138.9, 37.90], [139.1, 38.05]]
}];

var myStyle = {
    "color": "#ff7800",
    "weight": 5,
    "opacity": 0.65
};

L.geoJSON(myLines, {
    style: myStyle
}).addTo(mymap);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/722eeb90-31c2-bcd1-3510-4bd81a74b281.png)

#### 色を指定する

L.geoJSONクラスを定義する際に色を指定すればその色で塗ってくれます。

```javascript
  var states = [{
    "type": "Feature",
    "properties": {"party": "Republican"},
    "geometry": {
        "type": "Polygon",
        "coordinates": [[
            [-104.05, 48.99],
            [-97.22,  48.98],
            [-96.58,  45.94],
            [-104.03, 45.94],
            [-104.05, 48.99]
        ]]
    }
}, {
    "type": "Feature",
    "properties": {"party": "Democrat"},
    "geometry": {
        "type": "Polygon",
        "coordinates": [[
            [-109.05, 41.00],
            [-102.06, 40.99],
            [-102.03, 36.99],
            [-109.04, 36.99],
            [-109.05, 41.00]
        ]]
    }
}];

L.geoJSON(states, {
    style: function(feature) {
        switch (feature.properties.party) {
            case 'Republican': return {color: "#ff0000"};
            case 'Democrat':   return {color: "#0000ff"};
        }
    }
}).addTo(mymap);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/40174aa4-45ba-fdcc-4e91-3043721efba1.png)

# 5. コロプレス図を作る

地図にヒートマップを表示するアレです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/c0e93a4b-eed4-dec9-f76a-f79d5a43a636.png)
手順は以下のチュートリアルのものを参照しています
https://leafletjs.com/examples/choropleth/

使用するgeoJSONファイル(<a href="https://leafletjs.com/examples/choropleth/us-states.js">us-states.js</a>)は以下のような配置になっています。

```json:us-states.js
{
    "type": "Feature",
    "properties": {
        "name": "Alabama",
        "density": 94.65
    },
    "geometry": ...
    ...
}
```

#### geoJSONを表示する

上記のus-states.jsをhtmlと同じフォルダに置いて読み込んで使います

```javascript:index.html
<script src="us-states.js"></script>

<script type="text/javascript">
  //地図の中心地を定義
  var mymap = L.map('mapid').setView([37.8, -96], 4);

  //タイルレイヤーを取得
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: 'Map data &copy; <a href="https://openstreetmap.org">OpenStreetMap</a>',
    maxZoom: 18}).addTo(mymap);

  //"https://leafletjs.com/examples/choropleth/us-states.js"
  L.geoJson(statesData).addTo(mymap);
</script>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/84637fb0-66d1-8248-9d14-e7cf751703d6.png)

#### 色をつける

色や不透明度などを変更するにはL.geoJson()の第2引数にstyleを含むオプションを与えますが、ここに関数を書くことでgeoJSONの各feature内の値を参照して色を付けることが出来ます。以下ではfeature.properties.densityをfillColor()に渡して値を色に変換しています。

```javascript
  function getColor(d) {
    return d > 1000 ? '#800026' :
            d > 500  ? '#BD0026' :
            d > 200  ? '#E31A1C' :
            d > 100  ? '#FC4E2A' :
            d > 50   ? '#FD8D3C' :
            d > 20   ? '#FEB24C' :
            d > 10   ? '#FED976' :
                       '#FFEDA0';
  }

  function style(feature) {
    return {
        fillColor: getColor(feature.properties.density),
        weight: 2,
        opacity: 1,
        color: 'white',
        dashArray: '3',
        fillOpacity: 0.7
    };
  }

  L.geoJson(statesData, {style: style}).addTo(mymap);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/5fd31a17-088a-7f0c-e570-bdf62c1b90b4.png)

#### インタラクションを追加

ポリゴン上にマウスオーバーしたときのハイライト、マウスを外したときに元に戻す、クリックしたらズームする、という3つのインタラクションを追加します。それぞれstyleを変更するfunctionを書いておいてイベント発生時にonEachFeatureに呼び出してもらいます。

```javascript
  function highlightFeature(e) {
    var layer = e.target;

    layer.setStyle({
        weight: 5,
        color: '#666',
        dashArray: '',
        fillOpacity: 0.7
    });

    if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
        layer.bringToFront();
    }
  }

  function resetHighlight(e) {
    geojson.resetStyle(e.target);
  }

  function zoomToFeature(e) {
    mymap.fitBounds(e.target.getBounds());
  }

  function onEachFeature(feature, layer) {
    layer.on({
        mouseover: highlightFeature,
        mouseout: resetHighlight,
        click: zoomToFeature
    });
  }

  geojson = L.geoJson(statesData, {
      style: style,
      onEachFeature: onEachFeature
  }).addTo(mymap);
```

#### カスタムインフォを表示する

地図右上に表題とハイライトしている州の名前と人口密度を表示します

```javascript
  var info = L.control();

  info.onAdd = function (mymap) {
      this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
      this.update();
      return this._div;
  };

  // method that we will use to update the control based on feature properties passed
  info.update = function (props) {
      this._div.innerHTML = '<h4>US Population Density</h4>' +  (props ?
          '<b>' + props.name + '</b><br />' + props.density + ' people / mi<sup>2</sup>'
          : 'Hover over a state');
  };

  info.addTo(mymap);
```

内容の更新はマウスオーバーの際に呼ばれる関数にやってもらいます

```javascript
function highlightFeature(e) {
    ...
    info.update(layer.feature.properties);
}

function resetHighlight(e) {
    ...
    info.update();
}
```

情報欄の背景を白くしてフォントを設定するCSSを追加します

```css
.info {
    padding: 6px 8px;
    font: 14px/16px Arial, Helvetica, sans-serif;
    background: white;
    background: rgba(255,255,255,0.8);
    box-shadow: 0 0 15px rgba(0,0,0,0.2);
    border-radius: 5px;
}
.info h4 {
    margin: 0 0 5px;
    color: #777;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/3adf0a66-bc16-9548-43bd-f4ef349ac15b.png)

#### 色の目安を表示する



```javascript
  var legend = L.control({position: 'bottomright'});

  legend.onAdd = function (mymap) {

      var div = L.DomUtil.create('div', 'info legend'),
          grades = [0, 10, 20, 50, 100, 200, 500, 1000],
          labels = [];

      // loop through our density intervals and generate a label with a colored square for each interval
      for (var i = 0; i < grades.length; i++) {
          div.innerHTML +=
              '<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
              grades[i] + (grades[i + 1] ? '&ndash;' + grades[i + 1] + '<br>' : '+');
      }

      return div;
  };

  legend.addTo(mymap);
```

CSSに追記します

```css
.legend {
    line-height: 18px;
    color: #555;
}
.legend i {
    width: 18px;
    height: 18px;
    float: left;
    margin-right: 8px;
    opacity: 0.7;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/6bd57f62-da8f-595e-4913-77567fa30b66.png)


# もっと色々な地図を作りたいときに


読んだらよさげな記事のリンクを貼っときます

https://qiita.com/halboujp/items/67e70f55906b7266e1fc

https://qiita.com/frogcat/items/2e1746e3bc637123af44

https://qiita.com/ynunokawa/items/9158563e3041f2a090b0

https://ok2nd.github.io/leaflet/