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

Electronic Horizon

Electronic Horizon is only available with the Navigate license.

Electronic Horizon uses the map as a sensor to provide a continuous forecast of the upcoming road network by ingesting the map topography that is currently out of the driver’s sight. This functionality can be valuable for advanced navigation, driver-assistance, or ADAS-adjacent features.

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.

Main benefits of Electronic Horizon:

  • Predictive path awareness: Get early insight into upcoming road segments before a vehicle reaches them. The most preferred path is predicted based on probability.
  • Contextual map attributes: Query road signs, speed limits, and road attributes along the horizon.
  • Dynamic updates: Automatically load the required map data as the horizon evolves.
  • Route and tracking support: Works with a predefined route (map-matched mode) or without a route (tracking mode).

Types of data include:

  • Most preferred path (MPP) — the predicted roads most likely to be followed.
  • Alternative side paths — possible deviations or branching roads from the MPP.
  • Speed limits — speed information along the horizon.
  • Road geometry — the polyline representing each upcoming road segment.
  • Road classes — classification of roads.
  • Road types — specific road characteristics.
  • Toll points - upcoming toll structures with payment information.
  • Traffic lights - information on upcoming traffic signals.
  • ...and more — depending on the configured data loading options. Take a look at the SegmentData class for an overview.

The ElectronicHorizonEngine component fully supports offline use cases when the map data is cached, prefetched, or installed on a device. It works in hybrid mode so that it automatically fetches data online when a device contains no data.

Note

While the HERE SDK provides Electronic Horizon functionality, it currently does not offer direct support for the ADASISv2 and ADASISv3 protocols.

Key Concepts

ConceptDescription
Electronic HorizonA continuously updated model of the road network ahead of the vehicle.
Most preferred path (MPP)The route segment most likely to be followed based on previous location updates or an active route.
ElectronicHorizonDataLoaderA utility that automatically loads required map data for segments along the horizon.
ElectronicHorizonUpdateA data structure representing new or changed road segments in the current horizon.

Similar to the VisualNavigator, you need to update the ElectronicHorizonEngine with a location. However, unlike the VisualNavigator, this location must be map-matched. Therefore, you can use the MapMatchedLocation from the VisualNavigator or Navigator.

The ElectronicHorizonEngine class accepts an ElectronicHorizonListener to notify about the MPP ahead. This listener asynchronously delivers an ElectronicHorizonUpdate, which initially contains only empty lists.

The listener updates the ElectronicHorizonUpdate instance with each call, so clients need to maintain only one instance. At any given time, it represents the current known state of the electronic horizon through the following lists:

  • electronicHorizonPaths: Holds the complete electronic horizon path tree based on ElectronicHorizonOptions and current vehicle position. ElectronicHorizonDataLoadedStatus indicates when the tree is fully loaded. With the next update, a new path tree is generated that will be partially loaded - and so on.
  • addedSegments: Holds segments added since the previous update. Based on these paths, the data loader will request the desired map data.
  • removedSegmentIds: Holds removed segment IDs since the previous update. The ElectronicHorizonEngine removes segments based on the trailingDistanceInMeters when they fall behind the vehicle. Segments are also removed when they pass the decision point for side paths.

Use the ElectronicHorizonDataLoaderStatusListener to get notified when new data is loaded.

A typical sequence flow may look like this:

  1. ElectronicHorizonListener provides the first ElectronicHorizonUpdate.
  2. Load that update via ElectronicHorizonDataLoader.
  3. ElectronicHorizonDataLoaderStatusListener notifies when the update contains new or changed data.
  4. ElectronicHorizonListener provides an updated ElectronicHorizonUpdate based on the previous one.
  5. Repeat step 2 — load that update, and so on.

Note that there is no guarantee that each ElectronicHorizonUpdate is immediately followed by an ElectronicHorizonDataLoadedStatus. For example, you may receive two consecutive ElectronicHorizonUpdate events and only afterwards receive two ElectronicHorizonDataLoadedStatus events.

Integrate the Electronic Horizon API

Before integrating the Electronic Horizon API, make sure you have the following prerequisites:

  1. Integrate the HERE SDK into your project.
  2. Initialize the SDKNativeEngine or reuse an existing instance.
  3. Ensure you already have a VisualNavigator or Navigator supplying map-matched locations (for accurate horizon updates).
  4. Optionally, use a RoutePrefetcher to fetch map data for offline use, or use the MapDownloader to install the region data needed for your trip. If no map data is found on the device, the Electronic Horizon API will fetch the required map data online.

Now, let's start the integration — from initialization to updates and data retrieval.

Create and configure the ElectronicHorizonEngine instance

Use the ElectronicHorizonEngine class to open the horizon model. You can choose whether to pass an active Route or null for tracking mode without following a route.

In this example, we enable the electronic horizon in car mode, but other transport modes are also supported.

let lookAheadDistancesInMeters = [1000.0, 500.0, 250.0]
let trailingDistanceInMeters = 500.0
let electronicHorizonOptions = ElectronicHorizonOptions(
    lookAheadDistancesInMeters: lookAheadDistancesInMeters,
    trailingDistanceInMeters: trailingDistanceInMeters
)

let transportMode = TransportMode.car

do {
    electronicHorizonEngine = try ElectronicHorizonEngine(
        sdkEngine: ElectronicHorizonHandler.getSDKNativeEngine(),
        options: electronicHorizonOptions,
        transportMode: transportMode,
        route: route
    )
} catch let instantiationError {
    fatalError("ElectronicHorizonEngine is not initialized: \(instantiationError)")
}

The ElectronicHorizonEngine uses ElectronicHorizonOptions to define:

  • lookAheadDistancesInMeters for the main and side paths: The first entry of the list is for the most preferred path, the second is for the side paths of the first level, the third is for the side paths of the second level, and so on. Each entry defines how far ahead the path should be provided.
  • A trailingDistanceInMeters defining when past segments are removed: Segments will be removed by the HERE SDK once passed and the distance to them exceeds trailingDistanceInMeters.

The HERE SDK automatically maintains a list of segments ahead. Each time the horizon changes, the ElectronicHorizonListener is notified.

Update with map-matched location

You must continuously update the ElectronicHorizonEngine with a MapMatchedLocation from the VisualNavigator or Navigator.

electronicHorizonEngine.update(mapMatchedLocation: mapMatchedLocation)

Each update recalculates the preferred paths ahead based on the current position and direction.

Get notified on added paths ahead

Before you can load data, you need to know what roads are ahead. For this, create a delegate to get notified asynchronously about electronic horizon updates as a user moves forward. This informs about the available segment IDs and indexes so that the actual data can be requested by the ElectronicHorizonDataLoader later on:

private func createElectronicHorizonDelegate() -> ElectronicHorizonDelegate {
    class EHDelegate: ElectronicHorizonDelegate {
        weak var handler: ElectronicHorizonHandler?

        init(handler: ElectronicHorizonHandler) {
            self.handler = handler
        }

        func onElectronicHorizonUpdated(error: ElectronicHorizonError?, update: ElectronicHorizonUpdate?) {
            guard error == nil, let electronicHorizonUpdate = update else {
                print("(ElectronicHorizonHandler.LOG_TAG): ElectronicHorizonUpdate error: \(String(describing: error))")
                return
            }
            // Asynchronously start to load required data for the new segments.
            // Use the ElectronicHorizonDataLoaderStatusDelegate to get notified when new data is arriving.
            if electronicHorizonUpdate.electronicHorizon != nil {
                handler?.lastRequestedElectronicHorizon = electronicHorizonUpdate.electronicHorizon
            }
            if let segmentChanges = update.segmentChanges {
                handler?.electronicHorizonDataLoader.loadData(electronicHorizonUpdate: electronicHorizonUpdate)
            }
        }
    }
    return EHDelegate(handler: self)
}

Make sure to add the delegate to the ElectronicHorizion instance.

Create and configure a data loader

The ElectronicHorizonDelegate provides updates that can then be loaded. For this, you need to instantiate an ElectronicHorizonDataLoader to load asynchronously detailed map data for horizon segments. It accepts SegmentDataLoaderOptions, which let you define which data to include.

// Many more options are available, see SegmentDataLoaderOptions in the API Reference.
var segmentDataLoaderOptions = SegmentDataLoaderOptions()
segmentDataLoaderOptions.loadRoadSigns = true
segmentDataLoaderOptions.loadSpeedLimits = true
segmentDataLoaderOptions.loadRoadAttributes = true

// The cache size defines how many road segments are cached locally. A larger cache size
// can reduce data usage, but requires more storage memory in the cache.
let segmentDataCacheSize = 10
do {
    electronicHorizonDataLoader = try ElectronicHorizonDataLoader(
        sdkEngine: ElectronicHorizonHandler.getSDKNativeEngine(),
        options: segmentDataLoaderOptions,
        segmentDataCacheSize: Int32(segmentDataCacheSize)
    )
} catch let instantiationError {
    fatalError("ElectronicHorizonDataLoader is not initialized: \(instantiationError)")
}

For convenience, the ElectronicHorizonDataLoader wraps a SegmentDataLoader that continuously loads the required map data segments based on the most preferred path(s) of the ElectronicHorizonEngine.

As a second step, you probably also want to handle newly arriving map data segments provided by the ElectronicHorizonDataLoader. For this, you can create an ElectronicHorizonDataLoaderStatusListener. The HERE SDK calls this delegate when the data loader’s status updates and new segments are loaded.

/// Handle newly arriving map data segments provided by the ElectronicHorizonDataLoader.
/// This delegate is called when the status of the data loader is updated and new segments have been loaded.
private func createElectronicHorizonDataLoaderStatusDelegate() -> ElectronicHorizonDataLoaderStatusDelegate {
    class EHStatusDelegate: ElectronicHorizonDataLoaderStatusDelegate {
        weak var handler: ElectronicHorizonHandler?

        init(handler: ElectronicHorizonHandler) {
            self.handler = handler
        }

        func onElectronicHorizonDataLoaderStatusUpdated(electronicHorizonDataLoaderStatuses statusMap: [Int32: ElectronicHorizonDataLoadedStatus]) {
            print("\(ElectronicHorizonHandler.LOG_TAG): ElectronicHorizonDataLoaderStatus updated.")
            // Access the loaded segments here.
        }
    }

    return EHStatusDelegate(handler: self)
}

Make sure to add the delegate to the ElectronicHorizonDataLoader instance.

Accessing segment data

After the ElectronicHorizonDataLoaderStatusListener signals via onElectronicHorizonDataLoaderStatusUpdated(...) that segments are fully loaded, you can query individual segments for their attributes.

The statusMap parameter of type [Int32: ElectronicHorizonDataLoadedStatus] contains the following information:

  • The integer key represents the level of the most preferred path (0) and side paths (1, 2, ...).
  • The status contains information on whether the segment has been fully loaded and is ready to be used, for example, by checking for ElectronicHorizonDataLoadedStatus.FULLY_LOADED.

Now, access the segments that were part of a previously requested electronic horizon update. The app requested these segments for loading in the call to electronicHorizonDataLoader.loadData(...). The data loader provides a method the get access to the actual data via electronicHorizonDataLoader.getSegment(directedOCMSegmentId.id).

Internally, the data loader tracks which segments the app requested and continuously updates the provided ElectronicHorizonUpdate instance.

The following example waits until the MPP is fully loaded and then iterates over the entire tree:

