Кластеризация маркеров
Если в какой-то области карты сосредоточено большое количество маркеров, они могут отвлекать внимание и закрывать собой другие объекты карты. Чтобы избавиться от визуального шума, можно объединять маркеры в один объект (кластер) при уменьшении масштаба карты.
Чтобы использовать кластеры, нужно подключить плагин Clusterer
. Для этого нужно добавить следующую строку после подключения основного скрипта:
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
Также можно установить плагин с помощью npm:
npm install @2gis/mapgl-clusterer
Использование
Чтобы создать кластер, сначала нужно инициализировать объект Clusterer:
const clusterer = new mapgl.Clusterer(map, {
radius: 60,
});
В случае использования npm:
// Импортируйте плагин как ES-модуль...
import { Clusterer } from '@2gis/mapgl-clusterer';
// ...или как модуль CommonJS
const { Clusterer } = require('@2gis/mapgl-clusterer');
const clusterer = new Clusterer(map, {
radius: 60,
});
Параметр radius
определяет расстояние в пикселях. Если расстояние между маркерами меньше указанного значения, маркеры будут объединены в один объект.
Чтобы добавить маркеры в кластер, их не нужно создавать отдельно. Вместо этого нужно вызвать метод кластера load()
и указать массив с настройками маркеров (нужное количество объектов InputMarker). Например, чтобы создать кластер из трёх маркеров, можно использовать следующий код:
const markers = [
{ coordinates: [55.27887, 25.21001] },
{ coordinates: [55.30771, 25.20314] },
{ coordinates: [55.35266, 25.24382] },
];
clusterer.load(markers);
На карте ниже показан пример кластера из трёх маркеров. Чем меньше масштаб карты, тем меньше отдельных маркеров будет видно на карте.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="Default markers in cluster example" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 10.5,
key: 'Your API access key',
});
const markers = [
{ coordinates: [55.27887, 25.21001] },
{ coordinates: [55.30771, 25.20314] },
{ coordinates: [55.35266, 25.24382] },
];
const clusterer = new mapgl.Clusterer(map);
clusterer.load(markers);
</script>
</body>
</html>
Чтобы изменить набор маркеров в существующем кластере, нужно вызвать метод load()
повторно, указав новый массив с настройками маркеров.
Чтобы удалить кластер, нужно вызвать метод destroy()
. При удалении кластера также удаляются все входящие в него маркеры.
clusterer.destroy();
События
Чтобы добавить обработчик событий для кластера, нужно вызвать метод on()
. Полный список поддерживаемых событий можно найти в Справочнике API.
Например, подписаться на событие нажатия на кластер можно с помощью следующего кода:
clusterer.on('click', (event) => {
alert(`click`);
});
Функция-обработчик будет вызвана как при нажатии на кластер, так и при нажатии на отдельный маркер из этого кластера. Чтобы отличить нажатие на кластер от нажатия на маркер, можно использовать свойство target
объекта события (ClustererPointerEvent):
- Если пользователь нажал на кластер,
target.type
будет содержать строку "cluster", а вtarget.data
будет указана информация о маркерах в этом кластере (массив объектов InputMarker). - Если пользователь нажал на отдельный маркер,
target.type
будет содержать строку "marker", а вtarget.data
будет указана информация об этом маркере (объект InputMarker).
clusterer.on('click', (event) => {
alert(`${event.target.type} is clicked`);
});
Попробуйте нажать на кластер и на отдельный маркер в примере ниже.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="Cluster event handling example" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 10.5,
key: 'Your API access key',
});
const markers = [
{ coordinates: [55.27887, 25.21001] },
{ coordinates: [55.30771, 25.20314] },
{ coordinates: [55.35266, 25.24382] },
];
const clusterer = new mapgl.Clusterer(map);
clusterer.load(markers);
clusterer.on('click', (event) => {
alert(`${event.target.type} is clicked`);
});
</script>
</body>
</html>
Внешний вид маркера
Маркерам в составе кластера можно задать настройки внешнего вида так же, как и обычным маркерам. Настройки нужно указать в виде объекта InputMarker при вызове метода load()
.
const markers = [
{
coordinates: [55.27887, 25.21001],
icon: 'https://docs.2gis.com/img/mapgl/marker.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/markerHover.svg',
size: [36, 36],
hoverSize: [46, 46],
},
{
coordinates: [55.30771, 25.20314],
icon: 'https://docs.2gis.com/img/mapgl/marker.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/marker.svg',
size: [42, 42],
hoverSize: [48, 48],
},
{
coordinates: [55.35266, 25.24382],
icon: 'https://docs.2gis.com/img/mapgl/marker.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/markerHover.svg',
size: [44, 44],
hoverSize: [50, 50],
},
];
clusterer.load(markers);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="Custom markers in cluster example" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.31395, 25.22171],
zoom: 11,
key: 'Your API access key',
});
const markers = [
{
coordinates: [55.27887, 25.21001],
icon: 'https://docs.2gis.com/img/mapgl/marker.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/markerHover.svg',
size: [36, 36],
hoverSize: [46, 46],
},
{
coordinates: [55.30771, 25.20314],
icon: 'https://docs.2gis.com/img/mapgl/marker.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/marker.svg',
size: [42, 42],
hoverSize: [48, 48],
},
{
coordinates: [55.35266, 25.24382],
icon: 'https://docs.2gis.com/img/mapgl/marker.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/markerHover.svg',
size: [44, 44],
hoverSize: [50, 50],
label: {
relativeAnchor: [0, 0.5],
offset: [20, 0],
color: '#00f',
haloColor: '#ffffff',
haloRadius: 1,
fontSize: 12,
text: 'Dubai Airport'
}
},
];
const clusterer = new mapgl.Clusterer(map);
clusterer.load(markers);
</script>
</body>
</html>
Внешний вид кластера
Чтобы изменить внешний вид кластера, нужно указать параметр clusterStyle
. С помощью этого параметра можно задать иконку, цвет текста, размер текста и другие настройки (см. ClusterStyle).
const clusterer = new mapgl.Clusterer(map, {
clusterStyle: {
icon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
labelColor: '#ffffff',
labelFontSize: 16,
},
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="Cluster object style example" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 10.5,
key: 'Your API access key',
});
const markers = [
{ coordinates: [55.27887, 25.21001] },
{ coordinates: [55.30771, 25.20314] },
{ coordinates: [55.35266, 25.24382] },
];
const clusterer = new mapgl.Clusterer(map, {
clusterStyle: {
icon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
labelColor: '#ffffff',
labelFontSize: 16,
},
});
clusterer.load(markers);
</script>
</body>
</html>
Также можно использовать разные стили в зависимости от количества маркеров в кластере. Для этого в качестве значения параметра clusterStyle
нужно указать функцию, которая будет возвращать нужный стиль на основании первого аргумента (количества маркеров).
function clusterStyle(pointsCount) {
if (pointsCount < 3) {
return {
icon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
labelFontSize: 12,
};
}
return {
icon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
size: [35, 35],
hoverSize: [45, 45],
labelColor: '#ffffff',
labelFontSize: 16,
};
}
const clusterer = new mapgl.Clusterer(map, {
clusterStyle,
});
Попробуйте изменить масштаб карты в примере ниже, чтобы увидеть переключение стилей.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="Cluster function style example" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 10.5,
key: 'Your API access key',
});
const markers = [
{ coordinates: [55.27887, 25.21001] },
{ coordinates: [55.30771, 25.20314] },
{ coordinates: [55.35266, 25.24382] },
];
function clusterStyle(pointsCount) {
if (pointsCount < 3) {
return {
icon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
labelFontSize: 12,
};
}
return {
icon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
size: [35, 35],
hoverSize: [45, 45],
labelColor: '#ffffff',
labelFontSize: 16,
};
}
const clusterer = new mapgl.Clusterer(map, {
clusterStyle,
});
clusterer.load(markers);
</script>
</body>
</html>
Кроме количества маркеров, для переключения стилей можно использовать свойства маркеров. Для этого можно использовать второй аргумент функции - массив объектов ClusterTarget.
Например, можно изменить стиль, если у всех маркеров в кластере одинаковые координаты:
function clusterStyle(pointsCount, target) {
// Получаем информацию о маркерах
const points = target.data;
// Проверяем координаты маркеров
const divisible = points.some((point, index) => {
if (index > 0) {
const prevPoint = points[index - 1];
if (
point.coordinates[0] != prevPoint.coordinates[0] ||
point.coordinates[1] != prevPoint.coordinates[1]
) {
return true;
}
}
});
// Если у всех маркеров одинаковые координаты, меняем иконку кластера на красную
if (!divisible) {
return {
icon: '/img/mapgl/clusterHover.svg',
hoverIcon: '/img/mapgl/clusterHover.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
};
}
return {
icon: '/img/mapgl/cluster.svg',
hoverIcon: '/img/mapgl/cluster.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
};
}
const clusterer = new mapgl.Clusterer(map, {
clusterStyle,
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="Clusters customization with data example" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 10.5,
key: 'Your API access key',
});
const markers = [
{ coordinates: [55.27887, 25.21001] },
{ coordinates: [55.30771, 25.20314] },
{ coordinates: [55.35266, 25.24382] },
{ coordinates: [55.35266, 25.24382] },
{ coordinates: [55.35266, 25.24382] },
];
function clusterStyle(pointsCount, target) {
const points = target.data;
// determine that points have different coordinates
const divisible = points.some((point, index) => {
if (index > 0) {
const prevPoint = points[index - 1];
if (
point.coordinates[0] != prevPoint.coordinates[0] ||
point.coordinates[1] != prevPoint.coordinates[1]
)
return true;
}
});
// if all points have the same coordinates - make it red
if (!divisible) {
return {
icon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
};
}
return {
icon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
};
}
const clusterer = new mapgl.Clusterer(map, {
clusterStyle,
});
clusterer.load(markers);
</script>
</body>
</html>
Произвольные данные
Для маркеров можно указать произвольные данные при помощи поля userData
. Эти данные затем можно использовать, например, в обработчиках событий.
const markers = [
{ coordinates: [55.27887, 25.21001], userData: 1 },
{ coordinates: [55.30771, 25.20314], userData: 2 },
{ coordinates: [55.35266, 25.24382], userData: 3 },
];
function clusterStyle(pointsCount, target) {
// Получаем информацию о маркерах
const points = target.data;
// Добавляем произвольные данные
target.userData = {
// Можно добавлять любые типы данных
foo: { bar: 'baz' },
// Собираем данные из маркеров в кластере
dataFromPoints: points.map((p) => p.userData).join(', '),
};
return {
icon: '/img/mapgl/cluster.svg',
hoverIcon: '/img/mapgl/cluster.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
};
}
const clusterer = new mapgl.Clusterer(map, { clusterStyle });
clusterer.load(markers);
clusterer.on('click', (event) => {
if (event.target.type === 'cluster') {
alert(`Cluster user data: ${JSON.stringify(event.target.userData)}`);
} else {
alert(`Marker user data: ${event.target.data.userData}`);
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="UserData example" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 9.5,
key: 'Your API access key',
});
const markers = [
{ coordinates: [55.27887, 25.21001], userData: 1 },
{ coordinates: [55.30771, 25.20314], userData: 2 },
{ coordinates: [55.35266, 25.24382], userData: 3 },
];
function clusterStyle(pointsCount, target) {
// Get data from markers in the cluster
const points = target.data;
// Add custom data to cluster
target.userData = {
// Any data what you want
foo: { bar: 'baz' },
// Data from markers
dataFromPoints: points.map((p) => p.userData).join(', '),
};
return {
icon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/cluster.svg',
size: [25, 25],
hoverSize: [35, 35],
labelColor: '#ffffff',
};
}
const clusterer = new mapgl.Clusterer(map, { clusterStyle });
clusterer.load(markers);
clusterer.on('click', (event) => {
if (event.target.type === 'cluster') {
alert(`Cluster user data: ${JSON.stringify(event.target.userData)}`);
} else {
alert(`Marker user data: ${event.target.data.userData}`);
}
});
</script>
</body>
</html>
HTML-маркеры
В качестве маркеров и кластеров можно использовать произвольные HTML-элементы. Для этого нужно указать параметр type
со значением "html" и параметр html
с нужной HTML-разметкой.
При этом следует иметь в виду, что HTML-маркеры работают медленнее, чем обычные WebGL-маркеры. Чтобы избежать проблем с производительностью, старайтесь не отображать на экране более 100 HTML-маркеров одновременно. Количество отображаемых маркеров можно уменьшить, если увеличить значение параметра radius
у кластера.
Кластер может содержать оба типа маркеров:
const htmlMarker = document.createElement('div');
htmlMarker.classList.add('marker');
htmlMarker.innerText = 'HTML Marker 2';
const markers = [
{
type: 'html',
coordinates: [55.35266, 25.24382],
html: '<div class="marker">HTML Marker 1</div>',
},
{
type: 'html',
coordinates: [55.27887, 25.21001],
html: htmlMarker,
},
{
type: 'webgl',
coordinates: [55.55459, 25.156798],
},
{
type: 'webgl',
coordinates: [55.30771, 25.20314],
},
];
clusterer.load(markers);
Указать HTML-разметку для самого кластера можно при помощи параметра clusterStyle
:
const clusterer = new mapgl.Clusterer(map, {
clusterStyle: (count) => {
if (count < 4) {
return {
type: 'html',
html: `<div class="cluster">HTML cluster (${count})</div>`,
};
}
return {
type: 'webgl',
labelText: `WebGL cluster (${count})`,
};
},
});
Полный пример
<?doctype html>
<html>
<head>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
.marker, .cluster {
font-family: sans-serif;
font-size: 10px;
padding: 2px;
border-radius: 2px;
border: solid 1px black;
cursor: pointer;
}
.marker {
background: #00ffff;
}
.cluster {
background: #ffff00;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 10.5,
key: 'Your API access key',
});
const htmlMarker = document.createElement('div');
htmlMarker.classList.add('marker');
htmlMarker.innerText = 'HTML Marker 2';
const markers = [{
type: 'html',
coordinates: [55.35266, 25.24382],
html: '<div class="marker">HTML marker №1</div>',
}, {
type: 'html',
coordinates: [55.27887, 25.21001],
html: htmlMarker
}, {
type: 'webgl',
coordinates: [55.55459, 25.156798],
}, {
type: 'webgl',
coordinates: [55.30771, 25.20314],
}];
const clusterer = new mapgl.Clusterer(map, {
clusterStyle: (count) => {
if (count < 4) {
return {
type: 'html',
html: `<div class="cluster">HTML cluster (${count})</div>`
}
}
return {
type: 'webgl',
labelText: `WebGL cluster (${count})`
}
}
});
clusterer.load(markers);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2GIS Map API</title>
<meta name="description" content="Full example for cluster" />
<style>
html,
body,
#container {
margin: 0;
width: 500px;
height: 300px;
overflow: hidden;
}
.marker,
.cluster {
font-family: sans-serif;
font-size: 10px;
padding: 2px;
border-radius: 2px;
border: solid 1px black;
cursor: pointer;
}
.marker {
background: #00ffff;
}
.cluster {
background: #ffff00;
}
</style>
</head>
<body>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script src="https://unpkg.com/@2gis/mapgl-clusterer@^2/dist/clustering.js"></script>
<div id="container"></div>
<script>
const map = new mapgl.Map('container', {
center: [55.323, 25.235],
zoom: 10.5,
key: 'Your API access key',
});
const htmlMarker = document.createElement('div');
htmlMarker.classList.add('marker');
htmlMarker.innerText = 'HTML Marker 2';
const markers = [
{
type: 'html',
coordinates: [55.27887, 25.21001],
html: '<div class="marker">HTML marker №1</div>',
},
{
type: 'html',
coordinates: [55.30771, 25.20314],
html: htmlMarker,
},
{
coordinates: [55.35266, 25.24382],
icon: 'https://docs.2gis.com/img/mapgl/marker.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/markerHover.svg',
size: [44, 44],
hoverSize: [50, 50],
label: {
relativeAnchor: [0, 0.5],
offset: [20, 0],
color: '#00f',
haloColor: '#ffffff',
haloRadius: 1,
fontSize: 12,
text: 'Dubai Airport',
},
},
];
function clusterStyle(pointsCount) {
if (pointsCount < 3) {
return {
type: 'html',
html: `<div class="cluster">HTML cluster (${pointsCount})</div>`,
};
}
return {
icon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
hoverIcon: 'https://docs.2gis.com/img/mapgl/clusterHover.svg',
size: [35, 35],
hoverSize: [45, 45],
labelColor: '#ffffff',
labelFontSize: 16,
};
}
const clusterer = new mapgl.Clusterer(map, {
radius: 60,
clusterStyle,
});
clusterer.load(markers);
clusterer.on('click', (event) => {
alert(`${event.target.type} is clicked`);
});
</script>
</body>
</html>