Hands on

Isoline Routing in an Angular Application using the HERE Routing API

By Nic Raboy | 25 October 2018

Have you ever needed to search for locations that were in range to a starting point based on certain criteria? For example, maybe you wanted to find all the Starbucks that are within a driving distance of 5 minutes or less based on current traffic conditions. This can be accomplished using what is known as Isoline Routing.

Not too long ago, Richard Sueselbeck wrote a great article on Isoline Routing. In his article titled, Finding my Dream Home with Isoline Routing, he wrote about house hunting in a traffic congested area where he wanted to be within a certain travel time from his work place. You can’t search a radius because not all points in the radius might qualify based on the criteria, given there might be unknown traffic situations, rivers, or lack of roads.

In this tutorial, we won’t be house hunting, but we will be taking a look at how to calculate Isoline routes using Angular and the HERE Routing API for JavaScript.

Take a look at the following animated image to get an idea of what we hope to accomplish:

here-isoline-routing-angular

As you can see, we are displaying a map and two search boxes. The first search box is an address with no specific completeness and the second search box is a numeric value which represents the range in seconds. The goal is to convert the address to a latitude and longitude set of coordinates and use it as a starting point to find all possible ending locations that can be reached within the time range. All possible locations are displayed on the map to get an idea of what is accessible during that time frame.

Generating an Angular Application with a HERE Map

The first step in being successful with this project is to create a new Angular project. If you’ve been keeping up, I’ve already written several tutorials that make use of Angular and HERE Maps. For example, Display HERE Maps within your Angular Web Application shows how to display a map in Angular and Transportation Routing and Directions in an Angular Application with the HERE Routing API gives an introduction to the Routing API for JavaScript with Angular. Both tutorials, plus others are going to play a part in terms of getting us up to speed for Isoline Routing. The goal is not to reinvent the wheel in terms of tutorials too much.

That said, assuming your Angular CLI is ready to go, execute the following:

ng new demo

The above command will create a new Angular project. We will be creating a component to hold our map, but before we do, we need to include the various HERE JavaScript libraries into the project’s src/index.html file:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>HERE with Angular</title>
        <base href="/">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
        <app-root></app-root>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

With the libraries in place, the map component can be created using the Angular CLI. This map component will not only handle the visual aspect of things, but all the functions that relate to the map such as geocoding and routing.

From the Angular CLI, execute the following:

ng g component here-map

The above command will create a TypeScript file, HTML file, and CSS file within the project. Rather than explaining line for line what everything does in our map component, we’re going to just get up to speed from the other tutorials.

Open the project’s src/app/here-map/here-map.component.html file and include the following HTML markup:

<div #map [style.width]="width" [style.height]="height" style="float: left"></div>

With the container element in place when it comes to loading our map after the application renders, we can include the TypeScript that works with that UI element.

Open the project’s src/app/here-map/here-map.component.ts and include the following TypeScript:

import { Component, OnInit, ViewChild, ElementRef, Input } from '@angular/core';

declare var H: any;

@Component({
    selector: 'here-map',
    templateUrl: './here-map.component.html',
    styleUrls: ['./here-map.component.css']
})
export class HereMapComponent implements OnInit {

    @ViewChild("map")
    public mapElement: ElementRef;

    @Input()
    public appId: any;

    @Input()
    public appCode: any;

    @Input()
    public width: any;

    @Input()
    public height: any;

    private platform: any;
    private map: any;
    private router: any;
    private geocoder: any;

    public constructor() { }

    public ngOnInit() {
        this.platform = new H.service.Platform({
            "app_id": this.appId,
            "app_code": this.appCode
        });
        this.router = this.platform.getRoutingService();
        this.geocoder = this.platform.getGeocodingService();
    }

    public ngAfterViewInit() {
        let defaultLayers = this.platform.createDefaultLayers();
        this.map = new H.Map(
            this.mapElement.nativeElement,
            defaultLayers.normal.map,
            {
                zoom: 4,
                center: { lat: "37.0902", lng: "-95.7129" }
            }
        );
    }

}

The above code is essentially initializing the HERE platform in the ngOnInit method along with the services that will be used. Then in the ngAfterViewInit method, a map is created from the referenced UI container and displayed with a certain position and zoom.

I cannot stress this enough, it is worth checking out my previous tutorial if you’re having trouble making sense of the above component code and markup.

With the component in place, let’s focus on the bulk of what this tutorial hopes to accomplish.

Geocoding an Address to Latitude and Longitude Coordinates with Angular

When working with the HERE Routing API, we cannot use string addresses when identifying starting points or waypoints. Instead we need to use latitude and longitude coordinates, but memorizing those coordinates probably isn’t feasible for a lot of applications. This is where the HERE Geocoder API comes into play.

I previously wrote a tutorial titled, Using the HERE Geocoder API for JavaScript in an Angular Application which demonstrated geocoding, so we’ll use some of it as inspiration for this tutorial.

Open the project’s src/app/here-map/here-map.component.ts file and create the following function:

private getCoordinates(query: string) {
    return new Promise((resolve, reject) => {
        this.geocoder.geocode({ searchText: query }, result => {
            if(result.Response.View.length > 0) {
                if(result.Response.View[0].Result.length > 0) {
                    resolve(result.Response.View[0].Result);
                } else {
                    reject({ message: "no results found" });
                }
            } else {
                reject({ message: "no results found" });
            }
        }, error => {
            reject(error);
        });
    });
}

