# 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.
> #### Note
> Navigation is supported for all available transport modes - except for `publicTransit`. Public transit routes may lead to unsafe and unexpected results when being used for navigation.
>
> Navigation support for `bus` routes is currently limited: We expect enhancements for bus specific lane assistance and more precise turn-by-turn instructions for bus navigation in a future release of the HERE SDK.
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 to unpaved dirt roads and other paths that would not be accessible to drivers. Bicycle routes can make use of all available paths.
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.
Note that the HERE SDK provides no UI assets for _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.
> #### Note
> Tip: Reusable assets for use in your own iOS applications can be found in the MSDKUI open source project from HERE - available on GitHub under this [link](https://github.com/heremaps/msdkui-ios/tree/master/MSDKUI/Assets). More reusable icons can be found in the official [HERE Icon Library](https://github.com/heremaps/here-icons).
Voice guidance is provided as _maneuver notifications_ that can be fed as a `String` into any platform TTS (Text-To-Speech) solution.
> #### Note
> Offline guidance is supported. Turn-by-turn navigation and tracking fully works offline on already cached or downloaded offline map data - as long as the route does not reach regions _without_ cached or preloaded map data.
## Turn-By-Turn Navigation
The basic principle of turn-by-turn navigation is to frequently receive a location including speed and bearing values which is then matched to a street and compared to the desired route. A maneuver instruction is given to let you orientate _where_ you are and _where_ you have to go next.
When leaving the route, you can be notified of the deviation in meters. This notification can help you decide whether 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.
All code snippets from the below sections are also available on GitHub as part of the [Navigation example app](https://github.com/heremaps/here-sdk-examples/tree/master/examples/latest/navigate/android/Navigation). This app shows the code in connection and provides a testable driving experience and best practices such as keeping the screen alive during guidance. However, it does, not cover every aspect of a full-blown production-ready application. For example, the app does not show how to enable getting location updates while an app may operate in background.
In addition, you can also find a _NavigationQuickStart_ app on [GitHub](https://github.com/heremaps/here-sdk-examples) that shows how to start guidance with just a few lines of code. See also the next section.
## Get Started
Before we look into the navigation features of the HERE SDK in greater detail, lets first see a short coding example that shows how to start guidance with speakable maneuver instructions and a guidance view:
```swift
private func startGuidance(route: Route) {
do {
// Without a route set, this starts tracking mode.
try visualNavigator = VisualNavigator()
} catch let engineInstantiationError {
fatalError("Failed to initialize VisualNavigator. Cause: \(engineInstantiationError)")
}
// This enables a navigation view including a rendered navigation arrow.
visualNavigator!.startRendering(mapView: mapView)
// Hook in one of the many delegates. Here we set up a delegate to get instructions on the maneuvers to take while driving.
// For more details, please check the "Navigation" example app and the Developer's Guide.
visualNavigator!.maneuverNotificationDelegate = self
// Set a route to follow. This leaves tracking mode.
visualNavigator!.route = route
// VisualNavigator acts as LocationDelegate to receive location updates directly from a location provider.
// Any progress along the route is a result of getting a new location fed into the VisualNavigator.
setupLocationSource(locationDelegate: visualNavigator!, route: route)
}
// Conform to ManeuverNotificationDelegate.
func onManeuverNotification(_ text: String) {
print("ManeuverNotifications: \(text)")
}
private func setupLocationSource(locationDelegate: LocationDelegate, route: Route) {
do {
// Provides fake GPS signals based on the route geometry.
try locationSimulator = LocationSimulator(route: route,
options: LocationSimulatorOptions())
} catch let instantiationError {
fatalError("Failed to initialize LocationSimulator. Cause: \(instantiationError)")
}
locationSimulator!.delegate = locationDelegate
locationSimulator!.start()
}
```
This code excerpt will start a guidance view and it will print maneuver instructions to the console until you have reached the destination defined in the provided `route` (for the full code including declarations see the [_NavigationQuickStart_](https://github.com/heremaps/here-sdk-examples) example app.). Note that the maneuver instructions are meant to be spoken to a driver and they may contain strings like "Turn left onto Invalidenstraße in 500 meters.". More detailed maneuver instructions are also available - they are showed in the sections below.
Note that above we are using the simulation feature of the HERE SDK to acquire location updates. Of course, you can also feed real location updates into the `VisualNavigator`.
The basic principles of any navigation app are:
1. Create a `Route`. Without a route to follow you cannot start guidance.
2. Create a `VisualNavigator` instance and start rendering (or create a `Navigator` instance if you want to render the guidance view on your own).
3. Set a `Route` to the `VisualNavigator`.
4. Fed in location updates into the `VisualNavigator`. Without location data, no route progress along a route can be detected. This can be simulated like shown above - or you can feed real location updates.
As a quick start, take a look at the _NavigationQuickStart_ example app on [GitHub](https://github.com/heremaps/here-sdk-examples) and see how this works in action. If you read on, you can learn more about the many navigation features the HERE SDK has to offer.
> #### Note
> When setting a `Waypoint` you can influence on which side of the road a driver should reach the stop by setting a `sideOfStreetHint`. If a driver is moving, a bearing value can help to determine the initial direction by setting the `headingInDegrees` to a `Waypoint`. This can help to avoid unnecessary u-turns if the next destination lies in the back of a driver. Note that this can also help to optimize routes for pedestrians, for example, to avoid unnecessary street crossings.
## Use a Navigator to Listen for Guidance Events
As briefly mentioned above, before you can start to navigate to a destination, you need two things:
- A `Route` to follow. The `Route` must be set to the `Navigator` or `VisualNavigator` instance to start navigation.
- A location source that periodically tells the `Navigator` or `VisualNavigator` instance where you are.
Unless you have already calculated a route, create one: Getting a `Route` instance is shown [here](routing.md). If you only want to start the app in tracking mode, you can skip this step.
> #### Note
> During turn-by-turn navigation, you will get all `Maneuver` information from the `Navigator` or the `VisualNavigator` instance - synced with your current `Location`. As long as you navigate, do _not_ take the `Manuever` data from the `Route` object directly.
You have two choices to start guidance. Either by using the headless `Navigator` - or with the help of the `VisualNavigator`. Both provide the same interfaces, as the `Navigator` offers a subset of the `VisualNavigator`, but the `VisualNavigator` provides visual rendering assistance on top - with features such as smooth interpolation between discrete `Location` updates.
Another requirement is to provide `Location` instances - as navigation is _not_ possible without getting frequent updates on the current location. For this you can use a provider implementation that can be found on [GitHub](https://github.com/heremaps/here-sdk-examples/blob/master/examples/latest/navigate/ios/Navigation/Navigation/HEREPositioningProvider.swift).
It is possible to feed in new locations either by implementing a platform positioning solution or by using the HERE SDK positioning feature _or_ by setting up a location simulator.
The basic information flow is:
**Location Provider => Location => (Visual)Navigator => Events**
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.
First off, create a new instance of our reference implementation to acquire locations:
```swift
herePositioningProvider = HEREPositioningProvider()
```
Next, we can create a new `VisualNavigator` instance and set it as delegate to the `HEREPositioningProvider` from above. Note that the `VisualNavigator` class conforms to the `LocationDelegate` protocol that defines the `onLocationUpdated(location:)` method to receive locations.
```swift
do {
try visualNavigator = VisualNavigator()
} catch let engineInstantiationError {
fatalError("Failed to initialize VisualNavigator. Cause: \(engineInstantiationError)")
}
// Now visualNavigator will receive locations from the HEREPositioningProvider.
herePositioningProvider.startLocating(locationDelegate: visualNavigator,
accuracy: .navigation)
```
In addition, make sure to set the route you want to follow (unless you plan to be in tracking mode only):
```swift
visualNavigator.route = route
```
As a next step you may want to set a few delegates to get notified on the route progress, on the current location, on the next maneuver to take and on the route deviation:
```swift
visualNavigator.navigableLocationDelegate = self
visualNavigator.routeDeviationDelegate = self
visualNavigator.routeProgressDelegate = self
```
And here we set the conforming methods to fulfill the `RouteProgressDelegate`, the `NavigableLocationDelegate` and the `RouteDeviationDelegate` protocols:
```swift
// Conform to RouteProgressDelegate.
// Notifies on the progress along the route including maneuver instructions.
func onRouteProgressUpdated(_ routeProgress: RouteProgress) {
// [SectionProgress] is guaranteed to be non-empty.
let distanceToDestination = routeProgress.sectionProgress.last!.remainingDistanceInMeters
print("Distance to destination in meters: \(distanceToDestination)")
let trafficDelayAhead = routeProgress.sectionProgress.last!.trafficDelay
print("Traffic delay ahead in seconds: \(trafficDelayAhead)")
// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any.
let nextManeuverList = routeProgress.maneuverProgress
guard let nextManeuverProgress = nextManeuverList.first else {
print("No next maneuver available.")
return
}
let nextManeuverIndex = nextManeuverProgress.maneuverIndex
guard let nextManeuver = visualNavigator.getManeuver(index: nextManeuverIndex) else {
// Should never happen as we retrieved the next maneuver progress above.
return
}
let action = nextManeuver.action
let roadName = getRoadName(maneuver: nextManeuver)
let logMessage = "'\(String(describing: action))' on \(roadName) in \(nextManeuverProgress.remainingDistanceInMeters) meters."
if previousManeuverIndex != nextManeuverIndex {
// Log only new maneuvers and ignore changes in distance.
showMessage("New maneuver: " + logMessage)
} else {
// A maneuver update contains a different distance to reach the next maneuver.
showMessage("Maneuver update: " + logMessage)
}
previousManeuverIndex = nextManeuverIndex
}
// Conform to NavigableLocationDelegate.
// Notifies on the current map-matched location and other useful information while driving or walking.
func onNavigableLocationUpdated(_ navigableLocation: NavigableLocation) {
guard navigableLocation.mapMatchedLocation != nil else {
print("The currentNavigableLocation could not be map-matched. Are you off-road?")
return
}
let speed = navigableLocation.originalLocation.speedInMetersPerSecond
let accuracy = navigableLocation.originalLocation.speedAccuracyInMetersPerSecond
print("Driving speed: \(String(describing: speed)) plus/minus accuracy of \(String(describing: accuracy)).")
}
// Conform to RouteDeviationDelegate.
// Notifies on a possible deviation from the route.
func onRouteDeviation(_ routeDeviation: RouteDeviation) {
guard let route = visualNavigator.route else {
// May happen in rare cases when route was set to nil inbetween.
return
}
// Get current geographic coordinates.
var currentGeoCoordinates = routeDeviation.currentLocation.originalLocation.coordinates
if let currentMapMatchedLocation = routeDeviation.currentLocation.mapMatchedLocation {
currentGeoCoordinates = currentMapMatchedLocation.coordinates
}
// Get last geographic coordinates on route.
var lastGeoCoordinates: GeoCoordinates?
if let lastLocationOnRoute = routeDeviation.lastLocationOnRoute {
lastGeoCoordinates = lastLocationOnRoute.originalLocation.coordinates
if let lastMapMatchedLocationOnRoute = lastLocationOnRoute.mapMatchedLocation {
lastGeoCoordinates = lastMapMatchedLocationOnRoute.coordinates
}
} else {
print("User was never following the route. So, we take the start of the route instead.")
lastGeoCoordinates = route.sections.first?.departurePlace.originalCoordinates
}
guard let lastGeoCoordinatesOnRoute = lastGeoCoordinates else {
print("No lastGeoCoordinatesOnRoute found. Should never happen.")
return
}
let distanceInMeters = currentGeoCoordinates.distance(to: lastGeoCoordinatesOnRoute)
print("RouteDeviation in meters is \(distanceInMeters)")
}
```
Inside the `RouteProgressDelegate` 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` 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` 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.
Inside the `RouteProgressDelegate` we can also access the next maneuver that lies ahead of us. For this we use the `maneuverIndex`:
```swift
// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any.
let nextManeuverList = routeProgress.maneuverProgress
guard let nextManeuverProgress = nextManeuverList.first else {
print("No next maneuver available.")
return
}
let nextManeuverIndex = nextManeuverProgress.maneuverIndex
guard let nextManeuver = visualNavigator.getManeuver(index: nextManeuverIndex) else {
// Should never happen as we retrieved the next maneuver progress above.
return
}
```
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).
However, it can be useful to display localized street names or numbers (such as highway numbers), that can be retrieved as follows:
```swift
func getRoadName(maneuver: Maneuver) -> String {
let currentRoadTexts = maneuver.roadTexts
let nextRoadTexts = maneuver.nextRoadTexts
let currentRoadName = currentRoadTexts.names.defaultValue()
let currentRoadNumber = currentRoadTexts.numbers.defaultValue()
let nextRoadName = nextRoadTexts.names.defaultValue()
let nextRoadNumber = nextRoadTexts.numbers.defaultValue()
var roadName = nextRoadName == nil ? 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 maneuver.nextRoadType == RoadType.highway {
roadName = nextRoadNumber == nil ? nextRoadName : nextRoadNumber
}
if maneuver.action == ManeuverAction.arrive {
// We are approaching destination, so there's no next road.
roadName = currentRoadName == nil ? currentRoadNumber : currentRoadName
}
// Nil happens only in rare cases, when also the fallback above is nil.
return roadName ?? "unnamed road"
}
```
You can get the default road texts directly via `currentRoadTexts.names.defaultValue`, 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.preferredValue(for: [locale])`. If no language is available, the default language is returned.
> #### Note
> You can use the `RoadTextsDelegate` to get notified on the current `RoadTexts` you 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. This location is expected to be on a navigable path such as 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. 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`. Below we choose to use the rendering capabilities of the `VisualNavigator` to automatically update the map view.
> #### Note
> Note that the `Maneuver` instruction text (`nextManeuver.text`) is _empty_ during navigation when it is taken from `Navigator` or `VisualNavigator`. It only contains localized instructions when taken from a `Route` instance. The `ManeuverAction` enum is supposed to be used to show a visual indicator during navigation, and textual instructions fit more into a list to **preview maneuvers** before starting a trip.
>
> In opposition, `nextManeuver.roadTexts`, `nextManeuver.nextRoadTexts` and `nextManeuver.exitSignTexts` are meant to be shown as part of **turn-by-turn maneuvers** during navigation, so they are only non-empty when the `Maneuver` is taken from `Navigator` or `VisualNavigator`. If taken from a `Route` instance, these attributes are always empty.
Some roads, such as highways, do not have a name. Instead, in such cases, 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.text |
Provides a non-empty string. |
Provides an empty string. |
Example output for text : "Turn Right on Alexplatz in 100 m". |
maneuver.roadTexts |
Provies empty strings. |
Provides non-empty strings. |
Example output for roadTexts.names.defaultValue() : "Stadtring". |
maneuver.nextRoadTexts |
Provieds empty strings. |
Provides non-empty strings. |
Example output for nextRoadTexts.names.defaultValue() : "Halenseestraße". |
maneuver.exitSignTexts |
Provides empty strings. |
Provides non-empty strings. |
Example output for exitSignTexts.defaultValue() : "Hamburg". |
> #### Note
> 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 reroute users to their destination. Note that for a full route recalculation you may want to use the same route parameters. See next section for more details on how to get back to the route.
In the above example, we calculate the distance based on the coordinates contained in `RouteDeviation`: `distanceInMeters`. This 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.
Keep in mind, that in a drive guidance scenario, `lastLocationOnRoute` and `mapMatchedLocation` can be `null`. If `routeDeviation.lastLocationOnRoute` is `null`, then the user was never following the route - this can happen when the starting position is farther away from the road network. Usually, the `Navigator` / `VisualNavigator` will try to match `Location` updates to a road: If a driver is too far away, the location cannot be matched.
> #### Note
> 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.
The [Navigation example app](https://github.com/heremaps/here-sdk-examples/tree/master/examples/latest/navigate/ios/Navigation) shows how to detect the deviation.
## Listen for Road Sign Events
Along a road you can find many shields. While driving you can receive detailed notifications on these shields by setting a `RoadSignWarningDelegate`.
The resulting `RoadSignWarning` event contains information on the shield, including information such as `RoadSignType` and `RoadSignCategory`.
By default, the event will be fired with the same distance threshold as for other warners:
- On highways, the event is fired approximately 2000 meters ahead.
- On rural roads, the event is fired approximately 1500 meters ahead.
- In cities, the event is fired approximately 1000 meters ahead.
With `RoadSignWarningOptions` you can set a filter on which shields you want to get notified.
Some examples of priority road signs.
Note that not all road shields are included. `RoadSignType` lists all supported types. For example, road signs showing _speed limits_ are excluded, as these shields can be detected with the dedicated `SpeedLimitDelegate`.
The below code snippet shows a usage example:
```swift
private func setupRoadSignWarnings() {
var roadSignWarningOptions = RoadSignWarningOptions()
// Set a filter to get only shields relevant for trucks and heavyTrucks.
roadSignWarningOptions.vehicleTypesFilter = [RoadSignVehicleType.trucks, RoadSignVehicleType.heavyTrucks]
visualNavigator.roadSignWarningOptions = roadSignWarningOptions
}
...
// Conform to the RoadShieldsWarningDelegate.
// Notifies on road shields as they appear along the road.
func onRoadSignWarningUpdated(_ roadSignWarning: RoadSignWarning) {
print("Road sign distance (m): \(roadSignWarning.distanceToRoadSignInMeters)")
print("Road sign type: \(roadSignWarning.type.rawValue)")
if let signValue = roadSignWarning.signValue {
// Optional text as it is printed on the local road sign.
print("Road sign text: " + signValue.text)
}
// For more road sign attributes, please check the API Reference.
}
```
`RoadSignWarning` events are issued exactly two times:
- When `DistanceType` is `AHEAD` and `distanceToRoadSignInMeters` is > 0.
- When `DistanceType` is `PASSED` and `distanceToRoadSignInMeters` is 0.
## Handle Route Deviations
As we have seen in the above section, the `RouteDeviation` event can be used to detect when a driver leaves the original route. Note that this can happen accidentially or intentionally, for example, when a driver decides while driving to take another route to the destination - ignoring the previous made choices for a route alternaive and route options.
As shown above, you can detect the distance from the current location of the driver to the last known location on the route. Based on that distance, an application may decide whether it's time to calculate an entire new route or to guide the user back to the original route to keep the made choices for an route alternative and route options.
> #### Note
> Tip: The `RouteDeviation` event will be fired for each new location update. To avoid unnecessary handling of the event, it may be advisable to wait for a few seconds to check if the driver is still deviating. If the event is no longer fired, it means that the driver is back on the route.
The HERE SDK offers several APIs to handle route deviations:
1. Recalculate the entire route with the `RoutingEngine` with new or updated `RouteOptions` to provide _new_ route alternatives. If you use the current location of the user as new starting point, make sure to also specify a bearing direction for the first `Waypoint`.
2. Use the `returnToRoute()` method to calculate a new route to reach the originally chosen route alternative. It is available for the online `RoutingEngine` and the `OfflineRouteEngine`. Note that a route calculated with the `OfflineRouteEngine` does no longer include traffic information.
3. Refresh the old route with `routingEngine.refreshRoute()` using a new starting point that must lie on the original route and optionally update the route options. Requires a `RouteHandle` to identify the original route.
> #### Note
> On top, the HERE SDK offers the `DynamicRoutingEngine`, that allows to periodically request optimized routes based on the current traffic situation. It requires a route that was calculated online as it requires a `RouteHandle`.
The 1st and 3rd option are covered in the [Routing](routing.md) section. Note that the 3rd option to refresh the original route does not provide the path from a deviated location back to the route. Therefore, it is not covered below. However, an application may choose to use it to substract the travelled portion from the route and let users reach the new starting point on their own.
Based on parameters such as the distance and location of the deviated location an application needs to decide which option to offer to a driver.
> #### Note
> As of now, the `returnToRoute()` feature supports the same transport modes as the engine - you can use both, the `OfflineRoutingEngine` and the `RoutingEngine`. When executing the method with the `RoutingEngine`, only public transit routes are not supported - all other available transport modes for the `RoutingEngine` are supported.
Calculate a route online or offline that returns to the original route with the `RoutingEngine` or the `OfflineRoutingEngine`. Use the `returnToRoute()` method when you want to keep the originally chosen route, but want to help the driver to navigate back to the route as quickly as possible.
> #### Note
> The `returnToRoute()` of the `OfflineRoutingEngine` method requires cached or already downloaded map data. In most cases, the path back to the original route may be already cached while the driver deviated from the route. However, if the deviation is too large, consider to calculate a new route instead.
The route calculation requires the following parameters:
- The original `Route`, which is available from the `Navigator` / `VisualNavigator`.
- For use with the `OfflineRoutingEngine`, you will also need to set the normalized fraction of the route which was already travelled along the route, which is available from the `RouteDeviation` event: `routeDeviation.fractionTraveled`. For the online `RoutingEngine` this parameter is ignored. The `fractionTraveled` parameter is based on the last known location of the driver on the route. When the user left the route, no `RouteProgress` will be delivered. This value is normalized to be a value between 0 (no progress) and 1 (destination reached). The part of the route that was already travelled will be ignored by the route calculation.
- The new starting `Waypoint`, which may be the current map matched location of the driver.
The new starting point can be retrieved from the `RouteDeviation` event:
```swift
// Get current geographic coordinates.
var currentGeoCoordinates = routeDeviation.currentLocation.originalLocation.coordinates
if let currentMapMatchedLocation = routeDeviation.currentLocation.mapMatchedLocation {
currentGeoCoordinates = currentMapMatchedLocation.coordinates
}
// If too far away, consider to calculate a new route instead.
Waypoint newStartingPoint = Waypoint(currentGeoCoordinates)
```
With the online `RoutingEngine` it can happen that a completely new route is calculated - for example, when the user can reach the destination faster than with the previously chosen route alternative. The `OfflineRoutingEngine` preferrably reuses the non-travelled portion of the route.
In general, the algorithm will try to find the fastest way back to the original route, but it will also respect the distance to the destination. The new route will try to preserve the shape of the original route if possible.
Stopovers that are not already travelled will _not_ be skipped. For pass-through waypoints, there is no guarantee that the new route will take them into consideration at all.
Optionally, you can improve the route calculation by setting the heading direction of a driver:
```swift
if currentMapMatchedLocation.bearingInDegrees != nil {
newStartingPoint.headingInDegrees = currentMapMatchedLocation.bearingInDegrees
}
```
Finally, we can calculate the new route:
```swift
routingEngine.returnToRoute(originalRoute,
startingPoint: newStartingPoint,
routeFractionTraveled: routeFractionTravelled) { (routingError, routes) in
if (routingError == nil) {
let newRoute = routes?.first
// ...
} else {
// Handle error.
}
}
```
> #### Note
> Since the `CalculateRouteCompletionHandler` is reused, a list of routes is provided. However, the list will only contain one route. The error handling follows the same logic as for the `RoutingEngine`.
As a general guideline for the online and offline usage, the `returnToRoute()` feature will try to reuse the already calculated portion of the `originalRoute` that lies ahead. Traffic data is only updated and taken into account when used with the online `RoutingEngine`.
The resulting new route will also use the same `OptimizationMode` as found in the `originalRoute`.
However, for best results, it is recommended to use the online `RoutingEngine` to get traffic-optimized routes.
## Dynamically Find Better Routes
Use the `DynamicRoutingEngine` to periodically request optimized routes based on the current traffic situation. This engine searches for new routes that are faster (based on ETA) than the current route you are driving on.
By setting `DynamicRoutingEngineOptions`, you can define the `minTimeDifference` before getting notified on a better route. The `minTimeDifference` is compared to the remaining ETA of the currently set route. The `DynamicRoutingEngineOptions` also allow to set a `pollInterval` to determine how often the engine should search for better routes.
When receiving a better route, the difference to the original `route` is provided in meters and seconds:
```swift
// Conform to the DynamicRoutingDelegate.
// Notifies on traffic-optimized routes that are considered better than the current route.
func onBetterRouteFound(newRoute: Route,
etaDifferenceInSeconds: Int32,
distanceDifferenceInMeters: Int32) {
print("DynamicRoutingEngine: Calculated a new route.")
print("DynamicRoutingEngine: etaDifferenceInSeconds: \(etaDifferenceInSeconds).")
print("DynamicRoutingEngine: distanceDifferenceInMeters: \(distanceDifferenceInMeters).")
// An implementation can decide to switch to the new route:
// visualNavigator.route = newRoute
}
// Conform to the DynamicRoutingDelegate.
func onRoutingError(routingError: RoutingError) {
print("Error while dynamically searching for a better route: \(routingError).")
}
```
Based on the provided `etaDifferenceInSeconds` and `distanceDifferenceInMeters` in comparison to the current route, an application can decide if the `newRoute` should be used. If so, it can be set to the `Navigator` or `VisualNavigator` at any time.
Make sure to update the last map-matched location of the driver and set it to the `DynamicRoutingEngine` as soon as you get it - for example, as part of the `RouteProgress` or `NavigableLocation` update. This is important, so that a better route always starts close to the current location of the driver.
An example implementation for this can be found in the corresponding navigation [example app](examples.md).
To stop any ongoing navigation, call `visualNavigator.route = nil`, reset the above delegates to nil or simply call `stop()` on your location provider. More information can be found in the [stop navigation](#stop-navigation) section below.
For the full source code, please check the corresponding navigation [example app](examples.md).
## Customize the Navigation Experience
The _NavigationCustom_ example app shows how to switch to a custom `LocationIndicator` and to a different _type_ when navigation has stopped. It also shows how the navigation perspective can be customized. Find the example apps on [GitHub](https://github.com/heremaps/here-sdk-examples).
- With the `CameraBehavior` you can customize how the map view will look like during guidance. It allows to set an auto-zoom behavior with the `DynamicCameraBehavior` or a static tilt and zoom orientation with the `FixedCameraBehavior` that can be updated programmatically. It allows also other options like changing the principal point.
- With `ManeuverNotificationOptions` you can specify when TTS voice commands should be forwarded.
If you need more customization options for the map view, consider to use the `Navigator` instead of the `VisualNavigator`. With the _headless_ `Navigator`, you get the same features, but no default or customizable render options - instead, you can render the whole map view on your own - for example, if you want to have bigger route lines or any other visual customization, you can use the general rendering capabilities of the HERE SDK.
When using the `Navigator`, in order to still render a smooth map experience, you have to take care to update the map view's current target location yourself: A location provider will send new location updates only in discrete steps, which will - even when delivered with a high frequency - lead to a "jumping" map view. Therefore, it is recommended to use the `InterpolatedLocationDelegate` to get the same smoothened location updates as the `VisualNavigator`.
## Receive Waypoint Events
The `VisualNavigator` / `Navigator` classes provide 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 delegate below notifies when the destination is reached - and therefore navigation can be stopped.
- Whereas the second delegate below shows how to get notified on all types of waypoints _including_ the destination waypoint, but _excluding_ any _passThrough_ waypoints.
```swift
// Conform to DestinationReachedDelegate.
// Notifies when the destination of the route is reached.
func onDestinationReached() {
showMessage("Destination reached. Stopping turn-by-turn navigation.")
stopNavigation()
}
// Conform to MilestoneStatusDelegate.
// Notifies when a waypoint on the route is reached or missed.
func onMilestoneStatusUpdated(milestone: Milestone, status: MilestoneStatus) {
if milestone.waypointIndex != nil && status == MilestoneStatus.reached {
print("A user-defined waypoint was reached, index of waypoint: \(String(describing: milestone.waypointIndex))")
print("Original coordinates: \(String(describing: milestone.originalCoordinates))")
} else if milestone.waypointIndex != nil && status == MilestoneStatus.missed {
print("A user-defined waypoint was missed, index of waypoint: \(String(describing: milestone.waypointIndex))")
print("Original coordinates: \(String(describing: milestone.originalCoordinates))")
} else if milestone.waypointIndex == nil && status == MilestoneStatus.reached {
// For example, when transport mode changes due to a ferry a system-defined waypoint may have been added.
print("A system-defined waypoint was reached, index of waypoint: \(String(describing: milestone.mapMatchedCoordinates))")
} else if milestone.waypointIndex == nil && status == MilestoneStatus.missed {
// For example, when transport mode changes due to a ferry a system-defined waypoint may have been added.
print("A system-defined waypoint was missed, index of waypoint: \(String(describing: milestone.mapMatchedCoordinates))")
}
}
```
The `onMilestoneStatusUpdated()` method provides a `Milestone` instance that contains the information about the passed or missed waypoints along the route. Note that only _stopover_ waypoints are included. Also, the destination waypoint is included and any other stopover waypoint that was added by a user. In addition, waypoints added by the HERE SDK are included, for example, when there is a need to take a ferry. However, the first waypoint - which is the starting point of your trip - is excluded. Waypoints of type _passThrough_ are also excluded.
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.
The `MilestoneStatus` enum indicates if the corresponding `Milestone` has been _reached_ or _missed_.
## Receive Speed Limit Events
By implementing the `SpeedLimitDelegate` you can receive events on the speed limits that are available along a road. These can be the speed limits as indicated on the local signs, as well as warnings on special speed situations, like for example, speed limits that are only valid for specific weather conditions.
Speed limits that are marked as _conditional_ may be time-dependent. For example, speed limits for school zones can be valid only for a specific time of the day. In this case, the HERE SDK compares the device time with the time range of the speed limit. If the speed limit is currently valid, it will be propagated as event, otherwise not.
> #### Note
> The provided speed limits are only valid for cars.
An implementation example can be found in the _Navigation_ example app you can find on [GitHub](https://github.com/heremaps/here-sdk-examples):
```swift
// Conform to SpeedLimitDelegate.
// Notifies on the current speed limit valid on the current road.
func onSpeedLimitUpdated(_ speedLimit: SpeedLimit) {
let speedLimit = getCurrentSpeedLimit(speedLimit)
if speedLimit == nil {
print("Warning: Speed limits unknown, data could not be retrieved.")
} else if speedLimit == 0 {
print("No speed limits on this road! Drive as fast as you feel safe ...")
} else {
print("Current speed limit (m/s): \(String(describing: speedLimit))")
}
}
private func getCurrentSpeedLimit(_ speedLimit: SpeedLimit) -> Double? {
// Note that all values can be nil if no data is available.
// The regular speed limit if available. In case of unbounded speed limit, the value is zero.
print("speedLimitInMetersPerSecond: \(String(describing: speedLimit.speedLimitInMetersPerSecond))")
// A conditional school zone speed limit as indicated on the local road signs.
print("schoolZoneSpeedLimitInMetersPerSecond: \(String(describing: speedLimit.schoolZoneSpeedLimitInMetersPerSecond))")
// A conditional time-dependent speed limit as indicated on the local road signs.
// It is in effect considering the current local time provided by the device's clock.
print("timeDependentSpeedLimitInMetersPerSecond: \(String(describing: speedLimit.timeDependentSpeedLimitInMetersPerSecond))")
// A conditional non-legal speed limit that recommends a lower speed,
// for example, due to bad road conditions.
print("advisorySpeedLimitInMetersPerSecond: \(String(describing: speedLimit.advisorySpeedLimitInMetersPerSecond))")
// A weather-dependent speed limit as indicated on the local road signs.
// The HERE SDK cannot detect the current weather condition, so a driver must decide
// based on the situation if this speed limit applies.
print("fogSpeedLimitInMetersPerSecond: \(String(describing: speedLimit.fogSpeedLimitInMetersPerSecond))")
print("rainSpeedLimitInMetersPerSecond: \(String(describing: speedLimit.rainSpeedLimitInMetersPerSecond))")
print("snowSpeedLimitInMetersPerSecond: \(String(describing: speedLimit.snowSpeedLimitInMetersPerSecond))")
// For convenience, this returns the effective (lowest) speed limit between
// - speedLimitInMetersPerSecond
// - schoolZoneSpeedLimitInMetersPerSecond
// - timeDependentSpeedLimitInMetersPerSecond
return speedLimit.effectiveSpeedLimitInMetersPerSecond()
}
```
Note that speed limits depend on the specified transport mode. Currently, the HERE SDK differentiates for _cars_ and _trucks_ based on the legal customer vehicle regulations per country (CVR). That means, the above `SpeedLimit` event can indicate a lower speed limit for trucks: For example, on a highway, the speed limit will be at most [80 km/h in Germany](https://ec.europa.eu/transport/road_safety/going_abroad/germany/speed_limits_en.htm) - while for cars there may be a speed limit indicated that is 130 km/h or higher. Use map version 32 or higher to get CVR speed limits. On lower map versions trucks will receive the same speed limits as cars. Note that the map version can be updated with the `MapUpdater` - even if there are no downloaded regions - as navigation will only request the map data of the same version that is currently stored into the map cache. Therefore, keep in mind that this applies to both, online _and_ offline ussage.
> #### Info
> For trucks, we recommend to also specify the `TruckSpecifications` inside the `RouteOptions`. The properties `grossWeightInKilograms` and `weightInKilograms` can have an impact on the speed limit for trucks. These values are not only used for route calculation, but are also retrieved internally from the route to decide if the truck's weight is below or above 3.5 t. For most countries this has an impact on the legally allowed speed limit. If no weight is set, only the legally highest allowed speed limits for trucks will be forwarded - as the HERE SDK will then assume the truck's weight is very low.
## Receive Speed Warning Events
Although you can detect when you exceed speed limits yourself when you receive a new speed limit event (see above), there is a more convenient solution that can help you implement a speed warning feature for your app.
> #### Note
> This does not warn when temporary speed limits such as weather-dependent speed limits are exceeded.
`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:
```swift
// Conform to SpeedWarningDelegate.
// Notifies when the current speed limit is exceeded.
func onSpeedWarningStatusChanged(_ status: SpeedWarningStatus) {
if status == SpeedWarningStatus.speedLimitExceeded {
// Driver is faster than current speed limit (plus an optional offset).
// Play a notification sound to alert the driver.
// Note that this may not include temporary special speed limits, see SpeedLimitDelegate.
AudioServicesPlaySystemSound(SystemSoundID(1016))
}
if status == SpeedWarningStatus.speedLimitRestored {
print("Driver is again slower than current speed limit (plus an optional offset).")
}
}
```
> #### Note
> 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.
A `SpeedWarningStatus` is only delivered once the current speed is exceeded or when it is restored again - for example, when a driver is constantly driving too fast, only one event is fired.
`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 `speedLimitRestored` 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 the other for higher speed limits. The boundary is defined by `highSpeedBoundaryInMetersPerSecond`:
```swift
private func setupSpeedWarnings() {
let speedLimitOffset = SpeedLimitOffset(lowSpeedOffsetInMetersPerSecond: 2,
highSpeedOffsetInMetersPerSecond: 4,
highSpeedBoundaryInMetersPerSecond: 25)
visualNavigator.speedWarningOptions = SpeedWarningOptions(speedLimitOffset: 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 the example values used above,
- if the speed limit on the road is 27 m/s, the (high) speed offset used is 4 m/s. This means we will only receive a warning notification when we are driving above 31 m/s = 27 m/s + 4 m/s. The `highSpeedOffsetInMetersPerSecond` is used, as the current speed limit is greater than `highSpeedBoundaryInMetersPerSecond`.
- if the speed limit on the road is 20 m/s, the (low) speed offset used is 2 m/s. This means we will only receive a warning notification when we are driving above 22 m/s = 20 m/s + 2 m/s. The `lowSpeedOffsetInMetersPerSecond` is used, as the current speed limit is smaller than `highSpeedBoundaryInMetersPerSecond`.
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.
> #### Note
> Call `navigator.trackingTransportProfile(vehicleProfile: vehicleProfile)` and set a `VehicleProfile` with e.g. `truck` transport mode if you are a truck driver. By default, `car` is assumed and you will only receive speed limits that are valid for cars. Make sure to specify other vehicle properties like weight according to your vehicle.
## Receive Safety Cameras Events
You can attach a `SafetyCameraWarningDelegate` to the `Navigator` or `VisualNavigator` to get notfied on `SafetyCameraWarning` events that inform on cameras that detect the speed of a driver.
For most countries, this includes only permanently installed cameras. The HERE SDK does not inform whether the cameras are currently active - or not.
Getting notifications on safety cameras - also know as "speed cameras" - is _not_ available for all countries, due to the local laws and regulations. Note that for some countries, like in France, precise location information for speed cameras is disallowed by law: Instead, here the notifications can only be given with less accuracy to meet the governmental guidelines. For most countries, however, precise location information is allowed.
As of now, the below listed countries are supported.
### Coverage for Safety Cameras
- United States of America
- United Kingdom of Great Britain and Northern Ireland
- United Arab Emirates
- Turkey
- Thailand
- Taiwan
- Sweden
- Spain
- South Africa
- Slovenia
- Slovakia
- Singapore
- Serbia
- Saudi Arabia
- Russian Federation
- Romania
- Qatar
- Portugal
- Poland
- Oman
- Norway
- Netherlands
- Mexico
- Malaysia
- Macao
- Luxembourg
- Lithuania
- Latvia
- Kuwait
- Kazakhstan
- Italy
- Israel
- Isle of Man
- Iceland
- Hungary
- Hong Kong
- Greece
- France
- Finland
- Estonia
- Denmark
- Czechia
- Cyprus
- Croatia
- Chile
- Canada
- Bulgaria
- Brazil
- Bosnia and Herzegovina
- Belgium
- Belarus
- Bahrain
- Azerbaijan
- Austria
- Argentina
- Andorra
## Get Road Attributes
By implementing the `RoadAttributesDelegate` you can receive events on the road attributes. The events are fired whenever an attribute changes - while you are traveling on that road.
```swift
// Conform to the RoadAttributesDelegate.
// Notifies on the attributes of the current road including usage and physical characteristics.
func onRoadAttributesUpdated(_ roadAttributes: RoadAttributes) {
// This is called whenever any road attribute has changed.
// If all attributes are unchanged, no new event is fired.
// Note that a road can have more than one attribute at the same time.
print("Received road attributes update.")
if (roadAttributes.isBridge) {
// Identifies a structure that allows a road, railway, or walkway to pass over another road, railway,
// waterway, or valley serving map display and route guidance functionalities.
print("Road attributes: This is a bridge.")
}
if (roadAttributes.isControlledAccess) {
// Controlled access roads are roads with limited entrances and exits that allow uninterrupted
// high-speed traffic flow.
print("Road attributes: This is a controlled access road.")
}
if (roadAttributes.isDirtRoad) {
// Indicates whether the navigable segment is paved.
print("Road attributes: This is a dirt road.")
}
if (roadAttributes.isDividedRoad) {
// Indicates if there is a physical structure or painted road marking intended to legally prohibit
// left turns in right-side driving countries, right turns in left-side driving countries,
// and U-turns at divided intersections or in the middle of divided segments.
print("Road attributes: This is a divided road.")
}
if (roadAttributes.isNoThrough) {
// Identifies a no through road.
print("Road attributes: This is a no through road.")
}
if (roadAttributes.isPrivate) {
// Private identifies roads that are not maintained by an organization responsible for maintenance of
// public roads.
print("Road attributes: This is a private road.")
}
if (roadAttributes.isRamp) {
// Range is a ramp: connects roads that do not intersect at grade.
print("Road attributes: This is a ramp.")
}
if (roadAttributes.isRightDrivingSide) {
// Indicates if vehicles have to drive on the right-hand side of the road or the left-hand side.
// For example, in New York it is always true and in London always false as the United Kingdom is
// a left-hand driving country.
print("Road attributes: isRightDrivingSide = \(roadAttributes.isRightDrivingSide)")
}
if (roadAttributes.isRoundabout) {
// Indicates the presence of a roundabout.
print("Road attributes: This is a roundabout.")
}
if (roadAttributes.isTollway) {
// Identifies a road for which a fee must be paid to use the road.
print("Road attributes change: This is a road with toll costs.")
}
if (roadAttributes.isTunnel) {
// Identifies an enclosed (on all sides) passageway through or under an obstruction.
print("Road attributes: This is a tunnel.")
}
}
```
An implementation example can be found in the _Navigation_ example app you can find on [GitHub](https://github.com/heremaps/here-sdk-examples).
The HERE SDK itself is not reacting on such events as `roadAttributes.isTunnel`. An application may decide to switch to a night map scheme as long as `isTunnel` is true. Internally, the HERE SDK is using a tunnel interpolation algorithm to provide this detection - as usually the GPS signal is very weak or even lost while being in a tunnel.
## Get Lane Assistance
The HERE SDK provides lane recommendations to help a driver to stay on the route. When no `Route` is set, no lane assistance is provided.
Two independent delegates can be set to obtain the following events before reaching a junction (including intersections and roundabouts):
- `ManeuverViewLaneAssistance`: Provides a list of `Lane` recommendations if the next route maneuver takes place at a junction - regardless if the junction is considered complex or not.
- `JunctionViewLaneAssistance`: Provides a list of `Lane` recommendations only for complex junctions - regardless if a maneuver takes place at the junction or not. This event is _not_ delivered for non-complex junctions.
A complex junction is defined as follows:
- The junction has at least a bifurcation.
- The junction has at least two lanes whose directions do not follow the current route.
Both events can be delivered for the same junction or for different ones. A `Lane` instance contains information such as the available lanes on the current road, their direction category and whether the lane is recommended or not.
Both events are fired 300 meters ahead of a junction for non-highways and 1300 meters ahead of a junction on highways. However, for now the distance to the next complex junction is not exposed as part of the `JunctionViewLaneAssistance` event. For `ManeuverViewLaneAssistance`, the distance is available as part of the distance to the next maneuver which is available via the `RouteProgress` event.
Each lane can lead to multiple directions stored in `LaneDirectionCategory`:
- `straight`: A lane that goes straight up.
- `slightlyLeft`: A lane that goes slightly left around 45 degrees.
- `slightlyRight`: A lane that goes slightly right around 45 degrees.
- `quiteLeft`: A lane that goes quite left around 90 degrees.
- `quiteRight`: A lane that goes quite right around 90 degrees.
- `hardLeft`: A lane that goes hard left around 135 degrees.
- `hardRight`: A lane that goes hard right around 135 degrees.
- `uTurnLeft`: A lane that makes a left u-turn around 180 degrees.
- `uTurnRight`: A lane that makes a right u-turn around 180 degrees.
Note that all members can be `true` or `false` at the same time. Theoretically, all members can be `true` when the lane leads to all multiple directions. Most lanes, however, lead to one or two directions, for example, `quiteLeft` and `quiteRight` will be `true` when the lane splits up into two separate lanes.
To give visual feedback for the driver, it is recommended to create one transparent image asset for each of the nine possible directions. Each image can then be used as an overlay and several images can be blended into one lane pictogram that indicates the possible directions per lane on a road.
Most importantly, while the vehicle is traveling along the route, you can tell the driver which lane to take: This information is stored in the `Lane.recommendationState` and it is recommended to _highlight_ the pictogram of the recommended lane.
Illustration: Example of a possible visualization for a road with three lanes where the two leftmost roads lead to the next maneuver.
Note that the lane assistance information does _not_ contain the lanes of the contraflow, instead it only describes the lanes of the current driving direction. The list of lanes is always ordered from the leftmost lane (index 0) to the rightmost lane (last index) of the road.
This way, lane assistance works the same for both, left-hand _and_ righ-hand driving countries.
> #### Note
> Check `roadAttributes.isRightDrivingSide` to know if you are in a left-hand driving country. Maneuver instructions and other notifications automatically adapt to the country. For lane assistance, the code will work the same, regardless of the country, as the list of lanes is always ordered from left - starting with index 0 - to right.
It is recommended to show `ManeuverViewLaneAssistance` events immediately when the event is received. The event is synchronized with the `ManeuverNotificationDelegate` to receive voice guidance events.
Lane information provided by `JunctionViewLaneAssistance` events is recommended to be shown in a separate UI area indicating that there is an upcoming complex junction that needs attention.
### Get Lane Recommendations For Maneuvers at a Junction with ManeuverViewLaneAssistance
The `ManeuverViewLaneAssistance` event provides the recommended lanes at a junction where a maneuver takes place. On the map this maneuver is visualized by a maneuver arrow when the `VisualNavigator` is rendering the `MapView`. The location of the junction can be retrieved from the next `Maneuver` that is available as part of the `RouteProgress` event.
Each `ManeuverViewLaneAssistance` event is synchronized with the corresponding maneuver voice notification as sent by the `ManeuverNotificationDelegate`: This means that for most roads, the event arrives simultaneously and at the same frequency as the maneuver voice notification text that describes the next maneuver with the distance to the junction. As described below, this event can be used for a TTS engine to speak the maneuver message to the driver.
Similar to the other events described above, you can attach a `ManeuverViewLaneAssistanceDelegate` to the `Navigator` or `VisualNavigator`. The resulting `ManeuverViewLaneAssistance` object contains information about the available lanes on the current road and information such as their directions.
The following code snippet shows how to retrieve the information which lanes to take:
```swift
// Conform to the ManeuverViewLaneAssistanceDelegate.
// Notifies which lane(s) lead to the next (next) maneuvers.
func onLaneAssistanceUpdated(_ laneAssistance: ManeuverViewLaneAssistance) {
// This lane list is guaranteed to be non-empty.
let lanes = laneAssistance.lanesForNextManeuver
logLaneRecommendations(lanes)
let nextLanes = laneAssistance.lanesForNextNextManeuver
if !nextLanes.isEmpty {
print("Attention, the next next maneuver is very close.")
print("Please take the following lane(s) after the next maneuver: ")
logLaneRecommendations(nextLanes)
}
}
private func logLaneRecommendations(_ lanes: [Lane]) {
// The lane at index 0 is the leftmost lane adjacent to the middle of the road.
// The lane at the last index is the rightmost lane.
var laneNumber = 0
for lane in lanes {
// This state is only possible if laneAssistance.lanesForNextNextManeuver is not empty.
// For example, when two lanes go left, this lanes leads only to the next maneuver,
// but not to the maneuver after the next maneuver, while the highly recommended lane also leads
// to this next next maneuver.
if lane.recommendationState == .recommended {
print("Lane \(laneNumber) leads to next maneuver, but not to the next next maneuver.")
}
// If laneAssistance.lanesForNextNextManeuver is not empty, this lane leads also to the
// maneuver after the next maneuver.
if lane.recommendationState == .highlyRecommended {
print("Lane \(laneNumber) leads to next maneuver and eventually to the next next maneuver.")
}
if lane.recommendationState == .notRecommended {
print("Do not take lane \(laneNumber) to follow the route.")
}
laneNumber += 1
}
}
```
The `laneAssistance.lanesForNextNextManeuver` is normally an empty list, but there may be cases when two maneuvers are very close. In such cases, this list holds the information for the lanes to take immediately after the current maneuver is reached.
Until the next maneuver is reached, the information about the lanes to take is valid. It should be hidden once the next maneuver is reached or replaced by the information contained in any new `ManeuverViewLaneAssistance` event:
```swift
// See above code snippet for the RouteProgressDelegate.
if previousManeuverIndex != nextManeuverIndex {
// A new maneuver: Remove stale lane assistance info.
}
```
View the code for the `RouteProgressDelegate` above and you can find how to get the `nextManeuverIndex`, which will tell you when a new maneuver has to be taken.
### Get Lane Recommendations For Complex Junctions with JunctionViewLaneAssistance
In addition to `ManeuverViewLaneAssistance` (see above), the HERE SDK provides `JunctionViewLaneAssistance` events that notify on the available lanes at complex junctions - even if there is no actual maneuver happening at that junction. These notifications work in parallel to `ManeuverViewLaneAssistance`, but will _only_ fire before reaching a _complex_ junction (see above).
In comparison to `ManeuverViewLaneAssistance`, the `JunctionViewLaneAssistance` event can recommend _more_ lanes to safely pass a complex junction - but not every of those lanes may lead to the next maneuver after passing the junction.
Unlike `ManeuverViewLaneAssistance`, you can detect when the junction has been passed by checking the list if it is empty or not:
```swift
// Conform to the JunctionViewLaneAssistanceDelegate.
// Notfies which lane(s) lead to the next maneuvers at complex junctions.
func onLaneAssistanceUpdated(_ laneAssistance: JunctionViewLaneAssistance) {
let lanes = laneAssistance.lanesForNextJunction
if (lanes.isEmpty) {
print("You have passed the complex junction.")
} else {
print("Attention, a complex junction is ahead.")
logLaneRecommendations(lanes)
}
}
```
When the complex junction has been passed, it is recommended to update the UI of your app to remove the lane information. `JunctionViewLaneAssistance` events can be considered as an _additional_ hint which lanes to take at complex junctions - especially, when no maneuver takes places at such junctions, because this information is not provided with the `ManeuverViewLaneAssistance` event.
Keep in mind, that without a route to follow, you will not get any lane assistance related events.
## Truck Guidance
The HERE SDK supports premium truck routing and guidance with a variety of features. For example, during navigation you can attach a delegate to get notified on truck restrictions ahead, such as narrow tunnels. Other examples of possible restrictions can be bridges that are not high enough to be passed by a bigger truck or roads where the weight of the truck is beyond the permissible weight of the road.
See the following code snippet:
```swift
// Conform to the TruckRestrictionsWarningDelegate.
// Notifies truck drivers on road restrictions ahead.
func onTruckRestrictionsWarningUpdated(_ restrictions: [TruckRestrictionWarning]) {
// The list is guaranteed to be non-empty.
for truckRestrictionWarning in restrictions {
if truckRestrictionWarning.distanceType == DistanceType.ahead {
print("TruckRestrictionWarning ahead in \(truckRestrictionWarning.distanceInMeters) meters.")
} else if truckRestrictionWarning.distanceType == DistanceType.reached {
print("A restriction has been reached.")
} else if truckRestrictionWarning.distanceType == DistanceType.passed {
// If not preceded by a "reached"-notification, this restriction was valid only for the passed location.
print("A restriction was just passed.")
}
// One of the following restrictions applies, if more restrictions apply at the same time,
// they are part of another TruckRestrictionWarning element contained in the list.
if truckRestrictionWarning.weightRestriction != nil {
let type = truckRestrictionWarning.weightRestriction!.type
let value = truckRestrictionWarning.weightRestriction!.valueInKilograms
print("TruckRestriction for weight (kg): \(type): \(value)")
} else if truckRestrictionWarning.dimensionRestriction != nil {
// Can be either a length, width or height restriction of the truck. For example, a height
// restriction can apply for a tunnel. Other possible restrictions are delivered in
// separate TruckRestrictionWarning objects contained in the list, if any.
let type = truckRestrictionWarning.dimensionRestriction!.type
let value = truckRestrictionWarning.dimensionRestriction!.valueInCentimeters
print("TruckRestriction for dimension: \(type): \(value)")
} else {
print("TruckRestriction: General restriction - no trucks allowed.")
}
}
}
```
The `DistanceType.reached` notifies when a truck restriction has been reached. The event is followed by `passed`, when the restriction has been passed. If the restriction has no length, then `reached` is skipped and only a `reached` event is sent. Note that the `ahead` event is always sent first.
If all restrictions are nil, then a _general_ truck restriction applies. The type of the restriction can be also seen from the `TruckRestrictionWarningType`.
> #### Note
> When guidance is stopped by setting a null route _or_ a new route, then any restriction that was announced with an `ahead` notification, will instantly result in a `passed` event to clear pending restriction warnings. While following a route - any restriction that lies _not_ on the route is filtered out, but as soon as a driver deviates far enough (more than 15 meters) from a route, then supported restrictions ahead on the current road will lead again to restriction warnings.
The notification thresholds for truck restrictions differ slightly from other warners:
- In cities, the `ahead` event is sent 500 m ahead (instead of 1000 m ahead).
- On rural roads, the event is sent 750 m ahead (instead of 1500 m ahead).
- On highways, the event is sent 1500 m ahead (instead of 2000 m ahead).
The `TruckRestrictionWarning` event is based on the map data of the road network ahead. It delivers restrictions regardless of the currently set `TransportMode`.
> #### Note
> When calculating a route, you can specify `TruckOptions` including `TruckSpecifications`. This may have an influence on the resulting `Route`. However, it does _not_ influence the `TruckRestrictionWarning` event: Most restrictions found in the map data ahead are forwarded. Therefore, it may make sense for an application to filter out restriction warnings that are not relevant for the current vehicle. Note that this event is also delivering events in tracking mode when there is no route to follow.
More details on truck routing are given in the [routing](routing.md) section. For example, there you can find how to calculate a route specifically for trucks. In general, if a route contains the `Truck` transportation type, it is optimized for trucks.
In addition, you can specify several avoidance options, for example, to exclude certain city areas. All this can be specified before the route gets calculated and passed into the `Navigator` or `VisualNavigator`.
Worth to mention are also the following features:
- You can specify vehicle restrictions such as truck dimensions or if a truck is carrying hazardous goods via `TruckOptions` that can contain `TruckSpecifications` and `HazardousGood` lists. With this information you can shape the truck route. To get notified on upcoming truck restrictions, listen to the `TruckRestrictionWarning` event as shown above.
- You can listen for certain `RoadAttributes` as explained [above](navigation.md#get-road-attributes).
- When transport mode is set to `truck`, `SpeedLimit` events will indicate the customer vehicle regulated (CVR) speed limits that may be lower than for cars. For this, call `navigator.trackingTransportProfile(vehicleProfile: vehicleProfile)` and set a `VehicleProfile` with `truck` transport mode. By default, `car` is assumed. Make sure to specify other vehicle properties like weight according to your truck.
- Worth to mention, `grossWeightInKilograms` and `weightInKilograms` will effect CVR speed limits, as well as route restrictions and the estimated arrival time. Without setting proper `TruckSpecifications`, routes and notfifcations may be inapprobiate.
- You can exclude emission zones to not pollute the air in sensible inner city areas via `AvoidanceOptions`. With this you can also avoid certain `RoadFeatures` like tunnels. Those can be set via `TruckOptions` and are then excluded from route calculation.
- You can enable a map layer scheme that shows safety camera icons on the map: `MapScene.Layers.safetyCameras`. Note: This layer is also suitable for cars.
- You can enable a map layer scheme that is optimized to show truck-specific information on the map: `MapScene.Layers.vehicleRestrictions`. It offers several `MapFeatureModes`, for example, to highlight _active_ and _inactive_ restrictions as purple lines on an affected road - a gray line or a gray icon means that the restriction is inactive. If a road is crossing such a purple line - and the road itself is not indicated as purple - then this restriction does not apply on the current road. Note that an icon does not necessarily indicate an exact location: For example, in case of a restricted road an icon may be placed centered on the restricted road - or, if the restriction is longer, the icon may be repeated several times for the same restriction along one or several roads. The icon itself is localized per country and represents the type of restriction. For most restrictions, the location and the type of the restriction is also indicated through the `TruckRestrictionWarning` event (as shown above).
MapScene.Layers.vehicleRestrictions
## 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 plan to use an implementation that allows to switch between native location data from the device _and_ simulated location data for test drives.
As already mentioned above, the `VisualNavigator` conforms to the `LocationDelegate` protocol, so it can be used as delegate for classes that call `onLocationUpdated(location:)`.
> #### Note
> 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 the `herePositioningProvider`:
```swift
herePositioningProvider.startLocating(locationDelegate: visualNavigator,
accuracy: .navigation)
```
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.
Note that the `bearing` value taken from the `Location` object determines the _direction of movement_ which is then indicated by the `LocationIndicator` asset that rotates into that direction. When the user is not moving, then the last rotation is kept until a new bearing value is set. Depending on the source for the `Location` data, this value can be more or less accurate.
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](https://github.com/heremaps/here-sdk-examples/blob/master/examples/latest/navigate/ios/Navigation/Navigation/HEREPositioningProvider.swift).
## 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` as an alternative provider to allow switching between real location updates and simulated ones.
```swift
import heresdk
// A class that provides simulated location updates along a given route.
// The frequency of the provided updates can be set via LocationSimulatorOptions.
class HEREPositioningSimulator {
private var locationSimulator: LocationSimulator?
func startLocating(locationDelegate: LocationDelegate, route: Route) {
if let locationSimulator = locationSimulator {
locationSimulator.stop()
}
locationSimulator = createLocationSimulator(locationDelegate: locationDelegate, route: route)
locationSimulator!.start()
}
func stopLocating() {
if locationSimulator != nil {
locationSimulator!.stop()
locationSimulator = nil
}
}
// Provides fake GPS signals based on the route geometry.
private func createLocationSimulator(locationDelegate: LocationDelegate,
route: Route) -> LocationSimulator {
let notificationIntervalInSeconds: TimeInterval = 0.5
let locationSimulatorOptions = LocationSimulatorOptions(speedFactor: 2,
notificationInterval: notificationIntervalInSeconds)
let locationSimulator: LocationSimulator
do {
try locationSimulator = LocationSimulator(route: route,
options: locationSimulatorOptions)
} catch let instantiationError {
fatalError("Failed to initialize LocationSimulator. Cause: \(instantiationError)")
}
locationSimulator.delegate = locationDelegate
locationSimulator.start()
return locationSimulator
}
}
```
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 does not contain enough coordinates for the specified time interval, additional location events will be interpolated.
The code below shows how you can seamlessly switch between simulated _and_ real locations by calling `enableRoutePlayback(route:)` and `enableDevicePositioning()`:
```swift
// Provides location updates based on the given route.
func enableRoutePlayback(route: Route) {
herePositioningProvider.stopLocating()
herePositioningSimulator.startLocating(locationDelegate: visualNavigator, route: route)
}
// Provides location updates based on the device's GPS sensor.
func enableDevicePositioning() {
herePositioningSimulator.stopLocating()
herePositioningProvider.startLocating(locationDelegate: visualNavigator,
accuracy: .navigation)
}
```
Note that we need to ensure to stop any ongoing simulation or real location source before starting a new one.
You can see the code from above included in the _Navigation_ example app on [GitHub](https://github.com/heremaps/here-sdk-examples/blob/master/examples/latest/navigate/ios/Navigation/Navigation/NavigationExample.swift).
## 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.
> #### Note
> Maneuver notifications are targeted at drivers. It is not recommended to use them for pedestrian guidance.
A few example notifications:
```xml
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 `ManeuverNotificationDelegate`:
```swift
visualNavigator.maneuverNotificationDelegate = self
...
// Conform to ManeuverNotificationDelegate.
// Notifies on voice maneuver messages.
func onManeuverNotification(_ text: String) {
voiceAssistant.speak(message: text)
}
```
Here we use a helper class called `VoiceAssistant` that wraps a Text-To-Speech engine to speak the maneuver notification. The engine uses Apple's `AVSpeechSynthesizer` class. If you are interested, you can find this class as part of the _Navigation_ example app on [GitHub](examples.md).
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. For more `ManeuverNotificationOptions` consult the _API Reference_.
```swift
private func setupVoiceGuidance() {
let ttsLanguageCode = getLanguageCodeForDevice(supportedVoiceSkins: VisualNavigator.availableLanguagesForManeuverNotifications())
visualNavigator.maneuverNotificationOptions = ManeuverNotificationOptions(language: ttsLanguageCode,
unitSystem: UnitSystem.metric)
// Set language to our TextToSpeech engine.
let locale = LanguageCodeConverter.getLocale(languageCode: ttsLanguageCode)
if voiceAssistant.setLanguage(locale: locale) {
print("TextToSpeech engine uses this language: \(locale)")
} else {
print("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:
```swift
private func getLanguageCodeForDevice(supportedVoiceSkins: [heresdk.LanguageCode]) -> LanguageCode {
// 1. Determine if preferred device language is supported by our TextToSpeech engine.
let identifierForCurrenDevice = Locale.preferredLanguages.first!
var localeForCurrenDevice = Locale(identifier: identifierForCurrenDevice)
if !voiceAssistant.isLanguageAvailable(identifier: identifierForCurrenDevice) {
print("TextToSpeech engine does not support: \(identifierForCurrenDevice), falling back to en-US.")
localeForCurrenDevice = Locale(identifier: "en-US")
}
// 2. Determine supported voice skins from HERE SDK.
var languageCodeForCurrenDevice = LanguageCodeConverter.getLanguageCode(locale: localeForCurrenDevice)
if !supportedVoiceSkins.contains(languageCodeForCurrenDevice) {
print("No voice skins available for \(languageCodeForCurrenDevice), falling back to enUs.")
languageCodeForCurrenDevice = LanguageCode.enUs
}
return languageCodeForCurrenDevice
}
```
Note that the HERE SDK supports 37 languages. You can query the languages from the `VisualNavigator` with `VisualNavigator.availableLanguagesForManeuverNotifications()`. 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](examples.md).
> #### Note
> 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
- Serbian: sr-CS_anonymous_compact
- Swedish: sv-SE_alva_compact
- Thai: th-TH_kanya_compact
- Turkish: tr-TR_cem_compact
- Ukrainian: uk-UA_anonymous_compact
- Vietnamese: vi-VN_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
Open the HERE SDK framework and search for the `voice_assets` folder. If you want to shrink the size of the framework, you can remove the voice packages you do not need.
### Spatial Audio Maneuver Notifications
The same `voiceText` as provided by the `ManeuverNotificationDelegate` (see above) can be also enhanced with spatial audio information.
Spatial audio maneuver notifications allow to adjust the stereo panorama of the text-to-speech strings in real-time. This happens based on the maneuver location in relation to a driver sitting in a vehicle.
For this, use the `SpatialManeuverNotificationDelegate` instead (or in parallel) of the `ManeuverNotificationDelegate`. It triggers notifications when spatial maneuvers are available. In addition, add a `SpatialManeuverAzimuthDelegate` to trigger the azimuth elements which compose one of the spatial audio trajectories defined by the HERE SDK. The resulting `SpatialTrajectoryData` contains the next azimuth angle to be used and it indicates wether the spatial audio trajectory has finished or not.
Use `SpatialManeuverAudioCuePanning` to start panning and pass `CustomPanningData` to update the `estimatedAudioCueDuration` of the `SpatialManeuver` and to customize its `initialAzimuthInDegrees` and `sweepAzimuthInDegrees` properties.
## 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 `nil`. 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 delegates individually to `nil`.
You can reuse your location provider implementation to consume location updates in your app. With HERE positioning you can set multiple `LocationDelegate` instances.
When you use the `VisualNavigator`, call `stopRendering()`. Once called, the `MapView` will be no longer under control by the `VisualNavigator`:
- Settings, like map orientation, camera distance or tilt, which may have been altered during rendering are no longer updated. They will keep the last state before `stopRendering()` was called. For example, if the map was tilted during guidance, it will stay tilted. Thus, it is recommended to apply the desired camera settings after `stopRendering()` is called.
- The map will no longer move to the current location - even if you continue to feed new locations into the `VisualNavigator`.
- The default or custom location indicator owned by the `VisualNavigator` will be hidden again.
- Note that all location-based events such as the `RouteProgress` will be still delivered unless you unsubscribe by setting a nil delegate - see above.
> #### Note
> Since the `VisualNavigator` operates on a `MapView` instance, it is recommended to call `stopRendering()` before deinitializing a `MapView`. In addition, it is recommended to stop `LocationSimulator` and `DynamicRoutingEngine` in case they were started before.
## 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 all transport modes - except for public transit. Public transit routes may lead to unsafe and unexpected results when being used for tracking. Although all other transport modes are supported, tracking is most suitable for _car_ and _truck_ transport modes.
To enable tracking, all you need is to call:
```swift
visualNavigator.route = nil
herePositioningProvider.startLocating(locationDelegate: visualNavigator,
accuracy: .navigation)
```
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 nil.
> #### Note
> Note that in tracking mode you only get events for delegates such as the `NavigableLocationDelegate` or the `SpeedWarningDelegate` that can fire without the need for a route to follow. Other delegates such as the `RouteProgressDelegate` do not deliver events when a route is not set.
>
> This enables you to keep your delegates 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 delegates work in tracking mode.
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.
## Prepare a Trip
The HERE SDK provides support for route prefetching of map data. This allows to improve the user experience - for example, during turn-by-turn navigation to handle temporary network losses gracefully.
Note that this is _not_ needed if [offline maps](offline-maps.md) are already downloaded for the region where a trip takes place. In this case, all map data is already there, and no network connection is needed. Unlike, for example, the dedicated `OfflineRoutingEngine`, the `Navigator` or `VisualNavigator` will decide automatically when it is necessary to fallback to cached data or offline map data. In general, navigation requires map data, even if it is executed headless without showing a map view. The reason for this is that map data needs to be accessed during navigation for map matching and, for example, to notify on certain road attributes like speed limits. This data is taken from the available data on the device - or in case it is not there, it needs to be downloaded during navigation. Therefore, it can be beneficial to prefetch _more_ data in anticipation of the road ahead. Without prefetching, temporary connection losses can be handled less gracefully.
> #### Note
> Note that this is a beta release of this feature, so there could be a few bugs and unexpected behaviors. Related APIs may change for new releases without a deprecation process.
The `RoutePrefetcher` constructor requires a `SDKNativeEngine` instance as only parameter. You can get it via `SDKNativeEngine.sharedInstance` after the HERE SDK has been initialized.
With the `RoutePrefetcher` you can download map data in advance. The map data will be loaded into the [map cache](maps.md#adapt-map-caching). Note that the map cache has its own size constraints and may already contain data: The `RoutePrefetcher` may need to evict old cached data in order to store new map data.
- It is recommended to call once `routePrefetcher.prefetchAroundLocation(currentGeoCoordinates)` _before_ starting a trip. This call prefetches map data around the provided location with a radius of 2 km into the map cache and it ensures, that there is enough map data available when a user starts to follow the route - assuming that the route starts from the current location of the user.
- _After_ navigation has started, consider to call once `routePrefetcher.prefetchAroundRouteOnIntervals(navigator)`: It prefetches map data within a corridor along the route that is currently set to the provided `Navigator` instance. If no route is set, no data will be prefetched. The route corridor defaults to a length of 10 km and a width of 5 km. Map data is prefetched only in discrete intervals. Prefetching starts 1 km before reaching the end of the current corridor. Prefetching happens based on the current map-matched location - as indicated by the `RouteProgress` event. The first prefetching will start after travelling a distance of 9 km along the route. If a new route is set to the `navigator`, it is not necessary to call this method again - however, it has also no negative impact when it is called twice or more times.
If the `RoutePrefetcher` was successfully used at the start of a route - and then later the connectivity is lost, the cached data will be preserved even across future power cycles until the map cache is evicted. More about the map cache's eviction policy can be found [here](maps.md#adapt-map-caching).
For convenience, you can also call both methods together _before_ starting navigation. However, as a trade-off, keep in mind that `prefetchAroundRouteOnIntervals()` increases network traffic also for any subsequent guidance on new routes automatically.
Both calls help to optimize temporary offline use cases that rely on cached map data. While the `prefetchAroundLocation()` can be also used outside of a navigation use case, `prefetchAroundRouteOnIntervals()` requires an ongoing navigation scenario.