Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
8a8d56d
two cases of similar routes
VysotskiVadim Sep 20, 2022
c71b6ed
compare segments
VysotskiVadim Sep 20, 2022
57b0497
fixed different routes case
VysotskiVadim Sep 22, 2022
9b349a9
fixed test routes
VysotskiVadim Sep 22, 2022
81e3af6
made requirements more strict
VysotskiVadim Sep 22, 2022
a742ebb
use geomerty length
VysotskiVadim Sep 22, 2022
3f2441d
handle different ids
VysotskiVadim Sep 22, 2022
6a319fb
fixed mach of route to its half
VysotskiVadim Sep 23, 2022
7ed7049
always compare with shortest route
VysotskiVadim Sep 27, 2022
7c714cc
compare routes which ends the same
VysotskiVadim Sep 27, 2022
48fe49a
name explicitly
VysotskiVadim Sep 27, 2022
71b4c2c
name explicitly what it does
VysotskiVadim Sep 27, 2022
8bfcc45
added calculate route descriptions differences
VysotskiVadim Sep 27, 2022
ea5aceb
got rid of redundant simulation
VysotskiVadim Sep 27, 2022
99e5c4b
log faster route
VysotskiVadim Sep 27, 2022
b6e5c43
added request alternatives button
VysotskiVadim Sep 27, 2022
fbceeec
testing description similarity
VysotskiVadim Sep 27, 2022
b34b5b0
added description similarity
VysotskiVadim Sep 28, 2022
9f29b40
put rejected alternatives immediatly to the list
VysotskiVadim Sep 28, 2022
63e7fd9
implemented a system which records route updates
VysotskiVadim Sep 28, 2022
f2542ff
working on test
VysotskiVadim Sep 29, 2022
b5eb4a4
tested a route from munich to nurberg
VysotskiVadim Sep 29, 2022
5ce1c10
rename
VysotskiVadim Sep 29, 2022
67b4257
package refactoring
VysotskiVadim Sep 29, 2022
78e4641
name refactoring
VysotskiVadim Sep 29, 2022
58fd6e0
use rejected routes tracker in alternatives routes activities
VysotskiVadim Sep 29, 2022
195f84c
added metadata to tests
VysotskiVadim Sep 29, 2022
0003131
test faster route tracking
VysotskiVadim Sep 30, 2022
5b63d54
make accepted similarity as parameter
VysotskiVadim Sep 30, 2022
a2eb2af
added a case with faster route
VysotskiVadim Sep 30, 2022
f341422
fixed begining of the tracking
VysotskiVadim Sep 30, 2022
99ce419
improved performance
VysotskiVadim Sep 30, 2022
50168a3
cleanup
VysotskiVadim Sep 30, 2022
4cd1ec0
added dc through city case
VysotskiVadim Sep 30, 2022
3aac8c0
added faster route
VysotskiVadim Oct 3, 2022
199791b
package refactoring
VysotskiVadim Oct 3, 2022
209ce01
cleanup recorders
VysotskiVadim Oct 3, 2022
c113281
use faster route tracker
VysotskiVadim Oct 3, 2022
b12a7b3
got rid of dead code
VysotskiVadim Oct 3, 2022
5325db9
adding tests for faster routes
VysotskiVadim Oct 4, 2022
1e0f0cc
test fast routes updates
VysotskiVadim Oct 4, 2022
16f26d6
found multithreading related issue
VysotskiVadim Oct 4, 2022
02f229d
fixed faster multithreading
VysotskiVadim Oct 4, 2022
c485814
fixing code style
VysotskiVadim Oct 4, 2022
8f37c42
updated API
VysotskiVadim Oct 4, 2022
07d9e4d
fixed linter
VysotskiVadim Oct 4, 2022
da671cb
clenup
VysotskiVadim Oct 4, 2022
d05c1b3
cleanup
VysotskiVadim Oct 4, 2022
277ef2f
cleanup
VysotskiVadim Oct 4, 2022
2d783a0
writing docs
VysotskiVadim Oct 4, 2022
dfaefa3
implemented accept faster route
VysotskiVadim Oct 5, 2022
c94ca14
implemented decline of faster route
VysotskiVadim Oct 5, 2022
a081fb7
fixed code style
VysotskiVadim Oct 5, 2022
5c5e921
added comment
VysotskiVadim Oct 5, 2022
33e7cba
updated api
VysotskiVadim Oct 5, 2022
2cb0fa7
added missed experimental API annotation
VysotskiVadim Oct 5, 2022
16b72e1
added more logs
VysotskiVadim Oct 5, 2022
2bf5c91
fixed logs names
VysotskiVadim Oct 5, 2022
7fc4747
improved logs
VysotskiVadim Oct 5, 2022
5499888
added case with faster route
VysotskiVadim Oct 5, 2022
b2a0ef0
create faster route in observer
VysotskiVadim Oct 6, 2022
cc77d1c
applied lazy logs, improved messages, cleanup
VysotskiVadim Oct 6, 2022
22086be
improved naming
VysotskiVadim Oct 6, 2022
a7d9c49
fixed typos
VysotskiVadim Oct 6, 2022
dd97326
cleaning up
VysotskiVadim Oct 7, 2022
36f54b1
fixed log
VysotskiVadim Oct 7, 2022
344a909
fixing
VysotskiVadim Oct 7, 2022
f146b14
cleanup
VysotskiVadim Oct 7, 2022
c40354e
rename
VysotskiVadim Oct 7, 2022
011b3d7
exposes faster routes via mapbox navigation
VysotskiVadim Oct 7, 2022
77da23a
updated current api
VysotskiVadim Oct 7, 2022
730e97b
rename refatoring
VysotskiVadim Oct 7, 2022
5a15259
fixed tests
VysotskiVadim Oct 7, 2022
a1e5c50
fixed styles
VysotskiVadim Oct 7, 2022
25de20c
fixed naming
VysotskiVadim Oct 10, 2022
8ad9c71
updated api
VysotskiVadim Oct 10, 2022
364d4d8
added streets similarity
VysotskiVadim Oct 11, 2022
54fb028
print logs in unit tests
VysotskiVadim Oct 11, 2022
b367e70
compare routes by street names instead of geometries
VysotskiVadim Oct 11, 2022
3e1671a
extraced faster route core interface to add one more implementation
VysotskiVadim Oct 24, 2022
e698c45
added pinalty based faster route tracker
VysotskiVadim Oct 24, 2022
fb3fad1
trying streets penalties
VysotskiVadim Oct 24, 2022
8feade5
use only names for now
VysotskiVadim Oct 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions libnavigation-core/api/current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package com.mapbox.navigation.core {
method public com.mapbox.navigation.core.routealternatives.AlternativeRouteMetadata? getAlternativeMetadataFor(com.mapbox.navigation.base.route.NavigationRoute navigationRoute);
method public java.util.List<com.mapbox.navigation.core.routealternatives.AlternativeRouteMetadata> getAlternativeMetadataFor(java.util.List<com.mapbox.navigation.base.route.NavigationRoute> navigationRoutes);
method public com.mapbox.navigator.Experimental getExperimental();
method @UiThread @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public com.mapbox.navigation.core.fasterroute.FasterRoutesTracker getFasterRoutesTracker(com.mapbox.navigation.core.fasterroute.FasterRouteOptions fasterRouteOptions = FasterRouteOptions.<init>().build());
method public com.mapbox.navigation.core.trip.session.eh.GraphAccessor getGraphAccessor();
method public com.mapbox.navigation.core.history.MapboxHistoryRecorder getHistoryRecorder();
method public com.mapbox.navigation.core.replay.MapboxReplayer getMapboxReplayer();
Expand Down Expand Up @@ -184,6 +185,52 @@ package com.mapbox.navigation.core.directions.session {

}

package com.mapbox.navigation.core.fasterroute {

@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class FasterRouteOptions {
method public double component1();
method public com.mapbox.navigation.core.fasterroute.FasterRouteOptions copy(double maxAcceptableGeometrySimilarityToRejectedAlternatives);
method public double getMaxAcceptableGeometrySimilarityToRejectedAlternatives();
property public final double maxAcceptableGeometrySimilarityToRejectedAlternatives;
}

public static final class FasterRouteOptions.Builder {
ctor public FasterRouteOptions.Builder();
method public com.mapbox.navigation.core.fasterroute.FasterRouteOptions build();
method public com.mapbox.navigation.core.fasterroute.FasterRouteOptions.Builder maxAcceptableGeometrySimilarityToRejectedAlternatives(double value);
}

@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class FasterRoutesTracker {
method @UiThread public void acceptFasterRoute(com.mapbox.navigation.core.fasterroute.NewFasterRoute newFasterRoute);
method @UiThread public void declineFasterRoute(com.mapbox.navigation.core.fasterroute.NewFasterRoute newFasterRoute);
method @UiThread public void destroy();
method public void registerNewFasterRouteObserver(com.mapbox.navigation.core.fasterroute.NewFasterRouteObserver observer);
method public void unregisterNewFasterRouteObserver(com.mapbox.navigation.core.fasterroute.NewFasterRouteObserver observer);
}

@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class NewFasterRoute {
ctor public NewFasterRoute(com.mapbox.navigation.base.route.NavigationRoute fasterRoute, double fasterThanPrimaryRouteBy, int alternativeId);
method public com.mapbox.navigation.base.route.NavigationRoute component1();
method public double component2();
method public int component3();
method public com.mapbox.navigation.core.fasterroute.NewFasterRoute copy(com.mapbox.navigation.base.route.NavigationRoute fasterRoute, double fasterThanPrimaryRouteBy, int alternativeId);
method public int getAlternativeId();
method public com.mapbox.navigation.base.route.NavigationRoute getFasterRoute();
method public double getFasterThanPrimaryRouteBy();
property public final int alternativeId;
property public final com.mapbox.navigation.base.route.NavigationRoute fasterRoute;
property public final double fasterThanPrimaryRouteBy;
}

@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public fun interface NewFasterRouteObserver {
method public void onNewFasterRouteFound(com.mapbox.navigation.core.fasterroute.NewFasterRoute newFasterRoute);
}

public final class SimilarRoutesKt {
}
Comment on lines +229 to +230
Copy link
Contributor Author

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?

Copy link
Contributor

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.


}

package com.mapbox.navigation.core.formatter {

public final class FormattedDistanceData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -272,6 +275,9 @@ class MapboxNavigation @VisibleForTesting internal constructor(
)
)

@ExperimentalPreviewMapboxNavigationAPI
private var fasterRoutesInstance: FasterRoutesTracker? = null

Choose a reason for hiding this comment

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

This should be destroyed in MapboxNavigation#onDestroy since it's tightly coupled with the MapboxNavigation instance (including the coroutine scope).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FasterRoutesTracker takes coroutine scope as a parameter and stops working as soon as it cancels. FasterRoutesTracker was designed just to destroy FasterTracker earlier than MapboxNavigation. I'm already thinking of removing it to avoid confusions and simplify things.


private val routeUpdateMutex = Mutex()

// native Router Interface
Expand Down Expand Up @@ -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

Choose a reason for hiding this comment

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

nit: This is redundant, the whole class is tagged as @UiThread.

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(
Expand Down
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(

Choose a reason for hiding this comment

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

Suggested change
data class FasterRouteOptions internal constructor(
class FasterRouteOptions internal constructor(

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 equals, toString,
getHashCode every time I change set of fields. I plan to stop using data classes when feature goes stable and then implement all 3 methods. What do you think about this approach?

val maxAcceptableGeometrySimilarityToRejectedAlternatives: Double
) {
class Builder {

private var maxGeometrySimilarityToRejectedAlternatives = 0.5

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

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.

Copy link
Contributor Author

@VysotskiVadim VysotskiVadim Oct 11, 2022

Choose a reason for hiding this comment

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

is there a more complex metric that we could use to account for these situations?

I will try to add comparison of steps names

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As an experiment, I switched from comparing geometries to calculateStreetsSimilarity which calculates how much distance of the streets with the same name routes shares.
I see following corner cases with this approach:

  • It may happen that routes which are going through the different parts of the same streets may be considered similar (I don't think it's going to happen in case of alternative routes)
  • streets could have the same names in different cities


fun maxAcceptableGeometrySimilarityToRejectedAlternatives(value: Double): Builder {
assert(value in 0.0..1.0) { "similarity should be a value between 0 and 1" }
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
}
Loading