Кластеризация в Mapbox GL JS основана на алгоритме Supercluster, который выполняет пространственную агрегацию GeoJSON-объектов на стороне клиента. Основная идея заключается в объединении близко расположенных точек в агрегированные объекты (кластеры) при низких уровнях масштабирования и их постепенном «раскрытии» по мере увеличения zoom.
Кластер в терминах Mapbox GL JS представляет собой виртуальный GeoJSON-объект с дополнительными свойствами:
cluster: true — признак агрегированного объектаcluster_id — идентификатор кластераpoint_count — количество точек внутри кластераpoint_count_abbreviated — сокращённое отображение
количестваКластеризация активируется на уровне источника
GeoJSONSource:
map.addSource('points', {
type: 'geojson',
data: '/data/points.geojson',
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
Ключевые параметры:
cluster — включает механизм агрегацииclusterMaxZoom — максимальный zoom, при котором
выполняется кластеризацияclusterRadius — радиус объединения точек (в
пикселях)На низких масштабах данные преобразуются в иерархическую структуру кластеров. При увеличении zoom происходит декомпозиция — кластеры заменяются либо более мелкими кластерами, либо отдельными точками.
Для отображения кластеров обычно используются отдельные слои:
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': '{point_count_abbreviated}',
'text-size': 12
}
});
Отдельно отображаются одиночные точки:
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'points',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#f28cb1',
'circle-radius': 6
}
});
Раскрытие кластеров в Mapbox GL JS представляет собой переход от агрегированного состояния к более детализированному уровню отображения. Основной механизм реализуется через вычисление zoom-уровня, при котором кластер перестаёт существовать как единое целое.
Mapbox GL JS предоставляет метод:
source.getClusterExpansionZoom(clusterId, (err, zoom) => {
map.easeTo({
center: coordinates,
zoom
});
});
Функция возвращает минимальный zoom, при котором выбранный кластер разбивается на дочерние элементы.
Математически это связано с плотностью распределения точек и
параметрами clusterRadius и текущей плиточной сеткой.
При обработке события клика по кластеру выполняется запрос информации о конкретном агрегированном объекте:
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['clusters']
});
const clusterId = features[0].properties.cluster_id;
const coordinates = features[0].geometry.coordinates;
});
Далее используется последовательность API-вызовов:
map.getSource('points').getClusterExpansionZoom(
clusterId,
(err, zoom) => {
map.easeTo({
center: coordinates,
zoom: zoom
});
}
);
Данный механизм обеспечивает визуальное «раскрытие» кластера за счёт изменения масштаба карты, а не прямой модификации данных.
Supercluster поддерживает навигацию по дереву кластеров.
source.getClusterChildren(clusterId, (err, children) => {
});
Возвращаются объекты следующего уровня иерархии — это могут быть как под-кластеры, так и отдельные точки.
source.getClusterLeaves(clusterId, limit, offset, (err, leaves) => {
});
Параметры:
limit — максимальное количество возвращаемых точекoffset — смещение для постраничной выборкиЭтот механизм используется для анализа состава кластера без его визуального раскрытия.
Вместо изменения zoom возможна последовательная замена одного
кластера его дочерними элементами. Логика основана на
getClusterChildren, где каждый уровень отображается
отдельно.
Такая стратегия используется при построении интерфейсов с контролируемой иерархией.
При высокой плотности точек на ограниченной области применяется радиальное распределение объектов вокруг центра кластера. Геометрически точки размещаются по окружности:
Данный подход позволяет визуально разделить объекты без изменения zoom.
Кластеры существуют только в пределах заданного диапазона zoom:
Переходы между состояниями происходят на основе динамического перерасчёта Supercluster.
При обновлении источника данные пересчитываются:
map.getSource('points').setData(newGeoJson);
После этого:
cluster_idКластеризация не требует ручного пересоздания слоёв — визуализация остаётся связанной с source.
Часто применяется условная стилизация по
point_count:
'circle-color': [
'step',
['get', 'point_count'],
'#51bbd6',
100,
'#f1f075',
750,
'#f28cb1'
],
'circle-radius': [
'step',
['get', 'point_count'],
15,
100,
20,
750,
25
]
Поведение основано на пороговых значениях:
Mapbox GL JS различает взаимодействие с:
cluster: true)События привязываются к слоям:
map.on('click', 'unclustered-point', (e) => {});
или:
map.on('click', 'clusters', (e) => {});
Фильтрация выполняется через filter слоя, основанный на
наличии point_count.
Кластеризация зависит от нескольких факторов:
При больших наборах данных:
Раскрытие кластера через getClusterExpansionZoom не
гарантирует переход к единичным точкам. Результат зависит от структуры
данных:
Поведение раскрытия может модифицироваться через:
clusterRadiuscluster_idПри этом структура Supercluster остаётся неизменной, а изменяется только интерпретация результата.
Иерархия кластеров представляет собой дерево:
Каждый уровень может быть получен через API Mapbox GL JS без визуального раскрытия, что позволяет строить альтернативные интерфейсы анализа данных.