Карты в Angular и Leaflet: добавление меток

Библиотека 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: '&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);

  }

}

Если бы на этом этапе мы запустили свое приложение, в консоли мы бы столкнулись с двумя ошибками:

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: '&copy; <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: , ,

Добавить комментарий