Агрегация данных

Агрегация в контексте Mapbox GL JS — это преобразование большого количества точечных объектов в более компактные и интерпретируемые представления: кластеры, плотности, суммарные показатели или производные метрики. Такой подход критичен при работе с десятками тысяч и миллионами объектов, когда рендеринг каждого элемента отдельно приводит к деградации производительности и перегрузке визуального слоя.


Кластеризация GeoJSON-данных

Базовый механизм агрегации в Mapbox GL JS реализуется через кластеризацию источников типа GeoJSON.

map.addSource('points', {
  type: 'geojson',
  data: 'data.geojson',
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

Ключевые параметры:

  • cluster: true — включение кластеризации
  • clusterMaxZoom — максимальный зум, на котором выполняется агрегация
  • clusterRadius — радиус объединения точек в пикселях

Внутренне используется алгоритм, основанный на пространственном разбиении (аналогично Supercluster в экосистеме Mapbox), где точки объединяются в группы в зависимости от масштаба и расстояния.


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

После включения кластеризации источник автоматически генерирует дополнительные виртуальные сущности:

  • кластерные точки (cluster: true)
  • одиночные точки (cluster: false)
  • агрегированные свойства кластеров

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

map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'points',
  filter: ['has', 'point_count'],
  paint: {
    'circle-radius': [
      'step',
      ['get', 'point_count'],
      15, 10,
      25, 50,
      35
    ],
    'circle-color': '#51bbd6'
  }
});

И слой для отображения количества объектов:

map.addLayer({
  id: 'cluster-count',
  type: 'symbol',
  source: 'points',
  filter: ['has', 'point_count'],
  layout: {
    'text-field': '{point_count_abbreviated}',
    'text-size': 12
  }
});

clusterProperties: пользовательская агрегация

Mapbox GL JS позволяет расширять стандартную агрегацию с помощью clusterProperties. Это механизм вычисления пользовательских метрик внутри кластера.

map.addSource('points', {
  type: 'geojson',
  data: 'data.geojson',
  cluster: true,
  clusterProperties: {
    sum: ['+', ['get', 'value']],
    maxValue: ['max', ['get', 'value']]
  }
});

Здесь формируются дополнительные поля:

  • sum — сумма значений свойства value
  • maxValue — максимальное значение внутри кластера

Эти данные становятся доступны через свойства кластера и могут использоваться в стилях:

'circle-radius': [
  'interpolate',
  ['linear'],
  ['get', 'sum'],
  0, 10,
  1000, 40
]

Heatmap как форма агрегации

Heatmap-слой представляет собой непрямую агрегацию плотности точек. Вместо группировки объектов формируется непрерывное поле интенсивности.

map.addLayer({
  id: 'heatmap',
  type: 'heatmap',
  source: 'points',
  maxzoom: 15,
  paint: {
    'heatmap-weight': [
      'interpolate',
      ['linear'],
      ['get', 'value'],
      0, 0,
      10, 1
    ],
    'heatmap-intensity': 1,
    'heatmap-radius': 20,
    'heatmap-opacity': 0.8
  }
});

Математически это можно рассматривать как взвешенную плотность распределения точек, где каждая точка вносит вклад в локальную область влияния.


Агрегация векторных тайлов

При использовании векторных тайлов агрегация часто переносится на уровень генерации данных. Это снижает нагрузку на клиент.

Типичные сценарии:

  • предрасчёт сумм и статистик на сервере
  • генерация тайлов с агрегированными полями
  • использование инструментов типа Tippecanoe

Слой в Mapbox GL JS в этом случае получает уже агрегированные значения:

map.addSource('tiles', {
  type: 'vector',
  url: 'mapbox://tileset.id'
});

И стилизация работает напрямую с готовыми метриками:

'fill-color': [
  'interpolate',
  ['linear'],
  ['get', 'density'],
  0, '#f2f0f7',
  100, '#54278f'
]

Runtime-агрегация через queryRenderedFeatures

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

const features = map.queryRenderedFeatures({ layers: ['points-layer'] });

const aggregated = features.reduce((acc, f) => {
  acc.total += f.properties.value || 0;
  acc.count += 1;
  return acc;
}, { total: 0, count: 0 });

Такой подход применяется для:

  • динамической аналитики по текущему viewport
  • построения пользовательских графиков
  • реактивных интерфейсов поверх карты

Принципы построения кастомной агрегации

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

  1. Предагрегация на сервере (векторные тайлы)
  2. Кластеризация на клиенте (GeoJSON clustering)
  3. Визуальная агрегация (heatmap)
  4. Runtime-агрегация (queryRenderedFeatures)

Комбинация уровней позволяет балансировать между:

  • скоростью рендеринга
  • точностью данных
  • интерактивностью

Производительность и ограничения

Ключевые факторы, влияющие на эффективность агрегации:

  • размер входного GeoJSON
  • радиус кластеризации
  • максимальный zoom кластеризации
  • сложность выражений clusterProperties
  • количество одновременно отображаемых слоёв

Особенно затратными становятся:

  • глубокие выражения с вложенными операциями
  • пересчёт кластеров при частом обновлении источника
  • использование больших feature collections без предобработки

Взаимодействие агрегации и стилизации

Агрегированные данные в Mapbox GL JS тесно связаны с системой выражений стилей:

  • step — дискретная сегментация значений
  • interpolate — плавная интерполяция
  • match — категориальная агрегация

Пример комбинированного подхода:

'circle-color': [
  'step',
  ['get', 'point_count'],
  '#00ff00',
  100,
  '#ffa500',
  1000,
  '#ff0000'
]

Такая схема позволяет визуально кодировать распределение плотности без дополнительных вычислений вне GPU-пайплайна рендерера.