В интерактивных трёхмерных приложениях недостаточно просто отображать объекты на сцене. Необходимо обеспечивать взаимодействие пользователя с содержимым карты или глобуса: выбор объектов мышью, получение информации о сущностях, подсветку элементов, запуск контекстных действий и обработку событий наведения курсора.
В CesiumJS механизм определения объекта под курсором называется Picking. Он позволяет определить, какой объект находится в указанной точке экрана, а также получить ссылку на связанную сущность, примитив или 3D-модель.
Система Picking используется практически во всех интерактивных геоинформационных приложениях:
При отображении объектов Cesium выполняет преобразование координат через несколько пространств:
Когда пользователь нажимает кнопку мыши, браузер предоставляет координаты курсора в пикселях.
Например:
{
x: 450,
y: 320
}
Cesium использует эти координаты для построения луча, проходящего через камеру и точку сцены. Далее определяется пересечение луча с объектами визуализации.
Координаты мыши обычно извлекаются через объект события.
Пример:
const handler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
handler.setInputAction(function(click) {
console.log(click.position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
Результат:
Cartesian2 {
x: 450,
y: 320
}
Объект Cartesian2 содержит экранные координаты
курсора.
Основной инструмент выбора объектов — метод:
viewer.scene.pick(windowPosition);
Он возвращает объект, находящийся под курсором.
Пример:
handler.setInputAction(function(click) {
const pickedObject =
viewer.scene.pick(click.position);
console.log(pickedObject);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
Если объект найден, будет возвращена структура с информацией о выбранном элементе.
Если под курсором ничего нет:
undefined
Перед использованием результата Picking необходимо выполнять проверку.
const pickedObject =
viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject)) {
console.log("Объект найден");
}
Функция:
Cesium.defined()
является стандартным способом проверки значений в CesiumJS.
Наиболее распространённый сценарий — работа с сущностями.
Создание сущности:
viewer.entities.add({
name: "Город",
position: Cesium.Cartesian3.fromDegrees(
37.6173,
55.7558
),
point: {
pixelSize: 12,
color: Cesium.Color.RED
}
});
Получение сущности:
handler.setInputAction(function(click) {
const picked =
viewer.scene.pick(click.position);
if (Cesium.defined(picked)) {
console.log(picked.id);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
Вывод:
Entity
Свойство:
picked.id
содержит исходную сущность.
После получения объекта можно обращаться к его свойствам.
handler.setInputAction(function(click) {
const picked =
viewer.scene.pick(click.position);
if (Cesium.defined(picked)) {
const entity = picked.id;
console.log(entity.name);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
Результат:
Город
Описание может использоваться для информационных окон.
Создание сущности:
viewer.entities.add({
name: "Москва",
description: "Столица России",
position: Cesium.Cartesian3.fromDegrees(
37.6173,
55.7558
),
point: {
pixelSize: 12
}
});
Получение описания:
const entity = picked.id;
console.log(
entity.description.getValue()
);
Cesium имеет встроенный механизм выбора объекта.
viewer.selectedEntity = entity;
Пример:
handler.setInputAction(function(click) {
const picked =
viewer.scene.pick(click.position);
if (Cesium.defined(picked)) {
viewer.selectedEntity =
picked.id;
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
После выбора автоматически открывается информационная панель Viewer.
Иногда под курсором располагается несколько элементов.
Для таких случаев используется:
viewer.scene.drillPick()
const pickedObjects =
viewer.scene.drillPick(
click.position
);
Результат:
[
object1,
object2,
object3
]
Каждый элемент массива представляет найденный объект.
Можно указать максимальное число объектов.
const pickedObjects =
viewer.scene.drillPick(
click.position,
5
);
Будут возвращены не более пяти элементов.
Для обработки наведения применяется событие:
Cesium.ScreenSpaceEventType.MOUSE_MOVE
Пример:
handler.setInputAction(function(movement) {
const picked =
viewer.scene.pick(
movement.endPosition
);
if (Cesium.defined(picked)) {
console.log("Наведение");
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
Часто требуется визуально показать выбранный элемент.
Создадим сущность:
const city = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(
37.6173,
55.7558
),
point: {
pixelSize: 12,
color: Cesium.Color.YELLOW
}
});
Изменение цвета:
handler.setInputAction(function(movement) {
const picked =
viewer.scene.pick(
movement.endPosition
);
if (
Cesium.defined(picked) &&
picked.id === city
) {
city.point.color =
Cesium.Color.RED;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
Обычно хранится ссылка на ранее выделенный объект.
let highlighted = null;
Пример:
handler.setInputAction(function(movement) {
if (highlighted) {
highlighted.point.color =
Cesium.Color.YELLOW;
highlighted = null;
}
const picked =
viewer.scene.pick(
movement.endPosition
);
if (Cesium.defined(picked)) {
highlighted = picked.id;
highlighted.point.color =
Cesium.Color.RED;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
Обычный pick() определяет объект.
Для получения координат точки используется:
viewer.scene.pickPosition()
const position =
viewer.scene.pickPosition(
click.position
);
Возвращается:
Cartesian3
Пример использования:
if (Cesium.defined(position)) {
console.log(position);
}
Полученный Cartesian3 можно перевести в географические
координаты.
const cartographic =
Cesium.Cartographic.fromCartesian(
position
);
Получение значений:
const longitude =
Cesium.Math.toDegrees(
cartographic.longitude
);
const latitude =
Cesium.Math.toDegrees(
cartographic.latitude
);
handler.setInputAction(function(click) {
const position =
viewer.scene.pickPosition(
click.position
);
if (Cesium.defined(position)) {
const cartographic =
Cesium.Cartographic.fromCartesian(
position
);
console.log(
Cesium.Math.toDegrees(
cartographic.longitude
),
Cesium.Math.toDegrees(
cartographic.latitude
)
);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
Обе функции используются совместно, однако решают разные задачи.
Возвращает:
Объект сцены
Применяется для:
Пример:
const picked =
viewer.scene.pick(position);
Возвращает:
Cartesian3
Применяется для:
Пример:
const worldPosition =
viewer.scene.pickPosition(
position
);
Если требуется получить точку на поверхности Земли без анализа объектов сцены, используется:
viewer.camera.pickEllipsoid()
Пример:
const position =
viewer.camera.pickEllipsoid(
click.position
);
Возвращаемое значение:
Cartesian3
Работает:
viewer.camera.pickEllipsoid(...)
Работает:
viewer.scene.pickPosition(...)
Для современных приложений обычно предпочтителен именно
pickPosition().
Помимо Entity, Cesium позволяет выбирать низкоуровневые примитивы.
Создание примитива:
const primitive =
viewer.scene.primitives.add(
new Cesium.PointPrimitiveCollection()
);
Добавление точки:
primitive.add({
position:
Cesium.Cartesian3.fromDegrees(
30,
50
),
pixelSize: 10
});
Получение:
const picked =
viewer.scene.pick(
click.position
);
В этом случае:
picked.primitive
будет содержать выбранный примитив.
Поддержка выбора объектов встроена в движок 3D Tiles.
const picked =
viewer.scene.pick(
click.position
);
После выбора можно получить информацию:
console.log(
picked.getProperty("name")
);
Пример чтения атрибутов:
const buildingName =
picked.getProperty(
"building_name"
);
Для моделей также используется стандартный механизм.
Создание модели:
const modelEntity =
viewer.entities.add({
position:
Cesium.Cartesian3.fromDegrees(
30,
50
),
model: {
uri: "model.glb"
}
});
Выбор:
const picked =
viewer.scene.pick(
click.position
);
Получение сущности:
const entity =
picked.id;
Иногда требуется анализировать не один пиксель, а область вокруг курсора.
Для этого используется дополнительный параметр.
viewer.scene.pick(
position,
width,
height
);
Пример:
const picked =
viewer.scene.pick(
click.position,
10,
10
);
Будет проверяться область размером 10×10 пикселей.
Механизм выбора является достаточно быстрым, однако при интенсивном использовании необходимо учитывать несколько рекомендаций.
Плохо:
MOUSE_MOVE
viewer.scene.pick(...)
с десятками дополнительных операций.
Лучше:
Вместо повторного поиска:
viewer.entities.getById(...)
лучше сохранять объект сразу после выбора.
selectedEntity = picked.id;
Метод:
drillPick()
анализирует большее количество объектов и требует больше вычислений, чем обычный:
pick()
Поэтому его следует применять только тогда, когда действительно необходимо получить весь набор перекрывающихся объектов.
Во многих профессиональных приложениях применяется следующая схема:
pick().pickPosition().Подобная архитектура обеспечивает единый механизм взаимодействия с сущностями, примитивами, 3D Tiles, terrain и трёхмерными моделями, формируя основу большинства инструментов редактирования, анализа и навигации в приложениях на базе CesiumJS.