Hands On

Render and Interact with HERE Location Data using Leaflet and Angular

By Nic Raboy | 14 February 2019

When it comes to development, I’m all about choosing the right tool for the job. While HERE offers a great interactive map component as part of its JavaScript SDK, there might be reasons to explore other interactive map rendering options. Take Leaflet for example. With Leaflet you can provide your own tile layer while working with a very popular and easy to use open source library.

In a past tutorial you’ll remember that I had demonstrated how to use Angular with HERE, but this time around we’re going to shake things up a bit. We’re going to use Angular with HERE data, but we’re going to display and interact with it using Leaflet.

To get a better idea of what we plan to accomplish, take a look at the following animated image:

here-angular-leafletjs

We’re going to display an interactive map, geocode some addresses, and display those addresses on the map as markers using Leaflet, Angular, and the HERE RESTful API.

Starting with a New Angular Project and the Leaflet Dependencies

Before going forward, the assumption is that you have the Angular CLI installed and that you have a free HERE developer account. With these requirements met, go ahead and execute the following command:

ng new leaflet-project

The above command will create our new Angular project, but that project won’t be ready to use Leaflet. We have to first include the necessary JavaScript and CSS files.

Open the project’s src/index.html file and make it look like the following:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>LeafletProject</title>
        <base href="/">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css">
    </head>
    <body>
        <app-root></app-root>
        <script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>
    </body>
</html>

Notice that we’ve included the CSS library as well as the JavaScript library for Leaflet. Technically, this is all that is required to start using Leaflet with HERE in an Angular application. However, we’re going to do some more preparation work to set us up for some awesome features.

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 { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

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

Because we are no longer using the HERE JavaScript SDK, we are now relying on HTTP requests to the RESTful API. By default, HTTP in Angular is disabled, so by including the HttpClientModule and adding it to the imports array of the @NgModule block, we are enabling it.

While not absolutely necessary, we could also clean up the CSS a bit to allow us a full screen map. Open the project’s src/styles.css file and include the following:

body {
    margin: 0;
}

Now everything will take up the full width and height without the default margins.

It may seem like we’ve done a lot as of now, but it has been strictly configuration. Now we’re going to get into the fun stuff, which is development with Leaflet and Angular.

Create an Angular Component to Represent an Interactive HERE Map

While there are many ways to start using maps in your project, the cleanest approach is to create a dedicated Angular component so the maps can be reused throughout the application.

From the Angular CLI, execute the following:

ng g component here-map

The above command will generate several files for us that will represent our component. We’re going to start by opening the project’s src/app/here-map/here-map.component.html file and including the following markup:

<div #map [style.height]="height" style="width: 100%;"></div>

In the above markup you’ll notice the #map attribute. We’re going to use this as a reference so we can access this DOM element from our TypeScript code. You’ll also notice that we have a dynamic height and a static width. It is easy to full screen the width of a component, but you have to be a CSS wizard to full screen the height. To cheat, we’re going to use JavaScript to determine the height and set it as a variable after everything initializes.

With the HTML in place, open then project’s src/app/here-map/here-map.component.ts file and include the following:

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

declare var L: 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("appId")
    public appId: string;

    @Input("appCode")
    public appCode: string;

    private map: any;
    public srcTiles: string;
    public height: string;

    public constructor(private http: HttpClient) {
        this.height = window.innerHeight + "px";
    }

    public ngOnInit() {
        this.srcTiles = "https://2.base.maps.api.here.com/maptile/2.1/maptile/newest/reduced.day/{z}/{x}/{y}/512/png8?app_id=" + this.appId + "&app_code=" + this.appCode + "&ppi=320";
    }

    public ngAfterViewInit() {
        this.map = L.map(this.mapElement.nativeElement, {
            center: [37.7397, -121.4252],
            zoom: 10,
            layers: [L.tileLayer(this.srcTiles)],
            zoomControl: true
        });
    }

    public dropMarker(address: string) {
        this.http.get("https://geocoder.api.here.com/6.2/geocode.json", {
            params: {
                app_id: this.appId,
                app_code: this.appCode,
                searchtext: address
            }
        }).subscribe(result => {
            let location = result.Response.View[0].Result[0].Location.DisplayPosition;
            let marker = new L.Marker([location.Latitude, location.Longitude]);
            marker.addTo(this.map);
        });
    }

}

