Maneuver instructions

SDKs covered:
Navigation SDK
Navigation UI SDK

Navigation milestones inside the SDK provide a powerful way to give your user instructions or get cues to hide or show custom UI elements at defined locations along their route. Use default milestones to trigger voice and text instructions or create custom milestones that fit your particular app needs.

Default milestones

There are two default milestones that are used to trigger voice and text instructions: VoiceInstructionMilestone and BannerInstructionMilestone.

BannerInstructionMilestone

BannerInstructionMilestone fires every time textual instructions should be updated, most of the time in the format of a "banner" view on the top of the screen. This milestone provides a BannerInstructions object for the given point along the route. This object contains text and URLs for shield images that can be displayed on screen at the time the milestone fires.

VoiceInstructionMilestone

VoiceInstructionMilestone fires every time it's time to announce an instruction along a given DirectionsRoute. This milestone provides a plain text instruction with VoiceInstructionMilestone#getInstruction as well as a SSML version of the same instruction with VoiceInstructionMilestone#getSsmlAnnouncement.
SSML stands for Speech Synthesis Markup Language and is designed to work with AWS Polly.

In the Navigation UI SDK, banner and voice instructions are triggered by the default milestones described above. There are default styling rules for banner instructions and default settings for voice instructions. Instructions can be customized to an extent including overriding default behaviors when instructions are triggered and customizing the style of banner instructions. You must create custom milestones with the core Navigation SDK.

Note

For customizing the language used in instructions, see Localization.

Instruction triggers

Using NavigationView in your XML gives you the ability to listen to different updates or events that may occur during navigation.

BannerInstructionsListener

willDisplay(BannerInstructions instructions) will be triggered when a BannerInstructions is about to be displayed. The listener gives you the option to override any values and pass as the return value, which will be the value used for the banner instructions. You can return null and the instructions will be ignored.

SpeechAnnouncementListener

willVoice(SpeechAnnouncement announcement) will be triggered when a voice announcement is about to be voiced. The listener gives you the option to override any values and pass as the return value, which will be the value used for the voice announcement. You can return null and the announcement will be ignored.

To use these listeners, add them to your NavigationViewOptions before you call navigationView.startNavigation(NavigationViewOptions options):

NavigationViewOptions options = NavigationViewOptions.builder()
.navigationListener(this)
.routeListener(this)
.feedbackListener(this)
.build();

Instruction styling

You also have the option to add the custom Views used in the turn-by-turn UI to your XML. The top View that displays the maneuver image, instruction text, and sound button is called InstructionView.

<com.mapbox.services.android.navigation.ui.v5.instruction.InstructionView
    android:id="@+id/instructionView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

Once inflated in your Activity, the InstructionView can be updated with RouteProgress and Milestone objects inside a ProgressChangeListener and MilestoneEventListener respectively.

@Override
public void onProgressChange(Location location, RouteProgress routeProgress) {
instructionView.updateDistanceWith(routeProgress);
}
@Override
public void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) {
instructionView.updateBannerInstructionsWith(milestone);
}

Prior to the first time you want to update the InstructionView, you can control the distance formatting with InstructionView#setDistanceFormatter(DistanceFormatter distanceFormatter).

This will determine how distances are displayed in the view:

String unitType = DirectionsCriteria.METRIC;
String language = Locale.US.getLanguage();
int roundingIncrement = NavigationConstants.ROUNDING_INCREMENT_TWENTY_FIVE;
DistanceFormatter distanceFormatter = new DistanceFormatter(getContext(), language, unitType, roundingIncrement);
instructionView.setDistanceFormatter(distanceFormatter);
}

If this is not set, the view will create its own based on inferred parameters from the device's Android configuration. Please also make sure to set our default theme: R.style.NavigationViewLight (or create your own) and set it in your Activity or Fragment before super.onCreate().

The custom Views will now look for the attributes in the default theme to set text and background colors:

@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.NavigationViewLight);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navigation);
...
}

Milestone event listener

All milestones use the onMilestoneEvent callback to alert when they get triggered. If you want to make use of the milestones API, you will want to attach a MilestoneEventListener inside your app. When all the milestone trigger conditions are true, the callback is invoked and provides you with the latest routeProgress along with the milestone's corresponding String instruction and the Milestone itself that was triggered. You can use your text-to-speech engine of choice and have it consume the instruction.

@Override
public void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) {
exampleInstructionPlayer.play(instruction);
}

Custom instructions

When using the milestone event listener, the callback provides a String instruction value. During the milestone creation process, you can add the logic that generates this instruction. Begin by creating a new Instruction object which will provide an override method, buildInstruction, which provides a RouteProgress object for producing the instructions string. With the provided route progress, you can add information such as distance and duration remaining until the next maneuver. Once the Instruction is initialized, you will need to give it to the milestone using setInstruction. The example below shows how to add the Directions API instruction as the milestone instruction with no modifications.

