Кластеризация в картографических приложениях применяется для группировки большого количества точечных объектов на низких уровнях масштабирования. При увеличении масштаба группы постепенно «разворачиваются» в отдельные элементы, обеспечивая баланс между производительностью и читаемостью карты.
В контексте веб-карт кластеризация решает две ключевые задачи: снижение нагрузки на рендеринг и визуальная агрегация плотных наборов данных. Особенно это критично при работе с тысячами или миллионами точек, поступающих из GeoJSON, API или потоковых источников.
В Mapbox GL JS кластеризация реализуется на уровне
GeoJSONSource. Источник данных может быть сконфигурирован
так, чтобы библиотека автоматически группировала точки на стороне
клиента.
Ключевой принцип заключается в использовании пространственного индекса (вариант grid-based clustering и алгоритмов наподобие Supercluster), который формирует виртуальные группы точек в зависимости от текущего зума.
Основная конфигурация задаётся через параметры:
cluster: true — включает кластеризациюclusterRadius — радиус объединения точек в
пикселяхclusterMaxZoom — максимальный zoom, до которого
выполняется кластеризацияКластеризация активируется непосредственно в источнике данных:
map.addSource('earthquakes', {
type: 'geojson',
data: '/data/earthquakes.geojson',
cluster: true,
clusterRadius: 50,
clusterMaxZoom: 14
});
При включённой кластеризации Mapbox автоматически добавляет служебные поля к объектам:
point_count — количество точек внутри кластераpoint_count_abbreviated — сокращённое отображение
(например, 1.2k)cluster_id — уникальный идентификатор кластераКластеризация сама по себе не отображает визуальные элементы. Для визуализации используются отдельные слои:
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'earthquakes',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#51bbd6',
'circle-radius': [
'step',
['get', 'point_count'],
15,
100,
25,
750,
35
]
}
});
Логика step позволяет изменять размер круга в
зависимости от плотности данных. Чем больше точек в кластере, тем
крупнее визуальный элемент.
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'earthquakes',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 12
}
});
Этот слой отвечает за отображение числового значения внутри кластеров.
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'earthquakes',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#f28cb1',
'circle-radius': 6
}
});
Фильтр исключает все объекты, которые являются кластерами, оставляя только исходные точки.
Одним из ключевых сценариев является «разворачивание» кластера при клике. Для этого используется метод получения границ следующего уровня масштабирования.
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['clusters']
});
const clusterId = features[0].properties.cluster_id;
map.getSource('earthquakes').getClusterExpansionZoom(
clusterId,
(err, zoom) => {
if (err) return;
map.easeTo({
center: features[0].geometry.coordinates,
zoom: zoom
});
}
);
});
Метод getClusterExpansionZoom вычисляет оптимальный
уровень увеличения, при котором выбранный кластер перестаёт быть
агрегированным.
Для точечных объектов, не входящих в кластеры, обычно добавляется отдельная обработка:
map.on('click', 'unclustered-point', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const properties = e.features[0].properties;
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(`Magnitude: ${properties.magnitude}`)
.addTo(map);
});
Такой подход позволяет комбинировать агрегацию и детализированную информацию в одном интерфейсе.
Хотя API скрывает детали реализации, концептуально используется подход пространственного разбиения:
При увеличении масштаба сетка становится более детальной, и кластеры постепенно распадаются.
GeoJSON позволяет передавать дополнительные свойства, которые могут
агрегироваться через clusterProperties:
map.addSource('places', {
type: 'geojson',
data: '/data/places.geojson',
cluster: true,
clusterProperties: {
sumRating: ['+', ['get', 'rating']]
}
});
Здесь создаётся агрегированное поле sumRating, которое
суммирует значения рейтинга всех точек внутри кластера.
Доступные операции:
+ — суммаmax — максимумmin — минимумavg — среднее значениеПараметр clusterRadius влияет на плотность
кластеров:
clusterRadius: 80
Выбор значения зависит от плотности данных и масштаба карты. Для городских данных обычно используется меньший радиус, для глобальных — больший.
clusterMaxZoom определяет предел, после которого
кластеризация прекращается:
clusterMaxZoom: 12
После этого уровня все точки отображаются отдельно, даже если они находятся близко друг к другу.
Mapbox GL JS использует выражения фильтрации для разделения кластеров и одиночных точек:
['has', 'point_count'] — определяет кластер['!', ['has', 'point_count']] — определяет одиночную
точкуЭта логика является стандартным паттерном при построении многослойных визуализаций.
Кластеризация выполняется на стороне клиента и оптимизирована под WebGL. Это снижает нагрузку на сервер, но увеличивает потребление памяти в браузере.
Факторы влияния на производительность:
clusterPropertiesПри больших наборах данных кластеризация становится критически важным механизмом, позволяющим избежать деградации FPS.
Источник GeoJSON может быть обновлён в любой момент:
map.getSource('earthquakes').setData(newGeojson);
При этом кластеры пересчитываются автоматически. Это позволяет использовать потоковые данные (например, события в реальном времени), сохраняя стабильную визуализацию.
Кластеры могут быть дополнительно стилизованы через выражения:
Пример:
'circle-color': [
'step',
['get', 'point_count'],
'#00BCD4',
50,
'#2196F3',
200,
'#3F51B5'
]
Такая градация позволяет визуально различать уровни плотности без необходимости взаимодействия.
Кластеризация применяется в следующих типах приложений:
Во всех случаях ключевая цель — уменьшение визуального шума при сохранении структуры данных.
Несмотря на эффективность, подход имеет ограничения:
clusterPropertiesЭти ограничения компенсируются предварительной обработкой данных и гибкой настройкой источников.