При отображении большого количества точечных объектов на интерактивной карте возникает проблема перегрузки визуального слоя и деградации производительности. Отображение тысяч или миллионов маркеров в исходном виде приводит к снижению читаемости и резкому росту нагрузки на рендеринг.
Кластеризация представляет собой процесс объединения близко расположенных точек в агрегированные группы (кластеры), которые динамически изменяются в зависимости от масштаба карты. При увеличении масштаба кластеры распадаются на более мелкие группы или отдельные точки.
В основе кластеризации в Mapbox GL JS лежит алгоритм Supercluster, обеспечивающий быстрый пространственный индекс и агрегацию данных в режиме реального времени.
Кластеризация применяется к источнику данных типа
geojson. Основная конфигурация задаётся при добавлении
источника на карту:
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [37.6173, 55.7558]
}
}
]
},
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
Ключевые параметры:
cluster: true — включает механизм кластеризацииclusterRadius — радиус объединения точек в
пикселяхclusterMaxZoom — максимальный зум, на котором
выполняется кластеризацияПосле включения кластеризации Mapbox автоматически формирует дополнительные свойства для каждого кластера:
cluster: true — признак кластераpoint_count — количество точек внутри кластераcluster_id — уникальный идентификатор кластераЭти свойства используются для стилизации и интерактивного поведения слоёв.
Кластеры визуализируются через отдельные слои circle,
symbol или комбинированные стили.
Пример базового отображения кластеров:
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'points',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#51bbd6',
'circle-radius': [
'step',
['get', 'point_count'],
15,
10,
20,
50,
30
]
}
});
Отдельный слой для одиночных точек:
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'points',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#f28cb1',
'circle-radius': 6
}
});
Часто требуется визуализировать число объектов внутри кластера. Это
реализуется через слой symbol:
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} автоматически форматирует
числа (например, 1.2k).
Одной из ключевых операций является раскрытие кластера с приближением карты к области его содержимого.
map.on('click', 'clusters', async (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['clusters']
});
const clusterId = features[0].properties.cluster_id;
const source = map.getSource('points');
const zoom = await source.getClusterExpansionZoom(clusterId);
map.easeTo({
center: features[0].geometry.coordinates,
zoom
});
});
Метод getClusterExpansionZoom вычисляет оптимальный
масштаб, на котором кластер раскроется в отдельные элементы.
Кластер можно разложить на составляющие элементы:
map.getSource('points').getClusterLeaves(clusterId, 100, 0, (err, features) => {
console.log(features);
});
Параметры:
limit — максимальное количество возвращаемых
элементовoffset — смещение для пагинацииДля анализа иерархии используется метод получения дочерних кластеров:
map.getSource('points').getClusterChildren(clusterId, (err, features) => {
console.log(features);
});
Такой подход позволяет строить древовидную структуру кластеризации.
Кластеризация основана на переводе координат в проекцию и применении пространственного индекса. Алгоритм учитывает:
Фактически используется структура данных, аналогичная R-tree + k-d tree, оптимизированная для Web Mercator проекции.
При изменении:
кластеризация пересчитывается автоматически. Это обеспечивает актуальность представления данных без ручного пересоздания структуры.
Кластеры часто разделяются на категории по размеру:
[
'step',
['get', 'point_count'],
'#51bbd6',
20,
'#f1f075',
100,
'#f28cb1'
]
Такая логика позволяет визуально выделять плотные области данных.
GeoJSON позволяет передавать дополнительные свойства объектов, которые сохраняются при кластеризации.
{
type: 'Feature',
properties: {
category: 'restaurant'
},
geometry: {
type: 'Point',
coordinates: [30.5, 50.5]
}
}
Однако при агрегации свойства не суммируются автоматически. Для этого
применяются выражения clusterProperties.
Mapbox GL JS позволяет вычислять агрегированные значения:
map.addSource('points', {
type: 'geojson',
data: geojsonData,
cluster: true,
clusterProperties: {
sum: ['+', ['get', 'value']]
}
});
Таким образом кластеры могут содержать сумму, среднее значение или другие метрики.
Кластеризация критична для работы с большими наборами данных (десятки тысяч и более объектов). Основные факторы производительности:
clusterRadiusclusterMaxZoomУвеличение радиуса снижает количество кластеров, но уменьшает точность локализации. Уменьшение радиуса повышает детализацию, но увеличивает нагрузку.
Кластеры и отдельные точки обрабатываются через единый event API:
map.on('click', 'unclustered-point', (e) => {
console.log(e.features[0]);
});
Такой подход позволяет разделять поведение для агрегированных и одиночных объектов.
Кластеризация в Mapbox GL JS имеет ряд особенностей:
При серверной подготовке данных возможна предварительная кластеризация, однако встроенный механизм чаще используется благодаря:
Кластеры выполняют не только функцию группировки, но и визуализацию плотности распределения объектов. При этом каждый кластер является динамическим объектом, пересчитываемым на основе текущего viewport.
В результате формируется непрерывная модель плотности, приближенная к heatmap, но с дискретными интерактивными узлами.
Внутренний механизм использует принципы пространственного разбиения:
Это обеспечивает логарифмическую сложность операций добавления и поиска относительно количества точек.