# Directions

The HERE SDK provides a full-fledged route engine to calculate the best route directions from A to B, including multiple waypoints and localizable maneuver instructions for each turn. You can also specify your preferences by setting the desired route type (fastest or shortest) and various route options (such as speed profiles, route restrictions, vignette options, and more).

• Isoline routing: Calculate isoline polygons to represent the area of reach from a given point based on time, distance or fuel consumption.
• Search along a route: Search for places along an entire route (this feature is described in the Search section).
• Offline routing: When no internet connection is available, you can switch to a dedicated offline routing engine to calculate routes on already cached map data or preloaded offline maps data.

## Get Routes

The HERE SDK supports the following route types:

• Online car route directions.
• Online pedestrian route directions.
• Online truck route directions with highly customizable truck options.
• Online routes for electric vehicles to find the nearest charging stations (based on the calculated energy consumption and given battery specifications).
• Online scooter route directions.

Each route type is determined by one of the available route options: CarOptions, PedestrianOptions, TruckOptions, ScooterOptions, EVCarOptions and EVTruckOptions. These options can be set to the available overloads of the route engine's calculateRoute() method.

Start your trip by creating the route engine in this manner:

do {
try routingEngine = RoutingEngine()
} catch let engineInstantiationError {
fatalError("Failed to initialize routing engine. Cause: \(engineInstantiationError)")
}


Creating a new RoutingEngine instance can throw an error that we have to handle as shown above. Such an error can happen, when, for example, the HERE SDK initialization failed beforehand.

As a next step, you can calculate the route based on two waypoints - a starting location and a destination (both of type Waypoint that holds a GeoCoordinates instance). Below we set default CarOptions to calculate a route that is optimized for cars:

let carOptions = CarOptions()
routingEngine.calculateRoute(with: [Waypoint(coordinates: startGeoCoordinates!),
Waypoint(coordinates: destinationGeoCoordinates!)],
carOptions: carOptions) { (routingError, routes) in

if let error = routingError {
self.showDialog(title: "Error while calculating a route:", message: "\(error)")
return
}

let route = routes!.first
self.showRouteDetails(route: route!)
self.showRouteOnMap(route: route!)
self.logRouteViolations(route: route!)
}


You can call calculateRoute() multiple times, for example, to calculate routes with different routing options in parallel.

Each route calculation will be performed asynchronously. You will get a Route list or a RoutingError that holds a possible error when completed. If all goes well, it is nil. In case of an error, the route list is nil. For example, the engine cannot calculate routes if a route is not feasible for the specified mode of transportation.

If there is no error, the route list will contain only one result. By specifying the number of route alternatives via the route options, you can request additional route variants. By default, a route will be calculated with no route alternatives.

The showRouteDetails()-method from the code snippet above is used to show more route details including maneuver instructions. You can find the full source code in the accompanying example app. Maneuver instructions are also explained in greater detail below. The showRouteOnMap()-method contains an example, how to render a route on the map. We will explain this shortly in the section below.

### Note: Important

A route may contain a list of NoticeCode values that describe potential issues after a route was calculated. For example, when a route should avoid tunnels and the only possible route needs to pass a tunnel, the Route contains a notice that the requested avoidance of tunnels was violated.

• It is recommended to always check a calculated Route for possible violations.
• The NoticeCode is part of a Notice object. A list of possible Notice objects can be accessed per Section of a Route.
• The list will be empty, when no violation occurred.
• If any possible violation is not desired, it is recommended to skip routes that contain at least one violation.

However, an implementation may judge case by case depending on the requested route options and the actual list of NoticeCode values. More about route options can be found in the next section. Important: For the sake of simplicity, the code snippets in this guide do not evaluate the possible enum values of a notice.

You can detect possible route notices with the following method:

// A route may contain several warnings, for example, when a certain route option could not be fulfilled.
// An implementation may decide to reject a route if one or more violations are detected.
private func logRouteViolations(route: Route) {
let sections = route.sections
for section in sections {
for notice in section.notices {
print("This route contains the following warning: \(notice.code)")
}
}
}


## Use Route Options for Each Transport Mode

For the example above, we have set a new CarOptions instance to calculate a car route. You can calculate routes for other transport modes by using dedicated route options for each transport mode. The following route options exist:

