Hands On

Highlight Regions of a Map with Angular and a Reverse Geocoder

By Nic Raboy | 11 July 2019

I recently read a tutorial by Richard Süselbeck titled, How to Get the Shape of an Area Using the HERE Geocoder API, which demonstrated using the reverse geocoder to highlight certain areas on a map. I found this particularly interesting because all I had ever thought of the HERE Reverse Geocoder API was a means to find addresses from latitude and longitude positions, not shapes or regions.

While I know vanilla JavaScript is still widely appreciated when it comes to client facing web applications, I for one prefer a framework such as Angular, Vue.js, or React. In this tutorial we’re going to expand on what Richard did, but take it into the world of Angular and allow region highlighting through mouse events on the map.

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

ng-region-highlight

While Richard’s tutorial focused on highlighting countries, we’re going to focus on highlighting states from the USA when clicked. Apparently you can also highlight regions based on postal codes and other information as well, which is neat.

The Requirements

To be successful with this project you’ll need the following:

The Angular CLI is needed to work with Angular and the HERE account is required in order to get application tokens to use HERE services within your own applications. As of writing this tutorial, I’m using Angular 8.0.2. Older and newer versions will likely work with some minor modifications, but your results may vary.

Configuring and Laying the Project Foundation

To save us from having to explain using HERE with Angular in general, I want to direct you to an older tutorial that I wrote titled, Display HERE Maps within your Angular Web Application. This previous tutorial demonstrates how to create an Angular component to be used for all of our mapping needs.

To quickly get you up to speed with little explanation, execute the following:

ng new here-highlight-project

Choose the defaults when running the above command. We don’t need a router and we don’t need any special packages or CSS extensions. Next execute the following command to create a new component:

ng g component here-map

Make sure you’re in the project directory when you run the above command. With the component created, paste the following markup into the project’s src/app/here-map/here-map.component.html file:

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

The TypeScript that pairs with this particular HTML file goes in the project’s src/app/here-map/here-map.component.ts file and it looks like this:

import { Component, OnInit, ViewChild, Input, ElementRef } 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", { static: false })
    private mapElement: ElementRef;

    @Input("appid")
    private appId: string;

    @Input("appcode")
    private appCode: string;

    private platform: any;
    private map: any;
    private geocodingService: any;

    public constructor() { }

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

    public ngAfterViewInit() {
        this.map = new H.Map(
            this.mapElement.nativeElement,
            this.platform.createDefaultLayers().normal.map,
            {
                zoom: 5,
                center: {
                    lat: 39.8283,
                    lng: -98.5795
                }
            }
        );
        let mapEvent = new H.mapevents.MapEvents(this.map);
        let behavior = new H.mapevents.Behavior(mapEvent);
    }

}

Alright we’re almost caught up. Remember, the goal here isn’t to get started with HERE and Angular, but to do some fancy highlighting. This is why I’m not giving much or any context behind what we’ve done so far. We’re just catching up from my previous tutorial.

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

<here-map 
    #map 
    appid="APP_ID_HERE" 
    appcode="APP_CODE_HERE">
</here-map>

The TypeScript to pair with the above HTML can be found in the project’s src/app/app.component.ts file and it looks like the following:

import { Component, OnInit, 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", { static: false })
    private mapElement: HereMapComponent;
    
    public ngOnInit() { }

    public ngAfterViewInit() { }

}

The final thing to do is to import the HERE JavaScript SDK, something we haven’t done yet in this particular tutorial. This will give us access to the interactive map as well as the services that we plan to use.

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

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>HEREMapProject</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="http://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
        <script src="http://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
        <script src="http://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

Alright we’re caught up now. If we didn’t play catch up, what happens next might not make sense.

Selecting Regions of a Map with the HERE Reverse Geocoder

Just to reiterate, we’re going to be capturing mouse events, converting those events to latitude and longitude coordinates, using those coordinates in the reverse geocoder, and drawing the shape data that’s returned to represent region highlights. It sounds like we’re doing a lot, but it really isn’t.

