Cordova アプリでサクサク動く Google Map を実現する「cordova-plugin-googlemaps」

2018-03-22 追記:本記事は v1.4.1 時点の内容を取り扱っている。現時点で v2.2.8 がリリースされており、API にもいくつか違いが出てきているため、情報の取り扱いには注意されたし。

cordova-plugin-googlemaps というプラグインを使うと、Cordova アプリ上に Google マップを表示でき、柔軟にカスタマイズすることができる。

API キーの取得などでちょっとつまづいたので丁寧に紹介。

今回も iOS 向けのサンプルアプリを以下に実装したので、feat/googleMaps ブランチのソースを見てみてもらいたい。実際に動作させる際には、後述の API キーの設定が必要になる。

Google Maps API キーを取得する

まずは、iOS アプリから Google Maps にアクセスする許可をもらうため、API キーというものを Google で発行する。

上のページの「キーを取得する」ボタンから取得できる。細かな取得方法などは以下のサイトなどを参考のこと。

API キーを取得する際は、「キーの制限」で「iOS アプリ」を選択し、「リクエストを受けるアプリのバンドル ID」に config.xml に記載のアプリ識別子を設定する必要がある。<widget id="【ココがアプリ識別子】" 部分と一致させること。

登録できると、以下の URL で登録した API キーの参照・編集ができるはずだ。生成されたランダムな文字列が API キーになるので、これを控えておく。

プラグインのインストール

先に API キーの取得を行ったのには理由があって、プラグインをインストールする際に API キーを合わせて指定する必要があったためだ (後からでも良いのだが、合わせてやるようにしておくと間違いがない)。先程取得した API キーを挿入して、以下のようなコマンドでインストールできる。

$ cordova plugin add cordova-plugin-googlemaps --variable API_KEY_FOR_IOS="【ココに API キー】"

こうすると、config.xml に API キー情報が書き込まれる。コレが誤っていたり、API キー側に設定したアプリのバンドル識別子と config.xml の記載が食い違ったりしていると、マップが正しく表示されないので注意。

API キーの取り扱いについては、むやみに config.xml に API キーを書いたままコードを公開したりしないよう注意。と言っても突然課金が始まる、といったことはないが、API のリクエスト制限に達するとしばらく使えなくなったりするので、悪用されないようには注意したい。

サンプルアプリを動作させる場合は

拙作のサンプルアプリを動作させてみたい場合は、API キーにサンプルアプリのアプリ識別子を登録しておき、config.xml の「Your API Key Here」部分に API キーを追記したら、cordova prepare コマンドで config.xml を基にプラグイン・プラットフォーム情報を復元すれば良い。

プラグインのバージョンについて

現時点で、GoogleMaps プラグインは v1.4.1 がインストールされるはずだ。v2.0 Beta も存在していて、v1 系は2017年中に旧バージョンに落とされるようだが、現時点ではギリギリ v1 系が主流なので、v1.4.1 を使用することにする。

マップを表示するには

まずはとにかくマップを表示してみようと思う。まずは HTML と CSS で、ページ中にマップを表示する領域を作る。

<div id="map" style="width: 100%; height: 500px;"></div>

CSS は別途 CSS ファイルで定義しても問題ない。要素を特定するために id 属性を振っておく。

そして JavaScript 側で以下のように実装し、ページ読み込み時にマップを初期表示させる。

// マップオブジェクトを控えておく変数
var map = null;

document.addEventListener('deviceready', function() {
  // 対象の DOM 要素に Google マップを配置する
  var mapElement = document.getElementById('map');
  // マップの初期位置を表示する (座標は日本の中心あたりを適当に)
  map = plugin.google.maps.Map.getMap(mapElement, {
    camera: {
      latLng: {
        lat: 38.2586,
        lng: 137.6850
      },
      zoom: 4
    }
  });
  
  // マップが初期表示できる状態になったら何かする場合はこのように設定する
  map.addEventListener(plugin.google.maps.event.MAP_READY, function() {
    // ココに処理…
  });
}, false);

camera オプションの latlng で座標を指定する。いつも経度と緯度がごっちゃになるので整理。

こんな感じ。

コレでページを初期表示した時に日本列島を表示できているはずだ。タップやスライド、ピンチ操作などができると思う。

マップが表示される仕組み

このマップオブジェクトは、実際はブラウザがレンダリングしているものではなく、ネイティブで動作させているものを HTML 上に透過表示させているに過ぎないのだ。動作がサクサクしているのはネイティブで動作しているから。

そのため、マップを表示している領域上に、HTML で独自のメニューを置いたりもできる。div#map 要素内に Form 部品を配置し、スタイリングしてやれば良い。

指定座標にアニメーション移動してマーカーを打ってみる

では、先程のマップの初期表示が終わったところで、指定の座標値にアニメーションしながら移動し、マーカーを打ってみたいと思う。

// 変数「map」は、先程紹介した初期表示処理 getMap() ができているテイ
var map;
// マップ上に打ったマーカーを控えておくための変数
var markerCache;