• CarOptions to calculate car routes.
• TruckOptions to calculate truck routes.
• PedestrianOptions to calculate routes for pedestrians.
• EVCarOptions and EVTruckOptions to calculate routes for electric vehicles.
• ScooterOptions to calculate routes for scooters.

All of these route options allow you to further specify several parameters to optimize the route calculation to your needs.

Each of the above options contains a field that holds a common RouteOptions object. This option allows you to specify common options such as the number of route alternatives or the OptimizationMode to find the optimal route based on travel time and route length.

By default, a route will be calculated using the fastest route mode.

Alternatively, you can change the algorithm. For example, if you want to reach your destination quickly and the length of the route is less important to you, select the fastest route mode via RouteOptions. Select shortest if you prefer a shorter route, and time is not so important.

To find the best route for you, the routing algorithm takes into account many different parameters. This does not mean the algorithm will always provide the absolute shortest or fastest route. For example, consider the following road network:

When you plan a trip from A to B, you may have the choice between four different roads. Let's assume that the green route represents a highway, then this road may be the fastest route, although it is longer than any of the other routes that would guide you through a city.

If you prefer to take the shortest route instead, then the route algorithm may favor the blue route, although the yellow and the red routes are shorter. Why is this so? The yellow road is the shortest route, of course, but it has to cross a river where a ferry has to be taken. This could be regarded by the algorithm as time-costly. Therefore -as a result- it is possible to prefer the red or blue route over the yellow route, although both are slightly longer.

Let's explore the other two options. The routing algorithm may recommend the blue route as the shortest when comparing the blue and the red route, although it is slightly longer than the red route: several turns are typically not beneficial to a driver. In this case, the red route contains more turns than the blue route, but the blue route may be the preferred route as it is only slightly shorter. The routing algorithm penalizes turns and many other road properties, such as traffic lights or rail crossings that can slow a driver down.

Along with the common routing options, the HERE SDK offers specialized options for the various supported transport modes, such as truck routing, where you can specify, for example, the dimensions of your truck to find only the suitable routes where your truck would fit - considering parameters such as road width or tunnel height.

While the resulting routes are optimized based on certain criteria, there may be situations where you don't want to rely on that. Imagine a city trip through Berlin - finding the fastest or shortest route may not be an option if you want to experience many sightseeing spots the city has to offer. In such a case, setting additional waypoints may be a good idea. Find an example below.

## Get Truck Routes

The HERE SDK supports different transport modes (see above) for route calculation. Similar to the example above that shows how to calculate a route optimized for cars, you can also calculate routes for other transport types such as trucks.

While you can already get a route optimized for trucks by setting the default options, there are many more options available to find the best routes for a truck.

For example, TruckOptions contains additional fields such as TruckSpecifications to specify the dimensions of your truck or a SpeedProfile that can be optionally applied to take the vehicle weight into account.

## Shape the Route with Additional Waypoints

By default, when passing only start and destination waypoints, the resulting route will contain only one route Section. Each route object can contain more route sections depending on the number of set waypoints. Sections act as a route leg that break a route into several logical parts.

Waypoints are coordinates that can be set by the user to determine more sections or the shape the route (which can be useful if you want to make sure you cross a river with a ferry, for example).

There can be two types of waypoints:

• stopover: The default waypoint type. It is guaranteed that this point will be passed, therefore it appears in the list of maneuver instructions, and splits the route into separate route sections.
• passThrough: May not appear in the maneuver instructions list and is rather treated as a hint to shape the route, for example, as a result of a touch input. This type will not split the route into separate sections.

When creating a new Waypoint object, the stopover type is set by default - and this must be the type used for the first and the last waypoint. With just two waypoints acting as the start and destination, a route might look like below:

The RoutingEngine can handle multiple waypoints. The underlying algorithm will try to find the best path to connect all waypoints while respecting the order of the provided array - as well as the WaypointType. Each stopover-waypoint is passed between the starting location and the destination, which are the first and last item of the waypoint list respectively.

let waypoint1GeoCoordinates = createRandomGeoCoordinatesInViewport()
let waypoint2GeoCoordinates = createRandomGeoCoordinatesInViewport()
let waypoints = [Waypoint(coordinates: startGeoCoordinates),
Waypoint(coordinates: waypoint1GeoCoordinates),
Waypoint(coordinates: waypoint2GeoCoordinates),
Waypoint(coordinates: destinationGeoCoordinates)]

