Vue.jsとMapbox GL JSでオリジナルの地図を表示してみよう　～Mapbox GL JSの機能を知る編～

# はじめに
Vue.jsとMapbox GL JSを使って、このような機能を実装していきます。

- 地名検索
- 国名・地名の日本語化
- 位置情報表示

[VueMapbox](https://soal.github.io/vue-mapbox/) といったラッパーライブラリもありますが、
Mapbox GL JSの機能を知る編ということで、使わずに進めていきたいと思います。
結果として、Vue.jsの機能は全く使っていませんが・・・
次のステップとして、プラグイン化などしていけたらと思います:bow_tone1:

# mapboxとは
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/f5c01bbe-5be0-4fe3-5abc-764865ed4042.png" width=50%>
mapboxは、地図を使用したアプリ開発者向けのプラットフォームです。
そのなかでMapbox GL JSは、Web GLを使用して地図を表示するJavaScriptライブラリです。
その他の地図サービス・APIに比べて、カスタマイズ性が高いとされています。

# 環境構築
Vue.jsのプロジェクト作成とMapbox GL JSのインストールを行います。

## Vue CLIでプロジェクト作成
```
vue create mapbox-vue
```
今回はMapbox GL JSの機能を試したいので、
Vue RouterもVuexもなしのシンプルなプロジェクトにしています。

```
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Linter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save, Lint and fix on commit
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
```

[Vue CLI Installation](https://cli.vuejs.org/guide/installation.html)
[Vue CLI Creating a Project](https://cli.vuejs.org/guide/creating-a-project.html#creating-a-project)

## Mapbox GL JSのインストール
1. npmでMapbox GL JSをインストール

    ```
    npm install mapbox-gl @types/mapbox-gl --save
    ```
    ※TypeScriptを使用しているため、typesもインストールしています。

1. ```/public/index.html``` へ以下を追加し、CSSを読み込む

    ```html:/public/index.html（抜粋）
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.4.1/mapbox-gl.css" rel="stylesheet" />
    ```
    [Mapbox GL JS Install](https://www.mapbox.com/install/js/)

## mapboxアカウント作成
Access Tokenを取得するため、
[Create your Mapbox account](https://account.mapbox.com/auth/signup/)からアカウントを作成します。
SignUpするとAccountページにAccess Tokenが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/a5f318a7-a21e-63a7-b5bb-017d5282fd3a.png)


料金体系は、Webの場合50,000回までのマップ読み込みは無料となっています。
詳細は、[Mapbox pricing](https://www.mapbox.com/pricing/)へ。

# マップを表示する
まずは、マップを表示してみましょう。
```/src/components/MyMap.vue``` を追加します。
これからこのファイルを編集して機能を追加していきます。
```Your Access Token``` の箇所は、先ほど取得したAccess Tokenに変更してください。

```vue:/src/App.vue
<template>
  <div id="app">
    <my-map />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import MyMap from './components/MyMap.vue'

@Component({
  components: {
    'my-map': MyMap
  }
})
export default class App extends Vue {}
</script>

<style>
body {
  padding: 0;
  margin: 0;
}
#app {
  height: 100vh;
}
</style>
```

```vue:/src/components/MyMap.vue
<template>
  <div id="map"></div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import mapboxgl, { MapboxOptions, Map } from 'mapbox-gl'

@Component({})
export default class extends Vue {
  map: Map = {} as Map
  option: MapboxOptions = {
    accessToken: 'Your Access Token',
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v8',
    center: [143.767125, 38.681236],
    zoom: 4
  }

  mounted() {
    this.map = new mapboxgl.Map(this.option)
  }
}
</script>

<style scoped>
#map {
  width: 100%;
  height: 100%;
}
</style>
```

すると、このように地図が表示されます！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/89b02614-498d-21fd-6609-39224e05ec8e.png)

## 仕組み
1. `mapbox-gl` を読み込む

    ```javascript:/src/components/MyMap.vue
    import mapboxgl, { MapboxOptions, Map } from 'mapbox-gl'
    ```

1. mapboxのオプションを設定する

    ```javascript:/src/components/MyMap.vue（抜粋）
    option: MapboxOptions = {
      accessToken: 'Your Access Token',
      container: 'map',
      style: 'mapbox://styles/mapbox/streets-v8',
      center: [143.767125, 38.681236],
      zoom: 4
    }
    ```
設定できるオプションの詳細は、[Mapbox GL JS API reference Map](https://docs.mapbox.com/mapbox-gl-js/api/#map) へ。
`accessToken`、`container`は必須になります。
`container`には、マップをバインドするタグの`id`を指定します。

1. マップをバインドするタグを作成し、高さを指定する

    ```vue:/src/App.vue（抜粋）
    <style>
    body {
      padding: 0;
      margin: 0;
    }
    #app {
      height: 100vh;
    }
    </style>
    ```

    ```vue:/src/components/MyMap.vue（抜粋）
    <template>
      <div id="map"></div>
    </template>

    <style scoped>
    #map {
      width: 100%;
      height: 100%;
    }
    </style>
    ```

1. Mapオブジェクトを生成する

    ```javascript:/src/components/MyMap.vue（抜粋）
    mounted() {
      this.map = new mapboxgl.Map(this.option)
    }
    ```
DOM作成後である必要があるので、`mounted`フックで`Map`オブジェクトを生成します。
この`Map`オブジェクトに、マップを操作するファンクションやイベントが定義されています。

# 検索機能を追加する
次に、検索機能を追加してみましょう。
検索機能は、`mapbox-gl-geocoder` プラグインから提供されています。

1. npmで `mapbox-gl-geocoder` をインストール

    ```
    npm install @mapbox/mapbox-gl-geocoder --save
    ```

1. ```/public/index.html``` へ以下を追加し、CSSを読み込む

    ```html:/public/index.html（抜粋）
    <link href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.4.2/mapbox-gl-geocoder.css" rel="stylesheet" />
    ```

    [GitHub mapbox-gl-geocoder](https://github.com/mapbox/mapbox-gl-geocoder)

1. `Map` オブジェクトにコントロールを追加

    ```vue:/src/components/MyMap.vue（抜粋）
      <script lang="ts">
      // 省略
      const MapboxGeocoder = require('@mapbox/mapbox-gl-geocoder')

      @Component({})
      export default class extends Vue {
        // 省略
        mounted() {
          this.map = new mapboxgl.Map(this.option)
          this.map.addControl(
            new MapboxGeocoder({
              accessToken: this.option.accessToken,
              mapboxgl: mapboxgl
            })
          )
        }
      }
      </script>
    ```

すると、このように検索ができるようになります。
![mapbox検索.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/51b6fd28-b386-d06c-cbf1-29a00fb136a9.gif)

## 仕組み
検索時には、[Geocoding API](https://docs.mapbox.com/api/search/#geocoding) が呼ばれています。

```
GET https://api.mapbox.com/geocoding/v5/{endpoint}/{search_text}.json
```

Mapbox GL JSから、このような `Control` などのUIも提供されていますし、APIとしても提供されていますので、UIを自作することもできます。
その他のAPIの詳細は、[mapbox API Documentation](https://docs.mapbox.com/api/) へ。

# 地名を日本語化する
英語のままでは見づらいので、日本語化してみましょう。
日本語化は、`mapbox-gl-language` プラグインから提供されています。

1. npmで`mapbox-gl-language`をインストール

    ```
    npm install @mapbox/mapbox-gl-language --save
    ```

    [GitHub mapbox-gl-language](https://github.com/mapbox/mapbox-gl-language)

1. `Map`オブジェクトにコントロールを追加

    ```vue:/src/components/MyMap.vue（抜粋）
      <script lang="ts">
      // 省略
      const MapboxLanguage = require('@mapbox/mapbox-gl-language')

      @Component({})
      export default class extends Vue {
        // 省略
        mounted() {
          this.map = new mapboxgl.Map(this.option)
          // 省略
          this.map.addControl(
            new MapboxLanguage({
              defaultLanguage: 'ja'
            })
          )
        }
      }
      </script>
    ```

すると、このように国名や地名が日本語になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/d9116596-dbc6-9b5c-d992-4a5bab0375f7.png)

# マップのスタイルを作成する
[Mapbox Studio](https://studio.mapbox.com/)では、Web上で好きなスタイルを作成することができます。

## Mapbox Studio での編集
1. `New style`をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/0d402567-6c64-7d92-85d4-7d041c945209.png)

1. 好きなテンプレートを選択し、`Customize xxx`をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/9f620106-deab-04d1-03b0-a43ed57af43f.png)

1. エディターが表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/4ccd9ad0-b252-0b8b-9c49-9a6873bfed48.png)

ここで様々な編集ができます。
詳しい使い方は、[Mapbox Studio Manual](https://docs.mapbox.com/studio-manual/overview/) へ。

今回は、日本語のSatellite Streetsを作成しました。

## 作成したスタイルの適用
1. シェアアイコンをクリック
![Mapbox Studio share.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/5be35f5a-5cd9-e4ce-ebdb-ff8bc2282bb0.png)

1. `Style URL`をコピー
![Mapbox Studio style.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/035d67e6-a3cb-6b8a-b26d-c7f8f031847a.png)

1. `option` の `style`を変更

    ```vue:/src/components/MyMap.vue（抜粋）
      <script lang="ts">
      // 省略

      @Component({})
      export default class extends Vue {
        // 省略
        option: MapboxOptions = {
          accessToken: 'Your Access Token',
          container: 'map',
          style: 'Copied Style URL',
          center: [143.767125, 38.681236],
          zoom: 4
        }
        // 省略
      }
      </script>
    ```

すると、作成したスタイルが適用されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/e91d41d5-6753-ae7f-e200-efb08da498f1.png)

# 位置情報を表示する
最後に、位置情報を表示してみましょう。
今回は私の好きな美術館を表示してみたいと思います。

1. レイヤーを追加

    ```vue:/src/components/MyMap.vue（抜粋）
    <script lang="ts">
    // 省略
    @Component({})
    export default class extends Vue {
      // 省略
      mounted() {
        // 省略
        this.map.on('load', () => {
          this.map.addLayer({
            id: 'points',
            type: 'symbol',
            source: {
              type: 'geojson',
              data: {
                type: 'FeatureCollection',
                features: [
                  {
                    type: 'Feature',
                    geometry: {
                      type: 'Point',
                      coordinates: [139.775792, 35.715622]
                    },
                    properties: {
                      title: '国立西洋美術館',
                      icon: 'museum'
                    }
                  },
                  {
                    type: 'Feature',
                    geometry: {
                      type: 'Point',
                      coordinates: [139.630669, 35.457194]
                    },
                    properties: {
                      title: '横浜美術館',
                      icon: 'museum'
                    }
                  },
                  {
                    type: 'Feature',
                    geometry: {
                      type: 'Point',
                      coordinates: [139.051066, 35.2454]
                    },
                    properties: {
                      title: '彫刻の森美術館',
                      icon: 'museum'
                    }
                  },
                  {
                    type: 'Feature',
                    geometry: {
                      type: 'Point',
                      coordinates: [139.021225, 35.256709]
                    },
                    properties: {
                      title: 'ポーラ美術館',
                      icon: 'museum'
                    }
                  },
                  {
                    type: 'Feature',
                    geometry: {
                      type: 'Point',
                      coordinates: [139.726423, 35.665322]
                    },
                    properties: {
                      title: '国立新美術館',
                      icon: 'museum'
                    }
                  }
                ]
              }
            },
            layout: {
              'icon-image': ['concat', ['get', 'icon'], '-15'],
              'text-field': ['get', 'title'],
              'text-font': ['ヒラギノ角ゴ Pro W3', 'メイリオ', 'sans-serif'],
              'text-offset': [0, 0.6],
              'text-anchor': 'top'
            }
          })
        })
      }
    }
    </script>
    ```

すると、このようにアイコンと地名が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163867/fc0e7468-0370-9f5f-5ae5-a8cb47f9f7cb.png)

## 仕組み
様々なレイヤーを組み合わせることでマップを表示しています。
Layerには主に `type`、`sorce`、 `layout`、 `paint` が設定できます。
詳しい仕様は、[mapbox Style Specification Layers](https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers) へ。

今回は `座標` を `アイコン表示` するので、
`type` は `symbol` 、`source` は `geojson` にしています。

### GeoJSON とは
地理空間を表現するためのJSONフォーマットです。
詳しい仕様は、[GeoJSON ](https://geojson.org/) へ。

# おわりに
ここまでMapbox GL JSの主要な機能を見てきました。
[mapbox](https://www.mapbox.com/)のSolutionsには、様々なユースケースが紹介されていますので、こちらもぜひ！

業務ではラッパーライブラリを使用していますが、もう少し自由度がほしいと思うこともしばしば・・・。
次は、ここからVue.jsのアプリとしてもっと使いやすくしていきたいと思います:bow_tone1: