From 4a276cccc0c1c16680029115a5d908a0885f78e8 Mon Sep 17 00:00:00 2001 From: Dzina Dybouskaya Date: Wed, 18 Jan 2023 20:28:23 +0300 Subject: [PATCH 1/5] NAVAND-1073: fix alternative routes refresh --- changelog/unreleased/bugfixes/dd.md | 1 + .../core/RouteRefreshTest.kt | 109 +- .../utils/coroutines/TestUtils.kt | 5 +- ...nse_route_refresh_truncated_first_leg.json | 14 + .../route_response_single_route_multileg.json | 3904 +++++++++++++++++ ...nse_single_route_multileg_alternative.json | 1103 +++++ ..._route_multileg_alternative_refreshed.json | 160 + ...ponse_single_route_multileg_refreshed.json | 209 + .../navigation/core/MapboxNavigation.kt | 1 + .../core/NavigationComponentProvider.kt | 4 +- ...kt => PrimaryRouteProgressDataProvider.kt} | 2 +- .../core/RoutesProgressDataProvider.kt | 43 + .../AlternativeMetadataProvider.kt | 8 + .../AlternativeRouteProgressDataProvider.kt | 37 + .../RouteAlternativesController.kt | 4 +- .../routerefresh/RouteRefreshController.kt | 41 +- .../RouteRefreshControllerProvider.kt | 9 +- .../core/MapboxNavigationBaseTest.kt | 3 +- ...> PrimaryRouteProgressDataProviderTest.kt} | 4 +- .../core/RoutesProgressDataProviderTest.kt | 137 + .../navigation/core/RoutesProgressDataTest.kt | 40 + ...lternativeRouteProgressDataProviderTest.kt | 213 + .../RouteRefreshControllerTest.kt | 199 +- 23 files changed, 6208 insertions(+), 42 deletions(-) create mode 100644 changelog/unreleased/bugfixes/dd.md create mode 100644 instrumentation-tests/src/main/res/raw/route_response_single_route_multileg.json create mode 100644 instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative.json create mode 100644 instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative_refreshed.json create mode 100644 instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_refreshed.json rename libnavigation-core/src/main/java/com/mapbox/navigation/core/{RouteProgressDataProvider.kt => PrimaryRouteProgressDataProvider.kt} (95%) create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/RoutesProgressDataProvider.kt create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeMetadataProvider.kt create mode 100644 libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt rename libnavigation-core/src/test/java/com/mapbox/navigation/core/{RouteProgressDataProviderTest.kt => PrimaryRouteProgressDataProviderTest.kt} (98%) create mode 100644 libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt create mode 100644 libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataTest.kt create mode 100644 libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt diff --git a/changelog/unreleased/bugfixes/dd.md b/changelog/unreleased/bugfixes/dd.md new file mode 100644 index 00000000000..e39d093f963 --- /dev/null +++ b/changelog/unreleased/bugfixes/dd.md @@ -0,0 +1 @@ +- Fixed an issue where alternative routes might have had an incorrect or incomplete portion of the route refreshed or occasionally fail to refresh. \ No newline at end of file diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt index 8dfee215eee..bfef0a4d24f 100644 --- a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt @@ -1,9 +1,11 @@ package com.mapbox.navigation.instrumentation_tests.core +import android.content.Context import android.location.Location import androidx.annotation.IntegerRes import com.mapbox.api.directions.v5.DirectionsCriteria import com.mapbox.api.directions.v5.models.Closure +import com.mapbox.api.directions.v5.models.DirectionsResponse import com.mapbox.api.directions.v5.models.Incident import com.mapbox.api.directions.v5.models.RouteOptions import com.mapbox.geojson.Point @@ -26,6 +28,7 @@ import com.mapbox.navigation.instrumentation_tests.utils.coroutines.roadObjectsO import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routeProgressUpdates import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routesUpdates import com.mapbox.navigation.instrumentation_tests.utils.coroutines.sdkTest +import com.mapbox.navigation.instrumentation_tests.utils.coroutines.setNavigationRoutesAndWaitForAlternativesUpdate import com.mapbox.navigation.instrumentation_tests.utils.coroutines.setNavigationRoutesAndWaitForUpdate import com.mapbox.navigation.instrumentation_tests.utils.http.FailByRequestMockRequestHandler import com.mapbox.navigation.instrumentation_tests.utils.http.MockDirectionsRefreshHandler @@ -34,6 +37,8 @@ import com.mapbox.navigation.instrumentation_tests.utils.http.MockRoutingTileEnd import com.mapbox.navigation.instrumentation_tests.utils.idling.IdlingPolicyTimeoutRule import com.mapbox.navigation.instrumentation_tests.utils.location.MockLocationReplayerRule import com.mapbox.navigation.instrumentation_tests.utils.readRawFileText +import com.mapbox.navigation.instrumentation_tests.utils.routes.MockRoute +import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider.toNavigationRoutes import com.mapbox.navigation.testing.ui.BaseTest import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources import com.mapbox.navigation.testing.ui.utils.runOnMainSync @@ -80,6 +85,11 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja Point.fromLngLat(-75.525486, 38.772959), Point.fromLngLat(-74.698765, 39.822911) ) + private val multilegCoordinates = listOf( + Point.fromLngLat(38.577764, -121.496066), + Point.fromLngLat(38.576795, -121.480256), + Point.fromLngLat(38.582195, -121.468458) + ) private lateinit var failByRequestRouteRefreshResponse: FailByRequestMockRequestHandler @@ -424,7 +434,7 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja R.raw.route_response_route_refresh, R.raw.route_response_route_refresh_truncated_first_leg, "route_response_route_refresh", - acceptedGeometryIndex = 5 + acceptedGeometryIndex = 3 ) val routeOptions = generateRouteOptions(twoCoordinates) val requestedRoutes = mapboxNavigation.requestRoutes(routeOptions) @@ -433,10 +443,10 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja mapboxNavigation.setNavigationRoutes(requestedRoutes) mapboxNavigation.startTripSession() - // corresponds to currentRouteGeometryIndex = 5 - stayOnPosition(38.57622, -121.496731) + // corresponds to currentRouteGeometryIndex = 3 + stayOnPosition(38.577344, -121.496248) mapboxNavigation.routeProgressUpdates() - .filter { it.currentRouteGeometryIndex == 5 } + .filter { it.currentRouteGeometryIndex == 3 } .first() val refreshedRoutes = mapboxNavigation.routesUpdates() .filter { @@ -446,10 +456,76 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja .navigationRoutes assertEquals(224.224, requestedRoutes[0].getSumOfDurationAnnotationsFromLeg(0), 0.0001) - assertEquals(169.582, refreshedRoutes[0].getSumOfDurationAnnotationsFromLeg(0), 0.0001) + assertEquals(172.175, refreshedRoutes[0].getSumOfDurationAnnotationsFromLeg(0), 0.0001) assertEquals(227.918, requestedRoutes[1].getSumOfDurationAnnotationsFromLeg(0), 0.0001) - assertEquals(234.024, refreshedRoutes[1].getSumOfDurationAnnotationsFromLeg(0), 0.0001) + assertEquals(235.641, refreshedRoutes[1].getSumOfDurationAnnotationsFromLeg(0), 0.0001) + } + + @Test + fun route_refresh_updates_annotations_for_new_alternative_with_different_number_of_legs() = + sdkTest { + setupMockRequestHandlers( + multilegCoordinates, + R.raw.route_response_single_route_multileg, + R.raw.route_response_single_route_multileg_refreshed, + "route_response_single_route_multileg", + acceptedGeometryIndex = 70 + ) + mockWebServerRule.requestHandlers.add( + FailByRequestMockRequestHandler( + MockDirectionsRefreshHandler( + "route_response_single_route_multileg_alternative", + readRawFileText( + activity, + R.raw.route_response_single_route_multileg_alternative_refreshed + ), + acceptedGeometryIndex = 11 + ) + ) + ) + val routeOptions = generateRouteOptions(multilegCoordinates) + val requestedRoutes = mapboxNavigation.requestRoutes(routeOptions) + .getSuccessfulResultOrThrowException() + .routes + // alternative which was requested on the second leg of the original route, + // so the alternative has only one leg while the original route has two + val alternativeRoute = alternativeForMultileg(activity).toNavigationRoutes().first() + + mapboxNavigation.setNavigationRoutes(requestedRoutes, initialLegIndex = 1) + mapboxNavigation.startTripSession() + + // corresponds to currentRouteGeometryIndex = 70 for primary route and 11 for alternative route + stayOnPosition(38.581798, -121.476146) + mapboxNavigation.routeProgressUpdates() + .filter { + it.currentRouteGeometryIndex == 70 + } + .first() + + mapboxNavigation.setNavigationRoutesAndWaitForAlternativesUpdate( + requestedRoutes + alternativeRoute, + initialLegIndex = 1 + ) + + val refreshedRoutes = mapboxNavigation.routesUpdates() + .filter { + it.reason == ROUTES_UPDATE_REASON_REFRESH + } + .first() + .navigationRoutes + + assertEquals( + requestedRoutes[0].getSumOfDurationAnnotationsFromLeg(0), + refreshedRoutes[0].getSumOfDurationAnnotationsFromLeg(0), + 0.0001 + ) + + assertEquals(201.673, requestedRoutes[0].getSumOfDurationAnnotationsFromLeg(1), 0.0001) + assertEquals(202.881, refreshedRoutes[0].getSumOfDurationAnnotationsFromLeg(1), 0.0001) + + assertEquals(194.3, alternativeRoute.getSumOfDurationAnnotationsFromLeg(0), 0.0001) + assertEquals(187.126, refreshedRoutes[1].getSumOfDurationAnnotationsFromLeg(0), 0.0001) } @Test @@ -714,6 +790,27 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja ) ) } + + private fun alternativeForMultileg(context: Context): MockRoute { + val jsonResponse = readRawFileText(context, R.raw.route_response_single_route_multileg_alternative) + val coordinates = listOf( + Point.fromLngLat(38.577427, -121.478077), + Point.fromLngLat(38.582195, -121.468458) + ) + return MockRoute( + jsonResponse, + DirectionsResponse.fromJson(jsonResponse), + listOf( + MockDirectionsRequestHandler( + profile = DirectionsCriteria.PROFILE_DRIVING_TRAFFIC, + jsonResponse = jsonResponse, + expectedCoordinates = coordinates + ) + ), + coordinates, + emptyList() + ) + } } private fun NavigationRoute.getSumOfDurationAnnotationsFromLeg(legIndex: Int): Double = diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/coroutines/TestUtils.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/coroutines/TestUtils.kt index 1a25c7bcb45..16938130c4e 100644 --- a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/coroutines/TestUtils.kt +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/utils/coroutines/TestUtils.kt @@ -93,10 +93,11 @@ suspend fun MapboxNavigation.setNavigationRoutesAndAwaitError( } suspend fun MapboxNavigation.setNavigationRoutesAndWaitForAlternativesUpdate( - routes: List + routes: List, + initialLegIndex: Int = 0, ) = withTimeout(MAX_TIME_TO_UPDATE_ROUTE) { - setNavigationRoutes(routes) + setNavigationRoutes(routes, initialLegIndex) waitForAlternativeRoute() } diff --git a/instrumentation-tests/src/main/res/raw/route_response_route_refresh_truncated_first_leg.json b/instrumentation-tests/src/main/res/raw/route_response_route_refresh_truncated_first_leg.json index b7432b7d9da..9afe6cc115b 100644 --- a/instrumentation-tests/src/main/res/raw/route_response_route_refresh_truncated_first_leg.json +++ b/instrumentation-tests/src/main/res/raw/route_response_route_refresh_truncated_first_leg.json @@ -111,6 +111,12 @@ { "unknown": true }, + { + "unknown": true + }, + { + "unknown": true + }, { "speed": 40, "unit": "km/h" @@ -198,6 +204,8 @@ "unknown", "unknown", "unknown", + "unknown", + "unknown", "low", "low", "low", @@ -213,6 +221,8 @@ "low" ], "speed": [ + 6.2, + 6.3, 6.1, 5.8, 5.8, @@ -249,6 +259,8 @@ 9.7 ], "distance": [ + 10.2, + 9.7, 24.5, 4, 4.6, @@ -285,6 +297,8 @@ 3.7 ], "duration": [ + 1.802, + 3.002, 4.003, 0.687, 0.787, diff --git a/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg.json b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg.json new file mode 100644 index 00000000000..acafd61f835 --- /dev/null +++ b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg.json @@ -0,0 +1,3904 @@ +{ + "routes": [ + { + "country_crossed": false, + "weight_typical": 653.859, + "duration_typical": 474.677, + "weight_name": "auto", + "weight": 670.441, + "duration": 488.214, + "distance": 3742.875, + "legs": [ + { + "via_waypoints": [], + "admins": [ + { + "iso_3166_1_alpha3": "USA", + "iso_3166_1": "US" + } + ], + "annotation": { + "maxspeed": [ + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 0, + 0, + null, + null, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + null, + null, + null, + null, + null, + null + ], + "speed": [ + 4.4, + 4.4, + 10.3, + 10.3, + 8.9, + 8.9, + 6.1, + 8.3, + 8.3, + 8.3, + 8.3, + 12.5, + 12.5, + 12.5, + 13.3, + 13.3, + 13.3, + 11.9, + 11.9, + 11.9, + 6.7, + 6.7, + 6.7, + 9.4, + 4.2, + 10.6, + 12.5, + 13.6, + 13.6, + 13.6, + 8.1, + 8.1, + 9.2, + 9.2, + 8.1, + 10.8, + 10.8, + 8.6, + 13.9, + 13.9, + 13.3, + 13.3, + 13.9, + 13.9, + 12.8, + 7.2, + 8.1, + 10.6, + 8.3, + 8.3, + 10.8 + ], + "distance": [ + 27.1, + 8.9, + 13.3, + 11.4, + 10.7, + 109.9, + 121.8, + 6.9, + 4, + 100.3, + 10.6, + 11.2, + 101.6, + 10.2, + 10.1, + 126.8, + 11, + 9.3, + 102, + 11.1, + 11.5, + 50.8, + 59.8, + 63.9, + 58.7, + 64.3, + 48.2, + 18.8, + 3.7, + 14.6, + 52.6, + 61.3, + 19.6, + 45.6, + 64.3, + 26.4, + 38.4, + 64.4, + 48.6, + 31.5, + 9.7, + 30.5, + 50.8, + 13.8, + 61.4, + 122.1, + 34.1, + 29.9, + 7.8, + 8, + 9.2 + ], + "duration": [ + 6.108, + 2.01, + 1.292, + 1.106, + 1.205, + 12.365, + 19.931, + 0.828, + 0.477, + 12.036, + 1.275, + 0.9, + 8.125, + 0.817, + 0.76, + 9.508, + 0.828, + 0.778, + 8.542, + 0.931, + 1.722, + 7.613, + 8.964, + 6.766, + 14.081, + 6.093, + 3.856, + 1.383, + 0.275, + 1.073, + 6.535, + 7.605, + 2.134, + 4.98, + 7.984, + 2.44, + 3.548, + 7.484, + 3.499, + 2.27, + 0.724, + 2.291, + 3.66, + 0.993, + 4.804, + 16.907, + 4.231, + 2.832, + 0.935, + 0.963, + 0.85 + ] + }, + "weight_typical": 350.993, + "duration_typical": 259.293, + "weight": 350.993, + "duration": 259.293, + "steps": [ + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Drive south on 9th Street. Then, in 600 feet, Turn left onto N Street.", + "announcement": "Drive south on 9th Street. Then, in 600 feet, Turn left onto N Street.", + "distanceAlongGeometry": 182.131 + }, + { + "ssmlAnnouncement": "Turn left onto N Street, U.S. 40 Historic.", + "announcement": "Turn left onto N Street, U.S. 40 Historic.", + "distanceAlongGeometry": 63.333 + } + ], + "intersections": [ + { + "entry": [ + true + ], + "bearings": [ + 199 + ], + "duration": 8.129, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 9.959, + "geometry_index": 0, + "location": [ + -121.496066, + 38.577764 + ] + }, + { + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -121.496199, + 38.577457 + ], + "geometry_index": 2, + "admin_index": 0, + "weight": 4.98, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.007, + "turn_weight": 2, + "duration": 4.44, + "bearings": [ + 19, + 132, + 199, + 289 + ], + "out": 2, + "in": 0, + "entry": [ + false, + false, + true, + true + ] + }, + { + "bearings": [ + 18, + 84, + 199, + 289 + ], + "entry": [ + false, + true, + true, + false + ], + "in": 0, + "turn_weight": 4, + "turn_duration": 2.007, + "traffic_signal": true, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 2, + "geometry_index": 4, + "location": [ + -121.496289, + 38.577247 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "N Street" + }, + { + "type": "delimiter", + "text": "/" + }, + { + "mapbox_shield": { + "text_color": "black", + "name": "default", + "display_ref": "US 40 Historic", + "base_url": "https://api.mapbox.com/styles/v1" + }, + "type": "icon", + "text": "US 40 Historic" + } + ], + "type": "turn", + "modifier": "left", + "text": "N Street / US 40 Historic" + }, + "distanceAlongGeometry": 182.131 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "depart", + "instruction": "Drive south on 9th Street.", + "bearing_after": 199, + "bearing_before": 0, + "location": [ + -121.496066, + 38.577764 + ] + }, + "speedLimitSign": "mutcd", + "name": "9th Street", + "weight_typical": 35.614, + "duration_typical": 28.189, + "duration": 28.189, + "distance": 182.131, + "driving_side": "right", + "weight": 35.614, + "mode": "driving", + "geometry": "gerqhAb_pvfFlMfEvC`A`F`B`EpAtDnAny@bX" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Continue for a half mile.", + "announcement": "Continue for a half mile.", + "distanceAlongGeometry": 868.666 + }, + { + "ssmlAnnouncement": "In a quarter mile, Turn left onto 16th Street.", + "announcement": "In a quarter mile, Turn left onto 16th Street.", + "distanceAlongGeometry": 402.336 + }, + { + "ssmlAnnouncement": "Turn left onto 16th Street, U.S. 40 Historic.", + "announcement": "Turn left onto 16th Street, U.S. 40 Historic.", + "distanceAlongGeometry": 84.444 + } + ], + "intersections": [ + { + "entry": [ + false, + true, + true, + false + ], + "in": 0, + "bearings": [ + 19, + 109, + 199, + 289 + ], + "duration": 25.358, + "turn_weight": 18.75, + "turn_duration": 5.395, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 43.205, + "geometry_index": 6, + "location": [ + -121.496731, + 38.57622 + ] + }, + { + "entry": [ + true, + true, + false, + false + ], + "in": 3, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "duration": 0.859, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 2.029, + "geometry_index": 7, + "location": [ + -121.495405, + 38.57587 + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 18, + 110, + 199, + 289 + ], + "duration": 0.487, + "turn_weight": 1, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 1.588, + "geometry_index": 8, + "location": [ + -121.49533, + 38.57585 + ] + }, + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 18, + 109, + 290 + ], + "duration": 12.021, + "turn_weight": 0.5, + "turn_duration": 0.021, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 15.2, + "geometry_index": 9, + "location": [ + -121.495287, + 38.575838 + ] + }, + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 20, + 110, + 289 + ], + "duration": 1.327, + "turn_weight": 0.5, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 2.117, + "geometry_index": 10, + "location": [ + -121.494198, + 38.575543 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 108, + 199, + 290 + ], + "duration": 0.901, + "turn_weight": 0.5, + "turn_duration": 0.021, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.578, + "geometry_index": 11, + "location": [ + -121.494083, + 38.575511 + ] + }, + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 18, + 109, + 288 + ], + "duration": 8.179, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 10.496, + "geometry_index": 12, + "location": [ + -121.49396, + 38.57548 + ] + }, + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 20, + 112, + 289 + ], + "duration": 0.809, + "turn_weight": 0.5, + "turn_duration": 0.009, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 1.48, + "geometry_index": 13, + "location": [ + -121.492854, + 38.575189 + ] + }, + { + "entry": [ + true, + true, + true, + false + ], + "in": 3, + "bearings": [ + 18, + 106, + 199, + 292 + ], + "duration": 0.776, + "turn_weight": 1, + "turn_duration": 0.026, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 1.919, + "geometry_index": 14, + "location": [ + -121.492745, + 38.575155 + ] + }, + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 17, + 109, + 286 + ], + "duration": 9.533, + "turn_weight": 0.5, + "turn_duration": 0.008, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 12.168, + "geometry_index": 15, + "location": [ + -121.492633, + 38.57513 + ] + }, + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 23, + 109, + 289 + ], + "duration": 0.844, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 1.511, + "geometry_index": 16, + "location": [ + -121.491254, + 38.574763 + ] + }, + { + "entry": [ + false, + true, + true, + false + ], + "in": 3, + "bearings": [ + 13, + 109, + 199, + 289 + ], + "duration": 0.773, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 1.923, + "geometry_index": 17, + "location": [ + -121.491134, + 38.574731 + ] + }, + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 18, + 109, + 289 + ], + "duration": 8.559, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 10.961, + "geometry_index": 18, + "location": [ + -121.491033, + 38.574704 + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 20, + 109, + 204, + 289 + ], + "duration": 0.94, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 2.128, + "geometry_index": 19, + "location": [ + -121.489923, + 38.574409 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 109, + 199, + 289 + ], + "duration": 1.669, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 2.521, + "geometry_index": 20, + "location": [ + -121.489802, + 38.574377 + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 21, + 109, + 201, + 289 + ], + "duration": 7.669, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 10.371, + "geometry_index": 21, + "location": [ + -121.489677, + 38.574344 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 109, + 289 + ], + "duration": 9, + "turn_weight": 0.5, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 11.525, + "geometry_index": 22, + "location": [ + -121.489125, + 38.574197 + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "right" + ], + "valid": false, + "active": false + } + ], + "location": [ + -121.488475, + 38.574024 + ], + "geometry_index": 23, + "admin_index": 0, + "weight": 10.301, + "is_urban": true, + "mapbox_streets_v8": { + "class": "secondary" + }, + "turn_duration": 0.019, + "turn_weight": 2, + "duration": 6.796, + "bearings": [ + 18, + 108, + 199, + 289, + 338 + ], + "out": 1, + "in": 3, + "entry": [ + false, + true, + true, + false, + false + ] + }, + { + "bearings": [ + 110, + 201, + 288 + ], + "entry": [ + true, + true, + false + ], + "in": 2, + "turn_weight": 0.5, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 24, + "location": [ + -121.487777, + 38.573846 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "16th Street" + }, + { + "type": "delimiter", + "text": "/" + }, + { + "mapbox_shield": { + "text_color": "black", + "name": "default", + "display_ref": "US 40 Historic", + "base_url": "https://api.mapbox.com/styles/v1" + }, + "type": "icon", + "text": "US 40 Historic" + } + ], + "type": "turn", + "modifier": "left", + "text": "16th Street / US 40 Historic" + }, + "distanceAlongGeometry": 882 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn left onto N Street/US 40 Historic.", + "modifier": "left", + "bearing_after": 109, + "bearing_before": 199, + "location": [ + -121.496731, + 38.57622 + ] + }, + "speedLimitSign": "mutcd", + "name": "N Street", + "weight_typical": 160.868, + "duration_typical": 110.668, + "duration": 110.668, + "distance": 882, + "driving_side": "right", + "weight": 160.868, + "mode": "driving", + "ref": "US 40 Historic", + "geometry": "wdoqhAthqvfFzT{qAf@uCVuAlQacA~@eF|@uFdQcdAbAyEp@_F|UeuA~@oFt@iElQkdA~@qF`AyFdHoa@xIsg@bJsj@`Juf@" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In a quarter mile, Turn right onto J Street.", + "announcement": "In a quarter mile, Turn right onto J Street.", + "distanceAlongGeometry": 508.666 + }, + { + "ssmlAnnouncement": "Turn right onto J Street.", + "announcement": "Turn right onto J Street.", + "distanceAlongGeometry": 66.667 + } + ], + "intersections": [ + { + "entry": [ + true, + true, + false, + false + ], + "in": 3, + "bearings": [ + 19, + 108, + 199, + 290 + ], + "duration": 11.685, + "turn_weight": 5, + "turn_duration": 5.622, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 12.427, + "geometry_index": 25, + "location": [ + -121.487142, + 38.573669 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 3.859, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 5.704, + "geometry_index": 26, + "location": [ + -121.486904, + 38.574216 + ] + }, + { + "entry": [ + true, + true, + false, + false + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 290 + ], + "duration": 1.709, + "turn_weight": 1.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 3.57, + "geometry_index": 27, + "location": [ + -121.486726, + 38.574626 + ] + }, + { + "entry": [ + true, + false, + false + ], + "in": 1, + "bearings": [ + 19, + 199, + 289 + ], + "duration": 1.121, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.85, + "geometry_index": 29, + "location": [ + -121.486643, + 38.574818 + ] + }, + { + "entry": [ + true, + false, + false, + true + ], + "in": 2, + "bearings": [ + 18, + 108, + 199, + 289 + ], + "duration": 6.598, + "turn_weight": 1.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 9.56, + "geometry_index": 30, + "location": [ + -121.486588, + 38.574942 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 18, + 198 + ], + "duration": 7.572, + "turn_weight": 0.5, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 9.776, + "geometry_index": 31, + "location": [ + -121.486402, + 38.575392 + ] + }, + { + "lanes": [ + { + "indications": [ + "left" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "left", + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -121.486187, + 38.575916 + ], + "geometry_index": 32, + "admin_index": 0, + "weight": 4.673, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.019, + "turn_weight": 2, + "duration": 4.201, + "bearings": [ + 18, + 108, + 198, + 289 + ], + "out": 0, + "in": 2, + "entry": [ + true, + false, + false, + true + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 18, + 108, + 198 + ], + "duration": 5.037, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 6.647, + "geometry_index": 33, + "location": [ + -121.486117, + 38.576083 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 110, + 198, + 289 + ], + "duration": 7.964, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 10.732, + "geometry_index": 34, + "location": [ + -121.485951, + 38.576472 + ] + }, + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -121.485713, + 38.577019 + ], + "geometry_index": 35, + "admin_index": 0, + "weight": 3.94, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.019, + "turn_weight": 1, + "duration": 4.419, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "out": 0, + "in": 2, + "entry": [ + true, + true, + false, + true + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 20, + 108, + 199 + ], + "duration": 3.515, + "turn_weight": 0.5, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 4.797, + "geometry_index": 36, + "location": [ + -121.485616, + 38.577244 + ] + }, + { + "bearings": [ + 19, + 108, + 200, + 289 + ], + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "turn_weight": 1, + "turn_duration": 0.021, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 37, + "location": [ + -121.485467, + 38.577569 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "J Street" + } + ], + "type": "turn", + "modifier": "right", + "text": "J Street" + }, + "distanceAlongGeometry": 522 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn left onto 16th Street/US 40 Historic.", + "modifier": "left", + "bearing_after": 19, + "bearing_before": 110, + "location": [ + -121.487142, + 38.573669 + ] + }, + "speedLimitSign": "mutcd", + "name": "16th Street", + "weight_typical": 83.781, + "duration_typical": 65.134, + "duration": 65.134, + "distance": 522, + "driving_side": "right", + "weight": 83.781, + "mode": "driving", + "ref": "US 40 Historic", + "geometry": "iejqhAjq~ufFea@{MsXcJ_IkC_AYwFmBc[sJw_@mLmIkCiWkIea@{MaMaEiSiHia@uM" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In a quarter mile, Your destination will be on the right.", + "announcement": "In a quarter mile, Your destination will be on the right.", + "distanceAlongGeometry": 443.209 + }, + { + "ssmlAnnouncement": "Your destination is on the right.", + "announcement": "Your destination is on the right.", + "distanceAlongGeometry": 66.667 + } + ], + "intersections": [ + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -121.485232, + 38.578118 + ], + "geometry_index": 38, + "admin_index": 0, + "weight": 11.322, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 4.005, + "turn_weight": 7, + "duration": 7.533, + "bearings": [ + 21, + 109, + 199, + 289 + ], + "out": 1, + "in": 2, + "entry": [ + true, + true, + false, + false + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 108, + 199, + 289 + ], + "duration": 2.323, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 3.322, + "geometry_index": 39, + "location": [ + -121.484704, + 38.577976 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 109, + 199, + 288 + ], + "duration": 0.769, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.419, + "geometry_index": 40, + "location": [ + -121.48436, + 38.577887 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 23, + 109, + 289 + ], + "duration": 2.344, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 3.348, + "geometry_index": 41, + "location": [ + -121.484255, + 38.577859 + ] + }, + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -121.483923, + 38.57777 + ], + "geometry_index": 42, + "admin_index": 0, + "weight": 5.498, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.019, + "turn_weight": 1, + "duration": 5.691, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "out": 1, + "in": 3, + "entry": [ + true, + true, + true, + false + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 109, + 199, + 289 + ], + "duration": 1.027, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.735, + "geometry_index": 43, + "location": [ + -121.48337, + 38.577623 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 20, + 109, + 289 + ], + "duration": 4.793, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 6.348, + "geometry_index": 44, + "location": [ + -121.48322, + 38.577583 + ] + }, + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -121.482552, + 38.577406 + ], + "geometry_index": 45, + "admin_index": 0, + "weight": 21.693, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.019, + "turn_weight": 1, + "duration": 18.911, + "bearings": [ + 18, + 109, + 210, + 289 + ], + "out": 1, + "in": 3, + "entry": [ + true, + true, + true, + false + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "right" + ], + "valid": false, + "active": false + } + ], + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -121.481224, + 38.577052 + ], + "geometry_index": 46, + "admin_index": 0, + "weight": 7.17, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.019, + "turn_weight": 2, + "duration": 6.24, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "out": 1, + "in": 3, + "entry": [ + false, + true, + true, + false + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 109, + 199, + 289 + ], + "duration": 2.861, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 3.982, + "geometry_index": 47, + "location": [ + -121.480853, + 38.576954 + ] + }, + { + "entry": [ + true, + false, + false + ], + "in": 2, + "bearings": [ + 108, + 199, + 289 + ], + "duration": 0.979, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.676, + "geometry_index": 48, + "location": [ + -121.480528, + 38.576867 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 109, + 288 + ], + "duration": 0.96, + "turn_weight": 0.5, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.676, + "geometry_index": 49, + "railway_crossing": true, + "location": [ + -121.480443, + 38.576845 + ] + }, + { + "bearings": [ + 108, + 199, + 289 + ], + "entry": [ + true, + true, + false + ], + "in": 2, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 50, + "location": [ + -121.480356, + 38.576821 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "Your destination will be on the right" + } + ], + "type": "arrive", + "modifier": "right", + "text": "Your destination will be on the right" + }, + "distanceAlongGeometry": 459.209 + }, + { + "primary": { + "components": [ + { + "type": "text", + "text": "Your destination is on the right" + } + ], + "type": "arrive", + "modifier": "right", + "text": "Your destination is on the right" + }, + "distanceAlongGeometry": 66.667 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto J Street.", + "modifier": "right", + "bearing_after": 109, + "bearing_before": 19, + "location": [ + -121.485232, + 38.578118 + ] + }, + "speedLimitSign": "mutcd", + "name": "J Street", + "weight_typical": 70.73, + "duration_typical": 55.302, + "duration": 55.302, + "distance": 459.209, + "driving_side": "right", + "weight": 70.73, + "mode": "driving", + "geometry": "k{rqhA~yzufFzG_`@pDoTv@qEpDwSdHqa@nAkH`Jwh@bU_rAbEeVlDiSj@iDn@mDr@gE" + }, + { + "voiceInstructions": [], + "intersections": [ + { + "bearings": [ + 288 + ], + "entry": [ + true + ], + "in": 0, + "admin_index": 0, + "geometry_index": 51, + "location": [ + -121.480256, + 38.576795 + ] + } + ], + "bannerInstructions": [], + "speedLimitUnit": "mph", + "maneuver": { + "type": "arrive", + "instruction": "Your destination is on the right.", + "modifier": "right", + "bearing_after": 0, + "bearing_before": 108, + "location": [ + -121.480256, + 38.576795 + ] + }, + "speedLimitSign": "mutcd", + "name": "J Street", + "weight_typical": 0, + "duration_typical": 0, + "duration": 0, + "distance": 0, + "driving_side": "right", + "weight": 0, + "mode": "driving", + "geometry": "uhpqhA~bqufF??" + } + ], + "distance": 2045.339, + "summary": "N Street, 16th Street" + }, + { + "via_waypoints": [], + "admins": [ + { + "iso_3166_1_alpha3": "USA", + "iso_3166_1": "US" + } + ], + "closures": [ + { + "geometry_index_start": 312, + "geometry_index_end": 313 + } + ], + "incidents": [ + { + "id": "9457146989091490", + "type": "construction", + "creation_time": "2022-09-21T07:40:42Z", + "start_time": "2022-08-08T08:15:59Z", + "end_time": "2023-09-01T03:59:00Z", + "iso_3166_1_alpha2": "US", + "iso_3166_1_alpha3": "USA", + "description": "NJ-70 E/B: intermittent lane closures between NJ-38 K and New Jersey Tpke", + "long_description": "Intermittent lane closures due to long-term construction on NJ-70 Eastbound between NJ-38 K and New Jersey Tpke.", + "impact": "minor", + "sub_type": "CONSTRUCTION", + "alertc_codes": [ + 701, + 802 + ], + "lanes_blocked": [], + "length": 8744, + "congestion": { + "value": 101 + }, + "geometry_index_start": 400, + "geometry_index_end": 405, + "affected_road_names": [ + "NJ 70/John Davison Rockefeller Memorial Highway", + "NJ 70" + ] + } + ], + "annotation": { + "maxspeed": [ + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 48, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + 0, + 50, + 38, + 38, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "speed": [ + 10.8, + 10.8, + 11.4, + 11.4, + 6.9, + 5.3, + 4.2, + 4.2, + 9.7, + 9.7, + 8.6, + 8.6, + 8.6, + 10.6, + 7.8, + 10.8, + 5.8, + 6.9, + 6.9, + 11.1, + 14.4, + 13.3, + 13.3, + 13.1, + 11.9, + 6.9, + 6.9, + 6.9, + 6.9, + 5, + 5, + 5, + 5, + 3.9, + 3.9, + 3.9, + 3.9, + 4.4, + 8.6, + 11.1, + 7.5, + 8.6 + ], + "distance": [ + 21.1, + 10.3, + 10.9, + 15.9, + 98.6, + 63.3, + 54.1, + 10.4, + 10.3, + 52.8, + 4.5, + 25.1, + 36.5, + 63.5, + 64.4, + 62.6, + 66, + 64.1, + 65, + 113.6, + 10.2, + 116.9, + 121.3, + 124, + 116, + 2.7, + 2.6, + 2.8, + 2.7, + 2.6, + 2.7, + 2.7, + 2.6, + 2.8, + 2.7, + 2.8, + 2.8, + 57.1, + 64.8, + 63.7, + 65.6, + 10.7 + ], + "duration": [ + 1.944, + 0.951, + 0.954, + 1.396, + 14.195, + 11.986, + 12.987, + 2.488, + 1.064, + 5.426, + 0.524, + 2.916, + 4.24, + 6.015, + 8.276, + 5.78, + 11.316, + 9.235, + 9.356, + 10.22, + 0.708, + 8.771, + 9.1, + 9.494, + 9.715, + 0.395, + 0.376, + 0.398, + 0.387, + 0.517, + 0.534, + 0.537, + 0.516, + 0.728, + 0.7, + 0.713, + 0.71, + 12.855, + 7.53, + 5.731, + 8.741, + 1.248 + ] + }, + "weight_typical": 302.866, + "duration_typical": 215.384, + "weight": 319.448, + "duration": 228.921, + "steps": [ + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Drive east on J Street. Then, in 500 feet, Turn left onto 21st Street.", + "announcement": "Drive east on J Street. Then, in 500 feet, Turn left onto 21st Street.", + "distanceAlongGeometry": 157.791 + }, + { + "ssmlAnnouncement": "Turn left onto 21st Street.", + "announcement": "Turn left onto 21st Street.", + "distanceAlongGeometry": 80 + } + ], + "intersections": [ + { + "entry": [ + true + ], + "bearings": [ + 108 + ], + "duration": 2.935, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 3.595, + "geometry_index": 0, + "location": [ + -121.480256, + 38.576795 + ] + }, + { + "entry": [ + true, + true, + true, + false + ], + "in": 3, + "bearings": [ + 15, + 109, + 190, + 289 + ], + "duration": 2.39, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 3.904, + "geometry_index": 2, + "location": [ + -121.479914, + 38.576705 + ] + }, + { + "bearings": [ + 109, + 200, + 289 + ], + "entry": [ + true, + true, + false + ], + "in": 2, + "turn_weight": 0.5, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 4, + "location": [ + -121.479623, + 38.576627 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "21st Street" + } + ], + "type": "turn", + "modifier": "left", + "text": "21st Street" + }, + "distanceAlongGeometry": 157.791 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "depart", + "instruction": "Drive east on J Street.", + "bearing_after": 108, + "bearing_before": 0, + "location": [ + -121.480256, + 38.576795 + ] + }, + "speedLimitSign": "mutcd", + "name": "J Street", + "weight_typical": 25.463, + "duration_typical": 19.6, + "duration": 19.6, + "distance": 157.791, + "driving_side": "right", + "weight": 25.463, + "mode": "driving", + "geometry": "uhpqhA~bqufFvBkMz@_F~@kFzAyIzP_bA" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Continue for a half mile.", + "announcement": "Continue for a half mile.", + "distanceAlongGeometry": 628.666 + }, + { + "ssmlAnnouncement": "In a quarter mile, Turn right onto E Street.", + "announcement": "In a quarter mile, Turn right onto E Street.", + "distanceAlongGeometry": 402.336 + }, + { + "ssmlAnnouncement": "Turn right onto E Street.", + "announcement": "Turn right onto E Street.", + "distanceAlongGeometry": 50 + } + ], + "intersections": [ + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -121.478551, + 38.576341 + ], + "geometry_index": 5, + "admin_index": 0, + "weight": 24.623, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 7.51, + "turn_weight": 10, + "duration": 19.447, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "out": 0, + "in": 3, + "entry": [ + true, + true, + false, + false + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 12.979, + "turn_weight": 1, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 16.876, + "geometry_index": 6, + "location": [ + -121.478317, + 38.576879 + ] + }, + { + "lanes": [ + { + "indications": [ + "left" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "left", + "straight", + "right" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "location": [ + -121.478116, + 38.577339 + ], + "geometry_index": 7, + "admin_index": 0, + "weight": 3.94, + "is_urban": true, + "mapbox_streets_v8": { + "class": "secondary" + }, + "turn_duration": 0.019, + "turn_weight": 1, + "duration": 2.419, + "bearings": [ + 19, + 110, + 199, + 294 + ], + "out": 0, + "in": 2, + "entry": [ + true, + false, + false, + false + ] + }, + { + "lanes": [ + { + "indications": [ + "left" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "left", + "straight", + "right" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -121.478077, + 38.577427 + ], + "geometry_index": 8, + "admin_index": 0, + "weight": 2.76, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.021, + "turn_weight": 1.5, + "duration": 3.049, + "bearings": [ + 17, + 108, + 199, + 289 + ], + "out": 0, + "in": 2, + "entry": [ + true, + true, + false, + true + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 19, + 146, + 197, + 293 + ], + "duration": 5.459, + "turn_weight": 2, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 8.678, + "geometry_index": 9, + "location": [ + -121.478043, + 38.577516 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 20, + 108, + 199 + ], + "duration": 3.491, + "turn_weight": 1, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 5.268, + "geometry_index": 10, + "location": [ + -121.477849, + 38.577965 + ] + }, + { + "lanes": [ + { + "indications": [ + "left", + "straight", + "right" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 19, + 199 + ], + "duration": 4.297, + "turn_weight": 1, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 6.264, + "geometry_index": 12, + "location": [ + -121.477736, + 38.578216 + ] + }, + { + "lanes": [ + { + "indications": [ + "left", + "straight", + "right" + ], + "valid_indication": "straight", + "valid": true, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -121.477599, + 38.578526 + ], + "geometry_index": 13, + "admin_index": 0, + "weight": 9.311, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.007, + "turn_weight": 2, + "duration": 7.976, + "bearings": [ + 20, + 108, + 199, + 289 + ], + "out": 0, + "in": 2, + "entry": [ + true, + true, + false, + true + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 200, + 289 + ], + "duration": 8.249, + "turn_weight": 2, + "turn_duration": 0.021, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 11.874, + "geometry_index": 14, + "location": [ + -121.477346, + 38.579061 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 5.835, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 8.978, + "geometry_index": 15, + "location": [ + -121.47711, + 38.579609 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 11.333, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 15.577, + "geometry_index": 16, + "location": [ + -121.47688, + 38.580142 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 9.235, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 13.059, + "geometry_index": 17, + "location": [ + -121.476638, + 38.580704 + ] + }, + { + "bearings": [ + 20, + 108, + 199, + 290 + ], + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "turn_weight": 2, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 18, + "location": [ + -121.476403, + 38.58125 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "E Street" + } + ], + "type": "turn", + "modifier": "right", + "text": "E Street" + }, + "distanceAlongGeometry": 642 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn left onto 21st Street.", + "modifier": "left", + "bearing_after": 19, + "bearing_before": 109, + "location": [ + -121.478551, + 38.576341 + ] + }, + "speedLimitSign": "mutcd", + "name": "21st Street", + "weight_typical": 123.858, + "duration_typical": 89.6, + "duration": 103.137, + "distance": 642, + "driving_side": "right", + "weight": 140.44, + "mode": "driving", + "geometry": "iloqhAlxmufFs`@sMw[qKoDmAqDcAa[cKkAc@iL}DkRqGm`@yNga@wMi`@kMcb@cNca@uMga@aO" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In a quarter mile, Enter the roundabout and take the 3rd exit onto 26th Street.", + "announcement": "In a quarter mile, Enter the roundabout and take the 3rd exit onto 26th Street.", + "distanceAlongGeometry": 588.666 + }, + { + "ssmlAnnouncement": "Enter the roundabout and take the 3rd exit onto 26th Street.", + "announcement": "Enter the roundabout and take the 3rd exit onto 26th Street.", + "distanceAlongGeometry": 93.333 + } + ], + "intersections": [ + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 18, + 109, + 200, + 289 + ], + "duration": 12.168, + "turn_weight": 7, + "turn_duration": 1.908, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 19.055, + "geometry_index": 19, + "location": [ + -121.476146, + 38.581798 + ] + }, + { + "entry": [ + false, + true, + true, + false + ], + "in": 3, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "duration": 0.711, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 2.813, + "geometry_index": 20, + "location": [ + -121.474913, + 38.581464 + ] + }, + { + "entry": [ + true, + true, + false, + false + ], + "in": 3, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "duration": 8.794, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 12.311, + "geometry_index": 21, + "location": [ + -121.474802, + 38.581434 + ] + }, + { + "entry": [ + true, + true, + true, + false + ], + "in": 3, + "bearings": [ + 18, + 108, + 199, + 289 + ], + "duration": 9.094, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 12.663, + "geometry_index": 22, + "location": [ + -121.473534, + 38.581086 + ] + }, + { + "entry": [ + true, + true, + true, + false + ], + "in": 3, + "bearings": [ + 18, + 109, + 199, + 288 + ], + "duration": 9.517, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 13.16, + "geometry_index": 23, + "location": [ + -121.472211, + 38.580742 + ] + }, + { + "bearings": [ + 18, + 108, + 199, + 289 + ], + "entry": [ + true, + true, + true, + false + ], + "in": 3, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "geometry_index": 24, + "location": [ + -121.470864, + 38.58038 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "26th Street" + } + ], + "degrees": 269, + "driving_side": "right", + "type": "roundabout", + "modifier": "right", + "text": "26th Street" + }, + "distanceAlongGeometry": 602 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto E Street.", + "modifier": "right", + "bearing_after": 109, + "bearing_before": 20, + "location": [ + -121.476146, + 38.581798 + ] + }, + "speedLimitSign": "mutcd", + "name": "E Street", + "weight_typical": 73.414, + "duration_typical": 50.015, + "duration": 50.015, + "distance": 602, + "driving_side": "right", + "weight": 73.414, + "mode": "driving", + "geometry": "kazqhAbbiufFzSalAz@}EvTgnAnTuqArUesAhSenA" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Exit the roundabout onto 26th Street.", + "announcement": "Exit the roundabout onto 26th Street.", + "distanceAlongGeometry": 33 + } + ], + "intersections": [ + { + "entry": [ + false, + true, + false + ], + "in": 2, + "bearings": [ + 61, + 158, + 288 + ], + "duration": 1.766, + "turn_weight": 7, + "turn_duration": 0.182, + "mapbox_streets_v8": { + "class": "roundabout" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 8.861, + "geometry_index": 25, + "location": [ + -121.469597, + 38.580055 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 68, + 199, + 338 + ], + "duration": 7.595, + "turn_duration": 5.395, + "mapbox_streets_v8": { + "class": "roundabout" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 2.585, + "geometry_index": 29, + "location": [ + -121.469555, + 38.579974 + ] + }, + { + "bearings": [ + 108, + 248, + 336 + ], + "entry": [ + true, + false, + true + ], + "in": 1, + "turn_duration": 5.622, + "mapbox_streets_v8": { + "class": "roundabout" + }, + "is_urban": true, + "admin_index": 0, + "out": 2, + "geometry_index": 33, + "location": [ + -121.469453, + 38.580006 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "26th Street" + } + ], + "degrees": 269, + "driving_side": "right", + "type": "roundabout", + "modifier": "right", + "text": "26th Street" + }, + "distanceAlongGeometry": 33 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "roundabout", + "exit": 3, + "instruction": "Enter the roundabout and take the 3rd exit onto 26th Street.", + "modifier": "right", + "bearing_after": 158, + "bearing_before": 108, + "location": [ + -121.469597, + 38.580055 + ] + }, + "speedLimitSign": "mutcd", + "name": "26th Street", + "weight_typical": 14.77, + "duration_typical": 17.811, + "duration": 17.811, + "distance": 33, + "driving_side": "right", + "weight": 14.77, + "mode": "driving", + "geometry": "mtvqhAxh|tfFn@Ll@Kh@a@Xs@Hy@I{@Ys@e@a@q@Ko@Jg@d@Yt@" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In 800 feet, Turn right onto C Street.", + "announcement": "In 800 feet, Turn right onto C Street.", + "distanceAlongGeometry": 238.667 + }, + { + "ssmlAnnouncement": "Turn right onto C Street. Then Your destination will be on the left.", + "announcement": "Turn right onto C Street. Then Your destination will be on the left.", + "distanceAlongGeometry": 87.778 + } + ], + "intersections": [ + { + "entry": [ + true, + false, + true + ], + "in": 1, + "bearings": [ + 19, + 156, + 282 + ], + "duration": 12.94, + "turn_weight": 6.5, + "turn_duration": 0.115, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 21.569, + "geometry_index": 37, + "location": [ + -121.469499, + 38.580088 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 7.568, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 10.869, + "geometry_index": 38, + "location": [ + -121.469288, + 38.580574 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 5.779, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 8.768, + "geometry_index": 39, + "location": [ + -121.46905, + 38.581126 + ] + }, + { + "bearings": [ + 19, + 108, + 199, + 289 + ], + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "turn_weight": 5.6, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 40, + "location": [ + -121.468816, + 38.581668 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "C Street" + } + ], + "type": "turn", + "modifier": "right", + "text": "C Street" + }, + "distanceAlongGeometry": 252 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "exit roundabout", + "instruction": "Exit the roundabout onto 26th Street.", + "modifier": "slight right", + "bearing_after": 19, + "bearing_before": 336, + "location": [ + -121.469499, + 38.580088 + ] + }, + "speedLimitSign": "mutcd", + "name": "26th Street", + "weight_typical": 56.927, + "duration_typical": 35.105, + "duration": 35.105, + "distance": 252, + "driving_side": "right", + "weight": 56.927, + "mode": "driving", + "geometry": "ovvqhAtb|tfFk]eLoa@{M{`@sM{a@aN" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Your destination is on the left.", + "announcement": "Your destination is on the left.", + "distanceAlongGeometry": 10.745 + } + ], + "intersections": [ + { + "bearings": [ + 18, + 109, + 199, + 289 + ], + "entry": [ + true, + true, + false, + true + ], + "turn_weight": 7, + "turn_duration": 2.005, + "in": 2, + "stop_sign": true, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "geometry_index": 41, + "location": [ + -121.468575, + 38.582226 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "Your destination is on the left" + } + ], + "type": "arrive", + "modifier": "left", + "text": "Your destination is on the left" + }, + "distanceAlongGeometry": 10.745 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto C Street.", + "modifier": "right", + "bearing_after": 109, + "bearing_before": 19, + "location": [ + -121.468575, + 38.582226 + ] + }, + "speedLimitSign": "mutcd", + "name": "C Street", + "weight_typical": 8.435, + "duration_typical": 3.253, + "duration": 3.253, + "distance": 10.745, + "driving_side": "right", + "weight": 8.435, + "mode": "driving", + "geometry": "c|zqhA|hztfF|@iF" + }, + { + "voiceInstructions": [], + "intersections": [ + { + "bearings": [ + 289 + ], + "entry": [ + true + ], + "in": 0, + "admin_index": 0, + "geometry_index": 42, + "location": [ + -121.468458, + 38.582195 + ] + } + ], + "bannerInstructions": [], + "speedLimitUnit": "mph", + "maneuver": { + "type": "arrive", + "instruction": "Your destination is on the left.", + "modifier": "left", + "bearing_after": 0, + "bearing_before": 109, + "location": [ + -121.468458, + 38.582195 + ] + }, + "speedLimitSign": "mutcd", + "name": "C Street", + "weight_typical": 0, + "duration_typical": 0, + "duration": 0, + "distance": 0, + "driving_side": "right", + "weight": 0, + "mode": "driving", + "geometry": "ezzqhAraztfF??" + } + ], + "distance": 1697.536, + "summary": "J Street, 21st Street, E Street, 26th Street, C Street" + } + ], + "geometry": "gerqhAb_pvfFlMfEvC`A`F`B`EpAtDnAny@bXzT{qAf@uCVuAlQacA~@eF|@uFdQcdAbAyEp@_F|UeuA~@oFt@iElQkdA~@qF`AyFdHoa@xIsg@bJsj@`Juf@ea@{MsXcJ_IkC_AYwFmBc[sJw_@mLmIkCiWkIea@{MaMaEiSiHia@uMzG_`@pDoTv@qEpDwSdHqa@nAkH`Jwh@bU_rAbEeVlDiSj@iDn@mDr@gEvBkMz@_F~@kFzAyIzP_bAs`@sMw[qKoDmAqDcAa[cKkAc@iL}DkRqGm`@yNga@wMi`@kMcb@cNca@uMga@aOzSalAz@}EvTgnAnTuqArUesAhSenAn@Ll@Kh@a@Xs@Hy@I{@Ys@e@a@q@Ko@Jg@d@Yt@k]eLoa@{M{`@sM{a@aN|@iF", + "voiceLocale": "en-US" + } + ], + "waypoints": [ + { + "distance": 0.01, + "name": "9th Street", + "location": [ + -121.496066, + 38.577764 + ] + }, + { + "distance": 6.435, + "name": "J Street", + "location": [ + -121.480256, + 38.576795 + ] + }, + { + "distance": 6.499, + "name": "C Street", + "location": [ + -121.468458, + 38.582195 + ] + } + ], + "code": "Ok", + "uuid": "route_response_single_route_multileg" +} \ No newline at end of file diff --git a/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative.json b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative.json new file mode 100644 index 00000000000..af797976b7e --- /dev/null +++ b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative.json @@ -0,0 +1,1103 @@ +{ + "routes": [ + { + "country_crossed": false, + "weight_typical": 267.449, + "duration_typical": 194.301, + "weight_name": "auto", + "weight": 267.449, + "duration": 194.301, + "distance": 1391.623, + "legs": [ + { + "via_waypoints": [], + "admins": [ + { + "iso_3166_1_alpha3": "USA", + "iso_3166_1": "US" + } + ], + "annotation": { + "maxspeed": [ + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "speed": [ + 10.3, + 10, + 5.8, + 5.8, + 5.8, + 10.4, + 7, + 9.9, + 5.8, + 7.8, + 5.8, + 4.7, + 5, + 6.1, + 4.7, + 4.7, + 10.3, + 10.3, + 12.4, + 10, + 8.1, + 8.1, + 8.1, + 7.2, + 7.2, + 7.2, + 10, + 10, + 8.1 + ], + "distance": [ + 10.3, + 52.8, + 4.5, + 25.1, + 36.5, + 63.5, + 64.4, + 62.6, + 66, + 64.2, + 64.9, + 65.1, + 64.5, + 64.1, + 52.1, + 12.2, + 9.3, + 101.4, + 11.7, + 117.2, + 109.3, + 2.6, + 10.6, + 11.3, + 99.5, + 12.7, + 10.4, + 112.1, + 10.7 + ], + "duration": [ + 1, + 5.307, + 0.791, + 4.36, + 6.343, + 8.137, + 9.237, + 6.319, + 11.333, + 8.248, + 11.15, + 13.785, + 12.819, + 10.492, + 11.001, + 2.571, + 2.915, + 9.89, + 0.958, + 11.719, + 13.538, + 0.316, + 1.309, + 1.592, + 13.832, + 1.764, + 1.054, + 11.166, + 1.354 + ] + }, + "weight_typical": 267.449, + "duration_typical": 194.301, + "weight": 267.449, + "duration": 194.301, + "steps": [ + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Drive north on 21st Street for a half mile.", + "announcement": "Drive north on 21st Street for a half mile.", + "distanceAlongGeometry": 772.793 + }, + { + "ssmlAnnouncement": "In a quarter mile, Turn right onto C Street.", + "announcement": "In a quarter mile, Turn right onto C Street.", + "distanceAlongGeometry": 402.336 + }, + { + "ssmlAnnouncement": "Turn right onto C Street.", + "announcement": "Turn right onto C Street.", + "distanceAlongGeometry": 50 + } + ], + "intersections": [ + { + "entry": [ + true + ], + "bearings": [ + 17 + ], + "duration": 1, + "traffic_signal": true, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.225, + "geometry_index": 0, + "location": [ + -121.478077, + 38.577427 + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 19, + 146, + 197, + 293 + ], + "duration": 5.307, + "turn_weight": 2, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 8.493, + "geometry_index": 1, + "location": [ + -121.478043, + 38.577516 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 20, + 108, + 199 + ], + "duration": 5.15, + "turn_weight": 1, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 7.3, + "geometry_index": 2, + "location": [ + -121.477849, + 38.577965 + ] + }, + { + "lanes": [ + { + "indications": [ + "left", + "straight", + "right" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 19, + 199 + ], + "duration": 6.343, + "turn_weight": 1, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 8.77, + "geometry_index": 4, + "location": [ + -121.477736, + 38.578216 + ] + }, + { + "lanes": [ + { + "indications": [ + "left", + "straight", + "right" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -121.477599, + 38.578526 + ], + "geometry_index": 5, + "admin_index": 0, + "weight": 9.509, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 2.007, + "turn_weight": 2, + "duration": 8.137, + "bearings": [ + 20, + 108, + 199, + 289 + ], + "out": 0, + "in": 2, + "entry": [ + true, + true, + false, + true + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 200, + 289 + ], + "duration": 9.237, + "turn_weight": 2, + "turn_duration": 0.021, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 13.059, + "geometry_index": 6, + "location": [ + -121.477346, + 38.579061 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 6.319, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 9.56, + "geometry_index": 7, + "location": [ + -121.47711, + 38.579609 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 11.333, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 15.577, + "geometry_index": 8, + "location": [ + -121.47688, + 38.580142 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 199, + 289 + ], + "duration": 8.248, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 11.874, + "geometry_index": 9, + "location": [ + -121.476638, + 38.580704 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 20, + 108, + 199, + 290 + ], + "duration": 11.15, + "turn_weight": 2, + "turn_duration": 0.007, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 15.371, + "geometry_index": 10, + "location": [ + -121.476403, + 38.58125 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 108, + 200, + 289 + ], + "duration": 13.785, + "turn_weight": 2, + "turn_duration": 0.021, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 18.174, + "geometry_index": 11, + "location": [ + -121.476146, + 38.581798 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 110, + 199, + 289 + ], + "duration": 12.819, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 17.04, + "geometry_index": 12, + "location": [ + -121.475903, + 38.582351 + ] + }, + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 19, + 110, + 199, + 289 + ], + "duration": 10.492, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 14.305, + "geometry_index": 13, + "location": [ + -121.475663, + 38.582899 + ] + }, + { + "bearings": [ + 19, + 108, + 199, + 289 + ], + "entry": [ + true, + true, + false, + true + ], + "turn_weight": 2, + "turn_duration": 0.019, + "in": 2, + "stop_sign": true, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 14, + "location": [ + -121.475428, + 38.583445 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "C Street" + } + ], + "type": "turn", + "modifier": "right", + "text": "C Street" + }, + "distanceAlongGeometry": 772.793 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "depart", + "instruction": "Drive north on 21st Street.", + "bearing_after": 17, + "bearing_before": 0, + "location": [ + -121.478077, + 38.577427 + ] + }, + "speedLimitSign": "mutcd", + "name": "21st Street", + "weight_typical": 168.182, + "duration_typical": 122.893, + "duration": 122.893, + "distance": 772.793, + "driving_side": "right", + "weight": 168.182, + "mode": "driving", + "geometry": "epqqhAxzlufFqDcAa[cKkAc@iL}DkRqGm`@yNga@wMi`@kMcb@cNca@uMga@aOqa@eNga@_Nca@uMsZgKoEwA" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Continue for a half mile.", + "announcement": "Continue for a half mile.", + "distanceAlongGeometry": 605.497 + }, + { + "ssmlAnnouncement": "In a quarter mile, You will arrive at C Street.", + "announcement": "In a quarter mile, You will arrive at C Street.", + "distanceAlongGeometry": 402.336 + }, + { + "ssmlAnnouncement": "You have arrived at C Street.", + "announcement": "You have arrived at C Street.", + "distanceAlongGeometry": 55.556 + } + ], + "intersections": [ + { + "entry": [ + true, + true, + false, + true + ], + "in": 2, + "bearings": [ + 18, + 109, + 198, + 289 + ], + "duration": 12.805, + "turn_weight": 7, + "turn_duration": 2.005, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 19.69, + "geometry_index": 16, + "location": [ + -121.475188, + 38.583991 + ] + }, + { + "entry": [ + false, + true, + true, + false + ], + "in": 3, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "duration": 0.958, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 3.103, + "geometry_index": 18, + "location": [ + -121.473984, + 38.583669 + ] + }, + { + "entry": [ + true, + true, + false, + false + ], + "in": 3, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "duration": 11.719, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 15.747, + "geometry_index": 19, + "location": [ + -121.473857, + 38.583635 + ] + }, + { + "mapbox_streets_v8": { + "class": "street" + }, + "geometry_index": 20, + "admin_index": 0, + "weight": 19.417, + "is_urban": true, + "location": [ + -121.472583, + 38.583293 + ], + "stop_sign": true, + "out": 1, + "in": 3, + "turn_duration": 0.019, + "turn_weight": 2, + "duration": 15.164, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "entry": [ + true, + true, + true, + false + ] + }, + { + "mapbox_streets_v8": { + "class": "street" + }, + "geometry_index": 23, + "admin_index": 0, + "weight": 21.745, + "is_urban": true, + "location": [ + -121.471253, + 38.582934 + ], + "stop_sign": true, + "out": 1, + "in": 3, + "turn_duration": 0.019, + "turn_weight": 2, + "duration": 17.188, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "entry": [ + true, + true, + true, + false + ] + }, + { + "entry": [ + true, + true, + true, + false + ], + "in": 3, + "bearings": [ + 18, + 109, + 199, + 289 + ], + "duration": 12.219, + "turn_weight": 2, + "turn_duration": 0.019, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 16.03, + "geometry_index": 26, + "location": [ + -121.469908, + 38.582579 + ] + }, + { + "bearings": [ + 18, + 109, + 199, + 289 + ], + "entry": [ + true, + true, + true, + false + ], + "turn_weight": 2, + "turn_duration": 0.019, + "in": 3, + "stop_sign": true, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "geometry_index": 28, + "location": [ + -121.468575, + 38.582226 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "You will arrive at C Street" + } + ], + "type": "arrive", + "modifier": "straight", + "text": "You will arrive at C Street" + }, + "distanceAlongGeometry": 618.83 + }, + { + "primary": { + "components": [ + { + "type": "text", + "text": "You have arrived at C Street" + } + ], + "type": "arrive", + "modifier": "straight", + "text": "You have arrived at C Street" + }, + "distanceAlongGeometry": 55.556 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto C Street.", + "modifier": "right", + "bearing_after": 109, + "bearing_before": 18, + "location": [ + -121.475188, + 38.583991 + ] + }, + "speedLimitSign": "mutcd", + "name": "C Street", + "weight_typical": 99.267, + "duration_typical": 71.408, + "duration": 71.408, + "distance": 618.83, + "driving_side": "right", + "weight": 99.267, + "mode": "driving", + "geometry": "mj~qhAffgufFv@iEjQ}cAbA}FjTsnA`SeiALw@|@eF`AuFxPwbAhAsGz@aFdSgkA|@iF" + }, + { + "voiceInstructions": [], + "intersections": [ + { + "bearings": [ + 289 + ], + "entry": [ + true + ], + "in": 0, + "admin_index": 0, + "geometry_index": 29, + "location": [ + -121.468458, + 38.582195 + ] + } + ], + "bannerInstructions": [], + "speedLimitUnit": "mph", + "maneuver": { + "type": "arrive", + "instruction": "You have arrived at C Street.", + "bearing_after": 0, + "bearing_before": 109, + "location": [ + -121.468458, + 38.582195 + ] + }, + "speedLimitSign": "mutcd", + "name": "C Street", + "weight_typical": 0, + "duration_typical": 0, + "duration": 0, + "distance": 0, + "driving_side": "right", + "weight": 0, + "mode": "driving", + "geometry": "ezzqhAraztfF??" + } + ], + "distance": 1391.623, + "summary": "21st Street, C Street" + } + ], + "geometry": "epqqhAxzlufFqDcAa[cKkAc@iL}DkRqGm`@yNga@wMi`@kMcb@cNca@uMga@aOqa@eNga@_Nca@uMsZgKoEwAv@iEjQ}cAbA}FjTsnA`SeiALw@|@eF`AuFxPwbAhAsGz@aFdSgkA|@iF", + "voiceLocale": "en-US" + } + ], + "waypoints": [ + { + "distance": 0, + "name": "21st Street", + "location": [ + -121.478077, + 38.577427 + ] + }, + { + "distance": 0.03, + "name": "C Street", + "location": [ + -121.468458, + 38.582195 + ] + } + ], + "code": "Ok", + "uuid": "route_response_single_route_multileg_alternative" +} \ No newline at end of file diff --git a/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative_refreshed.json b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative_refreshed.json new file mode 100644 index 00000000000..acada8f3ff1 --- /dev/null +++ b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_alternative_refreshed.json @@ -0,0 +1,160 @@ +{ + "code": "Ok", + "route": { + "legs": [ + { + "annotation": { + "maxspeed": [ + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "unknown": true + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "speed": [ + 6.4, + 7, + 6.1, + 4.7, + 4.7, + 10.3, + 10.3, + 12.2, + 10, + 8.1, + 8.1, + 8.1, + 7.2, + 7.2, + 7.2, + 10, + 10, + 8.1 + ], + "distance": [ + 65.1, + 64.5, + 64.1, + 52.1, + 12.2, + 9.3, + 101.4, + 11.7, + 117.2, + 109.3, + 2.6, + 10.5, + 11.4, + 99.5, + 12.7, + 10.4, + 112, + 10.8 + ], + "duration": [ + 10.174, + 9.235, + 10.492, + 11.001, + 2.571, + 2.915, + 9.89, + 0.979, + 11.719, + 13.538, + 0.316, + 1.309, + 1.592, + 13.832, + 1.764, + 1.054, + 11.166, + 1.354 + ] + } + } + ] + } +} \ No newline at end of file diff --git a/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_refreshed.json b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_refreshed.json new file mode 100644 index 00000000000..adc4248af07 --- /dev/null +++ b/instrumentation-tests/src/main/res/raw/route_response_single_route_multileg_refreshed.json @@ -0,0 +1,209 @@ +{ + "code": "Ok", + "route": { + "legs": [ + {}, + { + "annotation": { + "maxspeed": [ + { + "unknown": true + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "speed": [ + 11.5, + 13.0, + 14.1, + 12.5, + 13.2, + 10.1, + 5.9, + 5.8, + 6.7, + 8.1, + 6, + 5, + 4.3, + 5, + 3.2, + 4.9, + 3.8, + 3.5, + 4.1, + 8.3, + 10.1, + 8.5, + 9.6 + ], + "distance": [ + 112.6, + 11.2, + 106.9, + 123.3, + 114, + 117, + 3.7, + 2.4, + 2.6, + 2.1, + 2.5, + 2.9, + 3.7, + 3.6, + 1.8, + 1.7, + 1.8, + 2.7, + 55.1, + 61.8, + 61.7, + 69.6, + 11.7 + ], + "duration": [ + 11.22, + 0.808, + 6.771, + 9.7, + 8.494, + 9.515, + 1.095, + 0.476, + 0.298, + 1.387, + 0.567, + 0.514, + 0.437, + 0.216, + 0.927, + 0.3, + 0.753, + 0.51, + 13.855, + 7.93, + 6.731, + 7.741, + 1.587 + ] + } + } + ] + } +} \ No newline at end of file diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt index 938d940ea2e..cd1f9775abd 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt @@ -555,6 +555,7 @@ class MapboxNavigation @VisibleForTesting internal constructor( navigationOptions.routeRefreshOptions, directionsSession, routeRefreshRequestDataProvider, + routeAlternativesController, evDynamicDataHolder ) diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt index 380f6de175e..416f9a7fdbf 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/NavigationComponentProvider.kt @@ -110,8 +110,8 @@ internal object NavigationComponentProvider { scope = scope ) - fun createRouteRefreshRequestDataProvider(): RouteProgressDataProvider = - RouteProgressDataProvider() + fun createRouteRefreshRequestDataProvider(): PrimaryRouteProgressDataProvider = + PrimaryRouteProgressDataProvider() fun createEVDynamicDataHolder(): EVDynamicDataHolder = EVDynamicDataHolder() diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/RouteProgressDataProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/PrimaryRouteProgressDataProvider.kt similarity index 95% rename from libnavigation-core/src/main/java/com/mapbox/navigation/core/RouteProgressDataProvider.kt rename to libnavigation-core/src/main/java/com/mapbox/navigation/core/PrimaryRouteProgressDataProvider.kt index 25a65e24b86..c2cca964130 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/RouteProgressDataProvider.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/PrimaryRouteProgressDataProvider.kt @@ -17,7 +17,7 @@ internal data class RouteProgressData( * Accumulates and provides route refresh model data from different sources. */ @MainThread -internal class RouteProgressDataProvider : RouteProgressObserver { +internal class PrimaryRouteProgressDataProvider : RouteProgressObserver { private val defaultRouteProgressData = RouteProgressData(0, 0, null) private var routeProgressData: RouteProgressData? = null diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/RoutesProgressDataProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/RoutesProgressDataProvider.kt new file mode 100644 index 00000000000..546beaf36ae --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/RoutesProgressDataProvider.kt @@ -0,0 +1,43 @@ +package com.mapbox.navigation.core + +import com.mapbox.navigation.base.route.NavigationRoute +import com.mapbox.navigation.core.routealternatives.AlternativeMetadataProvider +import com.mapbox.navigation.core.routealternatives.AlternativeRouteProgressDataProvider + +internal data class RoutesProgressData( + val primaryRoute: NavigationRoute, + val primaryRouteProgressData: RouteProgressData, + val alternativeRoutesProgressData: List> +) { + val allRoutesProgressData = listOf(primaryRoute to primaryRouteProgressData) + + alternativeRoutesProgressData +} + +internal class RoutesProgressDataProvider( + private val primaryRouteProgressDataProvider: PrimaryRouteProgressDataProvider, + private val alternativeMetadataProvider: AlternativeMetadataProvider, +) { + + suspend fun getRoutesProgressData( + routes: List + ): RoutesProgressData { + if (routes.isEmpty()) { + throw IllegalArgumentException("Routes must not be empty") + } + val primaryRouteProgressData = primaryRouteProgressDataProvider + .getRouteRefreshRequestDataOrWait() + val primary = routes.first() + val alternatives = routes.drop(1) + val alternativeRouteProgressDatas = alternatives.map { route -> + val alternativeMetadata = alternativeMetadataProvider.getMetadataFor(route) + val alternativeRouteProgressData = alternativeMetadata?.let { + AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + it + ) + } + route to alternativeRouteProgressData + } + return RoutesProgressData(primary, primaryRouteProgressData, alternativeRouteProgressDatas) + } +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeMetadataProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeMetadataProvider.kt new file mode 100644 index 00000000000..ce4720cbd5a --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeMetadataProvider.kt @@ -0,0 +1,8 @@ +package com.mapbox.navigation.core.routealternatives + +import com.mapbox.navigation.base.route.NavigationRoute + +internal interface AlternativeMetadataProvider { + + fun getMetadataFor(navigationRoute: NavigationRoute): AlternativeRouteMetadata? +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt new file mode 100644 index 00000000000..797b6c70ff4 --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt @@ -0,0 +1,37 @@ +package com.mapbox.navigation.core.routealternatives + +import com.mapbox.navigation.core.RouteProgressData + +internal object AlternativeRouteProgressDataProvider { + + fun getRouteProgressData( + primaryRouteProgressData: RouteProgressData, + alternativeMetadata: AlternativeRouteMetadata + ): RouteProgressData { + val legIndex: Int + val routeGeometryIndex: Int + val legGeometryIndex: Int? + val primaryFork = alternativeMetadata.forkIntersectionOfPrimaryRoute + val alternativeFork = alternativeMetadata.forkIntersectionOfAlternativeRoute + val isForkPointAheadOnPrimaryRoute = + primaryRouteProgressData.routeGeometryIndex < primaryFork.geometryIndexInRoute + if (isForkPointAheadOnPrimaryRoute) { + val legIndexDiff = primaryFork.legIndex - alternativeFork.legIndex + legIndex = primaryRouteProgressData.legIndex - legIndexDiff + val routeGeometryIndexDiff = + primaryFork.geometryIndexInRoute - alternativeFork.geometryIndexInRoute + routeGeometryIndex = + primaryRouteProgressData.routeGeometryIndex - routeGeometryIndexDiff + val legGeometryIndexDiff = + primaryFork.geometryIndexInLeg - alternativeFork.geometryIndexInLeg + legGeometryIndex = primaryRouteProgressData.legGeometryIndex?.let { + it - legGeometryIndexDiff + } + } else { + legIndex = alternativeFork.legIndex + routeGeometryIndex = alternativeFork.geometryIndexInRoute + legGeometryIndex = alternativeFork.geometryIndexInLeg + } + return RouteProgressData(legIndex, routeGeometryIndex, legGeometryIndex) + } +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/RouteAlternativesController.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/RouteAlternativesController.kt index 63380bf9f11..d45d0084a87 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/RouteAlternativesController.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/RouteAlternativesController.kt @@ -26,7 +26,7 @@ internal class RouteAlternativesController constructor( navigator: MapboxNativeNavigator, private val tripSession: TripSession, private val threadController: ThreadController -) { +) : AlternativeMetadataProvider { private var lastUpdateOrigin: RouterOrigin = RouterOrigin.Onboard @@ -142,7 +142,7 @@ internal class RouteAlternativesController constructor( observerProcessingJob?.cancel() } - fun getMetadataFor(navigationRoute: NavigationRoute): AlternativeRouteMetadata? { + override fun getMetadataFor(navigationRoute: NavigationRoute): AlternativeRouteMetadata? { return metadataMap[navigationRoute.id] } diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshController.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshController.kt index 87d2a70b3a3..048ab6627b9 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshController.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshController.kt @@ -1,5 +1,6 @@ package com.mapbox.navigation.core.routerefresh +import android.util.Log import androidx.annotation.VisibleForTesting import com.mapbox.api.directions.v5.models.DirectionsRoute import com.mapbox.api.directions.v5.models.RouteLeg @@ -12,7 +13,8 @@ import com.mapbox.navigation.base.route.NavigationRouterRefreshCallback import com.mapbox.navigation.base.route.NavigationRouterRefreshError import com.mapbox.navigation.base.route.RouteRefreshOptions import com.mapbox.navigation.core.RouteProgressData -import com.mapbox.navigation.core.RouteProgressDataProvider +import com.mapbox.navigation.core.RoutesProgressData +import com.mapbox.navigation.core.RoutesProgressDataProvider import com.mapbox.navigation.core.directions.session.RouteRefresh import com.mapbox.navigation.core.ev.EVRefreshDataProvider import com.mapbox.navigation.utils.internal.logE @@ -37,7 +39,7 @@ import kotlin.coroutines.resume internal class RouteRefreshController( private val routeRefreshOptions: RouteRefreshOptions, private val routeRefresh: RouteRefresh, - private val routeProgressDataProvider: RouteProgressDataProvider, + private val routesProgressDataProvider: RoutesProgressDataProvider, private val evRefreshDataProvider: EVRefreshDataProvider, private val routeDiffProvider: DirectionsRouteDiffProvider = DirectionsRouteDiffProvider(), private val localDateProvider: () -> Date, @@ -155,16 +157,16 @@ internal class RouteRefreshController( onNewState(RouteRefreshExtra.REFRESH_STATE_STARTED) } timeUntilNextAttempt = async { delay(routeRefreshOptions.intervalMillis) } - val routeProgressData = routeProgressDataProvider - .getRouteRefreshRequestDataOrWait() - val refreshedRoutes = refreshRoutesOrNull(routes, routeProgressData) + val routesProgressData = routesProgressDataProvider + .getRoutesProgressData(routes) + val refreshedRoutes = refreshRoutesOrNull(routesProgressData) if (refreshedRoutes.any { it != null }) { onNewState(RouteRefreshExtra.REFRESH_STATE_FINISHED_SUCCESS) return@coroutineScope RefreshedRouteInfo( refreshedRoutes.mapIndexed { index, navigationRoute -> navigationRoute ?: routes[index] }, - routeProgressData + routesProgressData.primaryRouteProgressData ) } } @@ -172,11 +174,12 @@ internal class RouteRefreshController( timeUntilNextAttempt.cancel() // otherwise current coroutine will wait for its child } onNewState(RouteRefreshExtra.REFRESH_STATE_FINISHED_FAILED) - val routeProgressData = routeProgressDataProvider - .getRouteRefreshRequestDataOrWait() + val routesProgressData = routesProgressDataProvider.getRoutesProgressData(routes) RefreshedRouteInfo( - routes.map { removeExpiringDataFromRoute(it, routeProgressData.legIndex) }, - routeProgressData + routesProgressData.allRoutesProgressData.map { + removeExpiringDataFromRoute(it.first, it.second?.legIndex ?: 0) + }, + routesProgressData.primaryRouteProgressData ) } @@ -260,14 +263,24 @@ internal class RouteRefreshController( } private suspend fun refreshRoutesOrNull( - routes: List, - routeProgressData: RouteProgressData, + routesData: RoutesProgressData, ): List { return coroutineScope { - routes.map { route -> + routesData.allRoutesProgressData.map { routeData -> async { withTimeoutOrNull(routeRefreshOptions.intervalMillis) { - refreshRouteOrNull(route, routeProgressData) + val routeProgressData = routeData.second + if (routeProgressData != null) { + refreshRouteOrNull(routeData.first, routeProgressData) + } else { + // No RouteProgressData - no refresh. Should not happen in production. + Log.w( + LOG_CATEGORY, + "Can't refresh route ${routeData.first.id}: " + + "no route progress data for it" + ) + null + } } } }.awaitAll() diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerProvider.kt index 366c723a0c1..4f7907911ee 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerProvider.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerProvider.kt @@ -1,10 +1,12 @@ package com.mapbox.navigation.core.routerefresh import com.mapbox.navigation.base.route.RouteRefreshOptions -import com.mapbox.navigation.core.RouteProgressDataProvider +import com.mapbox.navigation.core.PrimaryRouteProgressDataProvider +import com.mapbox.navigation.core.RoutesProgressDataProvider import com.mapbox.navigation.core.directions.session.DirectionsSession import com.mapbox.navigation.core.ev.EVDynamicDataHolder import com.mapbox.navigation.core.ev.EVRefreshDataProvider +import com.mapbox.navigation.core.routealternatives.AlternativeMetadataProvider import java.util.Date internal object RouteRefreshControllerProvider { @@ -12,12 +14,13 @@ internal object RouteRefreshControllerProvider { fun createRouteRefreshController( routeRefreshOptions: RouteRefreshOptions, directionsSession: DirectionsSession, - routeProgressDataProvider: RouteProgressDataProvider, + primaryRouteProgressDataProvider: PrimaryRouteProgressDataProvider, + alternativeMetadataProvider: AlternativeMetadataProvider, evDynamicDataHolder: EVDynamicDataHolder, ) = RouteRefreshController( routeRefreshOptions, directionsSession, - routeProgressDataProvider, + RoutesProgressDataProvider(primaryRouteProgressDataProvider, alternativeMetadataProvider), EVRefreshDataProvider(evDynamicDataHolder), DirectionsRouteDiffProvider(), { Date() }, diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt index 5680be04c4b..53229309a09 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/MapboxNavigationBaseTest.kt @@ -100,7 +100,7 @@ internal open class MapboxNavigationBaseTest { val historyRecordingStateHandler: HistoryRecordingStateHandler = mockk(relaxed = true) val developerMetadataAggregator: DeveloperMetadataAggregator = mockk(relaxUnitFun = true) val threadController = mockk(relaxed = true) - val routeProgressDataProvider = mockk(relaxed = true) + val routeProgressDataProvider = mockk(relaxed = true) val routesPreviewController = mockk(relaxed = true) val routesCacheClearer = mockk(relaxed = true) @@ -175,6 +175,7 @@ internal open class MapboxNavigationBaseTest { any(), any(), any(), + any(), any() ) } returns routeRefreshController diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/RouteProgressDataProviderTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/PrimaryRouteProgressDataProviderTest.kt similarity index 98% rename from libnavigation-core/src/test/java/com/mapbox/navigation/core/RouteProgressDataProviderTest.kt rename to libnavigation-core/src/test/java/com/mapbox/navigation/core/PrimaryRouteProgressDataProviderTest.kt index 8a76e55d3bb..ede9ab8d44f 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/RouteProgressDataProviderTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/PrimaryRouteProgressDataProviderTest.kt @@ -14,11 +14,11 @@ import org.junit.Rule import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class RouteProgressDataProviderTest { +class PrimaryRouteProgressDataProviderTest { @get:Rule val coroutineRule = MainCoroutineRule() - private val provider = RouteProgressDataProvider() + private val provider = PrimaryRouteProgressDataProvider() private val currentLegIndex = 9 private val routeGeometryIndex = 44 private val legGeometryIndex = 33 diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt new file mode 100644 index 00000000000..6ddd6815215 --- /dev/null +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt @@ -0,0 +1,137 @@ +package com.mapbox.navigation.core + +import com.mapbox.navigation.base.route.NavigationRoute +import com.mapbox.navigation.core.routealternatives.AlternativeMetadataProvider +import com.mapbox.navigation.core.routealternatives.AlternativeRouteMetadata +import com.mapbox.navigation.core.routealternatives.AlternativeRouteProgressDataProvider +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import kotlinx.coroutines.test.runBlockingTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class RoutesProgressDataProviderTest { + + private val primaryRouteProgressDataProvider = + mockk(relaxed = true) + private val alternativeMetadataProvider = mockk(relaxed = true) + private val sut = RoutesProgressDataProvider( + primaryRouteProgressDataProvider, + alternativeMetadataProvider + ) + + @Before + fun setUp() { + mockkObject(AlternativeRouteProgressDataProvider) + } + + @After + fun tearDown() { + unmockkObject(AlternativeRouteProgressDataProvider) + } + + @Test(expected = IllegalArgumentException::class) + fun `getRoutesProgressData for empty routes`() = runBlockingTest { + sut.getRoutesProgressData(emptyList()) + } + + @Test + fun `getRoutesProgressData for primary route only`() = runBlockingTest { + val primaryRouteProgressData = RouteProgressData(4, 5, 6) + val primaryRoute = mockk(relaxed = true) + coEvery { + primaryRouteProgressDataProvider.getRouteRefreshRequestDataOrWait() + } returns primaryRouteProgressData + val expected = RoutesProgressData(primaryRoute, primaryRouteProgressData, emptyList()) + + val actual = sut.getRoutesProgressData(listOf(primaryRoute)) + + assertEquals(expected, actual) + } + + @Test + fun `getRoutesProgressData for valid alternatives`() = runBlockingTest { + val primaryRouteProgressData = RouteProgressData(4, 5, 6) + val alternativeRoute1ProgressData = RouteProgressData(7, 8, 9) + val alternativeRoute2ProgressData = RouteProgressData(10, 11, 12) + val primaryRoute = mockk(relaxed = true) + val alternativeRoute1 = mockk(relaxed = true) + val alternativeRoute2 = mockk(relaxed = true) + val alternativeMetadata1 = mockk(relaxed = true) + val alternativeMetadata2 = mockk(relaxed = true) + coEvery { + primaryRouteProgressDataProvider.getRouteRefreshRequestDataOrWait() + } returns primaryRouteProgressData + every { + alternativeMetadataProvider.getMetadataFor(alternativeRoute1) + } returns alternativeMetadata1 + every { + alternativeMetadataProvider.getMetadataFor(alternativeRoute2) + } returns alternativeMetadata2 + every { + AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata1 + ) + } returns alternativeRoute1ProgressData + every { + AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata2 + ) + } returns alternativeRoute2ProgressData + val expected = RoutesProgressData( + primaryRoute, + primaryRouteProgressData, + listOf( + alternativeRoute1 to alternativeRoute1ProgressData, + alternativeRoute2 to alternativeRoute2ProgressData + ) + ) + + val actual = sut.getRoutesProgressData( + listOf(primaryRoute, alternativeRoute1, alternativeRoute2) + ) + + assertEquals(expected, actual) + } + + @Test + fun `getRoutesProgressData for one invalid alternative`() = runBlockingTest { + val primaryRouteProgressData = RouteProgressData(4, 5, 6) + val alternativeRoute2ProgressData = RouteProgressData(10, 11, 12) + val primaryRoute = mockk(relaxed = true) + val alternativeRoute1 = mockk(relaxed = true) + val alternativeRoute2 = mockk(relaxed = true) + val alternativeMetadata2 = mockk(relaxed = true) + coEvery { + primaryRouteProgressDataProvider.getRouteRefreshRequestDataOrWait() + } returns primaryRouteProgressData + every { alternativeMetadataProvider.getMetadataFor(alternativeRoute1) } returns null + every { + alternativeMetadataProvider.getMetadataFor(alternativeRoute2) + } returns alternativeMetadata2 + every { + AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata2 + ) + } returns alternativeRoute2ProgressData + val expected = RoutesProgressData( + primaryRoute, + primaryRouteProgressData, + listOf(alternativeRoute1 to null, alternativeRoute2 to alternativeRoute2ProgressData) + ) + + val actual = sut.getRoutesProgressData( + listOf(primaryRoute, alternativeRoute1, alternativeRoute2) + ) + + assertEquals(expected, actual) + } +} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataTest.kt new file mode 100644 index 00000000000..70b9f979c69 --- /dev/null +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataTest.kt @@ -0,0 +1,40 @@ +package com.mapbox.navigation.core + +import com.mapbox.navigation.base.route.NavigationRoute +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Test + +class RoutesProgressDataTest { + + @Test + fun `allRoutesProgressData for no alternatives`() { + val primaryRoute = mockk(relaxed = true) + val primaryRouteProgressData = RouteProgressData(1, 2, 3) + val expected = listOf(primaryRoute to primaryRouteProgressData) + + val actual = RoutesProgressData(primaryRoute, primaryRouteProgressData, emptyList()) + + assertEquals(expected, actual.allRoutesProgressData) + } + + @Test + fun `allRoutesProgressData for alternatives`() { + val primaryRoute = mockk(relaxed = true) + val alternativeRoute = mockk(relaxed = true) + val primaryRouteProgressData = RouteProgressData(1, 2, 3) + val alternativeRouteProgressData = RouteProgressData(4, 5, 6) + val expected = listOf( + primaryRoute to primaryRouteProgressData, + alternativeRoute to alternativeRouteProgressData + ) + + val actual = RoutesProgressData( + primaryRoute, + primaryRouteProgressData, + listOf(alternativeRoute to alternativeRouteProgressData) + ) + + assertEquals(expected, actual.allRoutesProgressData) + } +} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt new file mode 100644 index 00000000000..19cb175ad4b --- /dev/null +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt @@ -0,0 +1,213 @@ +package com.mapbox.navigation.core.routealternatives + +import com.mapbox.geojson.Point +import com.mapbox.navigation.core.RouteProgressData +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Test + +class AlternativeRouteProgressDataProviderTest { + + @Test + fun `fork passed`() { + val primaryRouteGeometryIndex = 200 + val primaryRouteProgressData = RouteProgressData(4, primaryRouteGeometryIndex, 30) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 5, + primaryForkRouteGeometryIndex = primaryRouteGeometryIndex - 1, + primaryForkLegGeometryIndex = 80, + alternativeForkLegIndex = 3, + alternativeForkRouteGeometryIndex = 90, + alternativeForkLegGeometryIndex = 40 + ) + val expected = RouteProgressData(3, 90, 40) + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + @Test + fun `fork passed, alternative is longer than primary`() { + val primaryRouteGeometryIndex = 200 + val primaryRouteProgressData = RouteProgressData(3, primaryRouteGeometryIndex, 40) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 3, + primaryForkRouteGeometryIndex = primaryRouteGeometryIndex - 1, + primaryForkLegGeometryIndex = 40, + alternativeForkLegIndex = 5, + alternativeForkRouteGeometryIndex = 130, + alternativeForkLegGeometryIndex = 80 + ) + val expected = RouteProgressData(5, 130, 80) + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + @Test + fun `on fork`() { + val primaryRouteGeometryIndex = 200 + val primaryRouteProgressData = RouteProgressData(4, primaryRouteGeometryIndex, 30) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 5, + primaryForkRouteGeometryIndex = primaryRouteGeometryIndex, + primaryForkLegGeometryIndex = 80, + alternativeForkLegIndex = 3, + alternativeForkRouteGeometryIndex = 90, + alternativeForkLegGeometryIndex = 40 + ) + val expected = RouteProgressData(3, 90, 40) + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + @Test + fun `before fork, alternative starts together with the primary route`() { + val primaryRouteProgressData = RouteProgressData(4, 200, 30) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 5, + primaryForkRouteGeometryIndex = 250, + primaryForkLegGeometryIndex = 80, + alternativeForkLegIndex = 5, + alternativeForkRouteGeometryIndex = 250, + alternativeForkLegGeometryIndex = 80 + ) + val expected = primaryRouteProgressData + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + @Test + fun `before fork, alternative starts after the primary route on the first leg`() { + // primary leg and route indices diff is 165 + val primaryRouteProgressData = RouteProgressData(4, 200, 35) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 5, + primaryForkRouteGeometryIndex = 250, + primaryForkLegGeometryIndex = 85, + alternativeForkLegIndex = 5, + alternativeForkRouteGeometryIndex = 220, + alternativeForkLegGeometryIndex = 59 + ) + val expected = RouteProgressData(4, 170, 9) + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + @Test + fun `before fork, alternative starts after the primary route on the second leg`() { + // primary leg and route indices diff is 165 + val primaryRouteProgressData = RouteProgressData(4, 200, 35) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 5, + primaryForkRouteGeometryIndex = 250, + primaryForkLegGeometryIndex = 85, + alternativeForkLegIndex = 3, + alternativeForkRouteGeometryIndex = 220, + alternativeForkLegGeometryIndex = 59 + ) + val expected = RouteProgressData(2, 170, 9) + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + @Test + fun `before fork, alternative starts after the primary route on the current leg`() { + // primary leg and route indices diff is 140 + val primaryRouteProgressData = RouteProgressData(4, 200, 60) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 4, + primaryForkRouteGeometryIndex = 250, + primaryForkLegGeometryIndex = 110, + alternativeForkLegIndex = 0, + alternativeForkRouteGeometryIndex = 90, + alternativeForkLegGeometryIndex = 90 + ) + val expected = RouteProgressData(0, 40, 40) + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + @Test + fun `before fork, primary starts after the alternative route on the current leg`() { + // primary leg and route indices diff is 140 + val primaryRouteProgressData = RouteProgressData(0, 40, 40) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 0, + primaryForkRouteGeometryIndex = 90, + primaryForkLegGeometryIndex = 90, + alternativeForkLegIndex = 4, + alternativeForkRouteGeometryIndex = 250, + alternativeForkLegGeometryIndex = 110 + ) + val expected = RouteProgressData(4, 200, 60) + + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) + + assertEquals(expected, actual) + } + + private fun alternativeRouteMetadata( + primaryForkLegIndex: Int, + primaryForkRouteGeometryIndex: Int, + primaryForkLegGeometryIndex: Int, + alternativeForkLegIndex: Int, + alternativeForkRouteGeometryIndex: Int, + alternativeForkLegGeometryIndex: Int, + ): AlternativeRouteMetadata { + return AlternativeRouteMetadata( + mockk(), + AlternativeRouteIntersection( + Point.fromLngLat(1.1, 2.2), + alternativeForkRouteGeometryIndex, + alternativeForkLegGeometryIndex, + alternativeForkLegIndex + ), + AlternativeRouteIntersection( + Point.fromLngLat(3.3, 4.4), + primaryForkRouteGeometryIndex, + primaryForkLegGeometryIndex, + primaryForkLegIndex + ), + mockk(), + mockk(), + 0 + ) + } +} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerTest.kt index 4410f516565..9be328eb979 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerTest.kt @@ -12,7 +12,8 @@ import com.mapbox.navigation.base.route.NavigationRouterRefreshCallback import com.mapbox.navigation.base.route.RouteRefreshOptions import com.mapbox.navigation.base.route.RouterFactory import com.mapbox.navigation.core.RouteProgressData -import com.mapbox.navigation.core.RouteProgressDataProvider +import com.mapbox.navigation.core.RoutesProgressData +import com.mapbox.navigation.core.RoutesProgressDataProvider import com.mapbox.navigation.core.directions.session.DirectionsSession import com.mapbox.navigation.core.directions.session.RouteRefresh import com.mapbox.navigation.core.ev.EVRefreshDataProvider @@ -64,8 +65,15 @@ class RouteRefreshControllerTest { private val routeProgressData = RouteProgressData(0, 1, 2) private val routeProgressDataProvider = - mockk(relaxed = true) { - coEvery { getRouteRefreshRequestDataOrWait() } returns routeProgressData + mockk(relaxed = true) { + coEvery { getRoutesProgressData(any()) } answers { + val routes = firstArg() as List + RoutesProgressData( + routes.first(), + routeProgressData, + routes.drop(1).map { it to routeProgressData } + ) + } } private val evRefreshDataProvider = mockk(relaxed = true) private val mockStatesObserver = mockk(relaxUnitFun = true) @@ -640,6 +648,100 @@ class RouteRefreshControllerTest { ) } + @Test + fun `remove expired data for alternative route`() = + runBlockingTest { + val currentTime = utcToLocalTime( + year = 2022, + month = Month.MAY, + date = 22, + hourOfDay = 12, + minute = 30, + second = 0 + ) + val primaryRoute = createNavigationRoute( + createTestTwoLegRoute( + firstLegAnnotations = createRouteLegAnnotation( + congestion = listOf("severe", "moderate"), + ) + ) + ) + val alternativeRoute = createNavigationRoute( + createTestTwoLegRoute( + firstLegIncidents = listOf( + createIncident( + id = "1", + endTime = "2022-05-22T14:00:00Z", + ), + createIncident( + id = "2", + endTime = "2022-05-22T12:00:00Z", // expired + ), + createIncident( + id = "3", + endTime = "2022-05-22T12:00:00-01", + ), + createIncident( + id = "4", + endTime = null, + ) + ), + secondLegIncidents = listOf( + createIncident( + id = "5", + endTime = "2022-05-23T10:00:00Z", + ), + createIncident( + id = "6", + endTime = "2022-05-22T12:29:00Z", // expired + ), + createIncident( + id = "7", + endTime = "wrong date format", + ) + ) + ) + ) + val routeRefreshStub = RouteRefreshStub().apply { + setRefreshedRoute(primaryRoute) + failRouteRefresh(alternativeRoute.id) + } + coEvery { + routeProgressDataProvider.getRoutesProgressData(any()) + } returns RoutesProgressData( + primaryRoute, + RouteProgressData(1, 2, 3), + listOf(alternativeRoute to routeProgressData) + ) + val routeRefreshOptions = RouteRefreshOptions.Builder() + .intervalMillis(30_000) + .build() + val routeRefreshController = createRouteRefreshController( + routeRefreshOptions = routeRefreshOptions, + localDateProvider = { currentTime }, + routeRefresh = routeRefreshStub, + ) + // act + val refreshedRoutesDeffer = async { + routeRefreshController.refresh(listOf(primaryRoute, alternativeRoute)) + } + advanceTimeBy( + expectedTimeToInvalidateCongestions(routeRefreshOptions.intervalMillis) + ) + // assert + val refreshedRoute = refreshedRoutesDeffer.getCompletedTest().routes[1] + refreshedRoute.assertCongestionExpiredForLeg(0) + refreshedRoute.assertCongestionExpiredForLeg(1) + assertEquals( + listOf("1", "3", "4"), + refreshedRoute.directionsRoute.legs()!![0].incidents()?.map { it.id() } + ) + assertEquals( + listOf("5", "7"), + refreshedRoute.directionsRoute.legs()!![1].incidents()?.map { it.id() } + ) + } + @Test(timeout = 2_000_000) fun `after invalidation route isn't updated until successful refresh`() = runBlockingTest { val initialRoute = createNavigationRoute(createTestTwoLegRoute()) @@ -807,8 +909,8 @@ class RouteRefreshControllerTest { .intervalMillis(30_000L) .build() coEvery { - routeProgressDataProvider.getRouteRefreshRequestDataOrWait() - } returns RouteProgressData(1, 0, null) + routeProgressDataProvider.getRoutesProgressData(listOf(currentRoute)) + } returns RoutesProgressData(currentRoute, RouteProgressData(1, 0, null), emptyList()) val routeRefreshController = createRouteRefreshController( routeRefreshOptions = routeRefreshOptions, routeDiffProvider = DirectionsRouteDiffProvider(), @@ -1021,6 +1123,77 @@ class RouteRefreshControllerTest { } } + @Test + fun `successful refresh of two routes starting at different locations`() = + runBlockingTest { + val initialRoutes = createNavigationRoutes( + createDirectionsResponse( + routes = listOf( + createTestTwoLegRoute( + firstLegAnnotations = createRouteLegAnnotation( + congestion = listOf("severe", "moderate"), + congestionNumeric = listOf(90, 50), + ) + ), + createTestTwoLegRoute( + firstLegAnnotations = createRouteLegAnnotation( + congestion = listOf("severe", "moderate"), + congestionNumeric = listOf(90, 50), + ) + ) + ) + ) + ) + val refreshedRoutes = createNavigationRoutes( + createDirectionsResponse( + routes = listOf( + createTestTwoLegRoute( + firstLegAnnotations = createRouteLegAnnotation( + congestion = listOf("severe", "severe"), + congestionNumeric = listOf(90, 90), + ) + ), + createTestTwoLegRoute( + firstLegAnnotations = createRouteLegAnnotation( + congestion = listOf("severe", "severe"), + congestionNumeric = listOf(90, 90), + ) + ) + ) + ) + ) + val primaryRouteProgressData = RouteProgressData(3, 4, 5) + val alternativeRouteProgressData = RouteProgressData(1, 2, 3) + coEvery { + routeProgressDataProvider.getRoutesProgressData(any()) + } returns RoutesProgressData( + initialRoutes.first(), + primaryRouteProgressData, + listOf(initialRoutes[1] to alternativeRouteProgressData) + ) + val routeRefreshStub = RouteRefreshStub().apply { + setRefreshedRoute(refreshedRoutes[0], RouteRefreshRequestData(3, 4, 5, emptyMap())) + setRefreshedRoute(refreshedRoutes[1], RouteRefreshRequestData(1, 2, 3, emptyMap())) + } + val refreshOptions = RouteRefreshOptions.Builder().build() + val routeRefreshController = createRouteRefreshController( + routeRefresh = routeRefreshStub, + routeRefreshOptions = refreshOptions + ) + + val refreshedRoutesDeferred = async { + routeRefreshController.refresh(initialRoutes) + } + advanceTimeBy(refreshOptions.intervalMillis) + + val result = refreshedRoutesDeferred.getCompletedTest() + + assertEquals( + RefreshedRouteInfo(refreshedRoutes, primaryRouteProgressData), + result + ) + } + @Test fun `primary route refresh failed, alternative route refreshed successfully, observer started, success`() = runBlockingTest { @@ -1279,8 +1452,12 @@ class RouteRefreshControllerTest { } val routeRefreshOptions = RouteRefreshOptions.Builder().build() coEvery { - routeProgressDataProvider.getRouteRefreshRequestDataOrWait() - } returns RouteProgressData(1, 0, null) + routeProgressDataProvider.getRoutesProgressData(initialRoutes) + } returns RoutesProgressData( + initialRoutes.first(), + RouteProgressData(1, 0, null), + listOf(initialRoutes[1] to RouteProgressData(1, 0, null)) + ) val routeRefreshController = createRouteRefreshController( routeRefreshOptions = routeRefreshOptions, routeDiffProvider = DirectionsRouteDiffProvider(), @@ -1365,8 +1542,12 @@ class RouteRefreshControllerTest { routeRefreshStub.doNotRespondForRouteRefresh(refreshedRoutes[1].id) val refreshOptions = RouteRefreshOptions.Builder().build() coEvery { - routeProgressDataProvider.getRouteRefreshRequestDataOrWait() - } returns RouteProgressData(1, 0, null) + routeProgressDataProvider.getRoutesProgressData(initialRoutes) + } returns RoutesProgressData( + initialRoutes.first(), + RouteProgressData(1, 0, null), + listOf(initialRoutes[1] to RouteProgressData(1, 0, null)) + ) val routeRefreshController = createRouteRefreshController( routeRefresh = routeRefreshStub, routeRefreshOptions = refreshOptions, From 414b551b366871251148ecd2fa1dab50004002cf Mon Sep 17 00:00:00 2001 From: Dzina Dybouskaya Date: Thu, 19 Jan 2023 19:45:10 +0300 Subject: [PATCH 2/5] NAVAND-1073: fix geometry leg index calculation --- .../core/RouteRefreshTest.kt | 69 +++++ .../AlternativeRouteProgressDataProvider.kt | 31 ++- .../core/RoutesProgressDataProviderTest.kt | 6 +- ...lternativeRouteProgressDataProviderTest.kt | 240 ++++++++++++------ 4 files changed, 255 insertions(+), 91 deletions(-) diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt index bfef0a4d24f..fff46879d5b 100644 --- a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt @@ -9,6 +9,7 @@ import com.mapbox.api.directions.v5.models.DirectionsResponse import com.mapbox.api.directions.v5.models.Incident import com.mapbox.api.directions.v5.models.RouteOptions import com.mapbox.geojson.Point +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions import com.mapbox.navigation.base.options.NavigationOptions import com.mapbox.navigation.base.options.RoutingTilesOptions @@ -59,6 +60,7 @@ import java.net.URI import java.util.concurrent.TimeUnit import kotlin.math.absoluteValue +@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) class RouteRefreshTest : BaseTest(EmptyTestActivity::class.java) { @get:Rule @@ -528,6 +530,73 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja assertEquals(187.126, refreshedRoutes[1].getSumOfDurationAnnotationsFromLeg(0), 0.0001) } + @Test + fun route_refresh_updates_annotations_for_new_alternative_with_more_legs() = + sdkTest { + setupMockRequestHandlers( + multilegCoordinates, + R.raw.route_response_single_route_multileg, + R.raw.route_response_single_route_multileg_refreshed, + "route_response_single_route_multileg", + acceptedGeometryIndex = 70 + ) + mockWebServerRule.requestHandlers.add( + FailByRequestMockRequestHandler( + MockDirectionsRefreshHandler( + "route_response_single_route_multileg_alternative", + readRawFileText( + activity, + R.raw.route_response_single_route_multileg_alternative_refreshed + ), + acceptedGeometryIndex = 11 + ) + ) + ) + val routeOptions = generateRouteOptions(multilegCoordinates) + val alternativeRoutes = mapboxNavigation.requestRoutes(routeOptions) + .getSuccessfulResultOrThrowException() + .routes + // alternative which was requested on the second leg of the original route, + // so the alternative has only one leg while the original route has two + val primaryRoute = alternativeForMultileg(activity).toNavigationRoutes().first() + + // corresponds to currentRouteGeometryIndex = 70 for alternative route and 11 for the primary route + mockLocationUpdatesRule.pushLocationUpdate( + mockLocationUpdatesRule.generateLocationUpdate { + latitude = 38.581798 + longitude = -121.476146 + } + ) + + mapboxNavigation.setNavigationRoutes(listOf(primaryRoute) + alternativeRoutes, initialLegIndex = 0) + mapboxNavigation.startTripSession() + + mapboxNavigation.routeProgressUpdates() + .filter { + it.currentRouteGeometryIndex == 11 + } + .first() + + val refreshedRoutes = mapboxNavigation.routesUpdates() + .filter { + it.reason == ROUTES_UPDATE_REASON_REFRESH + } + .first() + .navigationRoutes + + assertEquals( + alternativeRoutes[0].getSumOfDurationAnnotationsFromLeg(0), + refreshedRoutes[1].getSumOfDurationAnnotationsFromLeg(0), + 0.0001 + ) + + assertEquals(201.673, alternativeRoutes[0].getSumOfDurationAnnotationsFromLeg(1), 0.0001) + assertEquals(202.881, refreshedRoutes[1].getSumOfDurationAnnotationsFromLeg(1), 0.0001) + + assertEquals(194.3, primaryRoute.getSumOfDurationAnnotationsFromLeg(0), 0.0001) + assertEquals(187.126, refreshedRoutes[0].getSumOfDurationAnnotationsFromLeg(0), 0.0001) + } + @Test fun expect_route_refresh_to_update_annotations_incidents_and_closures_for_truncated_next_leg() = sdkTest { diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt index 797b6c70ff4..1c70928982c 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt @@ -1,10 +1,15 @@ package com.mapbox.navigation.core.routealternatives +import com.mapbox.navigation.base.route.NavigationRoute +import com.mapbox.navigation.base.utils.DecodeUtils.stepsGeometryToPoints import com.mapbox.navigation.core.RouteProgressData +import com.mapbox.navigation.utils.internal.ThreadController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext internal object AlternativeRouteProgressDataProvider { - fun getRouteProgressData( + suspend fun getRouteProgressData( primaryRouteProgressData: RouteProgressData, alternativeMetadata: AlternativeRouteMetadata ): RouteProgressData { @@ -22,11 +27,8 @@ internal object AlternativeRouteProgressDataProvider { primaryFork.geometryIndexInRoute - alternativeFork.geometryIndexInRoute routeGeometryIndex = primaryRouteProgressData.routeGeometryIndex - routeGeometryIndexDiff - val legGeometryIndexDiff = - primaryFork.geometryIndexInLeg - alternativeFork.geometryIndexInLeg - legGeometryIndex = primaryRouteProgressData.legGeometryIndex?.let { - it - legGeometryIndexDiff - } + legGeometryIndex = routeGeometryIndex - + prevLegsGeometryIndicesCount(alternativeMetadata.navigationRoute, legIndex) } else { legIndex = alternativeFork.legIndex routeGeometryIndex = alternativeFork.geometryIndexInRoute @@ -34,4 +36,21 @@ internal object AlternativeRouteProgressDataProvider { } return RouteProgressData(legIndex, routeGeometryIndex, legGeometryIndex) } + + private suspend fun prevLegsGeometryIndicesCount(route: NavigationRoute, currentLegIndex: Int): Int { + return withContext(ThreadController.DefaultDispatcher) { + var result = 0 + val stepsGeometries by lazy { route.directionsRoute.stepsGeometryToPoints() } + for (legIndex in 0 until currentLegIndex) { + val legGeometries = stepsGeometries.getOrNull(legIndex) + if (legGeometries != null) { + // remove step duplicates (the last point in prev step is the same as the first point in the current step) + result += legGeometries.sumOf { it.size } - legGeometries.size + 1 + } + } + // remove leg duplicates (the last point of the last step of prev leg is the same as the first point of the first step in the current leg) + result -= currentLegIndex + result + } + } } diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt index 6ddd6815215..dc995b060c2 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/RoutesProgressDataProviderTest.kt @@ -73,13 +73,13 @@ class RoutesProgressDataProviderTest { every { alternativeMetadataProvider.getMetadataFor(alternativeRoute2) } returns alternativeMetadata2 - every { + coEvery { AlternativeRouteProgressDataProvider.getRouteProgressData( primaryRouteProgressData, alternativeMetadata1 ) } returns alternativeRoute1ProgressData - every { + coEvery { AlternativeRouteProgressDataProvider.getRouteProgressData( primaryRouteProgressData, alternativeMetadata2 @@ -116,7 +116,7 @@ class RoutesProgressDataProviderTest { every { alternativeMetadataProvider.getMetadataFor(alternativeRoute2) } returns alternativeMetadata2 - every { + coEvery { AlternativeRouteProgressDataProvider.getRouteProgressData( primaryRouteProgressData, alternativeMetadata2 diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt index 19cb175ad4b..d194f757cf2 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt @@ -1,15 +1,45 @@ package com.mapbox.navigation.core.routealternatives import com.mapbox.geojson.Point +import com.mapbox.navigation.base.utils.DecodeUtils +import com.mapbox.navigation.base.utils.DecodeUtils.stepsGeometryToPoints import com.mapbox.navigation.core.RouteProgressData +import com.mapbox.navigation.testing.MainCoroutineRule +import com.mapbox.navigation.utils.internal.ThreadController +import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkObject +import io.mockk.unmockkStatic +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule import org.junit.Test +@OptIn(ExperimentalCoroutinesApi::class) class AlternativeRouteProgressDataProviderTest { + @get:Rule + val coroutineRule = MainCoroutineRule() + + @Before + fun setUp() { + mockkObject(ThreadController) + every { ThreadController.DefaultDispatcher } returns coroutineRule.testDispatcher + mockkStatic(DecodeUtils::class) + } + + @After + fun tearDown() { + unmockkObject(ThreadController) + unmockkStatic(DecodeUtils::class) + } + @Test - fun `fork passed`() { + fun `fork passed`() = coroutineRule.runBlockingTest { val primaryRouteGeometryIndex = 200 val primaryRouteProgressData = RouteProgressData(4, primaryRouteGeometryIndex, 30) val alternativeMetadata = alternativeRouteMetadata( @@ -18,7 +48,8 @@ class AlternativeRouteProgressDataProviderTest { primaryForkLegGeometryIndex = 80, alternativeForkLegIndex = 3, alternativeForkRouteGeometryIndex = 90, - alternativeForkLegGeometryIndex = 40 + alternativeForkLegGeometryIndex = 40, + emptyList() ) val expected = RouteProgressData(3, 90, 40) @@ -31,7 +62,7 @@ class AlternativeRouteProgressDataProviderTest { } @Test - fun `fork passed, alternative is longer than primary`() { + fun `fork passed, alternative is longer than primary`() = coroutineRule.runBlockingTest { val primaryRouteGeometryIndex = 200 val primaryRouteProgressData = RouteProgressData(3, primaryRouteGeometryIndex, 40) val alternativeMetadata = alternativeRouteMetadata( @@ -40,7 +71,8 @@ class AlternativeRouteProgressDataProviderTest { primaryForkLegGeometryIndex = 40, alternativeForkLegIndex = 5, alternativeForkRouteGeometryIndex = 130, - alternativeForkLegGeometryIndex = 80 + alternativeForkLegGeometryIndex = 80, + emptyList() ) val expected = RouteProgressData(5, 130, 80) @@ -53,7 +85,7 @@ class AlternativeRouteProgressDataProviderTest { } @Test - fun `on fork`() { + fun `on fork`() = coroutineRule.runBlockingTest { val primaryRouteGeometryIndex = 200 val primaryRouteProgressData = RouteProgressData(4, primaryRouteGeometryIndex, 30) val alternativeMetadata = alternativeRouteMetadata( @@ -62,7 +94,8 @@ class AlternativeRouteProgressDataProviderTest { primaryForkLegGeometryIndex = 80, alternativeForkLegIndex = 3, alternativeForkRouteGeometryIndex = 90, - alternativeForkLegGeometryIndex = 40 + alternativeForkLegGeometryIndex = 40, + emptyList() ) val expected = RouteProgressData(3, 90, 40) @@ -75,7 +108,7 @@ class AlternativeRouteProgressDataProviderTest { } @Test - fun `before fork, alternative starts together with the primary route`() { + fun `before fork, alternative starts together with the primary route`() = coroutineRule.runBlockingTest { val primaryRouteProgressData = RouteProgressData(4, 200, 30) val alternativeMetadata = alternativeRouteMetadata( primaryForkLegIndex = 5, @@ -83,9 +116,16 @@ class AlternativeRouteProgressDataProviderTest { primaryForkLegGeometryIndex = 80, alternativeForkLegIndex = 5, alternativeForkRouteGeometryIndex = 250, - alternativeForkLegGeometryIndex = 80 + alternativeForkLegGeometryIndex = 70, + listOf( + listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), + listOf(List(23) { mockk() }), + listOf(List(32) { mockk() }, List(19) { mockk() }), + listOf(List(14) { mockk() }, List(23) { mockk() }), // 135 points for legs #0-#3 + listOf(List(8) { mockk() }), + ) ) - val expected = primaryRouteProgressData + val expected = RouteProgressData(4, 200, 65) val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( primaryRouteProgressData, @@ -96,92 +136,123 @@ class AlternativeRouteProgressDataProviderTest { } @Test - fun `before fork, alternative starts after the primary route on the first leg`() { - // primary leg and route indices diff is 165 - val primaryRouteProgressData = RouteProgressData(4, 200, 35) - val alternativeMetadata = alternativeRouteMetadata( - primaryForkLegIndex = 5, - primaryForkRouteGeometryIndex = 250, - primaryForkLegGeometryIndex = 85, - alternativeForkLegIndex = 5, - alternativeForkRouteGeometryIndex = 220, - alternativeForkLegGeometryIndex = 59 - ) - val expected = RouteProgressData(4, 170, 9) + fun `before fork, alternative starts after the primary route on the first leg`() = + coroutineRule.runBlockingTest { + val primaryRouteProgressData = RouteProgressData(4, 200, 35) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 5, + primaryForkRouteGeometryIndex = 250, + primaryForkLegGeometryIndex = 85, + alternativeForkLegIndex = 5, + alternativeForkRouteGeometryIndex = 220, + alternativeForkLegGeometryIndex = 86, + listOf( + listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), + listOf(List(23) { mockk() }), + listOf(List(32) { mockk() }, List(19) { mockk() }), + listOf(List(14) { mockk() }, List(23) { mockk() }), // 135 points for legs #0-#3 + listOf(List(8) { mockk() }), + ) + ) + val expected = RouteProgressData(4, 170, 35) - val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( - primaryRouteProgressData, - alternativeMetadata - ) + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) - assertEquals(expected, actual) - } + assertEquals(expected, actual) + } @Test - fun `before fork, alternative starts after the primary route on the second leg`() { - // primary leg and route indices diff is 165 - val primaryRouteProgressData = RouteProgressData(4, 200, 35) - val alternativeMetadata = alternativeRouteMetadata( - primaryForkLegIndex = 5, - primaryForkRouteGeometryIndex = 250, - primaryForkLegGeometryIndex = 85, - alternativeForkLegIndex = 3, - alternativeForkRouteGeometryIndex = 220, - alternativeForkLegGeometryIndex = 59 - ) - val expected = RouteProgressData(2, 170, 9) + fun `before fork, alternative starts after the primary route on the second leg`() = + coroutineRule.runBlockingTest { + val primaryRouteProgressData = RouteProgressData(4, 200, 35) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 5, + primaryForkRouteGeometryIndex = 250, + primaryForkLegGeometryIndex = 85, + alternativeForkLegIndex = 3, + alternativeForkRouteGeometryIndex = 220, + alternativeForkLegGeometryIndex = 81, + listOf( + listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), + listOf(List(23) { mockk() }), // 135 points for legs #0-#1 + listOf(List(32) { mockk() }, List(19) { mockk() }), + listOf(List(14) { mockk() }, List(23) { mockk() }), + listOf(List(8) { mockk() }), + ) + ) + val expected = RouteProgressData(2, 170, 119) - val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( - primaryRouteProgressData, - alternativeMetadata - ) + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) - assertEquals(expected, actual) - } + assertEquals(expected, actual) + } @Test - fun `before fork, alternative starts after the primary route on the current leg`() { - // primary leg and route indices diff is 140 - val primaryRouteProgressData = RouteProgressData(4, 200, 60) - val alternativeMetadata = alternativeRouteMetadata( - primaryForkLegIndex = 4, - primaryForkRouteGeometryIndex = 250, - primaryForkLegGeometryIndex = 110, - alternativeForkLegIndex = 0, - alternativeForkRouteGeometryIndex = 90, - alternativeForkLegGeometryIndex = 90 - ) - val expected = RouteProgressData(0, 40, 40) + fun `before fork, alternative starts after the primary route on the current leg`() = + coroutineRule.runBlockingTest { + // primary leg and route indices diff is 140 + val primaryRouteProgressData = RouteProgressData(4, 200, 60) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 4, + primaryForkRouteGeometryIndex = 250, + primaryForkLegGeometryIndex = 110, + alternativeForkLegIndex = 0, + alternativeForkRouteGeometryIndex = 90, + alternativeForkLegGeometryIndex = 20, + listOf( + listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), + listOf(List(23) { mockk() }), + listOf(List(32) { mockk() }, List(19) { mockk() }), + listOf(List(14) { mockk() }, List(23) { mockk() }), + ) + ) + val expected = RouteProgressData(0, 40, 40) - val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( - primaryRouteProgressData, - alternativeMetadata - ) + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) - assertEquals(expected, actual) - } + assertEquals(expected, actual) + } @Test - fun `before fork, primary starts after the alternative route on the current leg`() { - // primary leg and route indices diff is 140 - val primaryRouteProgressData = RouteProgressData(0, 40, 40) - val alternativeMetadata = alternativeRouteMetadata( - primaryForkLegIndex = 0, - primaryForkRouteGeometryIndex = 90, - primaryForkLegGeometryIndex = 90, - alternativeForkLegIndex = 4, - alternativeForkRouteGeometryIndex = 250, - alternativeForkLegGeometryIndex = 110 - ) - val expected = RouteProgressData(4, 200, 60) + fun `before fork, primary starts after the alternative route on the current leg`() = + coroutineRule.runBlockingTest { + // primary leg and route indices diff is 140 + val primaryRouteProgressData = RouteProgressData(0, 40, 40) + val alternativeMetadata = alternativeRouteMetadata( + primaryForkLegIndex = 0, + primaryForkRouteGeometryIndex = 90, + primaryForkLegGeometryIndex = 90, + alternativeForkLegIndex = 4, + alternativeForkRouteGeometryIndex = 250, + alternativeForkLegGeometryIndex = 10, + listOf( + listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), + listOf(List(23) { mockk() }), + listOf(List(32) { mockk() }, List(19) { mockk() }), + listOf( + List(14) { mockk() }, + List(23) { mockk() }), // 135 points for legs #0-#3 + listOf(List(8) { mockk() }), + ) + ) + val expected = RouteProgressData(4, 200, 65) - val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( - primaryRouteProgressData, - alternativeMetadata - ) + val actual = AlternativeRouteProgressDataProvider.getRouteProgressData( + primaryRouteProgressData, + alternativeMetadata + ) - assertEquals(expected, actual) - } + assertEquals(expected, actual) + } private fun alternativeRouteMetadata( primaryForkLegIndex: Int, @@ -190,9 +261,14 @@ class AlternativeRouteProgressDataProviderTest { alternativeForkLegIndex: Int, alternativeForkRouteGeometryIndex: Int, alternativeForkLegGeometryIndex: Int, + stepsGeometries: List>> ): AlternativeRouteMetadata { return AlternativeRouteMetadata( - mockk(), + mockk { + every { directionsRoute } returns mockk { + every { stepsGeometryToPoints() } returns stepsGeometries + } + }, AlternativeRouteIntersection( Point.fromLngLat(1.1, 2.2), alternativeForkRouteGeometryIndex, From ea1e40802130dd43c73d7b793a1c37d5fde6afa4 Mon Sep 17 00:00:00 2001 From: Dzina Dybouskaya Date: Fri, 20 Jan 2023 14:09:03 +0300 Subject: [PATCH 3/5] NAVAND-1073: add example --- examples/src/main/AndroidManifest.xml | 5 + .../navigation/examples/MainActivity.kt | 6 + .../examples/core/RouteRefreshActivity.kt | 484 ++++++++++++++++++ .../res/layout/activity_route_refresh.xml | 58 +++ examples/src/main/res/values/strings.xml | 4 + 5 files changed, 557 insertions(+) create mode 100644 examples/src/main/java/com/mapbox/navigation/examples/core/RouteRefreshActivity.kt create mode 100644 examples/src/main/res/layout/activity_route_refresh.xml diff --git a/examples/src/main/AndroidManifest.xml b/examples/src/main/AndroidManifest.xml index 859373f2e7f..4836f095e08 100644 --- a/examples/src/main/AndroidManifest.xml +++ b/examples/src/main/AndroidManifest.xml @@ -95,6 +95,11 @@ android:label="@string/title_multileg_route"> + + + diff --git a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt index 61a60f6ca05..a3cdbc05af7 100644 --- a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt +++ b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt @@ -25,6 +25,7 @@ import com.mapbox.navigation.examples.core.MapboxVoiceActivity import com.mapbox.navigation.examples.core.MultiLegRouteExampleActivity import com.mapbox.navigation.examples.core.R import com.mapbox.navigation.examples.core.ReplayHistoryActivity +import com.mapbox.navigation.examples.core.RouteRefreshActivity import com.mapbox.navigation.examples.core.camera.MapboxCameraAnimationsActivity import com.mapbox.navigation.examples.core.databinding.LayoutActivityMainBinding import com.mapbox.navigation.examples.util.LocationPermissionsHelper @@ -140,6 +141,11 @@ class MainActivity : AppCompatActivity(), PermissionsListener { getString(R.string.description_draw_utility), RouteDrawingActivity::class.java ), + SampleItem( + getString(R.string.title_route_refresh), + getString(R.string.description_route_refresh), + RouteRefreshActivity::class.java + ), ) } diff --git a/examples/src/main/java/com/mapbox/navigation/examples/core/RouteRefreshActivity.kt b/examples/src/main/java/com/mapbox/navigation/examples/core/RouteRefreshActivity.kt new file mode 100644 index 00000000000..e1abb803444 --- /dev/null +++ b/examples/src/main/java/com/mapbox/navigation/examples/core/RouteRefreshActivity.kt @@ -0,0 +1,484 @@ +package com.mapbox.navigation.examples.core + +import android.annotation.SuppressLint +import android.content.res.Configuration +import android.content.res.Resources +import android.graphics.Color +import android.location.Location +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import androidx.core.content.ContextCompat +import com.mapbox.api.directions.v5.models.DirectionsRoute +import com.mapbox.api.directions.v5.models.RouteOptions +import com.mapbox.geojson.Point +import com.mapbox.maps.CameraOptions +import com.mapbox.maps.EdgeInsets +import com.mapbox.maps.MapboxMap +import com.mapbox.maps.Style.Companion.MAPBOX_STREETS +import com.mapbox.maps.plugin.LocationPuck2D +import com.mapbox.maps.plugin.animation.camera +import com.mapbox.maps.plugin.gestures.gestures +import com.mapbox.maps.plugin.locationcomponent.location +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions +import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions +import com.mapbox.navigation.base.options.NavigationOptions +import com.mapbox.navigation.base.route.NavigationRoute +import com.mapbox.navigation.base.route.NavigationRouterCallback +import com.mapbox.navigation.base.route.RouteRefreshOptions +import com.mapbox.navigation.base.route.RouterFailure +import com.mapbox.navigation.base.route.RouterOrigin +import com.mapbox.navigation.base.route.toNavigationRoute +import com.mapbox.navigation.base.trip.model.RouteProgress +import com.mapbox.navigation.core.MapboxNavigation +import com.mapbox.navigation.core.MapboxNavigationProvider +import com.mapbox.navigation.core.directions.session.RoutesObserver +import com.mapbox.navigation.core.replay.MapboxReplayer +import com.mapbox.navigation.core.replay.ReplayLocationEngine +import com.mapbox.navigation.core.replay.history.ReplayEventBase +import com.mapbox.navigation.core.replay.route.ReplayProgressObserver +import com.mapbox.navigation.core.replay.route.ReplayRouteMapper +import com.mapbox.navigation.core.routealternatives.NavigationRouteAlternativesObserver +import com.mapbox.navigation.core.routealternatives.RouteAlternativesError +import com.mapbox.navigation.core.trip.session.LocationMatcherResult +import com.mapbox.navigation.core.trip.session.LocationObserver +import com.mapbox.navigation.core.trip.session.RouteProgressObserver +import com.mapbox.navigation.examples.core.databinding.ActivityRouteRefreshBinding +import com.mapbox.navigation.examples.util.Utils +import com.mapbox.navigation.ui.maps.camera.NavigationCamera +import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource +import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler +import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState +import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider +import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApi +import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowView +import com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptions +import com.mapbox.navigation.ui.maps.route.line.MapboxRouteLineApiExtensions.setNavigationRoutes +import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApi +import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineView +import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptions +import com.mapbox.navigation.ui.maps.route.line.model.RouteLineColorResources +import com.mapbox.navigation.ui.maps.route.line.model.RouteLineResources +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * Use for testing alternative routes refresh. + * How to test: + * 1. Add a waypoint by long-tapping on the map. + * 2. Optionally, add more waypoints the same way. + * 3. Click "Start Navigation". + * 4. Remove alternative traffic from the map by clicking "Clear traffic". + * 5. Wait for refresh: the traffic should reappear. + * 6. Optionally, remove the traffic and wait for refresh again. + */ +@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) +class RouteRefreshActivity : AppCompatActivity() { + + /* ----- Layout binding reference ----- */ + private lateinit var binding: ActivityRouteRefreshBinding + + /* ----- Mapbox Maps components ----- */ + private lateinit var mapboxMap: MapboxMap + + /* ----- Mapbox Navigation components ----- */ + private lateinit var mapboxNavigation: MapboxNavigation + + // location puck integration + private val navigationLocationProvider = NavigationLocationProvider() + + private val mapboxReplayer = MapboxReplayer() + private val replayRouteMapper = ReplayRouteMapper() + private val replayProgressObserver = ReplayProgressObserver(mapboxReplayer) + private var coordinates = mutableListOf() + private var previewedRoutes = emptyList() + + // camera + private lateinit var navigationCamera: NavigationCamera + private lateinit var viewportDataSource: MapboxNavigationViewportDataSource + private val pixelDensity = Resources.getSystem().displayMetrics.density + private val overviewPadding: EdgeInsets by lazy { + EdgeInsets( + 140.0 * pixelDensity, + 40.0 * pixelDensity, + 120.0 * pixelDensity, + 40.0 * pixelDensity + ) + } + private val landscapeOverviewPadding: EdgeInsets by lazy { + EdgeInsets( + 30.0 * pixelDensity, + 380.0 * pixelDensity, + 20.0 * pixelDensity, + 20.0 * pixelDensity + ) + } + private val followingPadding: EdgeInsets by lazy { + EdgeInsets( + 180.0 * pixelDensity, + 40.0 * pixelDensity, + 150.0 * pixelDensity, + 40.0 * pixelDensity + ) + } + private val landscapeFollowingPadding: EdgeInsets by lazy { + EdgeInsets( + 30.0 * pixelDensity, + 380.0 * pixelDensity, + 110.0 * pixelDensity, + 40.0 * pixelDensity + ) + } + + // route line + private lateinit var routeLineAPI: MapboxRouteLineApi + private lateinit var routeLineView: MapboxRouteLineView + private lateinit var routeArrowView: MapboxRouteArrowView + private val routeArrowAPI: MapboxRouteArrowApi = MapboxRouteArrowApi() + + /* ----- Location and route progress callbacks ----- */ + private val locationObserver = object : LocationObserver { + override fun onNewRawLocation(rawLocation: Location) { + // not handled + } + + override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) { + // update location puck's position on the map + navigationLocationProvider.changePosition( + location = locationMatcherResult.enhancedLocation, + keyPoints = locationMatcherResult.keyPoints, + ) + + // update camera position to account for new location + viewportDataSource.onLocationChanged(locationMatcherResult.enhancedLocation) + viewportDataSource.evaluate() + } + } + + private val routeProgressObserver = + RouteProgressObserver { routeProgress -> + // update the camera position to account for the progressed fragment of the route + viewportDataSource.onRouteProgressChanged(routeProgress) + viewportDataSource.evaluate() + + // show arrow on the route line with the next maneuver + val maneuverArrowResult = routeArrowAPI.addUpcomingManeuverArrow(routeProgress) + val style = mapboxMap.getStyle() + if (style != null) { + routeArrowView.renderManeuverUpdate(style, maneuverArrowResult) + } + } + + private val routesObserver = RoutesObserver { result -> + if (result.navigationRoutes.isNotEmpty()) { + // generate route geometries asynchronously and render them + CoroutineScope(Dispatchers.Main).launch { + val result = routeLineAPI.setNavigationRoutes(result.navigationRoutes) + val style = mapboxMap.getStyle() + if (style != null) { + routeLineView.renderRouteDrawData(style, result) + } + } + + // update the camera position to account for the new route + viewportDataSource.onRouteChanged(result.navigationRoutes.first()) + viewportDataSource.evaluate() + } else { + // remove the route line and route arrow from the map + val style = mapboxMap.getStyle() + if (style != null) { + routeLineAPI.clearRouteLine { value -> + routeLineView.renderClearRouteLineValue( + style, + value + ) + } + routeArrowView.render(style, routeArrowAPI.clearArrows()) + } + + // remove the route reference to change camera position + viewportDataSource.clearRouteData() + viewportDataSource.evaluate() + } + } + + @SuppressLint("MissingPermission") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityRouteRefreshBinding.inflate(layoutInflater) + setContentView(binding.root) + mapboxMap = binding.mapView.getMapboxMap() + + // initialize the location puck + binding.mapView.location.apply { + this.locationPuck = LocationPuck2D( + bearingImage = ContextCompat.getDrawable( + this@RouteRefreshActivity, + R.drawable.mapbox_navigation_puck_icon + ) + ) + setLocationProvider(navigationLocationProvider) + enabled = true + } + + // initialize Mapbox Navigation + mapboxNavigation = MapboxNavigationProvider.create( + NavigationOptions.Builder(this) + .accessToken(getMapboxAccessTokenFromResources()) + .locationEngine(ReplayLocationEngine(mapboxReplayer)) + .routeRefreshOptions(RouteRefreshOptions.Builder().intervalMillis(30000).build()) + .build() + ) + mapboxNavigation.registerRouteAlternativesObserver(object : NavigationRouteAlternativesObserver { + override fun onRouteAlternatives( + routeProgress: RouteProgress, + alternatives: List, + routerOrigin: RouterOrigin + ) { + mapboxNavigation.setNavigationRoutes(listOf(routeProgress.navigationRoute) + alternatives) + } + + override fun onRouteAlternativesError(error: RouteAlternativesError) { + TODO("Not yet implemented") + } + + }) + // move the camera to current location on the first update + mapboxNavigation.registerLocationObserver(object : LocationObserver { + override fun onNewRawLocation(rawLocation: Location) { + val point = Point.fromLngLat(rawLocation.longitude, rawLocation.latitude) + val cameraOptions = CameraOptions.Builder() + .center(point) + .zoom(13.0) + .build() + mapboxMap.setCamera(cameraOptions) + mapboxNavigation.unregisterLocationObserver(this) + } + + override fun onNewLocationMatcherResult( + locationMatcherResult: LocationMatcherResult, + ) { + // not handled + } + }) + + // initialize Navigation Camera + viewportDataSource = MapboxNavigationViewportDataSource( + binding.mapView.getMapboxMap() + ) + navigationCamera = NavigationCamera( + binding.mapView.getMapboxMap(), + binding.mapView.camera, + viewportDataSource + ) + binding.mapView.camera.addCameraAnimationsLifecycleListener( + NavigationBasicGesturesHandler(navigationCamera) + ) + navigationCamera.registerNavigationCameraStateChangeObserver { navigationCameraState -> + // shows/hide the recenter button depending on the camera state + when (navigationCameraState) { + NavigationCameraState.TRANSITION_TO_FOLLOWING, + NavigationCameraState.FOLLOWING -> binding.recenter.visibility = INVISIBLE + + NavigationCameraState.TRANSITION_TO_OVERVIEW, + NavigationCameraState.OVERVIEW, + NavigationCameraState.IDLE -> binding.recenter.visibility = VISIBLE + } + } + if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + viewportDataSource.overviewPadding = landscapeOverviewPadding + } else { + viewportDataSource.overviewPadding = overviewPadding + } + if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + viewportDataSource.followingPadding = landscapeFollowingPadding + } else { + viewportDataSource.followingPadding = followingPadding + } + + // initialize route line + val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(this) + .withRouteLineBelowLayerId("road-label") + .withRouteLineResources( + RouteLineResources.Builder() + .routeLineColorResources( + RouteLineColorResources.Builder() + .alternativeRouteDefaultColor(Color.parseColor("#FF0000")) + .alternativeRouteHeavyCongestionColor(Color.parseColor("#00FF00")) + .alternativeRouteLowCongestionColor(Color.parseColor("#0000FF")) + .alternativeRouteModerateCongestionColor(Color.parseColor("#FFFF00")) + .alternativeRouteSevereCongestionColor(Color.parseColor("#FF00FF")) + .alternativeRouteUnknownCongestionColor(Color.parseColor("#00FFFF")) + .build() + ) + .build() + ) + .build() + routeLineAPI = MapboxRouteLineApi(mapboxRouteLineOptions) + routeLineView = MapboxRouteLineView(mapboxRouteLineOptions) + val routeArrowOptions = RouteArrowOptions.Builder(this).build() + routeArrowView = MapboxRouteArrowView(routeArrowOptions) + + // load map style + mapboxMap.loadStyleUri(MAPBOX_STREETS) { style -> + routeLineView.initializeLayers(style) + // add long click listener that search for a route to the clicked destination + binding.mapView.gestures.addOnMapLongClickListener { point -> + findRoute(point) + true + } + } + + // initialize view interactions + binding.recenter.setOnClickListener { + navigationCamera.requestNavigationCameraToFollowing() + } + binding.routeOverview.setOnClickListener { + navigationCamera.requestNavigationCameraToOverview() + binding.recenter.showTextAndExtend(2000L) + } + binding.startNavigation.setOnClickListener { + mapboxNavigation.setNavigationRoutes(previewedRoutes) + coordinates = mutableListOf() + val primaryRoute = previewedRoutes.firstOrNull() + previewedRoutes = mutableListOf() + if (primaryRoute != null) { + mapboxNavigation.registerRouteProgressObserver(routeProgressObserver) + mapboxNavigation.startTripSession() + binding.startNavigation.visibility = INVISIBLE + + navigationCamera.requestNavigationCameraToFollowing() + + startSimulation(primaryRoute.directionsRoute) + } + } + binding.clearAnnotations.setOnClickListener { + val routes = mapboxNavigation.getNavigationRoutes() + val newRoutes = routes.map { + it.directionsRoute + .toBuilder() + .legs(it.directionsRoute.legs()?.map { leg -> + leg.toBuilder() + .annotation( + leg.annotation()?.let { annotation -> + annotation.toBuilder() + .congestion(annotation.congestion()?.map { null }) + .congestionNumeric(annotation.congestionNumeric()?.map { null }) + .build() + } + ) + .build() + }) + .build() + .toNavigationRoute(it.origin) + } + mapboxNavigation.setNavigationRoutes(newRoutes) + } + + // start the trip session to being receiving location updates in free drive + // and later when a route is set, also receiving route progress updates + mapboxNavigation.startTripSession() + } + + override fun onStart() { + super.onStart() + mapboxNavigation.registerRoutesObserver(routesObserver) + mapboxNavigation.registerRouteProgressObserver(routeProgressObserver) + mapboxNavigation.registerRouteProgressObserver(replayProgressObserver) + mapboxNavigation.registerLocationObserver(locationObserver) + + mapboxReplayer.pushRealLocation(this, 0.0) + mapboxReplayer.playbackSpeed(1.5) + mapboxReplayer.play() + } + + override fun onStop() { + super.onStop() + mapboxNavigation.unregisterRoutesObserver(routesObserver) + mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver) + mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver) + mapboxNavigation.unregisterLocationObserver(locationObserver) + } + + override fun onDestroy() { + super.onDestroy() + routeLineAPI.cancel() + routeLineView.cancel() + mapboxNavigation.onDestroy() + } + + private fun findRoute(destination: Point) { + if (coordinates.isEmpty()) { + val origin = navigationLocationProvider.lastLocation?.let { + Point.fromLngLat(it.longitude, it.latitude) + } ?: return + coordinates.add(origin) + } + coordinates.add(destination) + + mapboxNavigation.requestRoutes( + RouteOptions.builder() + .applyDefaultNavigationOptions() + .applyLanguageAndVoiceUnitOptions(this) + .coordinatesList(coordinates) + .alternatives(true) + .build(), + object : NavigationRouterCallback { + override fun onRoutesReady( + routes: List, + routerOrigin: RouterOrigin + ) { + binding.startNavigation.visibility = VISIBLE + previewedRoutes = routes + previewRoutes(routes) + } + + override fun onFailure( + reasons: List, + routeOptions: RouteOptions + ) { + } + + override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) { + } + } + ) + } + + private fun previewRoutes(routes: List) { + // set route + routeLineAPI.setNavigationRoutes(routes) { + val style = mapboxMap.getStyle() + if (style != null) { + routeLineView.renderRouteDrawData(style, it) + } + } + // update the camera position to account for the new route + viewportDataSource.onRouteChanged(routes.first()) + viewportDataSource.evaluate() + + + // show UI elements + binding.routeOverview.visibility = VISIBLE + binding.routeOverview.showTextAndExtend(2000L) + + // move the camera to overview when new route is available + navigationCamera.requestNavigationCameraToOverview() + } + + private fun getMapboxAccessTokenFromResources(): String { + return Utils.getMapboxAccessToken(this) + } + + private fun startSimulation(route: DirectionsRoute) { + mapboxReplayer.stop() + mapboxReplayer.clearEvents() + val replayData: List = replayRouteMapper.mapDirectionsRouteGeometry(route) + mapboxReplayer.pushEvents(replayData) + mapboxReplayer.seekTo(replayData[0]) + mapboxReplayer.play() + } +} diff --git a/examples/src/main/res/layout/activity_route_refresh.xml b/examples/src/main/res/layout/activity_route_refresh.xml new file mode 100644 index 00000000000..aa27f7191b3 --- /dev/null +++ b/examples/src/main/res/layout/activity_route_refresh.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + diff --git a/examples/src/main/res/values/strings.xml b/examples/src/main/res/values/strings.xml index 39135f2d447..26e9242b6c1 100644 --- a/examples/src/main/res/values/strings.xml +++ b/examples/src/main/res/values/strings.xml @@ -36,7 +36,11 @@ BuildingHighlight Example Demonstrates how to highlight and extrude buildings on arrival. + Route Refresh Activity + Lets you play with route refreshes and check various scenarios. + Start Navigation + Clear traffic Play history Select history Replay speed %d From d3ecc8c8d19a1d4e16b31803806f95c962c8fafa Mon Sep 17 00:00:00 2001 From: Dzina Dybouskaya Date: Fri, 20 Jan 2023 14:58:05 +0300 Subject: [PATCH 4/5] NAVAND-1073: meet code review --- examples/src/main/AndroidManifest.xml | 5 -- .../navigation/examples/MainActivity.kt | 6 -- examples/src/main/res/values/strings.xml | 4 -- .../core/RouteRefreshTest.kt | 25 +++++-- .../AlternativeRouteProgressDataProvider.kt | 6 +- ...lternativeRouteProgressDataProviderTest.kt | 26 ++++--- qa-test-app/src/main/AndroidManifest.xml | 1 + .../qa_test_app/domain/TestActivitySuite.kt | 7 ++ .../qa_test_app/view}/RouteRefreshActivity.kt | 72 ++++++++++--------- .../main/res/layout/layout_route_refresh.xml | 5 +- qa-test-app/src/main/res/values/strings.xml | 2 + 11 files changed, 91 insertions(+), 68 deletions(-) rename {examples/src/main/java/com/mapbox/navigation/examples/core => qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view}/RouteRefreshActivity.kt (91%) rename examples/src/main/res/layout/activity_route_refresh.xml => qa-test-app/src/main/res/layout/layout_route_refresh.xml (94%) diff --git a/examples/src/main/AndroidManifest.xml b/examples/src/main/AndroidManifest.xml index 4836f095e08..859373f2e7f 100644 --- a/examples/src/main/AndroidManifest.xml +++ b/examples/src/main/AndroidManifest.xml @@ -95,11 +95,6 @@ android:label="@string/title_multileg_route"> - - - diff --git a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt index a3cdbc05af7..61a60f6ca05 100644 --- a/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt +++ b/examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt @@ -25,7 +25,6 @@ import com.mapbox.navigation.examples.core.MapboxVoiceActivity import com.mapbox.navigation.examples.core.MultiLegRouteExampleActivity import com.mapbox.navigation.examples.core.R import com.mapbox.navigation.examples.core.ReplayHistoryActivity -import com.mapbox.navigation.examples.core.RouteRefreshActivity import com.mapbox.navigation.examples.core.camera.MapboxCameraAnimationsActivity import com.mapbox.navigation.examples.core.databinding.LayoutActivityMainBinding import com.mapbox.navigation.examples.util.LocationPermissionsHelper @@ -141,11 +140,6 @@ class MainActivity : AppCompatActivity(), PermissionsListener { getString(R.string.description_draw_utility), RouteDrawingActivity::class.java ), - SampleItem( - getString(R.string.title_route_refresh), - getString(R.string.description_route_refresh), - RouteRefreshActivity::class.java - ), ) } diff --git a/examples/src/main/res/values/strings.xml b/examples/src/main/res/values/strings.xml index 26e9242b6c1..39135f2d447 100644 --- a/examples/src/main/res/values/strings.xml +++ b/examples/src/main/res/values/strings.xml @@ -36,11 +36,7 @@ BuildingHighlight Example Demonstrates how to highlight and extrude buildings on arrival. - Route Refresh Activity - Lets you play with route refreshes and check various scenarios. - Start Navigation - Clear traffic Play history Select history Replay speed %d diff --git a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt index fff46879d5b..033ef0dd3fc 100644 --- a/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt +++ b/instrumentation-tests/src/androidTest/java/com/mapbox/navigation/instrumentation_tests/core/RouteRefreshTest.kt @@ -556,19 +556,23 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja val alternativeRoutes = mapboxNavigation.requestRoutes(routeOptions) .getSuccessfulResultOrThrowException() .routes - // alternative which was requested on the second leg of the original route, - // so the alternative has only one leg while the original route has two + // In this test setup we are considering a case where user was driving along the route, + // started the second leg and received an alternative, and selected it before the fork. + // This means that the primary route is shorter than the alternative route (former primary route). val primaryRoute = alternativeForMultileg(activity).toNavigationRoutes().first() // corresponds to currentRouteGeometryIndex = 70 for alternative route and 11 for the primary route mockLocationUpdatesRule.pushLocationUpdate( mockLocationUpdatesRule.generateLocationUpdate { - latitude = 38.581798 - longitude = -121.476146 + latitude = 38.581798 + longitude = -121.476146 } ) - mapboxNavigation.setNavigationRoutes(listOf(primaryRoute) + alternativeRoutes, initialLegIndex = 0) + mapboxNavigation.setNavigationRoutes( + listOf(primaryRoute) + alternativeRoutes, + initialLegIndex = 0 + ) mapboxNavigation.startTripSession() mapboxNavigation.routeProgressUpdates() @@ -590,7 +594,11 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja 0.0001 ) - assertEquals(201.673, alternativeRoutes[0].getSumOfDurationAnnotationsFromLeg(1), 0.0001) + assertEquals( + 201.673, + alternativeRoutes[0].getSumOfDurationAnnotationsFromLeg(1), + 0.0001 + ) assertEquals(202.881, refreshedRoutes[1].getSumOfDurationAnnotationsFromLeg(1), 0.0001) assertEquals(194.3, primaryRoute.getSumOfDurationAnnotationsFromLeg(0), 0.0001) @@ -861,7 +869,10 @@ class RouteRefreshTest : BaseTest(EmptyTestActivity::class.ja } private fun alternativeForMultileg(context: Context): MockRoute { - val jsonResponse = readRawFileText(context, R.raw.route_response_single_route_multileg_alternative) + val jsonResponse = readRawFileText( + context, + R.raw.route_response_single_route_multileg_alternative + ) val coordinates = listOf( Point.fromLngLat(38.577427, -121.478077), Point.fromLngLat(38.582195, -121.468458) diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt index 1c70928982c..ab5c4c89dd3 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProvider.kt @@ -4,7 +4,6 @@ import com.mapbox.navigation.base.route.NavigationRoute import com.mapbox.navigation.base.utils.DecodeUtils.stepsGeometryToPoints import com.mapbox.navigation.core.RouteProgressData import com.mapbox.navigation.utils.internal.ThreadController -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext internal object AlternativeRouteProgressDataProvider { @@ -37,7 +36,10 @@ internal object AlternativeRouteProgressDataProvider { return RouteProgressData(legIndex, routeGeometryIndex, legGeometryIndex) } - private suspend fun prevLegsGeometryIndicesCount(route: NavigationRoute, currentLegIndex: Int): Int { + private suspend fun prevLegsGeometryIndicesCount( + route: NavigationRoute, + currentLegIndex: Int + ): Int { return withContext(ThreadController.DefaultDispatcher) { var result = 0 val stepsGeometries by lazy { route.directionsRoute.stepsGeometryToPoints() } diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt index d194f757cf2..662ec375d12 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/routealternatives/AlternativeRouteProgressDataProviderTest.kt @@ -121,7 +121,9 @@ class AlternativeRouteProgressDataProviderTest { listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), listOf(List(23) { mockk() }), listOf(List(32) { mockk() }, List(19) { mockk() }), - listOf(List(14) { mockk() }, List(23) { mockk() }), // 135 points for legs #0-#3 + listOf(List(14) { mockk() }, List(23) { mockk() }), + // 136 points for legs #0-#3 + // (135 if you don't count the current leg's starting point) listOf(List(8) { mockk() }), ) ) @@ -145,12 +147,14 @@ class AlternativeRouteProgressDataProviderTest { primaryForkLegGeometryIndex = 85, alternativeForkLegIndex = 5, alternativeForkRouteGeometryIndex = 220, - alternativeForkLegGeometryIndex = 86, + alternativeForkLegGeometryIndex = 85, listOf( listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), listOf(List(23) { mockk() }), listOf(List(32) { mockk() }, List(19) { mockk() }), - listOf(List(14) { mockk() }, List(23) { mockk() }), // 135 points for legs #0-#3 + listOf(List(14) { mockk() }, List(23) { mockk() }), + // 136 points for legs #0-#3 + // (135 if you don't count the current leg's starting point) listOf(List(8) { mockk() }), ) ) @@ -177,10 +181,10 @@ class AlternativeRouteProgressDataProviderTest { alternativeForkLegGeometryIndex = 81, listOf( listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), - listOf(List(23) { mockk() }), // 135 points for legs #0-#1 + listOf(List(23) { mockk() }), + // 136 points for legs #0-#1 + // (135 if you don't count the current leg's starting point) listOf(List(32) { mockk() }, List(19) { mockk() }), - listOf(List(14) { mockk() }, List(23) { mockk() }), - listOf(List(8) { mockk() }), ) ) val expected = RouteProgressData(2, 170, 119) @@ -196,7 +200,6 @@ class AlternativeRouteProgressDataProviderTest { @Test fun `before fork, alternative starts after the primary route on the current leg`() = coroutineRule.runBlockingTest { - // primary leg and route indices diff is 140 val primaryRouteProgressData = RouteProgressData(4, 200, 60) val alternativeMetadata = alternativeRouteMetadata( primaryForkLegIndex = 4, @@ -204,7 +207,7 @@ class AlternativeRouteProgressDataProviderTest { primaryForkLegGeometryIndex = 110, alternativeForkLegIndex = 0, alternativeForkRouteGeometryIndex = 90, - alternativeForkLegGeometryIndex = 20, + alternativeForkLegGeometryIndex = 90, listOf( listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), listOf(List(23) { mockk() }), @@ -233,14 +236,17 @@ class AlternativeRouteProgressDataProviderTest { primaryForkLegGeometryIndex = 90, alternativeForkLegIndex = 4, alternativeForkRouteGeometryIndex = 250, - alternativeForkLegGeometryIndex = 10, + alternativeForkLegGeometryIndex = 115, listOf( listOf(List(10) { mockk() }, List(7) { mockk() }, List(15) { mockk() }), listOf(List(23) { mockk() }), listOf(List(32) { mockk() }, List(19) { mockk() }), listOf( List(14) { mockk() }, - List(23) { mockk() }), // 135 points for legs #0-#3 + List(23) { mockk() } + ), + // 136 points for legs #0-#3 + // (135 if you don't count the current leg's starting point) listOf(List(8) { mockk() }), ) ) diff --git a/qa-test-app/src/main/AndroidManifest.xml b/qa-test-app/src/main/AndroidManifest.xml index 02fef99538e..024da8e15d4 100644 --- a/qa-test-app/src/main/AndroidManifest.xml +++ b/qa-test-app/src/main/AndroidManifest.xml @@ -77,6 +77,7 @@ + activity.startActivity() }, + TestActivityDescription( + "Route Refresh Example", + R.string.description_route_refresh, + ) { activity -> + activity.startActivity() + }, ) fun getTestActivities(category: String): List { diff --git a/examples/src/main/java/com/mapbox/navigation/examples/core/RouteRefreshActivity.kt b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/RouteRefreshActivity.kt similarity index 91% rename from examples/src/main/java/com/mapbox/navigation/examples/core/RouteRefreshActivity.kt rename to qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/RouteRefreshActivity.kt index e1abb803444..a838186d8d7 100644 --- a/examples/src/main/java/com/mapbox/navigation/examples/core/RouteRefreshActivity.kt +++ b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/RouteRefreshActivity.kt @@ -1,14 +1,14 @@ -package com.mapbox.navigation.examples.core +package com.mapbox.navigation.qa_test_app.view import android.annotation.SuppressLint import android.content.res.Configuration import android.content.res.Resources import android.graphics.Color import android.location.Location -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View.INVISIBLE import android.view.View.VISIBLE +import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.mapbox.api.directions.v5.models.DirectionsRoute import com.mapbox.api.directions.v5.models.RouteOptions @@ -21,7 +21,6 @@ import com.mapbox.maps.plugin.LocationPuck2D import com.mapbox.maps.plugin.animation.camera import com.mapbox.maps.plugin.gestures.gestures import com.mapbox.maps.plugin.locationcomponent.location -import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions import com.mapbox.navigation.base.options.NavigationOptions @@ -45,8 +44,9 @@ import com.mapbox.navigation.core.routealternatives.RouteAlternativesError import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.core.trip.session.LocationObserver import com.mapbox.navigation.core.trip.session.RouteProgressObserver -import com.mapbox.navigation.examples.core.databinding.ActivityRouteRefreshBinding -import com.mapbox.navigation.examples.util.Utils +import com.mapbox.navigation.qa_test_app.R +import com.mapbox.navigation.qa_test_app.databinding.LayoutRouteRefreshBinding +import com.mapbox.navigation.qa_test_app.utils.Utils import com.mapbox.navigation.ui.maps.camera.NavigationCamera import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler @@ -75,11 +75,10 @@ import kotlinx.coroutines.launch * 5. Wait for refresh: the traffic should reappear. * 6. Optionally, remove the traffic and wait for refresh again. */ -@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) class RouteRefreshActivity : AppCompatActivity() { /* ----- Layout binding reference ----- */ - private lateinit var binding: ActivityRouteRefreshBinding + private lateinit var binding: LayoutRouteRefreshBinding /* ----- Mapbox Maps components ----- */ private lateinit var mapboxMap: MapboxMap @@ -208,7 +207,7 @@ class RouteRefreshActivity : AppCompatActivity() { @SuppressLint("MissingPermission") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityRouteRefreshBinding.inflate(layoutInflater) + binding = LayoutRouteRefreshBinding.inflate(layoutInflater) setContentView(binding.root) mapboxMap = binding.mapView.getMapboxMap() @@ -232,20 +231,24 @@ class RouteRefreshActivity : AppCompatActivity() { .routeRefreshOptions(RouteRefreshOptions.Builder().intervalMillis(30000).build()) .build() ) - mapboxNavigation.registerRouteAlternativesObserver(object : NavigationRouteAlternativesObserver { - override fun onRouteAlternatives( - routeProgress: RouteProgress, - alternatives: List, - routerOrigin: RouterOrigin - ) { - mapboxNavigation.setNavigationRoutes(listOf(routeProgress.navigationRoute) + alternatives) - } + mapboxNavigation.registerRouteAlternativesObserver( + object : + NavigationRouteAlternativesObserver { + override fun onRouteAlternatives( + routeProgress: RouteProgress, + alternatives: List, + routerOrigin: RouterOrigin + ) { + mapboxNavigation.setNavigationRoutes( + listOf(routeProgress.navigationRoute) + alternatives + ) + } - override fun onRouteAlternativesError(error: RouteAlternativesError) { - TODO("Not yet implemented") + override fun onRouteAlternativesError(error: RouteAlternativesError) { + // no-op + } } - - }) + ) // move the camera to current location on the first update mapboxNavigation.registerLocationObserver(object : LocationObserver { override fun onNewRawLocation(rawLocation: Location) { @@ -360,18 +363,22 @@ class RouteRefreshActivity : AppCompatActivity() { val newRoutes = routes.map { it.directionsRoute .toBuilder() - .legs(it.directionsRoute.legs()?.map { leg -> - leg.toBuilder() - .annotation( - leg.annotation()?.let { annotation -> - annotation.toBuilder() - .congestion(annotation.congestion()?.map { null }) - .congestionNumeric(annotation.congestionNumeric()?.map { null }) - .build() - } - ) - .build() - }) + .legs( + it.directionsRoute.legs()?.map { leg -> + leg.toBuilder() + .annotation( + leg.annotation()?.let { annotation -> + annotation.toBuilder() + .congestion(annotation.congestion()?.map { "unknown" }) + .congestionNumeric( + annotation.congestionNumeric()?.map { null } + ) + .build() + } + ) + .build() + } + ) .build() .toNavigationRoute(it.origin) } @@ -460,7 +467,6 @@ class RouteRefreshActivity : AppCompatActivity() { viewportDataSource.onRouteChanged(routes.first()) viewportDataSource.evaluate() - // show UI elements binding.routeOverview.visibility = VISIBLE binding.routeOverview.showTextAndExtend(2000L) diff --git a/examples/src/main/res/layout/activity_route_refresh.xml b/qa-test-app/src/main/res/layout/layout_route_refresh.xml similarity index 94% rename from examples/src/main/res/layout/activity_route_refresh.xml rename to qa-test-app/src/main/res/layout/layout_route_refresh.xml index aa27f7191b3..42be1c084b9 100644 --- a/examples/src/main/res/layout/activity_route_refresh.xml +++ b/qa-test-app/src/main/res/layout/layout_route_refresh.xml @@ -1,8 +1,11 @@ + android:layout_height="match_parent" + tools:context=".view.RouteRefreshActivity" + > Screen for viewing SDK buttons. Example of how to use Mapbox SpeedInfo View Demonstrates the use of Rest Area API + Lets you play with route refreshes and check various scenarios + Clear traffic -- From 71bd99c3dda27f6ab272068b8a5a36f6e87e86d5 Mon Sep 17 00:00:00 2001 From: runner Date: Fri, 20 Jan 2023 14:37:27 +0000 Subject: [PATCH 5/5] Rename changelog files --- changelog/unreleased/bugfixes/{dd.md => 6857.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog/unreleased/bugfixes/{dd.md => 6857.md} (100%) diff --git a/changelog/unreleased/bugfixes/dd.md b/changelog/unreleased/bugfixes/6857.md similarity index 100% rename from changelog/unreleased/bugfixes/dd.md rename to changelog/unreleased/bugfixes/6857.md