Hands On

Getting Started with Harp.gl and Angular to Map Your Data

By Nic Raboy | 08 August 2019

As you may have heard, HERE Technologies released its new open source 3D map renderer, harp.gl, into beta. In case you missed it, harp.gl leverages WebGL, three.js, and TypeScript to visual data on a map while leveraging the power of the GPU. This opens the door to functionality like 3D maps, different camera options, and a lot more.

In this tutorial, we’re going to see how to get started with harp.gl, but using the popular Angular framework on the web.

To get an idea of what we’re going to accomplish, take a look at the following animated image:

harpgl-angular

The above image features a map and a few points that some might to refer to as markers. While it doesn’t look like much, how the tiles are rendered and how the points are rendered are actually a big deal, more specifically when working with massive amounts of data like you might find in HERE XYZ.

So let’s take a look at how to reproduce what was done in the image.

Create a New Angular Project with the Angular CLI

To be successful with this tutorial, you will need to have the Angular CLI available. With it installed, execute the following:

ng new harp-project

After the project is created, we need to import the harp.gl dependencies. There are two approaches to doing this:

  1. Using the Node Package Manager (NPM).
  2. Using browser libraries with <script> tags.

For this particular tutorial we’re going to be using browser libraries. This is similar to what I demonstrated when working with the core HERE Location Services (HLS) map in my tutorial titled, Display HERE Maps within your Angular Web Application.

The NPM approach is a little more complex, so we’ll explore it in another tutorial. Remember, harp.gl, as of writing this, is a beta product. It is expected that there are bugs here and there.

Open the project’s src/index.html file and include the following:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>HarpProject</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 style="margin: 0">
        <app-root></app-root>
        <script src="https://unpkg.com/three/build/three.min.js"></script>
        <script src="https://unpkg.com/@here/harp.gl/dist/harp.js"></script>
    </body>
</html>

Notice that we’ve included harp.gl as well as three.js.

We’re going to need one more dependency which is important for parsing GeoJSON data. To get this dependency, execute the following from your CLI:

npm install geojson --save

Since GeoJSON is still JSON, we don’t absolutely need the above dependency, but it will make our lives a lot easier. You can learn more about this package in a previous tutorial I wrote titled, Format Data into GeoJSON with JavaScript to be used with HERE XYZ.

We don’t need any further development dependencies to be successful in our project. However, we do need a HERE Developer Account and an XYZ access token.

Develop an Angular Component for the Harp.gl Map and Logic

With harp.gl included in our project through browser libraries and us having a developer account, we can proceed towards using harp.gl in our project. When it comes to Angular, the best practice is to create a separate component for all of our harp.gl shenanigans. This will help us reduce duplicate code throughout our application.

From the Angular CLI, execute the following:

ng g component harp

The above command will create a new component which consists of a CSS file, an HTML file, and a TypeScript file.

Let’s start by adding a placeholder element to our HTML file. Open the project’s src/app/harp/harp.component.html file and include the following:

<canvas #map style="width: 100vw; height: 100vh"></canvas>

We’re creating a <canvas> element that is set to take up the full viewport width and height of the browser. What’s most important to us is the #map reference variable. We cannot use commands like document.getElementById in Angular, so we have to use special reference variables if we wish to interact directly with DOM elements.

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

import { Component, OnInit, ViewChild, Input, ElementRef } from '@angular/core';
import * as GeoJSON from "geojson";

declare var harp: any;

@Component({
    selector: 'harp',
    templateUrl: './harp.component.html',
    styleUrls: ['./harp.component.css']
})
export class HarpComponent implements OnInit {

    @ViewChild("map", { static: false })
    private mapElement: ElementRef;

    @Input("token")
    private token: string;

    @Input("lat")
    private lat: Number;

    @Input("lng")
    private lng: Number;

    private map: any;

    public constructor() { }

    public ngOnInit() { }

    public ngAfterViewInit() { }

    public dropPoints(name: string, positions: any[]) { }

    public createPoints(positions: any[]): any { }

}

The above code is mostly boilerplate as there isn’t any real logic as of now.

You’ll notice that we are importing the GeoJSON dependency that we had previously installed. Because we are using browser libraries for our project rather than NPM modules with TypeScript definitions, we need to configure the transpiler to ignore any warnings that the definitions don’t exist. This can be done through the following:

declare var harp: any;

