@sargsyansevak/expo-mapbox-navigation 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +313 -0
  4. package/android/build.gradle +98 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/expo/modules/mapboxnavigation/ExpoMapboxNavigationModule.kt +120 -0
  7. package/android/src/main/java/expo/modules/mapboxnavigation/ExpoMapboxNavigationView.kt +1100 -0
  8. package/android/src/main/res/drawable-hdpi/icon_compass.png +0 -0
  9. package/android/src/main/res/drawable-hdpi/icon_mute.png +0 -0
  10. package/android/src/main/res/drawable-hdpi/icon_overview.png +0 -0
  11. package/android/src/main/res/drawable-hdpi/icon_sound.png +0 -0
  12. package/android/src/main/res/drawable-hdpi/icon_x.png +0 -0
  13. package/android/src/main/res/drawable-ldpi/icon_compass.png +0 -0
  14. package/android/src/main/res/drawable-ldpi/icon_mute.png +0 -0
  15. package/android/src/main/res/drawable-ldpi/icon_overview.png +0 -0
  16. package/android/src/main/res/drawable-ldpi/icon_sound.png +0 -0
  17. package/android/src/main/res/drawable-ldpi/icon_x.png +0 -0
  18. package/android/src/main/res/drawable-mdpi/icon_compass.png +0 -0
  19. package/android/src/main/res/drawable-mdpi/icon_mute.png +0 -0
  20. package/android/src/main/res/drawable-mdpi/icon_overview.png +0 -0
  21. package/android/src/main/res/drawable-mdpi/icon_sound.png +0 -0
  22. package/android/src/main/res/drawable-mdpi/icon_x.png +0 -0
  23. package/android/src/main/res/drawable-xhdpi/icon_compass.png +0 -0
  24. package/android/src/main/res/drawable-xhdpi/icon_mute.png +0 -0
  25. package/android/src/main/res/drawable-xhdpi/icon_overview.png +0 -0
  26. package/android/src/main/res/drawable-xhdpi/icon_sound.png +0 -0
  27. package/android/src/main/res/drawable-xhdpi/icon_x.png +0 -0
  28. package/android/src/main/res/drawable-xxhdpi/icon_compass.png +0 -0
  29. package/android/src/main/res/drawable-xxhdpi/icon_mute.png +0 -0
  30. package/android/src/main/res/drawable-xxhdpi/icon_overview.png +0 -0
  31. package/android/src/main/res/drawable-xxhdpi/icon_sound.png +0 -0
  32. package/android/src/main/res/drawable-xxhdpi/icon_x.png +0 -0
  33. package/android/src/main/res/drawable-xxxhdpi/icon_compass.png +0 -0
  34. package/android/src/main/res/drawable-xxxhdpi/icon_mute.png +0 -0
  35. package/android/src/main/res/drawable-xxxhdpi/icon_overview.png +0 -0
  36. package/android/src/main/res/drawable-xxxhdpi/icon_sound.png +0 -0
  37. package/android/src/main/res/drawable-xxxhdpi/icon_x.png +0 -0
  38. package/android/src/main/res/values/styles.xml +9 -0
  39. package/app.plugin.js +1 -0
  40. package/build/ExpoMapboxNavigation.types.d.ts +90 -0
  41. package/build/ExpoMapboxNavigation.types.d.ts.map +1 -0
  42. package/build/ExpoMapboxNavigation.types.js +2 -0
  43. package/build/ExpoMapboxNavigation.types.js.map +1 -0
  44. package/build/ExpoMapboxNavigationModule.d.ts +3 -0
  45. package/build/ExpoMapboxNavigationModule.d.ts.map +1 -0
  46. package/build/ExpoMapboxNavigationModule.js +3 -0
  47. package/build/ExpoMapboxNavigationModule.js.map +1 -0
  48. package/build/ExpoMapboxNavigationView.d.ts +5 -0
  49. package/build/ExpoMapboxNavigationView.d.ts.map +1 -0
  50. package/build/ExpoMapboxNavigationView.js +7 -0
  51. package/build/ExpoMapboxNavigationView.js.map +1 -0
  52. package/build/ExpoMapboxNavigationView.web.d.ts +4 -0
  53. package/build/ExpoMapboxNavigationView.web.d.ts.map +1 -0
  54. package/build/ExpoMapboxNavigationView.web.js +7 -0
  55. package/build/ExpoMapboxNavigationView.web.js.map +1 -0
  56. package/build/index.d.ts +4 -0
  57. package/build/index.d.ts.map +1 -0
  58. package/build/index.js +3 -0
  59. package/build/index.js.map +1 -0
  60. package/expo-module.config.json +9 -0
  61. package/ios/ExpoMapboxNavigation.podspec +46 -0
  62. package/ios/ExpoMapboxNavigationModule.swift +92 -0
  63. package/ios/ExpoMapboxNavigationView.swift +509 -0
  64. package/package.json +47 -0
  65. package/plugin/config.js +127 -0
  66. package/src/ExpoMapboxNavigation.types.ts +70 -0
  67. package/src/ExpoMapboxNavigationModule.ts +2 -0
  68. package/src/ExpoMapboxNavigationView.tsx +17 -0
  69. package/src/ExpoMapboxNavigationView.web.tsx +13 -0
  70. package/src/index.ts +11 -0
