Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

NAVAND-1545: introduce long routes optimised mode (#7564) #7589

Merged
merged 1 commit into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/unreleased/features/7564.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Introduced `NavigationOptions#longRoutesOptimisationOptions` which changes the Nav SDK behavior so that it's able to handle heavy routes objects. See documentation of `OptimiseNavigationForLongRoutes` to learn more about it.
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ class EvOfflineTest : BaseCoreNoCleanUpTest() {
profile = DirectionsCriteria.PROFILE_DRIVING_TRAFFIC,
jsonResponse = offlineRoutes.first().directionsResponse
.toBuilder()
.routes(offlineRoutes.map { it.directionsRoute })
.uuid("route-similar-to-$primaryRouteResponseUUID")
.build()
.toJson(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
@file:OptIn(ExperimentalPreviewMapboxNavigationAPI::class)

package com.mapbox.navigation.instrumentation_tests.core

import android.location.Location
import com.mapbox.api.directions.v5.DirectionsCriteria
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
import com.mapbox.navigation.base.options.LongRoutesOptimisationOptions
import com.mapbox.navigation.core.directions.session.RoutesExtra
import com.mapbox.navigation.core.internal.extensions.flowRouteProgress
import com.mapbox.navigation.instrumentation_tests.R
import com.mapbox.navigation.instrumentation_tests.utils.DelayedResponseModifier
import com.mapbox.navigation.instrumentation_tests.utils.http.MockDirectionsRequestHandler
import com.mapbox.navigation.instrumentation_tests.utils.location.stayOnPosition
import com.mapbox.navigation.instrumentation_tests.utils.readRawFileText
import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider
import com.mapbox.navigation.instrumentation_tests.utils.withMapboxNavigation
import com.mapbox.navigation.testing.ui.BaseCoreNoCleanUpTest
import com.mapbox.navigation.testing.ui.utils.coroutines.getSuccessfulResultOrThrowException
import com.mapbox.navigation.testing.ui.utils.coroutines.requestRoutes
import com.mapbox.navigation.testing.ui.utils.coroutines.routeProgressUpdates
import com.mapbox.navigation.testing.ui.utils.coroutines.routesPreviewUpdates
import com.mapbox.navigation.testing.ui.utils.coroutines.routesUpdates
import com.mapbox.navigation.testing.ui.utils.coroutines.sdkTest
import com.mapbox.navigation.testing.ui.utils.coroutines.setNavigationRoutesAsync
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import org.junit.Assert.assertEquals
import org.junit.Test

// old devices parse long routes very slowly
private const val EXTENDED_TIMEOUT_FOR_SLOW_PARSING = 200_000L

class LongRoutesSanityTest : BaseCoreNoCleanUpTest() {

override fun setupMockLocation(): Location {
Expand All @@ -27,6 +45,145 @@ class LongRoutesSanityTest : BaseCoreNoCleanUpTest() {

@Test
fun requestAndSetLongRouteWithoutOnboardTiles() = sdkTest(timeout = 120_000) {
val routeOptions = longRouteOptions()
val handler = MockDirectionsRequestHandler(
profile = DirectionsCriteria.PROFILE_DRIVING_TRAFFIC,
lazyJsonResponse = { readRawFileText(context, R.raw.long_route_7k) },
expectedCoordinates = routeOptions.coordinatesList()
).apply {
// It takes time for Direction API to calculate a long route
jsonResponseModifier = DelayedResponseModifier(12_000)
}
mockWebServerRule.requestHandlers.add(handler)
withMapboxNavigation { navigation ->
val routes = navigation
.requestRoutes(routeOptions)
.getSuccessfulResultOrThrowException().routes
navigation.setNavigationRoutesAsync(routes)
}
}

@Test
fun requestNewRoutesWhileLongRoutesAreSet() = sdkTest(EXTENDED_TIMEOUT_FOR_SLOW_PARSING) {
val longRoutesOptions = setupLongRoutes()
val shortRoutesOptions = setupShortRoutes()
withMapboxNavigation(
longRoutesOptimisationOptions =
LongRoutesOptimisationOptions.OptimiseNavigationForLongRoutes(
responseToParseSizeBytes = 5.megabytesInBytes()
)
) { navigation ->
navigation.setNavigationRoutesAsync(
navigation
.requestRoutes(longRoutesOptions)
.getSuccessfulResultOrThrowException().routes,
1
)
assertEquals(2, navigation.getNavigationRoutes().size)

val shortRoutes = navigation.requestRoutes(shortRoutesOptions)
assertEquals(2, navigation.getNavigationRoutes().size)

val secondWaypoint = longRoutesOptions.coordinatesList()[1]
stayOnPosition(
longitude = secondWaypoint.longitude(),
latitude = secondWaypoint.latitude(),
bearing = 190.0f
) {
navigation.startTripSession()
navigation.routeProgressUpdates().first()

val newRoutes = navigation.requestRoutes(longRoutesOptions)
.getSuccessfulResultOrThrowException()
assertEquals(1, navigation.getNavigationRoutes().size)
assertEquals(1, navigation.currentLegIndex())
}
}
}

@Test
fun requestNewRoutesWhileLongRoutesArePreviewed() = sdkTest(EXTENDED_TIMEOUT_FOR_SLOW_PARSING) {
val longRoutesOptions = setupLongRoutes()
val shortRoutesOptions = setupShortRoutes()
withMapboxNavigation(
longRoutesOptimisationOptions =
LongRoutesOptimisationOptions.OptimiseNavigationForLongRoutes(
responseToParseSizeBytes = 5.megabytesInBytes()
)
) { navigation ->
navigation.setRoutesPreview(
navigation
.requestRoutes(longRoutesOptions)
.getSuccessfulResultOrThrowException().routes
)

assertEquals(
2,
navigation.routesPreviewUpdates()
.map { it.routesPreview }
.filterNotNull()
.first()
.routesList.size
)

val shortRoutes = navigation.requestRoutes(shortRoutesOptions)
assertEquals(2, navigation.getRoutesPreview()?.routesList?.size)

val newLongRoutes = navigation.requestRoutes(longRoutesOptions)
.getSuccessfulResultOrThrowException()
assertEquals(1, navigation.getRoutesPreview()?.routesList?.size)
}
}

@Test
fun rerouteOnLongRoute() = sdkTest(EXTENDED_TIMEOUT_FOR_SLOW_PARSING) {
val longRoutesOptions = setupLongRoutes()
val longRoutesRerouteOptions = setupLongRoutesReroute()
withMapboxNavigation(
longRoutesOptimisationOptions =
LongRoutesOptimisationOptions.OptimiseNavigationForLongRoutes(
responseToParseSizeBytes = 5.megabytesInBytes()
)
) { navigation ->
navigation.setNavigationRoutesAsync(
navigation
.requestRoutes(longRoutesOptions)
.getSuccessfulResultOrThrowException().routes
)
assertEquals(2, navigation.getNavigationRoutes().size)

val rerouteOrigin = longRoutesRerouteOptions.coordinatesList().first()
stayOnPosition(
latitude = rerouteOrigin.latitude(),
longitude = rerouteOrigin.longitude(),
bearing = 190.0f
) {
navigation.startTripSession()
navigation.flowRouteProgress().first()
val droppingAlternatives = navigation.routesUpdates().first {
it.reason == RoutesExtra.ROUTES_UPDATE_REASON_ALTERNATIVE
}
assertEquals(1, droppingAlternatives.navigationRoutes.size)
val reroute = navigation.routesUpdates().first {
it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REROUTE
}
assertEquals(2, reroute.navigationRoutes.size)
}
}
}

private fun setupLongRoutes(): RouteOptions {
val routeOptions = longRouteOptions()
val handler = MockDirectionsRequestHandler(
profile = routeOptions.profile(),
lazyJsonResponse = { readRawFileText(context, R.raw.long_route_7k) },
expectedCoordinates = routeOptions.coordinatesList()
)
mockWebServerRule.requestHandlers.add(handler)
return routeOptions
}

private fun longRouteOptions(): RouteOptions {
val routeOptions = RouteOptions.builder()
.baseUrl(mockWebServerRule.baseUrl) // comment to use real Directions API
.applyDefaultNavigationOptions()
Expand All @@ -41,20 +198,45 @@ class LongRoutesSanityTest : BaseCoreNoCleanUpTest() {
.alternatives(true)
.enableRefresh(true)
.build()
return routeOptions
}

private fun setupLongRoutesReroute(): RouteOptions {
val routeOptions = longRouteRerouteOptions()
val handler = MockDirectionsRequestHandler(
profile = DirectionsCriteria.PROFILE_DRIVING_TRAFFIC,
lazyJsonResponse = { readRawFileText(context, R.raw.long_route_7k) },
profile = routeOptions.profile(),
lazyJsonResponse = { readRawFileText(context, R.raw.long_route_7k_reroute) },
expectedCoordinates = routeOptions.coordinatesList()
).apply {
// It takes time for Direction API to calculate a long route
jsonResponseModifier = DelayedResponseModifier(12_000)
}
)
mockWebServerRule.requestHandlers.add(handler)
withMapboxNavigation { navigation ->
val routes = navigation
.requestRoutes(routeOptions)
.getSuccessfulResultOrThrowException().routes
navigation.setNavigationRoutesAsync(routes)
}
return routeOptions
}

private fun longRouteRerouteOptions(): RouteOptions {
val routeOptions = RouteOptions.builder()
.baseUrl(mockWebServerRule.baseUrl) // comment to use real Directions API
.applyDefaultNavigationOptions()
.coordinates(
"4.899038,52.373577" +
";5.359980783143584,43.280050656855906" +
";11.571179644010442,48.145540095763664" +
";13.394784408007155,52.51274942160785" +
";-9.143239539655042,38.70880224984026" +
";9.21595128801522,45.4694220491258"
)
.alternatives(true)
.enableRefresh(true)
.build()
return routeOptions
}

private fun setupShortRoutes(): RouteOptions {
val shortRoute = RoutesProvider.dc_very_short(context)
mockWebServerRule.requestHandlers.addAll(shortRoute.mockRequestHandlers)
return RouteOptions.builder().applyDefaultNavigationOptions()
.coordinatesList(shortRoute.routeWaypoints)
.build()
}
}

private fun Int.megabytesInBytes() = this * 1024 * 1024
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import com.mapbox.navigation.core.history.model.HistoryEventGetStatus
import com.mapbox.navigation.core.history.model.HistoryEventPushHistoryRecord
import com.mapbox.navigation.core.history.model.HistoryEventSetRoute
import com.mapbox.navigation.core.history.model.HistoryEventUpdateLocation
import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity
import com.mapbox.navigation.instrumentation_tests.utils.idling.RouteProgressStateIdlingResource
import com.mapbox.navigation.instrumentation_tests.utils.location.MockLocationReplayerRule
import com.mapbox.navigation.instrumentation_tests.utils.routes.MockRoute
import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider
import com.mapbox.navigation.testing.ui.BaseTest
import com.mapbox.navigation.testing.ui.BaseCoreNoCleanUpTest
import com.mapbox.navigation.testing.ui.utils.MapboxNavigationRule
import com.mapbox.navigation.testing.ui.utils.coroutines.sdkTest
import com.mapbox.navigation.testing.ui.utils.coroutines.stopRecording
Expand All @@ -45,7 +44,7 @@ import org.junit.Test
import java.io.File
import java.io.InputStream

class MapboxHistoryTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.java) {
class MapboxHistoryTest : BaseCoreNoCleanUpTest() {

@get:Rule
val mapboxNavigationRule = MapboxNavigationRule()
Expand All @@ -64,7 +63,7 @@ class MapboxHistoryTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.j

@Before
fun createTestDirectory() {
testDirectory = File(activity.filesDir, "mapbox_history_test_directory")
testDirectory = File(context.filesDir, "mapbox_history_test_directory")
.also { it.mkdirs() }
}

Expand All @@ -79,8 +78,8 @@ class MapboxHistoryTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.j

runOnMainSync {
mapboxNavigation = MapboxNavigationProvider.create(
NavigationOptions.Builder(activity)
.accessToken(getMapboxAccessTokenFromResources(activity))
NavigationOptions.Builder(context)
.accessToken(getMapboxAccessTokenFromResources(context))
.historyRecorderOptions(
HistoryRecorderOptions.Builder()
.build()
Expand All @@ -97,13 +96,13 @@ class MapboxHistoryTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.j
@Test
fun verify_history_files_are_recorded_and_readable() {
// prepare
val mockRoute = RoutesProvider.dc_very_short(activity)
val mockRoute = RoutesProvider.dc_very_short(context)
mockWebServerRule.requestHandlers.addAll(mockRoute.mockRequestHandlers)
routeCompleteIdlingResource.register()

val routeOptions = RouteOptions.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(activity)
.applyLanguageAndVoiceUnitOptions(context)
.baseUrl(mockWebServerRule.baseUrl)
.coordinatesList(mockRoute.routeWaypoints).build()

Expand Down Expand Up @@ -154,13 +153,13 @@ class MapboxHistoryTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.j
@Test
fun verify_history_files_are_recorded_and_readable_with_silent_waypoints() {
// prepare
val mockRoute = RoutesProvider.dc_very_short_two_legs_with_silent_waypoint(activity)
val mockRoute = RoutesProvider.dc_very_short_two_legs_with_silent_waypoint(context)
mockWebServerRule.requestHandlers.addAll(mockRoute.mockRequestHandlers)
routeCompleteIdlingResource.register()

val routeOptions = RouteOptions.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(activity)
.applyLanguageAndVoiceUnitOptions(context)
.baseUrl(mockWebServerRule.baseUrl)
.coordinatesList(mockRoute.routeWaypoints)
.waypointIndicesList(listOf(0, 2))
Expand Down Expand Up @@ -353,7 +352,7 @@ class MapboxHistoryTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.j
private fun historyReaderFromAssetFile(
name: String
): MapboxHistoryReader {
val inputStream: InputStream = activity.assets.open(name)
val inputStream: InputStream = context.assets.open(name)
val outputFile = File(testDirectory, name)
outputFile.outputStream().use { fileOut ->
inputStream.copyTo(fileOut)
Expand Down
Loading