Cluster properties

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

Включение кластеризации в GeoJSON source

Основой работы кластеров является конфигурация источника данных:

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

cluster

Активирует кластеризацию. При значении true Mapbox GL JS перестаёт отображать все точки напрямую и вместо этого группирует их в кластеры на основе пространственной близости.

clusterRadius

Определяет радиус кластеризации в пикселях. Этот параметр влияет на плотность кластеров:

  • меньшие значения → больше кластеров меньшего размера
  • большие значения → меньше кластеров, но с большим количеством точек внутри

clusterMaxZoom

Задает максимальный zoom-уровень, на котором продолжается кластеризация. После этого уровня кластеры “распадаются” на отдельные точки.


clusterProperties: вычисление агрегированных значений

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

map.addSource('points', {
  type: 'geojson',
  data: geojsonData,
  cluster: true,
  clusterRadius: 50,
  clusterProperties: {
    sumValue: ['+', ['get', 'value']],
    maxValue: ['max', ['get', 'value']],
    minValue: ['min', ['get', 'value']]
  }
});

Принцип работы clusterProperties

Каждое свойство кластера вычисляется через выражение, которое агрегирует значения всех входящих в кластер точек. Внутри используются expression syntax Mapbox GL JS.

Основные типы агрегаций:

Сумма значений

sumValue: ['+', ['get', 'value']]

Суммирует значения свойства value всех точек.

Максимум и минимум

maxValue: ['max', ['get', 'value']],
minValue: ['min', ['get', 'value']]

Пользовательские счётчики

Можно создавать несколько категорий внутри кластера:

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

Встроенное свойство point_count

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

  • point_count — количество точек внутри кластера

Также доступно:

  • point_count_abbreviated — сокращённое отображение (например, 1.2k)

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


Использование clusterProperties в стилях

Свойства кластеров применяются в слоях через data-driven styling.

Circle layer для кластеров

map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'points',
  filter: ['has', 'point_count'],
  paint: {
    'circle-radius': [
      'step',
      ['get', 'point_count'],
      20,
      100,
      30,
      750,
      40
    ],
    'circle-color': [
      'step',
      ['get', 'point_count'],
      '#51bbd6',
      100,
      '#f1f075',
      750,
      '#f28cb1'
    ]
  }
});

Здесь используется выражение step, которое позволяет менять визуализацию в зависимости от размера кластера.


Текстовое отображение кластера

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

Можно также использовать пользовательские агрегаты:

'text-field': [
  'concat',
  'sum: ',
  ['to-string', ['get', 'sumValue']]
]

Доступ к кластерным данным через API

Mapbox GL JS предоставляет методы для работы с кластерами на уровне источника.

getClusterExpansionZoom

Позволяет определить zoom, на котором кластер “раскрывается”:

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

Это используется для интерактивного приближения к кластеру.


getClusterLeaves

Возвращает точки, входящие в кластер:

map.getSource('points').getClusterLeaves(
  clusterId,
  limit,
  offset,
  (err, features) => {
    console.log(features);
  }
);

Параметры:

  • limit — количество возвращаемых объектов
  • offset — смещение для пагинации

getClusterChildren

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

map.getSource('points').getClusterChildren(clusterId, (err, features) => {
  console.log(features);
});

clusterProperties и выражения агрегации

Expression system Mapbox GL JS поддерживает набор операторов, применимых в clusterProperties.

Арифметические операции

['+', value]
['*', value]
['-', value]
['/', value]

Условная логика

['case',
  condition, result,
  fallback
]

Получение свойства

['get', 'propertyName']

Комбинирование агрегаций

clusterProperties: {
  weightedSum: [
    '+',
    ['*', ['get', 'weight'], ['get', 'value']]
  ]
}

Практическая модель кластерных данных

После обработки GeoJSON каждый кластер приобретает структуру:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [lng, lat]
  },
  "properties": {
    "cluster": true,
    "cluster_id": 123,
    "point_count": 42,
    "sumValue": 1200,
    "maxValue": 98
  }
}

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


События взаимодействия с кластерами

Кластеры обрабатываются через события слоя:

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

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

Дальнейшая логика обычно включает:

  • раскрытие кластера через zoom
  • получение листьев кластера
  • отображение агрегированных данных

Динамическая визуализация на основе clusterProperties

clusterProperties позволяет строить аналитические кластеры:

  • распределение категорий внутри кластера
  • суммарные показатели (например, продажи, количество событий)
  • статистические характеристики (min, max, average через комбинации выражений)

Пример комбинированной аналитики:

clusterProperties: {
  totalSales: ['+', ['get', 'sales']],
  avgApprox: [
    '/',
    ['+', ['get', 'sales']],
    ['get', 'point_count']
  ],
  highPriorityCount: [
    '+',
    ['case', ['>', ['get', 'priority'], 5], 1, 0]
  ]
}

Ограничения и особенности поведения

Кластеризация выполняется на стороне клиента WebGL-движка Mapbox GL JS, что накладывает ограничения:

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

Поведение кластеров при изменении zoom

Кластеры динамически перестраиваются при изменении масштаба:

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

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