При отображении большого количества маркеров на карте возникает несколько проблем:
Кластеризация маркеров представляет собой механизм объединения близко расположенных маркеров в один визуальный объект — кластер. Вместо отображения сотен или тысяч отдельных точек карта показывает компактные группы с числовым индикатором количества объектов.
По мере изменения масштаба карты кластеры автоматически разделяются на более мелкие группы или отдельные маркеры.
Предположим, на карте отображаются 500 ресторанов в пределах крупного города. На небольшом масштабе все точки расположены очень близко друг к другу.
Без кластеризации карта выглядит перегруженной:
● ● ● ● ● ● ● ●
● ● ● ● ● ● ● ●
● ● ● ● ● ● ● ●
После кластеризации отображаются группы:
[120] [85]
[60]
[140] [95]
Каждый кластер содержит информацию о количестве маркеров внутри него.
При увеличении масштаба:
[120]
↓
[50] [40] [30]
После дальнейшего увеличения масштаба появляются уже отдельные маркеры.
Для работы с кластерами в современных версиях Google Maps JavaScript API используется библиотека:
@googlemaps/markerclusterer
Установка через npm:
npm install @googlemaps/markerclusterer
Импорт:
import { MarkerClusterer } from "@googlemaps/markerclusterer";
При использовании через CDN:
<script
src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js">
</script>
Базовая настройка карты:
const map = new google.maps.Map(
document.getElementById("map"),
{
center: { lat: 55.7558, lng: 37.6176 },
zoom: 10
}
);
Кластеризатор работает с коллекцией объектов Marker.
Пример создания массива:
const locations = [
{ lat: 55.751, lng: 37.618 },
{ lat: 55.752, lng: 37.619 },
{ lat: 55.753, lng: 37.620 },
{ lat: 55.754, lng: 37.621 }
];
const markers = locations.map(location => {
return new google.maps.Marker({
position: location
});
});
Обратите внимание, что маркеры не обязательно сразу добавлять на карту.
После подготовки массива маркеров создаётся экземпляр
MarkerClusterer.
const markerCluster = new MarkerClusterer({
map,
markers
});
Полный пример:
const map = new google.maps.Map(
document.getElementById("map"),
{
center: { lat: 55.7558, lng: 37.6176 },
zoom: 10
}
);
const markers = locations.map(position => {
return new google.maps.Marker({
position
});
});
new MarkerClusterer({
map,
markers
});
После выполнения кода близкие точки автоматически объединяются в группы.
На практике координаты часто поступают из базы данных или внешнего API.
Для тестирования можно создать случайные точки.
const markers = [];
for (let i = 0; i < 1000; i++) {
const marker = new google.maps.Marker({
position: {
lat: 55.75 + (Math.random() - 0.5) * 0.3,
lng: 37.61 + (Math.random() - 0.5) * 0.3
}
});
markers.push(marker);
}
new MarkerClusterer({
map,
markers
});
Даже при тысяче объектов производительность остаётся значительно выше по сравнению с отображением каждого маркера отдельно.
Современная библиотека поддерживает различные алгоритмы формирования кластеров.
По умолчанию используется:
SuperClusterAlgorithm
Он основан на пространственной индексации и эффективно работает с десятками тысяч точек.
Настройка алгоритма:
import {
MarkerClusterer,
SuperClusterAlgorithm
} from "@googlemaps/markerclusterer";
const clusterer = new MarkerClusterer({
map,
markers,
algorithm: new SuperClusterAlgorithm()
});
Одним из важнейших параметров является радиус объединения.
const clusterer = new MarkerClusterer({
map,
markers,
algorithm: new SuperClusterAlgorithm({
radius: 100
})
});
Параметр:
radius
определяет расстояние в пикселях, внутри которого точки считаются частью одного кластера.
radius: 40
Особенности:
radius: 120
Особенности:
Иногда требуется отключить кластеризацию после определённого уровня приближения.
const clusterer = new MarkerClusterer({
map,
markers,
algorithm: new SuperClusterAlgorithm({
maxZoom: 15
})
});
После достижения масштаба 15 и выше пользователь увидит отдельные маркеры.
Кластеризатор позволяет добавлять объекты после создания.
Добавление одного маркера:
const marker = new google.maps.Marker({
position: {
lat: 55.76,
lng: 37.63
}
});
clusterer.addMarker(marker);
Добавление массива:
clusterer.addMarkers([
marker1,
marker2,
marker3
]);
Удаление конкретного объекта:
clusterer.removeMarker(marker);
Удаление нескольких объектов:
clusterer.removeMarkers([
marker1,
marker2
]);
Полная очистка:
clusterer.clearMarkers();
После изменения набора маркеров иногда требуется пересчитать кластеры.
clusterer.render();
Метод инициирует повторное построение всех групп.
По умолчанию используются стандартные круглые значки.
Для полного контроля применяется собственный рендерер.
const renderer = {
render({ count, position }) {
return new google.maps.Marker({
position,
label: {
text: String(count),
color: "#ffffff",
fontSize: "14px"
}
});
}
};
Подключение:
new MarkerClusterer({
map,
markers,
renderer
});
Современный подход предполагает использование SVG.
const renderer = {
render({ count, position }) {
const svg = `
<svg width="60" height="60"
xmlns="http://www.w3.org/2000/svg">
<circle
cx="30"
cy="30"
r="25"
fill="#1976d2"/>
<text
x="30"
y="35"
text-anchor="middle"
fill="white"
font-size="16">
${count}
</text>
</svg>
`;
return new google.maps.Marker({
position,
icon: {
url:
"data:image/svg+xml;base64," +
btoa(svg),
scaledSize:
new google.maps.Size(60, 60)
}
});
}
};
Преимущества SVG:
Количество объектов внутри кластера может влиять на его внешний вид.
render({ count, position }) {
let size = 40;
if (count > 50) {
size = 60;
}
if (count > 200) {
size = 80;
}
return new google.maps.Marker({
position,
icon: {
url: createClusterIcon(size, count),
scaledSize:
new google.maps.Size(size, size)
}
});
}
Подобный подход позволяет визуально выделять наиболее плотные скопления объектов.
Кластер может реагировать на действия пользователя.
Пример увеличения масштаба:
clusterer.addListener(
"click",
event => {
map.setZoom(
map.getZoom() + 2
);
map.panTo(
event.latLng
);
}
);
Часто используется сценарий автоматического перехода к области, содержащей кластер.
В обработчике доступны данные о группе маркеров.
clusterer.addListener(
"click",
({ cluster }) => {
console.log(
cluster.markers.length
);
}
);
Результат:
153
Количество объектов внутри выбранного кластера.
В новых проектах рекомендуется использовать современный класс:
google.maps.marker.AdvancedMarkerElement
Создание маркеров:
const markers = locations.map(position => {
return new google.maps.marker
.AdvancedMarkerElement({
position
});
});
Кластеризатор поддерживает работу с такими объектами аналогично обычным маркерам.
Типичный пример применения — карта недвижимости.
Структура данных:
[
{
id: 1,
price: 120000,
lat: 55.75,
lng: 37.61
},
{
id: 2,
price: 150000,
lat: 55.74,
lng: 37.63
}
]
Создание маркеров:
const markers = properties.map(item => {
return new google.maps.Marker({
position: {
lat: item.lat,
lng: item.lng
}
});
});
Затем все объекты объединяются:
new MarkerClusterer({
map,
markers
});
На небольшом масштабе пользователь видит распределение объектов по районам, а при увеличении получает доступ к отдельным предложениям.
Логистические системы часто работают с тысячами адресов.
Преимущества кластеризации:
Для карт служб доставки кластеризация является практически обязательным инструментом.
Рекомендации при работе с десятками тысяч маркеров:
algorithm: new SuperClusterAlgorithm()
Наиболее эффективный вариант для крупных наборов данных.
Вместо загрузки всех точек:
100000 объектов
целесообразно получать данные только для текущей области карты.
map.addListener("idle", loadMarkers);
maxZoom: 14
Позволяет избежать отображения огромного количества объектов одновременно.
Современные маркеры лучше интегрированы с текущими версиями Google Maps API и обеспечивают более гибкую настройку интерфейса.
Нежелательный вариант:
new google.maps.Marker({
position,
map
});
Затем тот же объект передаётся кластеризатору.
Это приводит к лишним операциям отображения.
Предпочтительно:
new google.maps.Marker({
position
});
После чего передать массив в кластеризатор.
radius: 300
В результате карта теряет детализацию и объединяет удалённые объекты.
Даже кластеризация не решает проблему бесконечного роста объёма данных. При работе с очень крупными наборами рекомендуется серверная фильтрация по текущему окну карты.
Нежелательно создавать новый экземпляр при каждом обновлении данных:
new MarkerClusterer(...)
Гораздо эффективнее обновлять существующий объект:
clusterer.clearMarkers();
clusterer.addMarkers(markers);
clusterer.render();
Типичная последовательность выглядит следующим образом:
Источник данных
↓
Получение координат
↓
Создание маркеров
↓
MarkerClusterer
↓
Алгоритм группировки
↓
Кластеры
↓
Отображение на карте
Такой подход позволяет эффективно отображать от сотен до десятков тысяч географических объектов, сохраняя высокую производительность интерфейса и удобство взаимодействия с картой на любых уровнях масштабирования.