ngx-leaflet

Интеграция картографического функционала в Angular-приложения через обёртку над Leaflet позволяет использовать декларативный подход к работе с картами, сохраняя при этом всю мощь нативной библиотеки. Библиотека ngx-leaflet выступает связующим слоем между императивным API Leaflet и реактивной архитектурой Angular.

В основе подхода лежит разделение ответственности: Leaflet управляет отрисовкой карты, слоями, событиями и геометрией, а Angular отвечает за состояние приложения, жизненный цикл компонентов и реактивные потоки данных. ngx-leaflet предоставляет директиву, которая связывает DOM-элемент с экземпляром карты Leaflet.

Ключевой принцип заключается в том, что карта не создаётся напрямую через конструктор Leaflet внутри компонента, а декларативно описывается через биндинги Angular.

Установка и подключение зависимостей

Основные пакеты:

  • leaflet — ядро картографического движка
  • ngx-leaflet — Angular-обёртка

Установка:

npm install leaflet
npm install @asymmetrik/ngx-leaflet

Дополнительно требуется подключение типов:

npm install --save-dev @types/leaflet

Подключение стилей Leaflet

Без корректных стилей карта не будет отображаться правильно. Подключение осуществляется глобально:

"styles": [
  "node_modules/leaflet/dist/leaflet.css"
]

Альтернативно через styles.scss:

@import "leaflet/dist/leaflet.css";

Конфигурация модуля Angular

Перед использованием директив необходимо подключить модуль:

import { LeafletModule } from '@asymmetrik/ngx-leaflet';

@NgModule({
  imports: [
    LeafletModule
  ]
})
export class AppModule {}

Создание базовой карты

В компоненте создаётся объект конфигурации карты:

import { latLng, tileLayer, MapOptions } from 'leaflet';

export class MapComponent {
  options: MapOptions = {
    layers: [
      tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: 'OpenStreetMap'
      })
    ],
    zoom: 10,
    center: latLng(51.505, -0.09)
  };
}

Шаблон компонента:

<div
  leaflet
  [leafletOptions]="options">
</div>

Директива leaflet создаёт экземпляр карты и связывает его с DOM-элементом, а leafletOptions задаёт конфигурацию.

Управление экземпляром карты

Для получения доступа к нативному объекту карты используется событие инициализации:

<div
  leaflet
  [leafletOptions]="options"
  (leafletMapReady)="onMapReady($event)">
</div>
import { Map } from 'leaflet';

export class MapComponent {
  map!: Map;

  onMapReady(map: Map): void {
    this.map = map;
  }
}

Нативный объект позволяет использовать весь API Leaflet без ограничений.

Работа с тайловыми слоями

Leaflet использует систему слоёв для отображения данных. Основной слой — тайловый:

tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 19,
  attribution: 'OpenStreetMap'
})

Слои можно динамически менять, комбинировать и обновлять:

this.map.removeLayer(oldLayer);
this.map.addLayer(newLayer);

Маркеры и пользовательские объекты

Маркеры создаются через класс Marker:

import { marker } from 'leaflet';

const m = marker([51.5, -0.09]);
m.addTo(this.map);

С ngx-leaflet часто используется декларативный подход через массив слоёв:

import { Marker } from 'leaflet';

layers = [
  new Marker([51.5, -0.09])
];

Кастомизация маркеров

Иконки задаются через Icon:

import { icon, Marker } from 'leaflet';

const customIcon = icon({
  iconUrl: 'assets/marker.png',
  iconSize: [25, 41],
  iconAnchor: [12, 41]
});

const m = new Marker([51.5, -0.09], { icon: customIcon });

Реактивное управление слоями

В Angular-подходе изменения состояния часто привязаны к потокам данных. Карта обновляется через замену массива слоёв:

layers: any[] = [];

updateMarkers(data: any[]): void {
  this.layers = data.map(item =>
    marker([item.lat, item.lng])
  );
}

Изменение ссылки на массив вызывает обновление визуализации.

Работа с событиями карты

Leaflet предоставляет богатую систему событий:

this.map.on('click', (e) => {
  console.log(e.latlng);
});

В Angular-интеграции события могут обрабатываться через сам экземпляр карты:

onMapReady(map: Map): void {
  map.on('zoomend', () => {
    console.log(map.getZoom());
  });
}

