Cesium实体交互与事件处理完全指南
📊 Entity交互事件系统流程图
🛠️ 事件处理基础
1. 创建事件处理器
// ⭐创建事件处理器 - 所有交互的入口
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
2. 常用事件类型
// 鼠标左键单击
Cesium.ScreenSpaceEventType.LEFT_CLICK
// 鼠标右键单击
Cesium.ScreenSpaceEventType.RIGHT_CLICK
// 鼠标移动
Cesium.ScreenSpaceEventType.MOUSE_MOVE
// 鼠标左键按下
Cesium.ScreenSpaceEventType.LEFT_DOWN
// 鼠标左键释放
Cesium.ScreenSpaceEventType.LEFT_UP
// 鼠标右键按下
Cesium.ScreenSpaceEventType.RIGHT_DOWN
// 鼠标右键释放
Cesium.ScreenSpaceEventType.RIGHT_UP
// 鼠标中键点击
Cesium.ScreenSpaceEventType.MIDDLE_CLICK
// 鼠标滚轮
Cesium.ScreenSpaceEventType.WHEEL
// 双击
Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK
🔍 实体拾取与选择
1. 基本实体拾取
// ⭐左键点击选择实体
handler.setInputAction(function(click) {
// 获取点击位置的屏幕坐标
const pickedObject = viewer.scene.pick(click.position);
// 检查是否拾取到实体
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
// 获取拾取到的实体
const entity = pickedObject.id;
// 在控制台输出实体ID
console.log('选中实体:', entity.id);
// 处理选中逻辑
highlightEntity(entity);
showEntityInfo(entity);
} else {
// 点击空白区域
clearSelection();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 高亮显示实体
function highlightEntity(entity) {
// 重置之前选中的实体
clearSelection();
// 记录当前选中的实体
selectedEntity = entity;
// 保存原始外观
if (!entity._originalColor && entity.billboard) {
// 对于广告牌类型,改变颜色或缩放
entity._originalScale = entity.billboard.scale.getValue();
entity.billboard.scale = entity._originalScale * 1.5;
} else if (!entity._originalColor && entity.point) {
// 对于点类型,改变颜色
entity._originalColor = entity.point.color.getValue().clone();
entity.point.color = Cesium.Color.YELLOW;
} else if (!entity._originalColor && entity.polygon) {
// 对于多边形类型,改变填充颜色
entity._originalColor = entity.polygon.material.getValue().color.clone();
entity.polygon.material = Cesium.Color.fromAlpha(Cesium.Color.YELLOW, 0.5);
// 添加或强调轮廓
entity.polygon.outline = true;
entity.polygon.outlineColor = Cesium.Color.WHITE;
entity.polygon.outlineWidth = 2.0;
}
// 聚焦到选中的实体
viewer.flyTo(entity);
}
// 清除选择
function clearSelection() {
if (selectedEntity) {
// 恢复原始外观
if (selectedEntity.billboard && selectedEntity._originalScale) {
selectedEntity.billboard.scale = selectedEntity._originalScale;
selectedEntity._originalScale = undefined;
} else if (selectedEntity.point && selectedEntity._originalColor) {
selectedEntity.point.color = selectedEntity._originalColor;
selectedEntity._originalColor = undefined;
} else if (selectedEntity.polygon && selectedEntity._originalColor) {
selectedEntity.polygon.material = selectedEntity._originalColor;
selectedEntity._originalColor = undefined;
}
selectedEntity = undefined;
// 清除信息面板
hideEntityInfo();
}
}
// 显示实体信息
function showEntityInfo(entity) {
// 创建或更新信息面板
let infoBox = document.getElementById('entity-info-box');
if (!infoBox) {
infoBox = document.createElement('div');
infoBox.id = 'entity-info-box';
infoBox.style.position = 'absolute';
infoBox.style.bottom = '10px';
infoBox.style.right = '10px';
infoBox.style.backgroundColor = 'rgba(40, 40, 40, 0.9)';
infoBox.style.color = 'white';
infoBox.style.padding = '10px';
infoBox.style.borderRadius = '5px';
infoBox.style.maxWidth = '300px';
document.body.appendChild(infoBox);
}
// 构建信息内容
let content = `<h3>${entity.name || entity.id || '未命名实体'}</h3>`;
// 添加实体属性
if (entity.properties) {
content += '<table>';
const propertyNames = entity.properties.propertyNames;
for (let i = 0; i < propertyNames.length; i++) {
const name = propertyNames[i];
const value = entity.properties[name].getValue();
content += `<tr><td>${name}</td><td>${value}</td></tr>`;
}
content += '</table>';
} else {
content += '<p>没有额外属性</p>';
}
// 获取位置信息
if (entity.position) {
const position = entity.position.getValue(viewer.clock.currentTime);
const cartographic = Cesium.Cartographic.fromCartesian(position);
const longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(6);
const latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(6);
const height = cartographic.height.toFixed(2);
content += `<p>位置: ${longitude}, ${latitude}, ${height}m</p>`;
}
infoBox.innerHTML = content;
infoBox.style.display = 'block';
}
// 隐藏实体信息
function hideEntityInfo() {
const infoBox = document.getElementById('entity-info-box');
if (infoBox) {
infoBox.style.display = 'none';
}
}
2. 高级拾取 - 拾取射线
// ⭐使用拾取射线进行精确拾取
handler.setInputAction(function(click) {
// 从屏幕坐标创建射线
const ray = viewer.camera.getPickRay(click.position);
// 射线与地球求交得到交点
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
// 拾取实体
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
// 处理实体拾取
console.log('拾取到实体:', pickedObject.id.id);
} else if (Cesium.defined(cartesian)) {
// 拾取到地球表面点
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = cartographic.height;
console.log(`点击位置: 经度=${longitude.toFixed(6)}, 纬度=${latitude.toFixed(6)}, 高度=${height.toFixed(2)}米`);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
3. 深度检测拾取
// ⭐启用深度检测拾取 - 可穿透半透明实体
handler.setInputAction(function(click) {
// 拾取所有与射线相交的对象
const pickResult = viewer.scene.drillPick(click.position);
if (pickResult.length > 0) {
console.log(`拾取到${pickResult.length}个对象:`);
// 遍历所有拾取到的对象
for (let i = 0; i < pickResult.length; i++) {
const pickedObject = pickResult[i];
if (Cesium.defined(pickedObject.id)) {
// 处理实体
console.log(`- 实体 ${i+1}: ${pickedObject.id.id}`);
} else if (pickedObject.primitive) {
// 处理图元
console.log(`- 图元 ${i+1}: ${pickedObject.primitive.constructor.name}`);
}
}
} else {
console.log('未拾取到任何对象');
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
4. 多选实体
// ⭐实现多选实体
const selectedEntities = new Set();
// 添加单击事件处理
handler.setInputAction(function(click) {
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id;
// Ctrl键多选
if (Cesium.KeyboardEventModifier.CTRL) {
// 切换选择状态
if (selectedEntities.has(entity)) {
// 取消选择
selectedEntities.delete(entity);
resetEntityAppearance(entity);
} else {
// 添加到选择
selectedEntities.add(entity);
highlightEntity(entity);
}
} else {
// 单选(先清除已选)
clearMultiSelection();
selectedEntities.add(entity);
highlightEntity(entity);
}
updateSelectionInfo();
} else if (!Cesium.KeyboardEventModifier.CTRL) {
// 点击空白区域,取消所有选择
clearMultiSelection();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 高亮显示实体
function highlightEntity(entity) {
// 保存原始外观
saveOriginalAppearance(entity);
// 应用高亮样式
if (entity.billboard) {
entity.billboard.scale = entity._originalScale * 1.5;
} else if (entity.point) {
entity.point.color = Cesium.Color.YELLOW;
} else if (entity.polygon) {
entity.polygon.material = Cesium.Color.YELLOW.withAlpha(0.5);
entity.polygon.outline = true;
entity.polygon.outlineColor = Cesium.Color.WHITE;
}
}
// 保存实体原始外观
function saveOriginalAppearance(entity) {
if (!entity._originalAppearanceSaved) {
if (entity.billboard) {
entity._originalScale = entity.billboard.scale.getValue();
} else if (entity.point) {
entity._originalColor = entity.point.color.getValue().clone();
} else if (entity.polygon && entity.polygon.material) {
if (entity.polygon.material instanceof Cesium.ColorMaterialProperty) {
entity._originalColor = entity.polygon.material.color.getValue().clone();
} else {
// 对于复杂材质,存储类型信息
entity._originalMaterial = entity.polygon.material;
}
}
entity._originalAppearanceSaved = true;
}
}
// 重置实体外观
function resetEntityAppearance(entity) {
if (entity._originalAppearanceSaved) {
if (entity.billboard && entity._originalScale) {
entity.billboard.scale = entity._originalScale;
} else if (entity.point && entity._originalColor) {
entity.point.color = entity._originalColor;
} else if (entity.polygon) {
if (entity._originalColor) {
entity.polygon.material = entity._originalColor;
} else if (entity._originalMaterial) {
entity.polygon.material = entity._originalMaterial;
}
}
entity._originalAppearanceSaved = false;
}
}
// 清除多选
function clearMultiSelection() {
selectedEntities.forEach(entity => {
resetEntityAppearance(entity);
});
selectedEntities.clear();
updateSelectionInfo();
}
// 更新选择信息
function updateSelectionInfo() {
const selectionCount = selectedEntities.size;
let selectionBox = document.getElementById('selection-info-box');
if (!selectionBox) {
selectionBox = document.createElement('div');
selectionBox.id = 'selection-info-box';
selectionBox.style.position = 'absolute';
selectionBox.style.top = '10px';
selectionBox.style.left = '10px';
selectionBox.style.backgroundColor = 'rgba(40, 40, 40, 0.9)';
selectionBox.style.color = 'white';
selectionBox.style.padding = '10px';
selectionBox.style.borderRadius = '5px';
document.body.appendChild(selectionBox);
}
if (selectionCount > 0) {
selectionBox.innerHTML = `已选择 ${selectionCount} 个实体`;
selectionBox.style.display = 'block';
} else {
selectionBox.style.display = 'none';
}
}
🖱️ 鼠标悬停交互
1. 悬停高亮效果
// ⭐实现鼠标悬停高亮
let hoveredEntity;
// 添加鼠标移动事件处理
handler.setInputAction(function(movement) {
// 确保不干扰已选中实体
if (selectedEntity) return;
// 获取当前鼠标下的实体
const pickedObject = viewer.scene.pick(movement.endPosition);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const newHoveredEntity = pickedObject.id;
// 避免重复处理同一实体
if (hoveredEntity === newHoveredEntity) {
return;
}
// 清除之前的悬停实体
resetHoverEffect();
// 设置新的悬停实体
hoveredEntity = newHoveredEntity;
// 应用悬停效果
applyHoverEffect(hoveredEntity);
} else {
// 鼠标移开,清除悬停效果
resetHoverEffect();
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 应用悬停效果
function applyHoverEffect(entity) {
if (entity.billboard) {
entity._hoverOriginal = entity.billboard.scale.getValue();
entity.billboard.scale = entity._hoverOriginal * 1.2;
} else if (entity.point) {
entity._hoverOriginal = entity.point.color.getValue().clone();
entity.point.color = Cesium.Color.DEEPSKYBLUE;
} else if (entity.polygon) {
if (entity.polygon.material instanceof Cesium.ColorMaterialProperty) {
entity._hoverOriginal = entity.polygon.material.color.getValue().clone();
entity.polygon.material = new Cesium.ColorMaterialProperty(
Cesium.Color.LIGHTSTEELBLUE.withAlpha(0.7)
);
}
}
// 修改光标样式
viewer.canvas.style.cursor = 'pointer';
}
// 重置悬停效果
function resetHoverEffect() {
if (hoveredEntity) {
if (hoveredEntity.billboard && hoveredEntity._hoverOriginal) {
hoveredEntity.billboard.scale = hoveredEntity._hoverOriginal;
hoveredEntity._hoverOriginal = undefined;
} else if (hoveredEntity.point && hoveredEntity._hoverOriginal) {
hoveredEntity.point.color = hoveredEntity._hoverOriginal;
hoveredEntity._hoverOriginal = undefined;
} else if (hoveredEntity.polygon && hoveredEntity._hoverOriginal) {
hoveredEntity.polygon.material = hoveredEntity._hoverOriginal;
hoveredEntity._hoverOriginal = undefined;
}
hoveredEntity = undefined;
}
// 恢复默认光标
viewer.canvas.style.cursor = 'default';
}
2. 悬停信息提示
// ⭐创建悬停信息提示
function createTooltip() {
const tooltip = document.createElement('div');
tooltip.className = 'cesium-tooltip';
tooltip.style.display = 'none';
tooltip.style.position = 'absolute';
tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
tooltip.style.color = 'white';
tooltip.style.padding = '6px 10px';
tooltip.style.borderRadius = '4px';
tooltip.style.pointerEvents = 'none';
tooltip.style.zIndex = '1000';
document.body.appendChild(tooltip);
return tooltip;
}
// 创建提示框
const tooltip = createTooltip();
// 悬停显示提示
handler.setInputAction(function(movement) {
const pickedObject = viewer.scene.pick(movement.endPosition);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id;
// 显示提示框
tooltip.style.display = 'block';
tooltip.style.left = movement.endPosition.x + 10 + 'px';
tooltip.style.top = movement.endPosition.y + 10 + 'px';
// 构建提示内容
let content = entity.name || entity.id || '未命名实体';
// 添加简短属性信息
if (entity.properties) {
const propertyNames = entity.properties.propertyNames;
const maxProps = Math.min(3, propertyNames.length);
if (maxProps > 0) {
content += '<table style="margin-top:5px;">';
for (let i = 0; i < maxProps; i++) {
const name = propertyNames[i];
const value = entity.properties[name].getValue();
content += `<tr><td>${name}</td><td>${value}</td></tr>`;
}
content += '</table>';
}
}
tooltip.innerHTML = content;
} else {
// 隐藏提示框
tooltip.style.display = 'none';
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
🔄 拖拽与位置编辑
1. 基本拖拽功能
// ⭐实现实体拖拽功能
let isDragging = false;
let draggedEntity;
// 鼠标按下事件 - 开始拖拽
handler.setInputAction(function(click) {
// 如果已经在拖拽,不处理
if (isDragging) return;
// 拾取实体
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
draggedEntity = pickedObject.id;
// 标记为拖拽状态
isDragging = true;
// 修改光标样式
viewer.canvas.style.cursor = 'grabbing';
// 禁用相机旋转 - 防止拖拽时相机移动
viewer.scene.screenSpaceCameraController.enableRotate = false;
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 鼠标移动事件 - 拖拽中
handler.setInputAction(function(movement) {
if (!isDragging || !draggedEntity) return;
// 从屏幕坐标创建射线
const ray = viewer.camera.getPickRay(movement.endPosition);
// 射线与地球表面相交
const newPosition = viewer.scene.globe.pick(ray, viewer.scene);
if (Cesium.defined(newPosition)) {
// 更新实体位置
draggedEntity.position = newPosition;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 鼠标释放事件 - 结束拖拽
handler.setInputAction(function() {
if (isDragging) {
// 恢复状态
isDragging = false;
draggedEntity = undefined;
// 恢复光标样式
viewer.canvas.style.cursor = 'default';
// 重新启用相机旋转
viewer.scene.screenSpaceCameraController.enableRotate = true;
}
}, Cesium.ScreenSpaceEventType.LEFT_UP);
2. 高级可编辑点位
// ⭐创建可编辑点位
function createEditablePoint(position, options = {}) {
// 创建点实体
const point = viewer.entities.add({
position: position,
point: {
pixelSize: options.pixelSize || 10,
color: options.color || Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
heightReference: options.heightReference || Cesium.HeightReference.CLAMP_TO_GROUND
},
// 自定义属性
properties: {
isEditable: true
}
});
return point;
}
// 创建可编辑多边形
function createEditablePolygon(positions, options = {}) {
// 创建控制点
const controlPoints = [];
for (let i = 0; i < positions.length; i++) {
const controlPoint = createEditablePoint(positions[i], {
pixelSize: 8,
color: Cesium.Color.LIME,
heightReference: options.heightReference
});
// 添加索引属性
controlPoint.properties.pointIndex = i;
controlPoint.properties.polygonPoints = positions;
controlPoints.push(controlPoint);
}
// 创建多边形
const polygon = viewer.entities.add({
polygon: {
hierarchy: new Cesium.CallbackProperty(function() {
// 动态获取控制点位置
return new Cesium.PolygonHierarchy(positions);
}, false),
material: options.material || Cesium.Color.BLUE.withAlpha(0.5),
heightReference: options.heightReference
},
properties: {
controlPoints: controlPoints
}
});
return {
polygon: polygon,
controlPoints: controlPoints
};
}
// 实现点位拖拽
let isDragging = false;
let draggedPoint;
// 鼠标按下事件
handler.setInputAction(function(click) {
if (isDragging) return;
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id;
// 检查是否为可编辑点
if (entity.properties &&
entity.properties.isEditable &&
entity.properties.isEditable.getValue()) {
// 开始拖拽
isDragging = true;
draggedPoint = entity;
// 修改光标样式
viewer.canvas.style.cursor = 'grabbing';
viewer.scene.screenSpaceCameraController.enableRotate = false;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 鼠标移动事件
handler.setInputAction(function(movement) {
if (!isDragging || !draggedPoint) return;
// 获取新位置
const ray = viewer.camera.getPickRay(movement.endPosition);
const newPosition = viewer.scene.globe.pick(ray, viewer.scene);
if (Cesium.defined(newPosition)) {
// 更新拖拽点的位置
draggedPoint.position = newPosition;
// 如果是多边形控制点,更新对应多边形顶点
if (draggedPoint.properties.polygonPoints &&
draggedPoint.properties.pointIndex !== undefined) {
const pointIndex = draggedPoint.properties.pointIndex.getValue();
const polygonPoints = draggedPoint.properties.polygonPoints.getValue();
// 更新多边形顶点位置
polygonPoints[pointIndex] = newPosition;
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 鼠标释放事件
handler.setInputAction(function() {
if (isDragging) {
isDragging = false;
draggedPoint = undefined;
viewer.canvas.style.cursor = 'default';
viewer.scene.screenSpaceCameraController.enableRotate = true;
}
}, Cesium.ScreenSpaceEventType.LEFT_UP);
3. 可调整半径圆形
// ⭐创建可调整半径的圆
function createResizableCircle(center, radius, options = {}) {
// 创建中心点
const centerPoint = createEditablePoint(center, {
pixelSize: 10,
color: Cesium.Color.RED,
heightReference: options.heightReference
});
// 创建初始半径
const currentRadius = new Cesium.CallbackProperty(function() {
return radius;
}, false);
// 创建边缘控制点位置 (初始在东侧)
const edgePosition = new Cesium.CallbackProperty(function() {
// 从中心点位置获取经纬度
const centerCartographic = Cesium.Cartographic.fromCartesian(centerPoint.position.getValue());
const lon = centerCartographic.longitude;
const lat = centerCartographic.latitude;
// 根据半径计算边缘点位置 (向东偏移)
const radiusRadians = radius / 6378137.0; // 转换为弧度
const edgeLon = lon + radiusRadians / Math.cos(lat);
return Cesium.Cartesian3.fromRadians(edgeLon, lat, centerCartographic.height);
}, false);
// 创建边缘控制点
const edgePoint = createEditablePoint(edgePosition, {
pixelSize: 8,
color: Cesium.Color.LIME,
heightReference: options.heightReference
});
// 创建圆形
const circle = viewer.entities.add({
position: centerPoint.position,
ellipse: {
semiMajorAxis: currentRadius,
semiMinorAxis: currentRadius,
material: options.material || Cesium.Color.BLUE.withAlpha(0.5),
heightReference: options.heightReference || Cesium.HeightReference.CLAMP_TO_GROUND,
outline: true,
outlineColor: Cesium.Color.WHITE
},
properties: {
isResizableCircle: true,
centerPoint: centerPoint,
edgePoint: edgePoint
}
});
// 特殊拖拽处理
edgePoint.properties.isEdgePoint = true;
edgePoint.properties.circleEntity = circle;
centerPoint.properties.isCircleCenter = true;
centerPoint.properties.circleEntity = circle;
centerPoint.properties.edgePoint = edgePoint;
return {
circle: circle,
centerPoint: centerPoint,
edgePoint: edgePoint
};
}
// 修改拖拽处理以支持圆形调整
handler.setInputAction(function(movement) {
if (!isDragging || !draggedPoint) return;
const ray = viewer.camera.getPickRay(movement.endPosition);
const newPosition = viewer.scene.globe.pick(ray, viewer.scene);
if (Cesium.defined(newPosition)) {
if (draggedPoint.properties.isEdgePoint &&
draggedPoint.properties.isEdgePoint.getValue()) {
// 获取关联的圆
const circle = draggedPoint.properties.circleEntity.getValue();
const centerPoint = circle.properties.centerPoint.getValue();
const centerPosition = centerPoint.position.getValue(viewer.clock.currentTime);
// 更新控制点位置
draggedPoint.position = newPosition;
// 计算新半径
radius = Cesium.Cartesian3.distance(centerPosition, newPosition);
// 更新圆的半径
circle.ellipse.semiMajorAxis = radius;
circle.ellipse.semiMinorAxis = radius;
} else if (draggedPoint.properties.isCircleCenter &&
draggedPoint.properties.isCircleCenter.getValue()) {
// 移动中心点
draggedPoint.position = newPosition;
// 同时移动边缘点,保持相对位置
const circle = draggedPoint.properties.circleEntity.getValue();
const edgePoint = draggedPoint.properties.edgePoint.getValue();
const radius = circle.ellipse.semiMajorAxis.getValue();
// 计算新的边缘点位置
const centerCartographic = Cesium.Cartographic.fromCartesian(newPosition);
const edgeLon = centerCartographic.longitude + radius / 6378137.0 / Math.cos(centerCartographic.latitude);
edgePoint.position = Cesium.Cartesian3.fromRadians(
edgeLon,
centerCartographic.latitude,
centerCartographic.height
);
} else {
// 普通点拖拽
draggedPoint.position = newPosition;
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
📏 测量与绘制工具
1. 距离测量工具
// ⭐实现距离测量工具
function createDistanceMeasurementTool() {
// 保存状态和点
let isActive = false;
let isDrawing = false;
let startPoint;
let endPoint;
let currentLine;
let currentDistance;
// 开始测量
function start() {
isActive = true;
viewer.canvas.style.cursor = 'crosshair';
}
// 结束测量
function stop() {
isActive = false;
isDrawing = false;
viewer.canvas.style.cursor = 'default';
startPoint = undefined;
endPoint = undefined;
currentLine = undefined;
currentDistance = undefined;
}
// 计算两点间距离
function calculateDistance(point1, point2) {
return Cesium.Cartesian3.distance(point1, point2);
}
// 显示距离标签
function createDistanceLabel(position, distance) {
return viewer.entities.add({
position: position,
label: {
text: `${distance.toFixed(2)} 米`,
font: '14px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -10),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
}
// 处理点击事件
handler.setInputAction(function(click) {
if (!isActive) return;
// 从屏幕坐标获取地球表面点
const ray = viewer.camera.getPickRay(click.position);
const position = viewer.scene.globe.pick(ray, viewer.scene);
if (!Cesium.defined(position)) return;
if (!isDrawing) {
// 第一次点击,记录起点
isDrawing = true;
startPoint = position;
// 创建起点标记
viewer.entities.add({
position: startPoint,
point: {
pixelSize: 8,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
}
});
// 创建动态线
currentLine = viewer.entities.add({
polyline: {
positions: new Cesium.CallbackProperty(function() {
return [startPoint, endPoint || startPoint];
}, false),
width: 2,
material: Cesium.Color.YELLOW,
clampToGround: true
}
});
// 创建动态距离标签
currentDistance = viewer.entities.add({
position: new Cesium.CallbackProperty(function() {
if (!endPoint) return startPoint;
// 放在线的中间位置
return Cesium.Cartesian3.midpoint(startPoint, endPoint, new Cesium.Cartesian3());
}, false),
label: {
text: new Cesium.CallbackProperty(function() {
if (!endPoint) return "0.00 米";
const distance = calculateDistance(startPoint, endPoint);
return `${distance.toFixed(2)} 米`;
}, false),
font: '14px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
backgroundColor: Cesium.Color.BLACK.withAlpha(0.7),
showBackground: true,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -10),
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
} else {
// 第二次点击,完成测量
endPoint = position;
// 创建终点标记
viewer.entities.add({
position: endPoint,
point: {
pixelSize: 8,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
}
});
// 计算最终距离
const distance = calculateDistance(startPoint, endPoint);
// 重置状态,准备下一次测量
isDrawing = false;
startPoint = undefined;
endPoint = undefined;
currentLine = undefined;
currentDistance = undefined;
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 鼠标移动事件处理
handler.setInputAction(function(movement) {
if (!isActive || !isDrawing) return;
// 更新终点位置
const ray = viewer.camera.getPickRay(movement.endPosition);
endPoint = viewer.scene.globe.pick(ray, viewer.scene);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 右键取消当前测量
handler.setInputAction(function() {
if (isDrawing) {
// 删除临时实体
if (currentLine) viewer.entities.remove(currentLine);
if (currentDistance) viewer.entities.remove(currentDistance);
// 重置状态
isDrawing = false;
startPoint = undefined;
endPoint = undefined;
currentLine = undefined;
currentDistance = undefined;
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
return {
start: start,
stop: stop,
isActive: function() { return isActive; }
};
}
// 使用距离测量工具
const distanceTool = createDistanceMeasurementTool();
// 可以通过UI按钮触发测量
// 例如: document.getElementById('measureDistance').onclick = distanceTool.start;
2. 面积测量工具
// ⭐实现面积测量工具
function createAreaMeasurementTool() {
// 保存状态和点
let isActive = false;
let points = [];
let currentPolygon;
let currentArea;
// 开始测量
function start() {
isActive = true;
points = [];
viewer.canvas.style.cursor = 'crosshair';
}
// 结束测量
function stop() {
isActive = false;
viewer.canvas.style.cursor = 'default';
// 清除临时实体
if (currentPolygon) viewer.entities.remove(currentPolygon);
if (currentArea) viewer.entities.remove(currentArea);
currentPolygon = undefined;
currentArea = undefined;
points = [];
}
// 计算多边形面积(平面近似)
function calculateArea(positions) {
if (positions.length < 3) return 0;
// 将点转换为地理坐标
const coordinates = [];
for (let i = 0; i < positions.length; i++) {
const cartographic = Cesium.Cartographic.fromCartesian(positions[i]);
coordinates.push([
Cesium.Math.toDegrees(cartographic.longitude),
Cesium.Math.toDegrees(cartographic.latitude)
]);
}
// 确保多边形闭合
if (coordinates[0][0] !== coordinates[coordinates.length-1][0] ||
coordinates[0][1] !== coordinates[coordinates.length-1][1]) {
coordinates.push([coordinates[0][0], coordinates[0][1]]);
}
// 使用球面面积计算
return Math.abs(calculateSphericalArea(coordinates));
}
// 球面多边形面积计算
function calculateSphericalArea(coordinates) {
const radiusEarth = 6378137.0; // 地球半径(米)
let area = 0;
if (coordinates.length > 2) {
const len = coordinates.length;
for (let i = 0; i < len - 1; i++) {
const p1 = coordinates[i];
const p2 = coordinates[i + 1];
// 转换为弧度
const p1Lon = Cesium.Math.toRadians(p1[0]);
const p1Lat = Cesium.Math.toRadians(p1[1]);
const p2Lon = Cesium.Math.toRadians(p2[0]);
const p2Lat = Cesium.Math.toRadians(p2[1]);
// 使用球面三角公式
area += (p2Lon - p1Lon) * Math.sin(p1Lat);
}
area = area * radiusEarth * radiusEarth * 0.5;
}
return area;
}
// 格式化面积显示
function formatArea(area) {
if (area < 10000) {
return `${area.toFixed(2)} 平方米`;
} else {
return `${(area / 1000000).toFixed(4)} 平方公里`;
}
}
// 处理点击事件
handler.setInputAction(function(click) {
if (!isActive) return;
// 从屏幕坐标获取地球表面点
const ray = viewer.camera.getPickRay(click.position);
const position = viewer.scene.globe.pick(ray, viewer.scene);
if (!Cesium.defined(position)) return;
// 添加点
points.push(position);
// 创建点标记
viewer.entities.add({
position: position,
point: {
pixelSize: 8,
color: Cesium.Color.LIME,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
}
});
// 至少有3个点时创建多边形
if (points.length === 3) {
// 创建多边形
currentPolygon = viewer.entities.add({
polygon: {
hierarchy: new Cesium.CallbackProperty(function() {
return new Cesium.PolygonHierarchy(points);
}, false),
material: Cesium.Color.BLUE.withAlpha(0.5),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
outline: true,
outlineColor: Cesium.Color.WHITE
}
});
// 创建面积标签
currentArea = viewer.entities.add({
position: new Cesium.CallbackProperty(function() {
// 计算多边形中心
const center = Cesium.BoundingSphere.fromPoints(points).center;
return center;
}, false),
label: {
text: new Cesium.CallbackProperty(function() {
const area = calculateArea(points);
return `面积: ${formatArea(area)}`;
}, false),
font: '14px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
backgroundColor: Cesium.Color.BLACK.withAlpha(0.7),
showBackground: true,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
} else if (points.length > 3) {
// 当添加更多点时,多边形会自动更新
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 双击完成绘制
handler.setInputAction(function(click) {
if (!isActive || points.length < 3) return;
// 创建最终多边形和面积标签
const finalPoints = [...points];
// 计算面积
const area = calculateArea(finalPoints);
// 创建永久多边形
viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(finalPoints),
material: Cesium.Color.GREEN.withAlpha(0.5),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
outline: true,
outlineColor: Cesium.Color.WHITE
}
});
// 创建永久面积标签
viewer.entities.add({
position: Cesium.BoundingSphere.fromPoints(finalPoints).center,
label: {
text: `面积: ${formatArea(area)}`,
font: '14px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
backgroundColor: Cesium.Color.BLACK.withAlpha(0.7),
showBackground: true,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
// 重置当前工作状态
points = [];
if (currentPolygon) viewer.entities.remove(currentPolygon);
if (currentArea) viewer.entities.remove(currentArea);
currentPolygon = undefined;
currentArea = undefined;
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
// 右键取消当前点
handler.setInputAction(function() {
if (!isActive || points.length === 0) return;
// 移除最后一个点
points.pop();
// 如果不足3个点,移除多边形
if (points.length < 3) {
if (currentPolygon) viewer.entities.remove(currentPolygon);
if (currentArea) viewer.entities.remove(currentArea);
currentPolygon = undefined;
currentArea = undefined;
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
return {
start: start,
stop: stop,
isActive: function() { return isActive; }
};
}
// 使用面积测量工具
const areaTool = createAreaMeasurementTool();
// 可以通过UI按钮触发测量
// 例如: document.getElementById('measureArea').onclick = areaTool.start;
🖌️ 自定义绘制工具
1. 绘制矩形工具
// ⭐实现矩形绘制工具
function createRectangleDrawingTool(options = {}) {
// 保存状态
let isActive = false;
let isDrawing = false;
let startPosition;
let endPosition;
let currentRectangle;
// 开始绘制
function start() {
isActive = true;
viewer.canvas.style.cursor = 'crosshair';
}
// 结束绘制
function stop() {
isActive = false;
isDrawing = false;
viewer.canvas.style.cursor = 'default';
// 清除临时实体
if (currentRectangle) viewer.entities.remove(currentRectangle);
startPosition = undefined;
endPosition = undefined;
currentRectangle = undefined;
}
// 处理鼠标按下事件 - 开始绘制
handler.setInputAction(function(click) {
if (!isActive || isDrawing) return;
// 获取起点位置
const ray = viewer.camera.getPickRay(click.position);
startPosition = viewer.scene.globe.pick(ray, viewer.scene);
if (!Cesium.defined(startPosition)) return;
isDrawing = true;
// 创建临时矩形
currentRectangle = viewer.entities.add({
rectangle: {
coordinates: new Cesium.CallbackProperty(function() {
if (!startPosition || !endPosition) {
return Cesium.Rectangle.fromDegrees(0, 0, 0, 0);
}
// 计算矩形坐标
const startCartographic = Cesium.Cartographic.fromCartesian(startPosition);
const endCartographic = Cesium.Cartographic.fromCartesian(endPosition);
const west = Math.min(startCartographic.longitude, endCartographic.longitude);
const east = Math.max(startCartographic.longitude, endCartographic.longitude);
const south = Math.min(startCartographic.latitude, endCartographic.latitude);
const north = Math.max(startCartographic.latitude, endCartographic.latitude);
return Cesium.Rectangle.fromRadians(west, south, east, north);
}, false),
material: options.material || Cesium.Color.BLUE.withAlpha(0.5),
height: options.height || 0,
heightReference: options.heightReference || Cesium.HeightReference.CLAMP_TO_GROUND,
outline: true,
outlineColor: Cesium.Color.WHITE
}
});
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 处理鼠标移动事件 - 更新矩形
handler.setInputAction(function(movement) {
if (!isActive || !isDrawing) return;
// 更新终点位置
const ray = viewer.camera.getPickRay(movement.endPosition);
endPosition = viewer.scene.globe.pick(ray, viewer.scene);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 处理鼠标释放事件 - 完成绘制
handler.setInputAction(function() {
if (!isActive || !isDrawing) return;
isDrawing = false;
// 创建永久矩形
if (startPosition && endPosition) {
// 计算最终矩形坐标
const startCartographic = Cesium.Cartographic.fromCartesian(startPosition);
const endCartographic = Cesium.Cartographic.fromCartesian(endPosition);
const west = Math.min(startCartographic.longitude, endCartographic.longitude);
const east = Math.max(startCartographic.longitude, endCartographic.longitude);
const south = Math.min(startCartographic.latitude, endCartographic.latitude);
const north = Math.max(startCartographic.latitude, endCartographic.latitude);
// 创建最终矩形
const finalRectangle = viewer.entities.add({
rectangle: {
coordinates: Cesium.Rectangle.fromRadians(west, south, east, north),
material: options.material || Cesium.Color.BLUE.withAlpha(0.5),
height: options.height || 0,
heightReference: options.heightReference || Cesium.HeightReference.CLAMP_TO_GROUND,
outline: true,
outlineColor: Cesium.Color.WHITE
}
});
// 如果提供了回调函数,则调用
if (options.callback) {
options.callback(finalRectangle);
}
}
// 清除临时实体
if (currentRectangle) viewer.entities.remove(currentRectangle);
// 重置状态
startPosition = undefined;
endPosition = undefined;
currentRectangle = undefined;
}, Cesium.ScreenSpaceEventType.LEFT_UP);
// 右键取消当前绘制
handler.setInputAction(function() {
if (isDrawing) {
isDrawing = false;
// 清除临时实体
if (currentRectangle) viewer.entities.remove(currentRectangle);
// 重置状态
startPosition = undefined;
endPosition = undefined;
currentRectangle = undefined;
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
return {
start: start,
stop: stop,
isActive: function() { return isActive; }
};
}
// 使用矩形绘制工具
const rectangleTool = createRectangleDrawingTool({
material: Cesium.Color.RED.withAlpha(0.5),
callback: function(entity) {
console.log('矩形绘制完成:', entity);
}
});
// 可以通过UI按钮触发绘制
// 例如: document.getElementById('drawRectangle').onclick = rectangleTool.start;
2. 自定义路径绘制工具
// ⭐实现路径绘制工具
function createPathDrawingTool(options = {}) {
// 保存状态
let isActive = false;
let positions = [];
let currentLine;
// 开始绘制
function start() {
isActive = true;
positions = [];
viewer.canvas.style.cursor = 'crosshair';
}
// 结束绘制
function stop() {
isActive = false;
viewer.canvas.style.cursor = 'default';
// 清除临时实体
if (currentLine) viewer.entities.remove(currentLine);
currentLine = undefined;
positions = [];
}
// 处理点击事件 - 添加路径点
handler.setInputAction(function(click) {
if (!isActive) return;
// 获取点击位置
const ray = viewer.camera.getPickRay(click.position);
const position = viewer.scene.globe.pick(ray, viewer.scene);
if (!Cesium.defined(position)) return;
// 添加点
positions.push(position);
// 创建点标记
viewer.entities.add({
position: position,
point: {
pixelSize: 8,
color: Cesium.Color.YELLOW,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
heightReference: options.heightReference || Cesium.HeightReference.CLAMP_TO_GROUND
}
});
// 第一个点时创建线
if (positions.length === 1) {
currentLine = viewer.entities.add({
polyline: {
positions: new Cesium.CallbackProperty(function() {
return positions;
}, false),
width: options.width || 3,
material: options.material || Cesium.Color.YELLOW,
clampToGround: options.clampToGround !== false
}
});
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 双击完成绘制
handler.setInputAction(function() {
if (!isActive || positions.length < 2) return;
// 创建最终路径
const finalPositions = [...positions];
viewer.entities.add({
polyline: {
positions: finalPositions,
width: options.width || 3,
material: options.material || new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.2,
color: Cesium.Color.YELLOW
}),
clampToGround: options.clampToGround !== false
}
});
// 如果提供了回调函数,则调用
if (options.callback) {
options.callback(finalPositions);
}
// 重置状态
positions = [];
if (currentLine) viewer.entities.remove(currentLine);
currentLine = undefined;
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
// 鼠标移动事件 - 预览路径
handler.setInputAction(function(movement) {
if (!isActive || positions.length === 0) return;
// 获取当前鼠标位置
const ray = viewer.camera.getPickRay(movement.endPosition);
const currentPosition = viewer.scene.globe.pick(ray, viewer.scene);
if (!Cesium.defined(currentPosition)) return;
// 更新预览路径
if (!currentLine && positions.length > 0) {
currentLine = viewer.entities.add({
polyline: {
positions: new Cesium.CallbackProperty(function() {
return [...positions, currentPosition];
}, false),
width: options.width || 3,
material: options.material || Cesium.Color.YELLOW,
clampToGround: options.clampToGround !== false
}
});
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 右键删除最后一个点
handler.setInputAction(function() {
if (!isActive || positions.length === 0) return;
// 移除最后一个点
positions.pop();
// 如果没有点了,删除线
if (positions.length === 0 && currentLine) {
viewer.entities.remove(currentLine);
currentLine = undefined;
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
return {
start: start,
stop: stop,
isActive: function() { return isActive; }
};
}
// 使用路径绘制工具
const pathTool = createPathDrawingTool({
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.2,
color: Cesium.Color.GREEN
}),
width: 5,
callback: function(positions) {
console.log('路径绘制完成,点数:', positions.length);
}
});
// 可以通过UI按钮触发绘制
// 例如: document.getElementById('drawPath').onclick = pathTool.start;
📱 触摸设备交互
1. 适配触摸设备
// ⭐适配触摸设备的实体选择
function setupTouchInteraction() {
// 创建触摸事件处理器
const touchHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
// 触摸点击(单指点击)
touchHandler.setInputAction(function(tap) {
// 获取触摸位置
const pickedObject = viewer.scene.pick(tap.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id;
// 处理实体选择
console.log('触摸选择实体:', entity.id);
// 高亮显示
highlightEntity(entity);
showEntityInfo(entity);
} else {
// 点击空白区域
clearSelection();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 处理长按事件(模拟右键)
let touchStartPosition;
let touchStartTime;
const LONG_PRESS_THRESHOLD = 800; // 毫秒
// 触摸开始
touchHandler.setInputAction(function(touch) {
touchStartPosition = touch.position.clone();
touchStartTime = Date.now();
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 触摸结束
touchHandler.setInputAction(function(touch) {
// 检查是否是长按
const touchEndTime = Date.now();
const touchDuration = touchEndTime - touchStartTime;
if (touchDuration >= LONG_PRESS_THRESHOLD) {
// 长按处理 - 模拟右键菜单
const pickedObject = viewer.scene.pick(touchStartPosition);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id;
showContextMenu(entity, touchStartPosition);
}
}
}, Cesium.ScreenSpaceEventType.LEFT_UP);
// 显示上下文菜单
function showContextMenu(entity, position) {
// 创建或获取上下文菜单
let contextMenu = document.getElementById('entity-context-menu');
if (!contextMenu) {
contextMenu = document.createElement('div');
contextMenu.id = 'entity-context-menu';
contextMenu.style.position = 'absolute';
contextMenu.style.backgroundColor = 'rgba(40, 40, 40, 0.9)';
contextMenu.style.color = 'white';
contextMenu.style.padding = '10px';
contextMenu.style.borderRadius = '5px';
contextMenu.style.zIndex = '1000';
document.body.appendChild(contextMenu);
}
// 设置菜单位置
contextMenu.style.left = position.x + 'px';
contextMenu.style.top = position.y + 'px';
// 构建菜单内容
contextMenu.innerHTML = `
<div class="menu-item" data-action="zoom">放大到此实体</div>
<div class="menu-item" data-action="hide">隐藏此实体</div>
<div class="menu-item" data-action="info">显示详细信息</div>
`;
// 添加菜单项点击事件
const menuItems = contextMenu.querySelectorAll('.menu-item');
menuItems.forEach(item => {
item.addEventListener('click', function() {
const action = this.getAttribute('data-action');
switch(action) {
case 'zoom':
viewer.flyTo(entity);
break;
case 'hide':
entity.show = false;
break;
case 'info':
showEntityInfo(entity);
break;
}
// 隐藏菜单
contextMenu.style.display = 'none';
});
});
// 显示菜单
contextMenu.style.display = 'block';
// 点击其他区域隐藏菜单
const hideMenu = function() {
contextMenu.style.display = 'none';
document.removeEventListener('click', hideMenu);
};
setTimeout(() => {
document.addEventListener('click', hideMenu);
}, 100);
}
// 处理多点触控(缩放、平移)
// 注意:Cesium已内置处理了大部分触控手势,此处仅作示例
}
// 初始化触摸交互
setupTouchInteraction();
🧩 高级交互案例
1. 实体联动与关联
// ⭐实现实体联动与关联
function createLinkedEntities() {
// 创建主实体
const mainEntity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(116.39, 39.9),
billboard: {
image: './images/marker-red.png',
scale: 0.8,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM
},
label: {
text: '主控点',
font: '14px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -36)
},
// 自定义属性
properties: {
type: 'main',
linkedEntities: []
}
});
// 创建从属实体
const childEntities = [];
for (let i = 0; i < 5; i++) {
// 创建子实体
const childEntity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(
116.39 + (Math.random() - 0.5) * 0.1,
39.9 + (Math.random() - 0.5) * 0.1
),
billboard: {
image: './images/marker-blue.png',
scale: 0.6,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM
},
label: {
text: `子点 ${i+1}`,
font: '12px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -30)
},
// 自定义属性
properties: {
type: 'child',
parentEntity: mainEntity,
index: i
}
});
childEntities.push(childEntity);
// 添加到主实体的关联列表
mainEntity.properties.linkedEntities.push(childEntity);
// 创建连接线
viewer.entities.add({
polyline: {
positions: new Cesium.CallbackProperty(function() {
const mainPosition = mainEntity.position.getValue(viewer.clock.currentTime);
const childPosition = childEntity.position.getValue(viewer.clock.currentTime);
return [mainPosition, childPosition];
}, false),
width: 1,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.CYAN
})
},
properties: {
type: 'connection',
sourceEntity: mainEntity,
targetEntity: childEntity
}
});
}
// 实现拖拽主实体时联动
let isDragging = false;
let draggedEntity;
let startMousePosition;
let startEntityPosition;
// 鼠标按下事件
handler.setInputAction(function(click) {
if (isDragging) return;
// 拾取实体
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
draggedEntity = pickedObject.id;
// 只处理主实体
if (draggedEntity.properties &&
draggedEntity.properties.type &&
draggedEntity.properties.type.getValue() === 'main') {
// 标记为拖拽状态
isDragging = true;
// 保存初始位置
startMousePosition = click.position.clone();
startEntityPosition = draggedEntity.position.getValue(viewer.clock.currentTime).clone();
// 修改光标样式
viewer.canvas.style.cursor = 'grabbing';
// 禁用相机旋转
viewer.scene.screenSpaceCameraController.enableRotate = false;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 鼠标移动事件
handler.setInputAction(function(movement) {
if (!isDragging || !draggedEntity) return;
// 计算鼠标位移
const dx = movement.endPosition.x - startMousePosition.x;
const dy = movement.endPosition.y - startMousePosition.y;
// 将屏幕位移转换为地理位移
const ray = viewer.camera.getPickRay(movement.endPosition);
const newPosition = viewer.scene.globe.pick(ray, viewer.scene);
if (Cesium.defined(newPosition)) {
// 更新主实体位置
draggedEntity.position = newPosition;
// 获取子实体列表
const linkedEntities = draggedEntity.properties.linkedEntities.getValue();
// 计算位移向量
const oldCartographic = Cesium.Cartographic.fromCartesian(startEntityPosition);
const newCartographic = Cesium.Cartographic.fromCartesian(newPosition);
const lonOffset = newCartographic.longitude - oldCartographic.longitude;
const latOffset = newCartographic.latitude - oldCartographic.latitude;
// 更新所有关联实体位置
for (let i = 0; i < linkedEntities.length; i++) {
const childEntity = linkedEntities[i];
const childPosition = childEntity.position.getValue(viewer.clock.currentTime);
const childCartographic = Cesium.Cartographic.fromCartesian(childPosition);
// 应用相同的偏移
const newChildPosition = Cesium.Cartesian3.fromRadians(
childCartographic.longitude + lonOffset,
childCartographic.latitude + latOffset,
childCartographic.height
);
childEntity.position = newChildPosition;
}
// 更新起始位置,以防持续拖拽
startMousePosition = movement.endPosition.clone();
startEntityPosition = newPosition.clone();
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 鼠标释放事件
handler.setInputAction(function() {
if (isDragging) {
// 恢复状态
isDragging = false;
draggedEntity = undefined;
// 恢复光标样式
viewer.canvas.style.cursor = 'default';
// 重新启用相机旋转
viewer.scene.screenSpaceCameraController.enableRotate = true;
}
}, Cesium.ScreenSpaceEventType.LEFT_UP);
return {
mainEntity: mainEntity,
childEntities: childEntities
};
}
// 创建联动实体组
const linkedEntitiesGroup = createLinkedEntities();
2. 自定义交互控制器
// ⭐创建自定义交互控制器
function createEntityControlPanel(entity) {
// 创建控制面板
let controlPanel = document.getElementById('entity-control-panel');
if (!controlPanel) {
controlPanel = document.createElement('div');
controlPanel.id = 'entity-control-panel';
controlPanel.style.position = 'absolute';
controlPanel.style.bottom = '20px';
controlPanel.style.left = '20px';
controlPanel.style.backgroundColor = 'rgba(40, 40, 40, 0.9)';
controlPanel.style.color = 'white';
controlPanel.style.padding = '10px';
controlPanel.style.borderRadius = '5px';
controlPanel.style.maxWidth = '300px';
controlPanel.style.display = 'none';
document.body.appendChild(controlPanel);
}
// 更新控制面板内容
controlPanel.innerHTML = `
<h3>${entity.name || entity.id || '未命名实体'}</h3>
<div>
<label>可见性:</label>
<input type="checkbox" id="entity-visibility" ${entity.show ? 'checked' : ''}>
</div>
<div>
<label>透明度:</label>
<input type="range" id="entity-opacity" min="0" max="1" step="0.1" value="1">
</div>
<div>
<label>高度:</label>
<input type="range" id="entity-height" min="0" max="1000" step="10" value="0">
</div>
<div class="button-group">
<button id="entity-zoom">缩放至</button>
<button id="entity-delete">删除</button>
</div>
`;
// 显示控制面板
controlPanel.style.display = 'block';
// 获取控制元素
const visibilityCheckbox = document.getElementById('entity-visibility');
const opacitySlider = document.getElementById('entity-opacity');
const heightSlider = document.getElementById('entity-height');
const zoomButton = document.getElementById('entity-zoom');
const deleteButton = document.getElementById('entity-delete');
// 设置初始值
if (entity.position) {
const position = entity.position.getValue(viewer.clock.currentTime);
const cartographic = Cesium.Cartographic.fromCartesian(position);
heightSlider.value = cartographic.height;
}
// 处理可见性变化
visibilityCheckbox.addEventListener('change', function() {
entity.show = this.checked;
});
// 处理透明度变化
opacitySlider.addEventListener('input', function() {
const opacity = parseFloat(this.value);
// 根据实体类型应用透明度
if (entity.billboard) {
entity.billboard.color = Cesium.Color.WHITE.withAlpha(opacity);
} else if (entity.polygon && entity.polygon.material) {
// 对于材质,我们需要保存原始颜色
if (!entity._originalMaterialColor) {
if (entity.polygon.material instanceof Cesium.ColorMaterialProperty) {
entity._originalMaterialColor = entity.polygon.material.color.getValue().clone();
} else {
entity._originalMaterialColor = Cesium.Color.BLUE;
}
}
entity.polygon.material = entity._originalMaterialColor.withAlpha(opacity);
} else if (entity.polyline && entity.polyline.material) {
if (!entity._originalLineColor) {
if (entity.polyline.material instanceof Cesium.ColorMaterialProperty) {
entity._originalLineColor = entity.polyline.material.color.getValue().clone();
} else {
entity._originalLineColor = Cesium.Color.WHITE;
}
}
entity.polyline.material = entity._originalLineColor.withAlpha(opacity);
}
});
// 处理高度变化
heightSlider.addEventListener('input', function() {
const height = parseFloat(this.value);
if (entity.position) {
// 获取当前位置
const position = entity.position.getValue(viewer.clock.currentTime);
const cartographic = Cesium.Cartographic.fromCartesian(position);
// 更新高度
const newPosition = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
height
);
entity.position = newPosition;
}
});
// 缩放到实体
zoomButton.addEventListener('click', function() {
viewer.flyTo(entity);
});
// 删除实体
deleteButton.addEventListener('click', function() {
viewer.entities.remove(entity);
controlPanel.style.display = 'none';
});
return controlPanel;
}
// 使用控制面板
handler.setInputAction(function(click) {
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id;
createEntityControlPanel(entity);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
3. 实体状态切换与动画
// ⭐实现实体状态切换与动画
function createAnimatedEntity(position, options = {}) {
// 状态定义
const states = {
normal: {
color: Cesium.Color.GREEN,
scale: 1.0,
pulseRate: 0.0
},
warning: {
color: Cesium.Color.YELLOW,
scale: 1.2,
pulseRate: 0.5
},
alert: {
color: Cesium.Color.RED,
scale: 1.5,
pulseRate: 1.0
}
};
// 当前状态
let currentState = 'normal';
// 创建状态指示器实体
const entity = viewer.entities.add({
position: position,
// 点
point: {
pixelSize: 20,
color: new Cesium.CallbackProperty(function(time) {
const state = states[currentState];
// 如果有脉冲效果
if (state.pulseRate > 0) {
const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);
const pulseFactor = 0.5 + 0.5 * Math.sin(seconds * state.pulseRate * Math.PI);
// 在原始颜色和白色之间插值
return Cesium.Color.lerp(
state.color,
Cesium.Color.WHITE,
pulseFactor * 0.5,
new Cesium.Color()
);
}
return state.color;
}, false),
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
},
// 圆形
ellipse: {
semiMajorAxis: 100,
semiMinorAxis: 100,
material: new Cesium.CallbackProperty(function(time) {
const state = states[currentState];
// 如果有脉冲效果
if (state.pulseRate > 0) {
const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);
const pulseFactor = 0.5 + 0.5 * Math.sin(seconds * state.pulseRate * Math.PI);
// 动态创建材质
return new Cesium.ColorMaterialProperty(
Cesium.Color.fromAlpha(state.color, 0.3 + 0.2 * pulseFactor)
);
}
return new Cesium.ColorMaterialProperty(
Cesium.Color.fromAlpha(state.color, 0.3)
);
}, false),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
outline: true,
outlineColor: new Cesium.CallbackProperty(function() {
return states[currentState].color;
}, false)
},
// 标签
label: {
text: options.text || '状态指示器',
font: '14px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -30),
backgroundColor: new Cesium.CallbackProperty(function() {
return Cesium.Color.fromAlpha(states[currentState].color, 0.7);
}, false),
showBackground: true,
scale: new Cesium.CallbackProperty(function(time) {
const state = states[currentState];
// 如果有脉冲效果
if (state.pulseRate > 0) {
const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);
const pulseFactor = 0.5 + 0.5 * Math.sin(seconds * state.pulseRate * Math.PI);
return state.scale * (1.0 + pulseFactor * 0.1);
}
return state.scale;
}, false)
},
// 自定义属性
properties: {
state: currentState
}
});
// 切换状态方法
function setState(stateName) {
if (states[stateName]) {
currentState = stateName;
entity.properties.state = currentState;
}
}
// 添加点击事件处理
handler.setInputAction(function(click) {
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && pickedObject.id === entity) {
// 循环切换状态
const stateNames = Object.keys(states);
const currentIndex = stateNames.indexOf(currentState);
const nextIndex = (currentIndex + 1) % stateNames.length;
setState(stateNames[nextIndex]);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
return {
entity: entity,
setState: setState
};
}
// 创建动态状态指示器
const stateIndicator = createAnimatedEntity(
Cesium.Cartesian3.fromDegrees(116.4, 39.9),
{ text: '状态指示器' }
);
// 状态自动切换示例
let stateIndex = 0;
const states = ['normal', 'warning', 'alert'];
// 每5秒切换一次状态
setInterval(() => {
stateIndex = (stateIndex + 1) % states.length;
stateIndicator.setState(states[stateIndex]);
}, 5000);
4. 高级拖放实体交互
// ⭐实现拖放交互 - 支持在目标区域放置拖动的实体
function setupDragAndDropInteraction() {
// 创建一些可拖动实体
const draggableEntities = [];
// 生成不同类型的可拖动实体
function createDraggableEntity(position, options = {}) {
const entity = viewer.entities.add({
position: position,
billboard: {
image: options.image || './images/draggable.png',
scale: 0.8,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM
},
label: {
text: options.text || '可拖动',
font: '12px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -30)
},
properties: {
isDraggable: true,
type: options.type || 'default',
value: options.value || 0
}
});
draggableEntities.push(entity);
return entity;
}
// 创建目标区域
function createDropTarget(position, options = {}) {
const radius = options.radius || 200;
const entity = viewer.entities.add({
position: position,
ellipse: {
semiMajorAxis: radius,
semiMinorAxis: radius,
material: Cesium.Color.fromAlpha(options.color || Cesium.Color.BLUE, 0.3),
outline: true,
outlineColor: options.color || Cesium.Color.BLUE,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
},
label: {
text: options.text || '目标区域',
font: '16px sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
},
properties: {
isDropTarget: true,
acceptTypes: options.acceptTypes || ['default'],
radius: radius,
onDrop: options.onDrop
}
});
return entity;
}
// 拖拽状态
let isDragging = false;
let draggedEntity;
let originalPosition;
let hoveredDropTarget;
// 鼠标按下开始拖拽
handler.setInputAction(function(click) {
if (isDragging) return;
const pickedObject = viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
const entity = pickedObject.id;
// 检查是否为可拖动实体
if (entity.properties && entity.properties.isDraggable &&
entity.properties.isDraggable.getValue()) {
// 开始拖拽
isDragging = true;
draggedEntity = entity;
// 保存原始位置(用于取消拖拽)
originalPosition = entity.position.getValue(viewer.clock.currentTime).clone();
// 修改视觉样式表示拖拽状态
if (entity.billboard) {
entity._originalScale = entity.billboard.scale.getValue();
entity.billboard.scale = entity._originalScale * 1.3;
}
// 修改光标样式
viewer.canvas.style.cursor = 'grabbing';
// 禁用相机旋转
viewer.scene.screenSpaceCameraController.enableRotate = false;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 鼠标移动处理拖拽
handler.setInputAction(function(movement) {
if (!isDragging || !draggedEntity) return;
// 获取新位置
const ray = viewer.camera.getPickRay(movement.endPosition);
const newPosition = viewer.scene.globe.pick(ray, viewer.scene);
if (Cesium.defined(newPosition)) {
// 更新实体位置
draggedEntity.position = newPosition;
// 检测是否悬停在目标区域上
checkDropTargets(newPosition);
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 检测目标区域
function checkDropTargets(position) {
// 清除之前的悬停目标
if (hoveredDropTarget) {
// 恢复原始外观
hoveredDropTarget.ellipse.material = Cesium.Color.fromAlpha(
hoveredDropTarget.ellipse.outlineColor.getValue(),
0.3
);
hoveredDropTarget = undefined;
}
// 获取所有目标区域
const entities = viewer.entities.values;
let closestTarget = null;
let minDistance = Number.MAX_VALUE;
for (let i = 0; i < entities.length; i++) {
const entity = entities[i];
// 检查是否为目标区域
if (entity.properties && entity.properties.isDropTarget &&
entity.properties.isDropTarget.getValue()) {
// 计算拖拽实体与目标区域的距离
const targetPosition = entity.position.getValue(viewer.clock.currentTime);
const distance = Cesium.Cartesian3.distance(position, targetPosition);
// 检查是否在目标区域内(半径范围内)
const radius = entity.properties.radius.getValue();
if (distance < radius && distance < minDistance) {
// 检查类型兼容性
const acceptTypes = entity.properties.acceptTypes.getValue();
const entityType = draggedEntity.properties.type.getValue();
if (acceptTypes.includes(entityType) || acceptTypes.includes('*')) {
closestTarget = entity;
minDistance = distance;
}
}
}
}
// 如果找到了有效目标区域
if (closestTarget) {
hoveredDropTarget = closestTarget;
// 修改外观以显示可放置
hoveredDropTarget.ellipse.material = Cesium.Color.fromAlpha(
Cesium.Color.GREEN,
0.5
);
}
}
// 鼠标释放处理放置
handler.setInputAction(function() {
if (!isDragging || !draggedEntity) return;
// 如果拖拽到了目标区域
if (hoveredDropTarget) {
// 执行放置回调
const onDrop = hoveredDropTarget.properties.onDrop;
if (onDrop) {
const callback = onDrop.getValue();
if (typeof callback === 'function') {
callback(draggedEntity, hoveredDropTarget);
}
}
// 重置目标区域外观
hoveredDropTarget.ellipse.material = Cesium.Color.fromAlpha(
hoveredDropTarget.ellipse.outlineColor.getValue(),
0.3
);
} else {
// 没有放置到有效区域,恢复原位
draggedEntity.position = originalPosition;
}
// 恢复拖拽实体外观
if (draggedEntity.billboard && draggedEntity._originalScale) {
draggedEntity.billboard.scale = draggedEntity._originalScale;
draggedEntity._originalScale = undefined;
}
// 重置状态
isDragging = false;
draggedEntity = undefined;
originalPosition = undefined;
hoveredDropTarget = undefined;
// 恢复光标样式
viewer.canvas.style.cursor = 'default';
// 重新启用相机旋转
viewer.scene.screenSpaceCameraController.enableRotate = true;
}, Cesium.ScreenSpaceEventType.LEFT_UP);
// 创建一些示例实体
createDraggableEntity(
Cesium.Cartesian3.fromDegrees(116.35, 39.85),
{ text: '拖我-A类', type: 'typeA', value: 100 }
);
createDraggableEntity(
Cesium.Cartesian3.fromDegrees(116.40, 39.85),
{ text: '拖我-B类', type: 'typeB', value: 200 }
);
// 创建目标区域
createDropTarget(
Cesium.Cartesian3.fromDegrees(116.35, 39.95),
{
text: 'A类目标',
color: Cesium.Color.BLUE,
acceptTypes: ['typeA'],
radius: 300,
onDrop: function(draggedEntity, target) {
console.log('A类实体被放置:', draggedEntity.properties.value.getValue());
// 处理放置逻辑
target.label.text = target.label.text.getValue() + ' +' +
draggedEntity.properties.value.getValue();
}
}
);
createDropTarget(
Cesium.Cartesian3.fromDegrees(116.45, 39.95),
{
text: 'B类目标',
color: Cesium.Color.ORANGE,
acceptTypes: ['typeB'],
radius: 300,
onDrop: function(draggedEntity, target) {
console.log('B类实体被放置:', draggedEntity.properties.value.getValue());
// 处理放置逻辑
target.label.text = target.label.text.getValue() + ' +' +
draggedEntity.properties.value.getValue();
}
}
);
createDropTarget(
Cesium.Cartesian3.fromDegrees(116.40, 40.0),
{
text: '通用目标',
color: Cesium.Color.PURPLE,
acceptTypes: ['*'], // 接受所有类型
radius: 300,
onDrop: function(draggedEntity, target) {
console.log('通用实体被放置:',
draggedEntity.properties.type.getValue(),
draggedEntity.properties.value.getValue());
// 处理放置逻辑
draggedEntity.show = false; // 隐藏放置的实体
target.label.text = target.label.text.getValue() + ' +' +
draggedEntity.properties.value.getValue();
}
}
);
}
// 初始化拖放交互
setupDragAndDropInteraction();
📝 Entity交互最佳实践总结
创建响应式交互:
- 为不同的交互设计适当的视觉反馈
- 使用光标样式变化指示可交互状态
- 对触摸设备提供适当的交互体验
性能优化:
- 减少实体数量,考虑使用Entity clustering
- 对动态更新的实体使用CallbackProperty缓存值
- 使用事件节流和防抖技术处理频繁事件
用户体验提升:
- 提供清晰的交互反馈(高亮、动画)
- 设计直观的拖拽和选择行为
- 提供撤销/恢复操作的能力
高级功能实现:
- 将相关实体联系起来实现联动效果
- 使用事件委托处理大量实体的交互
- 结合HTML UI提供更丰富的交互体验
维护和调试:
- 为交互状态添加适当的日志记录
- 实现交互状态的可视化调试
- 采用模块化设计使交互系统易于维护
🧩 记忆助手
实体交互五步骤: 拾取(Pick) → 识别(Identify) → 反馈(Feedback) → 操作(Operate) → 更新(Update)
拖拽实现三要素: 按下(开始) → 移动(更新) → 释放(完成)
事件处理核心类: ScreenSpaceEventHandler处理所有交互