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.

Screenshot: An application running guidance - empowered by the HERE SDK.

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?

There are two main entry points for navigation. The headless Navigator provides navigation events that you can render yourself. The VisualNavigator provides the same navigation features plus a preconfigured 3D map view, smooth interpolation, junction views, and other optional visual elements. Both classes react to Location data and conform to LocationDelegate.

Voice guidance is supported by giving textual instructions with optional phoneme support and natural guidance information. These strings can be used with any available TTS feature (third-party or native OS). Use the RoutePrefetcher to optimize the experience in low connectivity situations, or download and install offline maps to navigate completely without an internet connection.

Happy path: set up guidance

The normal turn-by-turn sequence is: create a navigator, register the delegates your app needs, provide real or simulated Location updates, set a Route, start guidance by starting the location source, and consume navigation events to update the UI, voice guidance, warnings, and progress state. A route is required for guidance; omit it when you intentionally want tracking mode.

1. Create a navigator

Create either a headless Navigator or a VisualNavigator. Use VisualNavigator.startRendering() only when you want the HERE SDK to control the navigation presentation on a MapView; rendering is optional.

2. Register delegates

Register only the delegates needed by the application. For example, RouteProgressDelegate supplies progress state and EventTextDelegate supplies maneuver text that can be shown or forwarded to a TTS engine. Warner delegates and UI updates are optional and can be added independently.

3. Provide locations

Start a real location provider, or use LocationSimulator during development. The provider must deliver frequent, valid Location updates to the navigator; navigation cannot calculate route progress without them. See Get Locations and Build a navigation app for the complete provider and simulator implementations.

4. Set a route

Set the calculated Route on the navigator. Getting a Route instance is shown here. During guidance, read Maneuver information from the navigator because it is synchronized with the current Location, rather than reading it directly from the Route object.

5. Start guidance

Starting the location source after setting the route starts guidance. The same sequence works with real locations or simulated locations.

6. Consume navigation events

Use route progress to update distance, ETA, and maneuver state; use event text for voice guidance; and add optional warning and location delegates for the rest of the navigation UI. The full navigation app tutorial shows how to connect these events to a complete application.

The following is the smallest realistic VisualNavigator setup using a real location provider. Replace VisualNavigator with Navigator when the application renders the map and navigation UI itself:

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 following is the smallest realistic VisualNavigator setup using a real location provider. Replace VisualNavigator with Navigator when the application renders the map and navigation UI itself:

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

    // Optional: let VisualNavigator render the navigation view on the MapView.
    visualNavigator!.startRendering(mapView: mapView)

    // Optional delegates: consume progress and maneuver text in the application.
    visualNavigator!.routeProgressDelegate = self
    visualNavigator!.eventTextDelegate = self

    // Required for guidance: set the route and deliver locations.
    visualNavigator!.route = route
    herePositioningProvider.startLocating(locationDelegate: visualNavigator!,
                                          accuracy: .navigation)
}

The RouteProgressDelegate and EventTextDelegate callbacks represent application-owned progress, UI, and voice handling. For simulated Location events, use the LocationSimulator pattern in the navigation app tutorial.

📘

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.

Screenshot: Location flow from provider to application events via Location and (Visual) Navigator.

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.

In tracking mode, only delegates that can operate without a route deliver events. For example, NavigableLocationDelegate, SpeedWarningDelegate, and the warners are available, while RouteProgressDelegate does not deliver events. Keep delegates alive when switching between tracking and guidance, or set unused delegates to nil when the application no longer needs their events. Consult the API Reference for delegate-specific tracking support.

Tracking can be useful when drivers already know the directions to take, but would like to get additional information such as the current street name or any speed limits along the trip. Public transit does not support tracking because public transit routes may lead to unsafe and unexpected results in this mode.

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

visualNavigator.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.

Navigation lifecycle

Restart guidance after tracking or completion

Setting route = nil stops turn-by-turn events but leaves tracking events active. Setting a new Route again switches the same navigator back to guidance, and subsequent turn-by-turn events are propagated as location updates arrive. The application can therefore reuse its navigator and location provider instead of creating them for every transition.

When the destination is reached, turn-by-turn event propagation stops automatically. The navigator and location source are not automatically shut down, and route-independent warners may continue to deliver events. Handle DestinationReachedDelegate or the relevant progress state in the application, then either set a new route to restart guidance, switch to tracking, or stop the location source and release the navigator resources.

Stop rendering and release resources

