Кластеризация объектов

Кластеризация в CesiumJS применяется для оптимизации отображения большого количества точечных объектов на глобусе. При увеличении плотности данных отдельные сущности объединяются в группы (кластеры), что снижает нагрузку на рендеринг и улучшает читаемость сцены.

Основной механизм кластеризации реализован на уровне DataSource и применяется к сущностям (Entity), добавленным через GeoJsonDataSource, KmlDataSource или пользовательские источники данных.


Основные принципы кластеризации

Кластеризация основана на объединении объектов, попадающих в один экранный радиус в пикселях. Cesium использует параметр экранного расстояния, а не географическое, что обеспечивает стабильность визуального результата при разных уровнях масштабирования.

Ключевые характеристики:

  • объединение объектов происходит в экранном пространстве
  • кластеры динамически пересчитываются при изменении камеры
  • поведение регулируется параметрами Clustering
  • результатом является временная замена набора сущностей одним кластерным объектом

Включение кластеризации в DataSource

Кластеризация активируется через объект clustering, доступный у любого DataSource.

const dataSource = new Cesium.GeoJsonDataSource();

viewer.dataSources.add(dataSource);

dataSource.clustering.enabled = true;
dataSource.clustering.pixelRange = 50;
dataSource.clustering.minimumClusterSize = 3;

Основные параметры clustering

enabled

  • включает или отключает кластеризацию
  • тип: boolean

pixelRange

  • радиус объединения объектов в пикселях
  • увеличение значения приводит к более крупным кластерам

minimumClusterSize

  • минимальное количество объектов для формирования кластера
  • если объектов меньше — кластер не создаётся

Поведение кластеров в сцене

При включенной кластеризации Cesium автоматически:

  • заменяет группу сущностей одним кластерным объектом
  • создаёт динамический billboard и label
  • обновляет состояние при движении камеры
  • восстанавливает исходные объекты при увеличении масштаба

Кластер не является отдельной сущностью сцены, а представляет собой временную визуализацию.


Настройка визуального представления кластеров

Cesium предоставляет событие clusterEvent, позволяющее полностью управлять внешним видом кластеров.

const clustering = dataSource.clustering;

clustering.clusterEvent.addEventListener(function(
  clusteredEntities,
  cluster
) {
  cluster.label.show = true;
  cluster.label.text = clusteredEntities.length.toString();
  cluster.billboard.show = true;
});

Кастомизация внешнего вида кластеров

Изменение иконки кластера

clustering.clusterEvent.addEventListener(function(entities, cluster) {
  cluster.billboard.image = '/images/cluster.png';
  cluster.billboard.scale = 0.8;
});

Возможна генерация иконки в зависимости от размера кластера:

clustering.clusterEvent.addEventListener(function(entities, cluster) {
  const count = entities.length;

  if (count > 50) {
    cluster.billboard.image = '/icons/cluster-large.png';
  } else if (count > 10) {
    cluster.billboard.image = '/icons/cluster-medium.png';
  } else {
    cluster.billboard.image = '/icons/cluster-small.png';
  }
});

Использование HTML-Canvas для динамических иконок

Cesium позволяет создавать изображения кластеров на лету:

function createClusterCanvas(count) {
  const canvas = document.createElement('canvas');
  canvas.width = 64;
  canvas.height = 64;

  const ctx = canvas.getContext('2d');

  ctx.fillStyle = '#2E86AB';
  ctx.beginPath();
  ctx.arc(32, 32, 28, 0, Math.PI * 2);
  ctx.fill();

  ctx.fillStyle = '#FFFFFF';
  ctx.font = 'bold 20px sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(count, 32, 32);

  return canvas;
}

clustering.clusterEvent.addEventListener(function(entities, cluster) {
  cluster.billboard.image = createClusterCanvas(entities.length);
});

Работа с текстовыми метками кластеров

Label внутри кластера используется для отображения количества объектов или дополнительной информации.

