×
Namespaces

Variants
Actions
Revision as of 11:29, 28 September 2012 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 Nokia Maps API and display routing details on screen. The structure of a route response is discussed in depth. The worked example also combines the use of the Nokia Maps API with the Map Image API in order to display the turnpoint and junction view associated with each maneuver. 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: Nokia Maps 2.2.1
Article
Keywords: Nokia Maps, JavaScript, routing, search
Created: jasfox (08 Feb 2012)
Last edited: jasfox (28 Sep 2012)

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 Nokia 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 Developer Playground, 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.
  • Add Turnpoint images and Junction Views (where they exist) for each maneuver.
  • 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 Nokia 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 geometry 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 = getReadableText(maneuver);
addMarker( maneuver, turnCodeToDirection(maneuver.turn), instructions, route.geometry);
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.");
}
};


Translating Maneuver Instructions

Each maneuver is passed into the getReadableText() method. This extracts three pieces of information from the maneuver - the next street, the turn code and the action code. The latter two are enumerators which define the maneuver as follows:

  • Turn Codes (0 to 23): TURN_UNDEFINED, TURN_NO_TURN, TURN_KEEP_MIDDLE, TURN_KEEP_RIGHT, TURN_LIGHT_RIGHT, TURN_QUITE_RIGHT, TURN_HEAVY_RIGHT, TURN_KEEP_LEFT, TURN_LIGHT_LEFT, TURN_QUITE_LEFT, TURN_HEAVY_LEFT, TURN_RETURN, TURN_ROUNDABOUT_1, TURN_ROUNDABOUT_2, TURN_ROUNDABOUT_3, TURN_ROUNDABOUT_4, TURN_ROUNDABOUT_5, TURN_ROUNDABOUT_6, TURN_ROUNDABOUT_7, TURN_ROUNDABOUT_8,TURN_ROUNDABOUT_9,TURN_ROUNDABOUT_10, TURN_ROUNDABOUT_11, TURN_ROUNDABOUT_12
  • Action Codes (0 to 16): ACTION_UNDEFINED, ACTION_NO_ACTION, ACTION_END, ACTION_STOPOVER, ACTION_JUNCTION, ACTION_ROUNDABOUT, ACTION_UTURN, ACTION_ENTER_HIGHWAY_FROM_RIGHT, ACTION_ENTER_HIGHWAY_FROM_LEFT, ACTION_ENTER_HIGHWAY, ACTION_LEAVE_HIGHWAY, ACTION_CHANGE_HIGHWAY, ACTION_CONTINUE_HIGHWAY, ACTION_FERRY, ACTION_PASS_JUNCTION, ACTION_PASS_STATION


