ライブラリなしでGoogle MapをVue.jsで使う方法

Vue.jsでGoogle Mapを利用する場合にvue2-google-mapsといったライブラリががありますが本文書でライブラリを利用せずにVue.jsでGoogle Mapを利用する方法を解説しています。Vue CLIでVueプロジェクトの作成経験がある人を対象に記述しているのでVueを起動するまでの詳しい説明は行っていません。
Google Mapsライブラリのscriptタグをindex.htmlに追加する方法とscriptタグをJavaScriptを利用して動的に追加する方法で動作確認を行っています。動作確認する中で発生した問題の対応方法についても記述しています。
目次
Googleアカウントの作成
Google Mapを利用するためにはGoogleアカウントが必要となります。普段使っているアカウントを利用したくない人は新規で作成を行ってください。
Googleアカウントを取得するためには本人確認のために電話番号が必須となります。
Google Cloud Platformの登録
Googleアカウント取得後はGoogle Mapを利用するために必要となるAPIキーを取得するためにGoogle Maps Platformにアクセスします。

右上の”始める”ボタンをクリックするとGoogle Cloud Platformの無料トライアルの登録画面が表示されるので必要な情報を入力する必要があります。利用規約の確認と個人情報、クレジットカード情報の入力が必要となります。
ステップ1では利用規約の確認を行います。

”続行”ボタンをクリックするとアカウントの種類(ビジネスor個人)や名前と住所、クレジット情報を入力する必要があります。

上記のページの説明にある通りGoogle Platformの無料トライアルの登録なので90日間で300ドルのクレジットが無料で使えます。無料トライアル期間が終了しても自動的には請求されることはありません。
無料トライアルの設定が完了するとようこそ画面が表示されるので”OK”を押してください。

APIの有効化するためにマップ、ルート、プレイスから使いたいAPIを選択します。今回はGoogle Mapを利用して場所の指定を行うのでプレイスを選択します。

プレイスをチェックしたら”有効にする”ボタンをクリックしてください。

有効にするとAPIキーが表示されるのでコピーして”完了”ボタンを押してください。

Google Maps Platformのダッシュボードが表示されます。各APIのリクエスト回数や請求額が表示されます。

Vue.jsの環境構築
本書ではVue.jsはVue CLIを使って作成します。
% vue create vue-google-map
Default Vue 2を選択します。
? Please pick a preset: (Use arrow keys)
❯ Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
Manually select features
インストールが完了したら、componentsフォルダにGoogleMap.vueファイルを作成します。
GoogleMap.vueファイルにはh1タグを記述します。
<template>
<h1>Google Map</h1>
</template>
App.vueファイルを開いて、Hello Worldコンポーネントを作成したGoogleMapコンポーネントに変更します。
<template>
<div>
<google-map />
</div>
</template>
<script>
import GoogleMap from './components/GoogleMap.vue'
export default {
name: 'App',
components: {
GoogleMap
}
}
</script>
プロジェクトフォルダでnpm run devコマンドを実行して下記の画面が表示されることを確認します。

Vue.jsの環境構築は完了です。
スクリプトタグを利用した表示方法
publicフォルダにあるindex.htmlにscriptタグを追加してGoogle Mapの表示を行います。Google Mapの公式ドキュメントを参考にしながら行っています。
ドキュメントに記載されているscriptタグは下記となります。本文書ではkeyの設定値にはYOUR_API_KEYと記述していきますが、これはGoogle Maps Platformで取得したAPIキーを設定してください。
<script async
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
callback=initMapが設定されているため、Google Mapのロードが完了するとinitMap関数が実行されますが定義していないためエラーが発生します。のちほどinitMap関数を利用しますがここでは削除しています。
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY">
</script>
</body>
</html>

ブラウザで確認すると何も変化はありませんがデベロッパーツールのコンソールでgoogleと打つとscriptタグによって作成されたgoogleオブジェクトを確認することができます。index.htmlにscriptタグを追加することでGoogle Mapが動作していることが確認できます。