let carOptions = CarOptions()
routingEngine.calculateRoute(with: waypoints,
carOptions: carOptions) { (routingError, routes) in

if let error = routingError {
self.showDialog(title: "Error while calculating a route:", message: "\(error)")
return
}

let route = routes!.first
self.showRouteDetails(route: route!)
self.showRouteOnMap(route: route!)
self.logRouteViolations(route: route!)
}


By adding two additional stopover-waypoints to the route using the code snippet above, we now have three route sections between the starting point and the destination as seen in the illustration below.

Please note that the waypoints list order defines the order in which they are going to be passed along the route.

Additional information on the route - such as the estimated time it takes to travel to the destination and the total length of the route in meters - can be retrieved from the Route object as shown below:

let estimatedTravelTimeInSeconds = route.durationInSeconds
let lengthInMeters = route.lengthInMeters


Travel time and length are also available for each Section. Without additional stopover-waypoints, the route will contain only one Section. If additional stopover-waypoints are provided, the route is separated into several route sections between each waypoint, as well as from the starting point to the first waypoint and from the last waypoint to the destination.

By default, an additional waypoint splits a route into separate sections and forces the route to pass this point and to generate a maneuver instruction for it.

Each Section contains the shape of the route in form of a GeoPolyline - represented as an array of coordinates where the first coordinate marks the starting point and the last one the destination.

This can be useful to visualize the route on the map by using, for example, map polylines in a different color for each Section. However, you can also get the polyline directly from the Route object. Below is a code snippet that shows how this could be implemented by using a MapPolyline that is drawn between each waypoint including the starting point and the destination:

let routeGeoPolyline = try! GeoPolyline(vertices: route.polyline)
let routeMapPolyline = MapPolyline(geometry: routeGeoPolyline,
widthInPixels: 20,
color: UIColor(red: 0,
green: 0.56,
blue: 0.54,
alpha: 0.63))


The first screenshot below shows a route without additional waypoints - and therefore only one route section. Starting point and destination are indicated by green-circled map markers. Note that the code for drawing the circled objects is not shown here, but can be seen from the example's source code, if you are interested.

The second screenshot shows the same route as above, but with two additional stopover-waypoints, indicated by red-circled map markers. The route therefore, contains three route sections.

## Zoom to the Route

For some use cases, it may be useful to zoom to the calculated route. The camera class provides a convenient method to adjust the viewport so that a route fits in:

let routeGeoBox = route.boundingBox
camera.lookAt(area: routeGeoBox,
orientation: MapCamera.OrientationUpdate())


Here we use the enclosing bounding box of the route object. This can be used to instantly update the camera: Zoom level and target point of the camera will be changed, so that the given bounding rectangle fits exactly into the viewport. Additionally, we can specify an orientation to specify more camera parameters - here we keep the default values. Note that calling lookAt() will instantly change the view.

## Get Maneuver Instructions

Each Section contains the maneuver instructions a user may need to follow to reach the destination. For each turn, a Maneuver object contains an action and the location where the maneuver must be taken. The action may indicate actions like "depart" or directions such as "turn left".

let sections = route.sections
for section in sections {
logManeuverInstructions(section: section)
}


And here is the code to access the maneuver instructions per section:

private func logManeuverInstructions(section: Section) {
print("Log maneuver instructions per route section:")
let maneuverInstructions = section.maneuvers
for maneuverInstruction in maneuverInstructions {
let maneuverAction = maneuverInstruction.action
let maneuverLocation = maneuverInstruction.coordinates
let maneuverInfo = "\(maneuverInstruction.text)"
+ ", Action: \(maneuverAction)"
+ ", Location: \(maneuverLocation)"
print(maneuverInfo)
}
}


Additionally, an (optionally) HTML-formatted instruction is included. When logging the above maneuverInfo to the console, it may produce a result as printed below:

Take the <span class="exit">2nd exit</span> from Moritzplatz roundabout onto
<span class="next-street">Prinzenstraße</span> toward
<span class="sign"><span lang="de">Neukölln</span></span>.
<span class="distance-description">Go for <span class="length">692 m</span>.
</span>,
Action: 42,
Location: <GeoCoordinates 0x600003c0d120 latitude:52.503866 longitude:13.4109056>


