Sorry, you need to enable JavaScript to visit this website.

Adding EV Routing to a TomTom Web Map: Building the Web Application

Part 2 in a series on using TomTom Search and Routing APIs and the Long Distance EV Routing service to supply routing and charging information to electric vehicle (EV) owners.

In a previous article, we discussed what you need to know to create an application that uses the TomTom Long Distance EV Routing service to supply routing and charging information to users. Now we’re going to build the app.

The sample application described in this article allows a user to specify a starting and finishing location, calculate a route between these endpoints, and display both the route and charging stops along the route. Here are the basic steps the app will take:

  1. Initialize the map (init).
  2. Search for the starting location when the user clicks the “Calculate Route” button (findStart).
  3. Search for the finishing location after the starting location is found (findFinish).
  4. Calculate the route when the finishing location is found (calculateRoute).
  5. Add the route to the map when the route is successfully calculated (addRoute).
  6. Add markers to the route for the starting location, finishing location, and charging stations (addRouteMarkers).
  7. Format and display summary text describing the route and charging stops (addRouteSummary).
  8. Pan and zoom the map to optimally fit the route (fitMapToRoute).

The running application will look like the following:

EVPt2

This application uses HTML to provide text boxes and buttons for user interaction, and to add places to display summary text and the map. While the HTML should be familiar to any web developer, it’s worth highlighting the following section, which includes the TomTom Web SDK and the CSS styling it uses:

<link rel="stylesheet" type="text/css" href="https://api.tomtom.com/maps-sdk-for-web/cdn/5.x/5.36.1/maps/maps.css">
<script src="https://api.tomtom.com/maps-sdk-for-web/cdn/5.x/5.36.1/maps/maps-web.min.js"></script>
<script src="https://api.tomtom.com/maps-sdk-for-web/cdn/5.x/5.36.1/services/services-web.min.js"></script>

The next three lines point to the three JavaScript scripts that comprise the application. Here’s  the function we use to access the TomTom Long-Distance EV Routing service:

<script src="calculateLongDistanceEVRoute.js"></script>

This one points to the script containing the parameters we use to describe the electric vehicle, including the consumption model and charging modes:

<script src=”ev_model.js”></script>

And this one indicates the script that holds the main logic for our application:

<script src="ev_routing.js"></script>

Note that the three JavaScript files should be placed in the same directory.

Searching, Routing, and Interacting with the User

The ev_routing.js file contains the majority of the logic for the application, including all user interaction and interactions with the Search and Routing APIs.

We start by initializing the map using the init function, where we set the name of our application and its version. This information is used by TomTom to track usage statistics. We also create the map and associate it with the HTML division where it will be displayed:

tt.setProductInfo(application.name, application.version);
map = tt.map({ key: application.key, container: ids.html.map });

Our “findStart” function is called when the user clicks the “Calculate Route” button. The function first checks that the map we created is fully loaded:

if (!map.loaded()) {
  displayMessage('Please try again later, map is still loading.');
  return;
}

It then removes any previous route, route markers, and summary text.  Removing a route is accomplished by removing the route layer and its source from the map, using the same identifiers we used when we first created them:

map.removeLayer(ids.route.layer);
map.removeSource(ids.route.source);

The markers are maintained in an array.  You can get rid of an individual marker by using “pop” to remove the marker from that array and the marker’s “remove” function to remove it from the map:

markers.pop().remove();

Once any previous route information has been removed, we find the location indicated by the user using our “findLocation” method, then call the findFinish function after the location is found.

findLocation(ids.html.start, findFinish);

The findLocation function simply uses TomTom’s Fuzzy Search service, specifying your personal API key and the user-provided text as parameters:

tt.services.fuzzySearch({ key: application.key, query: queryText })
  .go()
  .then(callbackFunction)
  .catch(function(error) {
    displayMessage('Could not find ' + elementId + ' (' + queryText + '). ' +
      error.message);
  });

The findFinish function is called after the starting location is found. It finds the finishing location and, if successful, calls our calculateRoute function:

findLocation(ids.html.finish, calculateRoute);

The calculateRoute function is called once the finishing location is found. It uses our own calculateLongDistanceEVRoute function to make a web request to TomTom’s Long-Distance EV Routing service. If successful, we display that route; otherwise, we alert the user:

calculateLongDistanceEVRoute({
  key: application.key,
  locations: [startLocation.position, finishLocation.position],
  avoid: 'unpavedRoads',
  vehicleEngineType: 'electric',
  vehicleWeight: consumptionModel.vehicleWeight,
  accelerationEfficiency: consumptionModel.accelerationEfficiency,
  decelerationEfficiency: consumptionModel.decelerationEfficiency,
  uphillEfficiency: consumptionModel.uphillEfficiency,
  downhillEfficiency: consumptionModel.downhillEfficiency,
  constantSpeedConsumptionInkWhPerHundredkm: consumptionModel.constantSpeedConsumptionInkWhPerHundredkm,
  currentChargeInkWh: consumptionModel.currentChargeInkWh,
  maxChargeInkWh: consumptionModel.maxChargeInkWh,
  auxiliaryPowerInkW: consumptionModel.auxiliaryPowerInkW,
  minChargeAtDestinationInkWh: minChargeAtDestinationInkWh,
  minChargeAtChargingStopsInkWh: minChargeAtDestinationInkWh,
  chargingModes : chargingModes
})
.go()
.then(displayRoute)
.catch(function(error) {
  if (error.hasOwnProperty('message'))
    error = error.message;

  displayMessage('Error calculating route. ' + error);
});

