mapbox-gl.js + Vue.js で OpenStreetMap タイルの地図を表示する


この記事は CivicTechテック好き Advent Calendar 2020 の 8 日目の記事です。

この記事について

この記事は Vue.js と mapbox-gl.js を組み合わせて OpenStreetMap (OSM) タイルの地図を表示する方法についての記事です。

地域課題と結びつくことが多いシビックテックのプロジェクトでは、地図をアプリに組み込む機会が結構あります。
以前 (例えば初期版の紙マップ) は Leaflet も選択肢でしたが、バイナリベクトルタイルの表示や TypeScript との相性の良さなどから、現行版の紙マップ をはじめとして、mapbox-gl.js を使ったプロジェクトも増えています。東京都新型コロナウイルス感染症対策サイトで一時的に表示されていた人口推移(参考値)のマップ(現在は非表示)の実装を通して私も触る機会 があり、使いやすかったため新しいプロジェクトでも採用したりしています。
本記事では mapbox-gl.js を Vue.js のプロジェクトで使う際に、最初に作る最低限の地図の表示までの実装を紹介します。

Vue.js との組合せは VueMapbox など、ライブラリ化されたコンポーネントもありますが、今回は mapbox-gl.js を直接使います。地図を出すコンテナ要素の下に作られる DOM 要素たちのことを特に気にしなければ、それほど相性の悪いライブラリでもないため、素直に組み合わせることができます。

最小限のコードで動く完成品が CodePen に置いてあります。

環境

  • Vue.js 2.6.11
  • mapbox-gl.js 1.13.0

作るもの

タイルマップの標準的な入力処理と表示だけができるマップです。

コンポーネントの実装

実装の方針として、テンプレートは id 付きの div 要素を一つ持ち、 mouted フックの中で mapboxgl.Map オブジェクトの初期化を行います。mapboxgl.Map オブジェクトへのアクセスは頻繁に行いたくなるので、このコンポーネントのデータオブジェクトとして保持しておき、また、マップの初期位置、初期ズームは外から与えたい場合が多いため、コンポーネントのプロパティに持たせます。最後に、mapbox-gl.js では地図を出すコンテナ要素に ID を使うため、1 ページに複数の地図を出したりできるように、この ID もプロパティとして外から与えられるようにしておきます。

これらのことをコードで表現すると以下のようになります。


const mapComponent = {
  template: `<div :id="mapId"/>`,
  props: {
    defaultCenterLat: { type: Number, default: 35.6811574199219 },
    defaultCenterLng: { type: Number, default: 139.76702113084735 },
    defaultZoom: { type: Number, default: 14 },
    mapId: { type: String },
  },
  data() {
    return { map: null }
  },
  mounted() {
    this.createMap()
  },
  methods: {
    createMap() {
      this.map = new mapboxgl.Map({
        container: this.mapId,
        zoom: this.defaultZoom,
        style: {
          version: 8,
          sources: {
            OSM: {
              type: 'raster',
              tiles: [
                'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
                'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png',
              ],
              tileSize: 256,
              attribution : '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
            },
          },
          layers: [{ id: 'OSM', type: 'raster', source: 'OSM' }],
        },
        center: [this.defaultCenterLng, this.defaultCenterLat],
      })
    },
  },
}

呼び出し方について

あとは Vue コンポーネントとして登録し、map-component として map-id 指定と共に呼び出せば完成です。


new Vue({
  el: '#app',
  components: {
    mapComponent,
  }
})
<div id="app">
  <map-component :map-id="'map'"></map-component>
</div>

複数出す場合、例えば 2 枚の地図を出して、1 枚目と 2 枚目で初期位置を変えるなどする場合は、map-id として異なる文字列を与え、 default-center-latdefault-center-lng にそれぞれ緯度、経度を与えます。


<div id="app">
  <map-component :map-id="'map1'"> </map-component>
  <map-component :map-id="'map2'" :default-center-lat="35.688515514961521" :default-center-lng="139.70118255576324"> </map-component>
</div>

スタイルについて

コントロールや帰属表示のスタイリングに、 mapbox-gl.js では専用の CSS を持っています。
https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.csslink タグで読み込むか、mapbox-gl モジュールにバンドルされているものを mapbox-gl/dist/mapbox-gl.css からインポートする必要があります。

また、地図を出すコンテナの大きさが 0 だと、全く地図が描画されないように見えるのですが、JavaScript 側のバグを疑って時間を浪費してしまうことが多かったため、開発中は CSS の ID 指定でコンテナ要素の大きさを指定するなどしています。


#map {
  height: 500px;
  width: 960px;
}

まとめ

Vue.js と mapbox-gl.js を使って OSM タイルの地図を表示する方法の一つを紹介しました。ここから機能拡張していく前提で、mapboxgl.Map オブジェクトをデータオブジェクトに持ち、コンテナの ID と、初期の中心とズームをプロパティとして外部から与えられるだけの、簡素なものとして実装しました。