EV Routing sample using HERE Maps API for Javascript - Part 1
This month we celebrated World EV Day, so we are going to look at building a simple sample showing EV routing features of the HERE Routing API using HERE Maps API for Javascript. This sample will expand on the existing “Map with Driving Route from A to B” sample (source code).
EV routing features of the HERE Routing API extend the routing service electric vehicle specific options, this behavior can be enabled by adding 'ev[makeReachable]=true' parameter to the request. It also leverages HERE EV Charge Points, a database that includes vehicle brand, real time availability of charging stations, subscription and pricing information.
Additional parameters related to the charging preferences and attributes of the vehicle are necessary, they are explained in this documentation. Below are the parameters we use for this sample showing a route from Los Angeles to Grand Canyon National Park.
var router = platform.getRoutingService(null, 8),
routeRequestParams = {
'transportMode': 'car',
'origin':'34.059761,-118.276802', //LA
'destination': '36.197203,-112.05298',//Grand Canyon North Rim
'units': 'imperial',
'return': 'polyline,turnByTurnActions,actions,instructions,travelSummary',
'ev[freeFlowSpeedTable]':'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,250,0.351',
'ev[trafficSpeedTable]':'0,0.349,27,0.319,45,0.329,60,0.266,75,0.287,90,0.318,100,0.33,110,0.335,120,0.35,130,0.36,250,0.36',
'ev[auxiliaryConsumption]':'1.8',
'ev[ascent]':'9',
'ev[descent]':'4.3',
'ev[initialCharge]':'80',
'ev[maxCharge]':'80',
'ev[chargingCurve]':'0,239,32,199,56,167,60,130,64,111,68,83,72,55,76,33,78,17,80,1',
'ev[maxChargingVoltage]':'400',
'ev[maxChargeAfterChargingStation]':'72',
'ev[minChargeAtChargingStation]':'8',
'ev[minChargeAtDestination]':'8',
'ev[chargingSetupDuration]':'300',
'ev[makeReachable]':'true',
'ev[connectorTypes]':'iec62196Type1Combo,iec62196Type2Combo,Chademo,Tesla'
};
When the EV routing features are enabled, the response will include a 'postActions' in the section, which describes the charging stop. Below is the sample of the JSON response of the first section from Los Angeles to the Hesperia Charging Station.
{
"id": "2f614d10-xxx",
"sections": [
{
"id": "25e5ad38-xxx",
"type": "vehicle",
"actions": [...],
"turnByTurnActions": [...],
"postActions": [
{
"action": "chargingSetup",
"duration": 300
},
{
"action": "charging",
"duration": 1170,
"consumablePower": 250,
"arrivalCharge": 24.1241,
"targetCharge": 72
}
],
"departure": {
"time": "2022-09-20T23:45:16-07:00",
"place": {
"type": "place",
"location": {
"lat": 34.06008,
"lng": -118.27608,
"elv": 56
},
"originalLocation": {
"lat": 34.0597609,
"lng": -118.276802
}
},
"charge": 80
},
"arrival": {
"time": "2022-09-21T01:02:57-07:00",
"place": {
"type": "chargingStation",
"location": {
"lat": 34.42483,
"lng": -117.3857801,
"elv": 1046
},
"id": "ZW06NTZlMWM3ZjA0MGQ4ZTcyNDIwZWViN2Y5MzZiMDZmZWE",
"name": "Tesla Supercharger Hesperia, CA",
"attributes": {
"power": 250,
"current": 625,
"voltage": 400,
"supplyType": "dc",
"connectorType": "tesla"
},
"brand": {
"hrn": "8f3182163857c5463c883017ff1cd3cc",
"name": "Tesla"
}
},
"charge": 24.1241
},
"travelSummary": {
"duration": 4661,
"length": 124806,
"consumption": 55.8759,
"baseDuration": 4597
},
"polyline": "<encoded polyline>",
"language": "en-us",
"transport": {
"mode": "car"
}
},
.....
}
In order to show this new additional information about the charging stop on the map and the popup, we are going to replace the markers that originally show the maneuvers to show the charging stations, as shown in the code below.
/**
* Creates a series of H.map.Marker points from the route and adds them to the map.
* @param {Object} route A route as received from the H.service.RoutingService
*/
function addManueversToMap(route) {
var svgEVMarkup = '<svg />EV charge icon</svg>',
dotMarkup = '<svg /> dot icon</svg>',
dotIcon = new H.map.Icon(dotMarkup, {anchor: {x:8, y:8}}),
EVIcon = new H.map.Icon(svgEVMarkup, {anchor: {x:8, y:8}}),
group = new H.map.Group(),
i,
j;
//adding departing marker
var dotMarker = new H.map.Marker({
lat: 34.059761,
lng: -118.276802},
{icon: dotIcon});
dotMarker.instruction = "Departing from Los Angeles";
group.addObject(dotMarker);
route.sections.forEach((section, index, theArray) => {
let poly = H.geo.LineString.fromFlexiblePolyline(section.polyline).getLatLngAltArray();
let actions = section.actions;
let action = actions[actions.length-1];
var EVMarker = new H.map.Marker({
lat: poly[action.offset * 3],
lng: poly[action.offset * 3 + 1]},
{icon: EVIcon});
var dotMarker = new H.map.Marker({
lat: poly[action.offset * 3],
lng: poly[action.offset * 3 + 1]},
{icon: dotIcon});
if (index < theArray.length -1 && index >-1){
EVMarker.instruction = section.postActions[1].action + " "
+ "Arrival Charge: " + section.postActions[1].arrivalCharge + "% "
+ "Consumable Power: " + section.postActions[1].consumablePower + " "
+ "Duration: " + toMMSS(section.postActions[1].duration) + " "
+ "Target Charge: " + section.postActions[1].targetCharge + "% ";
group.addObject(EVMarker);
}else{
dotMarker.instruction = action.instruction;
group.addObject(dotMarker);
}
group.addEventListener('tap', function (evt) {
map.setCenter(evt.target.getGeometry());
openBubble(evt.target.getGeometry(), evt.target.instruction);
}, false);
// Add the maneuvers group to the map
map.addObject(group);
});
}
And we also need the additional charging details in the turn-by-turn directions, as shown in the code below.
function addManueversToPanel(route) {
var nodeOL = document.createElement('ol');
nodeOL.style.fontSize = 'small';
nodeOL.style.marginLeft ='5%';
nodeOL.style.marginRight ='5%';
nodeOL.className = 'directions';
route.sections.forEach((section, sid, theSArray) => {
section.actions.forEach((action, idx, theArray) => {
var li = document.createElement('li'),
spanArrow = document.createElement('span'),
spanInstruction = document.createElement('span');
spanArrow.className = 'arrow ' + (action.direction || '') + action.action;
spanInstruction.innerHTML = section.actions[idx].instruction;
li.appendChild(spanArrow);
li.appendChild(spanInstruction);
nodeOL.appendChild(li);
if (idx == theArray.length-1 && sid < theSArray.length - 2) {
spanArrow.className = 'arrow ' + section.postActions[1].action;
spanInstruction.innerHTML += section.postActions[1].action + " "
+ "Arrival Charge: " + section.postActions[1].arrivalCharge + "% "
+ "Consumable Power: " + section.postActions[1].consumablePower + " "
+ "Duration: " + toMMSS(section.postActions[1].duration) + " "
+ "Target Charge: " + section.postActions[1].targetCharge + "% ";
}
li.appendChild(spanArrow);
li.appendChild(spanInstruction);
nodeOL.appendChild(li);
});
});
And we need to add the charging time of each section to the total duration of the travel.
function addSummaryToPanel(route) {
let duration = 0,
distance = 0;
route.sections.forEach((section, index, theArray) => {
distance += section.travelSummary.length;
duration += section.travelSummary.duration;
//adding charging time
if (index < theArray.length -1) duration += section.postActions[0].duration + section.postActions[1].duration;
});
....
}
Here is the result of the sample app.
That is all for part 1, we hope you find this useful and give this a try. If you are interested in trying this sample, here is the source code of this sample and the code changes from the original sample. Stay tuned for the next part as we continue to add more functionalities to this application.
Have your say
Sign up for our newsletter
Why sign up:
- Latest offers and discounts
- Tailored content delivered weekly
- Exclusive events
- One click to unsubscribe