We’re going to be capturing our interactions in the AppComponent class and responding to those interactions in the HereMapComponent class. It is best we start with the responses before the captures.

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

public getPositionAt(x: number, y: number) {}
public highlightRegion(position: any) {}

We need to be able to convert pixel positions into geo coordinate positions and the getPositionAt function that we design should do this. The highlightRegion function should reverse geocode our position, get our shape, and draw it on the map as polygon objects.

Take a look at the getPositionAt function:

public getPositionAt(x: number, y: number) {
    return this.map.screenToGeo(x, y);
}

The map variable is private which is why we’re doing the conversion in the HereMapComponent class rather than the AppComponent class. The x and y values are picked up from the click event that we’ll see soon. These are viewport positions, not map positions.

Next we have the highlightRegion function which is a little more complex:

public highlightRegion(position: any) {
    let reverseGeocodingParameters = {
        prox: position.lat + "," + position.lng,
        mode: "retrieveAddresses",
        maxresults: "1",
        additionaldata: "IncludeShapeLevel,state"
    };
    this.geocodingService.reverseGeocode(
        reverseGeocodingParameters,
        success => {
            let locations = success.Response.View[0].Result;
            let shape = locations[0].Location.Shape.Value; 
            this.map.removeObjects(this.map.getObjects());
            let customStyle = {
                strokeColor: "black",
                fillColor: "rgba(0,175,170,0.5)",
                lineWidth: 2,
                lineJoin: "bevel"
            };
            let geometry = H.util.wkt.toGeometry(shape);
            if(geometry instanceof H.geo.MultiGeometry) {
                let geometryArray = geometry.getGeometries();
                for (let i = 0; i < geometryArray.length; i++) {
                    this.map.addObject(
                        new H.map.Polygon(
                            geometryArray[i].getExterior(),
                            { style: customStyle }
                        )
                    );
                }
            } else {            
                this.map.addObject(
                    new H.map.Polygon(
                        geometry.getExterior(),
                        { style: customStyle }
                    )
                );
            } 
        },
        error => {
            console.error(error);
        }
    );
}

By default, the HERE reverse geocoding service will not return shape information. We can request that information by adding the following to the request options:

additionaldata: "IncludeShapeLevel,state"

The above option will give us Well-Known Text (WKT) format shape data with our response. Before we can use it, we need to convert it to a geometry. The goal is to draw this geometry as a polygon on the map, but we need to check to make sure it is a single geometry or multiple. Think Hawaii in the United States. There are several islands which would be several polygon shapes.

To make use of these functions, let’s travel back into the project’s src/app/app.component.ts file:

public onMapClick(event: any) {
    let position = this.mapElement.getPositionAt(event.clientX, event.clientY);
    this.mapElement.highlightRegion(position);
}

By itself, the onMapClick function will never trigger. However, when it does, we’ll get the latitude and longitude coordinates given the pixel values from the event. Then we’ll take those latitude and longitude values and highlight the regions on the map.

To make use of the onMapClick function, we can alter the project’s src/app/app.component.html file:

<here-map 
    #map 
    appid="APP_ID" 
    appcode="APP_CODE"
    (click)="onMapClick($event)">
</here-map>

When our component is clicked, the onMapClick function will be called. Now there are other ways to accomplish this. For example, we could add a listener directly to the map like so:

this.map.addEventListener("tap", event => {
    // Do stuff here
});

It is going to come down to your preference. I figured since we’re using Angular, we might want to do things the Angular way.

Conclusion

You just saw how to extend Richard Süselbeck’s previously written tutorial on reverse geocoded shapes to Angular. While we didn’t necessarily accomplish anything new between the two, we filled the framework gap that was left by the vanilla JavaScript example.

If you haven’t already, I encourage you to check out my previous tutorial on Angular as well as the one written by Richard.