Интеграция с Leaflet

Deck.gl и Leaflet решают разные задачи визуализации геоданных. Leaflet предоставляет лёгкую и удобную систему работы с картами, тайлами, маркерами и слоями, тогда как Deck.gl ориентирован на высокопроизводительную GPU-визуализацию больших объёмов пространственных данных через WebGL.

Интеграция двух библиотек позволяет объединить преимущества обеих технологий:

  • использовать привычную инфраструктуру Leaflet;
  • подключать тайловые подложки OpenStreetMap и других провайдеров;
  • отображать миллионы объектов через Deck.gl;
  • применять аппаратное ускорение для сложной визуализации;
  • использовать интерактивность обеих библиотек одновременно.

Типичная архитектура выглядит следующим образом:

Leaflet Map
├── TileLayer
├── Marker Layers
├── GeoJSON Layers
└── Deck.gl Layer
    ├── ScatterplotLayer
    ├── ArcLayer
    ├── HexagonLayer
    ├── GridLayer
    └── PolygonLayer

В такой конфигурации Leaflet отвечает за карту и навигацию, а Deck.gl рисует данные поверх карты через отдельный WebGL-канвас.


Библиотека @deck.gl/leaflet

Для интеграции используется пакет:

npm install @deck.gl/core
npm install @deck.gl/layers
npm install @deck.gl/leaflet
npm install leaflet

Главным элементом выступает класс:

LeafletLayer

Он создаёт мост между жизненным циклом Leaflet и системой рендеринга Deck.gl.

Импорт:

import L from 'leaflet';
import { LeafletLayer } from '@deck.gl/leaflet';
import { ScatterplotLayer } from '@deck.gl/layers';

Создание карты Leaflet

Базовая карта создаётся стандартным способом.

const map = L.map('map').setView(
  [55.751244, 37.618423],
  10
);

L.tileLayer(
  'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
  {
    attribution: '© OpenStreetMap'
  }
).addTo(map);

HTML-контейнер:

<div id="map"></div>

CSS:

#map {
  width: 100%;
  height: 600px;
}

После этого карта полностью управляется Leaflet.


Добавление первого слоя Deck.gl

Создадим слой точек.

const points = [
  {
    position: [37.62, 55.75],
    size: 100
  },
  {
    position: [37.70, 55.80],
    size: 200
  }
];

Описание слоя:

const scatterplotLayer =
  new ScatterplotLayer({
    id: 'scatter',
    data: points,

    getPosition: d => d.position,

    getRadius: d => d.size,

    getFillColor: [255, 0, 0],

    radiusUnits: 'meters',

    pickable: true
  });

Создание LeafletLayer:

const deckLayer = new LeafletLayer({
  layers: [scatterplotLayer]
});

Подключение:

map.addLayer(deckLayer);

После добавления точки отображаются поверх тайлов Leaflet.


Как работает синхронизация камер

Одной из самых сложных задач интеграции является синхронизация камеры.

В обычном приложении Deck.gl самостоятельно управляет объектом ViewState:

{
  longitude,
  latitude,
  zoom,
  pitch,
  bearing
}

При работе с Leaflet управление передаётся карте Leaflet.

Каждый раз при:

  • масштабировании;
  • панорамировании;
  • изменении размеров окна;
  • анимации карты;

LeafletLayer получает обновлённые параметры и автоматически передаёт их в Deck.gl.

Разработчику не требуется самостоятельно вычислять:

longitude
latitude
zoom

Все параметры берутся из текущего состояния Leaflet.


Обновление слоёв

Слои могут изменяться динамически.

Например, после получения новых данных:

function updatePoints(data) {
  deckLayer.setProps({
    layers: [
      new ScatterplotLayer({
        id: 'scatter',
        data,

        getPosition: d => d.position,

        getRadius: 100,

        getFillColor: [0, 128, 255]
      })
    ]
  });
}