This file and the above code is the bulk of our project. To make things easier to understand, we’re going to look at the above code piece by piece.

Within the HereMapComponent class, we have the following:

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

@Input("appId")
public appId: string;

@Input("appCode")
public appCode: string;

The ViewChild is referencing our #map attribute from the HTML. This is how we’re getting reference to the HTML component for further usage. Each of the @Input annotations represents a possible attribute that the user can provide. In our example the user will be providing the app id and app code found in their developer dashboard.

Remember that dynamic height I was talking about. We’re setting it here:

public constructor(private http: HttpClient) {
    this.height = window.innerHeight + "px";
}

We are calculating the inner browser height and setting it using JavaScript. This way we can avoid all the tricky vertical height stuff that comes with standard CSS.

Because we’re using Leaflet, we’re relying heavily on the various HERE RESTful APIs and this includes the Map Tile API.

public ngOnInit() {
    this.srcTiles = "https://2.base.maps.api.here.com/maptile/2.1/maptile/newest/reduced.day/{z}/{x}/{y}/512/png8?app_id=" + this.appId + "&app_code=" + this.appCode + "&ppi=320";
}

Using the provided app id and app code we can construct our URL for getting tiles from the API. This is set in the ngAfterViewInit when we initialize Leaflet.

public ngAfterViewInit() {
    this.map = L.map(this.mapElement.nativeElement, {
        center: [37.7397, -121.4252],
        zoom: 10,
        layers: [L.tileLayer(this.srcTiles)],
        zoomControl: true
    });
}

When initializing Leaflet, we are using the HTML component that we referenced, are centering the map on certain coordinates, and are using our HERE Tile API for the layer.

While we won’t be working with markers and geocoding by default, we’re going to lay the foundation within our component:

public dropMarker(address: string) {
    this.http.get("https://geocoder.api.here.com/6.2/geocode.json", {
        params: {
            app_id: this.appId,
            app_code: this.appCode,
            searchtext: address
        }
    }).subscribe(result => {
        let location = result.Response.View[0].Result[0].Location.DisplayPosition;
        let marker = new L.Marker([location.Latitude, location.Longitude]);
        marker.addTo(this.map);
    });
}

When the dropMarker method is executed, we make a request to the HERE Geocoder API with a search query. In our scenario the search query is an address or location. The results are then used to create a marker which is added to the map.

Before we can start using our new component, we need to wire it up. 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 { HttpClientModule } from '@angular/common/http';

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

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

Notice that this time around we have imported our component and added it to the declarations array of the @NgModule block.

The tough stuff is over and we can now work towards displaying our map.

Displaying and Interacting with the Angular Map Component

Since this is a simple project, we don’t have a router. Instead everything will be rendered inside the project’s src/app/app.component.html file. Open this file and include the following:

<here-map #map appId="APP-ID-HERE" appCode="APP-CODE-HERE"></here-map>

There are a few things to take note of in the above. First, it is only coincidence that we’ve added a #map attribute. We don’t truly need to add one and if we did, it doesn’t need to have the same name as the previous component. Second, notice the appId and appCode attributes. You’ll want to swap the placeholder values with your own tokens.

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

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { HereMapComponent } from "./here-map/here-map.component";

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

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

    public ngOnInit() { }

    public ngAfterViewInit() {
        this.mapElement.dropMarker("tracy, ca");
        this.mapElement.dropMarker("lathrop, ca");
    }

}

We want to be able to make use of the dropMarker function that sits in our map component. To do this we need to gain reference to our HTML component with the #map attribute. Once we have reference, then we can start calling the public functions that reside in it.

In our example, we are adding two markers for two local cities in California.

Conclusion

You just saw how to use Leaflet in your Angular project to work with HERE data. HERE is flexible so you’re able to use whatever renderer you want to display your interactive maps. In my previous tutorial we used the default renderer, but this time we used Leaflet which is just one of many options.