×
Namespaces

Variants
Actions
Revision as of 18:49, 24 April 2013 by jasfox (Talk | contribs)

HERE Maps API - Advanced Routing

From Nokia Developer Wiki
Jump to: navigation, search

This article explains how to request routing information using the HERE Maps API and display routing details on screen. The structure of a route response is discussed in depth. Internationalization of the route instructions is also discussed.

Article Metadata
Code Example
Source file: address_route.zip
Tested with
Devices(s): Google Chrome, Firefox, Internet Explorer, Opera
Compatibility
Platform(s): Web Browser
Dependencies: HERE Maps 2.2.4
Article
Keywords: HERE Maps, JavaScript, routing, search
Created: jasfox (08 Feb 2012)
Last edited: jasfox (24 Apr 2013)

Contents

Introduction

Note.pngNote: Use of Nokia's routing service is subject to terms and conditions, specifically it is prohibited to provide real time turn-by-turn navigation services, however the routing service may be used in most applications, provided that the routing itself is not the primary functionality. The relevant section of the terms and conditions is repeated below:
4 (i) You will not: use or incorporate, without Nokia’s prior written permission, the Service, Location API Developer Package or any part thereof, in connection with any Application or other service (a) which has the primary functionality of providing turn-by-turn navigation services, real time navigation or route guidance; or (b) where such Application’s functionality is substantially similar to the HERE Maps or navigation/location-based products distributed by Nokia or its affiliates; or (c) which has the primary purpose of capturing or collecting end user data;

The full terms and conditions for the use of the location APIs can be found here

Although a simple point-to-point routing example may be found on the API explorer, it does not go further than displaying a route on screen. Further development is required in order to full utilize the power of the routing service and obtain the most from the API. Given that it is usually easier to modify an existing example rather than build one from scratch, this example attempts to fill a gap by providing a series of library functions to do the following:

  • Calculate a route based on two or more waypoints as defined by parameters in the query string.
  • Alternatively, calculate a route based on two or more addresses defined in the query string.
  • Display the shortest route found on a map on screen.
  • Interrogate the details of the route response and display natural language turn-by-turn instructions for each of the maneuvers required.
  • Offer alternative information for an international audience (e.g displaying miles rather than kilometers, turn-by-turn instructions in different languages. )

Tip.pngTip: Since April 2012, the HERE Maps website has now started supporting routing deep links, e.g. to find the route from Madrid to Marseille via Barcelona, use the following URL: http://maps.nokia.com/drive/Madrid/Barcelona/Marseille . Creating these URLs would allow you to swiftly incorporate a desktop and mobile friendly solution, without the need to build everything from scratch provided you don't need to keep a user on your website. The basic commentary for this Worked Example makes the assumption that you wish provide your own routing solution from scratch in order to keep the user on your website. The library functions provided allow you to create, style and place the generated routing instructions where you see fit.

Preliminary Preparation

Prior to making a routing request, we need at least two waypoints, a start point and a destination. These are supplied by the query string parameters.

Parsing Query Parameters

The reading in of query parameters has been discussed in a previous article. A standard Library function called splitQueryString() has been created for splitting a query string into parts. This can used to extract addresses and/or geocoordinates from the request which can then be used as the input waypoints to calculate the route. For extended character sets (e.g. Chinese) it has been assumed that the addresses have been appropriately URI encoded, and therefore need to be decoded prior to use.

function splitQueryString() {
var param = new Array();
var query = window.location.search.substring(1);
var parms = query.split('&');
for (var i=0; i<parms.length; i++) {
var pos = parms[i].indexOf('=');
if (pos > 0) {
var key = parms[i].substring(0,pos);
var val = parms[i].substring(pos+1);
param[key] = decodeURI(val);
}
}
return param;
}
var param = splitQueryString();

It remains to decide the format of the query string to be parsed. Given that people tend to think in terms of addresses, it would be useful to accept a series of addresses in the format below:

http://www.example.com/?addr1=...&add2=...&addr3=...

However geocoding addresses (that is transforming them into latitude/longitude) is an inexact science, and no consideration has been given to the fact that the address may be misspelt or the address format may be wrong. It would be more accurate if geolocations could be specified by latitude and longitude directly, so the following format is also supported.

http://www.example.com/?lat1=...&long1=...&lat2=...&long2=...&lat3=...&long3=...

In order to support both of these waypoint formats, the first 10 parameters addr0...addr9 and lat/long0...lat/long9 are parsed in the function below.