// マップが初期表示できる状態になったら処理を開始する
map.addEventListener(plugin.google.maps.event.MAP_READY, function() {
  // 指定座標に向けてアニメーション移動する
  map.animateCamera({
    target: {
      lat: 35.658581,
      lng: 139.745433
    },
    zoom: 17,
    tilt: 60,
    bearing: 140,
    duration: 5000
  }, function() {
    // アニメーション後のコールバック関数
    
    // ズームが終わったらマーカーを付ける
    map.addMarker({
      // アニメーション移動で指定した座標と同じ座標
      position: {
        lat: 35.658581,
        lng: 139.745433
      },
      // マーカーをクリックした時に表示するインフォメーション
      title: 'Welecome to\nCordova GoogleMaps plugin',
      snippet: 'This plugin is awesome!',
      animation: plugin.google.maps.Animation.BOUNCE
    }, function(marker) {
      // マーカーを付けた後のコールバック関数
      
      // 後でマーカーを削除するため、マーカーオブジェクトを退避する
      markerCache = marker;
      // インフォウィンドウを表示する
      marker.showInfoWindow();
      // マーカーのインフォメーションウィンドウがクリックされた時のイベントを設定する
      marker.on(plugin.google.maps.event.INFO_CLICK, function() {
        alert('Hello world!');
      });
    });
  });
});

コレで、マップが初期表示された後に、東京タワーめがけてカメラがズームしていって、マーカーが地図上に打たれるようになった。少々コードが長くなった上にコールバック関数が多く、この調子だと「コールバック地獄」が目に見えているのが分かるかと思う。実際は適宜関数を外出しして管理してほしい。

マーカーをクリックするとインフォメーションのフキダシウィンドウが出るのだが、ココに改行 ¥n を含められるのがこのプラグインの強みらしい。今回は指定の座標地点にマーカーを置いたが、他にもマップを円で囲んだり、任意の図形を描いたりと色々なことができる。

一度打ったマーカーは、marker.remove() を呼ぶことで削除できるのだが、全く別の処理でマーカーを消す場合は、コールバック関数で受け取った marker を一旦別の場所に保持しておく必要がある。というワケでグローバル付近に markerCache という変数を作っておいた次第。マーカーを含めた全てのオブジェクトをまっさらに削除するのであれば、map.clear() というメソッドがある。

任意の住所文字列を基に地図検索 (ジオコーディング) をする

ユーザが入力した住所や地名などを基に地図検索することをジオコーディングというそうなのだが、これをやってみようと思う。

まずはユーザに向けて検索窓を提供する。

<!-- マップを表示する領域 -->
<div id="map"></div>

<!-- 検索窓 -->
<p>
  <input type="text" id="address" value="" placeholder="例 : 東京スカイツリー">
  <input type="button" id="search" value="住所検索">
</p>

次に、「住所検索」ボタンが押された時に、#address の値を拾ってジオコーディングを行う処理を書く。

// 「住所検索」ボタンが押された時の処理
document.getElementById('search').addEventListener('click', function() {
  // ユーザ入力値を取得する (確実に文字列化するため空文字を結合)
  var addressValue = document.getElementById('address').value + '';
  // 入力チェック
  if(!addressValue) {
    alert('検索文字列を入力してください');
    return;
  }
    
  // ジオコーディングを行う
  plugin.google.maps.Geocoder.geocode({
      address: addressValue
  }, function(results) {
    // ジオコーディング語のコールバック関数
    
    // 検索結果がない場合は終了
    if(!results.length) {
      alert('指定の情報が見つかりませんでした');
      return;
    }
    
    // 検索結果1件目の位置に移動する
    var resultPosition = results[0].position;
    // 検索した位置にアニメーション移動する
    map.animateCamera({
      target: resultPosition,
      zoom: 18,
      duration: 2000
    }, function() {
      // アニメーション移動後のコールバック関数
      // マーカーを打つ
      map.addMarker({
        position: resultPosition,
        title: addressValue
      }, function(marker) {
        // マーカーを打った後のコールバック関数
        // マーカーオブジェクトを退避する
        markerCache = marker;
        // インフォウィンドウを表示する
        marker.showInfoWindow();
      });
    });
  });
});

見事なコールバック地獄になっているので、実際は適宜処理を分割したい。

ひとまず、ジオコーディングを行う最小構成は

plugin.google.maps.Geocoder.geocode({
  address: '検索する住所情報'
}, function(results) {
  // 検索結果1件目の位置を取得する
  var resultPosition = results[0].position;
});

この形になる。

あとはココから、検索した住所に対して移動するだとか、マーカーを打つだとかすれば良い。

ステータスバープラグインとの相性について

この GoogleMaps プラグインだが、cordova-plugin-statusbar プラグインとの相性が悪い。cordova-plugin-statusbar プラグインによってステータスバーを表示するよう設定していると、その高さ分だけ Google マップが表示領域の上側にズレて表示されてしまう。

GitHub にも Issue は上がっており、解消方法はいくつかあるのだが、手っ取り早いのは div#mappadding-top:20px; を付与するという方法。マップの表示領域の上側に、ステータスバーの高さ 20px 分の余白を予め開けておくというワケだ。


他にも色々な API が用意されていてココでは紹介しきれないので、公式の README や、紹介記事を見てみてほしい。

手軽に快適な動作の Google マップを実現できるのでオススメ。