Leaflet supports shapes. By providing a GeoJSON file that contains data for boundaries, you can indicate counties, states, and countries on your map.
Note: This is Part 4 of a 4-part series on using Angular and Leaflet.
In this tutorial, you will learn how to render shapes for the continental states of the United States of America.
To complete this tutorial, you will need:
This tutorial will plot GeoJSON data for the outlines of the states of the United States of America.
Visit Eric Celeste’s GeoJSON and KML data for the United States and download the 5m GeoJSON file (gz_2010_us_040_00_5m.json
).
Save this file in your /assets/data
directory.
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:
- npx @angular/cli generate service shape --skip-tests
This will create a new file: shape.service.ts
.
Next, you will add this new service as a provider in your app.module.ts
.
Open app.module.ts
in your code editor and make the following changes:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { MarkerService } from './marker.service';
import { PopupService } from './popup.service';
import { ShapeService } from './shape.service';
import { AppComponent } from './app.component';
import { MapComponent } from './map/map.component';
@NgModule({
declarations: [
AppComponent,
MapComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
MarkerService,
PopupService,
ShapeService
],
bootstrap: [AppComponent]
})
export class AppModule { }
Your application now supports your new ShapeService
.
Next, open your newly created shape.service.ts
in your code editor and add HttpClient
to the constructor:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ShapeService {
constructor(private http: HttpClient) { }
getStateShapes() {
return this.http.get('/assets/data/gz_2010_us_040_00_5m.json');
}
}
The function getStateShapes()
will return an observable of the serialized GeoJSON object. To use this, you will need to subscribe to the observable in your MapComponent
.
import { Component, AfterViewInit } from '@angular/core';
import * as L from 'leaflet';
import { MarkerService } from '../marker.service';
import { ShapeService } from '../shape.service';
// ...
@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
private map;
private states;
constructor(
private markerService: MarkerService,
private shapeService: ShapeService
) { }
// ...
ngAfterViewInit(): void {
this.initMap();
this.markerService.makeCapitalCircleMarkers(this.map);
this.shapeService.getStateShapes().subscribe(states => {
this.states = states;
});
}
}
This code injects the ShapeService
in the constructor, creates a local variable to store the data, and calls the getStateShapes()
function to pull the data and subscribe to the result.
Note: An even better approach would be to pre-load the data in a resolver.
Once the data is loaded, you will need to add the shapes to the map as a layer. Leaflet provides a factory just for GeoJSON layers that you can leverage. Let’s put this logic in its own function and then call it after the data has been resolved.
// ...
@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
private map;
private states;
// ...
private initStatesLayer() {
const stateLayer = L.geoJSON(this.states, {
style: (feature) => ({
weight: 3,
opacity: 0.5,
color: '#008f68',
fillOpacity: 0.8,
fillColor: '#6DB65B'
})
});
this.map.addLayer(stateLayer);
}
ngAfterViewInit(): void {
this.initMap();
this.markerService.makeCapitalCircleMarkers(this.map);
this.shapeService.getStateShapes().subscribe(states => {
this.states = states;
this.initStatesLayer();
});
}
}
The initStatesLayer()
function creates a new GeoJSON layer and adds it to the map.
Save your changes. Then, stop your application and relaunch it. Open the application in your web browser (localhost:4200
) and observe the borders for the states:
Next, you will will attach mouseover
and mouseout
events to interact with each of the shapes with onEachFeature
:
private highlightFeature(e) {
const layer = e.target;
layer.setStyle({
weight: 10,
opacity: 1.0,
color: '#DFA612',
fillOpacity: 1.0,
fillColor: '#FAE042'
});
}
private resetFeature(e) {
const layer = e.target;
layer.setStyle({
weight: 3,
opacity: 0.5,
color: '#008f68',
fillOpacity: 0.8,
fillColor: '#6DB65B'
});
}
private initStatesLayer() {
const stateLayer = L.geoJSON(this.states, {
style: (feature) => ({
weight: 3,
opacity: 0.5,
color: '#008f68',
fillOpacity: 0.8,
fillColor: '#6DB65B'
}),
onEachFeature: (feature, layer) => (
layer.on({
mouseover: (e) => (this.highlightFeature(e)),
mouseout: (e) => (this.resetFeature(e)),
})
)
});
this.map.addLayer(stateLayer);
}
Save your changes. Then, open the application in your web browser (localhost:4200
) and move your mouse over the shapes:
However, the markers appear faint because the shape layer is above the marker layer.
There are two approaches to addressing this. The first approach would be to move the makeCapitalCircleMarkers()
call directly after initStatesLayer()
. The second approach would be to call bringToBack()
on the shape layer after it is added to the map.
Here is the complete map.component.ts
file with the bringToBack()
approach:
import { Component, AfterViewInit } from '@angular/core';
import * as L from 'leaflet';
import { MarkerService } from '../marker.service';
import { ShapeService } from '../shape.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;
private states;
constructor(
private markerService: MarkerService,
private shapeService: ShapeService
) { }
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: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
tiles.addTo(this.map);
}
private highlightFeature(e) {
const layer = e.target;
layer.setStyle({
weight: 10,
opacity: 1.0,
color: '#DFA612',
fillOpacity: 1.0,
fillColor: '#FAE042'
});
}
private resetFeature(e) {
const layer = e.target;
layer.setStyle({
weight: 3,
opacity: 0.5,
color: '#008f68',
fillOpacity: 0.8,
fillColor: '#6DB65B'
});
}
private initStatesLayer() {
const stateLayer = L.geoJSON(this.states, {
style: (feature) => ({
weight: 3,
opacity: 0.5,
color: '#008f68',
fillOpacity: 0.8,
fillColor: '#6DB65B'
}),
onEachFeature: (feature, layer) => (
layer.on({
mouseover: (e) => (this.highlightFeature(e)),
mouseout: (e) => (this.resetFeature(e)),
})
)
});
this.map.addLayer(stateLayer);
stateLayer.bringToBack();
}
ngAfterViewInit(): void {
this.initMap();
// this.markerService.makeCapitalMarkers(this.map);
this.markerService.makeCapitalCircleMarkers(this.map);
this.shapeService.getStateShapes().subscribe(states => {
this.states = states;
this.initStatesLayer();
});
}
}
Save your changes. Then, open the application in your web browser (localhost:4200
) and observe the scaled circle markers for state capitals and the shapes for state borders:
You now have a map that supports shapes.
In this post, you created a shape service that loads data and constructs shapes. You added interactivity with L.GeoJSON
’s onEachFeature()
and L.DomEvent.On
.
This concludes the 4-part series on using Angular and Leaflet.
If you’d like to learn more about Angular, check out our Angular topic page for exercises and programming projects.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
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!
Thanks so much, this has been my absolutely first introduction to Leaflet for Angular.
Just a note:
I have found this tutorial very useful. Thank you Chris Engelsma.
The article was very good, it helped me a lot, it could answer a question, as I add the “bindPopup” in the “Shapes” and if possible how to add the name of the state in the “Shape”
Thank you very much