Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import com.mapbox.mapboxsdk.maps.MapboxMap
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback
import com.mapbox.mapboxsdk.maps.Style
import com.mapbox.navigation.base.internal.extensions.applyDefaultParams
import com.mapbox.navigation.base.trip.model.RouteLegProgress
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.replay.route.ReplayRouteLocationEngine
import com.mapbox.navigation.core.stops.ArrivalController
import com.mapbox.navigation.core.stops.ArrivalOptions
import com.mapbox.navigation.examples.R
import com.mapbox.navigation.examples.utils.Utils
import com.mapbox.navigation.examples.utils.extensions.toPoint
Expand Down Expand Up @@ -109,7 +112,7 @@ class MultipleStopsActivity : AppCompatActivity(), OnMapReadyCallback {
)
}

fun initLocationEngine() {
private fun initLocationEngine() {
val requestLocationUpdateRequest =
LocationEngineRequest.Builder(DEFAULT_INTERVAL_IN_MILLISECONDS)
.setPriority(LocationEngineRequest.PRIORITY_NO_POWER)
Expand Down Expand Up @@ -149,6 +152,7 @@ class MultipleStopsActivity : AppCompatActivity(), OnMapReadyCallback {

@SuppressLint("MissingPermission")
fun initListeners() {
mapboxNavigation?.attachArrivalController(arrivalObserver)
startNavigation.setOnClickListener {
navigationMapboxMap?.updateCameraTrackingMode(NavigationCamera.NAVIGATION_TRACKING_MODE_GPS)
navigationMapboxMap?.updateLocationLayerRenderMode(RenderMode.GPS)
Expand All @@ -161,6 +165,20 @@ class MultipleStopsActivity : AppCompatActivity(), OnMapReadyCallback {
}
}

private val arrivalObserver = object : ArrivalController {
val arrivalOptions = ArrivalOptions.Builder()
.arriveInSeconds(60.0)
.build()
override fun arrivalOptions(): ArrivalOptions = arrivalOptions

override fun onStopArrival(routeLegProgress: RouteLegProgress): Boolean {
// This example shows you can use both time and distance.
// Move to the next step when the distance is small
Timber.i("arrival_debug legIndex=${routeLegProgress.legIndex()} distanceRemaining=${routeLegProgress.distanceRemaining()}")
return routeLegProgress.distanceRemaining() < 5.0
Comment thread
kmadsen marked this conversation as resolved.
}
}

override fun onStart() {
super.onStart()
mapView.onStart()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ import com.mapbox.navigation.core.directions.session.RoutesRequestCallback
import com.mapbox.navigation.core.fasterroute.FasterRouteController
import com.mapbox.navigation.core.fasterroute.FasterRouteObserver
import com.mapbox.navigation.core.routerefresh.RouteRefreshController
import com.mapbox.navigation.core.stops.ArrivalController
import com.mapbox.navigation.core.stops.ArrivalProgressObserver
import com.mapbox.navigation.core.stops.AutoArrivalController
import com.mapbox.navigation.core.telemetry.MapboxNavigationTelemetry
import com.mapbox.navigation.core.telemetry.events.FeedbackEvent
import com.mapbox.navigation.core.trip.service.TripService
Expand Down Expand Up @@ -141,6 +144,7 @@ constructor(
private val internalOffRouteObserver = createInternalOffRouteObserver()
private val fasterRouteController: FasterRouteController
private val routeRefreshController: RouteRefreshController
private val arrivalProgressObserver: ArrivalProgressObserver

private var notificationChannelField: Field? = null
private val MAPBOX_NAVIGATION_NOTIFICATION_PACKAGE_NAME =
Expand Down Expand Up @@ -208,6 +212,9 @@ constructor(
fasterRouteController = FasterRouteController(directionsSession, tripSession, logger)
routeRefreshController = RouteRefreshController(directionsSession, tripSession, logger)
routeRefreshController.start()

arrivalProgressObserver = ArrivalProgressObserver(tripSession)
attachArrivalController()
}

/**
Expand Down Expand Up @@ -449,6 +456,37 @@ constructor(
tripSession.unregisterStateObserver(tripSessionStateObserver)
}

/**
* Attach your own controller to determine when drivers arrived at stops via [ArrivalController]
* Use [navigateNextRouteLeg] to manually move navigator to the next stop. To reset to the
* automatic arrival controller, call attachArrivalController()
*
* @param arrivalController ArrivalObserver
*/
@JvmOverloads fun attachArrivalController(arrivalController: ArrivalController = AutoArrivalController()) {
arrivalProgressObserver.attach(arrivalController)
tripSession.registerRouteProgressObserver(arrivalProgressObserver)
}

/**
* Disable arrival at stops completely. Use this if you want to write your
* own mechanism for handling arrival at stops.
*/
fun removeArrivalController() {
Comment thread
kmadsen marked this conversation as resolved.
tripSession.unregisterRouteProgressObserver(arrivalProgressObserver)
}

/**
* After arriving at a stop, this can be used to manually decide when to start
* navigating to the next stop. Use the [ArrivalController] to control when to
* call navigateNextRouteLeg.
*
* @return true if navigation to next stop could be started, false otherwise
*/
fun navigateNextRouteLeg(): Boolean {
return arrivalProgressObserver.navigateNextRouteLeg()
}

/**
* Start observing faster routes for a trip session via [FasterRouteObserver]
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.mapbox.navigation.core.stops

import com.mapbox.navigation.base.trip.model.RouteLegProgress
import com.mapbox.navigation.base.trip.model.RouteProgress
import com.mapbox.navigation.base.trip.model.RouteProgressState
import com.mapbox.navigation.core.MapboxNavigation

/**
* When navigating to points of interest, you may want to control the arrival
* experience. This interface gives you options to change arrival via [MapboxNavigation.attachArrivalController]
*/
interface ArrivalController {

/**
* Override the options for your arrival callback.
*/
fun arrivalOptions(): ArrivalOptions

/**
* Based on your [ArrivalOptions], this will be called as the next stop is approached.
* To manually navigate the next leg, return false and call [MapboxNavigation.navigateNextRouteLeg]
*
* @return true to automatically move to the next step, false to do it manually
*/
fun onStopArrival(routeLegProgress: RouteLegProgress): Boolean

/**
* Once the [RouteProgress.currentState] has reached [RouteProgressState.ROUTE_ARRIVED]
* for the last stop, this will be called once.
*/
fun onRouteArrival(routeProgress: RouteProgress) {}
}

/**
* The default controller for arrival. This will move onto the next leg automatically
* if there is one.
*/
class AutoArrivalController : ArrivalController {

/**
* Default arrival options
*/
override fun arrivalOptions(): ArrivalOptions = ArrivalOptions.Builder().build()

/**
* By default this will move onto the next step.
*/
override fun onStopArrival(routeLegProgress: RouteLegProgress): Boolean {
return true
}
}

/**
* Choose when to be notified of arrival.
*/
data class ArrivalOptions(

/**
* While the next stop is less than [arrivalInSeconds] away,
* [ArrivalController.onStopArrival] will be called
*/
val arrivalInSeconds: Double?,

/**
* While the next stop is less than [arrivalInMeters] away,
* [ArrivalController.onStopArrival] will be called
*/
val arrivalInMeters: Double?
) {
/**
* Build your arrival options.
*/
class Builder {

private var arrivalInSeconds: Double? = 5.0
private var arrivalInMeters: Double? = 40.0

/**
* (Recommended) Use time estimation for arrival, arrival is influenced by traffic conditions.
* Arrive when the estimated time to a stop is less than or equal to this threshold
*/
fun arriveInSeconds(arriveInSeconds: Double?): Builder {
this.arrivalInSeconds = arriveInSeconds
return this
}

/**
* Arrive when the estimated distance to a stop is less than or equal to this threshold
*/
fun arriveInMeters(arriveInMeters: Double?): Builder {
this.arrivalInMeters = arriveInMeters
return this
}

/**
* Build the object. If you want to disable this feature use [MapboxNavigation.removeArrivalController]
*/
fun build(): ArrivalOptions {
check(arrivalInSeconds != null || arrivalInSeconds != null) {
"Choose a method to be notified of arrival, time and/or distance."
}
return ArrivalOptions(
arrivalInSeconds = arrivalInSeconds,
arrivalInMeters = arrivalInMeters
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.mapbox.navigation.core.stops

import com.mapbox.navigation.base.trip.RouteProgressObserver
import com.mapbox.navigation.base.trip.model.RouteLegProgress
import com.mapbox.navigation.base.trip.model.RouteProgress
import com.mapbox.navigation.base.trip.model.RouteProgressState
import com.mapbox.navigation.core.trip.session.TripSession

internal class ArrivalProgressObserver(
private val tripSession: TripSession
) : RouteProgressObserver {

private var arrivalController: ArrivalController = AutoArrivalController()
private var arrivedForRoute = false

fun attach(arrivalController: ArrivalController) {
this.arrivalController = arrivalController
}

fun navigateNextRouteLeg(): Boolean {
val numberOfLegs = tripSession.getRouteProgress()?.route()?.legs()?.size
?: return false
val legIndex = tripSession.getRouteProgress()?.currentLegProgress()?.legIndex()
?: return false
val nextLegIndex = legIndex + 1
return if (nextLegIndex < numberOfLegs) {
val navigationStatus = tripSession.updateLegIndex(nextLegIndex)
return nextLegIndex == navigationStatus.legIndex
} else {
true
}
}

override fun onRouteProgressChanged(routeProgress: RouteProgress) {
val routeLegProgress = routeProgress.currentLegProgress()
?: return

val arrivalOptions = arrivalController.arrivalOptions()
if (routeProgress.currentState() == RouteProgressState.ROUTE_ARRIVED && !hasMoreLegs(routeProgress)) {
doOnRouteArrival(routeProgress)
} else if (arrivalOptions.arrivalInSeconds != null) {
checkStopArrivalTime(arrivalOptions.arrivalInSeconds, routeLegProgress)
} else if (arrivalOptions.arrivalInMeters != null) {
checkStopArrivalDistance(arrivalOptions.arrivalInMeters, routeLegProgress)
}
arrivedForRoute = (routeProgress.currentState() ?: RouteProgressState.ROUTE_UNCERTAIN) == RouteProgressState.ROUTE_ARRIVED
}

private fun hasMoreLegs(routeProgress: RouteProgress): Boolean {
val currentLegIndex = routeProgress.currentLegProgress()?.legIndex()
val lastLegIndex = routeProgress.route()?.legs()?.lastIndex
return (currentLegIndex != null && lastLegIndex != null) && currentLegIndex < lastLegIndex
}

private fun checkStopArrivalTime(arrivalInSeconds: Double, routeLegProgress: RouteLegProgress) {
if (routeLegProgress.durationRemaining() <= arrivalInSeconds) {
Comment thread
kmadsen marked this conversation as resolved.
doOnStopArrival(routeLegProgress)
}
}

private fun checkStopArrivalDistance(arrivalInMeters: Double, routeLegProgress: RouteLegProgress) {
if (routeLegProgress.distanceRemaining() <= arrivalInMeters) {
Comment thread
kmadsen marked this conversation as resolved.
doOnStopArrival(routeLegProgress)
}
}

private fun doOnStopArrival(routeLegProgress: RouteLegProgress) {
val moveToNextLeg = arrivalController.onStopArrival(routeLegProgress)
if (moveToNextLeg) {
navigateNextRouteLeg()
}
Comment thread
kmadsen marked this conversation as resolved.
}

private fun doOnRouteArrival(routeProgress: RouteProgress) {
if (!arrivedForRoute) {
arrivalController.onRouteArrival(routeProgress)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.mapbox.navigation.navigator.internal.TripStatus
import com.mapbox.navigation.utils.internal.JobControl
import com.mapbox.navigation.utils.internal.ThreadController
import com.mapbox.navigation.utils.internal.ifNonNull
import com.mapbox.navigator.NavigationStatus
import java.util.Date
import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -341,6 +342,19 @@ class MapboxTripSession(
}
}

/**
* Follows a new leg of the already loaded directions.
* Returns an initialized navigation status if no errors occurred
* otherwise, it returns an invalid navigation status state.
*
* @param legIndex new leg index
*
* @return an initialized [NavigationStatus] if no errors, invalid otherwise
*/
override fun updateLegIndex(legIndex: Int): NavigationStatus {
return navigator.updateLegIndex(legIndex)
}

/**
* Updates the configuration to enable or disable the extended kalman filter (EKF).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.navigation.base.trip.RouteProgressObserver
import com.mapbox.navigation.base.trip.model.RouteProgress
import com.mapbox.navigation.core.trip.service.TripService
import com.mapbox.navigator.NavigationStatus

internal interface TripSession {

Expand Down Expand Up @@ -50,4 +51,5 @@ internal interface TripSession {
fun updateSensorEvent(sensorEvent: SensorEvent)

fun useExtendedKalmanFilter(useEKF: Boolean)
fun updateLegIndex(legIndex: Int): NavigationStatus
}
Loading