Skip to content

Commit

Permalink
Compare routes using step names
Browse files Browse the repository at this point in the history
  • Loading branch information
kmadsen committed Jul 13, 2020
1 parent 6c52c50 commit 1263ce6
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 29 deletions.
Expand Up @@ -23,6 +23,7 @@ import com.mapbox.navigation.base.internal.extensions.applyDefaultParams
import com.mapbox.navigation.base.internal.extensions.coordinates
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
import com.mapbox.navigation.core.replay.MapboxReplayer
import com.mapbox.navigation.core.replay.ReplayLocationEngine
import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
Expand All @@ -39,7 +40,7 @@ import kotlinx.android.synthetic.main.activity_replay_route_layout.*

/**
* This activity shows how to use the MapboxNavigation
* class with the Navigation SDK's [ReplayHistoryLocationEngine].
* class with the Navigation SDK's [MapboxReplayer] and [ReplayLocationEngine].
*/
class ReplayActivity : AppCompatActivity(), OnMapReadyCallback {

Expand Down Expand Up @@ -82,6 +83,13 @@ class ReplayActivity : AppCompatActivity(), OnMapReadyCallback {
navigationMapboxMap?.restoreFrom(state)
}
initializeFirstLocation()

mapboxNavigation?.attachFasterRouteObserver(object : FasterRouteObserver {
override fun onFasterRoute(currentRoute: DirectionsRoute, alternatives: List<DirectionsRoute>, isAlternativeFaster: Boolean) {
navigationMapboxMap?.drawRoutes(alternatives)
mapboxNavigation?.setRoutes(alternatives)
}
})
}
mapboxMap.addOnMapLongClickListener { latLng ->
mapboxMap.locationComponent.lastKnownLocation?.let { originLocation ->
Expand Down Expand Up @@ -110,7 +118,7 @@ class ReplayActivity : AppCompatActivity(), OnMapReadyCallback {
override fun onRoutesReady(routes: List<DirectionsRoute>) {
MapboxLogger.d(Message("route request success $routes"))
if (routes.isNotEmpty()) {
navigationMapboxMap?.drawRoute(routes[0])
navigationMapboxMap?.drawRoutes(routes)

val replayEvents = replayRouteMapper.mapGeometry(routes[0].geometry()!!)
mapboxReplayer.pushEvents(replayEvents)
Expand Down
Expand Up @@ -23,6 +23,7 @@ import com.mapbox.navigation.base.internal.extensions.applyDefaultParams
import com.mapbox.navigation.base.internal.extensions.coordinates
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
import com.mapbox.navigation.core.replay.MapboxReplayer
import com.mapbox.navigation.core.replay.ReplayLocationEngine
import com.mapbox.navigation.core.replay.history.CustomEventMapper
Expand Down Expand Up @@ -171,6 +172,13 @@ class ReplayHistoryActivity : AppCompatActivity() {
}
})

mapboxNavigation.attachFasterRouteObserver(object : FasterRouteObserver {
override fun onFasterRoute(currentRoute: DirectionsRoute, alternatives: List<DirectionsRoute>, isAlternativeFaster: Boolean) {
navigationContext?.navigationMapboxMap?.drawRoutes(alternatives)
navigationContext?.mapboxNavigation?.setRoutes(alternatives)
}
})

playReplay.setOnClickListener {
mapboxReplayer.play()
mapboxNavigation.startTripSession()
Expand Down Expand Up @@ -235,7 +243,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
override fun onRoutesReady(routes: List<DirectionsRoute>) {
MapboxLogger.d(Message("route request success $routes"))
if (routes.isNotEmpty()) {
navigationContext?.navigationMapboxMap?.drawRoute(routes[0])
navigationContext?.navigationMapboxMap?.drawRoutes(routes)
navigationContext?.startNavigation()
}
}
Expand Down
Expand Up @@ -32,7 +32,9 @@ import com.mapbox.navigation.core.directions.session.DirectionsSession
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.fasterroute.FasterRouteController
import com.mapbox.navigation.core.fasterroute.FasterRouteDetector
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
import com.mapbox.navigation.core.fasterroute.RouteComparator
import com.mapbox.navigation.core.internal.MapboxDistanceFormatter
import com.mapbox.navigation.core.internal.accounts.MapboxNavigationAccounts
import com.mapbox.navigation.core.internal.trip.service.TripService
Expand Down Expand Up @@ -207,6 +209,7 @@ class MapboxNavigation(
directionsSession,
tripSession,
routeOptionsProvider,
FasterRouteDetector(RouteComparator()),
logger
)
routeRefreshController = RouteRefreshController(directionsSession, tripSession, logger)
Expand Down
Expand Up @@ -9,17 +9,22 @@ import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.routeoptions.RouteOptionsProvider
import com.mapbox.navigation.core.trip.session.TripSession
import com.mapbox.navigation.utils.internal.MapboxTimer
import com.mapbox.navigation.utils.internal.ThreadController
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch

internal class FasterRouteController(
private val directionsSession: DirectionsSession,
private val tripSession: TripSession,
private val routeOptionsProvider: RouteOptionsProvider,
private val fasterRouteDetector: FasterRouteDetector,
private val logger: Logger
) {

private val jobControl = ThreadController.getMainScopeAndRootJob()

private val fasterRouteTimer = MapboxTimer()
private val fasterRouteDetector = FasterRouteDetector()
private var fasterRouteObserver: FasterRouteObserver? = null

fun attach(fasterRouteObserver: FasterRouteObserver) {
Expand All @@ -40,6 +45,7 @@ internal class FasterRouteController(
fun stop() {
this.fasterRouteObserver = null
fasterRouteTimer.stopJobs()
jobControl.job.cancelChildren()
}

private fun requestFasterRoute() {
Expand Down Expand Up @@ -72,8 +78,10 @@ internal class FasterRouteController(
override fun onRoutesReady(routes: List<DirectionsRoute>) {
val currentRoute = directionsSession.routes.firstOrNull()
?: return
tripSession.getRouteProgress()?.let { progress ->
val isAlternativeFaster = fasterRouteDetector.isRouteFaster(routes[0], progress)
val routeProgress = tripSession.getRouteProgress()
?: return
jobControl.scope.launch {
val isAlternativeFaster = fasterRouteDetector.isRouteFaster(routes[0], routeProgress)
fasterRouteObserver?.onFasterRoute(currentRoute, routes, isAlternativeFaster)
}
}
Expand Down
Expand Up @@ -2,12 +2,21 @@ package com.mapbox.navigation.core.fasterroute

import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.navigation.base.trip.model.RouteProgress
import com.mapbox.navigation.utils.internal.ThreadController
import kotlinx.coroutines.withContext

internal class FasterRouteDetector {
fun isRouteFaster(newRoute: DirectionsRoute, routeProgress: RouteProgress): Boolean {
val newRouteDuration = newRoute.duration() ?: return false
internal class FasterRouteDetector(
private val routeComparator: RouteComparator
) {

suspend fun isRouteFaster(
alternativeRoute: DirectionsRoute,
routeProgress: RouteProgress
): Boolean = withContext(ThreadController.IODispatcher) {
val alternativeDuration = alternativeRoute.duration() ?: return@withContext false
val weightedDuration = routeProgress.durationRemaining * PERCENTAGE_THRESHOLD
return newRouteDuration < weightedDuration
val isRouteFaster = alternativeDuration < weightedDuration
return@withContext isRouteFaster && routeComparator.isNewRoute(routeProgress, alternativeRoute)
}

companion object {
Expand Down
@@ -0,0 +1,50 @@
package com.mapbox.navigation.core.fasterroute

import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.api.directions.v5.models.LegStep
import com.mapbox.navigation.base.trip.model.RouteProgress

/**
* Compares if an alternative route is different from the current route progress.
*/
internal class RouteComparator {

private val mapLegStepToName: (LegStep) -> String = { it.name() ?: "" }

/**
* @param routeProgress current route progress
* @param alternativeRoute suggested new route
*
* @return true when the alternative route has different
* geometry from the current route progress
*/
fun isNewRoute(routeProgress: RouteProgress, alternativeRoute: DirectionsRoute): Boolean {
val currentDescription = routeDescription(routeProgress)
val alternativeDescription = alternativeDescription(alternativeRoute)
alternativeDescription.ifEmpty {
return false
}

return isNewRoute(currentDescription, alternativeDescription)
}

private fun routeDescription(routeProgress: RouteProgress): String {
val routeLeg = routeProgress.currentLegProgress?.routeLeg
val steps = routeLeg?.steps()
val stepIndex = routeProgress.currentLegProgress?.currentStepProgress?.stepIndex
?: return ""

return steps?.listIterator(stepIndex)?.asSequence()
?.joinToString(transform = mapLegStepToName) ?: ""
}

private fun alternativeDescription(directionsRoute: DirectionsRoute): String {
val steps = directionsRoute.legs()?.firstOrNull()?.steps()
return steps?.asSequence()
?.joinToString(transform = mapLegStepToName) ?: ""
}

private fun isNewRoute(currentGeometry: String, alternativeGeometry: String): Boolean {
return currentGeometry != alternativeGeometry
}
}
Expand Up @@ -8,6 +8,7 @@ import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.routeoptions.RouteOptionsProvider
import com.mapbox.navigation.core.trip.session.TripSession
import com.mapbox.navigation.testing.MainCoroutineRule
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
Expand All @@ -25,20 +26,22 @@ class FasterRouteControllerTest {
val coroutineRule = MainCoroutineRule()

private val directionsSession: DirectionsSession = mockk()
private val tripSession: TripSession = mockk()
private val tripSession: TripSession = mockk {
every { getRouteProgress() } returns mockk()
}
private val fasterRouteObserver: FasterRouteObserver = mockk {
every { restartAfterMillis() } returns FasterRouteObserver.DEFAULT_INTERVAL_MILLIS
every { onFasterRoute(any(), any(), any()) } returns Unit
}
private val routesRequestCallbacks = slot<RoutesRequestCallback>()

private val logger: Logger = mockk()
private val routeOptionsProvider: RouteOptionsProvider = mockk()
private val fasterRouteController = FasterRouteController(directionsSession, tripSession, routeOptionsProvider, logger)

private val routeOptionsResultSuccess: RouteOptionsProvider.RouteOptionsResult.Success = mockk()
private val routeOptionsResultSuccessRouteOptions: RouteOptions = mockk()
private val routeOptionsResultError: RouteOptionsProvider.RouteOptionsResult.Error = mockk()
private val fasterRouteDetector: FasterRouteDetector = mockk()

private val fasterRouteController = FasterRouteController(directionsSession, tripSession, routeOptionsProvider, fasterRouteDetector, logger)

@Before
fun setup() {
Expand Down Expand Up @@ -109,26 +112,22 @@ class FasterRouteControllerTest {

@Test
fun `should notify observer of a faster route`() = coroutineRule.runBlockingTest {
coEvery { fasterRouteDetector.isRouteFaster(any(), any()) } returns true
mockRouteOptionsProvider(routeOptionsResultSuccess)
val currentRoute: DirectionsRoute = mockk {
every { routeIndex() } returns "0"
every { duration() } returns 801.332
}
every { directionsSession.routes } returns listOf(currentRoute)
every { tripSession.getEnhancedLocation() } returns mockk {
every { latitude } returns -33.874308
every { longitude } returns 151.206087
}
every { tripSession.getRouteProgress() } returns mockk {
every { durationRemaining } returns 601.334
}
every { directionsSession.requestFasterRoute(any(), capture(routesRequestCallbacks)) } returns mockk()

fasterRouteController.attach(fasterRouteObserver)
coroutineRule.testDispatcher.advanceTimeBy(TimeUnit.MINUTES.toMillis(6))
val routes = listOf<DirectionsRoute>(mockk {
every { routeIndex() } returns "0"
every { duration() } returns 351.013
})
routesRequestCallbacks.captured.onRoutesReady(routes)

Expand All @@ -140,26 +139,22 @@ class FasterRouteControllerTest {

@Test
fun `should notify observer if current route is fastest`() = coroutineRule.runBlockingTest {
coEvery { fasterRouteDetector.isRouteFaster(any(), any()) } returns false
mockRouteOptionsProvider(routeOptionsResultSuccess)
val currentRoute: DirectionsRoute = mockk {
every { routeIndex() } returns "0"
every { duration() } returns 801.332
}
every { directionsSession.routes } returns listOf(currentRoute)
every { tripSession.getEnhancedLocation() } returns mockk {
every { latitude } returns -33.874308
every { longitude } returns 151.206087
}
every { tripSession.getRouteProgress() } returns mockk {
every { durationRemaining } returns 751.334
}
every { directionsSession.requestFasterRoute(any(), capture(routesRequestCallbacks)) } returns mockk()

fasterRouteController.attach(fasterRouteObserver)
coroutineRule.testDispatcher.advanceTimeBy(TimeUnit.MINUTES.toMillis(6))
val routes = listOf<DirectionsRoute>(mockk {
every { routeIndex() } returns "0"
every { duration() } returns 951.013
})
routesRequestCallbacks.captured.onRoutesReady(routes)

Expand Down
Expand Up @@ -4,16 +4,22 @@ import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.navigation.base.trip.model.RouteProgress
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class FasterRouteDetectorTest {

private val fasterRouteDetector = FasterRouteDetector()
private val routeComparator: RouteComparator = mockk {
every { isNewRoute(any(), any()) } returns true
}

private val fasterRouteDetector = FasterRouteDetector(routeComparator)

@Test
fun shouldDetectWhenRouteIsFaster() {
fun shouldDetectWhenRouteIsFaster() = runBlocking {
every { routeComparator.isNewRoute(any(), any()) } returns true
val newRoute: DirectionsRoute = mockk()
every { newRoute.duration() } returns 402.6
val routeProgress: RouteProgress = mockk()
Expand All @@ -25,7 +31,20 @@ class FasterRouteDetectorTest {
}

@Test
fun shouldDetectWhenRouteIsSlower() {
fun shouldDetectWhenRouteIsFasterOnlyIfDifferent() = runBlocking {
every { routeComparator.isNewRoute(any(), any()) } returns false
val newRoute: DirectionsRoute = mockk()
every { newRoute.duration() } returns 402.6
val routeProgress: RouteProgress = mockk()
every { routeProgress.durationRemaining } returns 797.447

val isFasterRoute = fasterRouteDetector.isRouteFaster(newRoute, routeProgress)

assertFalse(isFasterRoute)
}

@Test
fun shouldDetectWhenRouteIsSlower() = runBlocking {
val newRoute: DirectionsRoute = mockk()
every { newRoute.duration() } returns 512.2
val routeProgress: RouteProgress = mockk()
Expand All @@ -37,7 +56,7 @@ class FasterRouteDetectorTest {
}

@Test
fun shouldDefaultToFalseWhenDurationIsNull() {
fun shouldDefaultToFalseWhenDurationIsNull() = runBlocking {
val newRoute: DirectionsRoute = mockk()
every { newRoute.duration() } returns null
val routeProgress: RouteProgress = mockk()
Expand All @@ -49,7 +68,7 @@ class FasterRouteDetectorTest {
}

@Test
fun shouldNotAllowSlightlyFasterRoutes() {
fun shouldNotAllowSlightlyFasterRoutes() = runBlocking {
val newRoute: DirectionsRoute = mockk()
every { newRoute.duration() } returns 634.7
val routeProgress: RouteProgress = mockk()
Expand Down

0 comments on commit 1263ce6

Please sign in to comment.