Карты в Angular и Leaflet: добавление форм
Development | Комментировать запись
Leaflet поддерживает формы. Предоставив программе файл GeoJSON с данными о границах формы, вы можете определять на картах страны, штаты, области и т.д.
Примечание: Это продолжение серии по использованию Angular и Leaflet. Другие части можно найти по тегу Leaflet maps.
В этом мануале вы узнаете, как добавить формы для штатов США.
Требования
Полный список требований вы найдете в первом мануале этой серии.
1: Загрузка данных GeoJSON
В этом руководстве мы будем использовать данные GeoJSON для отображения контуров штатов США.
Посетите эту ссылку и загрузите файл gz_2010_us_040_00_5m.json.
Сохраните этот файл в каталоге /assets/data.
2: Создание сервиса форм
На данный момент у вас должна быть рабочая реализация Leaflet в приложении Angular.
Используйте терминал, чтобы перейти в каталог проекта. Затем выполните следующую команду, чтобы создать новый сервис:
npx @angular/cli generate service shape --skip-tests
Эта команда создаст новый файл, shape.service.ts.
Затем добавьте этот новый сервис в свой файл app.module.ts в качестве провайдера.
Откройте 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 { 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 { }
Теперь ваше приложение поддерживает новый сервис по имени ShapeService.
3: Загрузка фигур
Затем откройте только что созданный файл shape.service.ts в редакторе кода и добавьте HttpClient в конструктор:
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');
  }
}
Функция getStateShapes() вернет наблюдаемый объект сериализованного объекта GeoJSON. Чтобы использовать его, вам нужно будет подписаться на observable в MapComponent (src/app/map/map.component.ts).
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;
    });
  }
}
Этот код внедряет ShapeService в конструктор, создает локальную переменную для хранения данных и вызывает функцию getStateShapes() для извлечения данных и подписки на результат.
Примечание: Еще лучше было бы предварительно загрузить данные в резолвер.
После загрузки данных нужно добавить на карту фигуры в виде слоя. Leaflet предоставляет фабрику для слоев GeoJSON, которую мы можем использовать в этом мануале. Давайте поместим эту логику в отдельную функцию в файле src/app/map/map.component.ts, а затем вызовем ее после обработки данных.
// ...
@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();
    });
  }
}
Функция initStatesLayer() создает новый слой GeoJSON и добавляет его на карту.
Сохраните эти изменения. Затем остановите приложение и перезапустите его. Откройте приложение в браузере (localhost:4200) и проверьте, правильно ли отображаются границы штатов.
Затем мы закрепим события mouseover и mouseout для взаимодействия с каждой из фигур с помощью 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); }
Сохраните изменения. Затем откройте приложение в браузере (localhost:4200) и наведите указатель мыши на штат – он изменит свой цвет по границам.
Однако теперь метки выглядят тусклыми, потому что слой фигур находится над слоем меток.
Есть два подхода к решению этой проблемы. Первый подход – поместить вызов makeCapitalCircleMarkers() непосредственно после initStatesLayer(). Второй подход – вызвать метод bringToBack() на слой фигур после того, как он будет добавлен на карту.
Мы выбрали второй подход, метод bringToBack(). Вот полный файл map.component.ts с учетом этого метода.
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();
    });
  }
}
Сохраните изменения. Затем откройте приложение в своем браузере (localhost:4200) и обратите внимание на круглые маркеры, которые мы создали во второй части этой серии – теперь они размещены над формами штатов.
Итак, вы создали карту, которая поддерживает фигуры.
Заключение
Этот мануал помог вам создать сервис форм, который загружает данные о границах и собирает фигуры. Мы добавили интерактивности с помощью методов onEachFeature() и L.DomEvent.On.
На этом мы заканчиваем нашу серию о создании карт на Angular и Leaflet.
Tags: Angular, AngularJS, Leaflet, Leaflet maps