Vue.jsを使いつつ、OpenStreetMapを表示する


iOS専門で色々やってたはずが、万屋になってきた私です。
おはこんばんちわ。

直近で、Webサイト上で地図を表示して、選択したポイントの緯度経度を取得したいことがあったのですが、G【自主規制】を使うにも金がないので、OpenStreetMapを試しに使ってみることにした次第です。

OpenStreetMapとは

OpenStreetMapとは、自由に利用できて、編集機能のある世界地図を作る共同プロジェクトです。
イメージ的には、Wikipediaの地図版がOpenStreetMapです。(以下、OSMと略)
プロジェクト自体は、英国で2004年から始まり、日本では2008年から活動が始まっているようです。
現状、日本国内での普及はまだまだではありますが、世界的にみると発症のヨーロッパエリアに限って言えば、商用地図にも負けないほど内容が充実しているらしいです。
日本エリアの地図ですが、GoogleMapなどの商用地図に比べるとポイントデータなどがまだまだ不足していたりもしますが、ただ単に地図を表示したかったり、今回のように緯度経度を選択して取得する程度であれば十分なデータがすでにあるので、普通にWeb上で使いたい程度であれば大きな問題にはならないと思います。

OpenLayresとは

今回、OSMをWebページ上で表示する際、「OpenLayres」を利用しました。
ブラウザ上で地図データを表示するJavaScriptベースで組まれたオープンソースライブラリです。
イメージ的には、Google MapやBing Mapsのような操作感を持った地図アプリケーションを構築するためのライブラリです。

Vue.jsを使いつつ、Mapを表示していく

まず最初にnpmモジュールの「ol」をインストールします。
この際気をつけないといけない点ですが、「OpenLayres」というモジュールも存在しますが、こちらは古いモジュールで、非推奨になっています。「ol」のモジュールを使いましょう。

Install

プロジェクトディレクトリで、以下コマンドを実行しましょう。

npm install ol

MapComponent

以下が今回のOSM表示のComponentのソースです。

Map.vue
<template>
    <div id='OSMCanvas'>
    </div>
</template>
<script>
import 'ol/ol.css'
import {toLonLat , transform} from 'ol/proj'
import Map from 'ol/Map'
import View from 'ol/View'
import TitleLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'

export default {
    name: 'mapview',
    components: {},
    data: function () {
        return {
            mapview: null
        }
    },
    methods: {
        initialize () {
            this.createMap()
            this.mapview.on("click", ev => {
                var lonlat = transform(ev.coordinate, 'EPSG:3857', 'EPSG:4326')
                this.selectLatLong = lonlat
                this.$parent.selectLonLat = lonlat
                this.$emit('panretMessage')
            })
        },
        createMap () {
            this.mapview = new Map ({
                target: 'OSMCanvas',
                layers: [
                    new TitleLayer({
                        source: new OSM()
                    })
                ],
                view: new View({
                    center:[0,0],
                    zoom:0
                })
            })
        }
    }
}

</script>

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

HTML部分

Map.vue
<template>
    <div id='OSMCanvas'/>
</template>

HTML部分では、OSMCanvasというID名を持つdivを用意するだけです。
このdivに対して、OSMの地図を展開します。

CSS部分

Map.vue
<style scoped>
#OSMCanvas {
    width: 100%;
    height: 100%;
}
</style>

Map.vueは別のコンポーネント内で利用する前提で考えていたので、OSMCanvasの親要素の縦横100%になるように指定しているだけです。

Script部分

Map.vue
<script>
//olモジュールの読み込み
import 'ol/ol.css'
import {transform} from 'ol/proj'
import Map from 'ol/Map'
import View from 'ol/View'
import TitleLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'

export default {
    name: 'mapview',
    components: {},
    data: function () {
        return {
            mapview: null
        }
    },
    methods: {
        //親コンポーネントから実行する初期化メソッド
        initialize () {
            //OpenLayersを使いOSMを生成するメソッドを呼び出し
            this.createMap()
            //地図を一回クリックしたときに発火するイベントの設定
            this.mapview.on("click", ev => {
                var lonlat = transform(ev.coordinate, 'EPSG:3857', 'EPSG:4326')
                this.$parent.selectLonLat = lonlat
                this.$emit('mapClick')
            })
        },
        //Mapを生成する
        createMap () {
            this.mapview = new Map ({
                target: 'OSMCanvas',
                layers: [
                    new TitleLayer({
                        source: new OSM()
                    })
                ],
                view: new View({
                    center:[0,0],
                    zoom:0
                })
            })
        }
    }
}

</script>

デフォルトでは、座標系としてEPSG:3857が使用されています。
このEPSG:3857とはなにか?EPSGというのが、GIS(地理情報システム)の投影方法のコードを指します。
その3857番で指定されているのが「球面メルカトル図法」という投影方法を指し、Google Mapなどで広く使われている
投影方法とのことです。(3857が定義される前は非公式ながらもEPSG:900913として使われてたらしいです。)
詳しくなく、調べつつ補った部分なのでマサカリ飛ばしてください。
EPSG:3857で取得した座標は、X,Y座標をメートルで指定されているので、既存のポイントデータなどで広く使われている緯度経度を利用することは出来ません。
そのため、クリックイベントの引数に含まれているX,Y座標を緯度経度に変換する必要があります。
緯度経度のEPSGコードは、EPSG:4326と定義されているので、OpenLayersに用意されている「transform」メソッドを利用、変換し緯度経度を取得出来ます。
変換した緯度経度を親コンポーネントに対して渡し、あとは親コンポーネントの指定したメソッド内で取得した緯度経度を元に処理を行うことが出来ます。

親コンポーネント

Base.vue
<template>
    <div id='BaseView'>
        <MapView v-on:mapClick="getLonLat" ref="map"/>
    </div>
</template>
<script>
import MapView from './MapView.vue'


export default {
    name: 'mapview',
    data: function () {
        return {
            selectLonLat: null
        }
    },
    components: {
        MapView,
    },
    methods: {
        getLonLat() {
            console.log(this.selectLonLat)
        }
    },
    mounted() {
        this.$refs.map.initialize()
    }
}
</script>

<style scoped>

#BaseView {
    width: 100vw;
    height: 100vh;
}
</style>

親コンポーネントは上記の通りです。
当初、子のMap.vueのmountedで、「createMap」を呼びMapを生成していました。表示自体は問題なく出来ましたが、親のDOM要素のサイズが確定していないためか、クリックした地図の位置と大きくずれた座標が返却されてしまいました。
そのため、親要素のmountedのタイミングで、子のMap.vueのinitializeメソッドをRequestすることで、取得座標のズレを解消しています。

おわりに

まず表示するまでの簡単なメモ書きにはなります。まだまだVue.jsもOSMも未経験分野なので、落とし穴が多く点在しているとは思いますが、ちまちま解消していけたらと思っております。
知識不足の中挑戦しておりますので、指摘や間違い等ありましたらコメントいただけますと幸いです。
どうぞよろしくお願いいたします。