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.
The Navigation UI SDK consumes the RouteProgress
object to display the user's progress along the route across several different UI components, including:
- Display route information like total duration and distance remaining in the bottom bar.
- Display information about the next step in the instructions banner.
- Animate the progress bar.
- Show error messages when appropriate.
- Display the arrival screen.
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.
RouteLegProgress
The RouteLegProgress
class is specific to the current leg the user is on. If there is only one leg in the directions route, much of this information will be the same as in the parent RouteProgress
.
RouteStepProgress
The RouteStepProgress
class is a progress object specific to the current step the user is on.
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 RouteProgressObserver routeProgressObserver = new RouteProgressObserver() {
@Override
public void onRouteProgressChanged(RouteProgress routeProgress) {
}
};
private val routeProgressObserver = object : RouteProgressObserver {
override fun onRouteProgressChanged(routeProgress: RouteProgress) {
}
}
By default, the Navigation UI SDK automatically uses the information in the RouteProgress
object to update various UI components as the device moves along the route.
If you want to use a custom RouteProgressObserver
object with NavigationView
, pass the RouteProgressObserver
object to the NavigationViewOptions.builder()
:
NavigationViewOptions.Builder optionsBuilder = NavigationViewOptions.builder();
optionsBuilder.routeProgressObserver(routeProgressObserver);
navigationView.startNavigation(optionsBuilder.build());
val optionsBuilder = NavigationViewOptions.builder()
optionsBuilder.routeProgressObserver(routeProgressObserver)
navigationView.startNavigation(optionsBuilder.build())
If you've created your own RouteProgressObserver
object, you'll need to:
- Register the
RouteProgressObserver
with your already-instantiatedMapboxNavigation
object.
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver);
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
- Don’t forget to unregister the observer with
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
. This line isn't needed if you're already runningMapboxNavigation
'sonDestroy()
method, which automatically unregisters the observer for you.
@Override
public void onStop() {
super.onStop();
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver);
}
override fun onStop() {
super.onStop()
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
}
Show Route Progress
The Navigation UI SDK's NavigationMapRoute
class displays a route line on the map. You can style the route by passing in a style resource when first constructing an instance of this class. There is also an option to alter the appearance of the route line based on the user's current progress along the route, specifically whether to display the section of the route line the user has already completed (the route line behind the user's current location).
If you are using NavigationMapRoute
, this behavior is controlled by the vanishRouteLineEnabled
parameter. By default, the value is false
and the route line will not vanish behind the puck during navigation. You can make the route line vanish behind the puck by setting the vanishRouteLineEnabled
parameter to true
and providing a reference to MapboxNavigation
:
@Override
MapboxNavigation mapboxNavigation = new MapboxNavigation(...);
NavigationMapRoute navigationMapRoute = new NavigationMapRoute.Builder(...)
.withMapboxNavigation(mapboxNavigation)
.withVanishRouteLineEnabled(true)
.build();
val mapboxNavigation = MapboxNavigation(...)
val navigationMapRoute = NavigationMapRoute.Builder(...)
.withMapboxNavigation(mapboxNavigation)
.withVanishRouteLineEnabled(true)
.build()
If you are using NavigationView
, you can enable this behavior with NavigationViewOptions
using enableVanishingRouteLine
:
NavigationViewOptions.Builder optionsBuilder = NavigationViewOptions.builder();
optionsBuilder.enableVanishingRouteLine(true)
navigationView.startNavigation(optionsBuilder.build())
val optionsBuilder = NavigationViewOptions.builder()
optionsBuilder.enableVanishingRouteLine(true)
navigationView.startNavigation(optionsBuilder.build())
If the vanishing route line option is enabled using either method, you can customize the color and transparency of the section of the route line the user has already completed. See the section on styling for more information about the route line appearance.
This activity demonstrates turn by turn navigation using the NavigationMapRoute
class. This can be used instead of the convenience class NavigationMapboxMap
if it suits your needs.
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 navigation experience.
The RouteProgressObserver
returns a RouteProgress
object. Check the current state with the RouteProgressState
object inside the returned RouteProgress
object:
private RouteProgressObserver routeProgressObserver = new RouteProgressObserver() {
@Override
public void onRouteProgressChanged(RouteProgress routeProgress) {
if (routeProgress.currentState() != null) {
RouteProgressState state = routeProgress.currentState();
}
}
};
private val routeProgressObserver = object : RouteProgressObserver {
override fun onRouteProgressChanged(routeProgress: RouteProgress) {
routeProgress.currentState()?.let { currentState ->
val state = currentState
}
}
}
There are six possible states: ROUTE_INVALID
, ROUTE_INITIALIZED
, LOCATION_TRACKING
, ROUTE_ARRIVED
, LOCATION_STALE
, and ROUTE_UNCERTAIN
.
Invalid
If the DirectionsRoute
provided via MapboxNavigation.startNavigation()
is not valid, the state will be RouteProgressState.ROUTE_INVALID
.
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 establishing this trust, the route-following logic will report that it's still in the RouteProgressState.ROUTE_INITIALIZED
state.
Tracking
Once MapboxNavigation
is confidently tracking the Location
updates and processing them against the DirectionsRoute
. The state will change to RouteProgressState.LOCATION_TRACKING
.
Arrived
When the user has arrived at the destination of the given RouteLeg
, the state will be RouteProgressState.ROUTE_ARRIVED
.
Stale
If a lack of Location
updates has caused a lack of confidence in the progress updates, the state will be RouteProgressState.LOCATION_STALE
.
Uncertain
The RouteProgressState.ROUTE_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
.
The Navigation UI SDK uses RouteProgressState
to style the puck based on the current state. The Navigation UI SDK exposes APIs that allow you to use default Mapbox-provided drawables or specify custom drawables for the puck based on the current state.
By default, the Mapbox Navigation UI SDK provides the following puck drawables for each route progress state:
RouteProgressState | Puck Drawable Image |
---|---|
Invalid | |
Initialized | |
Tracking | |
Arrived | |
Stale | |
Uncertain |
To use these default Mapbox-provided images, use PuckDrawableSupplier
.
If you are using NavigationView
:
@Override
public void onNavigationReady(boolean isRunning) {
NavigationViewOptions.Builder optionsBuilder = NavigationViewOptions.builder();
optionsBuilder.puckDrawableSupplier(new DefaultMapboxPuckDrawableSupplier());
navigationView.startNavigation(optionsBuilder.build());
}
override fun onNavigationReady(isRunning: Boolean) {
val optionsBuilder = NavigationViewOptions.builder()
optionsBuilder.puckDrawableSupplier(DefaultMapboxPuckDrawableSupplier())
navigationView.startNavigation(optionsBuilder.build())
}
If you are using NavigationMapboxMap
:
navigtionMapboxMap.setPuckDrawableSupplier(new DefaultMapboxPuckDrawableSupplier());
navigtionMapboxMap.setPuckDrawableSupplier(DefaultMapboxPuckDrawableSupplier())
You can also specify custom drawables for different route progress states. To create your own PuckDrawableSupplier
, you will have to override the implementation as follows and replace DefaultMapboxPuckDrawableSupplier()
with your implementation.
class MyPuckDrawableSupplier implements PuckDrawableSupplier {
@Override
public int getPuckDrawable(@NotNull RouteProgressState routeProgressState) {
switch (routeProgressState) {
case ROUTE_INITIALIZED:
case LOCATION_TRACKING:
case LOCATION_STALE:
return R.drawable.custom_user_puck_icon;
case ROUTE_INVALID:
case ROUTE_ARRIVED:
default:
return R.drawable.custom_puck_icon_uncertain_location;
}
}
}
class MyPuckDrawableSupplier : PuckDrawableSupplier {
override fun getPuckDrawable(routeProgressState: RouteProgressState): Int = when (routeProgressState) {
RouteProgressState.ROUTE_INVALID -> R.drawable.custom_puck_icon_uncertain_location
RouteProgressState.ROUTE_INITIALIZED -> R.drawable.custom_user_puck_icon
RouteProgressState.LOCATION_TRACKING -> R.drawable.custom_user_puck_icon
RouteProgressState.ROUTE_ARRIVED -> R.drawable.custom_puck_icon_uncertain_location
RouteProgressState.LOCATION_STALE -> R.drawable.custom_user_puck_icon
else -> R.drawable.custom_puck_icon_uncertain_location
}
}
Arrival experience
Show and create experiences when arriving at waypoints and destinations. There are a few options for customizing the arrival experience.
ArrivalObserver
The ArrivalObserver
interface is a callback, and you can have many observers. Each observer will receive one callback for every state change.
ArrivalObserver arrivalObserver = new ArrivalObserver() {
@Override public void onNextRouteLegStart(@NotNull RouteLegProgress routeLegProgress) {
}
@Override public void onFinalDestinationArrival(@NotNull RouteProgress routeProgress) {
}
};
val arrivalObserver = object : ArrivalObserver() {
@Override public void onNextRouteLegStart(@NotNull RouteLegProgress routeLegProgress) {
}
@Override public void onFinalDestinationArrival(@NotNull RouteProgress routeProgress) {
}
}
onNextRouteLegStart
is called once the driver has arrived at a stop and has started navigating the next leg.navigateNextRouteLeg
will also betrue
when this happens.onFinalDestinationArrival
is called once the driver has reached the final destination at the end of the route.routeProgress.currentState()
will equalRouteProgressState.ROUTE_ARRIVED
.
Register the ArrivalObserver
interface with your already-instantiated MapboxNavigation
object.
mapboxNavigation.registerArrivalObserver(arrivalObserver);
mapboxNavigation.registerArrivalObserver(arrivalObserver)
Don’t forget to unregister the ArrivalObserver
interface:
@Override
public void onStop() {
super.onStop();
mapView.onStop();
mapboxNavigation.unregisterArrivalObserver(arrivalObserver);
}
override fun onStop() {
super.onStop()
mapView.onStop()
mapboxNavigation.unregisterArrivalObserver(locationObserver)
}
ArrivalController
The ArrivalController
allows you to decide when to navigate the next RouteLeg
when navigating waypoints and routes with multiple stops. Use the ArrivalOptions
class to decide when you want to receive navigateNextRouteLeg
callbacks as you approach a stop.
MapboxNavigation.attachArrivalController(yourArrivalController)
.
AutoArrivalController
is the default and will automatically navigateNextRouteLeg
. To override this behavior, attach your own ArrivalController
or MapboxNavigation.removeArrivalController()
.
- When your controller
navigateNextRouteLeg
returnstrue
, the navigator will start navigating to the next stop. - When your controller
navigateNextRouteLeg
returnsfalse
forever, useMapboxNavigation.navigateNextRouteLeg
directly to continue navigation.
You can attach your own ArrivalController
. This example shows you can use both time and distance:
navigation.attachArrivalController(new ArrivalController() {
@NotNull @Override public ArrivalOptions arrivalOptions() {
return ArrivalOptions.Builder()
.arriveInSeconds(10.0)
.arriveInMeters(50.0)
.build();
}
@Override public boolean navigateNextRouteLeg(@NotNull RouteLegProgress routeLegProgress) {
return routeLegProgress.distanceRemaining() < 5.0;
}
});
mapboxNavigation.attachArrivalController(object : ArrivalController {
override fun arrivalOptions(): ArrivalOptions = ArrivalOptions.Builder()
.arriveInSeconds(10.0)
.arriveInMeters(50.0)
.build()
override fun navigateNextRouteLeg(routeLegProgress: RouteLegProgress): Boolean {
return routeLegProgress.distanceRemaining() < 5.0
}
})
Building highlighting
The Navigation UI SDK provides logic to highlight a particular building footprint Polgyon
in the form of a Mapbox Maps SDK for Android FillLayer
or extrude a footprint as a 3D building shape in the form of a Mapbox Maps SDK for Android FillExtrusionLayer
.
In the context of navigation, this highlighting is especially good for visually representing your user's next stop or final destination. For example, a package delivery driver might want to see the exact building that's associated with a delivery address. In this case, showing the highlighting would happen inside of the Navigation Core SDK's ArrivalObserver
callback methods.
By default, this highlight logic exposes methods to change the highlighting visibility, opacity, and color. Because the logic is adding a FillLayer
or FillExtrusionLayer
to the map's Style
object.
The only building Feature
s that are eligible for highlighting are ones whose Feature
properties meet all the following:
- "extrude" = true
- "underground" = false
Query coordinate
This highlighting logic depends on setting a query LatLng
coordinate. Setting this coordinate tells the Navigation UI SDK to look for a building that's associated with the coordinate. If the coordinate doesn't fall within the building footprint (think of a single Point
within a Polygon
shape), then the highlighting won't work. Mapbox doesn't provide a way to connect an address with a coordinate that will consistently fall within the footprint of the building associated with that address. You must have your own data and a way to determine what coordinate should be used as the query LatLng
coordinate.
Highlight a building footprint
The Navigation UI SDK's BuildingFootprintHighlightLayer
handles the required code for adding and styling a FillLayer
to highlight a building footprint.
MapboxMap mapboxMap = navigationView.retrieveNavigationMapboxMap().retrieveMap();
BuildingFootprintHighlightLayer buildingFootprintHighlightLayer = new BuildingFootprintHighlightLayer(mapboxMap);
buildingFootprintHighlightLayer.setQueryLatLng(new LatLng(latitude,longitude));
buildingFootprintHighlightLayer.updateVisibility(true);
var mapboxMap = navigationView.retrieveNavigationMapboxMap().retrieveMap()
var buildingFootprintHighlightLayer = BuildingFootprintHighlightLayer(mapboxMap)
buildingFootprintHighlightLayer.queryLatLng = LatLng(latitude,longitude)
buildingFootprintHighlightLayer.updateVisibility(true)
You can change the color and opacity of the highlighted building footprint:
buildingFootprintHighlightLayer.setOpacity(0.5f);
buildingFootprintHighlightLayer.setColor(Color.BLUE);
buildingFootprintHighlightLayer.opacity = 0.5f
buildingFootprintHighlightLayer.color = Color.BLUE
See a complete working example of how to highlight a single building footprint.
Highlight a building extrusion
The Navigation UI SDK's BuildingExtrusionHighlightLayer
handles the required code for adding and styling a FillExtrusionLayer
to highlight a 3D building extrusion. The extrusion's height is based on the height
property of the GeoJSON Feature
associated with that particular building footprint.
MapboxMap mapboxMap = navigationView.retrieveNavigationMapboxMap().retrieveMap();
BuildingExtrusionHighlightLayer buildingExtrusionHighlightLayer = new BuildingExtrusionHighlightLayer(mapboxMap);
buildingExtrusionHighlightLayer.setQueryLatLng(new LatLng(latitude,longitude));
buildingExtrusionHighlightLayer.updateVisibility(true);
var mapboxMap = navigationView.retrieveNavigationMapboxMap().retrieveMap()
var buildingExtrusionHighlightLayer = BuildingExtrusionLayer(mapboxMap)
buildingExtrusionHighlightLayer.queryLatLng = LatLng(latitude,longitude)
buildingExtrusionHighlightLayer.updateVisibility(true)
You can change the color and opacity of the highlighted building extrusions:
buildingExtrusionLayer.setOpacity(0.5f);
buildingExtrusionLayer.setColor(Color.BLUE);
buildingExtrusionLayer.opacity = 0.5f
buildingExtrusionLayer.color = Color.BLUE
See a complete working example of how to highlight a single 3D building extrusion.