Using the geocoder that was initialized earlier, we can take a query string and use it to get location information which includes latitude and longitude coordinates. The query string could be as vague as “Tracy, CA” or much more complete. The HERE Geocoder API will do great things to make sense of the data provided.

With a function in place for geocoding data, we can focus on calculating an Isoline route.

Calculating Isoline Coordinates Accessible on a Time-Based Range

Much of the code we see here will be taken directly from the JavaScript documentation for the HERE Routing API. We’re just going to make it Angular friendly so it becomes nicer to work with.

Within your project’s src/app/here-map/here-map.component.ts file, include the following function:

public route(start: string, range: string) {
    let params = {
        "mode": "fastest;car;traffic:enabled",
        "range": range,
        "rangetype": "time",
        "departure": "now"
    }
    this.map.removeObjects(this.map.getObjects());
    this.getCoordinates(start).then(geocoderResult => {
        params["start"] = geocoderResult[0].Location.DisplayPosition.Latitude + "," + geocoderResult[0].Location.DisplayPosition.Longitude;
        this.router.calculateIsoline(params, data => {
            if(data.response) {
                let center = new H.geo.Point(data.response.center.latitude, data.response.center.longitude),
                    isolineCoords = data.response.isoline[0].component[0].shape,
                    linestring = new H.geo.LineString(),
                    isolinePolygon,
                    isolineCenter;
                isolineCoords.forEach(coords => {
                    linestring.pushLatLngAlt.apply(linestring, coords.split(','));
                });
                isolinePolygon = new H.map.Polygon(linestring);
                isolineCenter = new H.map.Marker(center);
                this.map.addObjects([isolineCenter, isolinePolygon]);
                this.map.setViewBounds(isolinePolygon.getBounds());
            }
        }, error => {
            console.error(error);
        });
    }, error => {
        console.error(error);
    });
}

In the above route function, a few things are happening. First we are defining a few parameters. For example, we want to specify the criteria for an Isoline route. Our criteria is that we want the fastest route by car and we want traffic data to be used in the calculation. We’ll be accepting the range from the user and we want to specify that the range is a time in seconds. We also want to specify that the departure time is now, rather than some time in the future or past.

After we define a few of our parameters, we can clear the map of all previous objects which include polygons and markers.

With the map clear, we can get the coordinate information for our provided address. This coordinate information will act as center point, or ground zero, for the Isoline route. With the coordinates, we can set the final parameter which would be the starting position.

Next we calculate the Isoline route based on the parameters. Again, the result of the calculation was taken from the documentation. Essentially we are looping through each of the Isoline coordinates returned and creating a polygon from them. A marker is placed at our starting point and the map is centered so everything fits in the view.

There isn’t a lot of code here, but we’re accomplishing something very amazing.

Making the Map Component Functional with the Application

Now that we have a map component with geocoder and Isoline logic, we need to bring it together and use it in the application. In Angular, binding with forms is disabled by default. Because we’ll be accepting user input via forms, we need to enable it.

Open the project’s src/app/app.module.ts file and include the following:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from "@angular/forms";

import { AppComponent } from './app.component';
import { HereMapComponent } from './here-map/here-map.component';

@NgModule({
    declarations: [
        AppComponent,
        HereMapComponent
    ],
    imports: [
        BrowserModule,
        FormsModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

Essentially we’ve just imported FormsModule and added it to the imports array of the @NgModule block. Now that we have forms support, we can use them in our application, but before we explore the HTML, we should probably quickly take care of the TypeScript.

Open the project’s src/app/app.component.ts file and include the following:

import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

    public start: string;
    public range: string;

    public constructor() {
        this.start = "Tracy CA";
        this.range = "300";
    }

    public ngOnInit() { }

}

In order to bind data to a form, we need to initialize the variables that we plan to use. In the above code we have a public start and a public range variable that will be bound to the form. Each of these variables have a default value to help us out.

Now open the project’s src/app/app.component.html file and include the following HTML markup:

<div style="padding-bottom: 20px">
    <div>
        <label class="searchLabel">Start</label>
        <input type="text" [(ngModel)]="start" />
        <label class="searchLabel" style="margin-left: 10px">Range</label>
        <input type="text" [(ngModel)]="range" style="margin-left: 10px" />
        <button (click)="map.route(start, range)" style="margin-left: 5px">Search</button>
    </div>
</div>
<here-map
    #map
    appId="APP-ID-HERE"
    appCode="APP-CODE-HERE"
    width="75%"
    height="630px">
</here-map>

There are a fiew things to note in the above code. First, we have to input fields, both bound to the public variables we had just created. Then we have a button that once clicked, calls the map.route function we had defined in the map component. The variables being passed are the values from the form.

So what is the map variable? Well, that is a template variable that we’re using the here-map tag. The #map attribute represents a variable for that particular map instance, which we’re using in the button.

Make sure that you swap the appId and appCode with those found in your HERE developer account.

Conclusion

You just saw how to use Isoline Routing in your Angular application. If it still isn’t obvious the value that Isoline Routing offers, let me throw a few more examples at you:

  • Finding houses, condos, or apartments that are reachable within a certain travel distance or time. Could be valuable when purchasing housing near enough to your work place.
  • Hailing a ride share driver that is of a certain distance or time from you with more accuracy than a radius from your coordinates.
  • Determining an exit strategy in the event of a natural disaster or similar.

The examples are limitless, but just remember that Isoline Routing offers a better solution than using a radius or calculating a route for every possible point manually.