-
Notifications
You must be signed in to change notification settings - Fork 319
Faster route: part 1 #6434
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
base: main
Are you sure you want to change the base?
Faster route: part 1 #6434
Changes from all commits
8a8d56d
c71b6ed
57b0497
9b349a9
81e3af6
a742ebb
3f2441d
6a319fb
7ed7049
7c714cc
48fe49a
71b4c2c
8bfcc45
ea5aceb
99e5c4b
b6e5c43
fbceeec
b34b5b0
9f29b40
63e7fd9
f2542ff
b5eb4a4
5ce1c10
67b4257
78e4641
58fd6e0
195f84c
0003131
5b63d54
a2eb2af
f341422
99ce419
50168a3
4cd1ec0
3aac8c0
199791b
209ce01
c113281
b12a7b3
5325db9
1e0f0cc
16f26d6
02f229d
c485814
8f37c42
07d9e4d
da671cb
d05c1b3
277ef2f
2d783a0
dfaefa3
c94ca14
a081fb7
5c5e921
33e7cba
2cb0fa7
16b72e1
2bf5c91
7fc4747
5499888
b2a0ef0
cc77d1c
22086be
a7d9c49
dd97326
36f54b1
344a909
f146b14
c40354e
011b3d7
77da23a
730e97b
5a15259
a1e5c50
25de20c
8ad9c71
364d4d8
54fb028
b367e70
3e1671a
e698c45
fb3fad1
8feade5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,9 @@ import com.mapbox.navigation.core.directions.LegacyRouterAdapter | |
| import com.mapbox.navigation.core.directions.session.DirectionsSession | ||
| import com.mapbox.navigation.core.directions.session.RoutesExtra | ||
| import com.mapbox.navigation.core.directions.session.RoutesObserver | ||
| import com.mapbox.navigation.core.fasterroute.FasterRouteOptions | ||
| import com.mapbox.navigation.core.fasterroute.ComparisonFasterRouteTrackerCore | ||
| import com.mapbox.navigation.core.fasterroute.FasterRoutesTracker | ||
| import com.mapbox.navigation.core.history.MapboxHistoryReader | ||
| import com.mapbox.navigation.core.history.MapboxHistoryRecorder | ||
| import com.mapbox.navigation.core.internal.ReachabilityService | ||
|
|
@@ -272,6 +275,9 @@ class MapboxNavigation @VisibleForTesting internal constructor( | |
| ) | ||
| ) | ||
|
|
||
| @ExperimentalPreviewMapboxNavigationAPI | ||
| private var fasterRoutesInstance: FasterRoutesTracker? = null | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be destroyed in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| private val routeUpdateMutex = Mutex() | ||
|
|
||
| // native Router Interface | ||
|
|
@@ -1635,6 +1641,32 @@ class MapboxNavigation @VisibleForTesting internal constructor( | |
| routeRefreshController.unregisterRouteRefreshStateObserver(routeRefreshStatesObserver) | ||
| } | ||
|
|
||
| /*** | ||
| * @return existing instance of [FasterRoutesTracker]. | ||
| * New instance is created for the first call or after calling [FasterRoutesTracker.destroy] on existing | ||
| */ | ||
| @ExperimentalPreviewMapboxNavigationAPI | ||
| @UiThread | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: This is redundant, the whole class is tagged as |
||
| fun getFasterRoutesTracker( | ||
| fasterRouteOptions: FasterRouteOptions = FasterRouteOptions.Builder().build() | ||
| ): FasterRoutesTracker { | ||
| val currentInstance = fasterRoutesInstance | ||
| return if (currentInstance == null || currentInstance.isDestroyed) { | ||
| if (directionsSession.routes.isNotEmpty()) { | ||
| error("FasterRoutes should be created before setting the routes") | ||
| } | ||
| val newInstance = FasterRoutesTracker( | ||
| this, | ||
| ComparisonFasterRouteTrackerCore(fasterRouteOptions), | ||
| threadController.getMainScopeAndRootJob().scope | ||
| ) | ||
| fasterRoutesInstance = newInstance | ||
| newInstance | ||
| } else { | ||
| currentInstance | ||
| } | ||
| } | ||
|
|
||
| private fun startSession(withTripService: Boolean, withReplayEnabled: Boolean) { | ||
| runIfNotDestroyed { | ||
| tripSession.start( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| package com.mapbox.navigation.core.fasterroute | ||
|
|
||
| import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI | ||
| import com.mapbox.navigation.base.route.NavigationRoute | ||
| import com.mapbox.navigation.core.directions.session.RoutesExtra | ||
| import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult | ||
| import com.mapbox.navigation.core.fasterroute.Log.Companion.FASTER_ROUTE_LOG_CATEGORY | ||
| import com.mapbox.navigation.core.routealternatives.AlternativeRouteMetadata | ||
| import com.mapbox.navigation.utils.internal.logD | ||
|
|
||
| @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) | ||
| internal class ComparisonFasterRouteTrackerCore constructor( | ||
| options: FasterRouteOptions | ||
| ) : FasterRouteTrackerCore { | ||
|
|
||
| private val rejectedRoutesTracker = RejectedRoutesTracker( | ||
| maximumGeometrySimilarity = options.maxAcceptableGeometrySimilarityToRejectedAlternatives | ||
| ) | ||
|
|
||
| override fun fasterRouteDeclined(alternativeId: Int, route: NavigationRoute) { | ||
| rejectedRoutesTracker.addRejectedRoutes(mapOf(alternativeId to route)) | ||
| } | ||
|
|
||
| override suspend fun findFasterRouteInUpdate( | ||
| update: RoutesUpdatedResult, | ||
| alternativeRoutesMetadata: List<AlternativeRouteMetadata> | ||
| ): FasterRouteResult { | ||
| val metadataByRouteId = mutableMapOf<String, AlternativeRouteMetadata>().apply { | ||
| alternativeRoutesMetadata.forEach { this[it.navigationRoute.id] = it } | ||
| } | ||
| return when (update.reason) { | ||
| RoutesExtra.ROUTES_UPDATE_REASON_NEW, RoutesExtra.ROUTES_UPDATE_REASON_REROUTE -> { | ||
| onNewRoutes(alternativeRoutesMetadata) | ||
| FasterRouteResult.NoFasterRoute | ||
| } | ||
| RoutesExtra.ROUTES_UPDATE_REASON_ALTERNATIVE -> | ||
| onAlternativesChanged(alternativeRoutesMetadata, update, metadataByRouteId) | ||
| else -> FasterRouteResult.NoFasterRoute | ||
| } | ||
| } | ||
|
|
||
| private suspend fun onAlternativesChanged( | ||
| alternativeRoutesMetadata: List<AlternativeRouteMetadata>, | ||
| update: RoutesUpdatedResult, | ||
| metadataByRouteId: Map<String, AlternativeRouteMetadata> | ||
| ): FasterRouteResult { | ||
| val fasterAlternatives: Map<Int, NavigationRoute> = findFasterAlternatives( | ||
| alternativeRoutesMetadata, | ||
| update | ||
| ) | ||
| if (fasterAlternatives.isEmpty()) { | ||
| logD(FASTER_ROUTE_LOG_CATEGORY) { | ||
| "no alternatives which are faster then primary route found" | ||
| } | ||
| return FasterRouteResult.NoFasterRoute | ||
| } | ||
| logPotentialFasterRoutes(fasterAlternatives) | ||
|
|
||
| val untrackedAlternatives = rejectedRoutesTracker.findUntrackedAlternatives( | ||
| fasterAlternatives | ||
| ) | ||
| if (untrackedAlternatives.isEmpty()) { | ||
| logD(FASTER_ROUTE_LOG_CATEGORY) { | ||
| "all faster alternatives are similar to already rejected" | ||
| } | ||
| return FasterRouteResult.NoFasterRoute | ||
| } | ||
| logUntrackedFasterRoutes(untrackedAlternatives, metadataByRouteId) | ||
|
|
||
| return findTheFastestAlternative(untrackedAlternatives, metadataByRouteId, update) | ||
| } | ||
|
|
||
| private fun findTheFastestAlternative( | ||
| untracked: List<NavigationRoute>, | ||
| metadataByRouteId: Map<String, AlternativeRouteMetadata>, | ||
| update: RoutesUpdatedResult | ||
| ): FasterRouteResult { | ||
| val fasterAlternative = untracked.minByOrNull { | ||
| metadataByRouteId[it.id]!!.infoFromStartOfPrimary.duration | ||
| } ?: return FasterRouteResult.NoFasterRoute | ||
| val primaryRouteDuration = update.navigationRoutes.first().directionsRoute.duration() | ||
| val fasterAlternativeRouteDuration = metadataByRouteId[fasterAlternative.id]!! | ||
| .infoFromStartOfPrimary.duration | ||
| val fasterThanPrimary = primaryRouteDuration - fasterAlternativeRouteDuration | ||
| logD( | ||
| "route ${fasterAlternative.id} is faster than primary by $fasterThanPrimary", | ||
| FASTER_ROUTE_LOG_CATEGORY | ||
| ) | ||
| return FasterRouteResult.NewFasterRouteFound( | ||
| fasterAlternative, | ||
| fasterThanPrimaryBy = fasterThanPrimary, | ||
| alternativeId = metadataByRouteId[fasterAlternative.id]!!.alternativeId | ||
| ) | ||
| } | ||
|
|
||
| private fun findFasterAlternatives( | ||
| alternativeRoutesMetadata: List<AlternativeRouteMetadata>, | ||
| update: RoutesUpdatedResult | ||
| ): MutableMap<Int, NavigationRoute> { | ||
| return mutableMapOf<Int, NavigationRoute>() | ||
| .apply { | ||
| alternativeRoutesMetadata | ||
| .filter { | ||
| it.infoFromStartOfPrimary.duration < update.navigationRoutes.first() | ||
| .directionsRoute.duration() | ||
| } | ||
| .forEach { this[it.alternativeId] = it.navigationRoute } | ||
| } | ||
| } | ||
|
|
||
| private fun onNewRoutes(alternativeRoutesMetadata: List<AlternativeRouteMetadata>) { | ||
| val routeByAlternativeId: Map<Int, NavigationRoute> = mutableMapOf<Int, NavigationRoute>() | ||
| .apply { | ||
| alternativeRoutesMetadata.forEach { | ||
| this[it.alternativeId] = it.navigationRoute | ||
| } | ||
| } | ||
| logD( | ||
| "New routes were set, resetting tracker state", | ||
| FASTER_ROUTE_LOG_CATEGORY | ||
| ) | ||
| rejectedRoutesTracker.clean() | ||
| rejectedRoutesTracker.addRejectedRoutes(routeByAlternativeId) | ||
| } | ||
|
|
||
| private fun logUntrackedFasterRoutes( | ||
| untracked: List<NavigationRoute>, | ||
| metadataMap: Map<String, AlternativeRouteMetadata> | ||
| ) { | ||
| logD(FASTER_ROUTE_LOG_CATEGORY) { | ||
| "following routes are not similar to already rejected: " + | ||
| untracked.joinToString(separator = ", ") { | ||
| "${it.id}(${metadataMap[it.id]!!.alternativeId})" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun logPotentialFasterRoutes(fasterAlternatives: Map<Int, NavigationRoute>) { | ||
| val fasterAlternativesIdsLog = fasterAlternatives.entries | ||
| .joinToString(separator = ", ") { "${it.value.id}(${it.key})" } | ||
| logD(FASTER_ROUTE_LOG_CATEGORY) { | ||
| "considering following routes as a faster alternative: $fasterAlternativesIdsLog" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| internal sealed class FasterRouteResult { | ||
| object NoFasterRoute : FasterRouteResult() | ||
| data class NewFasterRouteFound( | ||
| val route: NavigationRoute, | ||
| val fasterThanPrimaryBy: Double, | ||
| val alternativeId: Int | ||
| ) : FasterRouteResult() | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||
| package com.mapbox.navigation.core.fasterroute | ||||||
|
|
||||||
| import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI | ||||||
|
|
||||||
| @ExperimentalPreviewMapboxNavigationAPI | ||||||
| data class FasterRouteOptions internal constructor( | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's okay to use data classes while feature is in experimental stage. I don't want to rewrite |
||||||
| val maxAcceptableGeometrySimilarityToRejectedAlternatives: Double | ||||||
| ) { | ||||||
| class Builder { | ||||||
|
|
||||||
| private var maxGeometrySimilarityToRejectedAlternatives = 0.5 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This ratio might work for intercity routes (or longer) but might make us unable to suggest faster alternatives in city centers that often rely on taking a turn earlier to later rejoin the original route and avoid congested intersection. These types of routes could have both a significant overlap while still having a significant time delta. I'm just speculating but just wanted to mention this as a discussion point, is there a more complex metric that we could use to account for these situations? Maybe road classes could be taken into account.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We do not compare alternative with the primary route. But if there is a rejected alternative which is very similar to the primary route, it could be a problem. What if we consider initial alternatives as rejected only if user picks slower route? It will decrease chance of this happening.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I will try to add comparison of steps names
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an experiment, I switched from comparing geometries to
|
||||||
|
|
||||||
| fun maxAcceptableGeometrySimilarityToRejectedAlternatives(value: Double): Builder { | ||||||
| assert(value in 0.0..1.0) { "similarity should be a value between 0 and 1" } | ||||||
VysotskiVadim marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| maxGeometrySimilarityToRejectedAlternatives = value | ||||||
| return this | ||||||
| } | ||||||
|
|
||||||
| fun build() = FasterRouteOptions(maxGeometrySimilarityToRejectedAlternatives) | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.mapbox.navigation.core.fasterroute | ||
|
|
||
| import com.mapbox.navigation.base.route.NavigationRoute | ||
| import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult | ||
| import com.mapbox.navigation.core.routealternatives.AlternativeRouteMetadata | ||
|
|
||
| internal interface FasterRouteTrackerCore { | ||
| fun fasterRouteDeclined(alternativeId: Int, route: NavigationRoute) | ||
|
|
||
| suspend fun findFasterRouteInUpdate( | ||
| update: RoutesUpdatedResult, | ||
| alternativeRoutesMetadata: List<AlternativeRouteMetadata> | ||
| ): FasterRouteResult | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
who knows how to get rid of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken it's a metalava issue and we leave it as is.