Tutorial

How To Build Maps in Angular with Leaflet, Part 2: The Marker Service

Updated on March 29, 2021
Default avatar

By Chris Engelsma

How To Build Maps in Angular with Leaflet, Part 2: The Marker Service

Introduction

Leaflet supports markers. These are indicators placed on the map that can contain information. This provides a way to highlight landmarks and destinations on a map.

Note: This is Part 2 of a 4-part series on using Angular and Leaflet.

In this tutorial, you will learn how to add markers to your map using a service to manage the marker logic.

Prerequisites

To complete this tutorial, you will need:

  • This tutorial builds directly upon the installation and steps in previous parts.

Step 1 — Downloading the GeoJSON Data

This tutorial will plot GeoJSON data for the state capitals of the United States of America. It will also include some additional metadata for state names, capital names, and population.

Create a new data subdirectory under the assets directory:

  1. mkdir src/assets/data

Then, save the usa-capitals.geojson file in this directory.

Step 2 — Creating the Marker Service

At this point, you should have a working implementation of Leaflet in an Angular application.

Use your terminal window to navigate to the project directory. Then, run the following command to generate a new service:

  1. npx @angular/cli generate service marker --skip-tests

This will create a new file: marker.service.ts.

Next, you will add this new service as a provider in your app.module.ts. You will also be loading the data from your assets folder so you will need to include the HttpClientModule.

Open app.module.ts in your code editor and make the following changes:

src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { HttpClientModule } from '@angular/common/http';
import { MarkerService } from './marker.service';

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

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

Your application now supports your new MarkerService.

Step 3 — Loading and Plotting Markers

Next, open your newly created marker.service.ts in your code editor and add HttpClient to the constructor:

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) { }
}

Create a new function that will load the GeoJSON data and create the markers. This function will take in a Leaflet map as a parameter.

Modify marker.service.ts to import Leaflet and declare a makeCapitalMarkers function:

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) { }

  makeCapitalMarkers(map: L.map): void { }
}

Using HttpClient, get the data and subscribe to the result.

Once you have the data, you will then loop through each feature, construct a marker, and add it to the map.

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) {
  }

  makeCapitalMarkers(map: L.map): void {
    this.http.get(this.capitals).subscribe((res: any) => {
      for (const c of res.features) {
        const lon = c.geometry.coordinates[0];
        const lat = c.geometry.coordinates[1];
        const marker = L.marker([lat, lon]);
        
        marker.addTo(map);
      }
    });
  }
}

This code handles the logic for loading and adding markers to the map.

Now, you will have to call this method from MapComponent:

src/app/map/map.component.ts
import { Component, AfterViewInit } from '@angular/core';
import * as L from 'leaflet';
import { MarkerService } from '../marker.service';

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
  private map;

  private initMap(): void {
    this.map = L.map('map', {
      center: [ 39.8282, -98.5795 ],
      zoom: 3
    });

    const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      minZoom: 3,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

    tiles.addTo(this.map);
  }

  constructor(private markerService: MarkerService) { }

  ngAfterViewInit(): void {
    this.initMap();
    this.markerService.makeCapitalMarkers(this.map);
  }
}

If you were to run your application at this point, you would encounter two errors in your console:

Output
marker-icon-2x.png:1 GET http://localhost:4200/marker-icon-2x.png 404 (Not Found) marker-shadow.png:1 GET http://localhost:4200/marker-shadow.png 404 (Not Found)

You will need to import Leaflet’s assets to your project to reference the marker-icon-2x.png and marker-shadow.png image files.

Open the angular.json file and add the Leaflet images directory:

angular.json
{
  // ...
  "projects": {
    "angular-leaflet-example": {
      // ...
      "architect": {
        "build": {
          // ...
          "options": {
            // ...
            "assets": [
              "src/favicon.ico",
              "src/assets",
              {
                "glob": "**/*",
                "input": "node_modules/leaflet/dist/images/",
                "output": "./assets"
              }
            ],
            // ..
          },
          // ...
        },
        // ...
      }
    }},
  "defaultProject": "angular-leaflet-example"
}

This code will copy Leaflet’s marker images locally.

Then, revisit the map.component.ts and define the icon:

src/app/map/map.component.ts
import { Component, AfterViewInit } from '@angular/core';
import * as L from 'leaflet';
import { MarkerService } from '../marker.service';

