Electronic Horizon
Electronic Horizon uses the map as a sensor to provide a continuous forecast of the upcoming road network by ingesting the map topography which is currently out of the driver’s sight. Together with the vehicle position and other relevant road attributes, the HERE Electronic Horizon API creates a simplified model of the road ahead. By using techniques like adaptive cruise control (ACC) or curve speed warning, you can help the driver to save fuel and to make driving safer and less stressful.
Types of data the HERE Electronic Horizon API can predict include:
- Most probable path
- Alternative side paths
- Speed limits
- Slopes
- Curvature coordinates
- Road classes
- Road types
The HERE Electronic Horizon API fully supports the offline use case and adheres to the vehicle’s advanced driver assistance systems (ADAS) standard being ADASISv2 compliant.
Before You Start
Please make sure to use the correct flavor of HERE SDK. Electronic Horizon is included in SDK for iOS since version 3.6.0.
NMAMapDataGroupADAS
and NMAMapDataGroupLinkGDBIdPVId
data groups before downloading the desired map data.Since the Electronic Horizon requires the user’s position, it is mandatory to require ACCESS_FINE_LOCATION
permission. Although the API can also work in offline mode, it is recommended to request at least the following permissions from the user:
<key>NSLocationAlwaysUsageDescription</key>
<string>This is needed to determine your current location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This is needed to determine your current location</string>
How to Retrieve Your First Electronic Horizon Events
Before you can access the Electronic Horizon, make sure to start NMAPositioningManager
:
NMAPositioningManager* positioningManager = [NMAPositioningManager sharedPositioningManager];
if ([positioningManager startPositioning]) {
positioningManager.mapMatchingEnabled = YES;
positioningManager.mapMatchMode = NMAMapMatchModeCar;
}
Then you can create the Electronic Horizon instance and set the delegate to receive events. Make sure your delegate conforms to the NMAElectronicHorizonDelegate
protocol. The protocol defines several callbacks which are marked as optional. Therefore you are only required to implement the callbacks you really need.
self.electronicHorizon = [[NMAElectronicHorizon alloc] init];
self.electronicHorizon.delegate = self;
- (void)electronicHorizon:(nonnull NMAElectronicHorizon *)electronicHorizon
didReceiveNewPosition:(nonnull NMAEHPosition *)position {
// handle event here
}
Now you are able to tell the Electronic Horizon engine to calculate the road network ahead – based on the current position of your device. You do this by simply calling:
[self.electronicHorizon update];
update
method is resource intensive. It is advised not to call this function too frequently. NMAPositioningManager
usually provides a new position each second. Instead you may want to update the Electronic Horizon only after: - A certain distance
- A certain amount of time
- A certain event
Since calling update
can take a while, the NMAElectronicHorizonDelegate
protocol provides several callbacks to get notified as soon as the predicted path has changed:
-
electronicHorizon:didReceiveNewPosition:
- Always called last after the other callbacks. May contain an unchangedNMAEHPathTree
if the position is the same as for the previous call. Always contains the full path. -
electronicHorizonDidReceiveTreeReset:
- As soon as the current position is beyond the mainNMAEHPathTree
look ahead or trailing distance,electronicHorizonDidReceiveTreeReset
gets called and a newNMAEHPathTree
object is constructed. Otherwise the user is still travelling on the expected paths and the mainNMAEHPathTree
is instead extended. -
electronicHorizon:didReceivelinkAdded:link:
- Notifies which link was just added to anNMAEHPathTree
. Note that the mainNMAEHPathTree
does not contain a parent while anNMAEHPathTree
may or may not contain children, no matter how deep it is nested in the hierarchy. -
electronicHorizon:didReceiveLinkRemoved:link:
- Called as soon as a link was removed from a parentNMAEHPathTree
. Note that it is not guaranteed to be called for every link that was once added to a path tree. -
electronicHorizon:didReceivePathAdded:
- A newNMAEHPathTree
is added to the existing tree. Since eachNMAEHPathTree
contains parents and children, you could start to traverse the road network from here although onlyelectronicHorizon:didReceiveNewPosition:
is guaranteed to provide the full path. -
electronicHorizon:didReceivePathRemoved:
- Called beforeelectronicHorizon:didReceiveNewPosition:
as soon as anNMAEHPathTree
was removed. Usually this is a good place to remove any map objects from the map that belong to thisNMAEHPathTree
. -
electronicHorizon:didReceiveChildDetached:child:
- A path was detached from a parent because the user has left the main path but is still travelling on a side path. The detached child most likely becomes the new main path.
Make sure to call update
after a valid position is provided by the
, otherwise no events are delivered. Please note that NMAPositioningManager
electronicHorizon:didReceiveNewPosition:
is guaranteed to be called after each update
request even if no map matched position could be found. The only requirement is a valid position which is automatically fetched by the Electronic Horizon after an update.
update
from a background thread as the execution may take some time.If the user is off-road, Electronic Horizon cannot calculate a predicted path since a map matched position is required to operate successfully. This can be detected with the following code snippet:
- (void)electronicHorizon:(nonnull NMAElectronicHorizon *)electronicHorizon
didReceiveNewPosition:(nonnull NMAEHPosition *)position {
NMAEHPathTree* pathTree = position.pathTree;
if (pathTree == nil) {
// we seem to be off-road
}
}
An NMAEHPathTree
represents the road network ahead and by default also behind. It contains nested NMAEHPathTrees
as children and parents but only the root path does not contain a parent NMAEHPathTree
. This can be used to find out if an NMAEHPathTree
represents the root path. The path belonging to the Position is always the main path on which the user is currently travelling. This may be the root path but it can also happen that the user decides to take a turn into a side path which then represents the new main path containing the root path as parent. When the path is detached and the tree gets restructured, the new main path becomes the root path again.
NMAEHPathTree* parentPathTree = pathTree.parent;
if (parentPathTree == nil) {
// pathTree is the main path and the root path
} else {
// pathTree is the main path, but not the root path
}
Each path tree contains at least one NMAEHLink
, which represents the smallest segment on the road that can be accessed via the API. Usually the main path of an NMAEHPathTrees
is split into several NMAEHLink
which can be uniquely identified by an ID. A road leg can be seen as a sequence of several links.
Predicting the Most Probable Path (MPP)
Without giving a route to follow, Electronic Horizon predicts the most probable path (which acts as root or main path) a user may take. Previous positions may be taken into account. If no previous position is available, the API suggests the most likely path solely based on the available road attributes.
The probability of each path can be extracted like shown below:
- (void)electronicHorizon:(nonnull NMAElectronicHorizon *)electronicHorizon
didReceiveNewPosition:(nonnull NMAEHPosition *)position {
NMAEHPathTree* pathTree = position.pathTree;
if (pathTree == nil) {
// off-road
return;
}
NMAEHPathTree* parentPathTree = pathTree.parent;
if (parentPathTree == nil) {
// pathTree is the main path
}
// Main path is expected to have a probability of 1 (= 100%)
float probability = pathTree.probability;
NSLog(@"Probability of main path: %f", probability);
for (NMAEHPathTree* childPathTree in pathTree.children) {
NSLog(@"Probability of side path: %f", childPathTree.probability);
}
}
If a path tree is predicted with a probability of 0, then it is highly unlikely that a driver may take this turn. Nevertheless Electronic Horizon is including such roads. In some cases an application may want to present such information to the user, such as in the case of one-way streets. Note that all probabilities except for the main path are below 0.5 (= 50%) as a driver is expected to follow the main path with a higher probability.
Setting a Route
Typically you want Electronic Horizon to follow a route which then becomes the MPP. You can directly set a route to the electronic horizon instance:
NMARoute* myRoute = [self getRoute];
[self.electronicHorizon setRoute: myRoute];
There is no option to unset a route as setting a nil route would lead to an NSError
. However, as soon as a user might leave the path of the route, the MPP is recalculated based on the new positions.
NMATransportModeCar
and NMATransportModeTruck
are supported. Other types throw an NSError
. Customize the Electronic Horizon Ahead and Behind
Typically you want to define the kilometres ahead that Electronic Horizon can predict. You can do this by setting an NSArray
to define up to n nested side paths.
int trailingDistanceInMeters = 200;
[self.electronicHorizon setTrailingDistanceInCentimeters: trailingDistanceInMeters * 100];
int mainPathDistanceInMeters = 1000;
int sidePathDistanceInMeters = 500;
NSArray *distances = [NSArray arrayWithObjects:
[NSNumber numberWithInt: mainPathDistanceInMeters * 100],
[NSNumber numberWithInt: sidePathDistanceInMeters * 100], nil];
[self.electronicHorizon setLookAheadDistancesInCentimeters: distances];
Old links and path tree children are removed only when they are fully behind the defined trailing distance from the current position. If a link is longer than the trailing distance, the link is not removed until its start and end nodes are outside the defined threshold. Therefore it may appear that setting a trailing distance might not have an immediate effect, which is especially valid for longer links. Note that the minimum trailing distance is 1 cm.
The main path has at least one child while the full path tree always contains n + 1 side paths. Although the driver is expected to follow the main path, at each junction alternative paths can be taken. Any road that is branching from the main path is called a 1st level side path. Since side paths can further branch into side paths, all side paths branching from 1st level side paths are called 2nd level side paths, and so on.
As you can see from the illustration below, the distance of each side path starts from the vehicle position. When three look-ahead distances are set (MPP, 1st side path, 2nd side path), then a side path of the 2nd side path is as long as one link. See the light blue arrow below as an example.

In order to understand how the root path tree and its children are generated, it may be helpful to visualize the paths programmatically on a map view as shown below for two look-ahead distances. The full path tree can be retrieved from electronicHorizon:didReceiveNewPosition:
callback:
- (void)electronicHorizon:(nonnull NMAElectronicHorizon *)electronicHorizon
didReceiveNewPosition:(nonnull NMAEHPosition *)position {
NMAEHPathTree* pathTree = position.pathTree;
if (pathTree != nil && [pathTree isEqual: self.previousPathTree]) {
// same PathTree object, we ignore any added paths
// and wait for a newly created PathTree
return;
}
self.previousPathTree = pathTree;
if (pathTree != nil) {
[self showAllLinksInPathTree: pathTree];
} else {
// offroad
}
}
NMAEHPathTree
object is extended. In this example we are only interested in a newly created path tree object to visualize the full path tree. Keep in mind that a user is usually travelling along the expected path. This path is then extended instead of being created freshly anew. Same applies for the paths behind the trailing distance which are removed from a parent path tree instead of being reset as part of a full tree reset (see electronicHorizonDidReceiveTreeReset:
). Once you can be sure to have a valid path tree, you can start rendering all nested links. The zIndex
is only used to ensure that deeper paths are rendered on top of the higher ones in the hierarchy:
- (void) showAllLinksInPathTree: (NMAEHPathTree *) mainPathTree {
int zIndex = 0;
[self addLinksToHereMap: mainPathTree.links
withColor: [UIColor redColor]
andIndex: zIndex];
// comment out the below to show only main path
for (NMAEHPathTree* firstLevelSidePath in mainPathTree.children) {
[self addLinksToHereMap: firstLevelSidePath.links
withColor: [UIColor blueColor]
andIndex: ++zIndex];
for (NMAEHPathTree* secondLevelSidePath in firstLevelSidePath.children) {
[self addLinksToHereMap: secondLevelSidePath.links
withColor: [UIColor greenColor]
andIndex: ++zIndex];
// and so on ...
}
}
}
- (void) addLinksToHereMap: (NMAEHLinkRange *) links
withColor: (UIColor *) linkColor
andIndex: (int) zIndex {
for (NMAEHLink* link in links) {
NMAGeoPolyline* geoPolyline = [self.mapAccessor getLinkPolyline: link];
if (geoPolyline) {
NMAMapPolyline* mapPolyline = [[NMAMapPolyline alloc] initWithPolyline: geoPolyline];
[mapPolyline setLineColor: linkColor];
[mapPolyline setLineWidth: 10];
[mapPolyline setZIndex: zIndex];
[self.mapView addMapObject: mapPolyline];
}
}
}
By accessing an NMAGeoPolyline
you can render the path as a map object onto the map as shown above. The path geometry of each link is defined by an NMAGeoPolyline
you should extract using an NMAEHMapAccessor
. Since the NMAGeoPolyline
is not part of the link itself, it must be extracted from the map data. Therefore it is mandatory to check for nil to ensure that the needed map data is available.
At each junction a driver may decide to drive back to where she/he was coming from. These paths have a probability below 50% but are nevertheless a valid choice and are therefore by default not excluded from Electronic Horizon path tree hierarchy. However, in most cases you would like to ignore unlikely turns. For example, with the following code snippet you can detect if a side path is following the parent path in opposite direction:
- (bool) isBackTurn: (NMAEHPathTree *) childPathTree {
NMAEHPathTree* parentPath = childPathTree.parent;
if (!parentPath) {
return false;
}
long childPathTreeOffset = [childPathTree offsetCentimeters];
for (NMAEHLink* parentLink in parentPath.links) {
if ([parentLink endOffsetCentimeters] == childPathTreeOffset) {
NMAEHLink* firstInChild = childPathTree.links.nextObject;
return firstInChild.id == parentLink.id &&
firstInChild.direction != parentLink.direction;
}
}
return false;
}
First you compare the end offset distance of the links of the parent path with the offset of the side path (child). Then you can compare the ID of the first link of the children with the ID of the last link of the parent: If the direction is different, then the child must be heading in the opposite direction. Each road segment is represented by a link which is uniquely identified by an ID. Only attributes like direction and / or offsets may be different for the same link. Note that this is just an example which should be further optimized and adapted to your specific needs.
If all goes well, you should see the full path tree rendered as shown below:
- The root path is red
- The 1st level side path is blue
- The 2nd level side path is green

It is not possible to set a look-ahead distance for the n + 1 child: Therefore the green side path (which is the 2nd level side path) is only as long as one link (the first link on a side path of the blue path). Remember that we have passed only two distance values for the main path (red) and the 1st level side path (blue). By default this results in a 3rd level side path (green) that consists of one link.
NMAEHPathTree
you receive with electronicHorizon:didReceiveNewPosition:
callback always includes the full road network ahead including unlikely paths like, for example, one-way or dead-end streets. Depending on your use case, it may be useful to ignore paths with a low probability or to limit the scope to the main path only - or even to filter out links that follow into the opposite direction like shown above. Make sure not to add too many side paths to the horizon as this would not only increase processing time but also the likelihood of less important links. In most cases it should be sufficient to set only one look-ahead distance as this would by default include first level side paths with a length of one link. Get Road Attributes
Electronic Horizon offers different road attributes that can be accessed from the available map data via an NMAEHMapAccessor
. Some attributes like slope data are directly accessible while NMAEHMetaData
(such as side of driving or unit system) and NMAEHLinkInformation
(such as form of route) hold multiple attributes. All attributes are valid for the entire link.
Below is an example on how to extract speed limits from NMAEHLinkInformation
:
- (void)logSpeedLimits: (NMAEHPathTree *)pathTree {
NMAEHMapAccessor* mapAccessor = self.electronicHorizon.mapAccessor;
for (NMAEHLink* link in pathTree.links) {
NMAEHLinkInformation* linkInformation = [mapAccessor getLinkInformation: link];
if (linkInformation) {
double speedLimitMetersPerSecond = linkInformation.speedLimitMetersPerSecond;
NSLog(@"Speed limit on path: %ld Km", lroundl(speedLimitMetersPerSecond * 3.6));
} else {
NSLog(@"Map data not available");
}
}
}
One speed limit is available per link and it is valid for the whole length of the link. In other words: If the speed limit changes in comparison to the previous link, the location of where the speed limit changes equals the start coordinate of the link.
Please refer to the API reference for more road attributes (like functional road classes) which could be extracted in a similar way.
Road attributes that are not part of NMAEHLinkInformation
must be retrieved directly from NMAEHMapAccessor
. Below you can find an example on how this could be implemented for road slope data:
double lengthInMeters;
NMAEHLinkInformation* linkInformation = [self.mapAccessor getLinkInformation: link];
if (linkInformation) {
lengthInMeters = linkInformation.lengthMeters;
} else {
NSLog(@"Map data not available");
return;
}
NSArray<NMAEHSlopeDataPoint *>* slopeDataPointList = [self.mapAccessor getSlopeDataPoints: link];
if (!slopeDataPointList) {
NSLog(@"Map data not available");
return;
}
for (NMAEHSlopeDataPoint* slopeDataPoint in slopeDataPointList) {
double normalizedPosition = slopeDataPoint.relativePositionOnLink;
double positionOnLinkInMeters = normalizedPosition * lengthInMeters;
double percent = slopeDataPoint.slopePercent;
NSLog(@"Slope data position: %f", positionOnLinkInMeters);
// if percent is greater/smaller than +/- 100% => Infinity
if (percent == +INFINITY) {
NSLog(@"Ascending slope > 45°");
} else if (percent == -INFINITY) {
NSLog(@"Downward slope > 45°");
} else {
if (percent > 0) {
NSLog(@"Ascending slope: %f", percent);
} else {
NSLog(@"Downward slope: %f", percent);
}
}
}
While the raw slope data is extracted from [self.mapAccessor getSlopeDataPoints: link]
, you may also want to retrieve additional data from NMAEHLinkInformation
such as the total length in metres of the current link. An NMAEHSlopeDataPoint
holds only the normalized position of the link (for example, 0.0 = Start, 0.5 = Middle, 1.0 = End). In order to find the position of the NMAEHSlopeDataPoint
relative to the beginning of the link, we should multiply its position by the relative length:
double positionOnLinkInMeters = normalizedPosition * lengthInMeters;
The slope itself is given as percentage value as it would appear on a traffic sign. 100% equals a slope of 45° degrees. Although the world-wide steepest known slope is around 35° degrees, in theory it can happen that a slope is higher than 45° degrees. This is indicated by the Electronic Horizon API as positive or negative infinity as shown above. There can be no percentage value above 100% or below -100%. A positive value indicates an ascending slope while a negative value represents a downward slope. The coordinate of the slope data point can be found by comparing its relative position with the curvature data extracted from the NMAGeoPolyline
of the link (see above).
Finding the Distance to a Link
Usually an Electronic Horizon implementation may want to inform a driver in advance about upcoming relevant road attributes such as a tunnel ahead. In order to calculate the distance from the current vehicle position to the start of an NMAEHLink
(for example, when representing such attribute as a Tunnel), the API provides offset values in centimetres.
These offsets are available for each NMAEHLink
, NMAEHPathTree
, and the Most Probably Path (MPP). Note that an offset of a side path can be directly retrieved from the side path. It indicates the length from the start of its parent path to the beginning of the side path. Side paths that branch from the MPP contain the length from the start of the MPP to the beginning of the side path. An NMAEHLink
contains two offsets indicating start and end offset of the link. The current vehicle position is updated with each update
call and it is indicated by position offset.

This can be used to accumulate the offset values to find the distance to any given NMAEHLink
in relation to the vehicle position. The vehicle position is given by position.offsetCentimeters
. Note that the MPP may not be the root path in case the driver has decided to leave the previous MPP. The implementation below iterates through the entire path tree starting from the path that contains the NMAEHLink
of interest:
- (NSUInteger) getDistanceFromVehicleToStartOfLinkInCentimeters: (NMAEHPosition *) position
path: (NMAEHPathTree *) pathContainingTheLink
link: (NMAEHLink *) link {
NSUInteger offset = link.startOffsetCentimeters;
NMAEHPathTree* currentPath = pathContainingTheLink;
while (currentPath != position.pathTree) {
offset += currentPath.offsetCentimeters;
currentPath = currentPath.parent;
}
return offset - position.offsetCentimeters;
}
If your application wants to inform a driver about upcoming tunnels, you first need to find a link that represents a tunnel. Then you can calculate the distance to that link and inform the user. Note that the distance information is provided with an accuracy down to centimetres, so you may want to convert the distance value to metres by dividing by 100:
NMAEHLinkInformation* linkInformation = [self.mapAccessor getLinkInformation: link];
if (linkInformation && linkInformation.isTunnel) {
NSUInteger distanceFromVehicleToTunnelInCentimeters =
[self getDistanceFromVehicleToStartOfLinkInCentimeters: self.position path: pathTree link: link];
NSLog(@"Tunnel ahead in: : %ld meters", distanceFromVehicleToTunnelInCentimeters/100);
}
Retrieving ADASIS v2 Message Data
The Electronic Horizon API is capable of enriching the vehicle data by providing relevant message data for ADASIS v2 compliant in-car clients. To prepare communication to a client, you first need to provide a message configuration to define the desired types of data. As a next step, you can create an instance of the NMAAdasisV2Engine
which consumes that configuration.
To receive messages, the delegate class must conform to the NMAAdasisV2EngineDelegate
protocol which provides adasisV2Engine:didReceiveAdasisMessage:
callback.
- (id)init {
NMAAdasisV2MessageConfiguration* adasisV2MessageConfiguration =
NMAAdasisV2MessageConfiguration.createWithAllMessagesEnabled;
self.adasisV2Engine = [[NMAAdasisV2Engine alloc] initWithConfiguration:adasisV2MessageConfiguration];
self.adasisV2Engine.delegate = self;
}
- (void)adasisV2Engine:(nonnull NMAAdasisV2Engine *)adasisV2Engine
didReceiveAdasisMessage:(nonnull NSData *)message {
// The ADASIS v2 specification defines messages with 8 bytes payload data
// where the first three bits define the message type followed by the actual content.
NSLog(@"ADASIS message received. Length: %lu", (unsigned long)message.length);
}
The above creates a configuration which enables all available message types. Alternatively you can create a configuration enabling only default message types by calling:
NMAAdasisV2MessageConfiguration* adasisV2MessageConfiguration =
NMAAdasisV2MessageConfiguration.createWithDefaultMessagesEnabled;
This enables only POSITION
, STUB
, SEGMENT
, and META-DATA
message types. In total, six different message types are defined by the ADASIS v2 specification:
-
POSITION
message specifies the current position(s) of the vehicle -
STUB
message indicates the start of a new path that has an origin at an existing one -
SEGMENT
message specifies the most important attributes of a part of the path -
PROFILE SHORT
message describes attribute of the path whose value can be expressed in 10 bits -
PROFILE LONG
message describes attribute of the path whose value can be expressed in 32 bits -
META-DATA
message contains utility data
The Electronic Horizon API provides getters and setters to dynamically enable or disable each of the messages and its parts containing data (such as slopes) individually like shown below for SEGMENT
message type:
if (adasisV2MessageConfiguration.segmentEnabled) {
adasisV2MessageConfiguration.segmentEnabled = false;
}
You can run a single NMAAdasisV2Engine
instance along with an NMAElectronicHorizion
instance but you have to make sure to call [self.adasisV2Engine update]
in order to trigger a sequence of ADASIS messages based on the current device position. The usage follows the same pattern as described already for the NMAElectronicHorizion
instance. Therefore it is required to start NMAPositioningManager
before you are able to receive your first ADASIS v2 compliant messages.
ADASIS v2 assumes a one-way communication between an ADASIS v2 Horizon provider and a client. The Electronic Horizon API ensures that the receiving sequence of messages is compliant to the ADASIS v2 specification, so usually you do not need to parse its content. Note that the ADASIS specification is proprietary. In order to establish a communication to a client, please contact your ADASIS v2 stakeholder. The AdasisV2Engine solely acts as provider and therefore does not receive or parse any information from a contributing client – as this would typically depend on the individual needs of an actual implementation.