Skip to content

Commit 1c7d7fa

Browse files
committed
implemented route privew
1 parent 8aaca27 commit 1c7d7fa

File tree

17 files changed

+1136
-14
lines changed

17 files changed

+1136
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ This release depends on, and has been tested with, the following Mapbox dependen
8282
- Added `ViewBinderCustomization.infoPanelRoutePreviewButtonBinder` and `ViewBinderCustomization.infoPanelStartNavigationButtonBinder` that allows injection of custom Info Panel buttons for Route Preview and Start Navigation in `NavigationView`. [#6490](https://github.com/mapbox/mapbox-navigation-android/pull/6490)
8383
- Added `ViewBinderCustomization.actionCompassButtonBinder`, `ViewBinderCustomization.actionCameraModeButtonBinder`, `ViewBinderCustomization.actionToggleAudioButtonBinder` and `ViewBinderCustomization.actionRecenterButtonBinder` that allows injection of a custom Compass, Camera Mode, Toggle Voice Instructions and Camera Recenter Buttons in `NavigationView`. [#6490](https://github.com/mapbox/mapbox-navigation-android/pull/6490)
8484
- Introduced `ActionButtonsBinder` base `UIBinder` class, that allows use of a custom action buttons layout with default action buttons in `NavigationView`. [#6490](https://github.com/mapbox/mapbox-navigation-android/pull/6490)
85+
- Added experimental routes preview state, see `MapboxNavigaton#setRoutesPreview`, `MapboxNavigaton#setRoutesPreviewPrimaryRoute`, `MapboxNavigaton#registerRoutesPreviewObserver`, `MapboxNavigaton#getRoutesPreview`. [#6495](https://github.com/mapbox/mapbox-navigation-android/pull/6495)
8586
#### Bug fixes and improvements
8687
- Improved precision of congestion visualization and independent leg styling transitions, especially for long routes. [#6476](https://github.com/mapbox/mapbox-navigation-android/pull/6476)
8788
- Fixed an issue with congestion visualization on the route line that occurred during route refreshes. [#6476](https://github.com/mapbox/mapbox-navigation-android/pull/6476)

examples/src/main/java/com/mapbox/navigation/examples/core/MapboxNavigationActivity.kt

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.res.Configuration
55
import android.content.res.Resources
66
import android.location.Location
77
import android.os.Bundle
8+
import android.view.View
89
import android.view.View.INVISIBLE
910
import android.view.View.VISIBLE
1011
import android.widget.Toast
@@ -19,8 +20,10 @@ import com.mapbox.maps.MapboxMap
1920
import com.mapbox.maps.Style.Companion.MAPBOX_STREETS
2021
import com.mapbox.maps.plugin.LocationPuck2D
2122
import com.mapbox.maps.plugin.animation.camera
23+
import com.mapbox.maps.plugin.gestures.OnMapClickListener
2224
import com.mapbox.maps.plugin.gestures.gestures
2325
import com.mapbox.maps.plugin.locationcomponent.location
26+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
2427
import com.mapbox.navigation.base.TimeFormat
2528
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
2629
import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
@@ -35,6 +38,7 @@ import com.mapbox.navigation.core.MapboxNavigation
3538
import com.mapbox.navigation.core.MapboxNavigationProvider
3639
import com.mapbox.navigation.core.directions.session.RoutesObserver
3740
import com.mapbox.navigation.core.formatter.MapboxDistanceFormatter
41+
import com.mapbox.navigation.core.preview.RoutesPreviewObserver
3842
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
3943
import com.mapbox.navigation.core.trip.session.LocationObserver
4044
import com.mapbox.navigation.core.trip.session.NavigationSessionStateObserver
@@ -52,11 +56,10 @@ import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
5256
import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApi
5357
import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowView
5458
import com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptions
55-
import com.mapbox.navigation.ui.maps.route.line.MapboxRouteLineApiExtensions.setRoutes
59+
import com.mapbox.navigation.ui.maps.route.line.MapboxRouteLineApiExtensions.findClosestRoute
5660
import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApi
5761
import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineView
5862
import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptions
59-
import com.mapbox.navigation.ui.maps.route.line.model.RouteLine
6063
import com.mapbox.navigation.ui.tripprogress.api.MapboxTripProgressApi
6164
import com.mapbox.navigation.ui.tripprogress.model.DistanceRemainingFormatter
6265
import com.mapbox.navigation.ui.tripprogress.model.EstimatedTimeToArrivalFormatter
@@ -75,6 +78,7 @@ import kotlinx.coroutines.Dispatchers
7578
import kotlinx.coroutines.launch
7679
import java.util.Locale
7780

81+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
7882
class MapboxNavigationActivity : AppCompatActivity() {
7983

8084
/* ----- Layout binding reference ----- */
@@ -89,6 +93,8 @@ class MapboxNavigationActivity : AppCompatActivity() {
8993
// location puck integration
9094
private val navigationLocationProvider = NavigationLocationProvider()
9195

96+
private val waypoints = mutableListOf<Point>()
97+
9298
// camera
9399
private lateinit var navigationCamera: NavigationCamera
94100
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
@@ -229,18 +235,17 @@ class MapboxNavigationActivity : AppCompatActivity() {
229235
}
230236

231237
private val routesObserver = RoutesObserver { result ->
232-
if (result.routes.isNotEmpty()) {
238+
if (result.navigationRoutes.isNotEmpty()) {
233239
// generate route geometries asynchronously and render them
234-
CoroutineScope(Dispatchers.Main).launch {
235-
val result = routeLineAPI.setRoutes(
236-
listOf(RouteLine(result.routes.first(), null))
237-
)
240+
routeLineAPI.setNavigationRoutes(
241+
result.navigationRoutes,
242+
mapboxNavigation.getAlternativeMetadataFor(result.navigationRoutes)
243+
) {
238244
val style = mapboxMap.getStyle()
239245
if (style != null) {
240-
routeLineView.renderRouteDrawData(style, result)
246+
routeLineView.renderRouteDrawData(style, it)
241247
}
242248
}
243-
244249
// update the camera position to account for the new route
245250
viewportDataSource.onRouteChanged(result.routes.first())
246251
viewportDataSource.evaluate()
@@ -263,11 +268,52 @@ class MapboxNavigationActivity : AppCompatActivity() {
263268
}
264269
}
265270

271+
private val routesPreviewObserver = RoutesPreviewObserver { update ->
272+
val routePreview = update.routesPreview
273+
if (routePreview != null) {
274+
routeLineAPI.setNavigationRoutes(
275+
routePreview.routesList,
276+
routePreview.alternativesMetadata
277+
) {
278+
val style = mapboxMap.getStyle()
279+
if (style != null) {
280+
routeLineView.renderRouteDrawData(style, it)
281+
}
282+
}
283+
// update the camera position to account for the new route
284+
viewportDataSource.onRouteChanged(routePreview.primaryRoute)
285+
viewportDataSource.evaluate()
286+
287+
binding.mapView.gestures.removeOnMapClickListener(previewMapClickListener)
288+
binding.mapView.gestures.addOnMapClickListener(previewMapClickListener)
289+
} else {
290+
binding.mapView.gestures.removeOnMapClickListener(previewMapClickListener)
291+
}
292+
}
293+
266294
private val navigationSessionStateObserver = NavigationSessionStateObserver {
267295
logD("NavigationSessionState=$it", LOG_CATEGORY)
268296
logD("sessionId=${mapboxNavigation.getNavigationSessionState().sessionId}", LOG_CATEGORY)
269297
}
270298

299+
private val routeClickPadding = com.mapbox.android.gestures.Utils.dpToPx(30f)
300+
301+
private val previewMapClickListener = OnMapClickListener {
302+
CoroutineScope(Dispatchers.Main).launch {
303+
val result = routeLineAPI.findClosestRoute(
304+
it,
305+
binding.mapView.getMapboxMap(),
306+
routeClickPadding
307+
)
308+
309+
val routeFound = result.value?.navigationRoute
310+
if (routeFound != null && routeFound != routeLineAPI.getPrimaryNavigationRoute()) {
311+
mapboxNavigation.changeRoutesPreviewPrimaryRoute(routeFound)
312+
}
313+
}
314+
false
315+
}
316+
271317
@SuppressLint("MissingPermission")
272318
override fun onCreate(savedInstanceState: Bundle?) {
273319
super.onCreate(savedInstanceState)
@@ -438,6 +484,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
438484
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
439485
mapboxNavigation.registerLocationObserver(locationObserver)
440486
mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
487+
mapboxNavigation.registerRoutesPreviewObserver(routesPreviewObserver)
441488
}
442489

443490
override fun onStop() {
@@ -447,6 +494,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
447494
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
448495
mapboxNavigation.unregisterLocationObserver(locationObserver)
449496
mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
497+
mapboxNavigation.unregisterRoutesPreviewObserver(routesPreviewObserver)
450498
}
451499

452500
override fun onDestroy() {
@@ -464,19 +512,23 @@ class MapboxNavigationActivity : AppCompatActivity() {
464512
Point.fromLngLat(it.longitude, it.latitude)
465513
} ?: return
466514

515+
waypoints.add(destination)
516+
val coordinates = listOf(origin) + waypoints
517+
val layersList = listOf(mapboxNavigation.getZLevel()) + waypoints.map { null }
467518
mapboxNavigation.requestRoutes(
468519
RouteOptions.builder()
469520
.applyDefaultNavigationOptions()
470521
.applyLanguageAndVoiceUnitOptions(this)
471-
.coordinatesList(listOf(origin, destination))
472-
.layersList(listOf(mapboxNavigation.getZLevel(), null))
522+
.alternatives(true)
523+
.coordinatesList(coordinates)
524+
.layersList(layersList)
473525
.build(),
474526
object : NavigationRouterCallback {
475527
override fun onRoutesReady(
476528
routes: List<NavigationRoute>,
477529
routerOrigin: RouterOrigin
478530
) {
479-
setRouteAndStartNavigation(routes)
531+
setRoutesPreview(routes)
480532
}
481533

482534
override fun onFailure(
@@ -493,6 +545,19 @@ class MapboxNavigationActivity : AppCompatActivity() {
493545
)
494546
}
495547

548+
private fun setRoutesPreview(routes: List<NavigationRoute>) {
549+
binding.navigateButton.apply {
550+
visibility = View.VISIBLE
551+
setOnClickListener {
552+
visibility = View.GONE
553+
setRouteAndStartNavigation(mapboxNavigation.getRoutesPreview()!!.routesList)
554+
mapboxNavigation.setRoutesPreview(emptyList())
555+
waypoints.clear()
556+
}
557+
}
558+
mapboxNavigation.setRoutesPreview(routes)
559+
}
560+
496561
private fun setRouteAndStartNavigation(route: List<NavigationRoute>) {
497562
// set route
498563
mapboxNavigation.setNavigationRoutes(route)

examples/src/main/res/layout/layout_activity_navigation.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:app="http://schemas.android.com/apk/res-auto"
44
android:layout_width="match_parent"
5-
android:layout_height="match_parent">
5+
android:layout_height="match_parent"
6+
xmlns:tools="http://schemas.android.com/tools"
7+
>
68

79
<com.mapbox.maps.MapView
810
android:id="@+id/mapView"
@@ -77,4 +79,15 @@
7779
app:layout_constraintEnd_toEndOf="parent"
7880
app:layout_constraintTop_toBottomOf="@id/routeOverview" />
7981

82+
<Button
83+
android:id="@+id/navigateButton"
84+
android:layout_width="0dp"
85+
android:layout_height="wrap_content"
86+
app:layout_constraintEnd_toEndOf="parent"
87+
app:layout_constraintStart_toStartOf="parent"
88+
app:layout_constraintBottom_toBottomOf="parent"
89+
android:visibility="gone"
90+
tools:visibility="visible"
91+
android:text="Navigate"/>
92+
8093
</androidx.constraintlayout.widget.ConstraintLayout>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.mapbox.navigation.instrumentation_tests.core
2+
3+
import android.location.Location
4+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
5+
import com.mapbox.navigation.base.options.NavigationOptions
6+
import com.mapbox.navigation.base.route.NavigationRoute
7+
import com.mapbox.navigation.base.trip.model.RouteProgressState
8+
import com.mapbox.navigation.core.MapboxNavigation
9+
import com.mapbox.navigation.core.MapboxNavigationProvider
10+
import com.mapbox.navigation.core.directions.session.RoutesExtra
11+
import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
12+
import com.mapbox.navigation.core.preview.RoutesPreviewUpdate
13+
import com.mapbox.navigation.core.preview.RoutesPreviewUpdateReasons
14+
import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity
15+
import com.mapbox.navigation.instrumentation_tests.utils.MapboxNavigationRule
16+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routeProgressUpdates
17+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routesPreviewUpdates
18+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routesUpdates
19+
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.sdkTest
20+
import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider
21+
import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider.toNavigationRoutes
22+
import com.mapbox.navigation.testing.ui.BaseTest
23+
import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources
24+
import com.mapbox.navigation.testing.ui.utils.runOnMainSync
25+
import kotlinx.coroutines.flow.first
26+
import org.junit.Assert.assertEquals
27+
import org.junit.Assert.assertNull
28+
import org.junit.Before
29+
import org.junit.Rule
30+
import org.junit.Test
31+
32+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
33+
class RoutesPreviewTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.java) {
34+
35+
override fun setupMockLocation(): Location = mockLocationUpdatesRule.generateLocationUpdate {
36+
latitude = 38.894721
37+
longitude = -77.031991
38+
}
39+
40+
@get:Rule
41+
val mapboxNavigationRule = MapboxNavigationRule()
42+
private lateinit var mapboxNavigation: MapboxNavigation
43+
44+
@Before
45+
fun setUp() {
46+
runOnMainSync {
47+
mapboxNavigation = MapboxNavigationProvider.create(
48+
NavigationOptions.Builder(activity)
49+
.accessToken(getMapboxAccessTokenFromResources(activity))
50+
.build()
51+
)
52+
}
53+
}
54+
55+
@Test
56+
fun transitions_free_drive_to_preview_to_active_guidance_to_free_drive() = sdkTest {
57+
var currentRoutesPreview: RoutesPreviewUpdate? = null
58+
mapboxNavigation.registerRoutesPreviewObserver { update ->
59+
currentRoutesPreview = update
60+
}
61+
var currentRoutes: RoutesUpdatedResult? = null
62+
mapboxNavigation.registerRoutesObserver { update ->
63+
currentRoutes = update
64+
}
65+
// initial free drive
66+
mapboxNavigation.startTripSession()
67+
assertNull(currentRoutesPreview)
68+
assertNull(currentRoutes)
69+
// set routes preview
70+
val routes = RoutesProvider.dc_very_short(activity).toNavigationRoutes()
71+
mapboxNavigation.setRoutesPreview(routes)
72+
mapboxNavigation.routesPreviewUpdates()
73+
.first { it.reason == RoutesPreviewUpdateReasons.PREVIEW_NEW }
74+
assertEquals(RoutesPreviewUpdateReasons.PREVIEW_NEW, currentRoutesPreview?.reason)
75+
assertEquals(routes, currentRoutesPreview!!.routesPreview!!.routesList)
76+
assertNull(currentRoutes)
77+
// start active guidance
78+
mapboxNavigation.setNavigationRoutes(currentRoutesPreview!!.routesPreview!!.routesList)
79+
mapboxNavigation.setRoutesPreview(emptyList())
80+
mapboxNavigation.routesUpdates()
81+
.first { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_NEW }
82+
mapboxNavigation.routeProgressUpdates().
83+
first { it.currentState == RouteProgressState.TRACKING }
84+
assertEquals(RoutesPreviewUpdateReasons.PREVIEW_CLEAN_UP, currentRoutesPreview?.reason)
85+
assertNull(currentRoutesPreview!!.routesPreview)
86+
assertEquals(RoutesExtra.ROUTES_UPDATE_REASON_NEW, currentRoutes!!.reason)
87+
assertEquals(routes, currentRoutes!!.navigationRoutes)
88+
// back to free drive
89+
mapboxNavigation.setNavigationRoutes(emptyList())
90+
mapboxNavigation.routesUpdates()
91+
.first { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_CLEAN_UP }
92+
assertEquals(RoutesPreviewUpdateReasons.PREVIEW_CLEAN_UP, currentRoutesPreview?.reason)
93+
assertNull(currentRoutesPreview!!.routesPreview)
94+
assertEquals(RoutesExtra.ROUTES_UPDATE_REASON_CLEAN_UP, currentRoutes!!.reason)
95+
assertEquals(emptyList<NavigationRoute>(), currentRoutes!!.navigationRoutes)
96+
}
97+
98+
@Test
99+
fun route_preview_in_parallel_to_active_guidance() = sdkTest {
100+
var currentRoutesPreview: RoutesPreviewUpdate? = null
101+
mapboxNavigation.registerRoutesPreviewObserver { update ->
102+
currentRoutesPreview = update
103+
}
104+
var currentRoutes: RoutesUpdatedResult? = null
105+
mapboxNavigation.registerRoutesObserver { update ->
106+
currentRoutes = update
107+
}
108+
// initial free drive
109+
mapboxNavigation.startTripSession()
110+
assertNull(currentRoutesPreview)
111+
assertNull(currentRoutes)
112+
// set routes preview
113+
val initialRoutes = RoutesProvider.dc_very_short(activity).toNavigationRoutes()
114+
mapboxNavigation.setRoutesPreview(initialRoutes)
115+
mapboxNavigation.routesPreviewUpdates()
116+
.first { it.reason == RoutesPreviewUpdateReasons.PREVIEW_NEW }
117+
// start active guidance
118+
mapboxNavigation.setNavigationRoutes(currentRoutesPreview!!.routesPreview!!.routesList)
119+
mapboxNavigation.setRoutesPreview(emptyList())
120+
mapboxNavigation.routeProgressUpdates()
121+
.first { it.currentState == RouteProgressState.TRACKING }
122+
// preview a different route not leaving action guidance
123+
val updatedRoutes = RoutesProvider.dc_very_short_two_legs(activity).toNavigationRoutes()
124+
mapboxNavigation.setRoutesPreview(updatedRoutes)
125+
mapboxNavigation.routesPreviewUpdates()
126+
.first { it.routesPreview?.routesList == updatedRoutes }
127+
assertEquals(
128+
"active guidance should track initial routes",
129+
initialRoutes,
130+
currentRoutes?.navigationRoutes
131+
)
132+
// user decided to switch to previewed routes
133+
mapboxNavigation.setNavigationRoutes(currentRoutesPreview!!.routesPreview!!.routesList)
134+
mapboxNavigation.setRoutesPreview(emptyList())
135+
mapboxNavigation.routeProgressUpdates().first {
136+
it.navigationRoute == updatedRoutes[0]
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)