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

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

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