Next, the addRoute function is called from our displayRoute function, after a route has been successfully calculated.

We first add a route source to the map. This route source provides the GeoJSON for all of the points that make up the route:

map.addSource(ids.route.source, { type:  'geojson',  data: geoJson });

We then add a route layer to the map where we display the route described by this route source:

map.addLayer({
  id: ids.route.layer,
  type: 'line',
  source: ids.route.source,
  layout: { 'line-join': appearance.line.join, 'line-cap': appearance.line.cap },
  paint: { 'line-color': appearance.line.color, 'line-width': appearance.line.width }
});

We use our addMarker function to create the marker, set its color and position, and then add it to the map and to our own “markers” array. We maintain the markers array so we can later remove the markers when a user calculates a subsequent route.

markers.push(new tt.Marker({ color: color })
  .setLngLat(position)
  .addTo(map));

In our fitMapToRoute function, we pan and zoom the map so the route is optimally displayed.  We start by describing an area with no points:

const bounds = new tt.LngLatBounds();

and then expand the area to include each point that appears in the route:

bounds.extend(coordinate);

Finally, we use the map’s fitBounds function to pan and zoom the map to optimally display this area. We also include a small amount of padding so that markers at the edges of that area are fully displayed.

map.fitBounds(bounds, { padding: appearance.line.padding });

Working with the Long-Distance EV Routing Service

While TomTom provides Web SDK functions to simplify interaction with most services, it doesn’t currently provide one for the Long-Distance EV Routing service. To simplify this interaction, the sample application includes an SDK function — calculateLongDistanceEVRoute — similar in construction to other TomTom Web SDK functions. Specifically, this function provides the following logic:

  1. Format the parameters into a URL (formatUrl).
  2. Format the charging modes into a POST body (go).
  3. Issue the web request and fetch the response (go).
  4. Convert locations in the response to LatLng instances (RouteData).
  5. Convert coordinates to GeoJSON (toGeoJson).

Many of the parameters for the Long-Distance EV Routing service are passed as query string parameters in the URL. Our formatUrl function simply checks that all required properties are provided and adds a query string parameter for each one. It uses the JavaScript encodeURIComponent function to escape any special characters in the parameter values.

After the URL and the POST body have been formatted, our “go” function creates a JavaScript promise to enclose a fetch API call. If the route calculation succeeds, the JSON response is parsed and the “fulfill” function of that promise is called; otherwise, the “reject” function is called.

fetch(url, {
  method: 'POST',
  mode: 'cors',
  credentials: 'same-origin',
  headers: {
    'Content-Type': 'application/json'
  },
  body: body
})
.then(function(response) {
  response
    .json()
    .then(function(obj) {
      if (!obj.hasOwnProperty('error'))
        fulfill(new RouteData(obj));
      else
        reject(obj.error.description);
    });
})
.catch(function(error) {
  reject(error);
});

The JSON response we get back from the Long-Distance EV Routing service expresses locations using a latitude and longitude property. But the TomTom Web SDK and the underlying Mapbox framework instead expect instances of the “LngLat” object, which uses “lat” and “lng” properties.  For compatibility, our RouteData function (and object) converts all of the points in the route to LngLat instances.

When displaying a route on a map, we need to create a source for that route. The easiest way to express this route is to provide a standard GeoJSON object that describes it. To make this easier, we include a “toGeoJson” function in the prototype for our RouteData function. This is similar to what’s provided by the calculateRoute function in the TomTom Web SDK.

const geoJson = {
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: []
      }
    }
  ]
};

const coordinates = geoJson.features[0].geometry.coordinates;

this.routes[0].legs.forEach(function(leg) {
  leg.points.forEach(function(point) {
    coordinates.push([point.lng, point.lat]);
  });
});

Next Steps

We’ve learned how to use TomTom’s Search and Routing APIs to create a visually engaging application to help consumers plan trips in their EVs. The application automatically calculates an optimal route, with planned charging stops along the way. Using TomTom’s award-winning API, all of this is accomplished easily, and without much code.

To create even more useful applications, you can also take advantage of TomTom's Long Distance EV Service to provide the location of nearby charging parks, charging stations, and real-time information about the type and availability of ports at those stations.

The complete application source files are available for download here. For the application to function properly, all of these files should be placed in the same directory. And don’t forget to edit the “ev_routing.js” file to replace “YOUR_API_KEY” with an actual TomTom API key.

Good luck and happy mapping!

First published: 
Friday, June 19, 2020 - 20:28
Last edited: 
Tuesday, June 30, 2020 - 22:41