GuidesFlutter API ReferencesHERE SDK for Android API referencesHERE SDK for iOS API references
Guides

Get started with Navigation

Navigation is only available with the Navigate license.

The HERE SDK enables you to build a comprehensive turn-by-turn navigation experience. With this feature, your app can check the current device location against a calculated route and get navigational instructions just-in-time.

Key features include:

  • Automated rendering: A tailored navigation map view can be optionally rendered with the VisualNavigator. 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.

  • Tracking mode: Even without having a route to follow, the HERE SDK supports a tracking mode, which provides information about the current street, the map-matched location and other supporting details such as speed limits.

  • Real-time instructions: Voice guidance is provided with maneuver notifications that can be fed as a String into any platform TTS (Text-To-Speech) solution.

  • Support for warners: Stay aware with a comprehensive warner system that includes alerts on speed limits, truck restrictions, road signs and many more.

  • Offline support: Almost all navigation features work also without an internet connection when offline map data has been cached, installed or preloaded: only a few features require an online connection, for example, when using the DynamicRouteEngine to search online for traffic-optimized routes.

The basic principle of turn-by-turn navigation is to frequently receive a location including speed and bearing values 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

Application developers using turn-by-turn navigation are required to thoroughly test their applications in all expected usage scenarios to ensure safe and correct behavior. Application developers are responsible for warning app users of obligations including but not limited to:

  • Do not follow instructions that may lead to an unsafe or illegal situation.
  • Obey all local laws.
  • Be aware that using a mobile phone or some of its features while driving may be prohibited.
  • Always keep hands free to operate the vehicle while driving.
  • Make road safety the first priority while driving.

How does it work?

  1. There are two main entry points to start with navigation:
    1. The Navigator class provides all functionality to react on Location data. It provides guidance instructions to follow a route accompanied with an extensive warner system that provides all kind of information along the road. The latter is also available in tracking mode without following a route.
    2. The VisualNavigator class provides the same features as the headless Navigator, but offers a pre-configured 3D map view experience on top. On top, it provides junction views and other optional visual elements.
  2. Location data needs to be provided. Use your own solution - or use the HERE SDK: The Get Locations guide provides all the details.
  3. Voice guidance is supported by giving textual instructions with optional phoneme support and natural guidance information. These strings can be used with any available TTS feature (third-party or native OS).
  4. Use the RoutePrefetcher to optimize the experience for situations when there is not enough connectivity - or download and install offline maps to navigate completely without an internet connection.

Initialize the Navigator or VisualNavigator

With the VisualNavigator the HERE SDK provides a ready-made visual experience to start guidance. This component creates a map view with all the required bits to show the progress while advancing on a route. Optionally, you can customize the view or render it completely on your own with the headless Navigator.

You can initialize the Navigator in the same way as the VisualNavigator:

do {
    try visualNavigator = VisualNavigator()
} catch let engineInstantiationError {
    fatalError("Failed to initialize VisualNavigator. Cause: \(engineInstantiationError)")
}

Listen for navigation events

Before you can start to navigate to a destination, you need two things:

  1. A Route to follow. The Route must be set to the Navigator or VisualNavigator instance to start navigation.
  2. 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. 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.

The below code snippet shows all the code that is needed to start simulated guidance using simulated Location events taken from the Route. It uses the VisualNavigator instance, so that the HERE SDK will take over the rendering part until stopRendering() is called:

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 Guide.
    visualNavigator!.eventTextDelegate = 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 EventTextDelegate.