The turn code can be converted to a directional instruction (such as "Keep right", "Bear left" etc..) , whereas the action code can be used to supply more general information ("Take a Ferry", "Destination reached"). The actionCodeToInstructionEN() and turnCodeToInstructionEN() methods are simple mapping functions converting these codes to English. Obviously the language of the instructions needs to reflect the language of the user, so alternative mapping functions can also be used actionCodeToInstructionES() and turnCodeToInstructionES() library functions have been supplied to convert the instructions into Spanish. The decision as to which language to use should follow the browser preferences of the user. This can be obtained from the ApplicationContext of the map as described in the Setting default language example. Note that this approach could also be expanded to differentiate between different dialects of a language as well (e.g. British English "roundabout" and American English "traffic circle"")

function getReadableText(maneuver){
// Retrieve the maneuver instructions. For some reason the API distinguishes between major
// roads (routeName) and minor roads (streetName)
var street = maneuver.nextStreetName ;
if (street == ""){
street = maneuver.routeName;
}
// Base the instructions in the language of the map.
if (nokia.maps.util.ApplicationContext.get("defaultLanguage") == "es-ES"){
return actionCodeToInstructionES(turnCodeToInstructionES(maneuver.turn ),
street, maneuver.action, calculateDistance(maneuver.length) );
}
// Default language is English
return actionCodeToInstructionEN( turnCodeToInstructionEN(maneuver.turn ),
street, maneuver.action, calculateDistance(maneuver.length) );
}
English Spanish
function turnCodeToInstructionEN(turnCode){
var instruction = "";
switch(turnCode){
case 2:
instruction= "Keep middle lane";
break;
case 3:
instruction= "Keep right";
break; // etc .....
function turnCodeToInstructionES(turnCode){
var instruction = "";
switch(turnCode){
case 2:
instruction= "Manténgase en el carril central";
break;
case 3:
instruction= "Mántengase en la derecha";
break; // etc .....
function actionCodeToInstructionEN( turn, street, actionCode, distance ){
var instruction = "";
switch(actionCode)
{
case 0:
instruction ="<b>Start<\/b> at <b>" + street
+ "<\/b> Follow for <b>" + distance + "<\/b>";
break;
// ... etc
default:
instruction = turn + " onto <b>" + street
+ "<\/b> Follow for <b>" + distance + "<\/b>";
}
return instruction;
}
function actionCodeToInstructionES( turn, street, actionCode, distance ){
var instruction = "";
switch(actionCode){
case 0:
instruction ="<b>Comience<\/b> en <b>" + street
+ "<\/b> durante <b>" + distance + "<\/b>";
break;
// ... etc.
default:
instruction = turn + " hacia <b>" + street
+ "<\/b> Continue durante <b>" + distance + "<\/b>";
}
return instruction;
}


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";
}
}
}

Using the RESTful Maps API

Amongst the uses cases covered by the Map Image API it is possible to retrieve static images of turnpoints and junction views.

Note.pngNote: In order to display static Map Images , appId and token are compulsory in the RESTful Maps API.

Displaying Turnpoints

The Map Image API offers static images of turnpoints and junction views.A turnpoint image is a minimap with an arrow on it. The URL of a turnpoint is of the form:

http://m.nok.it/turnpoint?app_id=APPID&token=0TOKEN&r0{list.of.geocoordinates}&....etc...

In order to get the arrow curving in the correct direction, we need to follow the geometry of the route and list a series of latitude and longitude points. Fortunately the geometry class has a couple of functions to help with this - geometry.indexOf () will find the first instance of a geolocation in the list of coordinates, and thus defines the starting point of the maneuver. geometry.slice() will return a list of latitude and longitude points from the array starting from a known index. Combining these, it is possible to obtain a series of points which describe the maneuver. The length and accuracy of the line will depend upon the number of points defined. Additional parameters are able to define the color lc and shadow sc of the line; height h and width w of the image; and the underlying map language ml. The map language can be deduced from the default language of the ApplicationContext. The other attributes can be left as default values, or altered to your preference.

function getTurnpointImage(position,  geometry){
if (geometry.indexOf(position.latitude) < 4 ){
return "<img src='http://m.nok.it/turnpoint?r0="
+ geometry.slice(0, 5)
+ "&app_id=" + nokia.Settings.appId
+ "&token=" + nokia.Settings.authenticationToken
+ getMapLanguage()
+ "&h=240&w=240&lc=FF0000FF&sc=000000FF' />" ;
} else if (geometry.indexOf(position.latitude) + 6 > geometry.length ){
return "<img src='http://m.nok.it/turnpoint?r0="
+ geometry.slice(geometry.length - 6, geometry.length)
+ "&app_id=" + nokia.Settings.appId
+ "&token=" + nokia.Settings.authenticationToken
+ getMapLanguage()
+ "&h=240&w=240&lc=FF0000FF&sc=000000FF' />" ;
}
 
return "<img src='http://m.nok.it/turnpoint?r0="
+ geometry.slice(geometry.indexOf(position.latitude) - 2 , geometry.indexOf(position.latitude) + 7)
+ "&app_id=" + nokia.Settings.appId
+ "&token=" + nokia.Settings.authenticationToken
+ getMapLanguage()
+ "&h=240&w=240&lc=FF0000FF&sc=000000FF' />" ;
}

Displaying Junction Views

A junction view is a picture of major motorway junctions at a given location. The URL from the Map Image API is of the form:

http://m.nok.it/junction?app_id=APPID&token=0TOKEN&r={geocoordinate.of.junction}&turn={l.or.r}&....etc...

Again the usual parameters to define height h and width w of the image may be added if desired. If no image is found, a single pixel image is returned.


In this example, the geocoordinates of the junction will be the position of the maneuver. The only other decision is whether the maneuver is to turn left or right. Fortunately there is a direct mapping from the turnCodes.

  • TURN_KEEP_RIGHT, TURN_LIGHT_RIGHT, TURN_QUITE_RIGHT, TURN_HEAVY_RIGHT are right-hand turns. {{{1}}}
  • TURN_KEEP_LEFT, TURN_LIGHT_LEFT, TURN_QUITE_LEFT, TURN_HEAVY_LEFT are left hand turns. {{{1}}}
  • All other turn codes are undefined as junction view turns.

Therefore a simple mapping function will suffice:

<function turnCodeToDirection(turnCode){
var turn = "";
switch(turnCode){
case 3:
case 4:
case 5:
case 6:
turn= "&turn=r";
break;
case 7:
case 8:
case 9:
case 10:
turn= "&turn=l";
break;
default:
// Other instructions such as continue, or roundabouts will not have junction views.
turn = "";
}
return turn;
}
function getJunctionImage(position, turn){
return "<img src='http://m.nok.it/junction?r="+position.latitude + ","+ position.longitude+ turn
+ "&app_id=" + nokia.Settings.appId
+ "&token=" + nokia.Settings.authenticationToken
+ "&h=240&w=240' />" ;
}

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 images, as well as 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/>"
+ getTurnpointImage(maneuver.position, geometry)
+ getJunctionImage(maneuver.position, turn)
+ "<\/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

357 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.

×