Navigation SDK v2 for Androidを試してみる (いじってみる編)

## はじめに

[前回](https://qiita.com/OttyLab/private/d8cb612c4265a6a686fd)サンプルの中身について詳しく見ました。今回はこれを踏まえてナビゲーションアプリとしての使い勝手を向上させたいと思います。


## 注意事項
- この記事では2021/11/25現在で最新の`v2.0.2`を使用します。
- [Pricing](https://docs.mapbox.com/android/navigation/guides/pricing/)にご注意ください。特にMAUは100まで無料ですが、[ここに記載](https://docs.mapbox.com/android/navigation/guides/pricing/#how-are-unique-users-counted)されているようにアプリケーションの削除・インストールでカウントアップします。デバッグ中に何度も削除・インストールを繰り返すと無料枠を超える可能性があります。


## 変更点

以下の点について変更を行います。

- GPS
- 日本語（多言語）サポート
- カメラの変更
- バナー色の変更
- 音声案内の編集
- Map Styleの変更
- 通過済み経路の削除

## GPS

前回見たように、サンプルでは`ReplayLocationEngine`が使用されています。デバッグには便利ですが、実用上不便なので(というか使えないので)
デフォルトの`LocationEngine`を使用するように変更します。

### パーミッションの設定

マニフェストに以下の`android.permission.ACCESS_FINE_LOCATION`を設定してください。

```xml:app/src/main/AndroidManifest.xml
  2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3     package="com.example.navigationsdksample">
  4
  5     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  6
  7     <application
...
```

### `LocationEngine`の変更

実は`NavigationOptions`で何も指定しなければ[デフォルトの`LocationEngine`が使用される](https://github.com/mapbox/mapbox-navigation-android/blob/v2.0.2/libnavigation-base/src/main/java/com/mapbox/navigation/base/options/NavigationOptions.kt#L287-L288)ので、前回の[Navigation Coreの初期化](https://qiita.com/OttyLab/private/d8cb612c4265a6a686fd#navigation-core%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96)の以下の部分をコメントアウトすればほとんど完了です。

```kotlin:TurnByTurnExperienceActivity.kt
430         // initialize Mapbox Navigation
431         mapboxNavigation = if (MapboxNavigationProvider.isCreated()) {
432             MapboxNavigationProvider.retrieve()
433         } else {
434             MapboxNavigationProvider.create(
435                 NavigationOptions.Builder(this.applicationContext)
436                     .accessToken(getString(R.string.mapbox_access_token))
437                     // comment out the location engine setting block to disable simulation
438                     .locationEngine(replayLocationEngine) // ここ
439                     .build()
440             )
441         }
```

あとは不要になったコード(`mapboxReplayer`, `replayLocationEngine`, `replayProgressObserver`等)をガシガシ消します。


### パーミッションの取得

手動でパーミッションを与えても動きますが、ダイアログを表示たほうがユーザーフレンドリーでしょう。好きな方法で実装してOKですが、私はNavigation SDKの中にある[`LocationPermissionsHelper`](https://github.com/mapbox/mapbox-navigation-android/blob/v2.0.2/examples/src/main/java/com/mapbox/navigation/examples/util/LocationPermissionsHelper.kt)を拝借してきました。

### GitHub

変更点は[こちら](https://github.com/OttyLab/MapboxSamples/commit/2328a84d582ddf0bb60a80e862f9f93bca6bb9cd)です。


## 日本語（多言語）サポート

音声案内および方向指示はDirections APIで生成されます。そこで、言語や単位の設定は`RouteOptions`で行います。具体的には以下の場所にオプションを追加します。これらのオプションはDirections APIの[`language`](https://docs.mapbox.com/api/navigation/directions/#retrieve-directions:~:text=language)と[`voice_units `](https://docs.mapbox.com/api/navigation/directions/#retrieve-directions:~:text=Specify%20which%20type%20of%20units)に相当します。

```kotlin:TurnByTurnExperienceActivity.kt
615         mapboxNavigation.requestRoutes(
616             RouteOptions.builder()
617                 .applyDefaultNavigationOptions()
618                 .applyLanguageAndVoiceUnitOptions(this)
619                 .language("ja-JP") //ここ
620                 .voiceUnits(DirectionsCriteria.METRIC) //ここ
```

### 音声案内

`MapboxSpeechApi`と`MapboxVoiceInstructionsPlayer`の言語設定も変更します。`MapboxSpeechApi`はDirections APIで得られた音声案内の文字列（SSML）から音声ファイル(MP3)に変換する際のAmazon Pollyの設定に使用されます。`MapboxVoiceInstructionsPlayer`はデバイスのText to Speechを使用する際の設定です。

```kotlin:TurnByTurnExperienceActivity.kt
493         // initialize voice instructions api and the voice instruction player
494         speechApi = MapboxSpeechApi(
495             this,
496             getString(R.string.mapbox_access_token),
497             Locale.JAPAN.language
498         )
499         voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
500             this,
501             getString(R.string.mapbox_access_token),
502             Locale.JAPAN.language
503         )
```

### 方向指示
バナー表示はデフォルトでシステムロケールにしたがいます。なので、もしデバイスがUSになっていると残りの距離が`ft`単位になります。この挙動は[`DistanceFormatterOptions`](https://github.com/mapbox/mapbox-navigation-android/blob/v2.0.2/libnavigation-base/src/main/java/com/mapbox/navigation/base/formatter/DistanceFormatterOptions.kt#L79)で定義されています。そこで、以下のように`NavigationOptions`で`DistanceFormatterOptions`の設定を変更すれば画面表示も`m`単位に切り替わります。

```kotlin:TurnByTurnExperienceActivity.kt
427             MapboxNavigationProvider.create(
428                 NavigationOptions.Builder(this.applicationContext)
429                     .accessToken(getString(R.string.mapbox_access_token))
430                     .distanceFormatterOptions(DistanceFormatterOptions.Builder(this.applicationContext).locale(Locale.JAPAN).build())
431                     .build()
432             )
```

### 多言語サポート

上記のように強制的に日本語設定にするのもアプリケーション次第ではアリですが、一般的にはシステムロケールに合わせたほうがユーザーフレンドリーです。

そこでバナー表示で追加した`DistanceFormatterOptions`は削除し、他の部分は以下のように設定しました。


```kotlin:TurnByTurnExperienceActivity.kt
620         mapboxNavigation.requestRoutes(
621             RouteOptions.builder()
622                 .applyDefaultNavigationOptions()
623                 .applyLanguageAndVoiceUnitOptions(this)
624                 .language(application.inferDeviceLanguage())
625                 .voiceUnits(if(application.inferDeviceLocale().getUnitTypeForLocale() == UnitType.METRIC) DirectionsCriteria.METRIC else DirectionsCriteria.IMPERIAL)
```

```kotlin:TurnByTurnExperienceActivity.kt
498         // initialize voice instructions api and the voice instruction player
499         speechApi = MapboxSpeechApi(
500             this,
501             getString(R.string.mapbox_access_token),
502             applicationContext.inferDeviceLanguage()
503         )
504         voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
505             this,
506             getString(R.string.mapbox_access_token),
507             applicationContext.inferDeviceLanguage()
508         )
```

### GitHub

変更点は[こちら](https://github.com/OttyLab/MapboxSamples/commit/d818126e992a874d2a846f78934c19e8019944ea)です。


## カメラの設定

経路設定後、ナビゲーション開始時にFollowing(Puck後方上空からの視点)に切り替わってほしいです。`navigationCamera.requestNavigationCameraToOverview()`を以下のように変えるだけでOKです。

```kotlin:TurnByTurnExperienceActivity.kt
661     private fun setRouteAndStartNavigation(routes: List<DirectionsRoute>) {
...
671         // move the camera to following when new route is available
672         navigationCamera.requestNavigationCameraToFollowing()
673     }
```

また、街中で使うにはズームが少し小さい気がします。もう少しズームレベルを上げましょう。ズーム等、カメラのパラメータは`ViewportDataSource`が生成しているため、その[オプションで決められて](https://github.com/mapbox/mapbox-navigation-android/blob/v2.0.2/libnavui-maps/src/main/java/com/mapbox/navigation/ui/maps/camera/data/MapboxNavigationViewportDataSourceOptions.kt#L32-L44)います。

具体的には以下のように変更します。

```kotlin:TurnByTurnExperienceActivity.kt
438         // initialize Navigation Camera
439         viewportDataSource = MapboxNavigationViewportDataSource(mapboxMap)
440         viewportDataSource.options.followingFrameOptions.minZoom = 16.0 //ここ
441         viewportDataSource.options.followingFrameOptions.maxZoom = 18.0 //ここ
442         navigationCamera = NavigationCamera(
443             mapboxMap,
444             binding.mapView.camera,
445             viewportDataSource
446         )
```

### GitHub

変更点は[こちら](https://github.com/OttyLab/MapboxSamples/commit/78673b2a2c2bbe58bdf9d65cfae1b372e12bee5b)です。


## バナー色の変更

ボトムバナー(`MapboxTripProgressView`)の背景色を変えてみます。SDKでリソースで定義されているものは、リソースを再定義することで見た目を変更できます。`MapboxTripProgressView`の場合、[`mapbox_trip_progress_layout.xml`](https://github.com/mapbox/mapbox-navigation-android/blob/v2.0.2/libnavui-tripprogress/src/main/res/layout/mapbox_trip_progress_layout.xml)で定義されているので、このファイルをコピーして、自分のアプリのres以下にペースト、修正すればOKです。

### GitHub

変更点は[こちら](https://github.com/OttyLab/MapboxSamples/commit/9bc187677a91f52da6c460e075b2f2f1eeb8eefc)です。

オリジナルのファイルに対し、７行目を追加しました。

```xml
  1 <androidx.constraintlayout.widget.ConstraintLayout
  2     xmlns:android="http://schemas.android.com/apk/res/android"
  3     xmlns:app="http://schemas.android.com/apk/res-auto"
  4     xmlns:tools="http://schemas.android.com/tools"
  5     android:layout_width="match_parent"
  6     android:layout_height="wrap_content"
  7     android:background="#F5DEB3"
  8     >
```

## 音声案内の編集

Directions APIから受け取ったSSMLテキストをSDKがAmazon Pollyに送ることで音声案内が生成されます。`VoiceInstructionsObserver`はAmazon Pollyに送る直前に呼び出されるため、このタイミングで音声案内を編集すると任意の案内を行うことができます。特殊読みでどうしても正しく発話されない・・・等で使えるのではないでしょうか。

今回は以下のように変更してみました。語尾が「デステニー。」になります。ちなみに、元ネタは[がんばれ！おでんくんの神様](https://odenkun.com/character/images/chara08.jpg)です。

```kotlin:TurnByTurnExperienceActivity.kt
243     /**
244      * Observes when a new voice instruction should be played.
245      */
246     private val voiceInstructionsObserver = VoiceInstructionsObserver { voiceInstructions ->
247         val announcement = voiceInstructions.announcement()?.replace(Regex("です。"), "デステニー。")
248         val ssmlAnnouncement = voiceInstructions.ssmlAnnouncement()?.replace(Regex("です。"), "デステニー。")
249         val customVoiceInstructions = VoiceInstructions.builder()
250             .announcement(announcement)
251             .ssmlAnnouncement(ssmlAnnouncement)
252             .distanceAlongGeometry(voiceInstructions.distanceAlongGeometry())
253             .build();
254
255         speechApi.generate(customVoiceInstructions, speechCallback)
256     }
```

### GitHub

変更点は[こちら](https://github.com/OttyLab/MapboxSamples/commit/f6d19dfb3577a1806b24a34e8f58150d37880944)です。


## Map Styleの変更

サンプルでは[Streets](https://www.mapbox.com/maps/streets)が使用されていますが、よりナビゲーション向きの`mapbox://styles/mapbox/traffic-day-v2`に変更します。`Style.MAPBOX_STREETS`を以下のように変更するだけで完了です。


```kotlin:TurnByTurnExperienceActivity.kt
535         // load map style
536         mapboxMap.loadStyleUri(
537             Style.TRAFFIC_DAY //ここ
538         ) {
...
```

ただし、Style内で使用されているLayer名が異なるので以下の部分も変更が必要です。

```kotlin:TurnByTurnExperienceActivity.kt
525         val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(this)
526             .withRouteLineBelowLayerId("road-label-motorway-trunk") //ここ
527             .build()
```

### GitHub

変更点は[こちら](https://github.com/OttyLab/MapboxSamples/commit/426fd32f59ca0d8f81ed990a2db56a909696892c)です。


## 通過済み経路の削除
すでに通った経路は表示を隠したほうがわかりやすいです。Navigation SDKではVanishingという機能を使用することで実現できます。

まずは機能をONします。

```kotlin:TurnByTurnExperienceActivity.kt
535         val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(this)
536             .withRouteLineBelowLayerId("road-label-motorway-trunk")
537             .withVanishingRouteLineEnabled(true) //ここ
538             .build()
```

`onPositionChangedListener`を定義し、

```kotlin:TurnByTurnExperienceActivity.kt
338     private val onPositionChangedListener = OnIndicatorPositionChangedListener { point ->
339         val result = routeLineApi.updateTraveledRouteLine(point)
340         mapboxMap.getStyle()?.apply {
341             routeLineView.renderRouteLineUpdate(this, result)
342         }
343     }
```

turn-by-turnナビゲーション開始時にリスナに登録します。

```kotlin:TurnByTurnExperienceActivity.kt
684     private fun setRouteAndStartNavigation(routes: List<DirectionsRoute>) {
685         binding.mapView.location.addOnIndicatorPositionChangedListener(onPositionChangedListener)
```

これにより、Puckが動くたびに経路の通過地点が更新されます。また、処理の途中に現在のナビゲーションのステータス（Trackingなのか、終わったのか）を参照する箇所があります。このステータスをアップデートするために以下のコードを追加します。

```kotlin:TurnByTurnExperienceActivity.kt
348     private val routeProgressObserver = RouteProgressObserver { routeProgress ->
...
381         routeLineApi.updateWithRouteProgress(routeProgress,{})
382     }
```
### GitHub

変更点は[こちら](https://github.com/OttyLab/MapboxSamples/commit/b60efd98890e7068ba4e8ff7e8c045072b4a256c)です。


## 成果物
![result.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85105/f2efceb8-6479-baf8-facf-85d7bfd66350.gif)


## まとめ

Navigation SDKは非常にカスタマイズ性が高く、様々なユースケースにマッチします。ぜひ、色々と試してみてください！