func onEventTextUpdated(_ eventText: heresdk.EventText) {
    print("Maneuver text: \(eventText.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 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.

Note

Unlike for other engines, the VisualNavigator or the Navigator will automatically try to download online data when reaching regions that have not been cached, installed or preloaded beforehand. And vice versa, both components will make use of offline map data when it is available on a device - even if an online connection is available.

Feed locations into a navigator

As shown above, you need to provide Location instances - as navigation is not possible without getting frequent updates on the current location. Let's take another look on how to fed non-simulated as well as simulated Location data into the system.

The navigation component is decoupled from positioning: new locations can be provided by implementing a platform-specific positioning solution, using an external provider, leveraging the HERE Positioning feature of the HERE SDK, or setting up a location simulator.

Note that you can set any Location source as "location provider". Only onLocationUpdated() has to be called on the Navigator or VisualNavigator.

It is the responsibility of the developer to feed in valid locations into the VisualNavigator. For each received location, the VisualNavigator will respond with appropriate events that indicate the progress along the route, including maneuvers and a possible deviation from the expected route. The resulting events depend on the accuracy and frequency of the provided location signals.

When using HERE Positioning, we recommend using LocationAccuracy.navigation, as this provides the best results in a navigation context.

All events are given based on a map-matched location - this is done automatically by the HERE SDK which incorporates calling the MapMatcher component to match a raw location signal to the nearest road. Note that for the MapMatcher to work properly, it's required to set the Location.time parameter, otherwise, the location will be ignored. It is recommended to also provide bearing and speed parameters for each Location object.

A positioning provider implementation using HERE Positioning can be found on GitHub. It provides simulated Location events with the HEREPositioningSimulator class and non-simulated Location events with the HEREPositioningProvider class.

We recommend to follow the Positioning guide to discover more details on all supported HERE Positioning features.

Both, the Navigator and the VisualNavigator classes conform to the LocationDelegate protocol that defines the onLocationUpdated(location:) method to receive locations:

// Now visualNavigator will receive locations from the HEREPositioningProvider.
// Choose a suitable accuracy for the navigation use case.
herePositioningProvider.startLocating(locationDelegate: visualNavigator,
                                      accuracy: .navigation)

Optionally, set the route you want to follow - unless you plan to start in tracking mode:

visualNavigator.route = route

If you are interested in getting background location updates, you need to add the NSLocationAlwaysAndWhenInUseUsageDescription permission. More information can be found in the Positioning guide. Note that as long as you provide location updates, all navigation events will seamlessly continue to be delivered - even if the device screen is locked or the map view is paused.

Start and stop guidance

While turn-by-turn navigation automatically starts when a route is set and the herePositioningProvider 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 non-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. However, when a MapView is paused, it is not necessary to also stop the VisualNavigator. The VisualNavigator stops automatically to render when the MapView is paused and it starts rendering when the MapView is resumed (when the VisualNavigator was rendering before).

Start and stop 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 vehicle transport modes.

To enable tracking, all you need is to call:

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. In general, all warners are supported. 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.

When tracking is enabled, it is also recommended to enable the SpeedBasedCameraBehavior:

visualNavigator.cameraBehavior = SpeedBasedCameraBehavior()

This camera mode is automatically adjusting the camera's location to optimize the map view based on the current driving speed.

In order to stop following the camera, call:

visualNavigator.setCameraBehavior(null)

This can be also useful during guidance, if you want to temporarily enable gesture handling. It is recommended to automatically switch back to tracking if turn-by-turn navigation is ongoing - in order to not distract a driver.

Get maneuver progress events

During navigation, you typically want to attach a few delegates to get notified about the route progress, current location, and the next maneuver to take. The HERE SDK provides many different delegates for different purposes.

Below, we show how to get progress events:

visualNavigator.routeProgressDelegate = self

And here we set the conforming method to fulfill the RouteProgressDelegate protocol:

// 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, route: visualNavigator.route)
    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
}

With the routeProgress event we can access the next maneuver that lies ahead of us. For this we use the maneuverIndex:

// Contains the progress for the next maneuver ahead and the next-next maneuvers, if any.
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
}

Use nextManeuver.action to identify the maneuver a user has to take. The full list of supported ManeuverAction enum values can be found in the API Reference.

Note

A maneuver icon as indicated by the ManeuverAction enum is recommended to be shown as a visual indicator during navigation - while the Maneuver instruction text (nextManeuver.text) fits more into a list to preview maneuvers before starting a trip: these localized instructions are descriptive and will be understandable outside of an ongoing guidance context. However, commonly, they can be presented together with the corresponding ManeuverAction icons you can find in the open-source HERE Icon Library. Find more details on this in the Routing section.

With the data provided by the 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.

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.

Get street information from maneuvers

Once you have taken a Maneuver from the visualNavigator or navigator, the Maneuver class can be useful to display localized street names or numbers (such as highway numbers).

Road texts for the next maneuver can be retrieved as follows from a turn-by-turn maneuver:

enum RoadType {
    case highway
    case rural
    case urban
}

