Tutorials / Creating a Map Display App with HERE on AWS
Last Updated: July 02, 2020

Introduction

Location services are a key part of many apps and websites. With any app scenario that involves places of interest, the ability to show the location on a map is incredibly powerful.

HERE Technologies makes it easy to implement mapping applications through their APIs on hosted services such as AWS. In this article, we’ll create an application that uses HERE’s mapping and routing APIs and AWS functions to display a location and a route to that location.

Deploying HERE Functions on AWS

We’re going to use these three AWS Lambda Applications provided by HERE:

  • MapImage contains a function to access pre-rendered and optimized map images.

  • Routing contains a function that calculates a route between multiple points and a function that decides the best order given a list of points to pass by.

  • MapTile contains a function that renders fresh map tiles. All tiles combined on a grid form a map of the world in Mercator projection.

Start by logging into or registering your AWS account, then navigating to the HERE Location Services page in AWS Marketplace. This page gives you an overview of APIs, pricing, usage, and so on.

Click the Continue to Subscribe button to configure your software contract. The Standard tier should be sufficient for completing this tutorial.

After the contract is completed, you’ll be prompted to log into or create a HERE developer account, then you’ll see the Platform Activation page.

Under REST, click the Generate App ID and App Code button to create the ID and Code. Make note of these for the rest of the tutorial.

