Skip to content

Commit

Permalink
NAVAND-1545: introduce long routes optimised mode (#7564)
Browse files Browse the repository at this point in the history
  • Loading branch information
VysotskiVadim authored and dzinad committed Oct 27, 2023
1 parent 32c15d1 commit a73af29
Show file tree
Hide file tree
Showing 45 changed files with 45,620 additions and 335 deletions.
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

0 comments on commit a73af29

Please sign in to comment.