Google公式Flutter用Google Mapsプラグインを一通り使ってみた

# はじめに

この記事は[Flutter Advent Calendar 2018](https://qiita.com/advent-calendar/2018/flutter)の6日目分です。

すぐに枠が埋まってしまっていたのでFlutter Advent Calendarに参加する予定はなかったのですが、ふとカレンダーを覗いていみると6日枠が投稿されずに空いていたので急遽この記事を書くことにしました。

Flutterの最新情報に興味がある方はFlutterウィークリーもよかったらご覧ください
https://qiita.com/tags/flutterweekly

# Google公式Flutter用Google Mapsプラグインがついに公開

Flutterユーザーならみなさんご存知かと思いますがFlutter上でGoogle Mapsを使う公式プラグインは存在しませんでした。

ところが先日「[Flutterウィークリー #37](https://qiita.com/aoinakanishi/items/d2d6f6fd21b3ba287715)」でもお伝えしたとおり、Googleから公式のGoogle Mapsプラグインが公開されました。現在はデベロッパープレビューという形で提供されていますが、誰でも利用することが出来ます。

以前に
> FlutterでインラインMapViewを作ってみた(サンプルコード付き)
> https://qiita.com/aoinakanishi/items/35bef7de85fdee46bef9

の記事でOpen Street Mapのデータを使ってMapViewを自作しましたが、公式のGoogle Mapsプラグインがあればもう不要ですね。

## パッケージ
https://pub.dartlang.org/packages/google_maps_flutter

## google_maps_flutterのGithubリポジトリ
https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter

# APIキーの取得

https://cloud.google.com/maps-platform/
からGoogle Maps用のAPIキーを取得します

↑を開いて「使ってみる」をクリック
<img width="883" alt="Screen Shot 2018-12-08 at 15.28.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/46c817e8-3a39-e4cd-9131-468f4e77a852.png">

「Maps」を選んでCONTINUEをクリック
<img width="738" alt="Screen Shot 2018-12-08 at 15.29.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/6ffc9075-a54e-7050-720c-fc02830c466b.png">

既存のプロジェクトを選択するか新たにプロジェクトを追加してNEXTをクリック
<img width="624" alt="Screen Shot 2018-12-08 at 15.30.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/9244c961-181a-de01-9a94-56e5bd78c062.png">

ポチポチクリックしていくと以下のような画面が出てAPIキーの準備が整いました
<img width="637" alt="Screen Shot 2018-12-08 at 15.36.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/49b3057a-df38-2ab8-9e85-3d412426257b.png">

# dependencyにgoogle_maps_flutterを追加

```yaml
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  google_maps_flutter: ^0.0.3+3

dev_dependencies:
  flutter_test:
    sdk: flutter

```

# Android用設定

android/app/src/main/AndroidManifest.xmlを開き以下の通りcom.google.android.geo.API_KEYを追加

APIキーは先に発行したものに置き換えてください

```xml
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="flutter_google_maps"
        android:icon="@mipmap/ic_launcher">
        <meta-data android:name="com.google.android.geo.API_KEY"
            android:value="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789a_b"/>
        <activity
            ...
```

# iOS用設定

ios/Runner/Info.plistを開いて以下を追記します

``` xml
    <key>io.flutter.embedded_views_preview</key>
    <string>YES</string>
```

次にios/Runner/AppDelegate.swiftを開き、「import GoogleMaps」と「GMSServices.provideAPIKeyの行」を追加

~~GoogleのサンプルだとObjective-Cが例として出ていますが、こちらはSwiftになります。~~ Flutter公式プラグインにPR出してmergeされたました:tada:
https://github.com/flutter/plugins/pull/1161

``` swift
import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789a_b")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

```



# 共通部分のFlutterコード

Android, iOSの個別の設定が終わったらFlutterのコードを書きます

## パッケージの宣言

```dart
import 'package:google_maps_flutter/google_maps_flutter.dart';
```

## GoogleMap表示部分

```dart
      GoogleMap(
        onMapCreated: (GoogleMapController controller) {},
      ),
```

## main.dart全部(コピペで動きます)
```dart
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

void main() {
  runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: const Text('Flutter Google Maps')),
      body: MapsDemo(),
    ),
  ));
}

class MapsDemo extends StatefulWidget {
  @override
  State createState() => MapsDemoState();
}

class MapsDemoState extends State<MapsDemo> {

  GoogleMapController mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ここを追加
      body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child: GoogleMap(
          onMapCreated: _onMapCreated,
        ),
      ),
    );
  }

  void _onMapCreated(GoogleMapController controller) {
    setState(() {
      mapController = controller;
    });
  }
}

```

# スクリーンショット

## Android
<img width="483" alt="Screen Shot 2018-12-08 at 16.05.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/6981829d-5179-e974-1744-6a7bca1e8632.png">

## iOS
<img width="487" alt="Screen Shot 2018-12-08 at 16.02.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/1ada0135-edfb-c8c0-743d-bd37126ba54a.png">

Googleの提供するサンプルはここまでですが、プラグインのコードを見るといろいろと機能が実装されていますので触っていきましょう

# 最新バージョンのGoogleMapsプラグインを使う

2018/12/10時点の記事執筆時点ではPubに最新版(0.1.0)のGoogleMapsプラグインが提供されていないため、最新版のプラグインを使用する場合はこちらの作業が必要です。

### Google公式のプラグインをclone
> git clone git@github.com:flutter/plugins.git

### pubspec.yamlに以下を追加
```yaml
dev_dependencies:
  flutter_test:
    sdk: flutter
  google_maps_flutter:
    path: ../plugins/packages/google_maps_flutter/
```

`../plugins`は上でgit cloneしてきたディレクトリ

※`flutter packages upgrade`ではIDE(Android Studio)が読み込んでいるプラグインをプラグインを切り替えることが出来ないので一度プロジェクトを閉じてから開き直す

# Google Mapの動きを制御

GoogleMapのonMapCreatedコールバック時にGoogleMapControllerを使って地図を制御します。

## 表示位置(カメラ位置)を変更

この例ではonMapCreated時にGoogleMapControllerを使ってカメラ位置を移動しています。

アニメーションさせずに移動する場合は`animateCamera`の代わりに`moveCamera`を使用します

```dart
  void _onMapCreated(GoogleMapController controller) {
    setState(() {
      mapController = controller;
      mapController.animateCamera(CameraUpdate.newCameraPosition(
        const CameraPosition(
          target: LatLng(35.6580339,139.7016358),
          zoom: 17.0,
        ),
      ));
    });
  }
```

| | 必須 | 初期値 | |
|:-----------------|:-----------------:|:------------------:|:------------------|
| target           | ✓ |  | LatLngで緯度経度を指定 |
| bearing          |  | 0.0 | 方向 |
| tilt             |  | 0.0 | 角度 |
| zoom             |  | 0.0 | ズームレベル |

<img width="487" alt="Screen Shot 2018-12-10 at 05.16.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/493f04ba-72da-67ea-0d10-5078efcf4f16.png">

## 初期表示位置(カメラ位置)を変更 (バージョン0.1.0以上)
`initialCameraPosition`にCameraPositionを指定

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          )
        ),