Vue.js内でのgoogleオブジェクトの確認
Vue.jsの中でGoogle Mapを扱うためライフサイクルフックmountedの中でgoogleオブジェクトを確認します。
<template>
<h1>Google Map</h1>
</template>
<script>
export default {
mounted(){
console.log(window.google)
}
}
</script>
デベロッパーツールで確認するとGoogle Mapのロードが完了していない時にはundefinedと表示され、ロードが完了している場合はmapsが表示されます。
ロードの完了を確認する処理を追加しておきます。setIntervalを利用して500ms毎にwindow.googleの値をチェック(値があればロード完了と判断)しています。内部でclearIntervalを設定していますが、設定しない場合はロード完了後も500ms毎にコンソールにwindow.googleの値が表示されます。
<template>
<h1>Google Map</h1>
</template>
<script>
export default {
mounted(){
console.log(window.google)
let timer = setInterval(() => {
if(window.google){
clearInterval(timer);
console.log(window.google)
}
},500)
}
}
</script>
setIntervalではなくsetTimeOutを利用してもロードの確認を行えます。setTimeOutの場合は1度だけ指定時間後に実行するのでロードが遅い場合はエラーメッセージを表示させるようにしています。
<template>
<h1>Google Map</h1>
</template>
<script>
export default {
mounted(){
console.log(window.google)
setTimeout(() => {
if(!window.google){
console.log('ロードに時間がかかっています。')
}else{
console.log(window.google)
}
},3000)
}
}
</script>
Googleマップの表示
Googleマップを表示するためのdivを追加します。ref属性をつけることによりVue.jsのrefs機能を使ってVue.jsからこの要素に直接アクセスを行います。
<template>
<div>
<h1>Google Map</h1>
<div ref="map"></div>
</div>
</template>

Google Mapのドキュメントに記載されているものと同じ位置を指定しているためオーストラリアのシドニー付近の地図が表示されます。
<template>
<div>
<h1>Google Map</h1>
<div ref="map" style="height:500px;width:800px;"></div>
</div>
</template>
<script>
export default {
data(){
return {
map:'',
}
},
mounted(){
let timer = setInterval(() => {
if(window.google){
clearInterval(timer);
this.map = new window.google.maps.Map(this.$refs.map, {
center: {lat: -34.397, lng: 150.644},
zoom: 8
});
}
},500)
}
}
</script>

Googleマップにマーカーを表示
マップで指定した位置にマーカーを表示します。位置はデータプロパティのmyLatLngに保存しています。window.google.maps.Markerの引数のオブジェクトに位置と表示するmapを指定しています。
<template>
<div>
<h1>Google Map</h1>
<div ref="map" style="height:500px;width:800px;"></div>
</div>
</template>
<script>
export default {
data(){
return {
map:'',
myLatLng:{lat: -34.397, lng: 150.644},
}
},
mounted(){
let timer = setInterval(() => {
if(window.google){
clearInterval(timer);
this.map = new window.google.maps.Map(this.$refs.map, {
center: this.myLatLng,
zoom: 8
});
new window.google.maps.Marker({position:this.myLatLng,map:this.map})
}
},500)
}
}
</script>
ブラウザで確認すると地図上にマーカーを確認することができます。

scriptタグを動的に作成する方法
先ほどの設定ではscriptタグをindex.htmlに貼り付けていましたが、index.htmlに貼り付けるということはGoogleMapが利用されていないページにアクセスしてもGoogle Mapのライブラリをダウンロードすることになります。マップを表示するコンポーネントのみGoogle Mapのライブラリをダウンロードできるようにページに動的にscriptタブを追加する方法を確認します。

先ほどindex.htmlに追加したGoogle mapのscriptタグを削除してから行います。
document.createElementでscriptタグの要素を作成し、document.head.appendChildで作成したscriptタグをheadタグの中に追加してします。残りのコードの変更はありません。
<template>
<div>
<h1>Google Map</h1>
<div ref="map" style="height:500px;width:800px;"></div>
</div>
</template>
<script>
export default {
data(){
return {
map:'',
myLatLng:{lat: -34.397, lng: 150.644},
}
},
mounted(){
let script = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY';
script.async = true;
document.head.appendChild(script);
let timer = setInterval(() => {
if(window.google){
clearInterval(timer);
this.map = new window.google.maps.Map(this.$refs.map, {
center: this.myLatLng,
zoom: 8
});
new window.google.maps.Marker({position:this.myLatLng,map:this.map})
}
},500)
}
}
</script>
ブラウザをリロードすると先ほどと同様にブラウザ上にマップが表示されていることを確認することができます。