func getRoadName(maneuver: Maneuver, route: Route?) -> String {
    let currentRoadTexts = maneuver.roadTexts
    let nextRoadTexts = maneuver.nextRoadTexts

    let currentRoadName = currentRoadTexts.names.defaultValue()
    let currentRoadNumber = currentRoadTexts.numbersWithDirection.defaultValue()
    let nextRoadName = nextRoadTexts.names.defaultValue()
    let nextRoadNumber = nextRoadTexts.numbersWithDirection.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 getRoadType(maneuver: maneuver, route: route!) == 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"
}

// Determines the road type for a given maneuver based on street attributes.
// Returns the road type classification (highway, urban, or rural).
func getRoadType(maneuver: Maneuver, route: Route) -> RoadType {
    let sectionIndex = Int(maneuver.sectionIndex)
    let section = route.sections[sectionIndex]
    let spans = section.spans

    // If attributes list is empty then the road type is rural.
    guard !spans.isEmpty else {
        return .rural
    }

    let maneuverSpan: Span

    // Arrive maneuvers are placed after the last span of the route
    // and the span index for them would be greater than the span's list size.
    if maneuver.action == ManeuverAction.arrive {
        maneuverSpan = spans.last!
    } else {
        maneuverSpan = spans[Int(maneuver.spanIndex)]
    }

    let streetAttributes = maneuverSpan.streetAttributes

    // If attributes list contains either CONTROLLED_ACCESS_HIGHWAY, or MOTORWAY or RAMP then the road type is highway.
    // Check for highway attributes.
    if streetAttributes.contains(.controlledAccessHighway) ||
       streetAttributes.contains(.motorway) ||
       streetAttributes.contains(.ramp) {
        return .highway
    }

    // If attributes list contains BUILT_UP_AREA then the road type is urban.
    // Check for urban attributes.
    if streetAttributes.contains(.builtUpArea) {
        return .urban
    }

    // If the road type is neither urban nor highway, default to rural for all other cases.
    return .rural
}

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

The methods nextManeuver.roadTexts, nextManeuver.nextRoadTexts and nextManeuver.exitSignTexts are meant to be shown as part of turn-by-turn maneuvers during navigation: 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 PropertiesRoutingEngineNavigator / VisualNavigatorExamples
maneuver.textProvides a non-empty string.Provides a non-empty string.Example output for text: "Turn right onto Detmolder Straße towards A100."
maneuver.roadTextsProvides empty strings.Provides non-empty strings.Example output for roadTexts.names.defaultValue(): "Stadtring".
maneuver.nextRoadTextsProvides empty strings.Provides non-empty strings.Example output for nextRoadTexts.names.defaultValue(): "Halenseestraße".
maneuver.exitSignTextsProvides 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.

Update ETA and Traffic during navigation

The estimated time of arrival (ETA) can be updated by setting a TrafficOnRoute object to the Navigator or VisualNavigator. For this, it is recommended to periodically call routingEngine.calculateTrafficOnRoute(...).

You can find more details on TrafficOnRoute in the Update traffic information section.

Supported transport modes

Navigation is supported for all available transport modes - except for PUBLIC_TRANSIT. Public transit routes may lead to unsafe and unexpected results when being used for navigation.

The transport mode can vary across the Route, for example, if you walk through a park to reach a sightseeing spot, you may need to leave a car. After the route is calculated, the transport mode is attached to each Section of a Route object.

For car, truck, taxi, bus and scooter routes, the location of the device will be map-matched to streets, while for other modes, such as pedestrian routes, locations may be matched - in addition - to unpaved dirt roads and other paths that would not be accessible to drivers. On the other hand, certain roads like highways are not navigable for pedestrians. Bicycle routes can make use of all available paths - except highways.

Try the Navigation example apps

  • All code snippets from the above sections are also available on GitHub as part of the Navigation example app. This app shows the code in connection and provides a testable driving experience and best practices such as keeping the screen alive during guidance.

Additionally, you can find on GitHub the "NavigationQuickStart" example app. It shows how to get quickly started using a simulated location source.

Take a also look at the NavigationCustom example app on GitHub. This example app demonstrates how the HERE SDK can be set up to navigate to a location with a custom LocationIndicator. It illustrates the usage of the default pedestrian and navigation LocationIndicator assets. Additionally, the app shows how to customize the guidance view by setting a custom zoom level and tilt.