LeafletでGeoJSONを読み込みカスタムアイコンを表示する

OpenLayersでは難なく実装できましたが、Leafletではちょっと苦戦したので書いてみました
手順を踏みながら実装していきます

## GeoJSONとは
- JSONをもとにした、GISデータを記述するためのフォーマット
- 多くの GIS 技術やサービスの間で非常に人気のあるデータ形式

## GeoJSONオブジェクトを使ってアイコンを表示する
まずは、GeoJSONオブジェクトを定義して押上駅にアイコンを表示してみましょう
```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>アイコン表示</title>
    <link href="style.css" rel="stylesheet">
    <!-- Leaflet -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css" integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ==" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js" integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ==" crossorigin=""></script>
</head>
<body>
    <div id="map"></div>
    <script src="main.js"></script>
</body>
</html>
```
```style.css
html, body{
    height: 100%;
 }
  
#map{
   height: 100%;
}
```
ここまでは[前回の記事](https://qiita.com/asahina820/items/d9444c2abd82e6e8898b)と同じです

```main.js
let lat = 35.7100069; // 緯度
let lng= 139.8108103; // 経度
let zoom = 16; // ズームレベル

let map = L.map("map"); // 地図の生成
map.setView([lat, lng], zoom); // 緯度経度、ズームレベルを設定する

// タイルレイヤを生成し、地図に追加する
// 今回はOpenStreetMapを表示する
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
  {
    // 著作権の表示
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }
).addTo(map);

// アイコンを追加する
// GeoJSONオブジェクトを定義する
var geojsonFeature = {
  "type": "Feature",
  "properties": {
      "name": "押上駅",
      "amenity": "Station"
  },
  "geometry": {
      "type": "Point", // ジオメトリのタイプを指定する。今回は1点なのでポイント
      "coordinates": [139.8136911, 35.709058] // 位置情報をlng, latの順で指定する
  }
};

// GeoJSONオブジェクトを地図に追加する
L.geoJSON(geojsonFeature).addTo(map);
```
- GeoJSONオブジェクトを定義する
    - coordinatesで位置情報を経度, 緯度の順番で定義する
- GeoJSONオブジェクトを地図に追加する
- ポイントデータの場合、デフォルトのマーカーが生成される

![アイコン表示_GeoJSON.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/d764ebe4-4593-afc6-4814-d529d24fa0a2.png)
押上駅にぴょこんとアイコンが表示されました～
ここまでは[公式のサンプルコード](https://leafletjs.com/examples/geojson/)通りに実装すればすんなりいけます

## GeoJSONファイルを使ってアイコンを表示する
基本的にはGeoJSONオブジェクトをソースコードにべた書きせず、外部のGeoJSONファイルを読み込んで使うことがおおいです
ということでGeoJSONファイルをどっかからダウンロードして読み込んでみましょう

### データを取得する
- 今回は[国土交通省国土数値情報ダウンロードサイト](https://nlftp.mlit.go.jp/ksj/index.html)からデータをダウンロードする
    - 残念ながら駅のポイントデータはなかった・・（ラインだった）
    - [観光資源](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P12-v2_2.html)のうち東京都を選択
    - ダウンロードボタンを押したらGoogleフォームのアンケートが出てきた
- ダウンロードしたZipファイルを解凍する
![DLしたファイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/8886aad8-8ecc-8a51-2c41-9a43f7bfd227.png)
中身はこんな感じでした～

### GeoJSONファイルを用意する
- QGISを使ってシェープファイルをGeoJSONファイルに変換してエクスポートする
- ポイントデータはP12a.shpとかいてあるので、P12a-14_13.shpを使用する
- 変換方法は[QGIS 操作マニュアル](https://nlftp.mlit.go.jp/ksj/other/QGIS_manual.pdf)P.37を参照
- 出力したGeoJSONファイルはこんな感じ
    - 長いため途中省略
```tourism_resources.geojson
{
"type": "FeatureCollection",
"name": "tourism_resources",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::6668" } },
"features": [
{ "type": "Feature", "properties": { "P12_001": 10034, "P12_002": "南島", "P12_003": "13", "P12_004": "13421", "P12_005": "海岸・岬", "P12_006": "小笠原村南島", "P12_007": -1 }, "geometry": { "type": "Point", "coordinates": [ 142.175460418424194, 27.03785670484675 ] } },
{ "type": "Feature", "properties": { "P12_001": 10032, "P12_002": "三原山溶岩群", "P12_003": "13", "P12_004": "13361", "P12_005": "岩石・洞窟", "P12_006": "大島町", "P12_007": -1 }, "geometry": { "type": "Point", "coordinates": [ 139.381705306298642, 34.737855699876953 ] } },
 ・・略・・
]
}
```
- GeoJSONファイルを配置する
    - 今回はdataフォルダ内に配置した

### GeoJSONファイルを読み込む
```main.js
let lat = 35.7100069; // 緯度
let lng= 139.8108103; // 経度
let zoom = 16; // ズームレベル

let map = L.map("map"); // 地図の生成
map.setView([lat, lng], zoom); // 緯度経度、ズームレベルを設定する

// タイルレイヤを生成し、地図に追加する
// 今回はOpenStreetMapを表示する
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
  {
    // 著作権の表示
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }
).addTo(map);

// アイコンを追加する
// 外部のGeoJSONファイルを取得する
fetch("./data/tourism_resources.geojson")
  .then(response => response.json())
  // GeoJSONを地図に追加する
  .then(data => {
    L.geoJSON(data).addTo(map);
  });
```
- GeoJSONファイルを取得する
    - ネットで調べているとjQueryを使っているものが多かったが、~~宗教上の理由であまりjQueryを使いたくなかったため~~fetchを採用した
- 取得したGeoJSONファイルを地図に追加する

![アイコン表示_GeoJSON2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/c7e95170-2235-ac5c-7985-67f336206cc0.png)

東京都の観光資源がわらわらと表示されました～
index.htmlをサーバ使わずそのまま表示するとCORSエラーでGeoJSONファイルが読み込めないので、Live Server等でサーバを立てて表示しましょう
## カスタムアイコンを表示する
ポイントデータは、デフォルトのマーカーが生成されます
カスタムアイコンを表示したい場合は、pointToLayerオプションを使って、マーカー生成時にアイコン設定を渡せばよいです

```main.js
let lat = 35.7100069; // 緯度
let lng= 139.8108103; // 経度
let zoom = 16; // ズームレベル

let map = L.map("map"); // 地図の生成
map.setView([lat, lng], zoom); // 緯度経度、ズームレベルを設定する

// タイルレイヤを生成し、地図に追加する
// 今回はOpenStreetMapを表示する
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
  {
    // 著作権の表示
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }
).addTo(map);

// アイコンを追加する
// カスタムアイコンの設定
var customIcon = L.icon({
  iconUrl: './img/icon.png', // アイコン画像のURL
  iconSize:     [40, 40], // アイコンのサイズをピクセルで指定
  iconAnchor:   [25, 50], // アイコンの先端の座標（右上からの相対座標）
  popupAnchor:  [0, -50] // ポップアップが開く点の座標（iconAnchorが基準）
});

// 外部のGeoJSONファイルを取得する
fetch("./data/tourism_resources.geojson")
  .then(response => response.json())
  // GeoJSONを地図に追加する
  .then(data => {
    L.geoJSON(data, {
      // カスタムアイコンを設定する
      pointToLayer: function(feature, latlng) {
        return L.marker(latlng, {icon: customIcon});
      }
    }).addTo(map);
  });
```

- フリーアイコンをダウンロードしてimgフォルダに配置した
- アイコンの設定は[Markers With Custom Icons](https://leafletjs.com/examples/custom-icons/)を参考
- pointToLayer関数は、GeoJSONのポイントデータのレイヤを生成する方法を定義する関数で、データが追加されたときにポイントの特徴とその 位置情報を渡す

![アイコン表示_GeoJSON3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/a75a41a4-b3be-ae6a-47bd-9ad8b3d97dbc.png)

GeoJSONファイルのポイントデータにカスタムアイコンを設定できました～
ついでに、ポップアップも表示してみましょう

## アイコンをクリックしたときにポップアップを表示する
地物ごとにポップアップを表示したい場合は、onEachFeatureオプションを使って地物にポップアップを追加します
地物の名前はGeoJSONから取り出してポップアップに表示させます

```main.js
let lat = 35.7100069; // 緯度
let lng= 139.8108103; // 経度
let zoom = 16; // ズームレベル

let map = L.map("map"); // 地図の生成
map.setView([lat, lng], zoom); // 緯度経度、ズームレベルを設定する

// タイルレイヤを生成し、地図に追加する
// 今回はOpenStreetMapを表示する
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
  {
    // 著作権の表示
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }
).addTo(map);

// アイコンを追加する
// カスタムアイコンの設定
var customIcon = L.icon({
  iconUrl: './img/icon.png', // アイコン画像のURL
  iconSize:     [40, 40], // アイコンのサイズをピクセルで指定
  iconAnchor:   [25, 50], // アイコンの先端の座標（右上からの相対座標）
  popupAnchor:  [0, -50] // ポップアップが開く点の座標（iconAnchorが基準）
});

// 外部のGeoJSONファイルを取得する
fetch("./data/tourism_resources.geojson")
  .then(response => response.json())
  // GeoJSONを地図に追加する
  .then(data => {
    L.geoJSON(data, {
      // カスタムアイコンを設定する
      pointToLayer: function(feature, latlng) {
        return L.marker(latlng, {icon: customIcon});
      },
      // ポップアップを表示する
      onEachFeature: function(feature, layer){
        // 地物の名前を取り出す
        let name = feature.properties.P12_002;
        // ポップアップに名前を表示する
        layer.bindPopup(name);
      }
    }).addTo(map);
  });
```
- 地物の名前はGeoJSONファイルのpropertiesの"P12_002"に格納されていた
    - 名前を取り出してポップアップに表示させる
- onEachFeature関数は、作成した地物毎に呼び出される関数で、イベントやポップアップを地物に追加できる
![アイコン表示_GeoJSON4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2667439/7b8a5d0c-b757-5255-3091-0a4f4903f826.png)
アイコンをクリックするとポップアップが表示されるようになりました～
GISっぽくなりましたね。

## 参考文献
- GeoJSONについて
    - https://gis-oer.github.io/gitbook/book/materials/web_gis/GeoJSON/GeoJSON.html
    - https://datatracker.ietf.org/doc/html/rfc7946
- 利用したデータ
    - https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P12-v2_2.html
- シェープファイルからGeoJSONファイルへの変換
    - https://nlftp.mlit.go.jp/ksj/other/QGIS_manual.pdf
- Leafletについて
    - https://leafletjs.com/examples/geojson/
    - https://leafletjs.com/examples/custom-icons/
    - https://leafletjs.com/reference.html