@castelio-it/capacitor-mapboxnav 1.0.3

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 (41) hide show
  1. package/CapacitorMapboxnav.podspec +17 -0
  2. package/Package.swift +28 -0
  3. package/README.md +146 -0
  4. package/android/build.gradle +92 -0
  5. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  6. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  7. package/android/gradle.properties +22 -0
  8. package/android/gradlew +251 -0
  9. package/android/gradlew.bat +94 -0
  10. package/android/proguard-rules.pro +17 -0
  11. package/android/settings.gradle +2 -0
  12. package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +26 -0
  13. package/android/src/main/AndroidManifest.xml +24 -0
  14. package/android/src/main/java/com/castelioit/capacitormapboxnav/FreeDriveActivity.kt +126 -0
  15. package/android/src/main/java/com/castelioit/capacitormapboxnav/NavigationActivity.kt +276 -0
  16. package/android/src/main/java/com/castelioit/capacitormapboxnav/TurnByTurnExperienceActivity.kt +683 -0
  17. package/android/src/main/java/com/castelioit/capacitormapboxnav/capacitormapboxnav.kt +51 -0
  18. package/android/src/main/java/com/castelioit/capacitormapboxnav/capacitormapboxnavPlugin.java +142 -0
  19. package/android/src/main/res/.gitkeep +0 -0
  20. package/android/src/main/res/layout/mapbox_activity_free_drive.xml +25 -0
  21. package/android/src/main/res/layout/mapbox_activity_turn_by_turn_experience.xml +80 -0
  22. package/android/src/test/java/com/castelioit/capacitormapboxnav/capacitormapboxnavTest.kt +15 -0
  23. package/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +18 -0
  24. package/dist/docs.json +89 -0
  25. package/dist/esm/definitions.d.ts +58 -0
  26. package/dist/esm/definitions.js +2 -0
  27. package/dist/esm/definitions.js.map +1 -0
  28. package/dist/esm/index.d.ts +4 -0
  29. package/dist/esm/index.js +7 -0
  30. package/dist/esm/index.js.map +1 -0
  31. package/dist/esm/web.d.ts +35 -0
  32. package/dist/esm/web.js +20 -0
  33. package/dist/esm/web.js.map +1 -0
  34. package/dist/plugin.cjs.js +34 -0
  35. package/dist/plugin.cjs.js.map +1 -0
  36. package/dist/plugin.js +37 -0
  37. package/dist/plugin.js.map +1 -0
  38. package/ios/Sources/capacitormapboxnavPlugin/capacitormapboxnav.swift +8 -0
  39. package/ios/Sources/capacitormapboxnavPlugin/capacitormapboxnavPlugin.swift +33 -0
  40. package/ios/Tests/capacitormapboxnavPluginTests/capacitormapboxnavTests.swift +23 -0
  41. package/package.json +93 -0
