Route progress

Once turn-by-turn navigation begins, your project will need to follow the user's progress along the route to deliver contextual information in the correct way at the correct time. The Navigation SDK's RouteProgress object contains this information and this page teaches you about using it.

Levels of route progress

The Mapbox Navigation SDK's model for tracking route progress has three pieces with different levels of granularity: the route, the leg, and the step.

Route: The blue line is a route. A route stretches between the origin and the final destination.

Leg: The larger circles with a pink stroke represent waypoints, or stops, along the route. A leg is the part of the route between two waypoints.

Step: The smaller circles with a green stroke represent maneuvers. A step is the part of the leg between two maneuvers.

The Navigation SDK uses three classes to communicate information on the user's progress at these three different levels: RouteProgress, RouteLegProgress, and RouteStepProgress.

RouteProgress

The RouteProgress class contains all the progress information at any time during a navigation session. A new RouteProgress object is generated whenever there's a new valid Location update or if no new Location update is received in the past second.

RouteProgress information includes distance measurements, the percentage of the route that's been completed, the current step index, the remaining number of route legs, and much more. For further information on data encapsulated in RouteProgress, you can view the API reference docs here.

RouteLegProgress

The RouteLegProgress class is specific to the current leg the user is on. For further information on data encapsulated in RouteProgress, you can view the API reference docs here.

RouteStepProgress

The RouteStepProgress class is a progress object specific to the current step the user is on. For further information on data encapsulated in RouteProgress, you can view the API reference docs here.

Route progress and UI components

Several of the Navigation SDK's default UI components use route progress data to present guidance information to your users. Here's how each component uses route progress data:

  • Maneuvers: Invoke the Maneuver API when route progress changes to receive updated banner instruction data any time the route progress changes. Read more in the Maneuver instructions guide.
  • Trip progress: Invoke the Trip Progress API when route progress changes to keep the estimated arrival time, distance remaining, and time remaining up to date as the user progresses along the route. Read more in the Trip progress guide.
  • Route line: Invoke the Route Line API to render a route line on a map.
  • Route arrow: Invoke the Route Arrow API when route progress changes to determine when to show an arrow on the route line.
  • Buildings: Invoke the Buildings API when route progress changes to highlight the building upon arrival at a waypoint or final destination.
  • Notifications: Invoke the MapboxNavigation#updateNotification when route progress changes to receive updated maneuver instructions when the app is running in background. Read more in the Notifications guide.

Beyond using route progress to power the UI components provided by the SDK, you can customize how your application behaves as the user progresses along the route by listening to progress change, determining the route progress state, and implementing custom code.

Listen to progress change

Tracking a user's progress along a route is key to providing helpful and prompt navigation instructions. Implement the Navigation SDK's RouteProgressObserver interface to receive a RouteProgress object every time the user's location changes.

The RouteProgressObserver can typically be used to refresh most of your application's user interface when a change occurs. For example, if you're displaying the user's current progress until the user needs to do the next maneuver. Every time this interface's onRouteProgressChanged() method fires, you can update your view with the new information inside the RouteProgress object.

private val routeProgressObserver = object : RouteProgressObserver {
  override fun onRouteProgressChanged(routeProgress: RouteProgress) {

  }
}

If you've created your own RouteProgressObserver object, you'll need to:

  1. Register the RouteProgressObserver with your already-instantiated MapboxNavigation object.
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
  1. Don’t forget to unregister the observer with mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver). This line isn't needed if you're already running MapboxNavigation's onDestroy() method], which automatically unregisters the observer for you.
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)

RouteProgressState

The Navigation SDK's RouteProgressState enum contains various states that can occur while navigating. Using RouteProgressState helps you gain a better of understanding of what's happening in the overall navigation experience.

The RouteProgressObserver returns a RouteProgress object. Check the current state with the RouteProgressState object inside the returned RouteProgress object:

private val routeProgressObserver = object : RouteProgressObserver {
  override fun onRouteProgressChanged(routeProgress: RouteProgress) {
    routeProgress.currentState()?.let { currentState ->
      val state = currentState
    }
  }
}

There are five possible states: INITIALIZED, TRACKING, COMPLETE, OFF_ROUTE, and UNCERTAIN.

Initialized

When a new route is first loaded and the DirectionsRoute JSON is valid, route-following will start in the RouteProgressState.INITIALIZED state. From there, route-following will try to gain confidence that the Location objects being passed to the device are the user's location. To show this trust, at least a few location updates need to be delivered and they must be consecutively coherent in both time and space. While it is in the process of establishing this trust, the route-following logic will report that it's still in the RouteProgressState.INITIALIZED state.

Tracking

Once MapboxNavigation is confidently tracking the Location updates and processing them against the DirectionsRoute. The state will change to RouteProgressState.TRACKING.

Complete

When the user has arrived at the destination of the given RouteLeg, the state will be RouteProgressState.COMPLETE.

Off Route

The RouteProgressState.OFF_ROUTE occurs when the navigation system determines the user has deviated from the established route.

Uncertain

The RouteProgressState.UNCERTAIN state occurs when the Navigation SDK has already been tracking location updates (RouteProgressState.TRACKING), but the route-snapping algorithm has temporarily lost confidence. For example, due to a re-route when the device strays off the turn-by-turn directions route. After a couple of Location updates after the re-route, RouteProgressState would return back to RouteProgressState.TRACKING.

Visualizing the route progress state

The route progress state can be communicated to users by updating the style of the puck during navigation. In your RouteProgressObserver check the state and update the drawable used for the puck. You can design your own custom drawables and use them as shown below.

private val routeProgressObserver = object : RouteProgressObserver {
  override fun onRouteProgressChanged(routeProgress: RouteProgress) {
    routeProgress.currentState.let { currentState ->
      val puckDrawable = when (currentState) {
        RouteProgressState.INITIALIZED -> R.drawable.your_drawable_for_initialized_state
        RouteProgressState.TRACKING -> R.drawable.your_drawable_for_tracking_state
        RouteProgressState.COMPLETE -> R.drawable.your_drawable_for_complete_state
        RouteProgressState.OFF_ROUTE -> R.drawable.your_drawable_for_off_route_state
        RouteProgressState.UNCERTAIN -> R.drawable.your_drawable_for_uncertain_state
        else -> R.drawable.your_drawable_for_uncertain_state
      }
      mapView.location.apply {
        this.locationPuck = LocationPuck2D(
            bearingImage = ContextCompat.getDrawable(context, puckDrawable)
        )
      }
    }  
  }
}