for (level, status) in statusMap {
    // The integer key represents the level of the most preferred path (0) and side paths (1, 2, ...).
    // This example shows only how to look at the fully loaded segments of the most preferred path (level 0).
    if level == 0 && status == .fullyLoaded {
        // Now, level 0 segments have been fully loaded and you can access their data.
        // The electronicHorizonPaths list contains segments from all levels,
        // so you need to filter for level 0 below.
        for electronicHorizonPath in lastUpdate.electronicHorizonPaths {
            let electronicHorizonPathSegments = electronicHorizonPath.segments
            for segment in electronicHorizonPathSegments {
                // For any segment you can check the parentPathIndex to determine
                // if it is part of the most preferred path (MPP) or a side path.
                if segment.parentPathIndex != 0 {
                    // Skip side path segments as we only want to log MPP segment data in this example.
                    // And we only want to log fully loaded segments.
                    continue
                }

                guard let directedOCMSegmentId = segment.segmentId.ocmSegmentId else {
                    continue
                }

                // Retrieving segment data from the loader is executed synchronous. However, since the data has been
                // already loaded, this is a fast operation.
                let result = handler.electronicHorizonDataLoader.getSegment(segmentId: directedOCMSegmentId.id)
                if result.errorCode == nil, let segmentData = result.segmentData {
                    // Access the data that was requested to be loaded in SegmentDataLoaderOptions.
                    // For this example, we just log road signs.
                    guard let roadSigns = segmentData.roadSigns, !roadSigns.isEmpty else {
                        continue
                    }
                    for roadSign in roadSigns {
                        let roadSignCoordinates = handler.getGeoCoordinatesFromOffsetInMeters(
                            geoPolyline: segmentData.polyline,
                            offsetInMeters: Double(roadSign.offsetInMeters)
                        )
                        print("\(ElectronicHorizonHandler.LOG_TAG): RoadSign: type = \(roadSign.roadSignType.rawValue), offsetInMeters = \(roadSign.offsetInMeters), lat/lon: \(roadSignCoordinates.latitude)/\(roadSignCoordinates.longitude), segmentId = \(directedOCMSegmentId.id.localId)")
                    }
                }
            }
        }
    }
}

To fetch the geographic coordinates from a segment, you can use the provided offset in meters and the helper below:

/// Convert an offset in meters along a GeoPolyline to GeoCoordinates using the HERE SDK's coordinatesAtOffsetInMeters.
private func getGeoCoordinatesFromOffsetInMeters(geoPolyline: GeoPolyline, offsetInMeters: Double) -> heresdk.GeoCoordinates {
    return geoPolyline.coordinatesAt(offsetInMeters: offsetInMeters,
                                     direction: .fromBeginning)
}

Note that segment.parentPathIndex is 0 when it's the MPP. Optionally, you can iterate further down the tree to retrieve information for side paths.

Stop and clean up

When navigation ends or tracking mode stops, remove listeners and release resources.

electronicHorizonEngine.removeElectronicHorizonListener(listener);
electronicHorizonDataLoader.removeElectronicHorizonDataLoaderStatusListener(statusListener);

Best practices

  • Use look-ahead distances and trailing distances that match your use case (e.g., highway vs. urban).
  • Process only level 0 (most preferred path) segments unless you actively want side-path data.
  • Prefetch map data ahead of time if your app will run offline or in low-connectivity scenarios.
  • Choose a reasonable cache size for the data loader to balance memory and performance.
  • Remove listeners and free resources when no longer needed to avoid memory leaks or unnecessary background work.

Try the example app

In the "Navigation" example app on GitHub, the ElectronicHorizonHandler class demonstrates how to:

  • Initialize and start the Electronic Horizon.
  • Update it with map-matched locations during navigation.
  • Retrieve and log road-level data (for example, road signs) as the vehicle moves along a route.

For a full implementation, see
ElectronicHorizonHandler.swift.