【Leaflet & OpenStreetMap】Web地図ライブラリを使ってみる

## はじめに

  現場で地理空間ネットワークを管理するようなソフトウェアに触れることがあり、果たしてどうやって動いているのか気になったので、キャッチアップも兼ねて、今回は手軽に導入できそうな **leaflet.js** と **OpenStreetMap** を使用してみたいと思います。

[APIリファレンスはコチラ](https://leafletjs.com/reference.html#geojson)
  
  ※誤りありましたら、ご指摘いただけますと幸いです。

## leaflet.jsとOpenStreetMapとは
  **leaflet** は地図サービスでよく使用されるJavaScriptライブラリです。
  ちなみにleafletはあくまでJavaScriptライブラリであり、地図データ自体を持っていません。

  そこで、今回は **OpenStreetMap** を使用してみます。
  **OpenStreetMap** はユーザーが共同で作成するオープンデータの地図で、無料で商用利用も可能だそう。

## 使い方
  いたって簡単で、下記HTMLのようにCSSとJSを順番に呼び出して、

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Leaflet + OpenStreetMap サンプル</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <link rel="stylesheet" href="../css/leaflet.css">
</head>
<body>
    <h1>Leaflet + OpenStreetMap 地図アプリ</h1>
    <div id="map"></div>

    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script src="../js/leaflet.js"></script>
</body>
</html>

```

  そしてJSで初期化と OpenStreetMap のタイルレイヤーを追加してあげればOKです。

```javascript:leaflet.js
// 地図の初期化
const map = L.map('map').setView([35.689505, 139.704433], 13);

// OpenStreetMap のタイルレイヤーを追加
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
```

  初期化部分での **L** についてですが、これはleafletライブラリを読み込むと **L** という名前空間が自動で作成されます。leafletのすべての機能はこのLオブジェクトを通じて提供されているわけです。

  mapメソッドはその名の通りマップを作成するためのメソッドで、setViewメソッドは初期表示場所を指定できます。
  引数として**緯度・経度**と**ズームレベル**を指定しています。

  実際に表示してみると、、、

  ![image.png](http://hr-dash.tech/files/img/2025-02/20250209151207028.png)

  このように新宿周辺の地図情報を取得できました。
  ちなみに、上記で指定した緯度経度はRP本社を指しています。


## マーカーやポップアップアクションを追加してみる

  地図を表示できたところで、続いてマーカーやポップアップアクションを追加してみます。
  最初にマーカーの追加から。

```javascript:leaflet.js
/*
初期化・タイルレイヤー追加処理は省略
*/

// マーカーの追加
const marker = L.marker([35.689505, 139.704433]).addTo(map);

```

  L.markerで地図上にピン（マーカー）を立てることができます。
  ここでは同じくRP本社の緯度経度を指定。

  表示してみると、、、

 ![image.png](http://hr-dash.tech/files/img/2025-02/20250209151953487.png)

これだけだと味気ないので、ポップアップも追加してみます。

```javascript:leaflet.js
/*
初期化・タイルレイヤー追加処理は省略
*/

// マーカーにポップアップを追加する
const marker = L.marker([35.689505, 139.704433]).addTo(map);
const popup = L.popup().setContent("<b>RP本社</b><br><img src='../img/スクリーンショット 2025-02-09 073933.png' width='200' height='500'>");
L.marker([35.689505, 139.704433]).bindPopup(popup).bindTooltip("RP本社").addTo(map);

```
  
  L.popupでポップアップの追加、setContentでその中身を指定します。ここではテキストと画像を指定。
  ちなみに **bindTooltip** ではツールチップ（クリックしなくても表示される吹き出しみたいな）を指定できます。
  こうすることでカーソルを当てると簡単なポップアップが表示され、クリックすると画像も表示できるようになりました。

  ![image.png](http://hr-dash.tech/files/img/2025-02/20250209151406557.png)
  ![image.png](http://hr-dash.tech/files/img/2025-02/20250209151451910.png)

  
  また、クリックイベントを追加して、ピンを立てられるようにしてみましょう。

```javascript:leaflet.js
/*
初期化・タイルレイヤー追加処理は省略
*/

// クリックイベントでピンを追加
map.on('click', function(e) {
    const { lat, lng } = e.latlng;
    L.marker([lat.toFixed(6), lng.toFixed(6)]).addTo(map);
});

```

  何やら関数でeを受け取っていますが、この e は、**イベントオブジェクト（Event Object）** です。
  JavaScriptではイベント（クリック、スクロール、キー入力など）が発生したときに、そのイベントに関する情報がまとめられたオブジェクト が自動的に渡されるんですね。

  そしてイベントオブジェクトから **latlng** なるプロパティを取り出しています。
  これは **クリックされた箇所の緯度経度** になります。

  そして先程同様にマーカー表示できるようにしてあげれば、、

  ![image.png](http://hr-dash.tech/files/img/2025-02/20250209151601449.png)

  マーカーが追加できるようになりました。


## おまけ：geoJSON

  GeoJSON（ジオJSON） は、地理空間データ（地図情報）を表現するためのJSON形式のデータフォーマットになります。
  公式ドキュメントを見てみると様々なオプションを追加できることが分かります。

  ここでは、表現できるデータとして、点や線なんかもありますが、地図上で範囲を指定してみたいので、ここではポリゴンを設定してみます。

```javascript:leaflet.js
/*
初期化・タイルレイヤー追加処理は省略
*/

// GeoJSONデータ
const geojsonData = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [139.767125, 35.681236]
            },
            "properties": {
                "name": "RP本社",
                "description": "ここがRP本社です！"
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [139.70, 35.68],
                        [139.71, 35.68],
                        [139.71, 35.69],
                        [139.70, 35.69],
                        [139.70, 35.68]
                    ]
                ]
            },
            "properties": {
                "name": "RP本社周辺エリア",
                "description": "これはRP本社周辺の四角形のポリゴンです"
            }
        }
    ]
};

