Skip to content

Commit

Permalink
Added new experimental API for requesting a route
Browse files Browse the repository at this point in the history
  • Loading branch information
VysotskiVadim committed Mar 16, 2022
1 parent f39ba23 commit f371271
Show file tree
Hide file tree
Showing 15 changed files with 1,261 additions and 50 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ This release depends on, and has been tested with, the following Mapbox dependen
## Mapbox Navigation SDK 2.4.0-alpha.1 - February 24, 2022
### Changelog
[Changes between v2.3.0-rc.3 and v2.4.0-alpha.1](https://github.com/mapbox/mapbox-navigation-android/compare/v2.3.0-rc.3...v2.4.0-alpha.1)
- Add `MapboxNavigationApp.isSetup` to ensure views do not reset `MapboxNavigation`. Add `MapboxNavigationApp.getObserver` to be able to access registered observers. [#5358](https://github.com/mapbox/mapbox-navigation-android/pull/5358)
- Added `MapboxNavigation:buildRouteOptions` that is an experimentation API to safely request a route. [#5427](https://github.com/mapbox/mapbox-navigation-android/pull/5427)

#### Features
- Introduced a `NavigationRoute` object and related functions like `NavigationRouterCallback`, `MapboxNavigation#setNavigationRoutes`, etc. `NavigationRoute` is a domain specific wrapper on top of `DirectionsRoute` which provides information (and enforces its presence) about the original `DirectionsResponse` that this route is part of, as well as what `RouteOptions` were used to generate it.
Expand Down
1 change: 1 addition & 0 deletions examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ dependencies {

//Coroutines
implementation dependenciesList.coroutinesAndroid
implementation dependenciesList.androidXLifecycleRuntime

// Support libraries
implementation dependenciesList.androidXCore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.view.View.VISIBLE
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.bindgen.Expected
Expand All @@ -22,9 +23,8 @@ 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.TimeFormat
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
import com.mapbox.navigation.base.formatter.DistanceFormatterOptions
import com.mapbox.navigation.base.options.EventsAppMetadata
import com.mapbox.navigation.base.options.NavigationOptions
Expand Down Expand Up @@ -459,38 +459,40 @@ class MapboxNavigationActivity : AppCompatActivity() {
voiceInstructionsPlayer.shutdown()
}

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
private fun findRoute(destination: Point) {
val origin = navigationLocationProvider.lastLocation?.let {
Point.fromLngLat(it.longitude, it.latitude)
} ?: return

mapboxNavigation.requestRoutes(
RouteOptions.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(this)
.coordinatesList(listOf(origin, destination))
.layersList(listOf(mapboxNavigation.getZLevel(), null))
.build(),
object : RouterCallback {
override fun onRoutesReady(
routes: List<DirectionsRoute>,
routerOrigin: RouterOrigin
) {
setRouteAndStartNavigation(routes.first())
}

override fun onFailure(
reasons: List<RouterFailure>,
routeOptions: RouteOptions
) {
// no impl
}

override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
// no impl
}
lifecycleScope.launchWhenCreated {
val routeOptions = mapboxNavigation.buildRouteOptions { builder ->
builder
.fromCurrentLocation()
.toDestination(destination)
}
)
mapboxNavigation.requestRoutes(
routeOptions,
object : RouterCallback {
override fun onRoutesReady(
routes: List<DirectionsRoute>,
routerOrigin: RouterOrigin
) {
setRouteAndStartNavigation(routes.first())
}

override fun onFailure(
reasons: List<RouterFailure>,
routeOptions: RouteOptions
) {
// no impl
}

override fun onCanceled(
routeOptions: RouteOptions,
routerOrigin: RouterOrigin
) {
// no impl
}
}
)
}
}

private fun setRouteAndStartNavigation(route: DirectionsRoute) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.mapbox.navigation.instrumentation_tests.core

import android.location.Location
import androidx.test.platform.app.InstrumentationRegistry
import com.mapbox.api.directions.v5.models.RouteOptions
import com.mapbox.geojson.Point
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.core.MapboxNavigationProvider
import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity
import com.mapbox.navigation.instrumentation_tests.utils.MapboxNavigationRule
import com.mapbox.navigation.instrumentation_tests.utils.runOnMainSync
import com.mapbox.navigation.testing.ui.BaseTest
import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class RouteOptionsBuilderTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.java) {

@get:Rule
val mapboxNavigationRule = MapboxNavigationRule()

@Test
fun navigateFromCurrentLocation() {
val mapboxNavigation = runOnMainSync {
val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
MapboxNavigationProvider.create(
NavigationOptions.Builder(context)
.accessToken(getMapboxAccessTokenFromResources(context))
.build()
)
}
val routeOptions: RouteOptions = runBlocking(Dispatchers.Main) {
mapboxNavigation.buildRouteOptions { builder ->
builder
.fromCurrentLocation()
.toDestination(
coordinate = Point.fromLngLat(2.0, 2.0)
)
}
}

assertEquals(
listOf(
Point.fromLngLat(1.0, 1.0),
Point.fromLngLat(2.0, 2.0),
),
routeOptions.coordinatesList()
)
}

override fun setupMockLocation(): Location {
return mockLocationUpdatesRule.generateLocationUpdate {
longitude = 1.0
latitude = 1.0
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ fun runOnMainSync(runnable: Runnable) =
/**
* Runs the code block on the app's main thread and blocks until the block returns.
*/
fun runOnMainSync(fn: () -> Unit) =
InstrumentationRegistry.getInstrumentation().runOnMainSync(fn)
fun <T> runOnMainSync(fn: () -> T): T {
var result: T? = null
InstrumentationRegistry.getInstrumentation().runOnMainSync {
result = fn()
}
return result ?: error("got no result")
}

fun Int.loopFor(millis: Long) {
Espresso.onView(ViewMatchers.withId(this)).perform(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ import com.mapbox.navigation.core.routealternatives.RouteAlternativesError
import com.mapbox.navigation.core.routealternatives.RouteAlternativesObserver
import com.mapbox.navigation.core.routealternatives.RouteAlternativesRequestCallback
import com.mapbox.navigation.core.routeoptions.RouteOptionsUpdater
import com.mapbox.navigation.core.routeoptions.builder.LocationFromTripSessionProvider
import com.mapbox.navigation.core.routeoptions.builder.NavRouteOptionsBuilder
import com.mapbox.navigation.core.routeoptions.builder.NoWaypointsOptionsBuilder
import com.mapbox.navigation.core.routeoptions.builder.RouteOptionsBuilderWithWaypoints
import com.mapbox.navigation.core.routerefresh.RouteRefreshController
import com.mapbox.navigation.core.routerefresh.RouteRefreshControllerProvider
import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry
Expand Down Expand Up @@ -705,6 +709,17 @@ class MapboxNavigation @VisibleForTesting internal constructor(
return directionsSession.requestRoutes(routeOptions, callback)
}

@ExperimentalPreviewMapboxNavigationAPI
suspend fun buildRouteOptions(
optionsBlock: (NoWaypointsOptionsBuilder) -> RouteOptionsBuilderWithWaypoints
): RouteOptions {
tripSession.start(false)
val builder = NavRouteOptionsBuilder(LocationFromTripSessionProvider(tripSession))
optionsBlock(builder)
builder.applyLanguageAndVoiceUnitOptions(navigationOptions.applicationContext)
return builder.build()
}

/**
* Cancels a specific route request using the ID returned by [requestRoutes].
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.mapbox.navigation.core.routeoptions.builder

import android.location.Location
import com.mapbox.geojson.Point
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
import com.mapbox.navigation.core.trip.session.LocationObserver
import com.mapbox.navigation.core.trip.session.TripSessionLocationProvider
import com.mapbox.navigation.utils.internal.toPoint
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.yield
import kotlin.coroutines.resume

internal interface LocationProvider {
suspend fun getCurrentLocation(): CurrentLocation
}

internal data class CurrentLocation(
val point: Point,
val bearing: Double?,
val zLevel: Int?
)

internal class LocationFromTripSessionProvider(
private val tripSessionLocationProvider: TripSessionLocationProvider
) : LocationProvider {
override suspend fun getCurrentLocation(): CurrentLocation {
val currentLocation = tripSessionLocationProvider.locationMatcherResult
return currentLocation?.toCurrentLocation() ?: waitForTheFirstLocationEvent()
}

private suspend fun waitForTheFirstLocationEvent(): CurrentLocation {
val (result, cleanup) = suspendCancellableCoroutine<Pair<CurrentLocation, () -> Unit>>
{ continuation ->
val observer = object : LocationObserver {
override fun onNewRawLocation(rawLocation: Location) {
}

override fun onNewLocationMatcherResult(
locationMatcherResult: LocationMatcherResult
) {
continuation.resume(
Pair(
locationMatcherResult.toCurrentLocation(),
{
tripSessionLocationProvider.unregisterLocationObserver(this)
}
)
)
}
}
tripSessionLocationProvider.registerLocationObserver(observer)
continuation.invokeOnCancellation {
tripSessionLocationProvider.unregisterLocationObserver(observer)
}
}
yield() // you can't remove listener in the listener's callback
cleanup()
return result
}

private fun LocationMatcherResult.toCurrentLocation() = CurrentLocation(
point = enhancedLocation.toPoint(),
bearing = enhancedLocation.bearing.toDouble(),
zLevel = zLevel
)
}
Loading

0 comments on commit f371271

Please sign in to comment.