Полилинии и полигоны

Для отображения маршрутов и областей используются геометрические слои:

import { polyline, polygon } from 'leaflet';

const line = polyline([
  [51.5, -0.1],
  [51.51, -0.12]
]);

const area = polygon([
  [51.52, -0.1],
  [51.53, -0.12],
  [51.51, -0.13]
]);

Геометрические операции и визуализация

Leaflet не предоставляет встроенной геоаналитики, но поддерживает визуализацию результатов вычислений, выполненных отдельно. Например, маршрутизация может строиться через внешние сервисы, а затем отображаться через polyline.

Слои и контроль отображения

Для управления видимостью слоёв применяется LayerGroup:

import { layerGroup } from 'leaflet';

const group = layerGroup([marker1, marker2]);
group.addTo(this.map);

Это позволяет включать и отключать наборы объектов:

group.remove();
group.addTo(this.map);

Управление картой через Angular lifecycle

Важно учитывать жизненный цикл компонентов:

  • инициализация карты после ngAfterViewInit
  • уничтожение через ngOnDestroy
ngOnDestroy(): void {
  this.map.remove();
}

Это предотвращает утечки памяти.

Типизация и работа с TypeScript

Leaflet полностью поддерживает TypeScript. ngx-leaflet усиливает типизацию через интерфейсы:

import { MapOptions, Layer } from 'leaflet';

options: MapOptions;
layers: Layer[];

Типизация критична при масштабировании приложений с большим количеством географических объектов.

Производительность при работе с большими данными

При отображении тысяч объектов необходимо учитывать:

  • использование кластеризации маркеров
  • минимизацию перерисовок слоёв
  • отказ от частых мутаций массива слоёв

Кластеризация часто реализуется через дополнительные плагины Leaflet и интегрируется в Angular через ngx-leaflet так же, как обычные слои.

Интеграция с внешними источниками данных

Карта часто выступает визуализацией API:

this.http.get('/api/points').subscribe(data => {
  this.layers = data.map(p => marker([p.lat, p.lng]));
});

Обновление происходит реактивно при изменении данных.

Динамическое изменение центра и масштаба

Карта может программно изменять положение:

this.map.setView([52.52, 13.405], 12);

Также доступно плавное перемещение:

this.map.flyTo([52.52, 13.405], 10);

Работа с пользовательскими контролами

Leaflet позволяет добавлять UI-элементы:

import { control } from 'leaflet';

control.zoom({
  position: 'topright'
}).addTo(this.map);

Angular-интеграция не ограничивает использование нативных контролов.

Использование нескольких слоёв карт

Типичный сценарий — переключение между спутниковой и стандартной картой:

const streets = tileLayer('url1');
const satellite = tileLayer('url2');

Управление:

this.map.addLayer(streets);
this.map.removeLayer(satellite);

Геолокация пользователя

Leaflet поддерживает определение местоположения:

this.map.locate({ setView: true, maxZoom: 16 });

Обработка события:

this.map.on('locationfound', (e) => {
  console.log(e.latlng);
});

Масштабирование архитектуры ngx-leaflet

При росте приложения выделяются отдельные слои абстракции:

  • сервис карты (MapService)
  • фабрики слоёв
  • модели геоданных
  • адаптеры API

Такой подход снижает связанность компонентов и упрощает тестирование.

Паттерны проектирования в работе с картами

Часто применяются:

  • Factory для создания слоёв
  • Strategy для переключения источников тайлов
  • Observer для синхронизации данных
  • Facade для упрощённого доступа к Leaflet API

Расширение функциональности через плагины

Leaflet экосистема включает плагины для:

  • кластеризации
  • тепловых карт
  • маршрутизации
  • геокодирования

ngx-leaflet не ограничивает подключение расширений, так как работает поверх нативного API.

Оптимизация рендеринга

При работе с динамическими данными критично:

  • избегать полной пересборки карты
  • обновлять только изменённые слои
  • использовать requestAnimationFrame при анимациях
  • минимизировать количество DOM-операций

Синхронизация состояния приложения и карты

Карта может быть частью глобального состояния:

this.store.select('mapState').subscribe(state => {
  this.map.setView(state.center, state.zoom);
});

Это позволяет интегрировать Leaflet в архитектуры NgRx или аналогичные state management решения.