diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c452f0fc2..f17e0921893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ Mapbox welcomes participation and contributions from everyone. ## Unreleased #### Features #### Bug fixes and improvements +- Fixed an issue where re-routes could have failed for EV routes. [#6005](https://github.com/mapbox/mapbox-navigation-android/pull/6005) + +:warning: `RouteProgress#remainingWaypoints` defines the amount of all remaining waypoints, including explicit (requested with `RouteOptions#coordinatesList`) and implicit (added on the fly, like Electric Vehicle waypoints - [see](https://docs.mapbox.com/api/navigation/directions/#electric-vehicle-routing)) ones. ## Mapbox Navigation SDK 2.9.0-rc.1 - 28 October, 2022 ### Changelog @@ -24,7 +27,6 @@ This release depends on, and has been tested with, the following Mapbox dependen - Mapbox Java `v6.9.0-beta.1` ([release notes](https://github.com/mapbox/mapbox-java/releases/tag/v6.9.0-beta.1)) - Mapbox Android Core `v5.0.2` ([release notes](https://github.com/mapbox/mapbox-events-android/releases/tag/core-5.0.2)) - ## Mapbox Navigation SDK 2.9.0-beta.3 - 21 October, 2022 ### Changelog [Changes between v2.9.0-beta.2 and v2.9.0-beta.3](https://github.com/mapbox/mapbox-navigation-android/compare/v2.9.0-beta.2...v2.9.0-beta.3) diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/extensions/WaypointEx.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/extensions/WaypointEx.kt new file mode 100644 index 00000000000..829ac5bb0f2 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/extensions/WaypointEx.kt @@ -0,0 +1,52 @@ +package com.mapbox.navigation.base.internal.extensions + +import com.mapbox.api.directions.v5.models.DirectionsRoute +import com.mapbox.api.directions.v5.models.RouteOptions +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.trip.model.RouteProgress + +/** + * Return true if the waypoint is requested explicitly. False otherwise. + */ +fun Waypoint.isRequestedWaypoint(): Boolean = + when (this.internalType) { + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent -> true + Waypoint.InternalType.EvCharging -> false + } + +/** + * Return true if the waypoint is tracked in [RouteProgress.currentLegProgress]#legIndex, based on + * [DirectionsRoute.legs] index. False otherwise. + */ +fun Waypoint.isLegWaypoint(): Boolean = + when (this.internalType) { + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging -> true + Waypoint.InternalType.Silent -> false + } + +/** + * Return the index of **next requested** coordinate. See [RouteOptions.coordinatesList] + * + * For instance, EV waypoints are not requested explicitly, so they are not taken into account. + */ +fun indexOfNextRequestedCoordinate( + waypoints: List, + remainingWaypoints: Int, +): Int? { + if (remainingWaypoints > waypoints.size) { + return null + } + val nextWaypointIndex = waypoints.size - remainingWaypoints + var requestedIndex = 0 + waypoints.forEachIndexed { index, waypoint -> + if (waypoint.isRequestedWaypoint()) { + if (index >= nextWaypointIndex) { + return requestedIndex + } + requestedIndex++ + } + } + return null +} diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/route/Waypoint.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/route/Waypoint.kt new file mode 100644 index 00000000000..f2b888b0807 --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/route/Waypoint.kt @@ -0,0 +1,67 @@ +package com.mapbox.navigation.base.internal.route + +import androidx.annotation.IntDef +import com.mapbox.geojson.Point + +class Waypoint internal constructor( + val location: Point, + val name: String, + val target: Point?, + internal val internalType: InternalType +) { + + @Type + val type: Int = when (internalType) { + InternalType.Regular -> REGULAR + InternalType.Silent -> SILENT + InternalType.EvCharging -> EV_CHARGING + } + + companion object { + const val REGULAR = 1 + const val SILENT = 2 + const val EV_CHARGING = 3 + } + + @Target( + AnnotationTarget.PROPERTY, + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.FUNCTION, + AnnotationTarget.TYPE + ) + @Retention(AnnotationRetention.BINARY) + @IntDef(REGULAR, SILENT, EV_CHARGING) + annotation class Type + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Waypoint + + if (location != other.location) return false + if (type != other.type) return false + if (name != other.name) return false + if (target != other.target) return false + + return true + } + + override fun hashCode(): Int { + var result = location.hashCode() + result = 31 * result + type + result = 31 * result + name.hashCode() + result = 31 * result + target.hashCode() + return result + } + + override fun toString(): String { + return "Waypoint(location=$location, type=$type, name='$name', target=$target)" + } + + internal enum class InternalType { + Regular, + Silent, + EvCharging, + } +} diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/DirectionsRouteEx.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/DirectionsRouteEx.kt index e2b3fa9b556..a0400075507 100644 --- a/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/DirectionsRouteEx.kt +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/DirectionsRouteEx.kt @@ -4,6 +4,8 @@ package com.mapbox.navigation.base.internal.utils import com.mapbox.api.directions.v5.models.DirectionsRoute import com.mapbox.api.directions.v5.models.LegStep +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.internal.route.Waypoint.Companion.REGULAR import com.mapbox.navigation.base.utils.ifNonNull /** @@ -36,3 +38,20 @@ private fun DirectionsRoute.stepsNamesAsString(): String? = ?.joinToString { leg -> leg.steps()?.joinToString { step -> step.name() ?: "" } ?: "" } + +internal fun List.mapToSdk(): List = + map { nativeWaypoint -> + Waypoint( + location = nativeWaypoint.location, + internalType = nativeWaypoint.type.mapToSdk(), + name = nativeWaypoint.name, + target = nativeWaypoint.target, + ) + } + +private fun com.mapbox.navigator.WaypointType.mapToSdk(): Waypoint.InternalType = + when (this) { + com.mapbox.navigator.WaypointType.REGULAR -> Waypoint.InternalType.Regular + com.mapbox.navigator.WaypointType.SILENT -> Waypoint.InternalType.Silent + com.mapbox.navigator.WaypointType.EV_CHARGING -> Waypoint.InternalType.EvCharging + } diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/RouterEx.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/RouterEx.kt index 7a6e8f5f333..5f6b539dcc9 100644 --- a/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/RouterEx.kt +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/RouterEx.kt @@ -1,5 +1,7 @@ package com.mapbox.navigation.base.internal.utils +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.route.NavigationRoute import com.mapbox.navigator.RouterOrigin fun RouterOrigin.mapToSdkRouteOrigin(): com.mapbox.navigation.base.route.RouterOrigin = @@ -15,3 +17,5 @@ fun com.mapbox.navigation.base.route.RouterOrigin.mapToNativeRouteOrigin(): Rout com.mapbox.navigation.base.route.RouterOrigin.Onboard -> RouterOrigin.ONBOARD is com.mapbox.navigation.base.route.RouterOrigin.Custom -> RouterOrigin.CUSTOM } + +fun NavigationRoute.internalWaypoints(): List = waypoints diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/WaypointFactory.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/WaypointFactory.kt new file mode 100644 index 00000000000..247bf58b94f --- /dev/null +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/internal/utils/WaypointFactory.kt @@ -0,0 +1,27 @@ +package com.mapbox.navigation.base.internal.utils + +import androidx.annotation.VisibleForTesting +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.internal.route.Waypoint +import org.jetbrains.annotations.TestOnly + +@VisibleForTesting +object WaypointFactory { + @TestOnly + fun provideWaypoint( + location: Point, + name: String, + target: Point?, + @Waypoint.Type type: Int, + ): Waypoint = Waypoint( + location, + name, + target, + when (type) { + Waypoint.REGULAR -> Waypoint.InternalType.Regular + Waypoint.SILENT -> Waypoint.InternalType.Silent + Waypoint.EV_CHARGING -> Waypoint.InternalType.EvCharging + else -> throw IllegalStateException("Unknown waypoint type $type") + }, + ) +} diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/NavigationRoute.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/NavigationRoute.kt index 69d0afd1ffe..a6e84db9246 100644 --- a/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/NavigationRoute.kt +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/route/NavigationRoute.kt @@ -17,7 +17,10 @@ import com.mapbox.navigation.base.internal.NativeRouteParserWrapper import com.mapbox.navigation.base.internal.SDKRouteParser import com.mapbox.navigation.base.internal.factory.RoadObjectFactory.toUpcomingRoadObjects import com.mapbox.navigation.base.internal.route.RouteCompatibilityCache +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.internal.route.toNavigationRoute import com.mapbox.navigation.base.internal.utils.DirectionsRouteMissingConditionsCheck +import com.mapbox.navigation.base.internal.utils.mapToSdk import com.mapbox.navigation.base.internal.utils.mapToSdkRouteOrigin import com.mapbox.navigation.base.trip.model.roadobject.UpcomingRoadObject import com.mapbox.navigation.utils.internal.ThreadController @@ -293,6 +296,8 @@ class NavigationRoute internal constructor( */ val upcomingRoadObjects = nativeRoute.routeInfo.alerts.toUpcomingRoadObjects() + internal val waypoints: List by lazy { nativeRoute.waypoints.mapToSdk() } + /** * Indicates whether some other object is "equal to" this one. * diff --git a/libnavigation-base/src/main/java/com/mapbox/navigation/base/trip/model/RouteProgress.kt b/libnavigation-base/src/main/java/com/mapbox/navigation/base/trip/model/RouteProgress.kt index f54081a623e..9b9254675e2 100644 --- a/libnavigation-base/src/main/java/com/mapbox/navigation/base/trip/model/RouteProgress.kt +++ b/libnavigation-base/src/main/java/com/mapbox/navigation/base/trip/model/RouteProgress.kt @@ -33,7 +33,9 @@ import com.mapbox.navigation.base.trip.model.roadobject.UpcomingRoadObject * @param durationRemaining [Double] seconds time remaining until the route destination is reached. * @param fractionTraveled [Float] fraction traveled along the current route. This value is * between 0 and 1 and isn't guaranteed to reach 1 before the user reaches the end of the route. - * @param remainingWaypoints [Int] number of waypoints remaining on the current route. + * @param remainingWaypoints [Int] number of waypoints remaining on the current route. The waypoints number can be different + * with number of requested coordinates. For instance, [EV routing](https://docs.mapbox.com/api/navigation/directions/#electric-vehicle-routing) + * is adding additional waypoints, that are not requested explicitly. * @param upcomingRoadObjects list of upcoming road objects. * @param stale `true` if there were no location updates for a significant amount which causes * a lack of confidence in the progress updates being sent. diff --git a/libnavigation-base/src/test/java/com/mapbox/navigation/base/internal/extensions/WaypointExTest.kt b/libnavigation-base/src/test/java/com/mapbox/navigation/base/internal/extensions/WaypointExTest.kt new file mode 100644 index 00000000000..6e5bd763331 --- /dev/null +++ b/libnavigation-base/src/test/java/com/mapbox/navigation/base/internal/extensions/WaypointExTest.kt @@ -0,0 +1,261 @@ +package com.mapbox.navigation.base.internal.extensions + +import com.mapbox.navigation.base.internal.route.Waypoint +import io.mockk.every +import io.mockk.mockk +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +class WaypointExTest { + + @RunWith(Parameterized::class) + class FilterTest internal constructor( + private val waypoints: List, + private val requestedWaypointsExpected: List, + private val legsWaypointsExpected: List + ) { + companion object { + @JvmStatic + @Parameterized.Parameters + fun data() = listOf( + arrayOf( + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular + ), + listOf( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular + ), + listOf(Waypoint.InternalType.Regular, Waypoint.InternalType.Regular), + ), + arrayOf( + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular + ), + listOf( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular + ), + listOf( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + ), + arrayOf( + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + listOf(Waypoint.InternalType.Regular, Waypoint.InternalType.Regular), + listOf( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + ), + arrayOf( + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular, + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + listOf( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular, + Waypoint.InternalType.Regular, + Waypoint.InternalType.Regular + ), + listOf( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Regular, + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + ), + ) + + private fun checkWaypoints( + expectedWaypoints: List, + modified: List, + original: List, + ) { + assertEquals(expectedWaypoints.size, modified.size) + + var bufferIndex = -1 + modified.forEachIndexed { index, waypoint -> + assertEquals(expectedWaypoints[index], waypoint.internalType) + assertTrue(original.contains(waypoint)) + val idx = original.indexOf(waypoint) + assertTrue(idx > bufferIndex) + bufferIndex = idx + } + } + } + + @Test + fun testCases() { + checkWaypoints( + requestedWaypointsExpected, + waypoints.filter { it.isRequestedWaypoint() }, + waypoints + ) + checkWaypoints( + legsWaypointsExpected, + waypoints.filter { it.isLegWaypoint() }, + waypoints + ) + } + } + + @RunWith(Parameterized::class) + class IndexOfNextCoordinateTest( + private val testDescription: String, + private val waypoints: List, + private val remainingWaypoints: Int, + private val expectedIndex: Int? + ) { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data() = listOf( + arrayOf( + "Next index: 1 for 2 relevant waypoints and remaining waypoint 1", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Regular + ), + 1, + 1, + ), + arrayOf( + "Next index: 1 for 3 relevant waypoints and remaining waypoint 2", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular + ), + 2, + 1, + ), + arrayOf( + "Next index: 2 for 3 relevant waypoints and remaining waypoint 1", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular + ), + 1, + 2, + ), + arrayOf( + "Next index: 3 for 4 relevant waypoints and remaining waypoint 1", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Silent, + Waypoint.InternalType.Regular + ), + 1, + 3, + ), + arrayOf( + "Next index: 1 for 2 relevant waypoints (1 is EV) and remaining waypoint 2", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + 2, + 1, + ), + arrayOf( + "Next index: 1 for 3 relevant waypoints (2 is EV) and remaining waypoint 4", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Silent, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + 4, + 1, + ), + arrayOf( + "Next index: 1 for 3 relevant waypoints (2 is EV) and remaining waypoint 2", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Silent, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + 2, + 2, + ), + arrayOf( + "Next index: null for non-valid case - 3 relevant waypoints (2 is EV) and " + + "remaining waypoint 7", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Silent, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + 7, + null, + ), + arrayOf( + "Next index: 0 for 3 relevant waypoints (2 is EV) and remaining waypoint 5", + provideWaypoints( + Waypoint.InternalType.Regular, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Silent, + Waypoint.InternalType.EvCharging, + Waypoint.InternalType.Regular + ), + 5, + 0, + ), + ) + } + + @Test + fun testCases() { + assertEquals( + testDescription, + expectedIndex, + indexOfNextRequestedCoordinate(waypoints, remainingWaypoints) + ) + } + } +} + +private fun provideWaypoints(vararg waypointType: Waypoint.InternalType): List = + waypointType.map { mapToType -> + mockk { every { internalType } returns mapToType } + } diff --git a/libnavigation-base/src/test/java/com/mapbox/navigation/base/internal/route/NavigationRouteExTest.kt b/libnavigation-base/src/test/java/com/mapbox/navigation/base/internal/route/NavigationRouteExTest.kt index a08e62f3d7f..cee94da02fb 100644 --- a/libnavigation-base/src/test/java/com/mapbox/navigation/base/internal/route/NavigationRouteExTest.kt +++ b/libnavigation-base/src/test/java/com/mapbox/navigation/base/internal/route/NavigationRouteExTest.kt @@ -562,6 +562,7 @@ class NavigationRouteExTest { every { routeInfo } returns mockk(relaxed = true) every { routeId } returns "" every { routerOrigin } returns RouterOrigin.ONLINE + every { waypoints } returns emptyList() } ) } diff --git a/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/NavigationRouteTest.kt b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/NavigationRouteTest.kt index 65117f6b6ba..6b008ef41a3 100644 --- a/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/NavigationRouteTest.kt +++ b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/NavigationRouteTest.kt @@ -51,6 +51,7 @@ class NavigationRouteTest { every { routeInfo } returns mockk(relaxed = true) every { routeId } returns "$idBase#$it" every { routerOrigin } returns com.mapbox.navigator.RouterOrigin.ONBOARD + every { waypoints } returns emptyList() } ) } @@ -299,6 +300,7 @@ class NavigationRouteTest { every { routeInfo } returns mockk(relaxed = true) every { routeId } returns "some_id" every { routerOrigin } returns com.mapbox.navigator.RouterOrigin.ONBOARD + every { waypoints } returns emptyList() } ) } diff --git a/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/RouteExclusionsTest.kt b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/RouteExclusionsTest.kt index 03d42a5b32a..86c00e77f36 100644 --- a/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/RouteExclusionsTest.kt +++ b/libnavigation-base/src/test/java/com/mapbox/navigation/base/route/RouteExclusionsTest.kt @@ -42,6 +42,7 @@ class RouteExclusionsTest { every { routeInfo } returns mockk(relaxed = true) every { routeId } returns "$idBase#$it" every { routerOrigin } returns com.mapbox.navigator.RouterOrigin.ONBOARD + every { waypoints } returns emptyList() } ) } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/accounts/BillingController.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/accounts/BillingController.kt index a55249e7f25..fac4485f966 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/accounts/BillingController.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/accounts/BillingController.kt @@ -379,7 +379,7 @@ internal class BillingController( /** * Returns a list of remaining [Point]s that mark ends of legs on the route from the current [RouteProgress], - * ignoring origin and silent waypoints. + * ignoring origin. */ private fun getRemainingWaypointsOnRoute(routeProgress: RouteProgress?): List? { return routeProgress?.navigationRoute?.let { route -> @@ -394,7 +394,7 @@ internal class BillingController( /** * Returns a list of [Point]s that mark ends of legs on the route, - * ignoring origin and silent waypoints. + * ignoring origin. */ private fun getWaypointsOnRoute(navigationRoute: NavigationRoute): List? { return navigationRoute.directionsResponse.waypoints()?.drop(1)?.map { diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/history/model/HistoryEventMapper.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/history/model/HistoryEventMapper.kt index 3b617ca6bf3..081ae670b83 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/history/model/HistoryEventMapper.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/history/model/HistoryEventMapper.kt @@ -6,6 +6,8 @@ import com.mapbox.api.directions.v5.DirectionsCriteria import com.mapbox.api.directions.v5.models.DirectionsResponse import com.mapbox.api.directions.v5.models.DirectionsRoute import com.mapbox.api.directions.v5.models.RouteOptions +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.internal.utils.internalWaypoints import com.mapbox.navigation.base.internal.utils.mapToSdkRouteOrigin import com.mapbox.navigation.base.route.NavigationRoute import com.mapbox.navigation.base.route.toNavigationRoute @@ -21,6 +23,13 @@ import java.net.URL internal class HistoryEventMapper { + private companion object { + private const val LOG_CATEGORY = "HistoryEventMapper" + private const val NANOS_PER_SECOND = 1e-9 + private const val WAYPOINTS_JSON_KEY = "waypoints" + private const val NAME_JSON_KEY = "name" + } + fun map(historyRecord: HistoryRecord): HistoryEvent { val eventTimestamp = historyRecord.timestampNanoseconds * NANOS_PER_SECOND return when (historyRecord.type) { @@ -71,7 +80,9 @@ internal class HistoryEventMapper { legIndex = setRoute.legIndex, profile = mapToProfile(navigationRoute?.routeOptions), geometries = mapToGeometry(navigationRoute?.routeOptions), - waypoints = mapToWaypoints(navigationRoute?.routeOptions) + waypoints = mapToHistoryWaypoints( + navigationRoute + ) ) } @@ -148,16 +159,12 @@ internal class HistoryEventMapper { return jsonObject.toString() } - private fun mapToWaypoints(routeOptions: RouteOptions?): List { - val coordinatesList = routeOptions?.coordinatesList() - val waypointIndices = routeOptions?.waypointIndicesList() - return coordinatesList?.mapIndexed { index, coordinate -> - HistoryWaypoint( - point = coordinate, - isSilent = waypointIndices?.contains(index)?.not() == true - ) - } ?: emptyList() - } + private fun mapToHistoryWaypoints( + navigationRoute: NavigationRoute?, + ): List = + navigationRoute?.internalWaypoints() + ?.map { HistoryWaypoint(it.location, it.type == Waypoint.SILENT) } + ?: emptyList() @DirectionsCriteria.ProfileCriteria private fun mapToProfile(routeOptions: RouteOptions?): String { @@ -177,11 +184,4 @@ internal class HistoryEventMapper { pushHistoryRecord.type, pushHistoryRecord.properties ) - - private companion object { - - private const val NANOS_PER_SECOND = 1e-9 - private const val WAYPOINTS_JSON_KEY = "waypoints" - private const val NAME_JSON_KEY = "name" - } } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdater.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdater.kt index d3be3e814fd..6e1c23784e4 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdater.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdater.kt @@ -4,13 +4,14 @@ import com.mapbox.api.directions.v5.DirectionsCriteria import com.mapbox.api.directions.v5.models.Bearing import com.mapbox.api.directions.v5.models.RouteOptions import com.mapbox.geojson.Point +import com.mapbox.navigation.base.internal.extensions.indexOfNextRequestedCoordinate +import com.mapbox.navigation.base.internal.utils.internalWaypoints import com.mapbox.navigation.base.trip.model.RouteProgress import com.mapbox.navigation.core.MapboxNavigation import com.mapbox.navigation.core.reroute.MapboxRerouteController import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.core.trip.session.OffRouteObserver import com.mapbox.navigation.utils.internal.logE -import kotlin.math.min private const val DEFAULT_REROUTE_BEARING_TOLERANCE = 90.0 private const val LOG_CATEGORY = "RouteOptionsUpdater" @@ -46,94 +47,106 @@ class RouteOptionsUpdater { return RouteOptionsResult.Error(Throwable(msg)) } - val optionsBuilder = routeOptions.toBuilder() val coordinatesList = routeOptions.coordinatesList() - val remainingWaypoints = routeProgress.remainingWaypoints - if (remainingWaypoints == 0) { - val msg = """ - Reroute failed. There are no remaining waypoints on the route. - routeOptions=$routeOptions - locationMatcherResult=$locationMatcherResult - routeProgress=$routeProgress - """.trimIndent() - logE(msg, LOG_CATEGORY) - return RouteOptionsResult.Error(Throwable(msg)) + val (nextCoordinateIndex, remainingCoordinates) = indexOfNextRequestedCoordinate( + routeProgress.navigationRoute.internalWaypoints(), + routeProgress.remainingWaypoints, + ).let { + if (it == null) { + val msg = "Index of next coordinate is not defined" + logE(msg, LOG_CATEGORY) + return RouteOptionsResult.Error(Throwable(msg)) + } else if (coordinatesList.lastIndex < it) { + val msg = "Index of next coordinate is out of range of coordinates" + logE(msg, LOG_CATEGORY) + return RouteOptionsResult.Error(Throwable(msg)) + } else { + return@let it to coordinatesList.size - it + } } + val optionsBuilder = routeOptions.toBuilder() + try { - routeProgress.currentLegProgress?.legIndex?.let { index -> - val location = locationMatcherResult.enhancedLocation - optionsBuilder - .coordinatesList( - coordinatesList - .drop(coordinatesList.size - remainingWaypoints).toMutableList().apply { - add(0, Point.fromLngLat(location.longitude, location.latitude)) - } - ) - .bearingsList( - getUpdatedBearingList( - coordinatesList.size, - location.bearing.toDouble(), - routeOptions.bearingsList(), - remainingWaypoints - ) - ) - .radiusesList( - let radiusesList@{ - val radiusesList = routeOptions.radiusesList() - if (radiusesList.isNullOrEmpty()) { - return@radiusesList emptyList() - } - mutableListOf().also { - it.addAll(radiusesList.subList(index, coordinatesList.size)) - } + val location = locationMatcherResult.enhancedLocation + optionsBuilder + .coordinatesList( + coordinatesList + .subList(nextCoordinateIndex, coordinatesList.size) + .toMutableList() + .apply { + add(0, Point.fromLngLat(location.longitude, location.latitude)) } + ) + .bearingsList( + getUpdatedBearingList( + remainingCoordinates, + nextCoordinateIndex, + location.bearing.toDouble(), + routeOptions.bearingsList(), ) - .approachesList( - let approachesList@{ - val approachesList = routeOptions.approachesList() - if (approachesList.isNullOrEmpty()) { - return@approachesList emptyList() - } - mutableListOf().also { - it.addAll(approachesList.subList(index, coordinatesList.size)) - } + ) + .radiusesList( + let radiusesList@{ + val radiusesList = routeOptions.radiusesList() + if (radiusesList.isNullOrEmpty()) { + return@radiusesList emptyList() } - ) - .apply { - if (routeOptions.profile() == DirectionsCriteria.PROFILE_DRIVING_TRAFFIC) { - snappingIncludeClosuresList( - routeOptions.snappingIncludeClosuresList() - .withFirstTrue(remainingWaypoints, index, coordinatesList.size) - ) - snappingIncludeStaticClosuresList( - routeOptions.snappingIncludeStaticClosuresList() - .withFirstTrue(remainingWaypoints, index, coordinatesList.size) + radiusesList.subList( + nextCoordinateIndex - 1, + coordinatesList.size + ) + } + ) + .approachesList( + let approachesList@{ + val approachesList = routeOptions.approachesList() + if (approachesList.isNullOrEmpty()) { + return@approachesList emptyList() + } + mutableListOf().also { + it.addAll( + approachesList.subList( + nextCoordinateIndex - 1, + coordinatesList.size + ) ) } } - .waypointNamesList( - getUpdatedWaypointsList( - routeOptions.waypointNamesList(), - routeOptions.waypointIndicesList(), - coordinatesList.size - remainingWaypoints - 1 + ) + .apply { + if (routeOptions.profile() == DirectionsCriteria.PROFILE_DRIVING_TRAFFIC) { + snappingIncludeClosuresList( + routeOptions.snappingIncludeClosuresList() + .withFirstTrue(remainingCoordinates) ) - ) - .waypointTargetsList( - getUpdatedWaypointsList( - routeOptions.waypointTargetsList(), - routeOptions.waypointIndicesList(), - coordinatesList.size - remainingWaypoints - 1 + snappingIncludeStaticClosuresList( + routeOptions.snappingIncludeStaticClosuresList() + .withFirstTrue(remainingCoordinates) ) + } + } + .waypointNamesList( + getUpdatedWaypointsList( + routeOptions.waypointNamesList(), + routeOptions.waypointIndicesList(), + nextCoordinateIndex ) - .waypointIndicesList( - getUpdatedWaypointIndicesList( - routeOptions.waypointIndicesList(), - coordinatesList.size - remainingWaypoints - 1 - ) + ) + .waypointTargetsList( + getUpdatedWaypointsList( + routeOptions.waypointTargetsList(), + routeOptions.waypointIndicesList(), + nextCoordinateIndex ) - } + ) + .waypointIndicesList( + getUpdatedWaypointIndicesList( + routeOptions.waypointIndicesList(), + nextCoordinateIndex + ) + ) if ( routeOptions.profile() == DirectionsCriteria.PROFILE_DRIVING || @@ -143,9 +156,13 @@ class RouteOptionsUpdater { mutableListOf(locationMatcherResult.zLevel).apply { val legacyLayerList = routeOptions.layersList() if (legacyLayerList != null) { - addAll(legacyLayerList.takeLast(remainingWaypoints)) + addAll( + legacyLayerList.subList(nextCoordinateIndex, coordinatesList.size) + ) } else { - repeat(remainingWaypoints) { add(null) } + while (this.size < remainingCoordinates + 1) { + add(null) + } } } ) @@ -164,10 +181,10 @@ class RouteOptionsUpdater { } private fun getUpdatedBearingList( - coordinates: Int, + remainingCoordinates: Int, + nextCoordinateIndex: Int, currentAngle: Double, legacyBearingList: List?, - remainingWaypoints: Int ): MutableList { return ArrayList().also { newList -> val originTolerance = legacyBearingList?.getOrNull(0) @@ -176,15 +193,12 @@ class RouteOptionsUpdater { newList.add(Bearing.builder().angle(currentAngle).degrees(originTolerance).build()) if (legacyBearingList != null) { - newList.addAll( - legacyBearingList.subList( - coordinates - remainingWaypoints, - min(legacyBearingList.size, coordinates) - ) - ) + for (idx in nextCoordinateIndex..legacyBearingList.lastIndex) { + newList.add(legacyBearingList[idx]) + } } - while (newList.size < remainingWaypoints + 1) { + while (newList.size < remainingCoordinates + 1) { newList.add(null) } } @@ -192,30 +206,26 @@ class RouteOptionsUpdater { private fun getUpdatedWaypointIndicesList( waypointIndicesList: List?, - lastPassedWaypointIndex: Int + nextCoordinateIndex: Int ): MutableList { if (waypointIndicesList.isNullOrEmpty()) { return mutableListOf() } - return mutableListOf().also { updatedWaypointIndicesList -> - val updatedStartWaypointIndicesIndex = getUpdatedStartWaypointsListIndex( - waypointIndicesList, - lastPassedWaypointIndex - ) - updatedWaypointIndicesList.add(0) - updatedWaypointIndicesList.addAll( - waypointIndicesList.subList( - updatedStartWaypointIndicesIndex + 1, - waypointIndicesList.size - ).map { it - lastPassedWaypointIndex } - ) + return mutableListOf().apply { + add(0) + waypointIndicesList.forEach { value -> + val newVal = value - nextCoordinateIndex + 1 + if (newVal > 0) { + add(newVal) + } + } } } private fun getUpdatedWaypointsList( waypointsList: List?, waypointIndicesList: List?, - lastPassedWaypointIndex: Int + nextCoordinateIndex: Int ): MutableList { if (waypointsList.isNullOrEmpty()) { return mutableListOf() @@ -223,7 +233,7 @@ class RouteOptionsUpdater { return mutableListOf().also { updatedWaypointsList -> val updatedStartWaypointsListIndex = getUpdatedStartWaypointsListIndex( waypointIndicesList, - lastPassedWaypointIndex + nextCoordinateIndex ) updatedWaypointsList.add(waypointsList[updatedStartWaypointsListIndex]) updatedWaypointsList.addAll( @@ -237,11 +247,11 @@ class RouteOptionsUpdater { private fun getUpdatedStartWaypointsListIndex( waypointIndicesList: List?, - lastPassedWaypointIndex: Int + nextCoordinateIndex: Int ): Int { var updatedStartWaypointIndicesIndex = 0 waypointIndicesList?.forEachIndexed { indx, waypointIndex -> - if (waypointIndex <= lastPassedWaypointIndex) { + if (waypointIndex < nextCoordinateIndex) { updatedStartWaypointIndicesIndex = indx } } @@ -249,19 +259,17 @@ class RouteOptionsUpdater { } private fun List?.withFirstTrue( - remainingWaypoints: Int, - index: Int, - coordinatesListSize: Int, + remainingCoordinates: Int, ): List { return mutableListOf().also { newList -> // append true for the origin of the re-route request newList.add(true) if (isNullOrEmpty()) { // create `null` value for each upcoming waypoint - newList.addAll(arrayOfNulls(remainingWaypoints)) + newList.addAll(arrayOfNulls(remainingCoordinates)) } else { // get existing values for each upcoming waypoint - newList.addAll(subList(index + 1, coordinatesListSize)) + newList.addAll(takeLast(remainingCoordinates)) } } } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/MapboxTripSession.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/MapboxTripSession.kt index 58094b0bbf8..3474346f946 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/MapboxTripSession.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/MapboxTripSession.kt @@ -30,7 +30,7 @@ import com.mapbox.navigation.core.trip.session.eh.EHorizonObserver import com.mapbox.navigation.core.trip.session.eh.EHorizonSubscriptionManager import com.mapbox.navigation.navigator.internal.MapboxNativeNavigator import com.mapbox.navigation.navigator.internal.MapboxNativeNavigatorImpl -import com.mapbox.navigation.navigator.internal.TripStatus +import com.mapbox.navigation.navigator.internal.utils.calculateRemainingWaypoints import com.mapbox.navigation.utils.internal.JobControl import com.mapbox.navigation.utils.internal.ThreadController import com.mapbox.navigation.utils.internal.ifNonNull @@ -47,7 +47,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch import java.util.concurrent.CopyOnWriteArraySet -import kotlin.math.max /** * Default implementation of [TripSession] @@ -74,7 +73,6 @@ internal class MapboxTripSession( private companion object { private const val LOG_CATEGORY = "MapboxTripSession" - private const val INDEX_OF_INITIAL_LEG_TARGET = 1 } override suspend fun setRoutes( @@ -355,7 +353,7 @@ internal class MapboxTripSession( nativeBannerInstruction?.index ) } - val remainingWaypoints = calculateRemainingWaypoints(tripStatus) + val remainingWaypoints = tripStatus.calculateRemainingWaypoints() val routeProgress = getRouteProgressFrom( tripStatus.route, tripStatus.navigationStatus, @@ -377,33 +375,6 @@ internal class MapboxTripSession( triggerVoiceInstructionEvent(routeProgress, status) isOffRoute = tripStatus.navigationStatus.routeState == RouteState.OFF_ROUTE } - - private fun calculateRemainingWaypoints(tripStatus: TripStatus): Int { - val routeCoordinates = tripStatus.route?.routeOptions?.coordinatesList() - return if (routeCoordinates != null) { - val waypointsCount = routeCoordinates.size - val nextWaypointIndex = normalizeNextWaypointIndex( - tripStatus.navigationStatus.nextWaypointIndex - ) - return waypointsCount - nextWaypointIndex - } else { - 0 - } - } - - /** - * On the Android side, we always start navigation from the current position. - * So we expect that the next waypoint index will not be less than 1. - * But the native part considers the origin as a usual waypoint. - * It can return the next waypoint index 0. Be careful, this case isn't easy to reproduce. - * - * For example, nextWaypointIndex=0 leads to an incorrect rerouting. - * We don't want to get to an initial position even it hasn't been reached yet. - */ - private fun normalizeNextWaypointIndex(nextWaypointIndex: Int) = max( - INDEX_OF_INITIAL_LEG_TARGET, - nextWaypointIndex - ) } /** diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterParameterizedTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterParameterizedTest.kt index 2ee17f948c2..d2bd8eb8e1d 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterParameterizedTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterParameterizedTest.kt @@ -5,12 +5,15 @@ import com.mapbox.api.directions.v5.DirectionsCriteria import com.mapbox.api.directions.v5.models.RouteOptions import com.mapbox.core.constants.Constants import com.mapbox.geojson.Point +import com.mapbox.navigation.base.internal.extensions.indexOfNextRequestedCoordinate +import com.mapbox.navigation.base.internal.utils.internalWaypoints import com.mapbox.navigation.base.trip.model.RouteLegProgress import com.mapbox.navigation.base.trip.model.RouteProgress import com.mapbox.navigation.core.trip.session.LocationMatcherResult import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -22,7 +25,7 @@ import org.junit.runners.Parameterized class RouteOptionsUpdaterParameterizedTest( private val initWaypointNames: String, private val initWaypointIndices: String, - private val remainingWaypoints: Int, + private val nextCoordinateIndex: Int, private val expectedWaypointNames: String, private val expectedWaypointIndices: String ) { @@ -32,30 +35,30 @@ class RouteOptionsUpdaterParameterizedTest( @JvmStatic @Parameterized.Parameters fun data() = listOf( - arrayOf("start;finish", "0;6", 6, "start;finish", "0;6"), - arrayOf("start;finish", "0;6", 4, "start;finish", "0;4"), - arrayOf("start;finish", "0;6", 1, "start;finish", "0;1"), - arrayOf("start;mid;finish", "0;2;6", 5, "start;mid;finish", "0;1;5"), - arrayOf("start;mid;finish", "0;2;6", 4, "mid;finish", "0;4"), - arrayOf("start;mid;finish", "0;2;6", 3, "mid;finish", "0;3"), - arrayOf("start;mid1;mid2;finish", "0;2;4;6", 5, "start;mid1;mid2;finish", "0;1;3;5"), - arrayOf("start;mid1;mid2;finish", "0;2;4;6", 4, "mid1;mid2;finish", "0;2;4"), - arrayOf("start;mid1;mid2;finish", "0;2;4;6", 3, "mid1;mid2;finish", "0;1;3"), - arrayOf("start;mid1;mid2;finish", "0;2;4;6", 2, "mid2;finish", "0;2"), - arrayOf("start;mid1;mid2;finish", "0;2;4;6", 1, "mid2;finish", "0;1"), - arrayOf("start;mid1;mid2;finish", "0;2;3;6", 4, "mid1;mid2;finish", "0;1;4"), - arrayOf("start;mid1;mid2;finish", "0;2;3;6", 3, "mid2;finish", "0;3"), + arrayOf("start;finish", "0;6", 1, "start;finish", "0;6"), + arrayOf("start;finish", "0;6", 3, "start;finish", "0;4"), + arrayOf("start;finish", "0;6", 6, "start;finish", "0;1"), + arrayOf("start;mid;finish", "0;2;6", 2, "start;mid;finish", "0;1;5"), + arrayOf("start;mid;finish", "0;2;6", 3, "mid;finish", "0;4"), + arrayOf("start;mid;finish", "0;2;6", 4, "mid;finish", "0;3"), + arrayOf("start;mid1;mid2;finish", "0;2;4;6", 2, "start;mid1;mid2;finish", "0;1;3;5"), + arrayOf("start;mid1;mid2;finish", "0;2;4;6", 3, "mid1;mid2;finish", "0;2;4"), + arrayOf("start;mid1;mid2;finish", "0;2;4;6", 4, "mid1;mid2;finish", "0;1;3"), + arrayOf("start;mid1;mid2;finish", "0;2;4;6", 5, "mid2;finish", "0;2"), + arrayOf("start;mid1;mid2;finish", "0;2;4;6", 6, "mid2;finish", "0;1"), + arrayOf("start;mid1;mid2;finish", "0;2;3;6", 3, "mid1;mid2;finish", "0;1;4"), + arrayOf("start;mid1;mid2;finish", "0;2;3;6", 4, "mid2;finish", "0;3"), arrayOf( "start;mid1;mid2;mid3;mid4;mid5;finish", "0;1;2;3;4;5;6", - 6, + 1, "start;mid1;mid2;mid3;mid4;mid5;finish", "0;1;2;3;4;5;6" ), arrayOf( "start;mid1;mid2;mid3;mid4;mid5;finish", "0;1;2;3;4;5;6", - 2, + 5, "mid4;mid5;finish", "0;1;2" ) @@ -75,29 +78,36 @@ class RouteOptionsUpdaterParameterizedTest( @Test fun new_options_return_with_all_silent_waypoints() { - val routeOptions = provideRouteOptionsWithCoordinates() - .toBuilder() - .waypointNames(initWaypointNames) - .waypointIndices(initWaypointIndices) - .build() - val routeProgress: RouteProgress = mockk(relaxed = true) - val currentLegProgress: RouteLegProgress = mockk(relaxed = true) - every { routeProgress.currentLegProgress } returns currentLegProgress - every { routeProgress.remainingWaypoints } returns remainingWaypoints + mockkStatic(::indexOfNextRequestedCoordinate) { + val routeOptions = provideRouteOptionsWithCoordinates() + .toBuilder() + .waypointNames(initWaypointNames) + .waypointIndices(initWaypointIndices) + .build() + val routeProgress: RouteProgress = mockk(relaxed = true) + val currentLegProgress: RouteLegProgress = mockk(relaxed = true) + every { routeProgress.currentLegProgress } returns currentLegProgress + every { indexOfNextRequestedCoordinate(any(), any()) } returns nextCoordinateIndex + every { routeProgress.remainingWaypoints } returns 0 + every { routeProgress.navigationRoute.internalWaypoints() } returns listOf(mockk()) - val updatedRouteOptions = - routeRefreshAdapter.update(routeOptions, routeProgress, locationMatcherResult) - .let { - assertTrue(it is RouteOptionsUpdater.RouteOptionsResult.Success) - return@let it as RouteOptionsUpdater.RouteOptionsResult.Success - } - .routeOptions - val updatedWaypointNames = updatedRouteOptions.waypointNames() - val updatedWaypointIndices = updatedRouteOptions.waypointIndices() + val updatedRouteOptions = + routeRefreshAdapter.update(routeOptions, routeProgress, locationMatcherResult) + .let { + assertTrue(it is RouteOptionsUpdater.RouteOptionsResult.Success) + return@let it as RouteOptionsUpdater.RouteOptionsResult.Success + } + .routeOptions + val updatedWaypointNames = updatedRouteOptions.waypointNames() + val updatedWaypointIndices = updatedRouteOptions.waypointIndices() - assertEquals(expectedWaypointNames, updatedWaypointNames) - assertEquals(expectedWaypointIndices, updatedWaypointIndices) - MapboxRouteOptionsUpdateCommonTest.checkImmutableFields(routeOptions, updatedRouteOptions) + assertEquals(expectedWaypointIndices, updatedWaypointIndices) + assertEquals(expectedWaypointNames, updatedWaypointNames) + MapboxRouteOptionsUpdateCommonTest.checkImmutableFields( + routeOptions, + updatedRouteOptions + ) + } } private fun mockLocation() { diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterTest.kt index b82713e93d9..02a2490d124 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routeoptions/RouteOptionsUpdaterTest.kt @@ -6,6 +6,10 @@ import com.mapbox.api.directions.v5.models.Bearing import com.mapbox.api.directions.v5.models.RouteOptions import com.mapbox.core.constants.Constants import com.mapbox.geojson.Point +import com.mapbox.navigation.base.internal.extensions.indexOfNextRequestedCoordinate +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.internal.utils.WaypointFactory +import com.mapbox.navigation.base.internal.utils.internalWaypoints import com.mapbox.navigation.base.trip.model.RouteProgress import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.testing.LoggingFrontendTestRule @@ -13,6 +17,10 @@ import com.mapbox.navigation.testing.factories.createLocation import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -41,9 +49,9 @@ class RouteOptionsUpdaterTest { .coordinatesList( listOf( Point.fromLngLat(1.0, 1.0), - Point.fromLngLat(1.0, 1.0), - Point.fromLngLat(1.0, 1.0), - Point.fromLngLat(1.0, 1.0) + Point.fromLngLat(2.0, 2.0), + Point.fromLngLat(3.0, 3.0), + Point.fromLngLat(4.0, 4.0) ) ) .build() @@ -97,7 +105,47 @@ class RouteOptionsUpdaterTest { .roundaboutExits(true) .voiceInstructions(true) + private fun provideDefaultWaypointsList(): List = + listOf( + WaypointFactory.provideWaypoint( + Point.fromLngLat(1.0, 1.0), + "", + null, + Waypoint.REGULAR + ), + WaypointFactory.provideWaypoint( + Point.fromLngLat(2.0, 2.0), + "", + null, + Waypoint.REGULAR + ), + WaypointFactory.provideWaypoint( + Point.fromLngLat(3.0, 3.0), + "", + null, + Waypoint.REGULAR + ), + WaypointFactory.provideWaypoint( + Point.fromLngLat(4.0, 4.0), + "", + null, + Waypoint.REGULAR + ), + ) + private fun Any?.isNullToString(): String = if (this == null) "Null" else "NonNull" + + private fun mockWaypoint( + location: Point, + @Waypoint.Type type: Int, + name: String, + target: Point?, + ): Waypoint = mockk { + every { this@mockk.location } returns location + every { this@mockk.type } returns type + every { this@mockk.name } returns name + every { this@mockk.target } returns target + } } @Before @@ -113,6 +161,7 @@ class RouteOptionsUpdaterTest { val routeOptions = provideRouteOptionsWithCoordinates() val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns 1 + every { navigationRoute.internalWaypoints() } returns provideDefaultWaypointsList() } val newRouteOptions = @@ -137,10 +186,11 @@ class RouteOptionsUpdaterTest { } @Test - fun new_options_return_with_bearing() { + fun new_options_return_with_bearings() { val routeOptions = provideRouteOptionsWithCoordinatesAndBearings() val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns 1 + every { navigationRoute.internalWaypoints() } returns provideDefaultWaypointsList() } val newRouteOptions = @@ -172,6 +222,7 @@ class RouteOptionsUpdaterTest { val routeOptions = provideRouteOptionsWithCoordinates() val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns 2 + every { navigationRoute.internalWaypoints() } returns provideDefaultWaypointsList() } val newRouteOptions = @@ -203,6 +254,9 @@ class RouteOptionsUpdaterTest { .build() val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns 2 + every { + navigationRoute.internalWaypoints() + } returns provideDefaultWaypointsList() } val newRouteOptions = @@ -230,6 +284,7 @@ class RouteOptionsUpdaterTest { val routeOptions = provideRouteOptionsWithCoordinatesAndLayers() val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns 2 + every { navigationRoute.internalWaypoints() } returns provideDefaultWaypointsList() } val newRouteOptions = @@ -260,6 +315,25 @@ class RouteOptionsUpdaterTest { assertTrue(newRouteOptions is RouteOptionsUpdater.RouteOptionsResult.Error) } + @Test + fun index_of_next_coordinate_is_null() { + mockkStatic(::indexOfNextRequestedCoordinate) { + val routeOptions = provideRouteOptionsWithCoordinatesAndBearings() + val routeProgress: RouteProgress = mockk(relaxed = true) { + every { remainingWaypoints } returns 0 + // list size 1 + every { navigationRoute.routeOptions.coordinatesList() } returns listOf(mockk()) + } + every { indexOfNextRequestedCoordinate(any(), any()) } returns null + + val newRouteOptions = + routeRefreshAdapter.update(routeOptions, routeProgress, locationMatcherResult) + + verify(exactly = 1) { indexOfNextRequestedCoordinate(any(), any()) } + assertTrue(newRouteOptions is RouteOptionsUpdater.RouteOptionsResult.Error) + } + } + @Test fun no_options_on_invalid_input() { val invalidInput = listOf>( @@ -287,6 +361,7 @@ class RouteOptionsUpdaterTest { val routeOptions = provideRouteOptionsWithCoordinatesAndArriveByDepartAt() val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns 1 + every { navigationRoute.internalWaypoints() } returns provideDefaultWaypointsList() } val newRouteOptions = @@ -316,7 +391,7 @@ class RouteOptionsUpdaterTest { @RunWith(Parameterized::class) class BearingOptionsParameterized( val routeOptions: RouteOptions, - val remainingWaypointsParameter: Int, + val indexNextCoordinate: Int, val expectedBearings: List ) { @@ -329,7 +404,7 @@ class RouteOptionsUpdaterTest { fun params() = listOf( arrayOf( provideRouteOptionsWithCoordinatesAndBearings(), - 3, + 1, listOf( Bearing.builder() .angle(DEFAULT_REROUTE_BEARING_ANGLE.toDouble()) @@ -351,7 +426,7 @@ class RouteOptionsUpdaterTest { ), arrayOf( provideRouteOptionsWithCoordinates(), - 1, + 3, listOf( Bearing.builder() .angle(DEFAULT_REROUTE_BEARING_ANGLE.toDouble()) @@ -398,7 +473,7 @@ class RouteOptionsUpdaterTest { ) ) .build(), - 3, + 1, listOf( Bearing.builder() .angle(DEFAULT_REROUTE_BEARING_ANGLE.toDouble()) @@ -416,14 +491,20 @@ class RouteOptionsUpdaterTest { fun setup() { MockKAnnotations.init(this, relaxUnitFun = true, relaxed = true) mockLocation() + mockkStatic(::indexOfNextRequestedCoordinate) routeRefreshAdapter = RouteOptionsUpdater() } + @After + fun cleanup() { + unmockkStatic(::indexOfNextRequestedCoordinate) + } + @Test fun bearingOptions() { val routeProgress: RouteProgress = mockk(relaxed = true) { - every { remainingWaypoints } returns remainingWaypointsParameter + every { indexOfNextRequestedCoordinate(any(), any()) } returns indexNextCoordinate } val newRouteOptions = @@ -457,7 +538,7 @@ class RouteOptionsUpdaterTest { val routeOptions: RouteOptions, val remainingWaypointsParameter: Int, val legIndex: Int, - val expectedSnappingClosures: String? + val expectedSnappingClosures: String?, ) { private lateinit var routeRefreshAdapter: RouteOptionsUpdater @@ -465,11 +546,10 @@ class RouteOptionsUpdaterTest { companion object { @JvmStatic - @Parameterized.Parameters + @Parameterized.Parameters(name = "{0}") fun params() = listOf( arrayOf( - provideRouteOptionsWithCoordinates() - .toBuilder() + provideRouteOptionsWithCoordinates().toBuilder() .snappingIncludeClosuresList( listOf( true, @@ -490,8 +570,7 @@ class RouteOptionsUpdaterTest { "true;" ), arrayOf( - provideRouteOptionsWithCoordinates() - .toBuilder() + provideRouteOptionsWithCoordinates().toBuilder() .snappingIncludeClosuresList( listOf( true, @@ -534,7 +613,6 @@ class RouteOptionsUpdaterTest { @Before fun setup() { - MockKAnnotations.init(this, relaxUnitFun = true, relaxed = true) mockLocation() routeRefreshAdapter = RouteOptionsUpdater() @@ -545,6 +623,7 @@ class RouteOptionsUpdaterTest { val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns remainingWaypointsParameter every { currentLegProgress?.legIndex } returns legIndex + every { navigationRoute.internalWaypoints() } returns provideDefaultWaypointsList() } val newRouteOptions = @@ -581,6 +660,9 @@ class RouteOptionsUpdaterTest { val expectedSnappingStaticClosures: String? ) { + @get:Rule + val loggerRule = LoggingFrontendTestRule() + private lateinit var routeRefreshAdapter: RouteOptionsUpdater private lateinit var locationMatcherResult: LocationMatcherResult @@ -666,6 +748,7 @@ class RouteOptionsUpdaterTest { val routeProgress: RouteProgress = mockk(relaxed = true) { every { remainingWaypoints } returns remainingWaypointsParameter every { currentLegProgress?.legIndex } returns legIndex + every { navigationRoute.internalWaypoints() } returns provideDefaultWaypointsList() } val newRouteOptions = diff --git a/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/utils/TripStatusEx.kt b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/utils/TripStatusEx.kt new file mode 100644 index 00000000000..c5813ca8fc1 --- /dev/null +++ b/libnavigator/src/main/java/com/mapbox/navigation/navigator/internal/utils/TripStatusEx.kt @@ -0,0 +1,34 @@ +package com.mapbox.navigation.navigator.internal.utils + +import com.mapbox.navigation.base.internal.utils.internalWaypoints +import com.mapbox.navigation.navigator.internal.TripStatus +import kotlin.math.max + +private const val INDEX_OF_INITIAL_LEG_TARGET = 1 + +fun TripStatus.calculateRemainingWaypoints(): Int { + val routeWaypoints = this.route?.internalWaypoints() + return if (routeWaypoints != null) { + val waypointsCount = routeWaypoints.size + val nextWaypointIndex = normalizeNextWaypointIndex( + this.navigationStatus.nextWaypointIndex + ) + return waypointsCount - nextWaypointIndex + } else { + 0 + } +} + +/** + * On the Android side, we always start navigation from the current position. + * So we expect that the next waypoint index will not be less than 1. + * But the native part considers the origin as a usual waypoint. + * It can return the next waypoint index 0. Be careful, this case isn't easy to reproduce. + * + * For example, nextWaypointIndex=0 leads to an incorrect rerouting. + * We don't want to get to an initial position even it hasn't been reached yet. + */ +private fun normalizeNextWaypointIndex(nextWaypointIndex: Int) = max( + INDEX_OF_INITIAL_LEG_TARGET, + nextWaypointIndex +) diff --git a/libnavigator/src/test/java/com/mapbox/navigation/navigator/internal/router/utils/TripStatusExTest.kt b/libnavigator/src/test/java/com/mapbox/navigation/navigator/internal/router/utils/TripStatusExTest.kt new file mode 100644 index 00000000000..8ab17f3cfac --- /dev/null +++ b/libnavigator/src/test/java/com/mapbox/navigation/navigator/internal/router/utils/TripStatusExTest.kt @@ -0,0 +1,102 @@ +package com.mapbox.navigation.navigator.internal.router.utils + +import com.mapbox.geojson.Point +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.internal.utils.WaypointFactory +import com.mapbox.navigation.base.internal.utils.internalWaypoints +import com.mapbox.navigation.base.route.NavigationRoute +import com.mapbox.navigation.navigator.internal.TripStatus +import com.mapbox.navigation.navigator.internal.utils.calculateRemainingWaypoints +import io.mockk.every +import io.mockk.mockk +import junit.framework.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +class TripStatusExTest { + + @RunWith(Parameterized::class) + class CalculateRemainingWaypointsTest( + val description: String, + val routeWithWaypoints: NavigationRoute?, + val nextWaypointIndex: Int, + val expectedResult: Int, + ) { + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data() = listOf( + arrayOf( + "null route returns 0", + null, + Int.MIN_VALUE, + 0, + ), + arrayOf( + "next waypoint index normalized is normalized to default initial leg target", + mockk { + every { + internalWaypoints() + } returns provideMockListOfWaypoints( + Waypoint.REGULAR, + Waypoint.REGULAR + ) + }, + 0, + 1, + ), + arrayOf( + "all waypoints are taken into account to get remaining waypoints", + mockk { + every { + internalWaypoints() + } returns provideMockListOfWaypoints( + Waypoint.REGULAR, + Waypoint.SILENT, + Waypoint.EV_CHARGING, + Waypoint.SILENT, + Waypoint.REGULAR, + Waypoint.REGULAR, + Waypoint.SILENT, + Waypoint.EV_CHARGING, + Waypoint.EV_CHARGING, + Waypoint.REGULAR, + Waypoint.REGULAR, + ) + }, + 8, + 3, + ), + ) + + private fun provideMockListOfWaypoints( + @Waypoint.Type vararg types: Int + ): List = + types.map { type -> + WaypointFactory.provideWaypoint( + Point.fromLngLat(0.0, 0.0), + "", + null, + type + ) + } + } + + @Test + fun testCases() { + val tripStatus = TripStatus( + routeWithWaypoints, + mockk { + every { + nextWaypointIndex + } returns this@CalculateRemainingWaypointsTest.nextWaypointIndex + } + ) + + val result = tripStatus.calculateRemainingWaypoints() + + assertEquals(description, expectedResult, result) + } + } +} diff --git a/libnavui-maps/src/main/java/com/mapbox/navigation/ui/maps/building/BuildingProcessor.kt b/libnavui-maps/src/main/java/com/mapbox/navigation/ui/maps/building/BuildingProcessor.kt index 96ac3c753bf..bac4f463991 100644 --- a/libnavui-maps/src/main/java/com/mapbox/navigation/ui/maps/building/BuildingProcessor.kt +++ b/libnavui-maps/src/main/java/com/mapbox/navigation/ui/maps/building/BuildingProcessor.kt @@ -2,9 +2,10 @@ package com.mapbox.navigation.ui.maps.building import com.mapbox.bindgen.ExpectedFactory import com.mapbox.maps.RenderedQueryOptions +import com.mapbox.navigation.base.internal.extensions.isLegWaypoint +import com.mapbox.navigation.base.internal.utils.internalWaypoints import com.mapbox.navigation.ui.maps.building.model.BuildingError import com.mapbox.navigation.ui.maps.building.model.BuildingValue -import com.mapbox.navigation.utils.internal.ifNonNull import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -47,25 +48,18 @@ internal object BuildingProcessor { fun queryBuildingOnWaypoint( action: BuildingAction.QueryBuildingOnWaypoint ): BuildingResult.GetDestination { - val routeOptions = action.progress.route.routeOptions() - val coordinatesList = routeOptions?.coordinatesList() - val waypointTargets = routeOptions?.waypointTargetsList() - val coordinateIndex = action.progress.currentLegProgress?.legIndex!! + 1 - val routablePoint = coordinatesList?.getOrNull(coordinateIndex) - val locationPoint = waypointTargets?.getOrNull(coordinateIndex) - return ifNonNull(locationPoint) { point -> - BuildingResult.GetDestination(point) - } ?: BuildingResult.GetDestination(routablePoint) + val waypoints = action.progress.navigationRoute.internalWaypoints() + val waypointIndex = action.progress.currentLegProgress?.legIndex!! + 1 + val waypoint = waypoints.filter { it.isLegWaypoint() }.getOrNull(waypointIndex) + return BuildingResult.GetDestination(waypoint?.target ?: waypoint?.location) } fun queryBuildingOnFinalDestination( action: BuildingAction.QueryBuildingOnFinalDestination ): BuildingResult.GetDestination { - val routeOptions = action.progress.route.routeOptions() - val lastCoordinate = routeOptions?.coordinatesList()?.lastOrNull() - val lastWaypointTarget = routeOptions?.waypointTargetsList()?.lastOrNull() - return ifNonNull(lastWaypointTarget) { point -> - BuildingResult.GetDestination(point) - } ?: BuildingResult.GetDestination(lastCoordinate) + val lastWaypoint = action.progress.navigationRoute + .internalWaypoints() + .lastOrNull() + return BuildingResult.GetDestination(lastWaypoint?.target ?: lastWaypoint?.location) } } diff --git a/libnavui-maps/src/test/java/com/mapbox/navigation/ui/maps/building/BuildingProcessorTest.kt b/libnavui-maps/src/test/java/com/mapbox/navigation/ui/maps/building/BuildingProcessorTest.kt index 4224dbaf43b..a1b2025e246 100644 --- a/libnavui-maps/src/test/java/com/mapbox/navigation/ui/maps/building/BuildingProcessorTest.kt +++ b/libnavui-maps/src/test/java/com/mapbox/navigation/ui/maps/building/BuildingProcessorTest.kt @@ -8,6 +8,9 @@ import com.mapbox.maps.MapboxMap import com.mapbox.maps.QueryFeaturesCallback import com.mapbox.maps.RenderedQueryOptions import com.mapbox.maps.ScreenCoordinate +import com.mapbox.navigation.base.internal.route.Waypoint +import com.mapbox.navigation.base.internal.utils.WaypointFactory +import com.mapbox.navigation.base.internal.utils.internalWaypoints import com.mapbox.navigation.base.trip.model.RouteLegProgress import com.mapbox.navigation.base.trip.model.RouteProgress import io.mockk.every @@ -23,106 +26,111 @@ class BuildingProcessorTest { @Test fun `map query on waypoint arrival with waypoint targets`() { - val mockOriginForWaypointTarget = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForWaypointTarget = Point.fromLngLat(-122.4183, 37.7653) - val mockFinalForWaypointTarget = Point.fromLngLat(-122.4146, 37.7655) - val mockOriginForCoordinates = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForCoordinates = Point.fromLngLat(-122.4182, 37.7651) - val mockFinalForCoordinates = Point.fromLngLat(-122.4145, 37.7653) - val mockRouteOptions = mockk(relaxed = true) { - every { coordinatesList() } returns listOf( - mockOriginForCoordinates, - mockWaypointForCoordinates, - mockFinalForCoordinates - ) - every { waypointTargetsList() } returns listOf( - mockOriginForWaypointTarget, - mockWaypointForWaypointTarget, - mockFinalForWaypointTarget - ) - } - val mockRoute = mockk(relaxed = true) { - every { routeOptions() } returns mockRouteOptions - } + val waypoints = listOf( + provideWaypoint( + Point.fromLngLat(-122.4192, 37.7627), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4192, 37.7627), + ), + provideWaypoint( + Point.fromLngLat(-122.4182, 37.7651), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4183, 37.7653), + ), + provideWaypoint( + Point.fromLngLat(-122.4145, 37.7653), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4146, 37.7655), + ), + ) val mockRouteLegProgress = mockk(relaxed = true) { every { legIndex } returns 0 } val mockRouteProgress = mockk(relaxed = true) { every { currentLegProgress } returns mockRouteLegProgress - every { route } returns mockRoute + every { navigationRoute.internalWaypoints() } returns waypoints } val mockAction = BuildingAction.QueryBuildingOnWaypoint(mockRouteProgress) val result = BuildingProcessor.queryBuildingOnWaypoint(mockAction) - assertEquals(result.point, mockWaypointForWaypointTarget) + assertEquals(waypoints[1].target!!, result.point) } @Test fun `map query on waypoint arrival with some waypoint targets`() { - val mockOriginForWaypointTarget = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForWaypointTarget = null - val mockFinalForWaypointTarget = Point.fromLngLat(-122.4146, 37.7655) - val mockOriginForCoordinates = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForCoordinates = Point.fromLngLat(-122.4182, 37.7651) - val mockFinalForCoordinates = Point.fromLngLat(-122.4145, 37.7653) - val mockRouteOptions = mockk(relaxed = true) { - every { coordinatesList() } returns listOf( - mockOriginForCoordinates, - mockWaypointForCoordinates, - mockFinalForCoordinates - ) - every { waypointTargetsList() } returns listOf( - mockOriginForWaypointTarget, - mockWaypointForWaypointTarget, - mockFinalForWaypointTarget - ) - } - val mockRoute = mockk(relaxed = true) { - every { routeOptions() } returns mockRouteOptions - } + val waypoints = listOf( + provideWaypoint( + Point.fromLngLat(-122.4192, 37.7627), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4192, 37.7627), + ), + provideWaypoint( + Point.fromLngLat(-122.4182, 37.7651), + Waypoint.REGULAR, + "", + null, + ), + provideWaypoint( + Point.fromLngLat(-122.4145, 37.7653), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4146, 37.7655), + ), + ) val mockRouteLegProgress = mockk(relaxed = true) { every { legIndex } returns 0 } val mockRouteProgress = mockk(relaxed = true) { every { currentLegProgress } returns mockRouteLegProgress - every { route } returns mockRoute + every { navigationRoute.internalWaypoints() } returns waypoints } + val mockAction = BuildingAction.QueryBuildingOnWaypoint(mockRouteProgress) val result = BuildingProcessor.queryBuildingOnWaypoint(mockAction) - assertEquals(result.point, mockWaypointForCoordinates) + assertEquals(waypoints[1].location, result.point) } @Test fun `map query on waypoint arrival without waypoint targets`() { - val mockOriginForCoordinates = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForCoordinates = Point.fromLngLat(-122.4182, 37.7651) - val mockFinalForCoordinates = Point.fromLngLat(-122.4145, 37.7653) - val mockRouteOptions = mockk(relaxed = true) { - every { coordinatesList() } returns listOf( - mockOriginForCoordinates, - mockWaypointForCoordinates, - mockFinalForCoordinates - ) - every { waypointTargetsList() } returns null - } - val mockRoute = mockk(relaxed = true) { - every { routeOptions() } returns mockRouteOptions - } + val waypoints = listOf( + provideWaypoint( + Point.fromLngLat(-122.4192, 37.7627), + Waypoint.REGULAR, + "", + null, + ), + provideWaypoint( + Point.fromLngLat(-122.4182, 37.7651), + Waypoint.REGULAR, + "", + null, + ), + provideWaypoint( + Point.fromLngLat(-122.4145, 37.7653), + Waypoint.REGULAR, + "", + null, + ), + ) val mockRouteLegProgress = mockk(relaxed = true) { every { legIndex } returns 0 } val mockRouteProgress = mockk(relaxed = true) { every { currentLegProgress } returns mockRouteLegProgress - every { route } returns mockRoute + every { navigationRoute.internalWaypoints() } returns waypoints } val mockAction = BuildingAction.QueryBuildingOnWaypoint(mockRouteProgress) val result = BuildingProcessor.queryBuildingOnWaypoint(mockAction) - assertEquals(result.point, mockWaypointForCoordinates) + assertEquals(waypoints[1].location, result.point) } @Test @@ -150,69 +158,74 @@ class BuildingProcessorTest { @Test fun `map query on final destination arrival with waypoint targets`() { - val mockOriginForWaypointTarget = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForWaypointTarget = Point.fromLngLat(-122.4183, 37.7653) - val mockFinalForWaypointTarget = Point.fromLngLat(-122.4146, 37.7655) - val mockOriginForCoordinates = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForCoordinates = Point.fromLngLat(-122.4182, 37.7651) - val mockFinalForCoordinates = Point.fromLngLat(-122.4145, 37.7653) - val mockRouteOptions = mockk(relaxed = true) { - every { coordinatesList() } returns listOf( - mockOriginForCoordinates, - mockWaypointForCoordinates, - mockFinalForCoordinates - ) - every { waypointTargetsList() } returns listOf( - mockOriginForWaypointTarget, - mockWaypointForWaypointTarget, - mockFinalForWaypointTarget - ) - } - val mockRoute = mockk(relaxed = true) { - every { routeOptions() } returns mockRouteOptions - } + val waypoints = listOf( + provideWaypoint( + Point.fromLngLat(-122.4192, 37.7627), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4192, 37.7627), + ), + provideWaypoint( + Point.fromLngLat(-122.4182, 37.7651), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4183, 37.7653), + ), + provideWaypoint( + Point.fromLngLat(-122.4145, 37.7653), + Waypoint.REGULAR, + "", + Point.fromLngLat(-122.4146, 37.7655), + ), + ) val mockRouteLegProgress = mockk(relaxed = true) { every { legIndex } returns 0 } val mockRouteProgress = mockk(relaxed = true) { every { currentLegProgress } returns mockRouteLegProgress - every { route } returns mockRoute + every { navigationRoute.internalWaypoints() } returns waypoints } val mockAction = BuildingAction.QueryBuildingOnFinalDestination(mockRouteProgress) val result = BuildingProcessor.queryBuildingOnFinalDestination(mockAction) - assertEquals(result.point, mockFinalForWaypointTarget) + assertEquals(waypoints.last().target!!, result.point) } @Test fun `map query on final destination arrival without waypoint targets`() { - val mockOriginForCoordinates = Point.fromLngLat(-122.4192, 37.7627) - val mockWaypointForCoordinates = Point.fromLngLat(-122.4182, 37.7651) - val mockFinalForCoordinates = Point.fromLngLat(-122.4145, 37.7653) - val mockRouteOptions = mockk(relaxed = true) { - every { coordinatesList() } returns listOf( - mockOriginForCoordinates, - mockWaypointForCoordinates, - mockFinalForCoordinates - ) - every { waypointTargetsList() } returns null - } - val mockRoute = mockk(relaxed = true) { - every { routeOptions() } returns mockRouteOptions - } + val waypoints = listOf( + provideWaypoint( + Point.fromLngLat(-122.4192, 37.7627), + Waypoint.REGULAR, + "", + null, + ), + provideWaypoint( + Point.fromLngLat(-122.4182, 37.7651), + Waypoint.REGULAR, + "", + null, + ), + provideWaypoint( + Point.fromLngLat(-122.4145, 37.7653), + Waypoint.REGULAR, + "", + null, + ), + ) val mockRouteLegProgress = mockk(relaxed = true) { every { legIndex } returns 0 } val mockRouteProgress = mockk(relaxed = true) { every { currentLegProgress } returns mockRouteLegProgress - every { route } returns mockRoute + every { navigationRoute.internalWaypoints() } returns waypoints } val mockAction = BuildingAction.QueryBuildingOnFinalDestination(mockRouteProgress) val result = BuildingProcessor.queryBuildingOnFinalDestination(mockAction) - assertEquals(result.point, mockFinalForCoordinates) + assertEquals(waypoints.last().location, result.point) } @Test @@ -240,4 +253,16 @@ class BuildingProcessorTest { assertTrue(optionsSlot.captured.layerIds!!.contains("building")) assertTrue(optionsSlot.captured.layerIds!!.contains("building-extrusion")) } + + private fun provideWaypoint( + location: Point, + @Waypoint.Type type: Int, + name: String, + target: Point?, + ): Waypoint = WaypointFactory.provideWaypoint( + location, + name, + target, + type + ) } diff --git a/libtesting-navigation-base/src/main/java/com/mapbox/navigation/testing/factories/NavigationRouteFactory.kt b/libtesting-navigation-base/src/main/java/com/mapbox/navigation/testing/factories/NavigationRouteFactory.kt index 9060d1d6fee..5af3ef43d61 100644 --- a/libtesting-navigation-base/src/main/java/com/mapbox/navigation/testing/factories/NavigationRouteFactory.kt +++ b/libtesting-navigation-base/src/main/java/com/mapbox/navigation/testing/factories/NavigationRouteFactory.kt @@ -11,10 +11,12 @@ import com.mapbox.navigation.base.route.NavigationRoute import com.mapbox.navigation.base.route.RouterOrigin import com.mapbox.navigator.RouteInfo import com.mapbox.navigator.RouteInterface +import com.mapbox.navigator.Waypoint fun createNavigationRoute( directionsRoute: DirectionsRoute = createDirectionsRoute(), - routeInfo: RouteInfo = RouteInfo(emptyList()) + routeInfo: RouteInfo = RouteInfo(emptyList()), + waypoints: List = createWaypoints() ): NavigationRoute { return com.mapbox.navigation.base.internal.route.createNavigationRoute( directionsRoute, @@ -33,7 +35,8 @@ fun createNavigationRoute( routerOrigin = routerOrigin.mapToNativeRouteOrigin(), requestURI = directionsRoute.routeOptions()!!.toUrl("pk.*test_token*") .toString(), - routeInfo = routeInfo + routeInfo = routeInfo, + waypoints = waypoints, ), ) ) @@ -87,3 +90,4 @@ fun createRouteInterfacesFromDirectionRequestResponse( ) } } + diff --git a/libtesting-thirdparty/src/main/java/com/mapbox/navigation/testing/factories/NativeFactories.kt b/libtesting-thirdparty/src/main/java/com/mapbox/navigation/testing/factories/NativeFactories.kt index c26ff3da6f3..e0f19caa437 100644 --- a/libtesting-thirdparty/src/main/java/com/mapbox/navigation/testing/factories/NativeFactories.kt +++ b/libtesting-thirdparty/src/main/java/com/mapbox/navigation/testing/factories/NativeFactories.kt @@ -17,6 +17,7 @@ import com.mapbox.navigator.SpeedLimit import com.mapbox.navigator.UpcomingRouteAlert import com.mapbox.navigator.VoiceInstruction import com.mapbox.navigator.Waypoint +import com.mapbox.navigator.WaypointType import java.time.Instant import java.util.Date @@ -137,6 +138,25 @@ fun createFixedLocation( true ) +fun createWaypoints(): List = listOf( + Waypoint( + "name_1", + Point.fromLngLat(1.1, 1.2), + null, + null, + null, + WaypointType.REGULAR, + ), + Waypoint( + "name_2", + Point.fromLngLat(2.1, 2.2), + null, + null, + null, + WaypointType.REGULAR, + ), +) + // Add default parameters if you define properties fun createMapMatcherOutput() = MapMatcherOutput(emptyList(), false)