// GeoJSONレイヤーとして地図に追加
L.geoJSON(geojsonData, {
    onEachFeature: function (feature, layer) {
        if (feature.properties && feature.properties.name) {
            layer.bindPopup(`<b>${feature.properties.name}</b><br>${feature.properties.description}`);
        }
    },
    style: function (feature) {
        return feature.geometry.type === "Polygon" ? { color: "blue", fillOpacity: 0.3 } : {};
    }
}).addTo(map);

```

  featuresの中に **type（データの種類）**, **geometry（地理情報）**, **properties（追加情報）**の3種類のプロパティが含まれています。
  今回は特にgeometryの中に着目します。

  まず、typeは **Polygon** となっていますが、これは地域や建物の範囲等を示す情報を持つことができます。
  その他にもピンやランドマーク等の点情報を示す **Point**、道路等の線情報を示す **LineString**等があったりします。

  そして **coordinates** ですが今回は何やら5つの緯度経度情報が含まれています。
  これは今回ポリゴンで示す四角形の頂点を示しています。
  見てみると、違うのは小数点第二位のみであり、そして1番目と5番目の頂点は同じであることが分かります。

  #### 補足  : Polygonの座標について
  今回、1番目と5番目に同じ座標を指定しているものの、実は指定しなくても見た目上はほぼ同じ四角形になります。
  これは、Leafletが自動で調整をしてくれているためです。

  しかし、GeoJSONの公式仕様（[RFC7946](https://tex2e.github.io/rfc-translater/html/rfc7946.html)）を覗いてみると、次のような記述があります。

  >"The first and last positions are equivalent, and they MUST contain identical values; their representation SHOULD also be identical."

  簡単に翻訳すると、**「最初と最後の座標は等価であり、同一の値を含む必要があります（MUST）。また、その表現も同一であるべきです（SHOULD）。」** ということを示しています。

  正確なポリゴン解析のためには、最初と最後に同一の指定、かつ表現（小数点数等）も同一であるべきなのです。

  では最後に実際の表示を見てみます。

  ![image.png](http://hr-dash.tech/files/img/2025-02/20250209151800742.png)

  このように、四角形で囲まれた範囲を指定することができました。

  

## 最後に
  JSの学習を進めていく中で、実際に手を動かして、不明点は止まって調べて、また動かして、、というような原点に立ち返ったような学習を進めていますが、学べば学ぶほど奥深く、どんどん興味が沸く分野が増えてきました。

  その中の1つが地図情報との連携だったのですが、今回紹介した部分はあくまで導入部分でしかなく、まだまだ理解したい部分ももっとあるので、キャッチアップも兼ねて学習してみます。

  最近アウトプットができていなかった分、時間を見つけてまた定期的に技術記事も投稿していこうと思います。