Выделение объектов

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

В CesiumJS выделение обычно реализуется через:

  • определение объекта под курсором;
  • выбор объекта по щелчку мыши;
  • визуальное изменение выбранного элемента;
  • отображение описания и атрибутов;
  • снятие выделения;
  • поддержку множественного выбора.

Механизм выделения применяется в системах мониторинга, цифровых двойниках, военных ГИС, навигационных сервисах и инженерных платформах.


Обнаружение объекта под курсором

Для определения объекта под указателем используется метод scene.pick().

const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);

handler.setInputAction(function(movement) {

    const pickedObject = viewer.scene.pick(movement.endPosition);

    if (Cesium.defined(pickedObject)) {
        console.log("Объект найден");
    }

}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

Метод анализирует содержимое сцены в указанной экранной точке и возвращает найденный объект.

Параметр:

movement.endPosition

содержит координаты курсора в пикселях относительно окна визуализации.


Выбор объекта по щелчку

Наиболее распространённый сценарий — выбор объекта левым кликом.

handler.setInputAction(function(click) {

    const pickedObject = viewer.scene.pick(click.position);

    if (Cesium.defined(pickedObject)) {
        console.log("Объект выбран");
    }

}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

После выполнения pick() можно определить, какой именно объект был выбран.


Получение сущности Entity

Если объект создан через API сущностей, доступ к нему можно получить через свойство id.

handler.setInputAction(function(click) {

    const pickedObject = viewer.scene.pick(click.position);

    if (Cesium.defined(pickedObject)) {

        const entity = pickedObject.id;

        console.log(entity.name);
    }

}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Пример создания сущности:

const city = viewer.entities.add({
    name: "Москва",
    position: Cesium.Cartesian3.fromDegrees(37.6176, 55.7558),
    point: {
        pixelSize: 12,
        color: Cesium.Color.RED
    }
});

После выбора можно получить:

entity.name
entity.position
entity.description
entity.properties

Использование свойства selectedEntity

Viewer предоставляет встроенный механизм выбора.

viewer.selectedEntity = entity;

После присвоения:

  • объект становится текущим выбранным;
  • отображается информационная карточка;
  • активируются внутренние механизмы Cesium.

Пример:

handler.setInputAction(function(click) {

    const pickedObject = viewer.scene.pick(click.position);

    if (Cesium.defined(pickedObject)) {
        viewer.selectedEntity = pickedObject.id;
    }

}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Информационная карточка объекта

Для отображения информации используется свойство description.

viewer.entities.add({
    name: "Башня",
    description: `
        <h2>Башня связи</h2>
        <p>Высота: 120 м</p>
        <p>Год постройки: 2015</p>
    `,
    position: position,
    point: {
        pixelSize: 10
    }
});

После выбора объект автоматически отображается в панели информации.


Изменение цвета выбранного объекта

Один из самых распространённых способов выделения — изменение цвета.

Исходный объект:

const building = viewer.entities.add({
    polygon: {
        hierarchy: positions,
        material: Cesium.Color.BLUE
    }
});

Выделение:

building.polygon.material = Cesium.Color.YELLOW;

Снятие выделения:

building.polygon.material = Cesium.Color.BLUE;

Сохранение предыдущего выбора

При выборе нового объекта обычно требуется восстановить внешний вид предыдущего.

let selectedEntity = null;
handler.setInputAction(function(click) {

    const picked = viewer.scene.pick(click.position);

    if (!Cesium.defined(picked)) {
        return;
    }

    if (selectedEntity) {
        selectedEntity.point.color =
            Cesium.Color.RED;
    }

    selectedEntity = picked.id;

    selectedEntity.point.color =
        Cesium.Color.YELLOW;

}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

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


Выделение точек

Для точечных объектов часто изменяются:

  • цвет;
  • размер;
  • контур.

Пример:

entity.point.color = Cesium.Color.YELLOW;

entity.point.pixelSize = 20;

entity.point.outlineColor =
    Cesium.Color.BLACK;

entity.point.outlineWidth = 3;

До выбора:

point: {
    pixelSize: 10,
    color: Cesium.Color.RED
}

После выбора:

point: {
    pixelSize: 20,
    color: Cesium.Color.YELLOW,
    outlineColor: Cesium.Color.BLACK,
    outlineWidth: 3
}

Выделение полилиний

Для линий удобно изменять толщину и цвет.

Исходное состояние:

polyline: {
    positions: positions,
    width: 3,
    material: Cesium.Color.BLUE
}

После выбора:

entity.polyline.width = 8;

entity.polyline.material =
    Cesium.Color.YELLOW;

Это делает выбранный маршрут или коммуникацию значительно заметнее.


Выделение полигонов

Для полигонов обычно используются:

  • изменение цвета заливки;
  • изменение прозрачности;
  • отображение контура.

Пример:

entity.polygon.material =
    Cesium.Color.YELLOW.withAlpha(0.7);

Добавление контура:

entity.polygon.outline = true;

entity.polygon.outlineColor =
    Cesium.Color.BLACK;

Подсветка объекта при наведении

Выделение не всегда связано с выбором. Часто используется режим наведения курсора.

let highlightedEntity = null;
handler.setInputAction(function(movement) {

    if (highlightedEntity) {

        highlightedEntity.point.color =
            Cesium.Color.RED;

        highlightedEntity = null;
    }

    const picked =
        viewer.scene.pick(movement.endPosition);

    if (Cesium.defined(picked)) {

        highlightedEntity = picked.id;

        highlightedEntity.point.color =
            Cesium.Color.YELLOW;
    }

}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

Подобное поведение напоминает интерфейсы настольных ГИС.


Выделение 3D Tiles

Для наборов 3D Tiles применяется иной подход.

const pickedFeature =
    viewer.scene.pick(click.position);

Если найден объект тайлов:

if (pickedFeature instanceof Cesium.Cesium3DTileFeature) {

}

Подсветка выполняется через свойство color.

pickedFeature.color =
    Cesium.Color.YELLOW;

Пример:

handler.setInputAction(function(click) {

    const feature =
        viewer.scene.pick(click.position);

    if (feature instanceof Cesium.Cesium3DTileFeature) {

        feature.color =
            Cesium.Color.YELLOW;
    }

}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Восстановление цвета 3D Tiles

Необходимо сохранять исходное состояние.

let selectedFeature;
let originalColor;

Выбор:

if (selectedFeature) {
    selectedFeature.color = originalColor;
}

Сохранение цвета:

selectedFeature = feature;

originalColor =
    Cesium.Color.clone(feature.color);

Новое выделение:

feature.color =
    Cesium.Color.YELLOW;

Использование silhouette-подсветки

Cesium поддерживает постобработку контуров.

Создание стадии:

const silhouette =
    Cesium.PostProcessStageLibrary
        .createEdgeDetectionStage();

Настройка:

silhouette.uniforms.color =
    Cesium.Color.YELLOW;

silhouette.uniforms.length = 0.02;

Добавление:

viewer.scene.postProcessStages
    .add(silhouette);

Назначение объектов:

silhouette.selected = [feature];

Результат — вокруг объекта появляется яркий контур.


Выделение через силуэт выбранной сущности

Специальная стадия:

const silhouetteStage =
    Cesium.PostProcessStageLibrary
        .createSilhouetteStage();

Подключение:

viewer.scene.postProcessStages
    .add(silhouetteStage);

Выбор объекта:

silhouetteStage.selected = [pickedObject];

Подобный вариант особенно полезен для сложных моделей.


Работа с Model

Для моделей формата glTF выбор производится аналогично.

Создание модели:

viewer.entities.add({
    position: position,
    model: {
        uri: "model.glb"
    }
});

Выбор:

const picked =
    viewer.scene.pick(click.position);

Получение сущности:

const entity = picked.id;

Выделение:

entity.model.color =
    Cesium.Color.YELLOW;

Настройка прозрачности:

entity.model.color =
    Cesium.Color.YELLOW.withAlpha(0.8);

Использование модели с цветовым смешиванием

Для более заметной подсветки применяется режим смешивания.

entity.model.colorBlendMode =
    Cesium.ColorBlendMode.MIX;

Интенсивность:

entity.model.colorBlendAmount = 0.8;

Цвет выделения:

entity.model.color =
    Cesium.Color.YELLOW;

Множественный выбор объектов

Иногда требуется выбрать сразу несколько объектов.

Хранилище:

const selectedObjects = [];

Добавление:

selectedObjects.push(entity);

Выделение:

entity.point.color =
    Cesium.Color.YELLOW;

Снятие выделения со всех объектов:

selectedObjects.forEach(entity => {

    entity.point.color =
        Cesium.Color.RED;

});

Очистка:

selectedObjects.length = 0;

Выделение области рамкой

Для выбора группы объектов применяется прямоугольная область.

Общая схема:

  1. Пользователь нажимает кнопку мыши.
  2. Запоминается стартовая точка.
  3. Выполняется растягивание рамки.
  4. Определяются попавшие внутрь объекты.
  5. Производится массовое выделение.

Экранные координаты объектов вычисляются через:

Cesium.SceneTransforms
    .wgs84ToWindowCoordinates(
        viewer.scene,
        position
    );

После чего проверяется попадание точки внутрь прямоугольника.


Выделение через пользовательские свойства

Часто требуется хранить состояние выбора непосредственно внутри объекта.

entity.properties = new Cesium.PropertyBag({
    selected: false
});

При выборе:

entity.properties.selected = true;

Проверка:

if (entity.properties.selected) {

}

Такой подход упрощает организацию логики больших приложений.


Обработка снятия выделения

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

handler.setInputAction(function(click) {

    const picked =
        viewer.scene.pick(click.position);

    if (!Cesium.defined(picked)) {

        viewer.selectedEntity = null;

        return;
    }

}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Очистка пользовательской подсветки:

if (selectedEntity) {

    selectedEntity.point.color =
        Cesium.Color.RED;

    selectedEntity = null;
}

Производительность при большом количестве объектов

При работе с десятками тысяч объектов необходимо учитывать ряд особенностей:

  • избегать массового изменения материалов каждый кадр;
  • использовать выделение только для активных объектов;
  • минимизировать количество операций pick();
  • применять 3D Tiles для крупных сцен;
  • использовать постобработку вместо изменения геометрии;
  • кэшировать ссылки на выбранные объекты;
  • избегать перебора всех сущностей при каждом событии мыши.

Особенно эффективно использование контурной постобработки, поскольку визуальное выделение создаётся средствами графического процессора без изменения самих данных сцены.


Архитектура системы выделения

Типичная структура крупного приложения включает следующие компоненты:

SelectionManager

  • хранение выбранных объектов;
  • переключение состояния выбора;
  • очистка выбора.

HighlightManager

  • визуальная подсветка;
  • изменение материалов;
  • управление контурами.

PickController

  • обработка мыши;
  • выполнение scene.pick();
  • маршрутизация событий.

InfoPanel

  • отображение атрибутов;
  • вывод описания;
  • работа с метаданными объекта.

Подобная архитектура позволяет одинаково работать с сущностями, моделями glTF, примитивами и объектами 3D Tiles, обеспечивая единый механизм выделения во всём приложении.