This may be useful to easily build maneuver instructions lists describing the whole route in written form. For example, the ManeuverAction enum can be used to build your own unique routing experience.

## Find Traffic Along a Route

In addition to maneuvers (see above), each Section of a Route contains information of the traffic flow situation at the time when the route was calculated.

Each Section can contain a various amount of TrafficSpeed instances. These are valid along the sub sections of a section. The offset field indicates the index within the section's polyline which you can get as a list of GeoCordinates from the Section instance.

TrafficSpeed contains the baseSpeedInMetersPerSecond, which is the expected default travel speed. Note that this may not be the same as the current speed limit on a road - as a bad road condition may justify a slower travel speed. In addition, you can get the estimated actual travel speed based on the current traffic conditions with trafficSpeedInMetersPerSecond

Similar to the color encoding used for the traffic flow layer, you can indicate the traffic along a route using a jamFactor that has a range from 0 (no traffic) to 10 (road is blocked). See the Traffic section for more details on the traffic features of the HERE SDK.

An example how this value can be mapped to a suitable color is shown below:

Usually, the jamFactor can be interpreted like this:

• 0 <= jamFactor < 4: No or light traffic.
• 4 <= jamFactor < 8: Moderate or slow traffic.
• 8 <= jamFactor < 10: Severe traffic.
• jamFactor = 10: No traffic, ie. the road is blocked.

Note that the jamFactor is not necessarily a linear result calculated from the ratio of trafficSpeedInMetersPerSecond / baseSpeedInMetersPerSecondas it additionally takes also the road type and other conditions into account.

## Offline Routing

In addition to the RoutingEngine, there is also an equivalent for offline use cases available: The OfflineRoutingEngine. It can be constructed in the same way as its online counterpart we already showed above:

private var routingEngine: RoutingProtocol
private var onlineRoutingEngine: RoutingEngine
private var offlineRoutingEngine: OfflineRoutingEngine

...

do {
try onlineRoutingEngine = RoutingEngine()
} catch let engineInstantiationError {
fatalError("Failed to initialize routing engine. Cause: \(engineInstantiationError)")
}

do {
try offlineRoutingEngine = OfflineRoutingEngine()
} catch let engineInstantiationError {
fatalError("Failed to initialize offline routing engine. Cause: \(engineInstantiationError)")
}

// By default, use online routing engine.
routingEngine = onlineRoutingEngine

// Detect whether we should search online or offline.
setNetworkUpdateHandler()


The OfflineRoutingEngine provides the same interfaces as the RoutingEngine, but the results may slightly differ as the results are taken from already downloaded or cached map data instead of initiating a new request to a HERE backend service.

This way the data may be, for example, older compared to the data you may receive when using the RoutingEngine. On the other hand, this class provides results faster - as no online connection is necessary.

Note: You can only calculate routes on already cached or preloaded offline maps data.

As of now, only car routes are supported. Other transport modes may work, but are not officially supported yet.

All available interfaces of the OfflineRoutingEngine are also available in the RoutingEngine and both engines adopt the same protocol. This makes it easy to switch bewteen both engine instances as we will show below.

### Note

To get a quick overview of how all of this works, you can take a look at the offline RoutingExample class. It contains all code snippets shown below and it is part of the RoutingHybrid example app you can find on GitHub.

Below we show one possible use case for the OfflineRoutingEngine. For example, when you are on the go, it can happen that your connection is temprorarily lost. In such a case it makes sense to calculate routes on the already cached or downloaded map data.

To do so, first you need to check if the device has lost its connectivity. As a second step you can use the preferred engine:

// Sets the OfflineRoutingEngine as main engine when the device is not connected, otherwise this will set the
// RoutingEngine that requires connectivity.
private func setRoutingEngine() {
if isDeviceConnected {
routingEngine = onlineRoutingEngine
} else {
routingEngine = offlineRoutingEngine
}
}

// Warning: This only observes state changes of the network adapters of a device
// and it may not detect subsequent losses of Internet access.
private func setNetworkUpdateHandler() {
// Register for changes in network connectivity.
networkPathMonitor.pathUpdateHandler = { path in
if path.status == .satisfied {
self.isDeviceConnected = true
} else {
self.isDeviceConnected = false
}
}

// Start watching for network changes.
let queue = DispatchQueue.global(qos: .background)
networkPathMonitor.start(queue: queue)
}


Here, we use a boolean value isDeviceConnected to store if the device is connected or not.

Now, you can execute the same code on the current routingEngine instance, as shown in the previous sections above.

If the device is offline, the online variant will not find routes and will report an error. Vice versa, when the device is online, but no cached or preloaded map data is available for the area you want to use, the offline variant will report an error as well.

Note that the code to check if the device is online or not is using platform code. It is only checking a connection, so - for example - a device can be connected to a WiFi network but may still not be able to connect to the internet. A more comprehensive solution to check the connectivity of your device can be implemented by trying to make an actual connection first - and when that fails, you can switch to the OfflineRoutingEngine - or the other way round: You can try offline routing first to provide a super fast experience for the user, but when no map data is available, you can try online.

You can find the RoutingHybrid example app on GitHub.

## Show Reachable Area

With isoline routing you can generate polygons to represent the area of reach from a given point based on time, distance or energy consumption: The polygon will encompass all destinations that can be reached in a specific amount of time, a maximum travel distance, or even the charge level available at an electric vehicle.

Isoline routing considers real-time and historical traffic in its calculations.

Some examples of how this could be useful:

• Users can find restaurants within a 2 km walking distance from their current location.
• Users can search for hotels based on the distance to sights, for example, to find hotels within a 20 minute drive to major attractions - such as Disney World and Universal Studios in Orlando, USA.

Below we show a consumption based Isoline example for an electric vehicle scenario, where the driver wants to know which points are reachable within the provided limit of 400 Wh: So, the goal is to consume 400 Wh or less - and the question is: What can the driver reach within this energy limit?

Electric vehicles have a limited reachable range based on their current battery charge and factors affecting the rate of energy consumed, such as road slope or auxiliary power usage. Therefore, it is useful to visualize the appropriate range to avoid running out of energy before reaching a charging point. Since vehicles have unique consumption parameters, they are to be specified in the request for an accurate range to be calculated. For more details, see the electric vehicle section below.

The result will be a GeoPolygon shape that you can show on the map to provide the driver a visual orientation:

let calculationOptions = IsolineOptions.Calculation(rangeType: .consumptionInWattHours, rangeValues: [400])
let isolineOptions = IsolineOptions(calculationOptions: calculationOptions,
evCarOptions: getEVCarOptions())

routingEngine.calculateIsoline(center: Waypoint(coordinates: startGeoCoordinates),
isolineOptions: isolineOptions) { (routingError, isolines) in

if let error = routingError {
self.showDialog(title: "Error while calculating reachable area:", message: "\(error)")
return
}

// When routingError is nil, the isolines list is guaranteed to contain at least one isoline.
// The number of isolines matches the number of requested range values. Here we have used one range value,
// so only one isoline object is expected.
let isoline = isolines!.first!

// If there is more than one polygon, the other polygons indicate separate areas, for example, islands, that
// can only be reached by a ferry.
for geoPolygon in isoline.polygons {
// Show polygon on map.
let fillColor = UIColor(red: 0, green: 0.56, blue: 0.54, alpha: 0.5)
let mapPolygon = MapPolygon(geometry: geoPolygon, color: fillColor)
self.mapPolygonList.append(mapPolygon)
}
}


This finds the area that an electric vehicle can reach by consuming 400 Wh or less, while trying to take the fastest possible route into any possible straight direction from start.

Why fastest? This depends on the route's optimization mode (we have shown in earlier sections above) - of course, you can specify any mode. Note that each isoline needs exactly one center point from where a journey may start.

Since we are interested in the energy consumption, we also provided EVCarOptions. These options must include battery specifications as shown in the electric vehicle routing section below. If you provide a time or distance limit instead as range type, you can provide the usual route options as shown earlier.

The IsolineRangeType defines the type of the provide range value. Here we use 400. You can provide multiple values - and as a result you would then get multiple Isoline objects - one for each provided range value.

On the other hand, each Isoline object can contain multiple polygons for special cases, such as when the area of reach includes an island. Such areas are provided as separate polygons.

The shape of the resulting polygon can be defined by the maxPoints parameter. It determines the number of vertices of the GeoPolygon. Note that this parameter does not influence the actual calculation of the polygon, but just its apearance.

