Карты в Angular и Leaflet: добавление меток
Development | Комментировать запись
Библиотека Leaflet поддерживает метки (маркеры) – это расположенные на карте индикаторы, которые могут содержать какую-то информацию. Это позволяет выделять на карте ориентиры и пункты назначения.
Примечание: Это вторая часть серии по использованию Angular и Leaflet. Другие мануалы из этой серии можно найти по тегу Leaflet maps.
В этом руководстве вы узнаете, как добавить на карту метки с помощью сервиса для управления логикой меток.
Требования
Это продолжение мануала Создание карт в Angular и Leaflet, в котором вы найдете все требования к среде.
1: Загрузка данных GeoJSON
В этом руководстве мы разместим на карте данные GeoJSON столиц штатов Америки. Также карта будет включать дополнительные метаданные – названия штатов, столиц и население.
Примечание: Файл usa-capitals.geojson доступен в этом репозитории.
Создайте новый подкаталог data в каталоге assets:
mkdir src/assets/data
Затем сохраните файл usa-capitals.geojson в этом каталоге.
2: Создание сервиса меток
Поскольку это вторая часть серии, на данный момент у вас должна быть рабочая реализация Leaflet в приложении Angular.
Используйте окно терминала, чтобы перейти в каталог проекта, а затем выполните следующую команду, чтобы создать новый сервис:
npx @angular/cli generate service marker --skip-tests
Эта команда создаст новый файл по имени marker.service.ts.
Давайте добавим новый сервис в свой app.module.ts в качестве провайдера. Также мы будем загружать данные из папки assets, поэтому вам нужно будет включить HttpClientModule.
Откройте 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 { }
Итак, наше приложение поддерживает MarkerService, идем дальше.
3: Загрузка и размещение меток
Откройте файл marker.service.ts в редакторе кода и добавьте HttpClient в конструктор:
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) { } }
Создайте новую функцию, которая загрузит данные GeoJSON и создаст метки. Она принимает карту Leaflet в качестве параметра.
Отредактируйте файл src/app/marker.service.ts, чтобы импортировать Leaflet и объявить функцию makeCapitalMarkers:
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 { } }
Затем через HttpClient мы получим данные и подпишемся (с помощью аргумента subscribe) на результат.
Получив необходимые данные, приложение выполнит цикл по каждой функции, соберет маркер и добавит его на карту.
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);
}
});
}
}
Этот код обрабатывает логику загрузки и добавления маркеров на карту.
Теперь нужно вызвать этот метод из MapComponent:
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: '© <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); }
}
Если бы на этом этапе мы запустили свое приложение, в консоли мы бы столкнулись с двумя ошибками:
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)
То есть нам нужно будет импортировать ресурсы Leaflet в свой проект, чтобы ссылаться на файлы изображений marker-icon-2x.png и marker-shadow.png.
Откройте файл angular.json и добавьте каталог изображений Leaflet, images:
{
// ...
"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"
}
Этот код скопирует изображения маркеров Leaflet локально.
Затем снова зайдите в 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: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
tiles.addTo(this.map);
}
ngAfterViewInit(): void {
this.initMap();
this.markerService.makeCapitalMarkers(this.map);
}
}
Сохраните изменения, остановите приложение и перезапустите его. После этого откройте приложение в своем веб-браузере (localhost:4200) – и вы увидите, что столицы всех штатов отмечены синим значком.
На данный момент у нас есть карта, которая поддерживает стандартные метки.
4: Настройка круглых меток
А теперь мы попробуем заменить стандартные метки круглыми, после чего увеличим размер кругов, чтобы отразить население столицы штата.
Откройте src/app/marker.service.ts и создайте функцию makeCapitalCircleMarkers() (она очень похожа на функцию makrCapitalMarkers()). Вместо метода marker мы будем использовать метод circleMarker:
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);
}
});
}
Вызовите эту функцию в src/app/map/map.component.ts:
ngAfterViewInit(): void {
this.initMap();
// this.markerService.makeCapitalMarkers(this.map);
this.markerService.makeCapitalCircleMarkers(this.map);
}
Сохраните эти изменения и откройте приложение в браузере (localhost:4200). Вы увидите, что стандартные метки были заменены кружками.
Объект circleMarker принимает третий опциональный параметр – он может содержать свойство radius. В MarkerService отредактируйте функцию makeCapitalCircleMarkers и присвойте радиусу значение 20:
const circle = L.circleMarker([lat, lon], { radius: 20 }).addTo(map);
Этот код определяет одинаковый радиус для всех меток (20).
Теперь мы изменим радиус, чтобы отразить население столицы штата:
static scaledRadius(val: number, maxVal: number): number { return 20 * (val / maxVal); }
Эта функция принимает значение (в нашем случае это численность населения), максимальное значение (самое большое население) и возвращает метку радиусом в диапазоне [0–20].
Воспользуемся spread-оператором и map, чтобы определить столицу с наибольшим населением:
const maxPop = Math.max(...res.features.map(x => x.properties.population), 0);
По данным GeoJSON, наибольшее население будет в Фениксе, Аризона (1626078).
После этого мы можем собрать все вместе с помощью ScaledRadius в качестве функции радиуса.
Откройте 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); } }); } }
Сохраните изменения. Затем остановите приложение, перезапустите его и откройте в своем браузере. Обратите внимание на новые круглые метки – теперь они масштабированы согласно количеству населения в столице.
Итак, наша карта поддерживает метки.
Заключение
В этом руководстве мы создали сервис меток, который загружает данные и выстраивает маркеры на карте. Также вы умеете настраивать два вида маркеров – L.marker и L.circleMarker, – и знаете, как с помощью функции определить радиус каждой круглой метки.
Tags: Angular, Leaflet, Leaflet maps