When using VisualNavigator, call stopRendering() before destroying its MapView. After this call, the MapView is no longer controlled by VisualNavigator:

  • Map orientation, camera distance, and tilt keep their last values. Apply the desired camera settings after stopRendering() if needed.
  • The map no longer moves to the current location, even if locations continue to be fed into the VisualNavigator.
  • The default or custom location indicator owned by VisualNavigator is hidden.
  • Location-based events such as RouteProgress continue until their delegates are set to nil.

Stop a LocationSimulator and DynamicRoutingEngine when the application no longer needs them. A MapView pause does not require stopping the VisualNavigator: rendering pauses automatically and resumes when the MapView resumes, provided it was rendering before the pause.

Clean shutdown is application-owned. Stop the location source, clear delegates that are no longer needed, stop simulators and engines, and stop rendering before releasing the MapView.

Edge cases and platform-specific behavior

Location validity and map matching

Navigation events are based on a map-matched location. The HERE SDK uses the MapMatcher internally to match a raw location signal to the nearest road. The Location.time parameter is required; if it is missing, the location is ignored. Provide bearing and speed when available for better matching and navigation results.

The standalone Map matching locations section describes manual matching. For active navigation, prefer the map-matched events from NavigableLocationDelegate or the navigator's route progress.

Rendering and lifecycle boundaries

Calling stopRendering() changes only the rendering relationship with the MapView; it does not unsubscribe navigation delegates or stop location delivery. A paused MapView is different: VisualNavigator pauses and resumes rendering automatically while its lifecycle remains active.

Transport restrictions

Navigation is supported for all available transport modes except PUBLIC_TRANSIT. Public transit routes may lead to unsafe and unexpected results for turn-by-turn navigation as well as tracking. See Supported transport modes for the related transport-mode behavior.

Get maneuver progress events

During navigation, you typically want to attach a few 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(...).

Update traffic on route

During turn-by-turn navigation, it is recommended to calculate a dedicated TrafficOnRoute object by calling calculateTrafficOnRoute(). Since the traffic situation can change frequently while being on a trip, an application should repeat this call periodically.

The example implementation provided below ensures that traffic updates are performed at a configurable interval.

Upon completion of calculateTrafficOnRoute(), the VisualNavigator can be updated with the new TrafficOnRoute object, which adjusts the information on the duration provided by the RouteProgress object.

func updateTrafficOnRoute(routeProgress: RouteProgress) {
    guard let currentRoute = visualNavigator.route else {
        return
    }

    // Below, we use 10 minutes. A common range is between 5 and 15 minutes.
    let trafficUpdateIntervalInMilliseconds = 10 * 60000 // 10 minutes
    let now = Int(Date().timeIntervalSince1970 * 1000) // Current time in milliseconds
    if (now - lastTrafficUpdateInMilliseconds) < trafficUpdateIntervalInMilliseconds {
        return
    }
    // Store the current time when we update trafficOnRoute.
    lastTrafficUpdateInMilliseconds = now

    let sectionProgressList = routeProgress.sectionProgress
    guard let lastSectionProgress = sectionProgressList.last else {
        return
    }
    let traveledDistanceOnLastSectionInMeters =
        currentRoute.lengthInMeters - lastSectionProgress.remainingDistanceInMeters
    let lastTraveledSectionIndex = routeProgress.routeMatchedLocation.sectionIndex

    routeCalculator.calculateTrafficOnRoute(
        currentRoute: currentRoute,
        lastTraveledSectionIndex: Int(lastTraveledSectionIndex),
        traveledDistanceOnLastSectionInMeters: Int(traveledDistanceOnLastSectionInMeters)
    ) { routingError, trafficOnRoute in
        if let routingError = routingError {
            print("CalculateTrafficOnRoute error: \(routingError)")
            return
        }

        // Sets traffic data for the current route, affecting RouteProgress duration in SectionProgress,
        // while preserving route distance and geometry.
        self.visualNavigator.trafficOnRoute = trafficOnRoute
        print("Updated traffic on route.")
    }
}
📘

Note

This code initiates periodic calls to the HERE Routing backend. Depending on your contract, each call may be charged separately. It is the application's responsibility to decide how and how often this code should be executed.

The visualNavigator.trafficOnRoute property does not take effect immediately. Instead, the updated travel duration (ETA) is reflected in the next RouteProgress event.

Supported transport modes

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

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

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

Try the Navigation example apps

  • All code snippets from the 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.

On GitHub you can find many more example apps that cover certain topics in greater details. For example, you can find code to integrate TTS in the "Navigation" apps or to handle route deviations in the "Rerouting" app. Take a look at Explore the Navigation example apps


Did this page help you?