const iconRetinaUrl = 'assets/marker-icon-2x.png';
const iconUrl = 'assets/marker-icon.png';
const shadowUrl = 'assets/marker-shadow.png';
const iconDefault = L.icon({
  iconRetinaUrl,
  iconUrl,
  shadowUrl,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowSize: [41, 41]
});
L.Marker.prototype.options.icon = iconDefault;

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
  private map;

  constructor(private markerService: MarkerService) { }

  private initMap(): void {
    this.map = L.map('map', {
      center: [ 39.8282, -98.5795 ],
      zoom: 3
    });

    const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      minZoom: 3,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

    tiles.addTo(this.map);
  }

  ngAfterViewInit(): void {
    this.initMap();
    this.markerService.makeCapitalMarkers(this.map);
  }
}

Save your changes. Then, stop your application and relaunch it. Open the application in your web browser (localhost:4200) and observe the markers for the state capitals:

Screenshot of a map of the United States with marker pins indicating the location of state capitals.

At this point, you have a map that supports default markers.

Step 4 — Displaying Circle Markers

In this next step, you will change the markers from icons to circles. Then scale the size of the circles to reflect the population of the state capitol.

Open MarkerService and create a makeCapitalCircleMarkers() function. It will be very similar to the makrCapitalMarkers() function. Instad of Leaflet’s marker method, you will use the circleMarker method:

src/app/marker.service.ts
makeCapitalCircleMarkers(map: L.map): void {
  this.http.get(this.capitals).subscribe((res: any) => {
    for (const c of res.features) {
      const lon = c.geometry.coordinates[0];
      const lat = c.geometry.coordinates[1];
      const circle = L.circleMarker([lat, lon]);
      
      circle.addTo(map);
    }
  });
}

Then, call this function in MapComponent:

src/app/map/map.component.ts
ngAfterViewInit(): void {
  this.initMap();
  // this.markerService.makeCapitalMarkers(this.map);
  this.markerService.makeCapitalCircleMarkers(this.map);
}

Save these changes and open the application in your web browser (localhost:4200):

Screenshot of a map of the United States with circles indicating the location of state capitals.

The icons have now been replaced with circles.

circleMarker accepts a third optional parameter. This object can contain a radius property. In your MarkerService, modify the makeCapitalCircleMarkers function to use a radius of 20:

const circle = L.circleMarker([lat, lon], { radius: 20 }).addTo(map);

This code sizes all radii to be the same value (20).

Next, you will change the radius to reflect the population of the state capital:

static scaledRadius(val: number, maxVal: number): number {
  return 20 * (val / maxVal);
}

This function takes in a value (population), a max value (maximum population), and returns a radius in the range [0 - 20].

You will use the spread-operator and map to find the capital with the largest population:

const maxPop = Math.max(...res.features.map(x => x.properties.population), 0);

From the GeoJSON data, the largest population will be: “Phoenix, Arizona” (1626078).

Finally, you will put it all together by using ScaledRadius as the radius function.

Open MarkerService in your code editor and make the following changes:

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) { }

  static scaledRadius(val: number, maxVal: number): number {
    return 20 * (val / maxVal);
  }

  makeCapitalMarkers(map: L.map): void {
    this.http.get(this.capitals).subscribe((res: any) => {
      for (const c of res.features) {
        const lon = c.geometry.coordinates[0];
        const lat = c.geometry.coordinates[1];
        const marker = L.marker([lat, lon]);
        
        marker.addTo(map);
      }
    });
  }

  makeCapitalCircleMarkers(map: L.map): void {
    this.http.get(this.capitals).subscribe((res: any) => {

      const maxPop = Math.max(...res.features.map(x => x.properties.population), 0);

      for (const c of res.features) {
        const lon = c.geometry.coordinates[0];
        const lat = c.geometry.coordinates[1];
        const circle = L.circleMarker([lat, lon], {
          radius: MarkerService.scaledRadius(c.properties.population, maxPop)
        });
        
        circle.addTo(map);
      }
    });
  }
}

Save your changes. Then, stop your application and relaunch it. Open the application in your web browser (localhost:4200) and observe the new scaled circle markers for state capitals:

Screenshot of a map of the United States with circles scaled to indicate the population sizes.

You now have a map that supports markers.

Conclusion

In this post, you created a marker service that loads data and constructs markers. You learned how to create two types of markers: L.marker and L.circleMarker. Finally, you learned how to define the size of each circle marker by passing a function for the radius.

Continue to Part 3 of this series on using Angular and Leaflet.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Chris Engelsma

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
2 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

L.marker([lat, lon]).addTo(map);

should put the latitude first only longtitude

Where can I view the angular.json file containing the geojson?

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel