Кластеризация точечных данных в Mapbox GL JS применяется для повышения производительности и улучшения визуального восприятия при отображении большого количества объектов на карте. При увеличении числа маркеров до тысяч и десятков тысяч отрисовка каждого объекта отдельно становится неэффективной, а интерфейс перегружается визуально. Механизм кластеризации решает эту проблему за счёт группировки близко расположенных точек в агрегированные элементы.
Кластеризация в Mapbox GL JS реализуется на уровне источника данных
(source) и использует алгоритмы пространственного разбиения
(внутри движка используется подход, основанный на деревьях и тайловой
индексации, схожий с Supercluster).
Каждая точка в GeoJSON может быть автоматически объединена в кластер в зависимости от:
clusterRadius)clusterMaxZoom)При изменении масштаба карта динамически пересчитывает группы точек, что позволяет сохранять актуальную структуру кластеров без ручного пересчёта данных.
Кластеризация активируется на уровне GeoJSON-источника:
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [69.2401, 53.2145]
},
properties: {}
}
]
},
cluster: true,
clusterRadius: 50,
clusterMaxZoom: 14
});
Ключевые параметры:
cluster: true — включает кластеризациюclusterRadius — радиус объединения точек в
пикселяхclusterMaxZoom — максимальный zoom, на котором
продолжается группировкаПосле включения кластеризации источник начинает возвращать два типа объектов:
cluster: true)cluster: false или отсутствие
cluster свойства)Для их отображения используются отдельные слои.
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'points',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#51bbd6',
'circle-radius': [
'step',
['get', 'point_count'],
20,
100,
30,
750,
40
]
}
});
Здесь фильтр ['has', 'point_count'] определяет, что слой
применяется только к кластерным объектам.
Ключевое свойство:
point_count — количество точек внутри кластераДля отображения числа объектов внутри кластера используется текстовый слой:
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'points',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-size': 12
}
});
Используется формат {point_count_abbreviated}, который
автоматически сокращает большие числа (например, 1200 → 1.2k).
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'points',
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;
const source = map.getSource('points');
source.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) return;
map.easeTo({
center: features[0].geometry.coordinates,
zoom: zoom
});
});
});
Метод getClusterExpansionZoom вычисляет уровень
масштабирования, на котором кластер раскроется в отдельные элементы.
Mapbox GL JS предоставляет API для извлечения точек внутри кластера:
source.getClusterChildren(clusterId, (err, features) => {
console.log(features);
});
Также доступен метод получения всех точек внутри кластера:
source.getClusterLeaves(clusterId, limit, offset, (err, features) => {
console.log(features);
});
Эти методы полезны для построения кастомных всплывающих окон и анализа состава кластера.
Визуальная дифференциация кластеров часто основана на размере группы.
Для этого используется выражение step или
interpolate:
'circle-color': [
'step',
['get', 'point_count'],
'#51bbd6',
100,
'#f1f075',
750,
'#f28cb1'
]
Логика:
Это позволяет визуально оценивать плотность данных без чтения чисел.
Mapbox поддерживает использование выражений для динамической стилизации:
'circle-radius': [
'interpolate',
['linear'],
['get', 'point_count'],
1, 10,
100, 20,
1000, 40
]
Такой подход обеспечивает плавное масштабирование размеров кластеров.
Каждый кластер содержит агрегированные свойства, которые можно
расширять через clusterProperties.
map.addSource('points', {
type: 'geojson',
data: geojsonData,
cluster: true,
clusterProperties: {
sum: ['+', ['get', 'value']]
}
});
Здесь создаётся пользовательское поле sum, которое
аккумулирует значения value из всех точек кластера.
Кластеры пересчитываются автоматически при:
Это означает, что разработчику не требуется вручную пересобирать структуру кластеров при каждом взаимодействии.
Механизм кластеризации имеет ряд характерных особенностей:
Типичная архитектура слоёв включает три уровня:
Такое разделение обеспечивает независимое управление стилем и поведением каждого уровня представления данных.
Для улучшения UX применяется обработка событий:
map.on('mouseenter', 'clusters', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'clusters', () => {
map.getCanvas().style.cursor = '';
});
Дополнительно можно подсвечивать кластер при наведении, изменяя его
стиль через feature-state.
Кластеры корректно реагируют на фильтры слоя:
map.setFilter('unclustered-point', [
'==',
['get', 'category'],
'restaurant'
]);
При этом пересчёт кластеров учитывает только активные данные слоя.
При работе с десятками тысяч точек важны следующие принципы:
Кластеризация снижает нагрузку на отрисовку, но не устраняет стоимость хранения данных в памяти браузера.
В интерфейсах аналитики, логистики и геомаркетинга кластеризация становится основным инструментом визуального уплотнения информации. Она позволяет:
Использование встроенной кластеризации Mapbox в связке с Mapbox GL JS делает возможным построение высоконагруженных картографических приложений без перехода к серверной агрегации на ранних этапах разработки