Instruction myInstruction = new Instruction() {
@Override
public String buildInstruction(RouteProgress routeProgress) {
return routeProgress.currentLegProgress().upComingStep().maneuver().instruction();
}
};

Load banner instructions into a TextView

If you would like to use your own TextView and load a given BannerText instruction, you can do so with the InstructionLoader. The loader takes care of organizing text, loading road shields, and abbreviating text (if it doesn't fit in the TextView). Paired with our MilestoneEventListener, you can load instruction updates into your TextView:

@Override
public void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) {
if (milestone instanceof BannerInstructionMilestone) {
BannerText primaryInstruction = ((BannerInstructionMilestone) milestone).getBannerInstructions().primary();
InstructionLoader loader = new InstructionLoader(textView, primaryInstruction);
loader.loadInstruction();
}
}
}

Listen for the start or stop of a navigation session

The onRunning callback notifies you when the navigation session has started, the user has canceled the session, or the user has arrived at their final destination. From this information, you can decide when to show navigation notifications, know when it's safe to stop requesting user location updates, and more.

navigation.addNavigationEventListener(new NavigationEventListener() {
@Override
public void onRunning(boolean running) {
}
});

Custom milestones

Milestones bring flexibility to your app and how it handles navigation events. You can create a milestone in a few steps. First, choose how often you'd like the milestone to be triggered. Two options are provided:

  • StepMilestone, which is triggered each step in the route.
  • RouteMilestone, which will only be triggered once during the entire route.

You can also implement your own behavior for triggers by extending the Milestone class. Give the milestone a unique identifier that can be used to determine which milestone triggered the onMilestoneEvent callback. Set the triggers using any combination of the properties shown in the table below. It is important to note that trigger properties have different corresponding variable types that need to be accounted for when setting up the milestone. Lastly, build the milestone and pass it into the MapboxNavigation instance using addMilestone().

The snippet of code below shows the creation of a RouteMilestone with two conditions, both of which need to be true for the milestone to be triggered. Since it is a RouteMilestone, the milestone event only occurs once along the route. You can read the trigger statement as: both the step index must be less than three and the current step total distance must be greater than 200 meters for the milestone to be triggered.

navigation.addOffRouteListener(new OffRouteListener() {
@Override
public void userOffRoute(Location location) {
navigation.addMilestone(new RouteMilestone.Builder()
.setIdentifier("begin-route-milestone")
.setTrigger(
Trigger.all(
Trigger.lt(TriggerProperty.STEP_INDEX, 3), Trigger.gt(TriggerProperty.STEP_DISTANCE_TOTAL_METERS, 200))).build()
);

Trigger conditions

Besides the triggers already mentioned above, the SDK comes equipped to handle pretty much any case you'd like to build. The table below shows all the conditions offered inside the SDK and whether it is a compound statement or a simple statement.

Condition nameTypeDescription
allCompoundLogical equivalent to an AND statement, all the conditions must be true for the trigger to occur.
anyCompoundLogical equivalent to an OR statement, any of the conditions can be true to cause a trigger.
noneCompoundLogical equivalent to a NOR statement, all statements must equate to false for a trigger to occur.
eqSimpleEquality. The trigger property's current value must equal the exact value defined.
neqSimpleInequality. The trigger property's current value must not equal the exact value defined.
gtSimpleGreater than. The trigger property's current value must be greater than the defined value.
gteSimpleGreater than or equal. The trigger property's current value must be greater than or equal to the defined value.
ltSimpleLess than. The trigger property's current value must be less than the defined value
lteSimpleLess than or equal. The trigger property's current value must be less than or equal to the defined property.

Trigger properties

Below are the available trigger properties that can be used along with the conditions above to filter when a milestone should be triggered. Note that instead of the boolean types using the primitive type true or false, the TriggerProperty class uses custom boolean values for the triggers.

Property nameTypeDescription
STEP_INDEXintegerWhich step or steps the user must be on the trigger the milestone.
STEP_DISTANCE_TOTAL_METERSdoubleThe length that the current step must be.
STEP_DISTANCE_REMAINING_METERSdoubleWill trigger the milestone based on the distance remaining.
STEP_DURATION_REMAINING_SECONDSdoubleWill trigger the milestone based on the duration remaining.
STEP_DURATION_TOTAL_SECONDSdoubleWill trigger the milestone based on the total step duration.
STEP_DISTANCE_TRAVELED_METERSdoubleWill trigger the milestone based on the distance the user has traveled along the step already.
NEW_STEPbooleanWhen the user completes a maneuver and begins traversing along a new step.
FIRST_STEPbooleanWhen the user begins a navigation session and is on the first step.
LAST_STEPbooleanWhen the user is on the second to last step. Note the final step in direction route will only contain a point representing the final maneuver.
NEXT_STEP_DISTANCE_METERSdoubleThe next step's total distance in meters.
FIRST_LEGbooleanWhen the user is on the first leg.
LAST_LEGbooleanWhen the user is on the last leg.

Open an issue on GitHub if you feel a trigger property is missing and include the use case.

Was this page helpful?