var addresses = new Array();
var geoCodedWaypoints = new Array();
var expectedWaypoints = 0;
 
for (var i = 0; i < 10; i++){
if ((param['addr' + i] === undefined) == false){
addresses.push(param['addr' + i]);
expectedWaypoints++;
}
if (((param['lat' + i] === undefined) == false) && ((param['lat' + i] === undefined) == false)){
geoCodedWaypoints.push(new nokia.maps.geo.Coordinate
(parseFloat(param['lat' + i]), parseFloat(param['long'+ i])));
expectedWaypoints++;
}
}

Geocoding addresses

Tip.pngTip: Use the latitude/longitude format where possible, as this reduces user error. Usually you will be able to supply at least one of the desired coordinates either by geocoding your destination or using geolocation to obtain the user's start point

If the user has supplied the waypoints as free text addresses, it is necessary to convert these into geo-coordinates before calculating the route. In order to reduce processing time, these calculations can be made concurrently. The details of how to make concurrent search requests has been discussed in a previous article. Whenever a geocode response has been obtained, an entry is added to the geoCodedWaypoints array. Once all the addresses have been processed, the map can be set up, and the calculation of the route invoked.

 var i = addresses.length;
while(i--) {
nokia.places.search.manager.geoCode({
searchTerm :addresses[i],
onComplete: new searchManager(i).onSearchComplete
});
}
function searchManager($index) {
this.$index = $index;
this.onSearchComplete = function (data, requestStatus) {
// If the search has finished we can process the results
 
if (requestStatus == "OK") {
 
geoCodedWaypoints [$index] = data.location.position;
addressContainer.objects.add(new nokia.maps.map.StandardMarker
( geoCodedWaypoints [$index],{text: ($index + 1)}));
//increment the counter to notify another manager has finished
managersFinished++;
} else if(requestStatus === "ERROR") {
// we'll also increment in case of an error
managersFinished++;
}
 
// if all managers are finished, we call the final function
if(managersFinished === addresses.length) {
// We can now continue with known Geolocations
setUpTheMap();
// It does no harm in focusing on the appropriate area prior to making the
// routing calculation.
 
// hence we get the bounding box of the container
var bbox = addressContainer.getBoundingBox();
 
// if the bounding box is null then there are no objects inside
// meaning no markers have been added to it
if (bbox != null) {
// we have at least one address mapped so we zoomTo it
display.zoomTo(bbox);
}
calculateRouteFromKnownWaypoints();
}
}};

Making the route request and deciphering the result

Having obtained two waypoints, we can now make the route request. This consists of creating a router adding a callback function, making the request and obtaining the result. The code here is basically a repeat of the simple route example in the developer playground, and described in the Simple routing article. The main processing of the route occurs in the onRouteCalculated() method and will be discussed in greater detail below. In the example below, we are obtaining the shortest route for a vehicle - obviously the mode and type of transport may be altered as required.

var route;
var router = new nokia.maps.routing.Manager(); // create a route manager;
router.addObserver("state", onRouteCalculated);
var modes = [{
type: "shortest",
transportModes: ["car"],
options: "",
trafficMode: "default"
}];
router.calculateRoute(waypoints, modes);

The function onRouteCalculated() will be called when a route is calculated. Assuming a route has been returned we can:

  • Display a polyline showing the route on the map. (this is the same as the Playground example)
  • Display textual instructions of the list of each of the maneuvers which make up the route.
  • Add a small marker on each maneuver point which shows the associated text instruction and more visual information (i.e. Junction view and turnpoint).

In order to understand the details of the route processing it is necessary to understand the anatomy of a route:

  • A route consists of one or more legs. A leg is is the route between two of the specified waypoints. A simple route will only have one leg. A route with intermediate waypoints (i.e go via.... ) will have multiple legs.
  • legs consist of a series of maneuvers. A maneuver is a location where the driver must decide on an action (e.g. turn left here). Each maneuver will have an associated location and an associated turn instruction.
  • The route's shape defines all of the twists and turns that make up the route in order to display the polyline on screen. The geometry is a series of geolocations, but only some of them have an associated maneuver,

since a road may bend round a series of corners, but the driver can only make a decision to turn at a junction.

Taking the first returned route, we then need to walk through each maneuver in turn to extract the necessary routing information:

var onRouteCalculated = function (observedRouter, key, value) {
if (value == "finished") {
var routes = observedRouter.getRoutes();
var mapRoute = new nokia.maps.routing.component.RouteResultSet(routes[0]).container;
display.objects.add(mapRoute);
route = routes[0];
 
display.zoomTo(mapRoute.getBoundingBox(), false, "default");
 
// Build human readable instructions for each maneuver.
var instructions = "";
var details = "<ol>";
for (var i = 0; i < route.legs.length; i++){
for (var j = 0; j < route.legs[i].maneuvers.length; j++){
details = details + "<li>";
maneuver = route.legs[i].maneuvers[j];
instructions = route.legs[i].maneuvers[j].instruction;
addMarker( maneuver, 0, instructions);
details = details + zoomTolink(maneuver.position) + instructions;
details = details + "</li>";
}
}
details = details + "</ol>";
document.getElementById("details").innerHTML = details;
 
} else if (value == "failed") {
// Something has gone horribly wrong e.g. route too long.
alert("The routing request failed.");
}
};



Distance Calculations

Another cultural difference is that various parts of the globe use different measuring systems. It makes sense to ensure that the turn-by-turn instructions use the system most familiar to the user. This can be supplied as an additional sb scale bar parameter.

  • Firstly the parameter must be read in:
var param = new Array();
param['sb'] = 'k'; // default to metric measurements.
splitQueryString();
// Assume distances will be metric. i.e. scale bar in kilometers.
var metricMeasurements = ("m" != param['sb']);
  • Then the scale bar map component can be altered accordingly:
 display.addComponent( scaleBar);     
scaleBar.set("showImperialUnits", (metricMeasurements == false), true);

Internally, all the distance calculations are made in meters, but this can be converted to Imperial measurements as shown below. For distances greater than 1000m (metric) or 1610m (imperial), Kilometers and Miles are preferred:

function calculateDistance(distance){
if (metricMeasurements){
if (distance < 1000){
return "" + maneuver.length + " m.";
} else {
return "" + Math.floor(distance/100)/10 + " km.";
}
} else {
if (distance < 1610){
return "" + Math.floor(distance/1.0936) + " yards";
} else {
return "" + Math.floor(distance/160.934)/10 + " miles";
}
}
}

The distance of each maneuver is currently not exposed directly, the following code obtains the distance and units directly from the <span class=\"length\"> of the instruction and replaces the text.

// For imperial measurements, extract the distance span
if (metricMeasurements == false){
var ls = instructions.indexOf("<span class=\"length\">")
var ln = instructions.indexOf("</span>" , ls);
if (ls > -1 && ln > -1){
distNode = instructions.substring(ls + 21, ln);
var n=distNode.split(" ");
if (n[1] == "meters"){
imperialText = calculateDistance(n[0]);
} else {
imperialText = calculateDistance(n[0] * 1000);
}
instructions = instructions.substring(0, ls + 21)
+ imperialText + instructions.substring(ln);
}
 
}

Additional Map Controls

Marking the positions of the Maneuvers

Using the library functions to return static images as defined above, it is a simple matter to add these images to an info bubble associated with each maneuver position.

  • A marker is added to each maneuver position - the Worked Example uses a small blue circle.
  • A listener is added to the click event for the marker - this will display an Infobubble containing the HTML to display the turn-by-turn instruction text.
var infoBubbles = new nokia.maps.map.component.InfoBubbles();
display.addComponent( infoBubbles);
function addMarker( maneuver, turn, text, geometry){
 
var marker = new nokia.maps.map.Marker(
maneuver.position,{
icon: "http://example.com/..../icon.png",
anchor: new nokia.maps.util.Point(5, 5)
});
 
marker.html = "<div>" + text + "<br/>"
+ "<\/div>" ;
 
marker.addListener("click" , function(evt) {
infoBubbles.addBubble(evt.target.html, evt.target.coordinate);
}, false);
 
display.objects.add(marker);
}

Zooming to the position of a Maneuver

By defining a GeoBoundingBox with a single coordinate and using the zoomTo() method, the map may be controlled to focus upon a selected maneuver

function zoomTolink(position){
return " <a onclick='display.zoomTo(new nokia.maps.geo.BoundingBox("
+ "new nokia.maps.geo.Coordinate(" + position.latitude + "," + position.longitude + "))"
+ ", false, \"default\");'><img src='http://example.com/..../icon.png' /><\/a>&nbsp;";
}

Summary

Using the library of functions supplied in the Worked Example it is possible to add an in-depth routing service to a web application.

Madrid-Marseilles.png

526 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×