callbackのinitMapを利用する方法
scriptタグの後ろについていたcallback=initMapを利用してマップを表示することができるか確認を行います。
initMapを実行するためにコードの中では、windowsをつけてwindow.initMapとする必要があります。Google Mapのライブラリのロードが完了してから実行されるのでsetIntervalを使っていません。
mounted(){
let script = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap';
script.async = true;
document.head.appendChild(script);
window.initMap = () => {
this.map = new window.google.maps.Map(this.$refs.map, {
center: this.myLatLng,
zoom: 8
});
new window.google.maps.Marker({position:this.myLatLng,map:this.map})
}
}
複数のコンポーネントを同時に表示
scriptタグを動的に作成する方法ではコンポーネント毎にscriptタグを作成しているので複数のコンポーネントを同時に表示しようとするとマップを表示することが可能ですが、コンソールに下記のメッセージが表示されます。1つのページに複数回Google Maps JavaScript APIが含まれているというエラーです。
You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors

デベロッパーツールから確認するとわかりますが、2つのGoogleMapコンポーネントを1つのページに2つ使っている場合は、headタグの中に2つのscriptタグが挿入されていることが確認できます。

この問題を解決するためには、複数回scriptタグが登録されない仕組みを組み込む必要があります。
下記に問題に対応したコードを記述していますが、複数のscriptタグを追加しないようにwindowオブジェクトにmapLoadStartedという変数を設定して1つのコンポーネントのみでscriptタグ追加のコードを実行するように制御しています。mapLoadStartedはGoogle Mapライブラリのロードを開始したよという意味です。
mounted(){
if(!window.mapLoadStarted){
window.mapLoadStarted = true;
let script = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap';
script.async = true;
document.head.appendChild(script);
}
window.initMap = () => {
window.mapLoaded = true;
}
let timer = setInterval(() => {
if(window.mapLoaded){
clearInterval(timer);
this.map = new window.google.maps.Map(this.$refs.map, {
center: this.myLatLng,
zoom: 4
});
new window.google.maps.Marker({position:this.myLatLng,map:this.map})
}
},500)
},
またwindowオブジェクトにmapLoadedという変数を設定して、Google Mapライブラリのロードが完了したら実行されるinitMap関数を使ってmapLoadedの値をtrueにしています。trueになっている場合はロードが完了しているのでマップの描写のコードを実行しています。
windowオブジェクトにmapLoadedを加えることで”You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors”は解消しました。Vue Routerを利用してページを移動する場合にmapLoadedを使っていない場合はGoogleMapコンポーネントを利用しているページに移動する度にscriptタグが追加されエラーが発生しましたが設定後はエラーは解消されました。
propsを使ってマップの位置を変更
GoogleMapコンポーネントにpropsを追加し、表示するマップの場所を変更できるように更新します。またzoomで表示する地図の拡大・縮小率も変更できるようにします。
propsを使って親コンポーネントからmyLatLngとzoomの値を受け取ります。
<script>
export default {
props:{
myLatLng:{
type:Object,
required:true,
},
zoom:{
type:Number,
required:true,
}
},
data(){
return {
map:'',
}
},
mounted(){
let script = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY';
script.async = true;
document.head.appendChild(script);
let timer = setInterval(() => {
if(window.google){
clearInterval(timer);
this.map = new window.google.maps.Map(this.$refs.map, {
center: this.myLatLng,
zoom: this.zoom
});
new window.google.maps.Marker({position:this.myLatLng,map:this.map})
}
},500)
}
}
</script>
GoogleMapコンポーネントをimportしている親コンポーネントのApp.vueではpropsで経度と緯度とzoomの値を渡します。
<template>
<div>
<google-map :myLatLng="{lat: -25.344, lng: 131.036}" :zoom="4" />
</div>
</template>
先ほどとは異なる場所を指定したのでオーストラリアのウルルを中心としたマップが表示されます。

他にもGoogle Mapにはオプションが存在するのでここまでの基礎を理解することで使いこなすことができます。