The screenshot shows the center location of the polygon, indicated by a big green circle. It also shows a possible route beyond this area with two additional found charging stations along the route. In the next section you can find how to calculate routes for electric vehicles.

You can find the above code snippet as part of the EVRouting example app on GitHub.

## Get Routes for Electric Vehicles

Electric vehicle (EV) usage and sales continue growing around the world. How can HERE help to provide the best routes for electric vehicles?

• HERE EV Routing delivers optimized routes for EVs to get from A to B, minimizing the number of charging stops and optimizing battery charge times (based on the vehicle’s consumption model). It also takes into account a number of factors when planning trips, including topography, road geometry, real-time traffic information and traffic patterns.

• Instead of searching for charging stations along the route, HERE analyzes the charging time at every possible station and chooses the most appropriate combination that will deliver the shortest driving and charging times.

Getting EV routes is simple. Similar to getting car or truck routes, for EV routing you just need to add electric vehicle specific route options. This way you can get routes for electric vehicles in the same way as for other transport modes. Just specify EVRouteOptions and add them to the calculateRoute() method:

routingEngine.calculateRoute(with: [Waypoint(coordinates: startGeoCoordinates!),
Waypoint(coordinates: destinationGeoCoordinates!)],
evCarOptions: getEVCarOptions()) { (routingError, routes) in

if let error = routingError {
self.showDialog(title: "Error while calculating a route:", message: "\(error)")
return
}

// Use routes from list.
}


By default, EVRouteOptions will not include the required parameters to ensure that a destination can be reached without running out of energy.

To ensure this, you need to set the required parameters that may add charging stations to the route - while still optimizing the result for overall travel time.

Below you can see an example how to create such options:

private func getEVCarOptions() -> EVCarOptions {
var evCarOptions = EVCarOptions()

// The below three options are the minimum you must specify or routing will result in an error.
evCarOptions.consumptionModel.ascentConsumptionInWattHoursPerMeter = 9
evCarOptions.consumptionModel.descentRecoveryInWattHoursPerMeter = 4.3
evCarOptions.consumptionModel.freeFlowSpeedTable = [0: 0.239,
27: 0.239,
60: 0.196,
90: 0.238]

// Ensure that the vehicle does not run out of energy along the way and charging stations are added as additional waypoints.
evCarOptions.ensureReachability = true

// The below options are required when setting the ensureReachability option to true.
evCarOptions.routeOptions.optimizationMode = .fastest
evCarOptions.routeOptions.alternatives = 0
evCarOptions.batterySpecifications.connectorTypes = [.tesla, .iec62196Type1Combo, .iec62196Type2Combo]
evCarOptions.batterySpecifications.totalCapacityInKilowattHours = 80.0
evCarOptions.batterySpecifications.initialChargeInKilowattHours = 10.0
evCarOptions.batterySpecifications.targetChargeInKilowattHours = 72.0
evCarOptions.batterySpecifications.chargingCurve = [0: 239.0,
64: 111.0,
72: 1.0]

// Note: More EV options are availeble, the above shows only the minimum viable options.

return evCarOptions
}


Here we define the EVConsumptionModel to specify the energy consumption model of the electric vehicle. We also add BatterySpecifications. With these options, additional charging stations may be inserted into the route as additional stopovers - which means that the route will be split into more sections depending on the number of inserted charging stations.

If you want to include such required charging stations, it is also mandatory to use OptimizationMode.fastest and to specify zero route alternatives. You also need to set ensureReachability to true.

Note that we also specify the availbable battery connector types, so that charging stations that do not provide a compatible connector to charge the car will be excluded. In the example above, we also specified the initialChargeInKilowattHours. A relatively low value means that the route must include charging stations near the beginning - othewise, the route calculation may fail.

Usually, you can take the consumption and battery information from the car itself or consult the manual or your car manufacturer directly. Note that not all information may be available to you and some information may be only exclusively known by the manufacturer. Either way, the route calculation process will take the provided specifications into account and missing values will be filled with suitable default values.

Especially for longer journeys with electric vehicles, it is important to plan for charging stops along the way. After all, charging stations are much less common than petrol stations. With the above options, the RoutingEngine tries to find the fastest route, i.e., one with the lowest overall time consumed to reach the destination, while ensuring that the vehicle does not run out of energy along the way.

