Maps SDK for Androidチュートリアル

Androidで地図を表示する場合、第一の選択肢となる [Maps SDK for Android](https://developers.google.com/maps/documentation/android-sdk/intro) の使い方についてまとめてみました。

# Maps SDK for Androidとは

Google製のマップを表示するAndroid用のSDKです。地図の表示だけならモバイルは無制限に利用できます。iOSやブラウザ用もあります。

# 準備

API Keyを取得する必要があるので、[Maps SDK for Android - Get an API key](https://developers.google.com/maps/documentation/android-sdk/get-api-key) を参照してAPIキーを作成してください。

1. GCPコンソールにいく
2. gcpプロジェクト作成
3. 認証情報に行き、API key生成
4. API keyの制限

API keyの制限で、特定のパッケージ名のアプリに限定するためには、証明書に合わせたSHA-1のフィンガープリントを作成する必要があります。

AndroidStudioの新規プロジェクト作成で、「Google Maps Activity」を選択し作成しましょう。選択しなくてできますが、その場合SDKの指定や、Manifestなどの書き換えが必要になります。

`google_maps_api.xml` というファイルが作成されているので、APIキーを指定します。

```xml:google_maps_api.xml
<resources>
　　<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">YOUR_KEY_HERE</string>
</resources>
```

アプリをビルドするとマップが表示されます。「Google Maps Activity」の自動生成だと、シドニーが指定されています。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96747/426ee2b3-4e74-e7c7-189f-86bbd4f82968.png">

# 地図を表示する

地図を表示する方法を具体的に見ていきましょう。（「Google Maps Activity」の自動生成だとコードはすでに作成されています。）
Mapを表示する際に、 `MapFragment` を使う方法と、 `MapView` を使い方法があります。
ここでは前者の方法を見ます。

地図を表示したいactivityのレイアウトに、 以下のFragmentを記載します。
`android:name="com.google.android.gms.maps.MapFragment"` を指定するとActivityに自動的に`AreaMapFragment` が適用されます。(ActivityのコードからMapFragmentを追加することもできます)

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:name="com.google.android.gms.maps.MapFragment"
    android:id="@+id/map"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
```

次に、表示するActivityで `OnMapReadyCallback` インターフェースを実装します。

```kotlin:MainActivity.kt
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var mMap: GoogleMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)

        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }

    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
    }
}
```

`supportFragmentManager.findFragmentById(R.id.map)` で、xmlに記載したMapFragmentのidを指定します。
`getMapAsync()` でコールバックがセットされます。

mapの準備ができたら、 `onMapReady` が呼ばれます。以降、マーカーを設置したり、マップを動かしたりすることができるようになります。

# スタイルを変更する

マップの全体のデザインを変更したいときは、簡単に指定することができます。[Maps Platform Styling Wizard](https://developers.google.com/maps/documentation/android-sdk/styling)という、スタイルを作成するツールが用意されているので、容易に自分好みのスタイルを作ることができます。
(しかもiOS、ブラウザでも共通のスタイルを利用することができます)

ツールで地図をカスタマイズすると、jsonが生成されるので、リソースに `raw` フォルダを作成し追加します。

`onMapReady` でstyleを先ほどのjsonファイルを指定すると、マップに反映されます。

```kotlin:MainActivity.kt
override fun onMapReady(googleMap: GoogleMap) {

        // style
        val mapOptions = MapStyleOptions.loadRawResourceStyle(this, R.raw.style_map)
        googleMap.setMapStyle(mapOptions)
        
    }
```

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96747/32fe6bd5-387b-e1fe-9c09-e47c5ae988d0.png">

# マーカーを設置する

マーカーの設置は、緯度経度やタイトル、タップしたときの情報などを指定してセットすることができます。

```kotlin:MainActivity.kt
val skytree = LatLng(35.710063, 139.8107)

        val markerOption = MarkerOptions()
            .position(skytree)
            .anchor(0.5f, 0.5f)  // マーカーの位置をずらせます
            .title("skytree marker")  // iconをタップすると表示されます
            .snippet("The tallest tower in Japan.") // iconをタップすると表示されます
            .icon(BitmapDescriptorFactory.fromResource(R.drawable.skytree))
        mMap.addMarker(markerOption)

        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(skytree, 15f))
```

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96747/10868673-0a57-37fe-6b35-f52da8c0b821.png">

# 円を表示する

円を描画することもできます。

```kotlin:MainActivity.kt
mMap.addCircle(
        CircleOptions()
            .center(skytree)  // 中心の緯度経度
            .radius(500.0)  // 円の半径
            .strokeColor(0x500000FF)  // 境界線の色
            .strokeWidth(5f)  // 境界線の太さ
            .fillColor(0x0F0000FF)  // 円内部の色
    )
```

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96747/f18b4ca8-15f2-aa7f-2814-0352d3e837c0.png">

# カメラを動かす

パッと切り替える方法と、アニメーションしながら切り替える方法があります。

```kotlin:MainActivity.kt
move_button.setOnClickListener {
    mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(tokyoTower, 15f))
}
```

![move_to_tokyo_tower.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96747/de426338-fde3-67d9-4f59-866ec22f9a37.gif)


```kotlin:MainActivity.kt
animate_button.setOnClickListener {
    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(tokyoTower, 15f))
}
```

![animate_to_tokyo_tower.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96747/b657579e-2653-6d87-a7af-ba14b62eb3e2.gif)


# マーカーが収まるように表示する

複数のマーカーが地図に収まるように表示したい時があるかと思います。

```kotlin:MainActivity.kt
override fun onMapReady(googleMap: GoogleMap) {
  mMap = googleMap
  mMap.setOnMapLoadedCallback {
    fitMarkersOnScreen()
  }
}

private fun fitMarkersOnScreen() {
    val skytree = LatLng(35.710063, 139.8107)
    val kaminariMon = LatLng(35.710233, 139.797648)
    val asakusaViewHotel = LatLng(35.715151, 139.79829)
    val bounds = LatLngBounds.Builder().also { builder ->
        listOf(skytree, kaminariMon, asakusaViewHotel).forEach {
            mMap.addMarker(MarkerOptions().position(it))
            builder.include(it)
        }
    }.build()
    mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 300))
}
```

`newLatLngBounds` は、地図がレンダリングされたあとで呼び出さないといけません。 `setOnMapLoadedCallback` で呼び出すようにしましょう。

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96747/c5f121c4-5859-dd4c-c427-fa73fe8b09d8.png">


# その他

- `mapFragment.getMapAsync(this)` は `onCreate` で呼ぶようにしましう。onResumeなどでよぶと、地図が再生成されてしまうので、表示に時間がかかります。

# まとめ

Maps SDKを使えば、簡単に地図を表示することがわかったかなとおもいます。良い地図ライフを。