Кластеризация в 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 определяет границу, после которой
кластеризация прекращается и данные начинают отображаться как отдельные
точки. Это ключевой механизм управления детализацией при увеличении
масштаба.
Каждый кластер представляет собой виртуальный объект с дополнительными свойствами:
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: 12
При таком значении:
clusterRadius влияет на плотность группировки точек.
Этот параметр критически влияет на визуальное поведение при разных
уровнях масштабирования.
Пример адаптации под разные сценарии:
clusterRadius: 40 // плотная городская визуализация
clusterRadius: 80 // региональные данные
Поведение визуализации часто зависит от уровня масштаба, даже при неизменной структуре кластеров.
'circle-radius': [
'step',
['get', 'point_count'],
10,
10, 15,
50, 20,
100, 30
]
Здесь размер кластера увеличивается в зависимости от количества точек, но эффект может усиливаться через зум-условия.
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: {
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
});
}
);
});
При приближении к максимальному масштабу возникают три режима отображения:
Эти режимы определяются комбинацией:
clusterMaxZoomclusterRadiusПри работе с десятками тысяч объектов важно учитывать:
Ключевой принцип — неизменяемый источник данных с динамическим отображением через стиль.
В сложных сценариях применяется разделение данных на несколько GeoJSON источников:
Каждый источник активируется через фильтры zoom:
filter: ['all',
['>=', ['zoom'], 10],
['<', ['zoom'], 14]
]
Такой подход позволяет:
Сочетание параметров clusterRadius,
clusterMaxZoom, clusterProperties и
zoom-выражений позволяет формировать непрерывную эволюцию кластеров:
Эта модель обеспечивает согласованное масштабирование данных без потери структуры и контекста.