Кластеризация на разных зумах

Кластеризация в Mapbox GL JS основана на алгоритме пространственной агрегации точек (Supercluster), который объединяет близко расположенные объекты в группы в зависимости от текущего масштаба карты. При изменении зума происходит перерасчёт отображаемых кластеров на основе заранее подготовленного индекса, что обеспечивает высокую производительность даже при десятках и сотнях тысяч точек.


Кластеризация включается на уровне GeoJSONSource. Для этого используются параметры:

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

Пример конфигурации источника:

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

Поведение clusterMaxZoom

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

  • при зумах ниже значения — отображаются кластеры
  • при зумах выше — отображаются отдельные точки

Внутренняя модель кластеров

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

  • point_count — количество точек внутри кластера
  • cluster_id — идентификатор для дальнейшего раскрытия
  • geometry — координаты центра кластера

Mapbox автоматически генерирует эти свойства, что позволяет использовать их в стилях слоёв.


Отображение кластеров через слои

Кластеры и одиночные точки обычно отображаются разными слоями.

Слой кластеров

map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'points',
  filter: ['has', 'point_count'],
  paint: {
    'circle-color': '#51bbd6',
    'circle-radius': 18
  }
});

Слой количества точек в кластере

map.addLayer({
  id: 'cluster-count',
  type: 'symbol',
  source: 'points',
  filter: ['has', 'point_count'],
  layout: {
    'text-field': ['get', 'point_count_abbreviated'],
    'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
    'text-size': 12
  }
});

Слой отдельных точек

map.addLayer({
  id: 'unclustered-point',
  type: 'circle',
  source: 'points',
  filter: ['!', ['has', 'point_count']],
  paint: {
    'circle-color': '#f28cb1',
    'circle-radius': 6
  }
});

Кластеризация на разных зумах через clusterMaxZoom

Основной способ управления поведением кластеризации на разных уровнях масштаба заключается в настройке clusterMaxZoom.

Типичная логика:

  • низкий зум → крупные кластеры
  • средний зум → разбиение на мелкие кластеры
  • высокий зум → отдельные точки

Пример:

clusterMaxZoom: 12

При таком значении:

  • 0–11: активная кластеризация
  • 12+: отображение отдельных объектов

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

clusterRadius влияет на плотность группировки точек. Этот параметр критически влияет на визуальное поведение при разных уровнях масштабирования.

  • малый радиус → больше кластеров, меньшие группы
  • большой радиус → меньше кластеров, более крупные группы

Пример адаптации под разные сценарии:

clusterRadius: 40 // плотная городская визуализация
clusterRadius: 80 // региональные данные

Визуальная адаптация кластеров под зум

Поведение визуализации часто зависит от уровня масштаба, даже при неизменной структуре кластеров.

Использование интерполяции размера

'circle-radius': [
  'step',
  ['get', 'point_count'],
  10,
  10, 15,
  50, 20,
  100, 30
]

Здесь размер кластера увеличивается в зависимости от количества точек, но эффект может усиливаться через зум-условия.


Условное стилизование по zoom

Mapbox GL JS поддерживает выражение zoom, позволяющее менять визуализацию в зависимости от масштаба.

'circle-radius': [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 10,
  10, 20,
  15, 40
]

Такой подход позволяет:

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

Разделение слоёв по диапазонам зума

Для тонкой настройки часто используется разделение одного слоя на несколько с фильтрацией по zoom:

map.addLayer({
  id: 'clusters-low',
  type: 'circle',
  source: 'points',
  filter: ['all',
    ['has', 'point_count'],
    ['<', ['zoom'], 8]
  ],
  paint: {
    'circle-color': '#2c7bb6',
    'circle-radius': 20
  }
});
map.addLayer({
  id: 'clusters-high',
  type: 'circle',
  source: 'points',
  filter: ['all',
    ['has', 'point_count'],
    ['>=', ['zoom'], 8]
  ],
  paint: {
    'circle-color': '#d7191c',
    'circle-radius': 30
  }
});

Такое разделение позволяет создавать различные визуальные режимы:

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

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

Кластеры могут содержать вычисляемые свойства через clusterProperties, позволяя выполнять агрегацию при построении индекса.

Пример подсчёта категорий:

clusterProperties: {
  categoryA: ['+', ['case', ['==', ['get', 'type'], 'A'], 1, 0]],
  categoryB: ['+', ['case', ['==', ['get', 'type'], 'B'], 1, 0]]
}

Это позволяет:

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

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

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

  • изменение clusterMaxZoom
  • плавную анимацию перехода
  • повторное центрирование карты

Пример взаимодействия:

map.on('click', 'clusters', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['clusters']
  });

  const clusterId = features[0].properties.cluster_id;

  map.getSource('points').getClusterExpansionZoom(
    clusterId,
    (err, zoom) => {
      map.easeTo({
        center: features[0].geometry.coordinates,
        zoom: zoom
      });
    }
  );
});

Поведение кластеризации при экстремальных зумах

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

  1. Полная кластеризация (низкие зумы)
  2. Частичная декомпозиция (средние зумы)
  3. Отображение одиночных точек (высокие зумы)

Эти режимы определяются комбинацией:

  • clusterMaxZoom
  • clusterRadius
  • плотности исходных данных
  • визуальных фильтров слоёв

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

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

  • кластеризация должна выполняться на источнике, а не вручную
  • избегается пересоздание источника при каждом изменении зума
  • фильтрация должна выполняться через стили слоёв, а не через данные

Ключевой принцип — неизменяемый источник данных с динамическим отображением через стиль.


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

В сложных сценариях применяется разделение данных на несколько GeoJSON источников:

  • источник A: низкие зумы (грубая кластеризация)
  • источник B: средние зумы (детализированные кластеры)
  • источник C: высокие зумы (сырые точки)

Каждый источник активируется через фильтры zoom:

filter: ['all',
  ['>=', ['zoom'], 10],
  ['<', ['zoom'], 14]
]

Такой подход позволяет:

  • контролировать плотность отображения
  • снижать нагрузку на визуализацию
  • усиливать семантическую читаемость карты

Динамическая визуальная эволюция кластеров

Сочетание параметров clusterRadius, clusterMaxZoom, clusterProperties и zoom-выражений позволяет формировать непрерывную эволюцию кластеров:

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

Эта модель обеспечивает согласованное масштабирование данных без потери структуры и контекста.