clustering.clusterEvent.addEventListener(function(entities, cluster) {
  cluster.label.text = `${entities.length}`;
  cluster.label.font = 'bold 14px sans-serif';
  cluster.label.fillColor = Cesium.Color.WHITE;
  cluster.label.style = Cesium.LabelStyle.FILL_AND_OUTLINE;
});

Событие разбиения кластеров

При изменении масштаба кластеры распадаются автоматически, но можно отследить процесс через повторное срабатывание clusterEvent и изменение состава массива clusteredEntities.

Структура событий:

  • объединение → clusterEvent
  • разделение → повторный пересчёт кластеров

Применение кластеризации в GeoJSON

GeoJSON является наиболее частым источником точек для кластеризации.

const geoJson = new Cesium.GeoJsonDataSource();

viewer.dataSources.add(geoJson);

geoJson.load('points.geojson').then(function() {
  geoJson.clustering.enabled = true;
  geoJson.clustering.pixelRange = 60;
});

Каждая точка GeoJSON автоматически превращается в Entity, участвующий в кластеризации.


Кластеризация KML-данных

Для KML используется аналогичный механизм:

const kmlDataSource = new Cesium.KmlDataSource();

viewer.dataSources.add(kmlDataSource);

kmlDataSource.load('data.kml').then(function() {
  kmlDataSource.clustering.enabled = true;
  kmlDataSource.clustering.pixelRange = 40;
});

KML-структуры с большим количеством placemark особенно выигрывают от кластеризации.


Поведение при изменении камеры

Кластеризация тесно связана с камерой Viewer:

  • при увеличении масштаба кластеры уменьшаются
  • при уменьшении масштаба объединяются
  • пересчёт происходит автоматически при каждом движении камеры

Параметры камеры, влияющие на кластеризацию:

  • высота камеры
  • угол наклона
  • уровень zoom

Производительность и оптимизация

Кластеризация является ключевым инструментом оптимизации при работе с тысячами объектов.

Эффект оптимизации достигается за счёт:

  • уменьшения количества draw calls
  • снижения количества billboard-объектов
  • сокращения работы GPU при рендеринге

Рекомендуемые настройки:

dataSource.clustering.pixelRange = 50;
dataSource.clustering.minimumClusterSize = 5;

При слишком большом pixelRange:

  • ухудшается точность представления данных
  • теряется детализация распределения объектов

При слишком маленьком:

  • увеличивается нагрузка на рендеринг
  • кластеры практически не формируются

Управление видимостью объектов внутри кластера

Cesium автоматически скрывает отдельные сущности, но их состояние сохраняется.

Можно дополнительно управлять логикой:

clustering.clusterEvent.addEventListener(function(entities, cluster) {
  entities.forEach(function(entity) {
    entity.show = false;
  });

  cluster.label.text = String(entities.length);
});

Пользовательская логика кластеризации

Хотя встроенная кластеризация основана на пиксельном радиусе, можно реализовать расширенную логику:

  • кластеризация по типу объекта
  • кластеризация по атрибутам данных
  • кластеризация по уровню важности

Пример условной фильтрации:

clustering.clusterEvent.addEventListener(function(entities, cluster) {
  const highPriority = entities.filter(e => e.properties.priority === 1);

  cluster.label.text = `${highPriority.length}/${entities.length}`;
});

Ограничения механизма кластеризации

  • работает только с Entity-уровнем данных
  • не применяется напрямую к Primitive
  • не учитывает семантические связи между объектами
  • зависит от экранного разрешения и масштаба камеры

Типичные сценарии применения

  • отображение точек наблюдения
  • визуализация геолокационных событий
  • отображение объектов IoT
  • анализ плотности данных
  • визуализация пользовательских меток на глобусе

Поведение при динамическом обновлении данных

При добавлении новых сущностей кластеризация пересчитывается автоматически:

dataSource.entities.add({
  position: Cesium.Cartesian3.fromDegrees(30, 50),
  point: { pixelSize: 10 }
});

Новые объекты немедленно участвуют в существующей кластерной структуре без дополнительных вызовов обновления.