```

## 現在地ボタンを表示

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          ),
          myLocationEnabled: true,
        ),
```

<img width="459" alt="Screen Shot 2018-12-10 at 05.16.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/35e41ef2-9627-bf41-82c0-fba9e9628ad9.png">

## 地図タイプを変更

### サテライトマップ

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          ),
          mapType: MapType.satellite
        ),
```

<img width="459" alt="Screen Shot 2018-12-10 at 05.16.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/bfe5c049-1c58-6ad9-f169-81f530e81619.png">

### 地形マップ

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          ),
          mapType: MapType.terrain
        ),
```

<img width="487" alt="Screen Shot 2018-12-10 at 05.16.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/9ec1bfa8-9b90-c5d6-120f-3efa872ad73e.png">

### ハイブリットマップ

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          ),
          mapType: MapType.hybrid
        ),
```

<img width="459" alt="Screen Shot 2018-12-10 at 05.17.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/c5baf633-2c15-2a57-173d-fed9ead57516.png">

### 地図表示なし

独自地図を描画したい場合でGoogleMapsの他のAPIを使用したい場合などに使用します

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          ),
          mapType: MapType.none
        ),
```

<img width="487" alt="Screen Shot 2018-12-10 at 05.17.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/f954e789-da2f-59f5-6820-79b67c91ea85.png">

## 地図のズームレベル（最小と最大）を制御

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          ),
          minMaxZoomPreference: MinMaxZoomPreference(16, 18)
        ),
```

## カメラが移動できる範囲を指定

```dart
        child: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: LatLng(35.6580339,139.7016358),
            zoom: 17.0,
          ),
          cameraTargetBounds: CameraTargetBounds(
            LatLngBounds(
              southwest: LatLng(35.6483486,139.6928292),
              northeast: LatLng(35.6684576,139.7102434),
            )
          )
        ),