Within the HarpComponent class, we have several annotations in the form of @ViewChild and @Input. The @ViewChild allows us access to the reference variable we had set in the HTML file, while the @Input allow us access to any tag attributes that we’ll see soon. For this example, we’ll be able to pass the access token and a center latitude and longitude point as tag attributes.

When the view finishes rendering for the component, we make use of the ngAfterViewInit life-cycle event. In it we can configure the map, among other things.

public ngAfterViewInit() {
    this.map = new harp.MapView({
        canvas: this.mapElement.nativeElement,
        theme: "https://unpkg.com/@here/harp-map-theme@0.3.3/resources/berlin_tilezen_base.json",
    });
    const controls = new harp.MapControls(this.map);
    const omvDataSource = new harp.OmvDataSource({
        baseUrl: "https://xyz.api.here.com/tiles/herebase.02",
        apiFormat: harp.APIFormat.XYZOMV,
        styleSetName: "tilezen",
        authenticationCode: this.token,
    });
    this.map.addDataSource(omvDataSource);
    this.map.setCameraGeolocationAndZoom(new harp.GeoCoordinates(Number(this.lat), Number(this.lng)), 12);
}

Notice a few things that are happening in the above code. First we’re getting our <canvas> tag from the reference variable. After, we are defining our data source which is through HERE XYZ and an access token that we obtained at the beginning of this tutorial. It will eventually be passed in as a tag attribute.

Finally, after the data source is added to the map and rendered, we can re-center the map on the coordinates that were passed.

At this point we should have a basic map that we can interact with. Now let’s look at adding some points on the map.

To create points on the map, we have a two step process. The first step is to create a GeoJSON object:

public createPoints(positions: any[]): any {
    return GeoJSON.parse(positions, { Point: ["lat", "lng"] });
}

Using this createPoints function, we can accept an array of latitude and longitude objects, use our GeoJSON dependency, and return properly formatted GeoJSON features. The next step is to take those features, and add them to the map as another data source.

public dropPoints(name: string, positions: any[]) {
    const geoJsonDataProvider = new harp.GeoJsonDataProvider(name, this.createPoints(positions));
    const geoJsonDataSource = new harp.OmvDataSource({
        dataProvider: geoJsonDataProvider,
        name: name
    });
    this.map.addDataSource(geoJsonDataSource).then(() => {
        const styles = [{
            when: "$geometryType == 'point'",
            technique: "circles",
            renderOrder: 1,
            attr: {
                color: "#7ED321",
                size: 15
            }
        }];
        geoJsonDataSource.setStyleSet(styles);
        this.map.update();
    });
}

Notice that in the dropPoints method we are defining the style information for our points. The process behind showing points isn’t too different from showing the map tiles. However, we still need to render them on the map. To do this, we can update our ngAfterViewInit method like so:

public ngAfterViewInit() {
    this.map = new harp.MapView({
        canvas: this.mapElement.nativeElement,
        theme: "https://unpkg.com/@here/harp-map-theme@0.3.3/resources/berlin_tilezen_base.json",
    });
    const controls = new harp.MapControls(this.map);
    const omvDataSource = new harp.OmvDataSource({
        baseUrl: "https://xyz.api.here.com/tiles/herebase.02",
        apiFormat: harp.APIFormat.XYZOMV,
        styleSetName: "tilezen",
        authenticationCode: this.token,
    });
    this.map.addDataSource(omvDataSource);
    this.map.setCameraGeolocationAndZoom(new harp.GeoCoordinates(Number(this.lat), Number(this.lng)), 12);
    this.dropPoints("example-points", [{ lat: 37.7497, lng: -121.4252 }, { lat: 37.7597, lng: -121.4352 }]);
}

Not so bad right?

We can bring this tutorial to a close by actually showing the map on the screen with our hard-coded point values. Open the project’s src/app/app.component.html file and include the following markup:

<harp
    token="XYZ_ACCESS_TOKEN_HERE"
    lat="37.7397"
    lng="-121.4252">
</harp>

Remember, inside the project’s src/app/harp/harp.component.ts file we set the selector value to be harp, hence why the tag name is <harp> in the HTML file. When using this code, don’t forget to use your own access token for rendering.

Conclusion

You just saw how to use harp.gl in your Angular project for mapping and data visualization purposes. Remember, harp.gl leverages WebGL which puts more emphasis on the power of your GPU for intense visualization tasks.