Get started with Navigation
The HERE SDK enables you to build a comprehensive turn-by-turn navigation experience. With this feature, your app can check the current device location against a calculated route and get navigational instructions just-in-time.
Key features include:
-
Automated rendering: A tailored navigation map view can be optionally rendered with the
VisualNavigator. OncestartRendering()is called, it will add a preconfiguredMapMarker3Dinstance in form of an arrow to indicate the current direction - and incoming location updates are smoothly interpolated. In addition, the map orientation is changed to the best suitable default values. -
Tracking mode: Even without having a route to follow, the HERE SDK supports a tracking mode, which provides information about the current street, the map-matched location and other supporting details such as speed limits.
-
Real-time instructions: Voice guidance is provided with maneuver notifications that can be fed as a
Stringinto any platform TTS (Text-To-Speech) solution. -
Support for warners: Stay aware with a comprehensive warner system that includes alerts on speed limits, truck restrictions, road signs and many more.
-
Offline support: Almost all navigation features work also without an internet connection when offline map data has been cached, installed or preloaded: only a few features require an online connection, for example, when using the
DynamicRouteEngineto search online for traffic-optimized routes.
The basic principle of turn-by-turn navigation is to frequently receive a location including speed and bearing values. These values are then matched to a street and compared to the desired route. A maneuver instruction is given to let you orient where you are and where you want to go next.
When leaving the route, you can be notified of the deviation in meters. This notification can help you to decide whether or not to calculate a new route. And finally, a location simulator allows you to test route navigation during the development phase.
Note
Application developers using turn-by-turn navigation are required to thoroughly test their applications in all expected usage scenarios to ensure safe and correct behavior. Application developers are responsible for warning app users of obligations including but not limited to:
- Do not follow instructions that may lead to an unsafe or illegal situation.
- Obey all local laws.
- Be aware that using a mobile phone or some of its features while driving may be prohibited.
- Always keep hands free to operate the vehicle while driving.
- Make road safety the first priority while driving.
How does it work?
There are two main entry points for navigation. The headless Navigator provides navigation events that you can render yourself. The VisualNavigator provides the same navigation features plus a preconfigured 3D map view, smooth interpolation, junction views, and other optional visual elements. Both classes react to Location data and implement LocationListener.
Voice guidance is supported by giving textual instructions with optional phoneme support and natural guidance information. These strings can be used with any available TTS feature (third-party or native OS). Use the RoutePrefetcher to optimize the experience in low connectivity situations, or download and install offline maps to navigate completely without an internet connection. For more information on prefetching map data, see.
Happy path: set up guidance
The normal turn-by-turn sequence is: create a navigator, register the listeners your app needs, provide real or simulated Location updates, set a Route, start guidance by starting the location source, and consume navigation events to update the UI, voice guidance, warnings, and progress state. A route is required for guidance; omit it when you intentionally want tracking mode.
1. Create a navigator
Create either a headless Navigator or a VisualNavigator. Use VisualNavigator.startRendering() only when you want the HERE SDK to control the navigation presentation on a MapView; rendering is optional.
2. Register listeners
Register only the listeners needed by the application. For example, RouteProgressListener supplies progress state and EventTextListener supplies maneuver text that can be shown or forwarded to a TTS engine. Warner listeners and UI updates are optional and can be added independently.
3. Provide locations
Start a real location provider, or use LocationSimulator during development. The provider must deliver frequent, valid Location updates to the navigator; navigation cannot calculate route progress without them. See Get Locations and Build a navigation app for the complete provider and simulator implementations.
4. Set a route
Set the calculated Route on the navigator. Getting a Route instance is shown here. During guidance, read Maneuver information from the navigator because it is synchronized with the current Location, rather than reading it directly from the Route object.
5. Start guidance
Starting the location source after setting the route starts guidance. The same sequence works with real locations or simulated locations.
6. Consume navigation events
Use route progress to update distance, ETA, and maneuver state; use event text for voice guidance; and add optional warning and location listeners for the rest of the navigation UI. The full navigation app tutorial shows how to connect these events to a complete application.
The following is the smallest realistic VisualNavigator setup using a real location provider. Replace VisualNavigator with Navigator when the application renders the map and navigation UI itself:
private void startGuidance(Route route) {
try {
visualNavigator = new VisualNavigator();
} catch (InstantiationErrorException e) {
throw new RuntimeException("Initialization of VisualNavigator failed: " + e.error.name());
}
// Optional: let VisualNavigator render the navigation view on the MapView.
visualNavigator.startRendering(mapView);
// Optional listeners: consume progress and maneuver text in the application.
visualNavigator.setRouteProgressListener(routeProgress -> updateProgress(routeProgress));
visualNavigator.setEventTextListener(eventText -> speakOrDisplay(eventText.text));
// Required for guidance: set the route and deliver locations.
visualNavigator.setRoute(route);
herePositioningProvider.startLocating(visualNavigator, LocationAccuracy.NAVIGATION);
}private fun startGuidance(route: Route) {
try {
visualNavigator = VisualNavigator()
} catch (e: InstantiationErrorException) {
throw RuntimeException("Initialization of VisualNavigator failed: " + e.error.name)
}
// Optional: let VisualNavigator render the navigation view on the MapView.
visualNavigator!!.startRendering(mapView!!)
// Optional listeners: consume progress and maneuver text in the application.
visualNavigator!!.routeProgressListener = RouteProgressListener { routeProgress -> updateProgress(routeProgress) }
visualNavigator!!.eventTextListener = EventTextListener { eventText -> speakOrDisplay(eventText.text) }
// Required for guidance: set the route and deliver locations.
visualNavigator!!.route = route
herePositioningProvider.startLocating(visualNavigator!!, LocationAccuracy.NAVIGATION)
}The updateProgress() and speakOrDisplay() calls represent application-owned UI and voice handling. For simulated Location events, use the LocationSimulator pattern in the navigation app tutorial. The VisualNavigator or the Navigator will automatically try to download online data when reaching uncached regions, and will use available offline map data when it is present.
Feed locations into a navigator
As shown above, you need to provide Location instances - as navigation is not possible without getting frequent updates on the current location. Let's take another look on how to fed non-simulated as well as simulated Location data into the system.
The navigation component is decoupled from positioning: new locations can be provided by implementing a platform-specific positioning solution, using an external provider, leveraging the HERE Positioning feature of the HERE SDK, or setting up a location simulator.
Note that you can set any Location source as "location provider". Only onLocationUpdated() has to be called on the Navigator or VisualNavigator.
It is the responsibility of the developer to feed in valid locations into the VisualNavigator. For each received location, the VisualNavigator will respond with appropriate events that indicate the progress along the route, including maneuvers and a possible deviation from the expected route. The resulting events depend on the accuracy and frequency of the provided location signals.
When using HERE Positioning, we recommend using LocationAccuracy.NAVIGATION, as this provides the best results in a navigation context.
All events are given based on a map-matched location - this is done automatically by the HERE SDK which incorporates calling the
MapMatchercomponent to match a raw location signal to the nearest road. Note that for theMapMatcherto work properly, it's required to set theLocation.timeparameter, otherwise, the location will be ignored. It is recommended to also providebearingandspeedparameters for eachLocationobject.
A positioning provider implementation using HERE Positioning can be found on GitHub for both Java and Kotlin. It provides simulated Location events with the HEREPositioningSimulator class and non-simulated Location events with the HEREPositioningProvider class.
We recommend to follow the Positioning guide to discover more details on all supported HERE Positioning features.
Both, the Navigator and the VisualNavigator classes conform to the LocationListener interface that defines the onLocationUpdated() method to receive locations:
// Now visualNavigator will receive locations from the HEREPositioningProvider.
// Choose a suitable accuracy for the navigation use case.
herePositioningProvider.startLocating(visualNavigator, LocationAccuracy.NAVIGATION);// Now visualNavigator will receive locations from the HEREPositioningProvider.
// Choose a suitable accuracy for the navigation use case.
herePositioningProvider.startLocating(visualNavigator, LocationAccuracy.NAVIGATION)Optionally, set the route you want to follow - unless you plan to start in tracking mode:
visualNavigator.setRoute(route);visualNavigator.route = routeStart and stop guidance
Turn-by-turn guidance starts when a Route is set and the location source is started. To stop guidance while continuing to receive map-matched locations and route-independent warnings, set the current route to null and keep the location source running. To stop receiving navigation events altogether, set the navigator listeners individually to null and stop the location source when it is no longer needed. With HERE Positioning, you can set multiple LocationListener instances and reuse the provider to consume location updates elsewhere in your app.
Start and stop tracking
Tracking mode, also called driver's assistance mode, uses location updates without a route. It provides map-matched location and route-independent information such as the current street and speed limits, but it does not provide route progress or turn-by-turn maneuver events. Tracking is most suitable for vehicle transport modes.
To switch from guidance to tracking, clear the route and continue delivering locations:
To enable tracking, you only need to call:
visualNavigator.setRoute(null);
herePositioningProvider.startLocating(visualNavigator, LocationAccuracy.NAVIGATION)visualNavigator.route = null
herePositioningProvider.startLocating(visualNavigator, LocationAccuracy.NAVIGATION)Here we enable getting real GPS locations, but you could also play back locations from any route using the LocationSimulator (as shown above).
It is also possible to initialize the VisualNavigator without setting a route instance when the app starts directly in tracking mode.
Note
Note that in tracking mode you only get events for listeners such as the
NavigableLocationListeneror theSpeedWarningListenerthat can fire without the need for a route to follow. In general, all warners are supported. Other listeners such as theRouteProgressListenerdo not deliver events when a route is not set.This enables you to keep your listeners alive and to switch between free tracking and turn-by-turn-navigation on the fly.
Consult the API Reference for an overview to see which listeners work in tracking mode.
In tracking mode, only listeners that can operate without a route deliver events. For example, NavigableLocationListener, SpeedWarningListener, and the warners are available, while RouteProgressListener does not deliver events. Keep listeners alive when switching between tracking and guidance, or set unused listeners or delegates to null when the application no longer needs their events. Consult the API Reference for the listener-specific tracking support.
Tracking can be useful when drivers already know the directions to take, but would like to get additional information such as the current street name or any speed limits along the trip. Public transit does not support tracking because public transit routes may lead to unsafe and unexpected results in this mode.
When tracking is enabled, it is also recommended to enable the SpeedBasedCameraBehavior:
visualNavigator.setCameraBehavior(new SpeedBasedCameraBehavior());visualNavigator.cameraBehavior = SpeedBasedCameraBehavior()This camera mode is automatically adjusting the camera's location to optimize the map view based on the current driving speed.
In order to stop following the camera, call:
visualNavigator.setCameraBehavior(null);visualNavigator.cameraBehavior = nullThis can be also useful during guidance, if you want to temporarily enable gesture handling. It is recommended to automatically switch back to tracking if turn-by-turn navigation is ongoing - in order to not distract a driver.
Navigation lifecycle
Restart guidance after tracking or completion
Setting route = null stops turn-by-turn events but leaves tracking events active. Setting a new Route again switches the same navigator back to guidance, and subsequent turn-by-turn events are propagated as location updates arrive. The application can therefore reuse its navigator and location provider instead of creating them for every transition.
When the destination is reached, turn-by-turn event propagation stops automatically. The navigator and location source are not automatically shut down, and route-independent warners may continue to deliver events. Handle DestinationReachedListener or the relevant progress state in the application, then either set a new route to restart guidance, switch to tracking, or stop the location source and release the navigator resources.
Stop rendering and release resources
When using VisualNavigator, call stopRendering() before destroying its MapView. After this call, the MapView is no longer controlled by VisualNavigator:
- Map orientation, camera distance, and tilt keep their last values. Apply the desired camera settings after
stopRendering()if needed. - The map no longer moves to the current location, even if locations continue to be fed into the
VisualNavigator. - The default or custom location indicator owned by
VisualNavigatoris hidden. - Location-based events such as
RouteProgresscontinue until their listeners are set tonull.
Stop a LocationSimulator and DynamicRoutingEngine when the application no longer needs them. A MapView pause does not require stopping the VisualNavigator: rendering pauses automatically and resumes when the MapView resumes, provided it was rendering before the pause.
Clean shutdown is application-owned. Stop the location source, clear listeners and delegates that are no longer needed, stop simulators and engines, and stop rendering before releasing the MapView.
Edge cases and platform-specific behavior
Location validity and map matching
Navigation events are based on a map-matched location. The HERE SDK uses the MapMatcher internally to match a raw location signal to the nearest road. The Location.time parameter is required; if it is missing, the location is ignored. Provide bearing and speed when available for better matching and navigation results.
The standalone Map matching locations section describes manual matching. For active navigation, prefer the map-matched events from NavigableLocationListener or the navigator's route progress.
Rendering and lifecycle boundaries
Calling stopRendering() changes only the rendering relationship with the MapView; it does not unsubscribe navigation listeners or stop location delivery. A paused MapView is different: VisualNavigator pauses and resumes rendering automatically while its lifecycle remains active.
Transport restrictions
Navigation is supported for all available transport modes except PUBLIC_TRANSIT. Public transit routes may lead to unsafe and unexpected results for turn-by-turn navigation as well as tracking. See Supported transport modes for the related transport-mode behavior.
Get maneuver progress events
During navigation, you typically want to attach a few listeners to get notified about the route progress, current location, and the next maneuver to take. The HERE SDK provides many different listeners for different purposes.
Below, we show how to get progress events:
// Notifies on the progress along the route including maneuver instructions.
visualNavigator.setRouteProgressListener(new RouteProgressListener() {
@Override
public void onRouteProgressUpdated(@NonNull RouteProgress routeProgress) {
List<SectionProgress> sectionProgressList = routeProgress.sectionProgress;
// sectionProgressList is guaranteed to be non-empty.
SectionProgress lastSectionProgress = sectionProgressList.get(sectionProgressList.size() - 1);
Log.d(TAG, "Distance to destination in meters: " + lastSectionProgress.remainingDistanceInMeters);
Log.d(TAG, "Traffic delay ahead in seconds: " + lastSectionProgress.trafficDelay.getSeconds());
// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any.
List<ManeuverProgress> nextManeuverList = routeProgress.maneuverProgress;
ManeuverProgress nextManeuverProgress = nextManeuverList.get(0);
if (nextManeuverProgress == null) {
Log.d(TAG, "No next maneuver available.");
return;
}
int nextManeuverIndex = nextManeuverProgress.maneuverIndex;
Maneuver nextManeuver = visualNavigator.getManeuver(nextManeuverIndex);
if (nextManeuver == null) {
// Should never happen as we retrieved the next maneuver progress above.
return;
}
ManeuverAction action = nextManeuver.getAction();
String roadName = getRoadName(nextManeuver, visualNavigator.getRoute());
String logMessage = action.name() + " on " + roadName +
" in " + nextManeuverProgress.remainingDistanceInMeters + " meters.";
// Angle is null for some maneuvers like Depart, Arrive and Roundabout.
Double turnAngle = nextManeuver.getTurnAngleInDegrees();
if (turnAngle != null) {
if (turnAngle > 10) {
Log.d(TAG, "At the next maneuver: Make a right turn of " + turnAngle + " degrees.");
} else if (turnAngle < -10) {
Log.d(TAG, "At the next maneuver: Make a left turn of " + turnAngle + " degrees.");
} else {
Log.d(TAG, "At the next maneuver: Go straight.");
}
}
// Angle is null when the roundabout maneuver is not an enter, exit or keep maneuver.
Double roundaboutAngle = nextManeuver.getRoundaboutAngleInDegrees();
if (roundaboutAngle != null) {
// Note that the value is negative only for left-driving countries such as UK.
Log.d(TAG, "At the next maneuver: Follow the roundabout for " +
roundaboutAngle + " degrees to reach the exit.");
}
if (previousManeuverIndex != nextManeuverIndex) {
messageView.setText("New maneuver: " + logMessage);
} else {
// A maneuver update contains a different distance to reach the next maneuver.
messageView.setText("Maneuver update: " + logMessage);
}
previousManeuverIndex = nextManeuverIndex;
}
});// Notifies on the progress along the route including maneuver instructions.
visualNavigator.routeProgressListener =
RouteProgressListener { routeProgress: RouteProgress ->
// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any.
val nextManeuverList = routeProgress.maneuverProgress
val nextManeuverProgress = nextManeuverList[0]
if (nextManeuverProgress == null) {
Log.d(TAG, "No next maneuver available.")
return@RouteProgressListener
}
val nextManeuverIndex = nextManeuverProgress.maneuverIndex
val nextManeuver = visualNavigator.getManeuver(nextManeuverIndex)
?: // Should never happen as we retrieved the next maneuver progress above.
return@RouteProgressListener
val action = nextManeuver.action
val roadName = getRoadName(nextManeuver, visualNavigator.route)
val logMessage = action.name + " on " + roadName + " in " + nextManeuverProgress.remainingDistanceInMeters + " meters."
// Angle is null for some maneuvers like Depart, Arrive and Roundabout.
val turnAngle = nextManeuver.turnAngleInDegrees
if (turnAngle != null) {
if (turnAngle > 10) {
Log.d(TAG, "At the next maneuver: Make a right turn of $turnAngle degrees.")
} else if (turnAngle < -10) {
Log.d(TAG, "At the next maneuver: Make a left turn of $turnAngle degrees.")
} else {
Log.d(TAG, "At the next maneuver: Go straight.")
}
}
// Angle is null when the roundabout maneuver is not an enter, exit or keep maneuver.
val roundaboutAngle = nextManeuver.roundaboutAngleInDegrees
if (roundaboutAngle != null) {
// Note that the value is negative only for left-driving countries such as UK.
Log.d(TAG, "At the next maneuver: Follow the roundabout for " + roundaboutAngle + " degrees to reach the exit."
)
}
previousManeuverIndex = nextManeuverIndex
}With the routeProgress event we can access the next maneuver that lies ahead of us. For this we use the maneuverIndex:
// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any.
List<ManeuverProgress> nextManeuverList = routeProgress.maneuverProgress;
ManeuverProgress nextManeuverProgress = nextManeuverList.get(0);
if (nextManeuverProgress == null) {
Log.d(TAG, "No next maneuver available.");
return;
}
int nextManeuverIndex = nextManeuverProgress.maneuverIndex;
Maneuver nextManeuver = visualNavigator.getManeuver(nextManeuverIndex);// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any.
val nextManeuverList = routeProgress.maneuverProgress
val nextManeuverProgress = nextManeuverList[0]
if (nextManeuverProgress == null) {
Log.d(TAG, "No next maneuver available.")
return@RouteProgressListener
}
val nextManeuverIndex = nextManeuverProgress.maneuverIndex
val nextManeuver = visualNavigator.getManeuver(nextManeuverIndex)Use nextManeuver.getAction() to identify the maneuver a user has to take. The full list of supported ManeuverAction enum values can be found in the API Reference.
Note
A maneuver icon as indicated by the
ManeuverActionenum is recommended to be shown as a visual indicator during navigation - while theManeuverinstruction text (nextManeuver.getText()) fits more into a list to preview maneuvers before starting a trip: these localized instructions are descriptive and will be understandable outside of an ongoing guidance context. However, commonly, they can be presented together with the correspondingManeuverActionicons you can find in the open-source HERE Icon Library. Find more details on this in the Routing section.
With the data provided by the RouteProgressListener we can access detailed information on the progress per Section of the passed Route instance.
A route may be split into several sections based on the number of waypoints and transport modes. Note that remainingDistanceInMeters and trafficDelay.getSeconds() are already accumulated per section. We check the last item of the SectionProgress list to get the overall remaining distance to the destination and the overall estimated traffic delay.
Note that the trafficDelay.getSeconds() is based upon the time when the Route data was calculated - therefore, the traffic delay is not refreshed during guidance. The value is only updated along the progressed sections based on the initial data. Use the DynamicRoutingEngine to periodically request optimized routes based on the current traffic situation.
The maneuver information taken from visualNavigator can be used to compose a display for a driver to indicate the next action and other useful information like the distance until this action takes place. It is recommended to not use this for textual representations, unless it is meant for debug purposes as shown in the example above. Use voice guidance instead.
Get street information from maneuvers
Once you have taken a Maneuver from the visualNavigator or navigator, the Maneuver class can be useful to display localized street names or numbers (such as highway numbers).
Road texts for the next maneuver can be retrieved as follows from a turn-by-turn maneuver:
// Helper enum to classify road types.
public enum RoadType {
HIGHWAY,
RURAL,
URBAN
}
private String getRoadName(Maneuver maneuver, Route route) {
RoadTexts currentRoadTexts = maneuver.getRoadTexts();
RoadTexts nextRoadTexts = maneuver.getNextRoadTexts();
String currentRoadName = currentRoadTexts.names.getDefaultValue();
String currentRoadNumber = currentRoadTexts.numbersWithDirection.getDefaultValue();
String nextRoadName = nextRoadTexts.names.getDefaultValue();
String nextRoadNumber = nextRoadTexts.numbersWithDirection.getDefaultValue();
String roadName = nextRoadName == null ? nextRoadNumber : nextRoadName;
// On highways, we want to show the highway number instead of a possible road name,
// while for inner city and urban areas road names are preferred over road numbers.
if (getRoadType(maneuver, route) == RoadType.HIGHWAY) {
roadName = nextRoadNumber == null ? nextRoadName : nextRoadNumber;
}
if (maneuver.getAction() == ManeuverAction.ARRIVE) {
// We are approaching the destination, so there's no next road.
roadName = currentRoadName == null ? currentRoadNumber : currentRoadName;
}
if (roadName == null) {
// Happens only in rare cases, when also the fallback is null.
roadName = "unnamed road";
}
return roadName;
}
// Determines the road type for a given maneuver based on street attributes.
// Returns the road type classification (HIGHWAY, URBAN or RURAL).
private RoadType getRoadType(Maneuver maneuver, Route route) {
Section sectionOfManeuver = route.getSections().get(maneuver.getSectionIndex());
List<Span> spansInSection = sectionOfManeuver.getSpans();
// If attributes list is empty then the road type is rural.
if (spansInSection.isEmpty()) {
return RoadType.RURAL;
}
Span maneuverSpan;
// Arrive maneuvers are placed after the last span of the route
// and the span index for them would be greater than the span's list size.
if (maneuver.getAction() == ManeuverAction.ARRIVE) {
maneuverSpan = spansInSection.get(spansInSection.size() - 1);
} else {
maneuverSpan = spansInSection.get(maneuver.getSpanIndex());
}
List<StreetAttributes> streetAttributes = maneuverSpan.getStreetAttributes();
// If attributes list contains either CONTROLLED_ACCESS_HIGHWAY, or MOTORWAY or RAMP then the road type is highway.
// Check for highway attributes.
if (streetAttributes.contains(StreetAttributes.CONTROLLED_ACCESS_HIGHWAY)
|| streetAttributes.contains(StreetAttributes.MOTORWAY)
|| streetAttributes.contains(StreetAttributes.RAMP)) {
return RoadType.HIGHWAY;
}
// If attributes list contains BUILT_UP_AREA then the road type is urban.
// Check for urban attributes.
if (streetAttributes.contains(StreetAttributes.BUILT_UP_AREA)) {
return RoadType.URBAN;
}
// If the road type is neither urban nor highway, default to rural for all other cases.
return RoadType.RURAL;
}enum class RoadType { HIGHWAY, RURAL, URBAN }
private fun getRoadName(maneuver: Maneuver, route: Route?): String {
val currentRoadTexts = maneuver.roadTexts
val nextRoadTexts = maneuver.nextRoadTexts
val currentRoadName = currentRoadTexts.names.getDefaultValue()
val currentRoadNumber = currentRoadTexts.numbersWithDirection.getDefaultValue()
val nextRoadName = nextRoadTexts.names.getDefaultValue()
val nextRoadNumber = nextRoadTexts.numbersWithDirection.getDefaultValue()
var roadName = nextRoadName ?: nextRoadNumber
// On highways, we want to show the highway number instead of a possible road name,
// while for inner city and urban areas road names are preferred over road numbers.
route?.let {
if (getRoadType(maneuver, it) == RoadType.HIGHWAY) {
roadName = nextRoadNumber ?: nextRoadName
}
}
if (maneuver.action == ManeuverAction.ARRIVE) {
// We are approaching the destination, so there's no next road.
roadName = currentRoadName ?: currentRoadNumber
}
if (roadName == null) {
// Happens only in rare cases, when also the fallback is null.
roadName = "unnamed road"
}
return roadName
}
// Determines the road type for a given maneuver based on street attributes.
// Returns the road type classification (HIGHWAY, URBAN or RURAL).
private fun getRoadType(maneuver: Maneuver, route: Route): RoadType {
val sectionOfManeuver: Section = route.sections[maneuver.sectionIndex]
val spansInSection: List<Span> = sectionOfManeuver.spans
// If attributes list is empty then the road type is rural.
if (spansInSection.isEmpty()) {
return RoadType.RURAL
}
// Arrive maneuvers are placed after the last span of the route
// and the span index for them would be greater than the span's list size.
val maneuverSpan =
if (maneuver.action == ManeuverAction.ARRIVE) {
spansInSection.last()
} else {
spansInSection[maneuver.spanIndex]
}
val streetAttributes = maneuverSpan.streetAttributes
// If attributes list contains either CONTROLLED_ACCESS_HIGHWAY, or MOTORWAY or RAMP then the road type is highway.
// Check for highway attributes.
if (streetAttributes.contains(StreetAttributes.CONTROLLED_ACCESS_HIGHWAY)
|| streetAttributes.contains(StreetAttributes.MOTORWAY)
|| streetAttributes.contains(StreetAttributes.RAMP)
) {
return RoadType.HIGHWAY
}
// If attributes list contains BUILT_UP_AREA then the road type is urban.
// Check for urban attributes.
if (streetAttributes.contains(StreetAttributes.BUILT_UP_AREA)) {
return RoadType.URBAN
}
// If the road type is neither urban nor highway, default to rural for all other cases.
return RoadType.RURAL
}You can get the default road texts directly via currentRoadTexts.names.getDefaultValue(), like shown above. In most cases, this will be the name of the road as shown on the local signs.
Alternatively, you can get localized texts for the road name based on a list of preferred languages via currentRoadTexts.names.getPreferredValueForLocales(locales). If no language is available, the default language is returned.
Note
You can use the
RoadTextsListenerto get notified on the currentRoadTextsyou are driving on, e.g. during tracking mode.
As the location provided by the device's GPS sensor may be inaccurate, the VisualNavigator internally calculates a map-matched location that is given to us as part of the NavigableLocation object. For example, a street location is expected to be on a navigable path. But it can also be off-track, in case the user has left the road - or if the GPS signal is too poor to find a map-matched location.
It is recommended to use the map-matched location to give the user visual feedback. For example, to update the current map view based on the map-matched location. Only if the location could not be map-matched, such as, when the user is off-road, it may be useful to fallback to the unmatched originalLocation. Below we choose to use the rendering capabilities of the VisualNavigator to automatically update the map view.
Note
The methods
nextManeuver.getRoadTexts(),nextManeuver.getNextRoadTexts()andnextManeuver.getExitSignTexts()are meant to be shown as part of turn-by-turn maneuvers during navigation: they are only non-empty when theManeuveris taken fromNavigatororVisualNavigator. If taken from aRouteinstance, these attributes are always empty.
Some roads, such as highways, do not have a road name. Instead, you can try to retrieve the road number. Keep also in mind, that there may be unnamed roads somewhere in the world.
Below table demonstrates the usage of maneuver properties:
| Maneuver Properties | RoutingEngine | Navigator / VisualNavigator | Examples |
|---|---|---|---|
| maneuver.getText() | Provides a non-empty string. | Provides a non-empty string. | Example output for getText(): "Turn right onto Detmolder Straße towards A100." |
| maneuver.getRoadTexts() | Provides empty strings. | Provides non-empty strings. | Example output for getRoadTexts().names.getDefaultValue(): "Stadtring". |
| maneuver.getNextRoadTexts() | Provides empty strings. | Provides non-empty strings. | Example output for getNextRoadTexts().names.getDefaultValue(): "Halenseestraße". |
| maneuver.getExitSignTexts() | Provides empty strings. | Provides non-empty strings. | Example output for getExitSignTexts().getDefaultValue(): "Hamburg". |
Note
It is not required to trigger the above events yourself. Instead the
VisualNavigatorwill react on the provided locations as coming from the location provider implementation.
Update ETA and traffic during navigation
The estimated time of arrival (ETA) can be updated by setting a TrafficOnRoute object to the Navigator or VisualNavigator. For this, it is recommended to periodically call routingEngine.calculateTrafficOnRoute(...).
Update traffic on route
During turn-by-turn navigation, it is recommended to calculate a dedicated TrafficOnRoute object by calling calculateTrafficOnRoute(). Since the traffic situation can change frequently while being on a trip, an application should repeat this call periodically.
The example implementation provided below ensures that traffic updates are performed at a configurable interval.
Upon completion of calculateTrafficOnRoute(), the VisualNavigator can be updated with the new TrafficOnRoute object, which adjusts the information on the duration provided by the RouteProgress object (only for HERE SDK for Navigate).
public void updateTrafficOnRoute(RouteProgress routeProgress, VisualNavigator visualNavigator) {
Route currentRoute = visualNavigator.getRoute();
if (currentRoute == null) {
return;
}
// Below, we use 10 minutes. A common range is between 5 and 15 minutes.
long trafficUpdateIntervalInMilliseconds = 10 * 60000; // 10 minutes.
long now = System.currentTimeMillis();
if ((now - lastTrafficUpdateInMilliseconds) < trafficUpdateIntervalInMilliseconds) {
return;
}
// Store the current time when we update trafficOnRoute.
lastTrafficUpdateInMilliseconds = now;
List<SectionProgress> sectionProgressList = routeProgress.sectionProgress;
SectionProgress lastSectionProgress = sectionProgressList.get(sectionProgressList.size() - 1);
int traveledDistanceOnLastSectionInMeters = currentRoute.getLengthInMeters() - lastSectionProgress.remainingDistanceInMeters;
int lastTraveledSectionIndex = routeProgress.routeMatchedLocation.sectionIndex;
routingEngine.calculateTrafficOnRoute(currentRoute, lastTraveledSectionIndex, traveledDistanceOnLastSectionInMeters, new CalculateTrafficOnRouteCallback() {
@Override
public void onTrafficOnRouteCalculated(@Nullable RoutingError routingError, @Nullable TrafficOnRoute trafficOnRoute) {
if (routingError != null) {
Log.d(TAG, "CalculateTrafficOnRoute error: " + routingError.name());
return;
}
// Sets traffic data for the current route, affecting RouteProgress duration in SectionProgress,
// while preserving route distance and geometry.
visualNavigator.setTrafficOnRoute(trafficOnRoute);
Log.d(TAG, "Updated traffic on route.");
}
});
}private fun updateTrafficOnRoute(routeProgress: RouteProgress, visualNavigator: VisualNavigator) {
val currentRoute = visualNavigator.route ?: return
// Below, we use 10 minutes. A common range is between 5 and 15 minutes.
val trafficUpdateIntervalInMilliseconds = 10 * 60000L // 10 minutes.
val now = System.currentTimeMillis()
if ((now - lastTrafficUpdateInMilliseconds) < trafficUpdateIntervalInMilliseconds) {
return
}
// Store the current time when we update trafficOnRoute.
lastTrafficUpdateInMilliseconds = now
val sectionProgressList = routeProgress.sectionProgress
val lastSectionProgress = sectionProgressList[sectionProgressList.size - 1]
val traveledDistanceOnLastSectionInMeters = currentRoute.lengthInMeters - lastSectionProgress.remainingDistanceInMeters
val lastTraveledSectionIndex = routeProgress.routeMatchedLocation.sectionIndex
routingEngine?.calculateTrafficOnRoute(
currentRoute,
lastTraveledSectionIndex,
traveledDistanceOnLastSectionInMeters,
object : CalculateTrafficOnRouteCallback {
override fun onTrafficOnRouteCalculated(
routingError: RoutingError?,
trafficOnRoute: TrafficOnRoute?
) {
if (routingError != null) {
Log.d(TAG, "CalculateTrafficOnRoute error: " + routingError.name)
return
}
// Sets traffic data for the current route, affecting RouteProgress duration in SectionProgress,
// while preserving route distance and geometry.
visualNavigator.setTrafficOnRoute(trafficOnRoute)
Log.d(TAG, "Updated traffic on route.")
}
}
)
}Note
This code initiates periodic calls to the HERE Routing backend. Depending on your contract, each call may be charged separately. It is the application's responsibility to decide how and how often this code should be executed.
The visualNavigator.setTrafficOnRoute() method does not take effect immediately. Instead, the updated travel duration (ETA) is reflected in the next RouteProgress event.
Supported transport modes
Navigation is supported for all available transport modes - except for PUBLIC_TRANSIT. Public transit routes may lead to unsafe and unexpected results when being used for navigation.
The transport mode can vary across the Route, for example, if you walk through a park to reach a sightseeing spot, you may need to leave a car. After the route is calculated, the transport mode is attached to each Section of a Route object.
For car, truck, taxi, bus and scooter routes, the location of the device will be map-matched to streets, while for other modes, such as pedestrian routes, locations may be matched - in addition - to unpaved dirt roads and other paths that would not be accessible to drivers. On the other hand, certain roads like highways are not navigable for pedestrians. Bicycle routes can make use of all available paths - except highways.
Try the Navigation example apps
- All code snippets from the below sections are also available on GitHub as part of the Navigation example app provided in Java and Kotlin. This app shows the code in connection and provides a testable driving experience and best practices such as keeping the screen alive during guidance.
- If you are interested in getting background location updates, you can check the related section in the Positioning guide. Note that as long as you provide location updates, all navigation events will seamlessly continue to be delivered - even if the device screen is locked or the map view is paused.
Additionally, you can find on GitHub the "NavigationQuickStart" example app. It shows how to get quickly started using a simulated location source.
Take a also look at the "NavigationCustom" example app, provided for both Java and Kotlin on GitHub. This example app demonstrates how the HERE SDK can be set up to navigate to a location with a custom LocationIndicator. It illustrates the usage of the default pedestrian and navigation LocationIndicator assets. Additionally, the app shows how to customize the guidance view by setting a custom zoom level and tilt.
On GitHub you can find many more example apps that cover certain topics in greater details. For example, you can find code to integrate TTS in the "Navigation" apps or to handle route deviations in the "Rerouting" app. Take a look at Explore the Navigation example apps
Updated 4 days ago