```

## その他のパラメーター

GoogleMapクラスの初期化時に指定出来るパラメータ一覧

|  | デフォルト値 | 説明 |
|:-----------------|:-----------------|:------------------|
| onMapCreated |  | Mapが生成された時のコールバック |
| gestureRecognizers |  | ListView上に地図を表示するなど、Map上で使用可能なジェスチャーを制御したい場合に使用 |
| compassEnabled | true | マップ回転時にコンパスを表示 |
| cameraTargetBounds | CameraTargetBounds.unbounded | カメラが移動できる範囲を指定（前述のサンプル参照） |
| mapType | MapType.normal |　地図タイプ（前述のサンプル参照） |
| minMaxZoomPreference | MinMaxZoomPreference.unbounded | ズームレベルの最小、最大値を指定（前述のサンプル参照）|
| rotateGesturesEnabled | true | 地図の回転を許可 |
| scrollGesturesEnabled | true | 地図の移動を許可 |
| zoomGesturesEnabled | true | 地図のズームを許可 |
| tiltGesturesEnabled | true | 地図の傾きを許可 |
| trackCameraPosition | false | カメラ移動イベントをFlutterに通知 |
| myLocationEnabled | false | 現在地表示ボタンの表示 |

## 地図上にマーカーを表示

```dart
  void _onMapCreated(GoogleMapController controller) {
    setState(() {
      mapController = controller;
      var options = MarkerOptions(
        position: LatLng(35.6580339,139.7016358)
      );
      mapController.addMarker(options);
    });
```

<img width="487" alt="Screen Shot 2018-12-10 at 06.16.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/44a3a251-a4f5-103e-a810-0162326de9c4.png">

## マーカーに吹き出し(情報ウィンドウ)にテキスト表示

マーカーをタップすると吹き出しが表示されます

```dart
  void _onMapCreated(GoogleMapController controller) {
    setState(() {
      mapController = controller;
      var options = MarkerOptions(
        position: LatLng(35.6580339,139.7016358),
        infoWindowText: InfoWindowText("タイトル", "説明分等")
      );
      mapController.addMarker(options);
    });
```

<img width="487" alt="Screen Shot 2018-12-10 at 06.20.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/b188cb87-97de-1f58-3ffd-e5fadb87825c.png">

## マーカーの色を変更

```dart
      var options = MarkerOptions(
        position: LatLng(35.6580339,139.7016358),
        icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen)
      );
      mapController.addMarker(options);
```

Hueには0.0から360.0まで指定可能

<img width="487" alt="Screen Shot 2018-12-10 at 06.26.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/f4ff7b4c-ea56-28de-9e70-117552ff314f.png">

定義済みの色は以下の通り

|  | 値 |
|:----|----:|
| BitmapDescriptor.hueRed | 0.0 |
| BitmapDescriptor.hueOrange | 30.0 |
| BitmapDescriptor.hueYellow | 60.0 |
| BitmapDescriptor.hueGreen | 120.0 |
| BitmapDescriptor.hueCyan | 180.0 |
| BitmapDescriptor.hueAzure | 210.0 |
| BitmapDescriptor.hueBlue | 240.0 |
| BitmapDescriptor.hueViolet | 270.0 |
| BitmapDescriptor.hueMagenta | 300.0 |
| BitmapDescriptor.hueRose | 330.0 |

## マーカーの画像を変更

pubspec.yamlにリソースを追加

```yaml
  assets:
    # http://www.iconarchive.com/show/merry-christmas-icons-by-lovuhemant/Santa-icon.html
    - res/images/santa.png
```

main.dart

```dart
      var options = MarkerOptions(
        position: LatLng(35.6580339,139.7016358),
        icon: BitmapDescriptor.fromAsset("res/images/santa.png")
      );
      mapController.addMarker(options);
```

<img width="487" alt="Screen Shot 2018-12-10 at 06.36.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/129712/2600a1bc-9644-8ffd-1777-da58122160a2.png">

## その他のマーカーオプション

|  | 初期値 | 説明 |
|:---|:---|:---|
| alpha | 1.0 | マーカーの透過度 |
| anchor | Offset(0.5, 1.0) | マーカー画像の表示オフセット |
| consumeTapEvents | false | マーカータップイベントを取得（未実装） |
| draggable | false | マーカーの移動を許可 |
| flat | false | フラットに描画（地図の角度を変更した際に挙動が異なる） |
| icon | BitmapDescriptor.defaultMarker | マーカーとして表示する画像 |
| infoWindowAnchor | Offset(0.5, 0.0) | 吹き出しの表示オフセット |
| infoWindowText | InfoWindowText.noText | 吹き出しの表示内容（前述のサンプル参照） |
| position | LatLng(0.0, 0.0) | マーカー表示位置 |
| rotation | 0.0 | マーカーの回転(時計回り) |
| visible | true | 表示 |
| zIndex | 0.0 | マーカーのZ軸を指定（重なる時にどちらを手前に出すか） |

※`consumeTapEvents`については、Issueにも上がっている通り、未実装で現時点では使用することが出来ません
https://github.com/flutter/flutter/issues/25039

# まとめ

`google_maps_flutter`のコードを読みながら一通りの操作をしてみました。地図やマーカーの描画、付随する情報の表示であれば問題なく動作します。

各種イベントを取得して操作をする事ができない。オーバーレイやPolylineの描画も出来ません。スタイルの変更やGeoJSONなど、Google Maps APIを使った少し複雑なことが出来ない。など、デベロッパープレビューとして公開されたばかりのバージョン0.1.0なので出来ないことのほうが多いですが、今後に期待ですね。

## この記事のソースコード

https://github.com/aoinakanishi/flutter-google-maps-example