vue2-leafletの使い方、GeoJsonデータの表示・操作など

## はじめに
ウェブ上で簡単に地図を表示する人気ライブラリ「Leaflet.js」をVue.js環境でお手軽に使えるラッパーライブラリ「vue2-leaflet」を5日くらい色々いじって得た知見を本記事にまとめます。いじった結果のゴール地点は以下の画像のようなウェブアプリケーションです。①地図エリアに.geojsonファイルをドラッグドロップすると地図上に地物を表示、②読み込んだGeoJsonレイヤを一覧表示、③地物をクリックするとその地物の属性をすべて表示、という以上の三機能をVue.jsの特性を活かし実装しました。本記事ではvue2-leafletの導入・実装と、機能①に焦点を絞って解説したいと思います。
<img width="900" alt="スクリーンショット 2019-11-27 19.58.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/7195b222-f991-14a7-d8af-6bd31fb2f981.png">

## 環境
- npm 6.12.0
- @vue/cli 4.0.5
- leaflet 1.6.0
- vue2-leaflet 2.2.1
- 
「vue cli」環境で開発しました。「vue cli」環境構築については本記事では掲載しません（以下の記事が詳しいです）。
[Vue.js #001 – Vue CLI 3で環境構築](https://day-journal.com/memo/vue-js-001/)

## 導入

```
npm install leaflet vue2-leaflet
```

インストール後、main.jsにてcssを読み込ませます

```javascript:main.js
import Vue from 'vue'
import App from './App.vue'

//ここから
import { Icon }  from 'leaflet'
import 'leaflet/dist/leaflet.css'
//ここまで

// this part resolve an issue where the markers would not appear
delete Icon.Default.prototype._getIconUrl;

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

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')
```
以上で導入は完了です。


## 実装
すべて単一ファイルコンポーネントとして記載しています。

### script
```javascript
<script>
    import {
        LMap,
        LTileLayer,
        LControlLayers,
        LControlScale,
        LGeoJson,
    } from 'vue2-leaflet';

    export default {
        name: 'MapPane',
        components: {
            LMap,
            LTileLayer,
            LControlLayers,
            LControlScale,
            LGeoJson,
        },
        data() {
            return {
                center: [38, 140],
                zoom:5,
                options: {
                    onEachFeature: function(feature, layer) {
                        layer.options.smoothFactor = 2;
                    }
                },
                geojson: null,
            }
        },
</script>
```

### template
```vuejs
<template>
    <div class="mapPane"
        @dragover.prevent="dragover"
        @drop.prevent="drop"
        >
        <l-map
            :zoom="zoom"
            :center="center"
            :preferCanvas="true"
        >

            <l-control-layers
                position="topright"
                :collapsed="false"
            ></l-control-layers>
            <l-control-scale
                position="bottomleft"
                :imperial="false"
                :metric="true"
            ></l-control-scale>

            <l-tile-layer
                name="MIERUNE MONO"
                visible="true"
                url="https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png"
                attribution="Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a 
                href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
                layer-type="base"
            ></l-tile-layer>
            <l-geo-json
                :geojson="geojson"
                :options="options"
                :options-style="styleFunction"
                @click="onFeatureClick"
            ></l-geo-json>
        </l-map>
    </div>
</template>
```

長いので分解してみましょう。
まずLeafletマップ本体はl-mapです。l-mapの内側に、必要なUIパーツや表示したいレイヤーを記述します。

```vue
<l-map
    :zoom="zoom"
    :center="center"
    :preferCanvas="true"
>
<!-- ここにタイルレイヤーだとかコントロールだとかを追加する -->
</l-map>
```

素のl-mapだけだと背景地図すら表示されません。なのでまずタイルレイヤーを追加してみましょう。タイルレイヤーはl-tile-layerをl-map内に記述する事で追加されます。

```vue
<l-tile-layer
    name="MIERUNE MONO"
    visible="true"
    url="https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png"
    attribution="Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a 
    href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
    layer-type="base"
></l-tile-layer>
```

このとおり、l-map内に各種コンポーネントを追加記述して、望む機能をもつ地図をつくれる訳ですね。おなじみのレイヤーコントロール（レイヤー一覧、L.control.layers）は、前述の例のとおりl-control-layersで実装できたりと、基本的な機能をvueの記法で実装出来ます。そういったビルトインのUIパーツなどはおそらく実装には困らないと思いますので、本記事ではGeoJsonレイヤーの取扱いについて掘り下げていきたいと思います。

## GeoJsonレイヤーの取扱
### 実装
```vue
<l-geo-json
    :geojson="geojson"
    :options="options"
    :options-style="styleFunction"
    @click="onFeatureClick"
></l-geo-json>
```

l-geo-jsonをl-map内に追加します。v-bindでgeojsonオブジェクトを渡してやる必要があります。ここで、この<b>geojsonはリアクティブです。つまりdata内のgeojsonの変更がマップに即時反映されます。</b>vue2-leafletの前にvue-mapboxで遊んでいて、同様にgeojsonレイヤーのコンポーネントはあるのですが、リアクティブではありませんでした。この一点だけでもLeafletを優先して使う価値があると思います（GeoJsonビューア愛好家として）。

という訳でまずは.geojsonファイルのドラッグドロップ機能を実装しましょう。

```vue
<div class="mapPane"
    @dragover.prevent="dragover"
    @drop.prevent="drop"
    >
    <!-- l-mapなどなど -->
</div>
```

vueでドラッグドロップイベントを実装する場合はdragoverとdropをv-onで書きます。ここではドロップ時にdropというメソッドを実行せよ、という意味になります。.preventはブラウザの基本機能の実行を防ぐ構文です（例：ファイルをドロップすると、ブラウザ自体がそのファイルを開こうとするため）。さてここで、ドロップイベントだけ監視したいのだから、dragoverは不要ではないか？と考えると思います。しかしながらそれでは動作しません。おそらくdragover時にブラウザ処理が先行してしまうから（.preventが走らないから）だと思います。

さて、dropメソッドは、script内のmethodsにて宣言します。

```javascript
methods: {
    drop: function(event) {
        let fileList = event.dataTransfer.files;
        let vm = this
        for ( let i = 0; i < fileList.length; i++ ) {
            let reader=new FileReader()
            reader.onload=function(e){
                let geojson = JSON.parse(reader.result)
                vm.geojson = geojson
            }
            reader.readAsText(fileList[i])
        }
    },
}
```

drop内の無名関数の引数eventにはドロップされたファイルの情報などが含まれています。File APIにより、ドロップされた.geojsonファイルからgeojson形式のオブジェクトを取得します。File APIについての解説はここでは省きます。
さて、ここで

```javascript
let vm = this
```

この文の意味ですが、本当ならfor文内でもvueコンポーネントをthisで呼び出したい訳ですが、スコープが（良い言葉が思いつきませんがイメージ的には）一段深くなっており、thisで参照出来ません。そこで、thisでコンポーネントを参照出来るうちにvmという変数で保持している訳です。
さて、FileAPIでの読み込みが完了すると

```javascript
let geojson = JSON.parse(reader.result)
vm.geojson = geojson
```

このとおり、vueコンポーネント内の、data内の、geojsonに、たった今File APIで取得したGeoJson型オブジェクトを突っ込みます。するとl-geo-jsonのgeojsonはリアクティブなので地図に地物が追加されます。

#### 地物ごとの処理(onEachFeature)
l-geo-jsonのoptionsは、その他のコンポーネントと異なり、v-bindで:optionsに、オブジェクトをまとめて渡してやらなければなりません。

```vue
<!-- l-geo-json内 -->
:options="options"
```

```javascript
//data()内
options: {
    onEachFeature: function(feature, layer) {
        layer.options.smoothFactor = 2;
    }
},
```

この例では、LeafletにおけるL.GeoJSONでおなじみのonEachFeature()を設定しています。地物ごとに個別の処理を行える関数です。ここでは、各地物の描画を簡素化しています。

## 参考
- [Vue2Leaflet (Document)](https://vue-leaflet.github.io/Vue2Leaflet/#/)

公式ドキュメントですが、あまり詳しい事は書いてありません。あんまり複雑な事しないなら早いか。

- [KoRiGaN/Vue2Leaflet](https://github.com/KoRiGaN/Vue2Leaflet)

ソースですが、ここにexampleが多数ありとてもかなり非常に参考になります。

## まとめ
とてもとても長くなってしまいましたが、vue2-leafletの使い方を色々まとめました。どうやら私は、MapboxでもなんでもGISフレームワークの勉強の際は、とりあえず手持ちのGeoJsonやらを表示させるまでをチュートリアルと考えているフシがあります。vue.jsは使い始めですが、すげぇ便利だなって…。ここ数ヶ月はコードを書くばかりで知識のアウトプットもとい備忘録の作成を怠っていたため、来たるアドベントカレンダーへ向け、溜まっている下書きを清書していきたいです。