Deck.gl выполнит внутреннее сравнение свойств и перерисует только изменившиеся части сцены.


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

Часто требуется отображать разные типы объектов одновременно.

import {
  ScatterplotLayer,
  ArcLayer
} from '@deck.gl/layers';

Создание слоёв:

const pointsLayer =
  new ScatterplotLayer({
    id: 'points',
    data: points,

    getPosition: d => d.position,

    getFillColor: [255, 140, 0],

    getRadius: 80
  });

const arcsLayer =
  new ArcLayer({
    id: 'arcs',
    data: routes,

    getSourcePosition:
      d => d.source,

    getTargetPosition:
      d => d.target,

    getSourceColor:
      [0, 128, 255],

    getTargetColor:
      [255, 0, 0],

    getWidth: 5
  });

Передача в LeafletLayer:

deckLayer.setProps({
  layers: [
    pointsLayer,
    arcsLayer
  ]
});

Порядок в массиве определяет порядок отрисовки.


Интерактивность объектов

Deck.gl поддерживает выбор объектов через механизм picking.

Включение:

pickable: true

Обработка наведения:

new ScatterplotLayer({
  id: 'points',

  data,

  pickable: true,

  onHover: info => {
    if (info.object) {
      console.log(info.object);
    }
  }
});

Обработка клика:

new ScatterplotLayer({
  id: 'points',

  data,

  pickable: true,

  onClick: info => {
    console.log(info.object);
  }
});

Объект info содержит:

{
  object,
  x,
  y,
  coordinate,
  index,
  layer
}

Это позволяет создавать всплывающие окна, панели информации и другие элементы интерфейса.


Связь с Popup Leaflet

Часто события Deck.gl используются для открытия Popup Leaflet.

onClick: info => {
  if (!info.object) {
    return;
  }

  const [lng, lat] =
    info.object.position;

  L.popup()
    .setLatLng([lat, lng])
    .setContent(
      info.object.name
    )
    .openOn(map);
}

В результате визуализация остаётся в Deck.gl, а пользовательский интерфейс строится средствами Leaflet.


Работа с GeoJSON

Deck.gl способен отображать GeoJSON поверх Leaflet.

import {
  GeoJsonLayer
} from '@deck.gl/layers';

Создание слоя:

const geoLayer =
  new GeoJsonLayer({
    id: 'geojson',

    data: geojson,

    stroked: true,

    filled: true,

    pointRadiusMinPixels: 5,

    getLineColor:
      [0, 0, 0],

    getFillColor:
      [0, 150, 255, 120],

    getPointRadius: 50
  });

Добавление:

deckLayer.setProps({
  layers: [geoLayer]
});

Преимущество перед Leaflet GeoJSON заключается в том, что визуализация выполняется через GPU и значительно лучше масштабируется на больших объёмах данных.


Визуализация миллионов точек

Одно из основных преимуществ Deck.gl — возможность отображения очень больших наборов данных.

Пример:

const layer =
  new ScatterplotLayer({
    id: 'big-data',

    data: millionPoints,

    getPosition:
      d => d.coordinates,

    getRadius: 20,

    getFillColor:
      [255, 0, 0]
  });

Для Leaflet такое количество объектов стало бы серьёзной нагрузкой.

Deck.gl использует:

  • буферы WebGL;
  • шейдеры GPU;
  • пакетную отрисовку;
  • инстансинг.

Поэтому производительность остаётся высокой даже при сотнях тысяч и миллионах объектов.


Использование HexagonLayer

Для анализа плотности данных часто применяется агрегирование.

import {
  HexagonLayer
} from '@deck.gl/aggregation-layers';

Создание слоя:

const layer =
  new HexagonLayer({
    id: 'hex',

    data: points,

    getPosition:
      d => d.position,

    radius: 500,

    elevationScale: 50,

    extruded: true
  });

Добавление:

deckLayer.setProps({
  layers: [layer]
});

Карта Leaflet остаётся двумерной, а Deck.gl создаёт трёхмерную визуализацию поверх неё.


Использование HeatmapLayer

Тепловые карты хорошо подходят для анализа концентрации объектов.

import {
  HeatmapLayer
} from '@deck.gl/aggregation-layers';

Пример:

const heatmap =
  new HeatmapLayer({
    id: 'heatmap',

    data: points,

    getPosition:
      d => d.position,

    getWeight:
      d => d.weight
  });

Подключение:

deckLayer.setProps({
  layers: [heatmap]
});

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


Работа с PolygonLayer

Отображение полигонов:

import {
  PolygonLayer
} from '@deck.gl/layers';

Создание:

const polygons =
  new PolygonLayer({
    id: 'districts',

    data,

    getPolygon:
      d => d.polygon,

    getFillColor:
      [0, 120, 255, 80],

    getLineColor:
      [0, 0, 0],

    lineWidthMinPixels: 2,

    pickable: true
  });

Подходит для отображения:

  • административных границ;
  • кадастровых участков;
  • зон покрытия;
  • торговых территорий;
  • геозон.

Управление порядком отображения

Leaflet использует концепцию панелей (panes).

Deck.gl слой можно поместить в нужную панель.

const deckLayer =
  new LeafletLayer({
    pane: 'overlayPane',
    layers: [layer]
  });

Создание собственной панели:

map.createPane('deckPane');

Настройка:

map.getPane('deckPane')
   .style.zIndex = 500;

Использование:

const deckLayer =
  new LeafletLayer({
    pane: 'deckPane',
    layers: [layer]
  });

Это позволяет точно контролировать расположение относительно маркеров, всплывающих окон и других элементов Leaflet.


Управление прозрачностью

Общая прозрачность слоя:

new ScatterplotLayer({
  id: 'points',

  data,

  opacity: 0.5
});

Или через альфа-канал цвета:

getFillColor: [
  255,
  0,
  0,
  120
]

Комбинация прозрачности особенно полезна при наложении нескольких аналитических слоёв.


Очистка ресурсов

При удалении слоя необходимо освобождать ресурсы.

Удаление из карты:

map.removeLayer(deckLayer);

Или полное уничтожение:

deckLayer.remove();

Deck.gl автоматически освобождает:

  • текстуры;
  • буферы;
  • шейдерные программы;
  • внутренние объекты WebGL.

Это предотвращает утечки памяти при длительной работе приложения.


Ограничения интеграции

Несмотря на удобство интеграции, существуют некоторые ограничения.

Ограниченная поддержка наклона карты

Leaflet является преимущественно двумерной системой.

Поэтому возможности:

pitch
bearing

либо отсутствуют, либо ограничены по сравнению с полноценным использованием Deck.gl совместно с Mapbox или MapLibre.

Особенности событий

События мыши сначала обрабатываются Leaflet.

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

pointer-events

или дополнительная координация обработчиков.

Ограничения 3D-сцен

Хотя Deck.gl способен отображать экструзии и трёхмерные объекты, сама карта Leaflet остаётся плоской.

Для полноценных трёхмерных сцен обычно используют:

  • Deck.gl + Mapbox;
  • Deck.gl + MapLibre;
  • Deck.gl + Google Maps.

Практические сценарии использования

Геоаналитика

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

Логистика

  • маршруты доставки;
  • транспортные потоки;
  • перемещение курьеров;
  • контроль автопарка.

IoT и телеметрия

  • датчики;
  • устройства мониторинга;
  • события в реальном времени;
  • географическое распределение оборудования.

Финансовые системы

  • отображение торговых точек;
  • анализ транзакций;
  • география клиентов;
  • пространственная статистика.

Городские информационные системы

  • дорожная инфраструктура;
  • коммунальные сети;
  • зоны обслуживания;
  • административные данные.

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