@@ -0,0 +1,683 @@
1
+ package com.castelioit.capacitormapboxnav
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Intent
5
+ import android.content.res.Configuration
6
+ import android.content.res.Resources
7
+ import android.os.Bundle
8
+ import android.view.View
9
+ import android.widget.Toast
10
+ import androidx.appcompat.app.AppCompatActivity
11
+ import com.mapbox.api.directions.v5.models.Bearing
12
+ import com.mapbox.api.directions.v5.models.DirectionsRoute
13
+ import com.mapbox.api.directions.v5.models.RouteOptions
14
+ import com.mapbox.bindgen.Expected
15
+ import com.mapbox.common.location.Location
16
+ import com.mapbox.geojson.Point
17
+ import com.mapbox.maps.EdgeInsets
18
+ import com.mapbox.maps.ImageHolder
19
+ import com.mapbox.maps.plugin.PuckBearing
20
+ import com.mapbox.maps.plugin.LocationPuck2D
21
+ import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck
22
+ import com.mapbox.maps.plugin.animation.camera
23
+ import com.mapbox.maps.plugin.gestures.gestures
24
+ import com.mapbox.maps.plugin.locationcomponent.location
25
+ import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
26
+ import com.mapbox.navigation.base.TimeFormat
27
+ import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
28
+ import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
29
+ import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
30
+ import com.mapbox.navigation.base.options.NavigationOptions
31
+ import com.mapbox.navigation.base.route.NavigationRoute
32
+ import com.mapbox.navigation.base.route.NavigationRouterCallback
33
+ import com.mapbox.navigation.base.route.RouterFailure
34
+ import com.mapbox.navigation.core.MapboxNavigation
35
+ import com.mapbox.navigation.core.directions.session.RoutesObserver
36
+ import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter
37
+ import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
38
+ import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
39
+ import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation
40
+ import com.mapbox.navigation.core.replay.route.ReplayProgressObserver
41
+ import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
42
+ import com.mapbox.navigation.core.trip.session.LocationMatcherResult
43
+ import com.mapbox.navigation.core.trip.session.LocationObserver
44
+ import com.mapbox.navigation.core.trip.session.RouteProgressObserver
45
+ import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
46
+ import com.castelioit.capacitormapboxnav.databinding.MapboxActivityTurnByTurnExperienceBinding
47
+ import com.mapbox.maps.MapboxExperimental
48
+ import com.mapbox.navigation.tripdata.maneuver.api.MapboxManeuverApi
49
+ import com.mapbox.navigation.tripdata.progress.api.MapboxTripProgressApi
50
+ import com.mapbox.navigation.tripdata.progress.model.DistanceRemainingFormatter
51
+ import com.mapbox.navigation.tripdata.progress.model.EstimatedTimeToArrivalFormatter
52
+ import com.mapbox.navigation.tripdata.progress.model.PercentDistanceTraveledFormatter
53
+ import com.mapbox.navigation.tripdata.progress.model.TimeRemainingFormatter
54
+ import com.mapbox.navigation.tripdata.progress.model.TripProgressUpdateFormatter
55
+ import com.mapbox.navigation.ui.base.util.MapboxNavigationConsumer
56
+ import com.mapbox.navigation.ui.components.maneuver.view.MapboxManeuverView
57
+ import com.mapbox.navigation.ui.components.tripprogress.view.MapboxTripProgressView
58
+ import com.mapbox.navigation.ui.maps.NavigationStyles
59
+ import com.mapbox.navigation.ui.maps.camera.NavigationCamera
60
+ import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
61
+ import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler
62
+ import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState
63
+ import com.mapbox.navigation.ui.maps.camera.transition.NavigationCameraTransitionOptions
64
+ import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
65
+ import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApi
66
+ import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowView
67
+ import com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptions
68
+ import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApi
69
+ import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineView
70
+ import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineApiOptions
71
+ import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineViewOptions
72
+ import com.mapbox.navigation.voice.api.MapboxSpeechApi
73
+ import com.mapbox.navigation.voice.api.MapboxVoiceInstructionsPlayer
74
+ import com.mapbox.navigation.voice.model.SpeechAnnouncement
75
+ import com.mapbox.navigation.voice.model.SpeechError
76
+ import com.mapbox.navigation.voice.model.SpeechValue
77
+ import com.mapbox.navigation.voice.model.SpeechVolume
78
+ import java.util.Date
79
+ import java.util.Locale
80
+
81
+ /**
82
+ * This example demonstrates a basic turn-by-turn navigation experience by putting together some UI elements to showcase
83
+ * navigation camera transitions, guidance instructions banners and playback, and progress along the route.
84
+ */
85
+ @OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
86
+ class TurnByTurnExperienceActivity : AppCompatActivity() {
87
+
88
+ private companion object {
89
+ private const val BUTTON_ANIMATION_DURATION = 1500L
90
+ }
91
+
92
+ private var originLat: Double = 0.0
93
+ private var originLng: Double = 0.0
94
+ private var destLat: Double = 0.0
95
+ private var destLng: Double = 0.0
96
+ private var simulateRoute: Boolean = false
97
+
98
+ /**
99
+ * Debug observer that makes sure the replayer has always an up-to-date information to generate mock updates.
100
+ */
101
+ private lateinit var replayProgressObserver: ReplayProgressObserver
102
+
103
+ /**
104
+ * Debug object that converts a route into events that can be replayed to navigate a route.
105
+ */
106
+ private val replayRouteMapper = ReplayRouteMapper()
107
+
108
+ /**
109
+ * Bindings to the example layout.
110
+ */
111
+ private lateinit var binding: MapboxActivityTurnByTurnExperienceBinding
112
+
113
+ /**
114
+ * Used to execute camera transitions based on the data generated by the [viewportDataSource].
115
+ * This includes transitions from route overview to route following and continuously updating the camera as the location changes.
116
+ */
117
+ private lateinit var navigationCamera: NavigationCamera
118
+
119
+ /**
120
+ * Produces the camera frames based on the location and routing data for the [navigationCamera] to execute.
121
+ */
122
+ private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
123
+
124
+ /*
125
+ * Below are generated camera padding values to ensure that the route fits well on screen while
126
+ * other elements are overlaid on top of the map (including instruction view, buttons, etc.)
127
+ */
128
+ private val pixelDensity = Resources.getSystem().displayMetrics.density
129
+ private val overviewPadding: EdgeInsets by lazy {
130
+ EdgeInsets(
131
+ 140.0 * pixelDensity,
132
+ 40.0 * pixelDensity,
133
+ 120.0 * pixelDensity,
134
+ 40.0 * pixelDensity
135
+ )
136
+ }
137
+ private val landscapeOverviewPadding: EdgeInsets by lazy {
138
+ EdgeInsets(
139
+ 30.0 * pixelDensity,
140
+ 380.0 * pixelDensity,
141
+ 110.0 * pixelDensity,
142
+ 20.0 * pixelDensity
143
+ )
144
+ }
145
+ private val followingPadding: EdgeInsets by lazy {
146
+ EdgeInsets(
147
+ 180.0 * pixelDensity,
148
+ 40.0 * pixelDensity,
149
+ 150.0 * pixelDensity,
150
+ 40.0 * pixelDensity
151
+ )
152
+ }
153
+ private val landscapeFollowingPadding: EdgeInsets by lazy {
154
+ EdgeInsets(
155
+ 30.0 * pixelDensity,
156
+ 380.0 * pixelDensity,
157
+ 110.0 * pixelDensity,
158
+ 40.0 * pixelDensity
159
+ )
160
+ }
161
+
162
+ /**
163
+ * Generates updates for the [MapboxManeuverView] to display the upcoming maneuver instructions
164
+ * and remaining distance to the maneuver point.
165
+ */
166
+ private lateinit var maneuverApi: MapboxManeuverApi
167
+
168
+ /**
169
+ * Generates updates for the [MapboxTripProgressView] that include remaining time and distance to the destination.
170
+ */
171
+ private lateinit var tripProgressApi: MapboxTripProgressApi
172
+
173
+ /**
174
+ * Generates updates for the [routeLineView] with the geometries and properties of the routes that should be drawn on the map.
175
+ */
176
+ private lateinit var routeLineApi: MapboxRouteLineApi
177
+
178
+ /**
179
+ * Draws route lines on the map based on the data from the [routeLineApi]
180
+ */
181
+ private lateinit var routeLineView: MapboxRouteLineView
182
+
183
+ /**
184
+ * Generates updates for the [routeArrowView] with the geometries and properties of maneuver arrows that should be drawn on the map.
185
+ */
186
+ private val routeArrowApi: MapboxRouteArrowApi = MapboxRouteArrowApi()
187
+
188
+ /**
189
+ * Draws maneuver arrows on the map based on the data [routeArrowApi].
190
+ */
191
+ private lateinit var routeArrowView: MapboxRouteArrowView
192
+
193
+ /**
194
+ * Stores and updates the state of whether the voice instructions should be played as they come or muted.
195
+ */
196
+ private var isVoiceInstructionsMuted = false
197
+ set(value) {
198
+ field = value
199
+ if (value) {
200
+ binding.soundButton.muteAndExtend(BUTTON_ANIMATION_DURATION)
201
+ voiceInstructionsPlayer.volume(SpeechVolume(0f))
202
+ } else {
203
+ binding.soundButton.unmuteAndExtend(BUTTON_ANIMATION_DURATION)
204
+ voiceInstructionsPlayer.volume(SpeechVolume(1f))
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Extracts message that should be communicated to the driver about the upcoming maneuver.
210
+ * When possible, downloads a synthesized audio file that can be played back to the driver.
211
+ */
212
+ private lateinit var speechApi: MapboxSpeechApi
213
+
214
+ /**
215
+ * Plays the synthesized audio files with upcoming maneuver instructions
216
+ * or uses an on-device Text-To-Speech engine to communicate the message to the driver.
217
+ */
218
+ private lateinit var voiceInstructionsPlayer: MapboxVoiceInstructionsPlayer
219
+
220
+ /**
221
+ * Observes when a new voice instruction should be played.
222
+ */
223
+ private val voiceInstructionsObserver = VoiceInstructionsObserver { voiceInstructions ->
224
+ speechApi.generate(voiceInstructions, speechCallback)
225
+ }
226
+
227
+ /**
228
+ * Based on whether the synthesized audio file is available, the callback plays the file
229
+ * or uses the fall back which is played back using the on-device Text-To-Speech engine.
230
+ */
231
+ private val speechCallback =
232
+ MapboxNavigationConsumer<Expected<SpeechError, SpeechValue>> { expected ->
233
+ expected.fold(
234
+ { error ->
235
+ // play the instruction via fallback text-to-speech engine
236
+ voiceInstructionsPlayer.play(
237
+ error.fallback,
238
+ voiceInstructionsPlayerCallback
239
+ )
240
+ },
241
+ { value ->
242
+ // play the sound file from the external generator
243
+ voiceInstructionsPlayer.play(
244
+ value.announcement,
245
+ voiceInstructionsPlayerCallback
246
+ )
247
+ }
248
+ )
249
+ }
250
+
251
+ /**
252
+ * When a synthesized audio file was downloaded, this callback cleans up the disk after it was played.
253
+ */
254
+ private val voiceInstructionsPlayerCallback =
255
+ MapboxNavigationConsumer<SpeechAnnouncement> { value ->
256
+ // remove already consumed file to free-up space
257
+ speechApi.clean(value)
258
+ }
259
+
260
+ /**
261
+ * [NavigationLocationProvider] is a utility class that helps to provide location updates generated by the Navigation SDK
262
+ * to the Maps SDK in order to update the user location indicator on the map.
263
+ */
264
+ private val navigationLocationProvider = NavigationLocationProvider()
265
+
266
+ /**
267
+ * Gets notified with location updates.
268
+ *
269
+ * Exposes raw updates coming directly from the location services
270
+ * and the updates enhanced by the Navigation SDK (cleaned up and matched to the road).
271
+ */
272
+ private val locationObserver = object : LocationObserver {
273
+ var firstLocationUpdateReceived = false
274
+
275
+ override fun onNewRawLocation(rawLocation: Location) {
276
+ // not handled
277
+ }
278
+
279
+ override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
280
+ val enhancedLocation = locationMatcherResult.enhancedLocation
281
+ // update location puck's position on the map
282
+ navigationLocationProvider.changePosition(
283
+ location = enhancedLocation,
284
+ keyPoints = locationMatcherResult.keyPoints,
285
+ )
286
+
287
+ // update camera position to account for new location
288
+ viewportDataSource.onLocationChanged(enhancedLocation)
289
+ viewportDataSource.evaluate()
290
+
291
+ // if this is the first location update the activity has received,
292
+ // it's best to immediately move the camera to the current user location
293
+ if (!firstLocationUpdateReceived) {
294
+ firstLocationUpdateReceived = true
295
+ navigationCamera.requestNavigationCameraToOverview(
296
+ stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
297
+ .maxDuration(0) // instant transition
298
+ .build()
299
+ )
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Gets notified with progress along the currently active route.
306
+ */
307
+ private val routeProgressObserver = RouteProgressObserver { routeProgress ->
308
+ // update the camera position to account for the progressed fragment of the route
309
+ viewportDataSource.onRouteProgressChanged(routeProgress)
310
+ viewportDataSource.evaluate()
311
+
312
+ // draw the upcoming maneuver arrow on the map
313
+ val style = binding.mapView.mapboxMap.style
314
+ if (style != null) {
315
+ val maneuverArrowResult = routeArrowApi.addUpcomingManeuverArrow(routeProgress)
316
+ routeArrowView.renderManeuverUpdate(style, maneuverArrowResult)
317
+ }
318
+
319
+ // update top banner with maneuver instructions
320
+ val maneuvers = maneuverApi.getManeuvers(routeProgress)
321
+ maneuvers.fold(
322
+ { error ->
323
+ Toast.makeText(
324
+ this@TurnByTurnExperienceActivity,
325
+ error.errorMessage,
326
+ Toast.LENGTH_SHORT
327
+ ).show()
328
+ },
329
+ {
330
+ binding.maneuverView.visibility = View.VISIBLE
331
+ binding.maneuverView.renderManeuvers(maneuvers)
332
+ }
333
+ )
334
+
335
+ // update bottom trip progress summary
336
+ binding.tripProgressView.render(
337
+ tripProgressApi.getTripProgress(routeProgress)
338
+ )
339
+ }
340
+
341
+ /**
342
+ * Gets notified whenever the tracked routes change.
343
+ */
344
+ private val routesObserver = RoutesObserver { routeUpdateResult ->
345
+ if (routeUpdateResult.navigationRoutes.isNotEmpty()) {
346
+ // generate route geometries asynchronously and render them
347
+ routeLineApi.setNavigationRoutes(
348
+ routeUpdateResult.navigationRoutes
349
+ ) { value ->
350
+ binding.mapView.mapboxMap.style?.apply {
351
+ routeLineView.renderRouteDrawData(this, value)
352
+ }
353
+ }
354
+
355
+ // update the camera position to account for the new route
356
+ viewportDataSource.onRouteChanged(routeUpdateResult.navigationRoutes.first())
357
+ viewportDataSource.evaluate()
358
+ } else {
359
+ // remove the route line and route arrow from the map
360
+ val style = binding.mapView.mapboxMap.style
361
+ if (style != null) {
362
+ routeLineApi.clearRouteLine { value ->
363
+ routeLineView.renderClearRouteLineValue(
364
+ style,
365
+ value
366
+ )
367
+ }
368
+ routeArrowView.render(style, routeArrowApi.clearArrows())
369
+ }
370
+
371
+ // remove the route reference from camera position evaluations
372
+ viewportDataSource.clearRouteData()
373
+ viewportDataSource.evaluate()
374
+ }
375
+ }
376
+
377
+ private val mapboxNavigation: MapboxNavigation by requireMapboxNavigation(
378
+ onResumedObserver = object : MapboxNavigationObserver {
379
+ @SuppressLint("MissingPermission")
380
+ override fun onAttached(mapboxNavigation: MapboxNavigation) {
381
+ mapboxNavigation.registerRoutesObserver(routesObserver)
382
+ mapboxNavigation.registerLocationObserver(locationObserver)
383
+ mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
384
+ mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
385
+
386
+ if (simulateRoute) {
387
+ replayProgressObserver = ReplayProgressObserver(mapboxNavigation.mapboxReplayer)
388
+ mapboxNavigation.registerRouteProgressObserver(replayProgressObserver)
389
+ mapboxNavigation.startReplayTripSession()
390
+ } else {
391
+ mapboxNavigation.startTripSession()
392
+ }
393
+ }
394
+
395
+ override fun onDetached(mapboxNavigation: MapboxNavigation) {
396
+ mapboxNavigation.unregisterRoutesObserver(routesObserver)
397
+ mapboxNavigation.unregisterLocationObserver(locationObserver)
398
+ mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
399
+ if (simulateRoute && ::replayProgressObserver.isInitialized) {
400
+ mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver)
401
+ }
402
+ mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
403
+ if (simulateRoute) {
404
+ mapboxNavigation.mapboxReplayer.finish()
405
+ }
406
+ }
407
+ },
408
+ onInitialize = this::initNavigation
409
+ )
410
+
411
+ private fun Intent.getExtraDouble(key: String, defaultValue: Double): Double {
412
+ return if (hasExtra(key)) getDoubleExtra(key, defaultValue) else defaultValue
413
+ }
414
+
415
+ @SuppressLint("MissingPermission")
416
+ override fun onCreate(savedInstanceState: Bundle?) {
417
+ super.onCreate(savedInstanceState)
418
+ binding = MapboxActivityTurnByTurnExperienceBinding.inflate(layoutInflater)
419
+ setContentView(binding.root)
420
+
421
+ originLat = intent.getExtraDouble("originLat", 0.0)
422
+ originLng = intent.getExtraDouble("originLng", 0.0)
423
+ destLat = intent.getExtraDouble("destLat", 0.0)
424
+ destLng = intent.getExtraDouble("destLng", 0.0)
425
+ simulateRoute = intent.getBooleanExtra("simulateRoute", false)
426
+
427
+ // initialize Navigation Camera
428
+ viewportDataSource = MapboxNavigationViewportDataSource(binding.mapView.mapboxMap)
429
+ navigationCamera = NavigationCamera(
430
+ binding.mapView.mapboxMap,
431
+ binding.mapView.camera,
432
+ viewportDataSource
433
+ )
434
+ // set the animations lifecycle listener to ensure the NavigationCamera stops
435
+ // automatically following the user location when the map is interacted with
436
+ binding.mapView.camera.addCameraAnimationsLifecycleListener(
437
+ NavigationBasicGesturesHandler(navigationCamera)
438
+ )
439
+ navigationCamera.registerNavigationCameraStateChangeObserver { navigationCameraState ->
440
+ // shows/hide the recenter button depending on the camera state
441
+ when (navigationCameraState) {
442
+ NavigationCameraState.TRANSITION_TO_FOLLOWING,
443
+ NavigationCameraState.FOLLOWING -> binding.recenter.visibility = View.INVISIBLE
444
+ NavigationCameraState.TRANSITION_TO_OVERVIEW,
445
+ NavigationCameraState.OVERVIEW,
446
+ NavigationCameraState.IDLE -> binding.recenter.visibility = View.VISIBLE
447
+ }
448
+ }
449
+ // set the padding values depending on screen orientation and visible view layout
450
+ if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
451
+ viewportDataSource.overviewPadding = landscapeOverviewPadding
452
+ } else {
453
+ viewportDataSource.overviewPadding = overviewPadding
454
+ }
455
+ if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
456
+ viewportDataSource.followingPadding = landscapeFollowingPadding
457
+ } else {
458
+ viewportDataSource.followingPadding = followingPadding
459
+ }
460
+
461
+
462
+
463
+ // make sure to use the same DistanceFormatterOptions across different features
464
+ val distanceFormatterOptions = DistanceFormatterOptions.Builder(this).build()
465
+
466
+ // initialize maneuver api that feeds the data to the top banner maneuver view
467
+ maneuverApi = MapboxManeuverApi(
468
+ MapboxDistanceFormatter(distanceFormatterOptions)
469
+ )
470
+
471
+ // initialize bottom progress view
472
+ tripProgressApi = MapboxTripProgressApi(
473
+ TripProgressUpdateFormatter.Builder(this)
474
+ .distanceRemainingFormatter(
475
+ DistanceRemainingFormatter(distanceFormatterOptions)
476
+ )
477
+ .timeRemainingFormatter(
478
+ TimeRemainingFormatter(this)
479
+ )
480
+ .percentRouteTraveledFormatter(
481
+ PercentDistanceTraveledFormatter()
482
+ )
483
+ .estimatedTimeToArrivalFormatter(
484
+ EstimatedTimeToArrivalFormatter(this, TimeFormat.NONE_SPECIFIED)
485
+ )
486
+ .build()
487
+ )
488
+
489
+ // initialize voice instructions api and the voice instruction player
490
+ speechApi = MapboxSpeechApi(
491
+ this,
492
+ Locale.US.language
493
+ )
494
+ voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
495
+ this,
496
+ Locale.US.language
497
+ )
498
+
499
+ // initialize route line, the routeLineBelowLayerId is specified to place
500
+ // the route line below road labels layer on the map
501
+ val mapboxRouteLineViewOptions = MapboxRouteLineViewOptions.Builder(this)
502
+ .routeLineBelowLayerId("road-label-navigation")
503
+ .build()
504
+
505
+ routeLineApi = MapboxRouteLineApi(MapboxRouteLineApiOptions.Builder().build())
506
+ routeLineView = MapboxRouteLineView(mapboxRouteLineViewOptions)
507
+
508
+ // initialize maneuver arrow view to draw arrows on the map
509
+ val routeArrowOptions = RouteArrowOptions.Builder(this).build()
510
+ routeArrowView = MapboxRouteArrowView(routeArrowOptions)
511
+
512
+ // load map style
513
+ binding.mapView.mapboxMap.loadStyle(NavigationStyles.NAVIGATION_DAY_STYLE) {
514
+ // Ensure that the route line related layers are present before the route arrow
515
+ routeLineView.initializeLayers(it)
516
+
517
+ // If we have destination, find route immediately
518
+ if (destLat != 0.0 && destLng != 0.0) {
519
+ findRoute(Point.fromLngLat(destLng, destLat))
520
+ }
521
+
522
+ // add long click listener that search for a route to the clicked destination
523
+ binding.mapView.gestures.addOnMapLongClickListener { point ->
524
+ findRoute(point)
525
+ true
526
+ }
527
+ }
528
+
529
+ // initialize view interactions
530
+ binding.stop.setOnClickListener {
531
+ clearRouteAndStopNavigation()
532
+ finish()
533
+ }
534
+ binding.recenter.setOnClickListener {
535
+ navigationCamera.requestNavigationCameraToFollowing()
536
+ binding.routeOverview.showTextAndExtend(BUTTON_ANIMATION_DURATION)
537
+ }
538
+ binding.routeOverview.setOnClickListener {
539
+ navigationCamera.requestNavigationCameraToOverview()
540
+ binding.recenter.showTextAndExtend(BUTTON_ANIMATION_DURATION)
541
+ }
542
+ binding.soundButton.setOnClickListener {
543
+ // mute/unmute voice instructions
544
+ isVoiceInstructionsMuted = !isVoiceInstructionsMuted
545
+ }
546
+
547
+ // set initial sounds button state
548
+ binding.soundButton.unmute()
549
+ }
550
+
551
+ override fun onStart() {
552
+ super.onStart()
553
+ binding.mapView.onStart()
554
+ }
555
+
556
+ override fun onStop() {
557
+ super.onStop()
558
+ binding.mapView.onStop()
559
+ }
560
+
561
+ override fun onLowMemory() {
562
+ super.onLowMemory()
563
+ binding.mapView.onLowMemory()
564
+ }
565
+
566
+ override fun onDestroy() {
567
+ super.onDestroy()
568
+ binding.mapView.onDestroy()
569
+ maneuverApi.cancel()
570
+ routeLineApi.cancel()
571
+ routeLineView.cancel()
572
+ speechApi.cancel()
573
+ voiceInstructionsPlayer.shutdown()
574
+ }
575
+
576
+ @OptIn(MapboxExperimental::class)
577
+ private fun initNavigation() {
578
+ // initialize location puck
579
+ binding.mapView.location.apply {
580
+ setLocationProvider(navigationLocationProvider)
581
+ this.locationPuck = createDefault2DPuck( true)
582
+ puckBearingEnabled = true
583
+ puckBearing = PuckBearing.COURSE
584
+ enabled = true
585
+ }
586
+ }
587
+
588
+ private fun findRoute(destination: Point) {
589
+ val originLocation = navigationLocationProvider.lastLocation
590
+ val originPoint = if (originLocation != null) {
591
+ Point.fromLngLat(originLocation.longitude, originLocation.latitude)
592
+ } else {
593
+ Point.fromLngLat(originLng, originLat)
594
+ }
595
+
596
+ mapboxNavigation.requestRoutes(
597
+ RouteOptions.builder()
598
+ .applyDefaultNavigationOptions()
599
+ .applyLanguageAndVoiceUnitOptions(this)
600
+ .coordinatesList(listOf(originPoint, destination))
601
+ .apply {
602
+ originLocation?.bearing?.let { bearing ->
603
+ bearingsList(
604
+ listOf(
605
+ Bearing.builder()
606
+ .angle(bearing)
607
+ .degrees(45.0)
608
+ .build(),
609
+ null
610
+ )
611
+ )
612
+ }
613
+ }
614
+ .layersList(listOf(mapboxNavigation.getZLevel(), null))
615
+ .build(),
616
+ object : NavigationRouterCallback {
617
+ override fun onCanceled(routeOptions: RouteOptions, routerOrigin: String) {
618
+ // no impl
619
+ }
620
+
621
+ override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
622
+ Toast.makeText(this@TurnByTurnExperienceActivity, "Route request failed", Toast.LENGTH_SHORT).show()
623
+ }
624
+
625
+ override fun onRoutesReady(
626
+ routes: List<NavigationRoute>,
627
+ routerOrigin: String
628
+ ) {
629
+ setRouteAndStartNavigation(routes)
630
+ }
631
+ }
632
+ )
633
+ }
634
+
635
+ private fun setRouteAndStartNavigation(routes: List<NavigationRoute>) {
636
+ // set routes, where the first route in the list is the primary route that
637
+ // will be used for active guidance
638
+ mapboxNavigation.setNavigationRoutes(routes)
639
+
640
+ // show UI elements
641
+ binding.soundButton.visibility = View.VISIBLE
642
+ binding.routeOverview.visibility = View.VISIBLE
643
+ binding.tripProgressCard.visibility = View.VISIBLE
644
+
645
+ // move the camera to overview when new route is available
646
+ navigationCamera.requestNavigationCameraToOverview()
647
+
648
+ // start simulation if requested
649
+ if (simulateRoute) {
650
+ startSimulation(routes.first().directionsRoute)
651
+ }
652
+ }
653
+
654
+ private fun clearRouteAndStopNavigation() {
655
+ // clear
656
+ mapboxNavigation.setNavigationRoutes(listOf())
657
+
658
+ // stop simulation
659
+ if (simulateRoute) {
660
+ stopSimulation()
661
+ }
662
+
663
+ // hide UI elements
664
+ binding.soundButton.visibility = View.INVISIBLE
665
+ binding.maneuverView.visibility = View.INVISIBLE
666
+ binding.routeOverview.visibility = View.INVISIBLE
667
+ binding.tripProgressCard.visibility = View.INVISIBLE
668
+ }
669
+
670
+ private fun startSimulation(route: DirectionsRoute) {
671
+ mapboxNavigation.mapboxReplayer.stop()
672
+ mapboxNavigation.mapboxReplayer.clearEvents()
673
+ val replayData = replayRouteMapper.mapDirectionsRouteGeometry(route)
674
+ mapboxNavigation.mapboxReplayer.pushEvents(replayData)
675
+ mapboxNavigation.mapboxReplayer.seekTo(replayData[0])
676
+ mapboxNavigation.mapboxReplayer.play()
677
+ }
678
+
679
+ private fun stopSimulation() {
680
+ mapboxNavigation.mapboxReplayer.stop()
681
+ mapboxNavigation.mapboxReplayer.clearEvents()
682
+ }
683
+ }