Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compare routes using step names #3301

Merged
merged 1 commit into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.isRouteDescriptionDifferent(routeProgress, alternativeRoute)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 isRouteDescriptionDifferent(routeProgress: RouteProgress, alternativeRoute: DirectionsRoute): Boolean {
val currentDescription = routeDescription(routeProgress)
val alternativeDescription = alternativeDescription(alternativeRoute)
alternativeDescription.ifEmpty {
return false
}

return 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) ?: ""
}
}
Original file line number Diff line number Diff line change
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
Copy link
Contributor Author

@kmadsen kmadsen Jul 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These test values are covered in FasterRouteDetectorTest

}
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
Original file line number Diff line number Diff line change
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 { isRouteDescriptionDifferent(any(), any()) } returns true
}

private val fasterRouteDetector = FasterRouteDetector(routeComparator)

@Test
fun shouldDetectWhenRouteIsFaster() {
fun shouldDetectWhenRouteIsFaster() = runBlocking {
every { routeComparator.isRouteDescriptionDifferent(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.isRouteDescriptionDifferent(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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The answer is in, runBlocking is better than runBlockingTest

Kotlin/kotlinx.coroutines#1222 (comment)

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