Skip to content

Commit cb565a6

Browse files
committed
Compare routes using step names
1 parent 033dae5 commit cb565a6

File tree

9 files changed

+239
-29
lines changed

9 files changed

+239
-29
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.mapbox.navigation.base.internal.extensions.applyDefaultParams
2323
import com.mapbox.navigation.base.internal.extensions.coordinates
2424
import com.mapbox.navigation.core.MapboxNavigation
2525
import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
26+
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
2627
import com.mapbox.navigation.core.replay.MapboxReplayer
2728
import com.mapbox.navigation.core.replay.ReplayLocationEngine
2829
import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
@@ -39,7 +40,7 @@ import kotlinx.android.synthetic.main.activity_replay_route_layout.*
3940

4041
/**
4142
* This activity shows how to use the MapboxNavigation
42-
* class with the Navigation SDK's [ReplayHistoryLocationEngine].
43+
* class with the Navigation SDK's [MapboxReplayer] and [ReplayLocationEngine].
4344
*/
4445
class ReplayActivity : AppCompatActivity(), OnMapReadyCallback {
4546

@@ -82,6 +83,13 @@ class ReplayActivity : AppCompatActivity(), OnMapReadyCallback {
8283
navigationMapboxMap?.restoreFrom(state)
8384
}
8485
initializeFirstLocation()
86+
87+
mapboxNavigation?.attachFasterRouteObserver(object : FasterRouteObserver {
88+
override fun onFasterRoute(currentRoute: DirectionsRoute, alternatives: List<DirectionsRoute>, isAlternativeFaster: Boolean) {
89+
navigationMapboxMap?.drawRoutes(alternatives)
90+
mapboxNavigation?.setRoutes(alternatives)
91+
}
92+
})
8593
}
8694
mapboxMap.addOnMapLongClickListener { latLng ->
8795
mapboxMap.locationComponent.lastKnownLocation?.let { originLocation ->
@@ -110,7 +118,7 @@ class ReplayActivity : AppCompatActivity(), OnMapReadyCallback {
110118
override fun onRoutesReady(routes: List<DirectionsRoute>) {
111119
MapboxLogger.d(Message("route request success $routes"))
112120
if (routes.isNotEmpty()) {
113-
navigationMapboxMap?.drawRoute(routes[0])
121+
navigationMapboxMap?.drawRoutes(routes)
114122

115123
val replayEvents = replayRouteMapper.mapGeometry(routes[0].geometry()!!)
116124
mapboxReplayer.pushEvents(replayEvents)

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.mapbox.navigation.base.internal.extensions.applyDefaultParams
2323
import com.mapbox.navigation.base.internal.extensions.coordinates
2424
import com.mapbox.navigation.core.MapboxNavigation
2525
import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
26+
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
2627
import com.mapbox.navigation.core.replay.MapboxReplayer
2728
import com.mapbox.navigation.core.replay.ReplayLocationEngine
2829
import com.mapbox.navigation.core.replay.history.CustomEventMapper
@@ -171,6 +172,13 @@ class ReplayHistoryActivity : AppCompatActivity() {
171172
}
172173
})
173174

175+
mapboxNavigation.attachFasterRouteObserver(object : FasterRouteObserver {
176+
override fun onFasterRoute(currentRoute: DirectionsRoute, alternatives: List<DirectionsRoute>, isAlternativeFaster: Boolean) {
177+
navigationContext?.navigationMapboxMap?.drawRoutes(alternatives)
178+
navigationContext?.mapboxNavigation?.setRoutes(alternatives)
179+
}
180+
})
181+
174182
playReplay.setOnClickListener {
175183
mapboxReplayer.play()
176184
mapboxNavigation.startTripSession()
@@ -235,7 +243,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
235243
override fun onRoutesReady(routes: List<DirectionsRoute>) {
236244
MapboxLogger.d(Message("route request success $routes"))
237245
if (routes.isNotEmpty()) {
238-
navigationContext?.navigationMapboxMap?.drawRoute(routes[0])
246+
navigationContext?.navigationMapboxMap?.drawRoutes(routes)
239247
navigationContext?.startNavigation()
240248
}
241249
}

libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import com.mapbox.navigation.core.directions.session.DirectionsSession
3232
import com.mapbox.navigation.core.directions.session.RoutesObserver
3333
import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
3434
import com.mapbox.navigation.core.fasterroute.FasterRouteController
35+
import com.mapbox.navigation.core.fasterroute.FasterRouteDetector
3536
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
37+
import com.mapbox.navigation.core.fasterroute.RouteComparator
3638
import com.mapbox.navigation.core.internal.MapboxDistanceFormatter
3739
import com.mapbox.navigation.core.internal.accounts.MapboxNavigationAccounts
3840
import com.mapbox.navigation.core.internal.trip.service.TripService
@@ -207,6 +209,7 @@ class MapboxNavigation(
207209
directionsSession,
208210
tripSession,
209211
routeOptionsProvider,
212+
FasterRouteDetector(RouteComparator()),
210213
logger
211214
)
212215
routeRefreshController = RouteRefreshController(directionsSession, tripSession, logger)

libnavigation-core/src/main/java/com/mapbox/navigation/core/fasterroute/FasterRouteController.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@ import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
99
import com.mapbox.navigation.core.routeoptions.RouteOptionsProvider
1010
import com.mapbox.navigation.core.trip.session.TripSession
1111
import com.mapbox.navigation.utils.internal.MapboxTimer
12+
import com.mapbox.navigation.utils.internal.ThreadController
1213
import java.util.concurrent.TimeUnit
14+
import kotlinx.coroutines.cancelChildren
15+
import kotlinx.coroutines.launch
1316

1417
internal class FasterRouteController(
1518
private val directionsSession: DirectionsSession,
1619
private val tripSession: TripSession,
1720
private val routeOptionsProvider: RouteOptionsProvider,
21+
private val fasterRouteDetector: FasterRouteDetector,
1822
private val logger: Logger
1923
) {
2024

25+
private val jobControl = ThreadController.getMainScopeAndRootJob()
26+
2127
private val fasterRouteTimer = MapboxTimer()
22-
private val fasterRouteDetector = FasterRouteDetector()
2328
private var fasterRouteObserver: FasterRouteObserver? = null
2429

2530
fun attach(fasterRouteObserver: FasterRouteObserver) {
@@ -40,6 +45,7 @@ internal class FasterRouteController(
4045
fun stop() {
4146
this.fasterRouteObserver = null
4247
fasterRouteTimer.stopJobs()
48+
jobControl.job.cancelChildren()
4349
}
4450

4551
private fun requestFasterRoute() {
@@ -72,8 +78,10 @@ internal class FasterRouteController(
7278
override fun onRoutesReady(routes: List<DirectionsRoute>) {
7379
val currentRoute = directionsSession.routes.firstOrNull()
7480
?: return
75-
tripSession.getRouteProgress()?.let { progress ->
76-
val isAlternativeFaster = fasterRouteDetector.isRouteFaster(routes[0], progress)
81+
val routeProgress = tripSession.getRouteProgress()
82+
?: return
83+
jobControl.scope.launch {
84+
val isAlternativeFaster = fasterRouteDetector.isRouteFaster(routes[0], routeProgress)
7785
fasterRouteObserver?.onFasterRoute(currentRoute, routes, isAlternativeFaster)
7886
}
7987
}

libnavigation-core/src/main/java/com/mapbox/navigation/core/fasterroute/FasterRouteDetector.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@ package com.mapbox.navigation.core.fasterroute
22

33
import com.mapbox.api.directions.v5.models.DirectionsRoute
44
import com.mapbox.navigation.base.trip.model.RouteProgress
5+
import com.mapbox.navigation.utils.internal.ThreadController
6+
import kotlinx.coroutines.withContext
57

6-
internal class FasterRouteDetector {
7-
fun isRouteFaster(newRoute: DirectionsRoute, routeProgress: RouteProgress): Boolean {
8-
val newRouteDuration = newRoute.duration() ?: return false
8+
internal class FasterRouteDetector(
9+
private val routeComparator: RouteComparator
10+
) {
11+
12+
suspend fun isRouteFaster(
13+
alternativeRoute: DirectionsRoute,
14+
routeProgress: RouteProgress
15+
): Boolean = withContext(ThreadController.IODispatcher) {
16+
val alternativeDuration = alternativeRoute.duration() ?: return@withContext false
917
val weightedDuration = routeProgress.durationRemaining * PERCENTAGE_THRESHOLD
10-
return newRouteDuration < weightedDuration
18+
val isRouteFaster = alternativeDuration < weightedDuration
19+
return@withContext isRouteFaster && routeComparator.isRouteDescriptionDifferent(routeProgress, alternativeRoute)
1120
}
1221

1322
companion object {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.mapbox.navigation.core.fasterroute
2+
3+
import com.mapbox.api.directions.v5.models.DirectionsRoute
4+
import com.mapbox.api.directions.v5.models.LegStep
5+
import com.mapbox.navigation.base.trip.model.RouteProgress
6+
7+
/**
8+
* Compares if an alternative route is different from the current route progress.
9+
*/
10+
internal class RouteComparator {
11+
12+
private val mapLegStepToName: (LegStep) -> String = { it.name() ?: "" }
13+
14+
/**
15+
* @param routeProgress current route progress
16+
* @param alternativeRoute suggested new route
17+
*
18+
* @return true when the alternative route has different
19+
* geometry from the current route progress
20+
*/
21+
fun isRouteDescriptionDifferent(routeProgress: RouteProgress, alternativeRoute: DirectionsRoute): Boolean {
22+
val currentDescription = routeDescription(routeProgress)
23+
val alternativeDescription = alternativeDescription(alternativeRoute)
24+
alternativeDescription.ifEmpty {
25+
return false
26+
}
27+
28+
return currentDescription != alternativeDescription
29+
}
30+
31+
private fun routeDescription(routeProgress: RouteProgress): String {
32+
val routeLeg = routeProgress.currentLegProgress?.routeLeg
33+
val steps = routeLeg?.steps()
34+
val stepIndex = routeProgress.currentLegProgress?.currentStepProgress?.stepIndex
35+
?: return ""
36+
37+
return steps?.listIterator(stepIndex)?.asSequence()
38+
?.joinToString(transform = mapLegStepToName) ?: ""
39+
}
40+
41+
private fun alternativeDescription(directionsRoute: DirectionsRoute): String {
42+
val steps = directionsRoute.legs()?.firstOrNull()?.steps()
43+
return steps?.asSequence()
44+
?.joinToString(transform = mapLegStepToName) ?: ""
45+
}
46+
}

libnavigation-core/src/test/java/com/mapbox/navigation/core/fasterroute/FasterRouteControllerTest.kt

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
88
import com.mapbox.navigation.core.routeoptions.RouteOptionsProvider
99
import com.mapbox.navigation.core.trip.session.TripSession
1010
import com.mapbox.navigation.testing.MainCoroutineRule
11+
import io.mockk.coEvery
1112
import io.mockk.every
1213
import io.mockk.mockk
1314
import io.mockk.slot
@@ -25,20 +26,22 @@ class FasterRouteControllerTest {
2526
val coroutineRule = MainCoroutineRule()
2627

2728
private val directionsSession: DirectionsSession = mockk()
28-
private val tripSession: TripSession = mockk()
29+
private val tripSession: TripSession = mockk {
30+
every { getRouteProgress() } returns mockk()
31+
}
2932
private val fasterRouteObserver: FasterRouteObserver = mockk {
3033
every { restartAfterMillis() } returns FasterRouteObserver.DEFAULT_INTERVAL_MILLIS
3134
every { onFasterRoute(any(), any(), any()) } returns Unit
3235
}
3336
private val routesRequestCallbacks = slot<RoutesRequestCallback>()
34-
3537
private val logger: Logger = mockk()
3638
private val routeOptionsProvider: RouteOptionsProvider = mockk()
37-
private val fasterRouteController = FasterRouteController(directionsSession, tripSession, routeOptionsProvider, logger)
3839

3940
private val routeOptionsResultSuccess: RouteOptionsProvider.RouteOptionsResult.Success = mockk()
4041
private val routeOptionsResultSuccessRouteOptions: RouteOptions = mockk()
41-
private val routeOptionsResultError: RouteOptionsProvider.RouteOptionsResult.Error = mockk()
42+
private val fasterRouteDetector: FasterRouteDetector = mockk()
43+
44+
private val fasterRouteController = FasterRouteController(directionsSession, tripSession, routeOptionsProvider, fasterRouteDetector, logger)
4245

4346
@Before
4447
fun setup() {
@@ -109,26 +112,22 @@ class FasterRouteControllerTest {
109112

110113
@Test
111114
fun `should notify observer of a faster route`() = coroutineRule.runBlockingTest {
115+
coEvery { fasterRouteDetector.isRouteFaster(any(), any()) } returns true
112116
mockRouteOptionsProvider(routeOptionsResultSuccess)
113117
val currentRoute: DirectionsRoute = mockk {
114118
every { routeIndex() } returns "0"
115-
every { duration() } returns 801.332
116119
}
117120
every { directionsSession.routes } returns listOf(currentRoute)
118121
every { tripSession.getEnhancedLocation() } returns mockk {
119122
every { latitude } returns -33.874308
120123
every { longitude } returns 151.206087
121124
}
122-
every { tripSession.getRouteProgress() } returns mockk {
123-
every { durationRemaining } returns 601.334
124-
}
125125
every { directionsSession.requestFasterRoute(any(), capture(routesRequestCallbacks)) } returns mockk()
126126

127127
fasterRouteController.attach(fasterRouteObserver)
128128
coroutineRule.testDispatcher.advanceTimeBy(TimeUnit.MINUTES.toMillis(6))
129129
val routes = listOf<DirectionsRoute>(mockk {
130130
every { routeIndex() } returns "0"
131-
every { duration() } returns 351.013
132131
})
133132
routesRequestCallbacks.captured.onRoutesReady(routes)
134133

@@ -140,26 +139,22 @@ class FasterRouteControllerTest {
140139

141140
@Test
142141
fun `should notify observer if current route is fastest`() = coroutineRule.runBlockingTest {
142+
coEvery { fasterRouteDetector.isRouteFaster(any(), any()) } returns false
143143
mockRouteOptionsProvider(routeOptionsResultSuccess)
144144
val currentRoute: DirectionsRoute = mockk {
145145
every { routeIndex() } returns "0"
146-
every { duration() } returns 801.332
147146
}
148147
every { directionsSession.routes } returns listOf(currentRoute)
149148
every { tripSession.getEnhancedLocation() } returns mockk {
150149
every { latitude } returns -33.874308
151150
every { longitude } returns 151.206087
152151
}
153-
every { tripSession.getRouteProgress() } returns mockk {
154-
every { durationRemaining } returns 751.334
155-
}
156152
every { directionsSession.requestFasterRoute(any(), capture(routesRequestCallbacks)) } returns mockk()
157153

158154
fasterRouteController.attach(fasterRouteObserver)
159155
coroutineRule.testDispatcher.advanceTimeBy(TimeUnit.MINUTES.toMillis(6))
160156
val routes = listOf<DirectionsRoute>(mockk {
161157
every { routeIndex() } returns "0"
162-
every { duration() } returns 951.013
163158
})
164159
routesRequestCallbacks.captured.onRoutesReady(routes)
165160

libnavigation-core/src/test/java/com/mapbox/navigation/core/fasterroute/FasterRouteDetectorTest.kt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@ import com.mapbox.api.directions.v5.models.DirectionsRoute
44
import com.mapbox.navigation.base.trip.model.RouteProgress
55
import io.mockk.every
66
import io.mockk.mockk
7+
import kotlinx.coroutines.runBlocking
78
import org.junit.Assert.assertFalse
89
import org.junit.Assert.assertTrue
910
import org.junit.Test
1011

1112
class FasterRouteDetectorTest {
1213

13-
private val fasterRouteDetector = FasterRouteDetector()
14+
private val routeComparator: RouteComparator = mockk {
15+
every { isRouteDescriptionDifferent(any(), any()) } returns true
16+
}
17+
18+
private val fasterRouteDetector = FasterRouteDetector(routeComparator)
1419

1520
@Test
16-
fun shouldDetectWhenRouteIsFaster() {
21+
fun shouldDetectWhenRouteIsFaster() = runBlocking {
22+
every { routeComparator.isRouteDescriptionDifferent(any(), any()) } returns true
1723
val newRoute: DirectionsRoute = mockk()
1824
every { newRoute.duration() } returns 402.6
1925
val routeProgress: RouteProgress = mockk()
@@ -25,7 +31,20 @@ class FasterRouteDetectorTest {
2531
}
2632

2733
@Test
28-
fun shouldDetectWhenRouteIsSlower() {
34+
fun shouldDetectWhenRouteIsFasterOnlyIfDifferent() = runBlocking {
35+
every { routeComparator.isRouteDescriptionDifferent(any(), any()) } returns false
36+
val newRoute: DirectionsRoute = mockk()
37+
every { newRoute.duration() } returns 402.6
38+
val routeProgress: RouteProgress = mockk()
39+
every { routeProgress.durationRemaining } returns 797.447
40+
41+
val isFasterRoute = fasterRouteDetector.isRouteFaster(newRoute, routeProgress)
42+
43+
assertFalse(isFasterRoute)
44+
}
45+
46+
@Test
47+
fun shouldDetectWhenRouteIsSlower() = runBlocking {
2948
val newRoute: DirectionsRoute = mockk()
3049
every { newRoute.duration() } returns 512.2
3150
val routeProgress: RouteProgress = mockk()
@@ -37,7 +56,7 @@ class FasterRouteDetectorTest {
3756
}
3857

3958
@Test
40-
fun shouldDefaultToFalseWhenDurationIsNull() {
59+
fun shouldDefaultToFalseWhenDurationIsNull() = runBlocking {
4160
val newRoute: DirectionsRoute = mockk()
4261
every { newRoute.duration() } returns null
4362
val routeProgress: RouteProgress = mockk()
@@ -49,7 +68,7 @@ class FasterRouteDetectorTest {
4968
}
5069

5170
@Test
52-
fun shouldNotAllowSlightlyFasterRoutes() {
71+
fun shouldNotAllowSlightlyFasterRoutes() = runBlocking {
5372
val newRoute: DirectionsRoute = mockk()
5473
every { newRoute.duration() } returns 634.7
5574
val routeProgress: RouteProgress = mockk()

0 commit comments

Comments
 (0)