Now go to the [MapImage SAR and deploy the function.

Clicking the Deploy button will redirect you to a configuration page. On the bottom right, you have to fill in some application settings:

Fill in the details, click Deploy, and wait for AWS to create the application.

If you do this for the three MapImage, Routing, and MapTile applications, you’ll see three new applications on your [Lambda Applications dashboard and four new functions on your [Lambda Functions dashboard.

Using the Functions

Each function has a URL associated with it. Click on the function name from the Functions dashboard (in the table displayed above) and you’ll see this Designer:

Click on API Gateway to see the function URL:

Using the MapImage Function

The MapImage function returns a Base64-encoded JSON string of a map image. Without any query parameters (the URL being …/mapimage), the function returns a PNG image of the map of Berlin:

The MapImage function uses HERE’s mapview API, which allows for a lot of options. One example is the ctr query parameter, specifying the latitude and longitude coordinates for the center point of the map. For example, …/mapimage?ctr=50.85,4.35 returns a map centered on Brussels.

Using the Routing Function

The Routing function uses HERE’s calculateroute API. You can find all supported query parameters in the API documentation.

You’re required to pass a mode, a starting point, and a destination. An example of a mode is “fastest;car;traffic:disabled”, and this is passed in the mode query string parameter.

The start and destination are passed using the waypoint0 and waypoint1 parameters. You can add more waypoints as intermediate destinations if you wish, and then they are routed in that order.

Here’s an example:

.../routing?mode=fastest%3Bcar%3Btraffic:disabled&waypoint0=geo!52.5,13.4&waypoint1=geo!52.5,13.45

This returns a JSON response with routing information, such as the distance, travel time, and maneuvers.

Note that you must replace the semicolons in your mode with %3B because AWS considers the semicolon a query parameter separator, equivalent to the ampersand. HERE’s server does not do this, so examples by HERE will not have encoded the semicolon.

Routing with WaypointSequence

WaypointSequence uses HERE’s [FindSequence API. The starting point is specified in the "start" query parameter. Intermediate destinations are passed using destinationN parameters (destination0, destination1, …). Optionally, you can specify an end parameter. If you don’t, HERE will pick any of the intermediate destinations as the endpoint. A mode parameter is also required, just like the Routing function.

For example:

\.../waypointseq?mode=fastest%3Bcar%3Btraffic:disabled&start=geo!52.5,13.4&destination0=geo!52.5,13.45&destination1=geo!52.4,13.35&destination2=geo!52.45,13.35

This orders the three intermediate destinations to achieve the fastest route by car. The JSON response includes the best order of the waypoints and information about the connections, such as distance and estimated time.

Using the MapTile Function

MapTile’s URL looks like this, taking six parameters:

.../maptile/{type}/{scheme}/{zoomlevel}/{col}/{row}/{rez}

You can find a description for these parameters in the Map Tile API Reference.

The rez parameter in the MapTile URL corresponds to the size parameter, and type is a subdomain of the HERE API and only works with base as value at the moment.

You’ll see that the API reference also talks about a map id and a format parameter. The MapTile function always uses newest and jpg for these parameters. The response is a Base64-encoded JSON string of a JPG image.

Here’s an example:

.../maptile/base/normal.day/4/8/8/512

This returns the tile at the 9th row and the 9th column in the 16x16 grid that forms the world.

An Example Node.js App

To see these functions in action in a concrete example, let’s write a Node.js app that does the following:

  • For a list of coordinates, find the best sequence to pass by all these points as fast as possible using a car.

  • Generate a map with these points marked.

  • Display the instructions for the route.

All that is possible by using the deployed functions on AWS.

Create an empty directory on your computer. Our Node.js app will use axios as a dependency, for HTTP requests. Add the following package.json file to this directory:

{
  "name": "HERE Map Sample",
  "version": "0.0.1",
  "dependencies": {"axios": "0.19.0"}
}

Run npm install to obtain axios. Now we can start writing our code.

The first few lines of our code file are a couple of imports and constants:

const http = require('http');
const axios = require('axios');

const MAPIMAGE_URL = "https://application-id.execute-api.eu-west-1.amazonaws.com/Prod/mapimage";
const WAYPOINTSEQ_URL = "https://application-id.execute-api.eu-west-1.amazonaws.com/Prod/waypointseq";
const ROUTING_URL = "https://application-id.execute-api.eu-west-1.amazonaws.com/Prod/routing";

Replace these URLs with the URLs of your functions. Then, create a function called findSequence that calls the WaypointSequence function for a given list of coordinates:

async function findSequence(start, destinations) {
 var queryString = '?mode=fastest%3Bcar%3Btraffic:disabled&start=geo!' + start;
 for (let i = 0; i < destinations.length; i++) {
   queryString += '&destination${i}=geo!${destinations[i]}';
 }
 const response = await axios.get(WAYPOINTSEQ_URL + queryString);
 const waypoints = response.data.results[0].waypoints;
 var orderedDestinations = [ start ];
 for (let i = 1; i < waypoints.length; i++) {
   let destIndex = parseInt(waypoints[i].id.replace('destination', ''), 10);
   orderedDestinations.push(destinations[destIndex]);
 }
 return orderedDestinations;
}

We expect destinations to be an array of strings in the "lat,long" format.

First, we construct the full query string with the mode, starting position, and all destinations. Performing the actual HTTP request is a one-liner with axios.get. You never have to call JSON.parse yourself, since axios does the parsing. response.data holds the parsed JSON as a JavaScript object.

The response has a results field holding an array, where we’re interested in the first item.

That item contains a waypoints array with all ordered waypoints and information about them.

Most important for us is the id value. The first waypoint is obviously the starting point, so we can skip that one. All other waypoint IDs look like destinationN, where N can be seen as the index of our destinations array because we passed the destinations in that order.

Our next function calls the MapImage function:

async function generateMap(coordinates) {
 const response = await axios.get(MAPIMAGE_URL + "?h=600&w=600&poi=" + coordinates.join(","));
 return response.data;
}

The poi parameter is for "points of interest," and HERE will place numbered markers on these locations. The required format for the poi parameter is lat1,long1,lat2,long2,.…

response.data is a string in this case because axios parses the raw response as JSON.

Then we need a function to calculate the route through all ordered waypoints.

async function calculateRoute(waypoints) {
 var queryString = '?mode=fastest%3Bcar%3Btraffic:disabled';
 for (let i = 0; i < waypoints.length; i++) {
   queryString += '&waypoint${i}=geo!${waypoints[i]}';
 }
 const response = await axios.get(ROUTING_URL + queryString);
 const legManeuvers = response.data.response.route[0].leg.map(leg => leg.maneuver.map(m => m.instruction));
 return legManeuvers;
}

The query string has the same mode as we passed to WaypointSequence. The waypoints go in the waypoint0, waypoint1, … parameters.

In the JSON response, we can find the route in response.route[0].

The leg array contains more information about each part of the route (from waypoint to waypoint). Each leg item has a maneuver array containing information about each point where a maneuver is needed, including a text-based instruction.

Our calculateRoute function extracts these instructions and returns an array of string arrays (one string array per leg).

Lastly, we’re going to add some code to open a web server at port 55555 that will serve an HTML response with our map image and route instructions.

http.createServer(onRequest).listen(55555);

async function onRequest(request, response) {
 const coordinates = [
     "52.526609,13.378921",
     "52.498689,13.412367",
     "52.478137,13.447185",
     "52.471423,13.429075",
     "52.474094,13.382382",
     "52.485206,13.363671",
 ];
 const start = "52.517787,13.408217";
 const ordered = await findSequence(start, coordinates);
 const image = await generateMap(ordered);
 const legManeuvers = await calculateRoute(ordered);
 response.writeHead(200);
 response.write('<html><head><meta charset="utf-8"></head><body>');
 response.write('<img src="data:image/png;base64,');
 response.write(image);
 response.write('">');
 for (let i = 0; i < legManeuvers.length; i++) {
   response.write('<p>');
   response.write(legManeuvers[i].join('<br>'));
   response.write('</p>');
 }
 response.write('</body></html>');
 response.end();
}

The coordinates are hardcoded to a bunch of arbitrary locations in Berlin. Running this app with Node and accessing http://localhost:55555/ in your browser, you’ll see this:

It’s a map of Berlin with numbered markers for our starting point (1) and our destinations (2-7), and instructions on how to go from waypoint to waypoint.

Next Steps

We have deployed three applications by HERE on AWS Lambda and made use of them in a Node.js application that finds the best route between multiple waypoints. The APIs are very powerful and much more can be done with them than is demonstrated in this article. If you’d like to dive deeper, refer to HERE’s API documentation:

The Routing Resourceis also useful if you wish to render the route on a map instead of showing the maneuver instructions. This resource includes calculation of the route.

HERE also provides Isoline routing functionality, used to calculate the boundaries of the area that you can reach by driving for a given time or distance. You might think of this as a reachable distance. The calculateisoline resource returns the points of the isoline polygon, which can be rendered on a map using the Route resource.