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.
Navigation is supported for all available transport modes. 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 the car. After the route is calculated, the transport mode is attached to each Section
of the route. For car and truck routes, the location of the device will be map-matched to the streets, while for pedestrian routes, locations are matched to unpaved dirt roads and other paths that would not be accessible to drivers.
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. This mode is for drivers only.
Note that the HERE SDK provides no UI assets such as maneuver arrows to indicate visual feedback. Instead, all information that is available along a route is given as simple data types, allowing you to choose your own assets where applicable.
A tailored navigation map view can be optionally rendered with the VisualNavigator
. Once startRendering()
is called, it will add a preconfigured MapMarker3D
instance 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.
During active guidance, usage of MapScheme.GREY_DAY
is recommended.
Voice guidance is provided as maneuver notifications that can be fed as a String
into any platform TTS (Text-To-Speech) solution.
Turn-By-Turn Navigation
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: Important
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.
Use a Navigator to Listen for Guidance Events
Before you can start to navigate to a destination, you need two things:
- A
Route
to follow. - A location source that periodically tells you where you are.
Unless you have already calculated a route, create one: Getting a Route
instance is shown here. If you only want to start the app in tracking mode, you can skip this step.
Another requirement is to provide Location
instances - as navigation is not possible without getting frequent updates on the current location. Below we choose to use a LocationProviderImplementation
that we will later on use to switch between simulated locations and real locations. A possible implementation can be found on GitHub.
locationProvider = new LocationProviderImplementation();
Next, we can create a new VisualNavigator
instance and set it as listener to the LocationProviderImplementation
from above. Note that the VisualNavigator
class conforms to the LocationListener
interface that defines the onLocationUpdated()
method to receive locations.
try {
visualNavigator = new VisualNavigator();
} catch (InstantiationErrorException e) {
throw new RuntimeException("Initialization of VisualNavigator failed: " + e.error.name());
}
locationProvider.setListener(visualNavigator);
locationProvider.start();
Don't forget to start the provider to send events on new Locations
- and make sure to set the route you want to follow (unless you plan to be in tracking mode only):
visualNavigator.setRoute(route);
If you do not plan to use the VisualNavigator
's rendering capabilities, you can also use the Navigator
class instead. This class uses the same code under the hood and behaves exactly like the VisualNavigator
, but it offers no support for rendering a specialized navigation view. Note: For the examples below, we do not use the offered rendering capabilities.
As a next step you may want to attach a few listeners to get notified on the route progress, on the current location, the next maneuver to take and on the route deviation:
visualNavigator.setRouteProgressListener(new RouteProgressListener() {
@Override
public void onRouteProgressUpdated(@NonNull RouteProgress routeProgress) {
List<SectionProgress> sectionProgressList = routeProgress.sectionProgress;
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.trafficDelayInSeconds);
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) {
return;
}
ManeuverAction action = nextManeuver.getAction();
String nextRoadName = nextManeuver.getNextRoadName();
String road = nextRoadName == null ? nextManeuver.getNextRoadNumber() : nextRoadName;
if (action == ManeuverAction.ARRIVE) {
String currentRoadName = nextManeuver.getRoadName();
road = currentRoadName == null ? nextManeuver.getRoadNumber() : currentRoadName;
}
if (road == null) {
road = "unnamed road";
}
String logMessage = action.name() + " on " + road +
" in " + nextManeuverProgress.remainingDistanceInMeters + " meters.";
if (previousManeuverIndex != nextManeuverIndex) {
Snackbar.make(mapView, "New maneuver: " + logMessage, Snackbar.LENGTH_LONG).show();
}
previousManeuverIndex = nextManeuverIndex;
}
});
visualNavigator.setNavigableLocationListener(new NavigableLocationListener() {
@Override
public void onNavigableLocationUpdated(@NonNull NavigableLocation currentNavigableLocation) {
MapMatchedLocation mapMatchedLocation = currentNavigableLocation.mapMatchedLocation;
if (mapMatchedLocation == null) {
Snackbar.make(mapView,
"This new location could not be map-matched. Using raw location.",
Snackbar.LENGTH_SHORT).show();
updateMapView(currentNavigableLocation.originalLocation.coordinates,
currentNavigableLocation.originalLocation.bearingInDegrees);
return;
}
Log.d(TAG, "Current street: " + currentNavigableLocation.streetName);
if (currentNavigableLocation.speedLimitInMetersPerSecond == null) {
Log.d(TAG, "Warning: Speed limits unkown, data could not be retrieved.");
} else if (currentNavigableLocation.speedLimitInMetersPerSecond == 0) {
Log.d(TAG, "No speed limits on this road! Drive as fast as you feel safe ...");
} else {
Log.d(TAG, "Current speed limit (m/s): " + currentNavigableLocation.speedLimitInMetersPerSecond);
}
updateMapView(mapMatchedLocation.coordinates, mapMatchedLocation.bearingInDegrees);
}
});
visualNavigator.setRouteDeviationListener(new RouteDeviationListener() {
@Override
public void onRouteDeviation(@NonNull RouteDeviation routeDeviation) {
Route route = visualNavigator.getRoute();
if (route == null) {
return;
}
MapMatchedLocation currentMapMatchedLocation = routeDeviation.currentLocation.mapMatchedLocation;
GeoCoordinates currentGeoCoordinates = currentMapMatchedLocation == null ?
routeDeviation.currentLocation.originalLocation.coordinates : currentMapMatchedLocation.coordinates;
GeoCoordinates lastGeoCoordinatesOnRoute;
if (routeDeviation.lastLocationOnRoute != null) {
MapMatchedLocation lastMapMatchedLocationOnRoute = routeDeviation.lastLocationOnRoute.mapMatchedLocation;
lastGeoCoordinatesOnRoute = lastMapMatchedLocationOnRoute == null ?
routeDeviation.lastLocationOnRoute.originalLocation.coordinates : lastMapMatchedLocationOnRoute.coordinates;
} else {
Log.d(TAG, "User was never following the route. So, we take the start of the route instead.");
lastGeoCoordinatesOnRoute = route.getSections().get(0).getDeparture().mapMatchedCoordinates;
}
int distanceInMeters = (int) currentGeoCoordinates.distanceTo(lastGeoCoordinatesOnRoute);
Log.d(TAG, "RouteDeviation in meters is " + distanceInMeters);
}
});
Here we set a RouteProgressListener
, a NavigableLocationListener
and a RouteDeviationListener
.
Inside 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 trafficDelayInSeconds
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.
Inside the RouteProgressListener
we also can access the next manuever that lies ahead of us. For this we use the maneuverIndex
:
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);
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 like shown in the example above. Use voice guidance instead (see below).
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. This location is expected to be on a navigable path like, for example, a street. 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. An example for this can be seen below. Only if the location could not be map-matched, for example, when the user is off-road, it may be useful to fallback to the unmatched originalLocation
.
Note that the maneuver instruction text is empty during navigation. It only contains localized instructions when taken from the route
instance. The ManeuverAction
enum is supposed to be used to show a visual indicator. Consult the API Reference for a full list of supported actions.
Some roads do not have a road name. In such a case, you can try to retrieve the road number instead, for example, when you are on a highway.
It is not required to trigger the above events yourself. Instead the VisualNavigator
will react on the provided locations as coming from the location provider implementation.
If you detect a route deviation, you can decide based on distanceInMeters
if you want to calculate a new route to the destination. This way you can reroute users to their destination. Note that for recalculation you may want to use the same route parameters, but there is no automatic mechanism to choose the same route alternative as before - unless you provide UI or some other logic for this.
In the above example, we calculate the distance based on the coordinates contained in RouteDeviation
: distanceInMeters
is the straight-line distance between the expected location on the route and your actual location. If that is considered too far, you can set a newly calculated route to the VisualNavigator
instance - and all further events will be based on the new route. The Navigation example app shows how to reroute after a deviation of 30 meters.
Note that previous events in the queue may still be delivered once for the old route - as the events are delivered asynchronously. If desired, you can attach new delegates after setting the new route to prevent this.
It is possible to feed in a new locations either by implementing a platform positioning solution or use the HERE SDK positioning feature or by setting up a location simulator.
The basic information flow is:
Location Provider => Location => VisualNavigator => Events
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.
Below you can find an example on how to handle location updates:
private void updateMapView(GeoCoordinates currentGeoCoordinates,
Double bearingInDegrees) {
MapCamera.OrientationUpdate orientation = new MapCamera.OrientationUpdate();
orientation.bearing = bearingInDegrees;
mapView.getCamera().lookAt(currentGeoCoordinates, orientation, DEFAULT_DISTANCE_IN_METERS);
navigationArrow.setCoordinates(currentGeoCoordinates);
trackingArrow.setCoordinates(currentGeoCoordinates);
}
Here the main task is to update the map to the new location. Additionally, we rotate the map based on the current bearing. We also update a custom map marker to indicate the user's current location in form of a navigation arrow.
Screenshot: Turn-by-turn navigation example running on a device.
Note that we call updateMapView()
from within the NavigableLocationListener
- as we already have showed above. Each new location event results in a new NavigableLocation
that holds a map-matched location calculated out of the original GPS signal that we have fed into the VisualNavigator
. This map-matched location can then be consumed - like for example, as we have done above in updateMapView()
.
To stop any ongoing navigation, call visualNavigator.setRoute(null)
, reset the above delegates to null or simply call stop()
on your location provider. More information can be found in the stop navigation section below.
For the full source code, please check the corresponding navigation example app.
Receive Waypoint Events
The VisualNavigator
class provides more useful notifications. Below is an example of how to receive notifications on passed waypoints. Note that it is possible to be notified at the destination waypoint in two alternative ways: the first example below notifies when the destination is reached - and therefore navigation can be stopped. Whereas the second code snippet below shows how to get notified on all types of waypoints including the destination waypoint.
visualNavigator.setDestinationReachedListener(new DestinationReachedListener() {
@Override
public void onDestinationReached() {
String message = "Destination reached. Stopping turn-by-turn navigation.";
Snackbar.make(mapView, message, Snackbar.LENGTH_LONG).show();
stopNavigation();
}
});
visualNavigator.setMilestoneReachedListener(new MilestoneReachedListener() {
@Override
public void onMilestoneReached(Milestone milestone) {
if (milestone.waypointIndex != null) {
Log.d(TAG, "A user-defined waypoint was reached, index of waypoint: " + milestone.waypointIndex);
Log.d(TAG,"Original coordinates: " + milestone.originalCoordinates);
} else {
Log.d(TAG,"A system defined waypoint was reached at " + milestone.mapMatchedCoordinates);
}
}
});
The onMilestoneReached()
method provides a Milestone
instance that contains the information about the passed waypoints along the route. Note that only stopover waypoints are included. A Milestone
includes an index that refers to the waypoint list set by the user when calculating the route. If it is not available, then the Milestone
refers to a waypoint that was set during the route calculation - for example, when an additional stopover was included by the routing algorithm to indicate that a ferry must be taken.
Receive Speed Warning Events
Although you can detect speed limits when you receive a new NavigableLocation
(see above), there is a more convenient solution that can help you implement a speed warning feature for your app.
onSpeedWarningStatusChanged()
will notify as soon as the driver exceeds the current speed limit allowed. And it will also notify as soon as the driver is driving slower again after exceeding the speed limit:
visualNavigator.setSpeedWarningListener(new SpeedWarningListener() {
@Override
public void onSpeedWarningStatusChanged(SpeedWarningStatus speedWarningStatus) {
if (speedWarningStatus == SpeedWarningStatus.SPEED_LIMIT_EXCEEDED) {
Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone ringtone = RingtoneManager.getRingtone(context, ringtoneUri);
ringtone.play();
}
if (speedWarningStatus == SpeedWarningStatus.SPEED_LIMIT_RESTORED) {
Log.d(TAG, "Driver is again slower than current speed limit (plus an optional offset).");
}
}
});
Note that onSpeedWarningStatusChanged()
does not notify when there is no speed limit data available. This information is only available as part of a NavigableLocation
instance.
onSpeedWarningStatusChanged()
notifies dependent on the current road's speed limits and the driver's speed. This means that you can get speed warning events also in tracking mode independent of a route. And, consequently, you can receive a SPEED_LIMIT_RESTORED
event when the route has changed - after driving slower again.
Optionally, you can define an offset that is added to the speed limit value. You will be notified only when you exceed the speed limit, including the offset. Below, we define two offsets, one for lower and one for higher speed limits. The boundary is defined by highSpeedBoundaryInMetersPerSecond
:
private void setupSpeedWarnings() {
double lowSpeedOffsetInMetersPerSecond = 2;
double highSpeedOffsetInMetersPerSecond = 4;
double highSpeedBoundaryInMetersPerSecond = 25;
SpeedLimitOffset speedLimitOffset = new SpeedLimitOffset(
lowSpeedOffsetInMetersPerSecond, highSpeedOffsetInMetersPerSecond, highSpeedBoundaryInMetersPerSecond);
visualNavigator.setSpeedWarningOptions(new SpeedWarningOptions(speedLimitOffset));
}
Here we set the highSpeedBoundaryInMetersPerSecond
to 25 m/s: If a speed limit sign is showing a value above 25 m/s, the offset used is highSpeedOffsetInMetersPerSecond
. If it is below 25 m/s, the offset used is lowSpeedOffsetInMetersPerSecond
.
For example, if the speed limit on the road is 27 m/s, the offset is 4 m/s. This means we will only receive a warning notification when we are above 31 m/s = 27 m/s + 4 m/s.
You can also set negative offset values as well. This may be useful if you want to make sure you never exceed the speed limit by having a buffer before you reach the limit. Note that you will never get notifications when you drive too slow, for example, slower than a defined offset - unless a previous speed warning has been restored.
Implement a Location Provider
A location provider is necessary to be provide Location
instances to the VisualNavigator
. It can feed location data from any source. Here we use an implementation that allows to switch between native location data from the device and simulated location data for test drives.
Note that the VisualNavigator
already conforms to the LocationListener
interface, so it can be used as listener for classes that call onLocationUpdated()
:
public class LocationProviderImplementation {
@Nullable
private LocationListener locationListener;
public void setListener(@Nullable LocationListener locationListener) {
this.locationListener = locationListener;
}
...
The setListener()
method will be used by the visualNavigator
instance to hook in itself as a new locationListener
instance.
As source for location data, we will use a HEREPositioningProvider
that is based on the code as shown in the Find your Location section.
For navigation it is recommended to use LocationAccuracy.NAVIGATION
when starting the LocationEngine
as this guarantees the best results during turn-by-turn navigation.
To deliver events, we need to start our herePositioningProvider
and send events to the locationListener
instance:
public void start() {
herePositioningProvider.startLocating(new LocationUpdateListener() {
@Override
public void onLocationUpdated(Location location) {
handleLocationUpdate(location);
}
});
}
private void handleLocationUpdate(Location location) {
if (locationListener != null) {
locationListener.onLocationUpdated(location);
}
}
Note that the HEREPositioningProvider
uses the LocationUpdateListener
interface to receive location events which defines the same onLocationUpdated()
method as the LocationListener
interface. This will be aligned for future HERE SDK versions.
The required HERE SDK Location
type includes bearing and speed information along with the current geographic coordinates and other information that is consumed by the VisualNavigator
. The more accurate and complete the provided data is, the more precise the overall navigation experience will be.
Internally, the timestamp
of a Location
is used to evaluate, for example, if the user is driving through a tunnel or if the signal is simply lost.
You can find a reference implementation of the location provider on GitHub.
Set up a Location Simulator
During development, it may be convenient to playback the expected progress on a route for testing purposes. The LocationSimulator
provides a continuous location signal that is taken from the original route coordinates.
Below we integrate the LocationSimulator
into the LocationProviderImplementation
from above to allow switching between real location updates and simulated ones.
Setting up a location simulator can be done like shown below:
private LocationSimulator createLocationSimulator(Route route) {
double speedFactor = 3;
int notificationIntervalInMilliseconds = 500;
LocationSimulatorOptions locationSimulatorOptions =
new LocationSimulatorOptions(speedFactor, notificationIntervalInMilliseconds);
LocationSimulator locationSimulator;
try {
locationSimulator = new LocationSimulator(route, locationSimulatorOptions);
} catch (InstantiationErrorException e) {
throw new RuntimeException("Initialization of LocationSimulator failed: " + e.error.name());
}
locationSimulator.setListener(new LocationListener() {
@Override
public void onLocationUpdated(Location location) {
if (isSimulated) {
handleLocationUpdate(location);
}
}
});
return locationSimulator;
}
Again, we call handleLocationUpdate()
to forward simulated location events to the visualNavigator
instance.
By setting LocationSimulatorOptions
, we can specify, how fast the current simulated location will move. By default, the speed factor is 1.0, which is equal to the average speed a user normally drives or walks along each route segment without taking into account any traffic-related constraints. The default speed may vary based on the road geometry, road condition and other statistical data, but it is never higher than the current speed limit. Values above 1.0 will increase the speed proportionally. If the route contains not enough coordinates for the specified time interval, additional location events will be interpolated.
Make sure to stop any ongoing simulation before starting a new one:
if (locationSimulator != null) {
locationSimulator.stop();
}
locationSimulator = createLocationSimulator(route);
locationSimulator.start();
isSimulated = true;
You can see an implementation of the simulation code snippet from above included in the LocationProviderImplementation
on GitHub. It also shows how you can seamlessly switch between simulated and real locations by calling enableRoutePlayback(Route route)
and enableDevicePositioning()
:
Voice Guidance
While driving, the user's attention should stay focused on the route. You can construct visual representations from the provided maneuver data (see above), but you can also get localized textual representations that are meant to be spoken during turn-by-turn guidance. Since these maneuver notifications are provided as a String
, it is possible to use them together with any TTS solution.
A few example notifications:
Voice message: After 1 kilometer turn left onto North Blaney Avenue.
Voice message: Now turn left.
Voice message: After 1 kilometer turn right onto Forest Avenue.
Voice message: Now turn right.
Voice message: After 400 meters turn right onto Park Avenue.
Voice message: Now turn right.
To get these notifications, set up a ManeuverNotificationListener
:
visualNavigator.setManeuverNotificationListener(new ManeuverNotificationListener() {
@Override
public void onManeuverNotification(@NonNull String voiceText) {
voiceAssistant.speak(voiceText);
}
});
Here we use a helper class called VoiceAssistant
that wraps a Text-To-Speech engine to speak the maneuver notification. The engine uses Android's TextToSpeech
class. If you are interested, you can find this class as part of the Navigation example app on GitHub.
You can set a LanguageCode
to localize the notification text and a UnitSystem
to decide on metric or imperial length units. Make sure to call this before a route is set, as otherwise default settings (EN_US
, METRIC
) will be used:
private void setupVoiceGuidance() {
LanguageCode ttsLanguageCode = getLanguageCodeForDevice(VisualNavigator.getAvailableLanguagesForManeuverNotifications());
visualNavigator.setManeuverNotificationOptions(new ManeuverNotificationOptions(ttsLanguageCode, UnitSystem.METRIC));
Locale locale = LanguageCodeConverter.getLocale(ttsLanguageCode);
if (voiceAssistant.setLanguage(locale)) {
Log.d(TAG, "TextToSpeech engine uses this language: " + locale);
} else {
Log.e(TAG, "TextToSpeech engine does not support this language: " + locale);
}
}
For this example, we take the device's preferred language settings. One possible way to get these is shown below:
private LanguageCode getLanguageCodeForDevice(List<LanguageCode> supportedVoiceSkins) {
Locale localeForCurrenDevice = Locale.getDefault();
if (!voiceAssistant.isLanguageAvailable(localeForCurrenDevice)) {
Log.e(TAG, "TextToSpeech engine does not support: " + localeForCurrenDevice + ", falling back to EN_US.");
localeForCurrenDevice = new Locale("en", "US");
}
LanguageCode languageCodeForCurrenDevice = LanguageCodeConverter.getLanguageCode(localeForCurrenDevice);
if (!supportedVoiceSkins.contains(languageCodeForCurrenDevice)) {
Log.e(TAG, "No voice skins available for " + languageCodeForCurrenDevice + ", falling back to EN_US.");
languageCodeForCurrenDevice = LanguageCode.EN_US;
}
return languageCodeForCurrenDevice;
}
Note that the HERE SDK supports 37 languages. You can query the languages from the VisualNavigator
with VisualNavigator.getAvailableLanguagesForManeuverNotifications()
. All languages within the HERE SDK are specified as LanguageCode
enum. To convert this to a Locale
instance, you can use a LanguageCodeConverter
. This is an open source utility class you find as part of the Navigation example app on GitHub.
Each of the supported languages to generate maneuver notifications is stored as a voice skin inside the HERE SDK framework. Unzip the framework and look for the folder voice_assets. You can manually remove assets you are not interested in to decrease the size of the HERE SDK package.
However, in order to feed the maneuver notification into a TTS engine, you also need to ensure that your preferred language is supported by the TTS engine. Usually each device comes with some preinstalled languages, but not all languages may be present initially.
Supported Languages for Voice Guidance
Below you can find a list of all supported voice languages together with the name of the related voice skin that is stored inside the HERE SDK framework:
- Arabic (Saudi Arabia): ar-SA_tarik_compact
- Czech: cs-CZ_iveta_compact
- Danish: da-DK_magnus_compact
- German: de-DE_anna_compact
- Greek: el-GR_nikos_compact
- English (British): en-GB_serena_compact
- English (United States): en-US_tom_compact
- Spanish (Spain): es-ES_jorge_compact
- Spanish (Mexico): es-MX_angelica_compact
- Farsi (Iran): fa-IR_anonymous_compact
- Finnish: fi-FI_onni_compact
- French (Canada): fr-CA_chantal_compact
- French: fr-FR_audrey_compact
- Hebrew: he-IL_carmit_compact
- Hindi: hi-IN_lekha_compact
- Croatian: hr-HR_anonymous_compact
- Hungarian: hu-HU_mariska_compact
- Indonesian: (Bahasa) id-ID_damayanti_compact
- Italian: it-IT_alice_compact
- Japanese: ja-JP_sakura_compact
- Korean: ko-KR_sora_compact
- Norwegian: (Bokmål) nb-NO_henrik_compact
- Dutch: nl-NL_claire_compact
- Portuguese (Brazil): pt-BR_luciana_compact
- Polish: pt-PT_joana_compact
- Romanian: ro-RO_ioana_compact
- Russian: ru-RU_katya_compact
- Slovak: sk-SK_laura_compact
- Swedish: sv-SE_alva_compact
- Thai: th-TH_kanya_compact
- Turkish: tr-TR_cem_compact
- Ukrainian: uk-UA_anonymous_compact
- Chinese (Simplified China): zh-CN_tian-tian_compact
- Chinese (Traditional Hong Kong): zh-HK_sin-ji_compact
- Chinese (Traditional Taiwan): zh-TW_mei-jia_compact
Stop Navigation
While turn-by-turn navigation automatically starts when a route
is set and the LocationPrivider
is started, stopping navigation depends on the possible scenario:
Either, you want to stop navigation and switch to tracking mode (see below) to receive map-matched locations while still following a path - or you want to stop navigation without going back to tracking mode. For the first case, you only need to set the current route
to null
. This will only stop propagating all turn-by-turn navigation related events, but keep the ones alive to receive map-matched location updates and, for example, speed warning information. Note that propagation of turn-by-turn navigation events is automatically stopped when reaching the desired destination. Once you set a route
again, all turn-by-turn navigation related events will be propagated again.
If you want to stop navigation without going back to tracking mode - for example, to get only un-map-matched location updates directly from a location provider - it is good practice to stop getting all events from the VisualNavigator
. For this you should set all listeners individually to null
.
You can reuse your location provider implementation to consume location updates in your app. With HERE positioning you can set multiple LocationUpdateListener
instances.
Tracking
While you can use the VisualNavigator
class to start and stop turn-by-turn navigation, it is also possible to switch to a tracking mode that does not require a route to follow. This mode is also often referred to as the driver's assistance mode. It is available for car and truck transport modes.
To enable tracking, you only need to call:
visualNavigator.setRoute(null);
locationProvider.enableDevicePositioning();
Here we enable getting real GPS locations, but you could also play back locations from any route using the LocationSimulator
(as shown above).
Of course, it is possible to initialize the VisualNavigator
without setting a route
instance - if you are only interested in tracking mode you don't need to set the route explicitly to null. Note that in tracking mode you only get events for the NavigableLocationListener
. All other listeners will simply not deliver events when a route is not set. This enables you to keep your listeners attached and to switch between free tracking and turn-by-turn-navigation on the fly.
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.