The result of the calculation is a route optimized for electric vehicles - instead of just adding any charging stations found along the way - as we have shown in the Search section.

Once the route is calculated, you can gather more useful information. The below code snippet will log the estimated energy consumption per Section and list the actions you need to take in order to charge the battery - if needed:

// Find inserted charging stations that are required for this route.
// Note that this example assumes only one start waypoint and one destination waypoint.
// By default, each route has one section.
let additionalSectionCount = route.sections.count - 1
// Each additional waypoint splits the route into two sections.
print("Number of required stops at charging stations: \(additionalSectionCount)")
} else {
print("Based on the provided options, the destination can be reached without a stop at a charging station.")
}

var sectionIndex = 0
let sections = route.sections
for section in sections {
let evDetails = section.evDetails
print("Estimated net energy consumption in kWh for this section: \(String(describing: evDetails?.consumptionInKilowattHour))")
for postAction in section.postActions {
switch postAction.action {
case .chargingSetup:
print("At the end of this section you need to setup charging for \(postAction.durationInSeconds) s.")
break
case .charging:
print("At the end of this section you need to charge for \(postAction.durationInSeconds) s.")
break
case .wait:
print("At the end of this section you need to wait for \(postAction.durationInSeconds) s.")
break
default: fatalError("Unknown post action type.")
}
}

print("Section \(sectionIndex): Estimated departure battery charge in kWh: \(String(describing: section.departure.chargeInKilowattHours))")
print("Section \(sectionIndex): Estimated arrival battery charge in kWh: \(String(describing: section.arrival.chargeInKilowattHours))")

// Only charging stations that are needed to reach the destination are listed below.
let depStation = section.departure.chargingStation
if depStation != nil  && !chargingStationsIDs.contains(depStation?.id ?? "-1") {
print("Section \(sectionIndex), name of charging station: \(String(describing: depStation?.name))")
chargingStationsIDs.append(depStation?.id ?? "-1")
}

let arrStation = section.departure.chargingStation
if arrStation != nil && !chargingStationsIDs.contains(arrStation?.id ?? "-1") {
print("Section \(sectionIndex), name of charging station: \(String(describing: arrStation?.name))")
chargingStationsIDs.append(arrStation?.id ?? "-1")
}

sectionIndex += 1
}


A resulting route may look like in the screenshot below.

Here you can see that the route required two stops at a charging station - indicated by red markers. The route therefore contains three sections as each charging station splits the route - like when inserting additional waypoints.

The first section includes a post action, which describes the charging stop. It contains the information on the expected arrival charge among other information.

Unless specified otherwise, the energy consumption is assumed to be in Wh.

### Define the Consumption Model

The following parameters define a consumption model to get more accurate results for electric vehicles:

• ascentConsumptionInWattHoursPerMeter: Rate of energy consumed per meter rise in elevation.
• descentRecoveryInWattHoursPerMeter: Rate of energy recovered per meter fall in elevation.
• freeFlowSpeedTable: Function curve specifying consumption rate at a given free flow speed on a flat stretch of road.
• trafficSpeedTable: Function curve specifying consumption rate at a given speed under traffic conditions on a flat stretch of road.
• auxiliaryConsumptionInWattHoursPerSecond: Rate of energy consumed by the vehicle's auxiliary systems (e.g., air conditioning, lights) per second of travel.

A consumption speed table defines the energy consumption rate when the vehicle travels on a straight road without elevation change at a given speed in km/h. It represents a piecewise linear function.

Here is an example of a free flow speed list. On the left you see the speed and on the right, consumption:

• 0: 0.239
• 27: 0.239
• 45: 0.259
• 60: 0.196
• 75: 0.207
• 90: 0.238
• 100: 0.26
• 110: 0.296
• 120: 0.337
• 130: 0.351

In a graph it will look like this:

You can specify two different consumption speed tables - free flow speed table and traffic speed tables:

• Free flow speed: Describes the energy consumption when travelling at constant speed.
• Traffic speed: Describes the energy consumption when travelling under heavy traffic conditions, for example, when the vehicle is expected to often change the travel speed at a given average speed.

If a trafficSpeedTable is not provided then only the freeFlowSpeedTable is used for calculating speed-related energy consumption.

You can find an EVRouting example app on GitHub.