Android, iOS, PWAに対応するionicフレームワークで使えるGoogleマッププラグインの紹介

# Ionic framework

Ionic framework(アイオニック・フレームワーク)は、AngularJSベースで動くフレームワークです。日本ではあまり知名度がないのですが、世界的にはVue.jsと並んで有名です。

JSフレームワーク単体として使うこともできますが、Apache Cordova（もしくはIonic Capacitor)と組み合わせて、Android, iOS,ブラウザに対応するクロスプラットフォーム・アプリとしても開発することができます。

今回はこのIonicフレームワークで動作するGoogleマップ・プラグインの紹介です。

Ionicフレームワークの現在のバージョンは4です。

Ionic 1, Ionic 2, Ionic 3, Ionic 4(beta)とそれぞれ違いがありますが、今回は Ionic 4(beta)をメインに取り上げたいと思います。

Ionic4の最大の特徴は、AngularJSをメインに利用していることです。
これにより、AngularJSの知識がある人は、そのまま移行しやすくなっています。

# Ionic Nativeプラグインとは

Ionicフレームワークでは、Apache Cordovaと組み合わせて動作させるとき、ネイティブ機能（ブラウザの領域を超えた機能）を使うことができる[Ionic Native](https://ionicframework.com/docs/native/)という仕組みを用意しています。
これによりブラウザだけでは実現できない機能を簡単に使うことができるようになっています。

[Ionic Native](https://ionicframework.com/docs/native/)の実態は、Apache Cordova向けの様々なプラグインをTypeScriptで使えるようにした**ラッパープラグイン**です。

Ionic Nativeのプラグインを利用する場合は、それと一緒に別のプラグインもインストールします。

例えば私が担当している[@ionic-native/google-maps](https://www.npmjs.com/package/@ionic-native/google-maps)では、一緒に[cordova-plugin-googlemaps](https://www.npmjs.com/package/cordova-plugin-googlemaps)もインストールする必要があります。


# Google Maps JavaScript APIが常に最適解ではない

もしかしたら「`アプリ内でGoogleマップを表示するなら、Google Maps JavaScript API v3を使えばいいじゃないか`」と思う方もいるかも知れません。

はい、その通りです。**でもそれ最速ですか？**

Googleは地図を表示するのに、以下のようなAPIを提供してくれています。

- [Google Maps JavaScript API v3](https://developers.google.com/maps/documentation/javascript/tutorial)
    - 主にブラウザで表示することを想定
    - 多くのブラウザをサポート
    - 機能も充実
    - 2Dマップのみ
    - オフラインのときは使えない
- [Google Maps SDK for Android](https://developers.google.com/maps/documentation/android-sdk/intro)
    - Androidアプリ内で動作
    - 3D地図を表示可能
    - Google Maps JavaScript API v3よりも早い
    - Google Maps JavaScript API v3に比べると、機能数は少ない
    - 地図を表示した箇所はある程度、内部的に地図がキャッシュされる
    - オフラインでも動作可能
- [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk/intro)
    - iOSアプリ内で動作
    - 特徴はGoogle Maps SDK for Androidと同じだが、全ての機能が共通するわけではない
- [Google Static Maps API](https://developers.google.com/maps/documentation/maps-static/intro)
    - URLクエリ文字列で地図を指定する
    - `<img>`を使って表示することを想定したAPI
    - 静的な画像のみ
    - オフラインでは使用できない
- [Google Maps Embed API](https://developers.google.com/maps/documentation/embed/start)
    - iframeを使って表示することを想定
    - 1箇所だけの地図を表示するのには便利
    - あまりカスタマイズはできない
    - オフラインでは使用できない

このようにGoogleはそれぞれの目的に合わせて使えるAPIやSDKを提供してくれています。
例えばAndroidアプリを作るのであれば、`Google Maps JavaScript API v3`よりも、`Google Maps SDK for Android`を使うほうが、制御もしやすく、アプリとの親和性も高いです。

でも`Google Maps SDK for Android`や`Google Maps SDK for iOS`を使うには、__それぞれJavaやSwiftなどの言語を使用して、OSごとにネイティブアプリを作る必要があります__。

**それって大変じゃないですか？**

そこで私が開発している[cordova-plugin-googlemaps](https://github.com/mapsplugin/cordova-plugin-googlemaps)の紹介です。

# Android, iOS, ブラウザに対応するGoogleマップ・プラグイン

## 大まかな特徴
- TypeScriptでコードを書くことができる
- 1つのコードでAndroid, iOS, ブラウザに対応できる
- Androidアプリ内では、Google Maps Android APIを使用
- iOSアプリ内では、Google Maps SDK for iOSを使用
- ブラウザで表示するときは、Google Maps JavaScript API v3を使用します
- Android, iOSでは3Dマップやオフライン地図表示などが可能

----

# デモ

下の画像はAndroid版のスクリーンキャプチャです。

![](https://lh6.googleusercontent.com/pK_koMkReFb0xq_P1urRvWR62ZFdTlip-PrywOcmyM0ZxUGkCLBC2nbzzY01CA0D-EJB6_QLIbKsATCCc4Bwmgzl13XctMrFk0ycL9fBKRzTSR_WJD0m_h3WU5uPB8LUFVKyFNt1-es)

同じコードがブラウザでも動きます。さすがにアニメーション・ズームインはできませんが、とりあえず同じような動作をします。

![browser.gif](https://qiita-image-store.s3.amazonaws.com/0/39529/ccbe46c6-a607-b758-ac86-95e23c2837be.gif)


## デモの解説

上記デモのコードを紹介します。この程度のコードで動きます。

```typescript
import { Component, OnInit } from '@angular/core';
import { Platform } from '@ionic/angular';
import {
  GoogleMaps,
  GoogleMap,
  GoogleMapsEvent,
  Marker,
  GoogleMapsAnimation,
  MyLocation
} from '@ionic-native/google-maps';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  map: GoogleMap;

  // このHomePageクラスが作成されるときに実行される
  constructor(private platform: Platform) {}

  // ngOnInitは、AngularJSの準備が完了したら実行される
  async ngOnInit() {

    // Apache Cordovaから 'deviceready'イベントが発行されるのを待つ
    await this.platform.ready();

    // platform.ready()が完了したら、地図を作成する
    await this.loadMap();
  }

  async loadMap() {

    // Googleマップを作成
    this.map = GoogleMaps.create('map_canvas', {
      camera: {
        target: {
          lat: 43.0741704,
          lng: -89.3809802
        },
        zoom: 18,
        tilt: 30
      }
    });

  }

  // ボタンが押された時の処理
  onButtonClick() {

    // 現在位置を取得
    this.map.getMyLocation().then((location: MyLocation) => {
      // アニメーションで指定の位置にズームイン 
      this.map.animateCamera({
        target: location.latLng,
        zoom: 17,
        tilt: 30
      });
    
      // アニメーションが終了したらマーカーを追加
      let marker: Marker = this.map.addMarkerSync({
        title: '@ionic-native/google-maps plugin!',
        snippet: 'This plugin is awesome!',
        position: location.latLng,
        animation: GoogleMapsAnimation.BOUNCE
      });

      // 情報ウィンドウの表示
      marker.showInfoWindow();

      // もし情報ウィンドウがクリックされたら、アラートを表示
      marker.on(GoogleMapsEvent.MARKER_CLICK).subscribe(() => {
        alert('clicked!');
      })
    })
    .catch(err => {
      // getMyLocationでエラーが発生したら、メッセージを表示
      alert(err.error_message);
    });
  }
}
```

コード中のコメントを読んでいただければ、大体分かるとは思いますが、一つずつ見ていきましょう。

## 初期化のタイミング

Ionicフレームワークは、Apache Cordova上で動いています。
Apache Cordovaは、ブラウザサイドとネイティブサイドの2つがあり、ブラウザからネイティブの機能を呼び出すことができるのが便利なところです。
ただブラウザの初期化されるタイミングと、ネイティブサイドが初期化されるタイミングは異なります。

ブラウザサイドは、乱暴な言葉で言ってしまえば、単なるHTMLです。
もしフレームワークを何も利用しなければ、`window.onload`の時点で初期化完了です。
Ionic 4の場合、AngularJSを利用しているので、AngularJSの準備が完了したことを知らせる`ngOnInit()`を利用します。

ネイティブサイドはブラウザとは別の仕組みで動いているので、初期化に時間がかかります。
どのくらい時間がかかるのかは、インストールしているプラグインの数にもよりますが、大体1〜2秒程度かかります。
このネイティブサイドが準備完了になったことを知らせるのに、`deviceready`というイベントが発行されますが、これを待つのが、`platform.ready()`です。

この両方が準備完了になったら、この`loadMap()`関数を実行することができます。


```typescript

// このHomePageクラスが作成されるときに実行される
constructor(private platform: Platform) {}

// ngOnInitは、AngularJSの準備が完了したら実行される
async ngOnInit() {

  // Apache Cordovaから 'deviceready'イベントが発行されるのを待つ
  await this.platform.ready();

  // platform.ready()が完了したら、地図を作成する
  await this.loadMap();
}
```

## 地図の作成

地図の作成は、この部分で行っています。

`GoogleMaps.create()`の第1引数ではどこに地図を作成するかを指定し、第2引数は初期化オプションです。

`map_canvas`というのは、HTML側にある`<div id="map_canvas"></div>`のIDです。

```typescript
// Googleマップを作成
this.map = GoogleMaps.create('map_canvas', {
  camera: {
    target: {
      lat: 43.0741704,
      lng: -89.3809802
    },
    zoom: 18,
    tilt: 30
 }
});
```

## 位置情報の取得

HTML側のファイルには、このように定義してあり、ボタンがクリックされると`onButtonClick()`が実行されます。

```html
<div id="map_canvas">
  <ion-button (click)="onButtonClick()">Demo</ion-button>
</div>
```

ボタンが押されたら、まず`this.map.getMyLocation()`であなたの位置情報を取得します。

ブラウザ側の実装では`navigation.getCurrentLocation()`、Android/iOSではそれぞれの位置情報を取得するAPIを使って位置を取得します。位置情報を取得するにはパーミッションを取得したり、GPSの有無をチェックしたり、、、、など割と面倒ですが、そうゆうことは全部 `getMyLocation()`メソッドの内部に隠されています。あなたは`getMyLocation()`とすれば、位置情報を取得することができるのです。

```typescript
// ボタンが押された時の処理
onButtonClick() {

  // 現在位置を取得
  this.map.getMyLocation().then((location: MyLocation) => {
    ...
  })
  .catch(err => {
    // getMyLocationでエラーが発生したら、メッセージを表示
    this.showToast(err.error_message);
  });
}
```

## カメラ位置の操作

カメラとは、**地図を見るためのカメラ**です。
この**カメラに映る位置・角度・ズームを調整して、そこに映る地図が表示されている**と思ってください。　


![](https://raw.githubusercontent.com/mapsplugin/cordova-plugin-googlemaps-doc/master/v2.3.0/class/CameraPosition/target.gif)

次のコードは、`target`で指定する位置にアニメーションをしながらカメラを移動します。
カメラの移動が、`zoom=17, tilt=30`になるように自動的に調整してくれます。
`tilt`は、カメラと地図の角度です。

```typescript
// アニメーションで指定の位置にズームイン 
this.map.animateCamera({
  target: location.latLng,
  zoom: 17,
  tilt: 30
});
```

## マーカーの追加と情報ウィンドウの表示

カメラをあなたの現在位置に移動させたら、マーカーを表示させましょう。
このコードは、カメラの移動が終了したら実行されます。

```typescript
// アニメーションが終了したらマーカーを追加
let marker: Marker = this.map.addMarkerSync({
  title: '@ionic-native/google-maps plugin!',
  snippet: 'This plugin is awesome!',
  position: location.latLng,
  animation: GoogleMapsAnimation.BOUNCE
});

// 情報ウィンドウの表示
marker.showInfoWindow();

```

## マーカーがクリックされたときの処理

最後にマーカーがクリックされたときの処理を書きます。

```typescript
marker.on(GoogleMapsEvent.MARKER_CLICK).subscribe(() => {
  alert('clicked!');
});
```

マーカーがクリックされると、`MARKER_CLICK`イベントを発行します。
これをキャッチするには`one()`と`on()`の2つの方法があります。

`one()`は1回のみイベントをキャッチします。

```typescript
marker.one(GoogleMapsEvent.MARKER_CLICK).then(() => {
  alert('1回だけ表示されます');
});
```

`on()`は複数回イベントをキャッチします。

```typescript
marker.on(GoogleMapsEvent.MARKER_CLICK).subscribe(() => {
  alert('複数回表示されます');
});
```

目的に合わせて使い分けましょう。


# `map.animateCamera()`のマジック

ここでJavaScriptに少し詳しい方なら疑問に思うかもしれません。

**「あれっ？`map.animateCamera()`は非同期処理じゃないの？」**

はい。非同期処理です。というか、Apache Cordovaでネイティブサイドのコードを実行するときは、全て非同期処理になります。

```typescript
this.map.animateCamera({
  ...
}).then(() => {
  console.log('アニメーション完了!');
});
console.log('animateCamera()が実行された');

let a = 1;
console.log('a = 1が実行された');
```

例えば上記のコードを実行すれば、期待通りの答えになります。

```
animateCamera()が実行された
a = 1が実行された
アニメーション完了!
```

ではなぜ `animateCamera()`の次に来る`addMarkerSync()`は、`animateCamera()`の実行が完了してから実行されるのでしょう。

それは、**`async`, `await`のようなものを内部的に実装しているから**です。

もう少し詳しく説明すると、この地図プラグインは自前のコマンドキューを持っていて、そこでどれを並列で実行して、どれを同期処理で実行するかを制御しています。

なので可能な限りパフォーマンスが最大になるように設計されています。


----

# 各機能の紹介

ここからはこのプラグインに実装されている様々な機能を紹介します。

---

## HTMLInfoWindow

**「情報ウィンドウの中にHTMLを表示したい」**という場合は、`HTMLInfoWindow`というクラスを使用してください。

というのも、*Google Maps SDK for AndroidとGoogle Maps SDK for iOSでは情報ウィンドウの中にHTMLを表示できない*からです。

なのでHTMLを表示できるスペシャルな情報ウィンドウを作っちゃいました。それがHTMLInfoWindowです。

```TypeScript
let htmlInfo: HTMLInfoWindow = new HtmlInfoWindow();
htmlInfo.setContent('<h3>Hello world!</h3>');
htmlInfo.open(marker);
```
![Screen Shot 2018-09-12 at 11.30.58 AM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/780052c4-272b-cee2-64dc-86f2bf3a58b2.png)

え、改行されるのが嫌ですか？

じゃあ、CSSで指定してください。

```typescript
htmlInfoWindow.setContent('<h3>Hello world!</h3>', {
  'white-space': 'pre'
});
```

![Screen Shot 2018-09-12 at 11.33.09 AM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/d508141b-11ae-20a5-4a97-d096f59e005c.png)

え、なんですか？　こんなのじゃ、つまらない？　もっと面白いサンプルはないのかって？

じゃあ、こんなのはどうでしょう。

```typescript
let htmlInfoWindow = new HtmlInfoWindow();

// flip-flop contents
// https://davidwalsh.name/css-flip
let frame: HTMLElement = document.createElement('div');
frame.innerHTML = `
<div class="flip-container" id="flip-container">
  <div class="flipper">
    <div class="front">
    <h3>Click this photo!</h3>
    <img src="assets/imgs/hearst_castle.jpg">
  </div>
  <div class="back">
    <!-- back content -->
    Hearst Castle above the clouds on top of The Enchanted Hill. William Randolph Hearst started to build a fabulous estate on his ranchland overlooking the village of San Simeon in 1919.
    </div>
  </div>
</div>`;

frame.addEventListener("click", (evt) => {
  let container = document.getElementById('flip-container');
  if (container.className.indexOf(' hover') > -1) {
    container.className = container.className.replace(" hover", "");
  } else {
    container.className += " hover";
  }
});
htmlInfoWindow.setContent(frame, {
  width: "170px"
});


let marker: Marker = this.map.addMarkerSync({
  position: {lat: 35.685208, lng: -121.168225},
  draggable: true,
  disableAutoPan: true
});

marker.on(GoogleMapsEvent.MARKER_CLICK).subscribe(() => {
    htmlInfoWindow.open(marker);
});
marker.trigger(GoogleMapsEvent.MARKER_CLICK);
```
![browser.gif](https://qiita-image-store.s3.amazonaws.com/0/39529/6e0d4d28-3ad8-f2b5-dbc1-fdc03529c0f1.gif)

HTMLInfoWindowはブラウザ上で表示しています。なのでブラウザ上で表示できるものであれば、なんでも表示することが可能です。


----

## Polyline

地図上にポリラインを描画したい場合は、`map.addPolylineSync()`を使います。
`points`プロパティにラインを描画したい位置の配列を渡すだけでOKです。
`clickable = true`にすれば、`POLYLINE_CLICK`イベントも取得できます。

```TypeScript
let AIR_PORTS = [
  HND_AIR_PORT,
  HNL_AIR_PORT,
  SFO_AIR_PORT
];

// 地図にポリラインを追加
let polyline: Polyline = this.map.addPolylineSync({
  points: AIR_PORTS,
  color: '#AA00FF',
  width: 10,
  geodesic: true,
  clickable: true  // clickable = false in default
});

// ポリライン上がクリックされたら、その位置にマーカーを表示する
polyline.on(GoogleMapsEvent.POLYLINE_CLICK).subscribe((params: any) => {
  // params[0]にはクリックされた位置が入っている
  let position: LatLng = <LatLng>params[0];

  // クリックされたら位置にマーカーを追加
  let marker: Marker = this.map.addMarkerSync({
    position: position,
    title: position.toUrlValue(),
    disableAutoPan: true
  });

  // 情報ウィンドウの表示
  marker.showInfoWindow();
});
```

<img src="https://lh3.googleusercontent.com/TS3rEGSAaN7kQ2A5NUyAwFyfJcu45Umdfbx4H2elz1sI58dWV-G-8Mku6IJdUH4e-ZKE8QConQldvo495elbQihSNF4XewJq4oBuPbhD4SJhg57_8hRmOKYREiO3asWEPkZen6m3_z8" width="250">

-----

## Polygon

一定の領域を塗りつぶすには、`map.addPolygonSync()`を使います。
領域の頂点の位置の配列を渡すだけです。


```TypeScript
GORYOKAKU_POINTS: ILatLng[] = [
  {lat: 41.79883, lng: 140.75675},
  {lat: 41.799240000000005, lng: 140.75875000000002},
  {lat: 41.797650000000004, lng: 140.75905},
  {lat: 41.79637, lng: 140.76018000000002},
  {lat: 41.79567, lng: 140.75845},
  {lat: 41.794470000000004, lng: 140.75714000000002},
  {lat: 41.795010000000005, lng: 140.75611},
  {lat: 41.79477000000001, lng: 140.75484},
  {lat: 41.79576, lng: 140.75475},
  {lat: 41.796150000000004, lng: 140.75364000000002},
  {lat: 41.79744, lng: 140.75454000000002},
  {lat: 41.79909000000001, lng: 140.75465}
];

let polygon: Polygon = this.map.addPolygonSync({
  'points': this.GORYOKAKU_POINTS,
  'strokeColor' : '#AA00FF',
  'fillColor' : '#00FFAA',
  'strokeWidth': 10
});
```
![Screen Shot 2018-09-12 at 11.54.56 AM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/b8cb83c4-fc56-2ca9-8f11-31812a8bca63.png)


-----

## Circle

円を描画したい場合は、`map.addCircleSync()`を使います。
円の中心位置と、そこからの半径(m)を指定するだけです。

```TypeScript
let center: ILatLng = {"lat": 32, "lng": -97};
let radius = 300;  // radius (meter)

let circle: Circle = this.map.addCircleSync({
  'center': center,
  'radius': radius,
  'strokeColor' : '#AA00FF',
  'strokeWidth': 5,
  'fillColor' : '#00880055'
});
```
![Screen Shot 2018-09-12 at 12.23.19 PM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/e5c98f23-3d16-8fd6-6eb4-69119ae9f2f4.png)

-----

## GroundOverlay

地図の上の特定の地域にだけ画像を貼り付けたい場合、`map.addGroundOverlaySync()`を使用します。

```typescript
this.groundOverlay = this.map.addGroundOverlaySync({
  'url': 'assets/imgs/newark_nj_1922.jpg',
  'bounds': bounds,
  'opacity': 0.5
});
```
![Screen Shot 2018-09-12 at 12.26.35 PM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/73cdc8e2-85eb-3cc7-e08f-9be74b5329f8.png)

無駄に便利なのが、`bearing`プロパティです。ここに回転角を指定すれば、画像を回転することが可能です。

```typescript
this.groundOverlay = this.map.addGroundOverlaySync({
  'url': 'assets/imgs/newark_nj_1922.jpg',
  'bounds': bounds,
  'opacity': 0.5,
  'bearing': 45
});
```

![Screen Shot 2018-09-12 at 12.28.17 PM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/a77530a9-7f5b-581e-36b1-ceb1e02153a1.png)

実はGoogle Maps JavaScript v3の`GroundOverlay`には、この`bearing`に相当する機能はありません。
実装されてないので、自前で`GroundOverlay`に相当する機能を作っています。


-----

## タイルオーバーレイ

地図そのものを変えたいときは、`map.addTileOverlaySync()`を使います。

```TypeScript
this.map.addTileOverlaySync({
  getTile: (x: number, y: number, zoom: number) => {
    return "http://tile.stamen.com/watercolor/" +
            zoom + "/" + x + "/" + y + ".jpg";
  },

  // draw the debug information on tiles
  debug: false,

  opacity: 1.0
});
```

<img src="https://lh3.googleusercontent.com/2zUYMw-MaRZ9vhmoB7BGyRiViZSpTdDMrVzLLnet61Y2XdFRhN7Su_3GHcmF2azY-MU0FP2snewHPCYRQtjhFfWFOZsd7jPAstlmOVhCd3tJjZhvfbHr_zsl0q4P1vA6mRKp9byI-is" width="250">


-----

## マーカークラスター

地図上に数百箇所もマーカーを表示するのは、ユーザーとして非常に使い勝手が悪いです。
そうゆうときはマーカークラスターをオススメします。
ズームインするときに表示される赤い枠は、`boundsDraw = false`で消すことが可能です。

```TypeScript
let markerCluster: MarkerCluster = this.map.addMarkerClusterSync({
  markers: data,
  boundsDraw: true, // 赤い枠を表示
  icons: [
    {
      min: 3,
      max: 9,
      url: "./assets/markercluster/small.png",
      label: {
        color: "white"
      }
    },
    {
      min: 10,
      url: "./assets/markercluster/large.png",
      label: {
        color: "white"
      }
    }
  ]
});

// マーカーがクリックされたときの処理
markerCluster.on(GoogleMapsEvent.MARKER_CLICK).subscribe((params) => {
  let marker: Marker = params[1];
  marker.setTitle(marker.get("name"));
  marker.setSnippet(marker.get("address"));
  marker.showInfoWindow();
});
```

<img src="https://lh6.googleusercontent.com/KGOJ3Cq9y1yNWS2qr1UtPrFNZEQX2DygmZA4b5IZjTe9aiBcJtvKYzS9pURf_J6oyBG6kluf6ziWzBGnxNgIOq1g6ZCAGlBRXkEVks987a2BVARluCLo8mDe1wAlwG28_qkQvSLSBAI" width="250">

----

## ジオコーディング(1)

場所の名称や住所から緯度経度に変換することを『ジオコーディング』といいます。

```TypeScript
// 住所 -> 緯度経度
this.geocoder.geocode({
  "address": this.search_address
})
.then((results: GeocoderResult[]) => {
  console.log(results);

  return this.map1.addMarker({
    'position': results[0].position,
    'title':  JSON.stringify(results[0].position)
  })
})
.then(...)
```

<img src="https://lh5.googleusercontent.com/YTUppsBk9cU3lDYXEysd_zy_AgFKLwhU5wCC33gadfJAHylfRF4N1qiEHTY_bHha7nytfoAfDCVIAjJp9XLt32PWY1rV6WWyC4QahhgJZITG6DW9fXeeQExhv9GoLtNTRnvMugWMlDU" width="250">

----

## ジオコーディング(2)

このプラグインでは、バッチリクエスト（まとめて処理）もサポートしています。

```TypeScript
this.geocoder.geocode({
  // US Capital cities
  "address": [
    "Montgomery, AL, USA", "Juneau, AK, USA", ...
    "Madison, WI, USA", "Cheyenne, Wyoming, USA"
  ]
})
.then((mvcArray: BaseArrayClass<GeocoderResult[]>) => {

});
```

アメリカの50箇所（50州の州都名）をジオコーディングしている例です。
50箇所のジオコーディングを1.9秒で完了させています。

<img src="https://lh4.googleusercontent.com/eOOuw4NyFXzqxkCOiBzpECRxF5NTcTLCz4wKTRCSztIUt4VI9O6u_4qw2KfKbGnemXBebjwoVp7FxNNPpeV8htopp9v7JPlCwRnCx2_nUJBfvVI-zfU7N1lqKPAvUOYeYcYGB9pYIP0" width="250">

----

## KMLOverlay

データとコードを分離したい。そう思うかもしれません。
そんな場合は、データを`KMLファイル`に保存して、読み込みに`map.addKmlOverlay()`を使用してください。

```typescript
this.map.addKmlOverlay({
  url: "assets/kmloverlay/polygon.kml",
  icon: "green"
}).then((kmlOverlay: KMLOverlay) => {

  this.map.moveCamera(kmlOverlay.getDefaultViewport());

  // クリックされたときの処理。
  // どのオーバーレイがクリックされたのかが含まれる。
  kmlOverlay.on(GoogleMapsEvent.KML_CLICK).subscribe((params: any) => {
    let overlay: Polygon = params[0]; // depends on overlay
    let latLng: ILatLng = params[1];
    console.log(overlay, latLng);
  });

});

```

![kml.gif](https://qiita-image-store.s3.amazonaws.com/0/39529/f666e4b9-dc0b-b078-03e2-d813cc41ab11.gif)

## StreetView

Google StreetViewも表示できます。
地図の作成と同じようなやり方で、`GoogleMaps.createPanorama()`を使います。

```
this.panorama = GoogleMaps.createPanorama('pano_canvas', {
  camera: {
    target: initialPos
  }
});
```

![Screen Shot 2018-09-12 at 12.58.17 PM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/a853052e-17f5-5c91-79b1-8f2a7ffbf286.png)


デモでは、StreetViewの上に小さな地図を重ね合わせて表示しています。
これはこうゆう感じでできます。

```html
<div id="pano_canvas">
  <div id="map_canvas"></div>
</div>
```

```css
#pano_canvas {
  height: 90%;
  position: relative !important;
}

#map_canvas {
  bottom: 5%;
  left: 5%;
  width: 150px;
  height: 150px;
  position: absolute !important;
  z-index: 2;
}
```

```typescript
// パノラマの作成
this.panorama = GoogleMaps.createPanorama('pano_canvas', {
  camera: {
    target: initialPos
  }
});

// 地図を作成
this.map = GoogleMaps.create('map_canvas', {
  camera: {
    target: initialPos,
    zoom: 17
  }
});

// マーカーを作成
this.marker = this.map.addMarkerSync({
  flat: true,
  position: initialPos
});

// パノラマの位置が移動したら、マーカーの位置を移動させる
// panorama.positionプロパティを、marker.positionプロパティに同期させる
this.panorama.bindTo('position', this.marker);

// パノラマの位置が移動したら、地図のカメラ位置を変更する
this.panorama.on(GoogleMapsEvent.PANORAMA_LOCATION_CHANGE).subscribe((params:any[]) => {
  let location: StreetViewLocation = params[0];
  this.map.animateCamera({
    target: location.latLng,
    duration: 1000
  });
});

// パノラマのカメラの向きが変わったら、マーカーを回転させる
this.panorama.on(GoogleMapsEvent.PANORAMA_CAMERA_CHANGE).subscribe((params: any[]) => {
  let camera: StreetViewCameraPosition = params[0];
  this.marker.setRotation(camera.bearing - 180);
});
```

![streetview.gif](https://qiita-image-store.s3.amazonaws.com/0/39529/5f5a7ecc-4f4d-5f16-5adf-1871e4618faa.gif)


----

# 最後に

どうでしょうか？　なんとなく作れそうでしょ？

紹介したデモは、このページで見ることができます。
https://mapsplugin.github.io/ionic-googlemaps-quickdemo-v4/


最後にもう一度いいますが、**同じコードでAndroid, iOS, ブラウザ上のどこでも同じように動作します。**

えっ、地図以外の部分のUIはどうしたらいいのかって？

ここにIonic v4のAPIが定義されてます。サンプルを見ていれば分かると思います。
https://beta.ionicframework.com/docs/api

日本語で質問できる場所がほしいですか？
Ionicユーザー達がここで会話しています。Slackに登録すれば、日本語で質問できます。
https://ionic-jp.connpass.com/

(私にIonicのことはあまり聞かないでください）

ちなみに`@ionic-native/google-maps`は**vue.js上でIonicなしでも動きます（そのはず）**。