@@ -0,0 +1,1100 @@
1
+ package expo.modules.mapboxnavigation
2
+
3
+ import android.content.Context
4
+ import android.content.res.Configuration
5
+ import android.content.res.Resources
6
+ import android.graphics.Color
7
+ import android.view.Gravity
8
+ import android.view.View
9
+ import android.view.ViewGroup
10
+ import android.widget.ImageView
11
+ import android.widget.LinearLayout
12
+ import android.widget.TextView
13
+ import androidx.constraintlayout.widget.ConstraintLayout
14
+ import androidx.constraintlayout.widget.ConstraintSet
15
+ import androidx.lifecycle.LifecycleOwner
16
+ import androidx.lifecycle.setViewTreeLifecycleOwner
17
+ import com.mapbox.api.directions.v5.models.RouteOptions
18
+ import com.mapbox.bindgen.Expected
19
+ import com.mapbox.common.location.Location
20
+ import com.mapbox.geojson.Point
21
+ import com.mapbox.geojson.utils.PolylineUtils
22
+ import com.mapbox.maps.CameraOptions
23
+ import com.mapbox.maps.EdgeInsets
24
+ import com.mapbox.maps.ImageHolder
25
+ import com.mapbox.maps.MapView
26
+ import com.mapbox.maps.MapboxMap
27
+ import com.mapbox.maps.Style
28
+ import com.mapbox.maps.extension.localization.localizeLabels
29
+ import com.mapbox.maps.extension.style.layers.*
30
+ import com.mapbox.maps.extension.style.layers.generated.RasterLayer
31
+ import com.mapbox.maps.extension.style.sources.TileSet
32
+ import com.mapbox.maps.extension.style.sources.addSource
33
+ import com.mapbox.maps.extension.style.sources.generated.RasterSource
34
+ import com.mapbox.maps.plugin.LocationPuck2D
35
+ import com.mapbox.maps.plugin.animation.camera
36
+ import com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListener
37
+ import com.mapbox.maps.plugin.locationcomponent.location
38
+ import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
39
+ import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
40
+ import com.mapbox.navigation.base.route.NavigationRoute
41
+ import com.mapbox.navigation.base.route.NavigationRouterCallback
42
+ import com.mapbox.navigation.base.route.RouterFailure
43
+ import com.mapbox.navigation.base.route.RouterOrigin
44
+ import com.mapbox.navigation.base.trip.model.RouteLegProgress
45
+ import com.mapbox.navigation.base.trip.model.RouteProgress
46
+ import com.mapbox.navigation.core.arrival.ArrivalObserver
47
+ import com.mapbox.navigation.core.directions.session.RoutesObserver
48
+ import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
49
+ import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter
50
+ import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
51
+ import com.mapbox.navigation.core.mapmatching.MapMatchingAPICallback
52
+ import com.mapbox.navigation.core.mapmatching.MapMatchingFailure
53
+ import com.mapbox.navigation.core.mapmatching.MapMatchingOptions
54
+ import com.mapbox.navigation.core.mapmatching.MapMatchingSuccessfulResult
55
+ import com.mapbox.navigation.core.trip.session.LocationMatcherResult
56
+ import com.mapbox.navigation.core.trip.session.LocationObserver
57
+ import com.mapbox.navigation.core.trip.session.OffRouteObserver
58
+ import com.mapbox.navigation.core.trip.session.RouteProgressObserver
59
+ import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
60
+ import com.mapbox.navigation.tripdata.maneuver.api.MapboxManeuverApi
61
+ import com.mapbox.navigation.tripdata.progress.api.MapboxTripProgressApi
62
+ import com.mapbox.navigation.tripdata.progress.model.DistanceRemainingFormatter
63
+ import com.mapbox.navigation.tripdata.progress.model.EstimatedTimeToArrivalFormatter
64
+ import com.mapbox.navigation.tripdata.progress.model.TimeRemainingFormatter
65
+ import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateFormatter
66
+ import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateValue
67
+ import com.mapbox.navigation.ui.base.util.MapboxNavigationConsumer
68
+ import com.mapbox.navigation.ui.components.maneuver.model.ManeuverPrimaryOptions
69
+ import com.mapbox.navigation.ui.components.maneuver.model.ManeuverViewOptions
70
+ import com.mapbox.navigation.ui.components.maneuver.view.MapboxManeuverView
71
+ import com.mapbox.navigation.ui.components.maps.camera.view.MapboxRecenterButton
72
+ import com.mapbox.navigation.ui.components.maps.camera.view.MapboxRouteOverviewButton
73
+ import com.mapbox.navigation.ui.components.voice.view.MapboxSoundButton
74
+ import com.mapbox.navigation.ui.maps.camera.NavigationCamera
75
+ import com.mapbox.navigation.ui.maps.camera.data.FollowingFrameOptions.FocalPoint
76
+ import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
77
+ import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler
78
+ import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState
79
+ import com.mapbox.navigation.ui.maps.camera.transition.NavigationCameraTransitionOptions
80
+ import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
81
+ import com.mapbox.navigation.ui.maps.route.RouteLayerConstants.TOP_LEVEL_ROUTE_LINE_LAYER_ID
82
+ import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApi
83
+ import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowView
84
+ import com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptions
85
+ import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApi
86
+ import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineView
87
+ import com.mapbox.navigation.ui.maps.route.line.model.*
88
+ import com.mapbox.navigation.voice.api.*
89
+ import com.mapbox.navigation.voice.model.SpeechAnnouncement
90
+ import com.mapbox.navigation.voice.model.SpeechError
91
+ import com.mapbox.navigation.voice.model.SpeechValue
92
+ import com.mapbox.navigation.voice.model.SpeechVolume
93
+ import expo.modules.kotlin.AppContext
94
+ import expo.modules.kotlin.viewevent.EventDispatcher
95
+ import expo.modules.kotlin.views.ExpoView
96
+ import java.util.Locale
97
+
98
+ val PIXEL_DENSITY = Resources.getSystem().displayMetrics.density
99
+
100
+ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
101
+ ExpoView(context, appContext) {
102
+
103
+ // Set view tree lifecycle owner to the activity
104
+ // Otherwise leads to crashes when mapbox package tries to query the view tree lifecycle
105
+ // owner
106
+ // and gets null. Not set automatically for some reason.
107
+ // https://github.com/mapbox/mapbox-navigation-android/blob/188d4781b31bb328733eeca593edc8087e38d915/ui-utils/src/main/java/com/mapbox/navigation/ui/utils/internal/lifecycle/ViewLifecycleRegistry.kt#L67
108
+ init {
109
+ this.setViewTreeLifecycleOwner(
110
+ appContext.activityProvider?.currentActivity as LifecycleOwner
111
+ )
112
+ }
113
+
114
+ private var isMuted = false
115
+ private var currentCoordinates: List<Point>? = null
116
+ private var currentLocale = Locale.getDefault()
117
+ private var currentWaypointIndices: List<Int>? = null
118
+ private var currentRoutesRequestId: Long? = null
119
+ private var currentMapMatchingRequestId: Long? = null
120
+ private var isUsingRouteMatchingApi = false
121
+ private var currentRouteProfile: String? = null
122
+ private var currentRouteExcludeList: List<String>? = null
123
+ private var currentMapStyle: String? = null
124
+ private var currentCustomRasterSourceUrl: String? = null
125
+ private var currentPlaceCustomRasterLayerAbove: String? = null
126
+ private var currentDisableAlternativeRoutes: Boolean? = null
127
+ private var currentFollowingZoom: Double? = null
128
+ private var vehicleMaxHeight: Double? = null
129
+ private var vehicleMaxWidth: Double? = null
130
+
131
+ private val onRouteProgressChanged by EventDispatcher()
132
+ private val onCancelNavigation by EventDispatcher()
133
+ private val onWaypointArrival by EventDispatcher()
134
+ private val onFinalDestinationArrival by EventDispatcher()
135
+ private val onRouteChanged by EventDispatcher()
136
+ private val onUserOffRoute by EventDispatcher()
137
+ private val onRoutesLoaded by EventDispatcher()
138
+ private val onRouteFailedToLoad by EventDispatcher()
139
+
140
+ private val mapboxNavigation = MapboxNavigationApp.current()
141
+ private var mapboxStyle: Style? = null
142
+ private val navigationLocationProvider = NavigationLocationProvider()
143
+ private var voiceInstructionsPlayer =
144
+ MapboxVoiceInstructionsPlayer(context, currentLocale.toLanguageTag())
145
+
146
+ private val parentConstraintLayout =
147
+ ConstraintLayout(context).also {
148
+ addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
149
+ }
150
+
151
+ private val mapViewId = 1
152
+ private val mapView = createMapView(mapViewId, parentConstraintLayout)
153
+ private val mapboxMap = mapView.mapboxMap
154
+
155
+ private val viewportDataSource = createViewportDataSource(mapboxMap)
156
+ private val navigationCamera =
157
+ NavigationCamera(mapboxMap, mapView.camera, viewportDataSource).apply {
158
+ mapView.camera.addCameraAnimationsLifecycleListener(
159
+ NavigationBasicGesturesHandler(this)
160
+ )
161
+ }
162
+
163
+ private val maneuverViewId = 2
164
+ private val maneuverView = createManueverView(maneuverViewId, parentConstraintLayout)
165
+
166
+ private val tripProgressViewId = 3
167
+ private val tripProgressTimeRemainingTextView = createCenteredTextView()
168
+ private val tripProgressDistanceRemainingTextView = createCenteredTextView()
169
+ private val tripProgressArrivalTimeTextView = createCenteredTextView()
170
+ private val tripProgressView =
171
+ createTripProgressView(
172
+ id = tripProgressViewId,
173
+ parent = parentConstraintLayout,
174
+ tripProgressTimeRemainingTextView = tripProgressTimeRemainingTextView,
175
+ tripProgressDistanceRemainingTextView = tripProgressDistanceRemainingTextView,
176
+ tripProgressArrivalTimeTextView = tripProgressArrivalTimeTextView
177
+ )
178
+
179
+ private val soundButtonId = 4
180
+ private val soundButton =
181
+ createSoundButton(soundButtonId, parentConstraintLayout) {
182
+ voiceInstructionsPlayer.volume(SpeechVolume(if (isMuted) 1.0f else 0.0f))
183
+ it.findViewById<ImageView>(com.mapbox.navigation.ui.components.R.id.buttonIcon)
184
+ .setImageResource(
185
+ if (isMuted) R.drawable.icon_sound else R.drawable.icon_mute
186
+ )
187
+ isMuted = !isMuted
188
+ }
189
+
190
+ private val overviewButtonId = 5
191
+ private val overviewButton =
192
+ createOverviewButton(overviewButtonId, parentConstraintLayout) {
193
+ navigationCamera.requestNavigationCameraToOverview()
194
+ }
195
+
196
+ private val recenterButtonId = 6
197
+ private val recenterButton =
198
+ createRecenterButton(recenterButtonId, parentConstraintLayout) {
199
+ navigationCamera.requestNavigationCameraToFollowing()
200
+ }
201
+
202
+ private val cancelButtonId = 7
203
+ private val cancelButton =
204
+ createCancelButton(cancelButtonId, parentConstraintLayout) {
205
+ onCancelNavigation(mapOf())
206
+ }
207
+
208
+ private val parentConstraintSet =
209
+ createAndApplyConstraintSet(
210
+ mapViewId = mapViewId,
211
+ maneuverViewId = maneuverViewId,
212
+ tripProgressViewId = tripProgressViewId,
213
+ soundButtonId = soundButtonId,
214
+ overviewButtonId = overviewButtonId,
215
+ recenterButtonId = recenterButtonId,
216
+ cancelButtonId = cancelButtonId,
217
+ constraintLayout = parentConstraintLayout
218
+ )
219
+
220
+ private val routeLineApiOptions = MapboxRouteLineApiOptions.Builder().build()
221
+ private val routeLineApi = MapboxRouteLineApi(routeLineApiOptions)
222
+
223
+ private val routeLineViewOptions =
224
+ MapboxRouteLineViewOptions.Builder(context).routeLineBelowLayerId("road-label").build()
225
+ private val routeLineView = MapboxRouteLineView(routeLineViewOptions)
226
+
227
+ private val routeArrow = MapboxRouteArrowApi()
228
+ private val routeArrowOptions =
229
+ RouteArrowOptions.Builder(context)
230
+ .withAboveLayerId(TOP_LEVEL_ROUTE_LINE_LAYER_ID)
231
+ .build()
232
+ private val routeArrowView = MapboxRouteArrowView(routeArrowOptions)
233
+
234
+ private val distanceFormatter = DistanceFormatterOptions.Builder(context).build()
235
+ private var maneuverApi = MapboxManeuverApi(MapboxDistanceFormatter(distanceFormatter))
236
+
237
+ private var tripProgressFormatter =
238
+ TripProgressUpdateFormatter.Builder(context)
239
+ .distanceRemainingFormatter(DistanceRemainingFormatter(distanceFormatter))
240
+ .timeRemainingFormatter(TimeRemainingFormatter(context))
241
+ .estimatedTimeToArrivalFormatter(EstimatedTimeToArrivalFormatter(context))
242
+ .build()
243
+ private var tripProgressApi = MapboxTripProgressApi(tripProgressFormatter)
244
+
245
+ private var speechApi = MapboxSpeechApi(context, currentLocale.toLanguageTag())
246
+ private val voiceInstructionsPlayerCallback =
247
+ MapboxNavigationConsumer<SpeechAnnouncement> { value -> speechApi.clean(value) }
248
+
249
+ private val speechCallback =
250
+ MapboxNavigationConsumer<Expected<SpeechError, SpeechValue>> { expected ->
251
+ expected.fold(
252
+ { error ->
253
+ voiceInstructionsPlayer.play(
254
+ error.fallback,
255
+ voiceInstructionsPlayerCallback
256
+ )
257
+ },
258
+ { value ->
259
+ voiceInstructionsPlayer.play(
260
+ value.announcement,
261
+ voiceInstructionsPlayerCallback
262
+ )
263
+ }
264
+ )
265
+ }
266
+ private val voiceInstructionsObserver = VoiceInstructionsObserver { voiceInstructions ->
267
+ speechApi.generate(voiceInstructions, speechCallback)
268
+ }
269
+
270
+ private val routesRequestCallback =
271
+ object : NavigationRouterCallback {
272
+ override fun onRoutesReady(
273
+ routes: List<NavigationRoute>,
274
+ @RouterOrigin routerOrigin: String
275
+ ) {
276
+ onRoutesReady(routes)
277
+ }
278
+ override fun onCanceled(
279
+ routeOptions: RouteOptions,
280
+ @RouterOrigin routerOrigin: String
281
+ ) {}
282
+ override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
283
+ onRouteFailedToLoad(mapOf("errorMessage" to reasons.first().message))
284
+ }
285
+ }
286
+
287
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
288
+ private val mapMatchingRequestCallback =
289
+ object : MapMatchingAPICallback {
290
+ override fun success(result: MapMatchingSuccessfulResult) {
291
+ onRoutesReady(result.navigationRoutes)
292
+ }
293
+ override fun onCancel() {}
294
+ override fun failure(failure: MapMatchingFailure) {}
295
+ }
296
+
297
+ private val routesObserver =
298
+ object : RoutesObserver {
299
+ override fun onRoutesChanged(result: RoutesUpdatedResult) {
300
+ onRoutesLoaded(
301
+ mapOf(
302
+ "routes" to
303
+ mapOf(
304
+ "mainRoute" to
305
+ convertRoute(
306
+ result.navigationRoutes.first()
307
+ ),
308
+ "alternativeRoutes" to
309
+ result.navigationRoutes.drop(1).map {
310
+ convertRoute(it)
311
+ }
312
+ )
313
+ )
314
+ )
315
+
316
+ // Handle viewport data source
317
+ if (result.navigationRoutes.isNotEmpty()) {
318
+ viewportDataSource.onRouteChanged(result.navigationRoutes.first())
319
+ viewportDataSource.evaluate()
320
+ } else {
321
+ viewportDataSource.clearRouteData()
322
+ viewportDataSource.evaluate()
323
+ }
324
+
325
+ // Handle route lines
326
+ val alternativesMetadata =
327
+ mapboxNavigation?.getAlternativeMetadataFor(result.navigationRoutes)
328
+ if (alternativesMetadata != null) {
329
+ routeLineApi.setNavigationRoutes(
330
+ result.navigationRoutes,
331
+ alternativesMetadata
332
+ ) { value ->
333
+ mapboxStyle?.let { routeLineView.renderRouteDrawData(it, value) }
334
+ }
335
+ }
336
+
337
+ // Clear speech
338
+ speechApi.cancel()
339
+ voiceInstructionsPlayer.clear()
340
+
341
+ // Add observer to navigation camera
342
+ navigationCamera.registerNavigationCameraStateChangeObserver {
343
+ navigationCameraState ->
344
+ // shows/hide the recenter button depending on the camera
345
+ // state
346
+ when (navigationCameraState) {
347
+ NavigationCameraState.TRANSITION_TO_FOLLOWING,
348
+ NavigationCameraState.FOLLOWING -> recenterButton.visibility = View.GONE
349
+ NavigationCameraState.TRANSITION_TO_OVERVIEW,
350
+ NavigationCameraState.OVERVIEW,
351
+ NavigationCameraState.IDLE -> recenterButton.visibility = View.VISIBLE
352
+ }
353
+ }
354
+
355
+ this@ExpoMapboxNavigationView.onRouteChanged(mapOf())
356
+ }
357
+ }
358
+
359
+ private val routeProgressObserver =
360
+ object : RouteProgressObserver {
361
+ override fun onRouteProgressChanged(routeProgress: RouteProgress) {
362
+ // Handle viewport data source
363
+ viewportDataSource.onRouteProgressChanged(routeProgress)
364
+ viewportDataSource.evaluate()
365
+
366
+ // Handle route lines
367
+ routeLineApi.updateWithRouteProgress(routeProgress) { result ->
368
+ mapboxStyle?.let { routeLineView.renderRouteLineUpdate(it, result) }
369
+ }
370
+
371
+ // Handle route arrows
372
+ val updatedManeuverArrow = routeArrow.addUpcomingManeuverArrow(routeProgress)
373
+ mapboxStyle?.let {
374
+ routeArrowView.renderManeuverUpdate(it, updatedManeuverArrow)
375
+ }
376
+
377
+ // Handle manuevers
378
+ val maneuvers = maneuverApi.getManeuvers(routeProgress)
379
+ maneuverView.renderManeuvers(maneuvers)
380
+
381
+ // Handle trip progress view
382
+ tripProgressApi.getTripProgress(routeProgress).let {
383
+ update: TripProgressUpdateValue ->
384
+ val formatter = update.formatter
385
+ tripProgressTimeRemainingTextView.text =
386
+ formatter.getTimeRemaining(update.totalTimeRemaining)
387
+ tripProgressDistanceRemainingTextView.text =
388
+ formatter.getDistanceRemaining(update.distanceRemaining)
389
+ tripProgressArrivalTimeTextView.text =
390
+ formatter.getEstimatedTimeToArrival(update.estimatedTimeToArrival)
391
+ }
392
+
393
+ // Send progress event
394
+ this@ExpoMapboxNavigationView.onRouteProgressChanged(
395
+ mapOf(
396
+ "distanceRemaining" to routeProgress.distanceRemaining,
397
+ "distanceTraveled" to routeProgress.distanceTraveled,
398
+ "durationRemaining" to routeProgress.durationRemaining,
399
+ "fractionTraveled" to routeProgress.fractionTraveled
400
+ )
401
+ )
402
+ }
403
+ }
404
+
405
+ private val locationObserver =
406
+ object : LocationObserver {
407
+ override fun onNewLocationMatcherResult(
408
+ locationMatcherResult: LocationMatcherResult
409
+ ) {}
410
+ override fun onNewRawLocation(rawLocation: com.mapbox.common.location.Location) {
411
+ // Update puck location
412
+ navigationLocationProvider.changePosition(
413
+ location = rawLocation,
414
+ )
415
+ // Update viewport data source
416
+ viewportDataSource.onLocationChanged(rawLocation)
417
+ viewportDataSource.evaluate()
418
+ }
419
+ }
420
+
421
+ private val arrivalObserver =
422
+ object : ArrivalObserver {
423
+ override fun onWaypointArrival(routeProgress: RouteProgress) {
424
+ onWaypointArrival(
425
+ mapOf(
426
+ "distanceRemaining" to routeProgress.distanceRemaining,
427
+ "distanceTraveled" to routeProgress.distanceTraveled,
428
+ "durationRemaining" to routeProgress.durationRemaining,
429
+ "fractionTraveled" to routeProgress.fractionTraveled
430
+ )
431
+ )
432
+ }
433
+ override fun onNextRouteLegStart(routeLegProgress: RouteLegProgress) {}
434
+ override fun onFinalDestinationArrival(routeProgress: RouteProgress) {
435
+ onFinalDestinationArrival(mapOf())
436
+ }
437
+ }
438
+
439
+ private val offRouteObserver =
440
+ object : OffRouteObserver {
441
+ override fun onOffRouteStateChanged(offRoute: Boolean) {
442
+ if (offRoute) {
443
+ onUserOffRoute(mapOf())
444
+ }
445
+ }
446
+ }
447
+
448
+ private val onIndicatorPositionChangedListener = OnIndicatorPositionChangedListener { point ->
449
+ val result = routeLineApi.updateTraveledRouteLine(point)
450
+ mapboxStyle?.let { routeLineView.renderRouteLineUpdate(it, result) }
451
+ }
452
+
453
+ private fun createMapView(id: Int, parent: ViewGroup): MapView {
454
+ return MapView(context).apply {
455
+ setId(id)
456
+ parent.addView(this)
457
+
458
+ mapboxMap.loadStyle(Style.MAPBOX_STREETS) { style: Style -> mapboxStyle = style }
459
+
460
+ location.apply {
461
+ locationPuck =
462
+ LocationPuck2D(
463
+ bearingImage =
464
+ ImageHolder.from(
465
+ com.mapbox
466
+ .navigation
467
+ .ui
468
+ .components
469
+ .R
470
+ .drawable
471
+ .mapbox_navigation_puck_icon
472
+ ),
473
+ )
474
+ setLocationProvider(navigationLocationProvider)
475
+ puckBearingEnabled = true
476
+ enabled = true
477
+ }
478
+ }
479
+ }
480
+
481
+ private fun createManueverView(id: Int, parent: ViewGroup): MapboxManeuverView {
482
+ return MapboxManeuverView(context).apply {
483
+ setId(id)
484
+ parent.addView(this)
485
+
486
+ val maneuverViewOptions =
487
+ ManeuverViewOptions.Builder()
488
+ .primaryManeuverOptions(
489
+ ManeuverPrimaryOptions.Builder()
490
+ .textAppearance(R.style.ManeuverTextAppearance)
491
+ .build()
492
+ )
493
+ .build()
494
+
495
+ updateManeuverViewOptions(maneuverViewOptions)
496
+ }
497
+ }
498
+
499
+ private fun createCenteredTextView(): TextView {
500
+ return TextView(context).apply { setGravity(Gravity.CENTER) }
501
+ }
502
+
503
+ private fun createTripProgressView(
504
+ id: Int,
505
+ parent: ViewGroup,
506
+ tripProgressTimeRemainingTextView: TextView,
507
+ tripProgressDistanceRemainingTextView: TextView,
508
+ tripProgressArrivalTimeTextView: TextView
509
+ ): LinearLayout {
510
+ return LinearLayout(context).apply {
511
+ setId(id)
512
+ parent.addView(this)
513
+ setOrientation(LinearLayout.VERTICAL)
514
+ setBackgroundColor(Color.WHITE)
515
+ setPadding(
516
+ (5 * PIXEL_DENSITY).toInt(),
517
+ (5 * PIXEL_DENSITY).toInt(),
518
+ (5 * PIXEL_DENSITY).toInt(),
519
+ (5 * PIXEL_DENSITY).toInt()
520
+ )
521
+
522
+ addView(
523
+ tripProgressTimeRemainingTextView,
524
+ LayoutParams.MATCH_PARENT,
525
+ (40 * PIXEL_DENSITY).toInt()
526
+ )
527
+
528
+ val bottomContainer =
529
+ LinearLayout(context).apply {
530
+ setOrientation(LinearLayout.HORIZONTAL)
531
+ setGravity(Gravity.CENTER)
532
+ addView(
533
+ tripProgressDistanceRemainingTextView,
534
+ (60 * PIXEL_DENSITY).toInt(),
535
+ (20 * PIXEL_DENSITY).toInt()
536
+ )
537
+ addView(
538
+ tripProgressArrivalTimeTextView,
539
+ (60 * PIXEL_DENSITY).toInt(),
540
+ (20 * PIXEL_DENSITY).toInt()
541
+ )
542
+ }
543
+
544
+ addView(bottomContainer, LayoutParams.MATCH_PARENT, (20 * PIXEL_DENSITY).toInt())
545
+ }
546
+ }
547
+
548
+ private fun createSoundButton(
549
+ id: Int,
550
+ parent: ViewGroup,
551
+ onClick: (MapboxSoundButton) -> Unit
552
+ ): MapboxSoundButton {
553
+ return MapboxSoundButton(context).apply {
554
+ setId(id)
555
+ parent.addView(this)
556
+ findViewById<ImageView>(com.mapbox.navigation.ui.components.R.id.buttonIcon)
557
+ .setImageResource(R.drawable.icon_sound)
558
+ setOnClickListener { onClick(this) }
559
+ }
560
+ }
561
+
562
+ private fun createOverviewButton(
563
+ id: Int,
564
+ parent: ViewGroup,
565
+ onClick: () -> Unit
566
+ ): MapboxRouteOverviewButton {
567
+ return MapboxRouteOverviewButton(context).apply {
568
+ setId(id)
569
+ parent.addView(this)
570
+ findViewById<ImageView>(com.mapbox.navigation.ui.components.R.id.buttonIcon)
571
+ .setImageResource(R.drawable.icon_overview)
572
+ setOnClickListener { onClick() }
573
+ }
574
+ }
575
+
576
+ private fun createRecenterButton(
577
+ id: Int,
578
+ parent: ViewGroup,
579
+ onClick: () -> Unit
580
+ ): MapboxRecenterButton {
581
+ return MapboxRecenterButton(context).apply {
582
+ setId(id)
583
+ findViewById<ImageView>(com.mapbox.navigation.ui.components.R.id.buttonIcon)
584
+ .setImageResource(R.drawable.icon_compass)
585
+ parent.addView(this)
586
+ setVisibility(View.GONE)
587
+ setOnClickListener { onClick() }
588
+ }
589
+ }
590
+
591
+ private fun createCancelButton(
592
+ id: Int,
593
+ parent: ViewGroup,
594
+ onClick: (MapboxSoundButton) -> Unit
595
+ ): MapboxSoundButton {
596
+ return MapboxSoundButton(context).apply {
597
+ setId(id)
598
+ parent.addView(this)
599
+ findViewById<ImageView>(com.mapbox.navigation.ui.components.R.id.buttonIcon)
600
+ .setImageResource(R.drawable.icon_x)
601
+ setOnClickListener { onClick(this) }
602
+ }
603
+ }
604
+
605
+ private fun createAndApplyConstraintSet(
606
+ mapViewId: Int,
607
+ maneuverViewId: Int,
608
+ tripProgressViewId: Int,
609
+ soundButtonId: Int,
610
+ overviewButtonId: Int,
611
+ recenterButtonId: Int,
612
+ cancelButtonId: Int,
613
+ constraintLayout: ConstraintLayout
614
+ ): ConstraintSet {
615
+ return ConstraintSet().apply {
616
+ // Add MapView constraints
617
+ connect(mapViewId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
618
+ connect(mapViewId, ConstraintSet.BOTTOM, tripProgressViewId, ConstraintSet.TOP)
619
+ connect(mapViewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START)
620
+ connect(mapViewId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
621
+
622
+ // Add ManeuverView constraints
623
+ connect(
624
+ maneuverViewId,
625
+ ConstraintSet.TOP,
626
+ mapViewId,
627
+ ConstraintSet.TOP,
628
+ (4 * PIXEL_DENSITY).toInt()
629
+ )
630
+ connect(
631
+ maneuverViewId,
632
+ ConstraintSet.START,
633
+ ConstraintSet.PARENT_ID,
634
+ ConstraintSet.START,
635
+ (4 * PIXEL_DENSITY).toInt()
636
+ )
637
+ connect(
638
+ maneuverViewId,
639
+ ConstraintSet.END,
640
+ ConstraintSet.PARENT_ID,
641
+ ConstraintSet.END,
642
+ (4 * PIXEL_DENSITY).toInt()
643
+ )
644
+ constrainHeight(maneuverViewId, ConstraintSet.WRAP_CONTENT)
645
+
646
+ // Add TripProgressView constraints
647
+ connect(
648
+ tripProgressViewId,
649
+ ConstraintSet.BOTTOM,
650
+ ConstraintSet.PARENT_ID,
651
+ ConstraintSet.BOTTOM
652
+ )
653
+ connect(
654
+ tripProgressViewId,
655
+ ConstraintSet.START,
656
+ ConstraintSet.PARENT_ID,
657
+ ConstraintSet.START
658
+ )
659
+ connect(
660
+ tripProgressViewId,
661
+ ConstraintSet.END,
662
+ ConstraintSet.PARENT_ID,
663
+ ConstraintSet.END
664
+ )
665
+ constrainMinHeight(tripProgressViewId, (80 * PIXEL_DENSITY).toInt())
666
+ constrainWidth(tripProgressViewId, ConstraintSet.MATCH_CONSTRAINT)
667
+
668
+ // Add SoundButton constraints
669
+ connect(
670
+ soundButtonId,
671
+ ConstraintSet.TOP,
672
+ maneuverViewId,
673
+ ConstraintSet.BOTTOM,
674
+ (8 * PIXEL_DENSITY).toInt()
675
+ )
676
+ connect(
677
+ soundButtonId,
678
+ ConstraintSet.END,
679
+ ConstraintSet.PARENT_ID,
680
+ ConstraintSet.END,
681
+ (16 * PIXEL_DENSITY).toInt()
682
+ )
683
+ constrainWidth(soundButtonId, ConstraintSet.WRAP_CONTENT)
684
+ constrainHeight(soundButtonId, ConstraintSet.WRAP_CONTENT)
685
+
686
+ // Add OverviewButton constraints
687
+ connect(
688
+ overviewButtonId,
689
+ ConstraintSet.TOP,
690
+ soundButtonId,
691
+ ConstraintSet.BOTTOM,
692
+ (8 * PIXEL_DENSITY).toInt()
693
+ )
694
+ connect(
695
+ overviewButtonId,
696
+ ConstraintSet.END,
697
+ ConstraintSet.PARENT_ID,
698
+ ConstraintSet.END,
699
+ (16 * PIXEL_DENSITY).toInt()
700
+ )
701
+ constrainWidth(overviewButtonId, ConstraintSet.WRAP_CONTENT)
702
+ constrainHeight(overviewButtonId, ConstraintSet.WRAP_CONTENT)
703
+
704
+ // Add RecenterButton constraints
705
+ connect(
706
+ recenterButtonId,
707
+ ConstraintSet.TOP,
708
+ overviewButtonId,
709
+ ConstraintSet.BOTTOM,
710
+ (8 * PIXEL_DENSITY).toInt()
711
+ )
712
+ connect(
713
+ recenterButtonId,
714
+ ConstraintSet.END,
715
+ ConstraintSet.PARENT_ID,
716
+ ConstraintSet.END,
717
+ (16 * PIXEL_DENSITY).toInt()
718
+ )
719
+ constrainWidth(recenterButtonId, ConstraintSet.WRAP_CONTENT)
720
+ constrainHeight(recenterButtonId, ConstraintSet.WRAP_CONTENT)
721
+
722
+ // Add CancelButton constraints
723
+ connect(cancelButtonId, ConstraintSet.BOTTOM, tripProgressViewId, ConstraintSet.BOTTOM)
724
+ connect(cancelButtonId, ConstraintSet.TOP, tripProgressViewId, ConstraintSet.TOP)
725
+ connect(
726
+ cancelButtonId,
727
+ ConstraintSet.END,
728
+ ConstraintSet.PARENT_ID,
729
+ ConstraintSet.END,
730
+ (16 * PIXEL_DENSITY).toInt()
731
+ )
732
+ constrainWidth(cancelButtonId, ConstraintSet.WRAP_CONTENT)
733
+ constrainHeight(cancelButtonId, ConstraintSet.WRAP_CONTENT)
734
+
735
+ applyTo(constraintLayout)
736
+ }
737
+ }
738
+
739
+ private fun createViewportDataSource(mapboxMap: MapboxMap): MapboxNavigationViewportDataSource {
740
+ val portraitOverviewPadding =
741
+ EdgeInsets(
742
+ 140.0 * PIXEL_DENSITY,
743
+ 40.0 * PIXEL_DENSITY,
744
+ 120.0 * PIXEL_DENSITY,
745
+ 40.0 * PIXEL_DENSITY
746
+ )
747
+ val landscapeOverviewPadding =
748
+ EdgeInsets(
749
+ 30.0 * PIXEL_DENSITY,
750
+ 380.0 * PIXEL_DENSITY,
751
+ 110.0 * PIXEL_DENSITY,
752
+ 20.0 * PIXEL_DENSITY
753
+ )
754
+ val portraitFollowingPadding =
755
+ EdgeInsets(
756
+ 180.0 * PIXEL_DENSITY,
757
+ 40.0 * PIXEL_DENSITY,
758
+ 150.0 * PIXEL_DENSITY,
759
+ 40.0 * PIXEL_DENSITY
760
+ )
761
+ val landscapeFollowingPadding =
762
+ EdgeInsets(
763
+ 30.0 * PIXEL_DENSITY,
764
+ 380.0 * PIXEL_DENSITY,
765
+ 110.0 * PIXEL_DENSITY,
766
+ 40.0 * PIXEL_DENSITY
767
+ )
768
+
769
+ return MapboxNavigationViewportDataSource(mapboxMap).apply {
770
+ options.followingFrameOptions.focalPoint = FocalPoint(0.5, 0.9)
771
+ if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
772
+ ) {
773
+ followingPadding = landscapeFollowingPadding
774
+ overviewPadding = landscapeOverviewPadding
775
+ } else {
776
+ followingPadding = portraitFollowingPadding
777
+ overviewPadding = portraitOverviewPadding
778
+ }
779
+ }
780
+ }
781
+
782
+ override fun onAttachedToWindow() {
783
+ super.onAttachedToWindow()
784
+ mapboxNavigation?.registerRoutesObserver(routesObserver)
785
+ mapboxNavigation?.registerRouteProgressObserver(routeProgressObserver)
786
+ mapboxNavigation?.registerLocationObserver(locationObserver)
787
+ mapboxNavigation?.registerVoiceInstructionsObserver(voiceInstructionsObserver)
788
+ mapboxNavigation?.registerArrivalObserver(arrivalObserver)
789
+ mapboxNavigation?.registerOffRouteObserver(offRouteObserver)
790
+ mapView.location.addOnIndicatorPositionChangedListener(onIndicatorPositionChangedListener)
791
+ }
792
+
793
+ override fun onDetachedFromWindow() {
794
+ super.onDetachedFromWindow()
795
+ mapboxNavigation?.unregisterRoutesObserver(routesObserver)
796
+ mapboxNavigation?.unregisterRouteProgressObserver(routeProgressObserver)
797
+ mapboxNavigation?.unregisterLocationObserver(locationObserver)
798
+ mapboxNavigation?.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
799
+ mapboxNavigation?.unregisterArrivalObserver(arrivalObserver)
800
+ mapboxNavigation?.unregisterOffRouteObserver(offRouteObserver)
801
+ if (mapboxNavigation?.isDestroyed != true) {
802
+ mapboxNavigation?.stopTripSession()
803
+ }
804
+ speechApi.cancel()
805
+ voiceInstructionsPlayer.shutdown()
806
+ mapView.location.removeOnIndicatorPositionChangedListener(
807
+ onIndicatorPositionChangedListener
808
+ )
809
+ routeLineApi.cancel()
810
+ routeLineView.cancel()
811
+ maneuverApi.cancel()
812
+ }
813
+
814
+ private fun convertRoute(route: NavigationRoute): Map<String, Any> {
815
+ return mapOf(
816
+ "distance" to route.directionsRoute.distance(),
817
+ "expectedTravelTime" to route.directionsRoute.duration(),
818
+ "legs" to
819
+ route.directionsRoute.legs()?.map { leg ->
820
+ mapOf(
821
+ "steps" to
822
+ leg.steps()?.map { step ->
823
+ val stepGeometry = step.geometry()
824
+ val decodedPoints =
825
+ stepGeometry?.let {
826
+ PolylineUtils.decode(it, 6)
827
+ }
828
+ ?: emptyList()
829
+
830
+ mapOf(
831
+ "shape" to
832
+ mapOf(
833
+ "coordinates" to
834
+ decodedPoints.map {
835
+ point ->
836
+ mapOf(
837
+ "latitude" to
838
+ point.latitude(),
839
+ "longitude" to
840
+ point.longitude()
841
+ )
842
+ }
843
+ )
844
+ )
845
+ }
846
+ )
847
+ }
848
+ ) as
849
+ Map<String, Any>
850
+ }
851
+
852
+ private fun onRoutesReady(routes: List<NavigationRoute>) {
853
+ mapboxNavigation?.setNavigationRoutes(routes)
854
+ mapboxNavigation?.startTripSession(withForegroundService = false)
855
+ navigationCamera.requestNavigationCameraToFollowing(
856
+ stateTransitionOptions =
857
+ NavigationCameraTransitionOptions.Builder()
858
+ .maxDuration(0) // instant transition
859
+ .build()
860
+ )
861
+ }
862
+
863
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
864
+ fun setCoordinates(coordinates: List<Point>) {
865
+ currentCoordinates = coordinates
866
+ update()
867
+ }
868
+
869
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
870
+ fun setVehicleMaxHeight(maxHeight: Double?) {
871
+ vehicleMaxHeight = maxHeight
872
+ update()
873
+ }
874
+
875
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
876
+ fun setVehicleMaxWidth(maxWidth: Double?) {
877
+ vehicleMaxWidth = maxWidth
878
+ update()
879
+ }
880
+
881
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
882
+ fun setLocale(localeStr: String?) {
883
+ currentLocale =
884
+ if (localeStr == null || localeStr == "default") Locale.getDefault()
885
+ else Locale.Builder().setLanguageTag(localeStr).build()
886
+ update()
887
+ }
888
+
889
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
890
+ fun setWaypointIndices(indices: List<Int>?) {
891
+ currentWaypointIndices = indices
892
+ update()
893
+ }
894
+
895
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
896
+ fun setIsUsingRouteMatchingApi(useRouteMatchingApi: Boolean?) {
897
+ isUsingRouteMatchingApi = useRouteMatchingApi ?: false
898
+ update()
899
+ }
900
+
901
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
902
+ fun setRouteProfile(profile: String?) {
903
+ currentRouteProfile = profile
904
+ update()
905
+ }
906
+
907
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
908
+ fun setRouteExcludeList(excludeList: List<String>?) {
909
+ currentRouteExcludeList = excludeList
910
+ update()
911
+ }
912
+
913
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
914
+ fun setMapStyle(style: String?) {
915
+ currentMapStyle = style
916
+ update()
917
+ }
918
+
919
+ fun setIsMuted(isMutedProp: Boolean?) {
920
+ if (isMutedProp != null) {
921
+ isMuted = isMutedProp
922
+ voiceInstructionsPlayer.volume(SpeechVolume(if (isMuted) 0.0f else 1.0f))
923
+ soundButton
924
+ .findViewById<ImageView>(com.mapbox.navigation.ui.components.R.id.buttonIcon)
925
+ .setImageResource(if (isMuted) R.drawable.icon_mute else R.drawable.icon_sound)
926
+ }
927
+ }
928
+
929
+ fun setInitialLocation(initialLocation: Point?, zoom: Double?) {
930
+ if (initialLocation != null) {
931
+ val cameraOptions =
932
+ CameraOptions.Builder().center(initialLocation).zoom(zoom ?: 14.0).build()
933
+ mapView.getMapboxMap().setCamera(cameraOptions)
934
+ }
935
+ }
936
+
937
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
938
+ fun setCustomRasterSourceUrl(url: String?) {
939
+ currentCustomRasterSourceUrl = url
940
+ update()
941
+ }
942
+
943
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
944
+ fun setPlaceCustomRasterLayerAbove(layerId: String?) {
945
+ currentPlaceCustomRasterLayerAbove = layerId
946
+ update()
947
+ }
948
+
949
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
950
+ fun setDisableAlternativeRoutes(disableAlternativeRoutes: Boolean?) {
951
+ currentDisableAlternativeRoutes = disableAlternativeRoutes
952
+ update()
953
+ }
954
+
955
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
956
+ fun setFollowingZoom(followingZoom: Double?) {
957
+ currentFollowingZoom = followingZoom
958
+ viewportDataSource.followingZoomPropertyOverride(followingZoom)
959
+ viewportDataSource.evaluate()
960
+ }
961
+
962
+ fun recenterMap() {
963
+ navigationCamera.requestNavigationCameraToFollowing()
964
+ }
965
+
966
+ fun addCustomRasterLayer() {
967
+ val style = mapboxMap.getStyle() ?: return
968
+
969
+ val sourceId = "raster-source"
970
+ val layerId = "raster-layer"
971
+
972
+ if (style.styleLayerExists(layerId)) {
973
+ style.removeStyleLayer(layerId)
974
+ }
975
+ if (style.styleSourceExists(sourceId)) {
976
+ style.removeStyleSource(sourceId)
977
+ }
978
+
979
+ if (currentCustomRasterSourceUrl.isNullOrEmpty()) {
980
+ return
981
+ }
982
+
983
+ val rasterSource =
984
+ RasterSource.Builder(sourceId)
985
+ .tileSet(
986
+ TileSet.Builder("tileset", listOf(currentCustomRasterSourceUrl!!))
987
+ .build()
988
+ )
989
+ .tileSize(256)
990
+ .build()
991
+ style.addSource(rasterSource)
992
+
993
+ val rasterLayer = RasterLayer(layerId, sourceId)
994
+ val aboveLayerId = currentPlaceCustomRasterLayerAbove ?: "water"
995
+ style.addLayerAbove(rasterLayer, aboveLayerId)
996
+ }
997
+
998
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
999
+ private fun update() {
1000
+ voiceInstructionsPlayer =
1001
+ MapboxVoiceInstructionsPlayer(context, currentLocale.toLanguageTag())
1002
+ voiceInstructionsPlayer.volume(
1003
+ SpeechVolume(if (isMuted) 0.0f else 1.0f)
1004
+ ) // Initial volume based on current isMuted state
1005
+ speechApi = MapboxSpeechApi(context, currentLocale.toLanguageTag())
1006
+
1007
+ if (currentMapStyle != null) {
1008
+ mapboxMap.loadStyle(currentMapStyle!!) { style: Style ->
1009
+ mapboxStyle = style
1010
+ style.localizeLabels(currentLocale)
1011
+ addCustomRasterLayer()
1012
+ }
1013
+ } else {
1014
+ mapboxMap.getStyle { style: Style ->
1015
+ style.localizeLabels(currentLocale)
1016
+ addCustomRasterLayer()
1017
+ }
1018
+ }
1019
+
1020
+ val distanceFormatter =
1021
+ DistanceFormatterOptions.Builder(context).locale(currentLocale).build()
1022
+ maneuverApi = MapboxManeuverApi(MapboxDistanceFormatter(distanceFormatter))
1023
+
1024
+ tripProgressFormatter =
1025
+ TripProgressUpdateFormatter.Builder(context)
1026
+ .distanceRemainingFormatter(DistanceRemainingFormatter(distanceFormatter))
1027
+ .timeRemainingFormatter(TimeRemainingFormatter(context, currentLocale))
1028
+ .estimatedTimeToArrivalFormatter(EstimatedTimeToArrivalFormatter(context))
1029
+ .build()
1030
+ tripProgressApi = MapboxTripProgressApi(tripProgressFormatter)
1031
+
1032
+ if (currentMapMatchingRequestId != null) {
1033
+ mapboxNavigation?.cancelMapMatchingRequest(currentMapMatchingRequestId!!)
1034
+ }
1035
+
1036
+ if (currentRoutesRequestId != null) {
1037
+ mapboxNavigation?.cancelRouteRequest(currentRoutesRequestId!!)
1038
+ }
1039
+
1040
+ if (currentCoordinates != null) {
1041
+ if (isUsingRouteMatchingApi) {
1042
+ requestMapMatchingRoutes()
1043
+ } else {
1044
+ requestRoutes()
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ private fun requestRoutes() {
1050
+ var optionsBuilder =
1051
+ RouteOptions.builder()
1052
+ .applyDefaultNavigationOptions()
1053
+ .coordinatesList(currentCoordinates!!)
1054
+ .steps(true)
1055
+ .voiceInstructions(true)
1056
+ .language(currentLocale.toLanguageTag())
1057
+ .maxHeight(vehicleMaxHeight ?: null)
1058
+ .maxWidth(vehicleMaxWidth ?: null)
1059
+ .alternatives(currentDisableAlternativeRoutes != true)
1060
+
1061
+ if (currentWaypointIndices != null) {
1062
+ optionsBuilder = optionsBuilder.waypointIndicesList(currentWaypointIndices!!)
1063
+ }
1064
+
1065
+ if (currentRouteProfile != null) {
1066
+ optionsBuilder = optionsBuilder.profile(currentRouteProfile!!)
1067
+ }
1068
+
1069
+ if (currentRouteExcludeList != null) {
1070
+ optionsBuilder = optionsBuilder.excludeList(currentRouteExcludeList!!)
1071
+ }
1072
+
1073
+ currentRoutesRequestId =
1074
+ mapboxNavigation?.requestRoutes(optionsBuilder.build(), routesRequestCallback)
1075
+ }
1076
+
1077
+ @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
1078
+ private fun requestMapMatchingRoutes() {
1079
+ var optionsBuilder =
1080
+ MapMatchingOptions.Builder()
1081
+ .coordinates(currentCoordinates!!)
1082
+ .bannerInstructions(true)
1083
+ .voiceInstructions(true)
1084
+ .language(currentLocale.toLanguageTag())
1085
+
1086
+ if (currentWaypointIndices != null) {
1087
+ optionsBuilder = optionsBuilder.waypoints(currentWaypointIndices!!)
1088
+ }
1089
+
1090
+ if (currentRouteProfile != null) {
1091
+ optionsBuilder = optionsBuilder.profile(currentRouteProfile!!)
1092
+ }
1093
+
1094
+ currentMapMatchingRequestId =
1095
+ mapboxNavigation?.requestMapMatching(
1096
+ optionsBuilder.build(),
1097
+ mapMatchingRequestCallback
1098
+ )
1099
+ }
1100
+ }