前言
本文将复现并改造官方案例
3D Tiles Feature Picking
[1]
,首先加载了 Cesium Ion 中的“纽约市三维建筑物数据”,具体如下图所示:通过查看数据描述内容,这样我们便可以读懂每个属性字段的含义啦。
注:关于如何加载 Cesium Ion 中的数据请移步至我曾在
加载官方在 Cesium Ion 中提供的三维模型数据
一文中的介绍。
实现效果
针对官方示例,这里进行了改造,使用了曾在
使用 Cesium 实现属性弹窗的功能
一文中使用的
Popup
弹窗组件。
代码实现
首先,需要先加载 3d 建筑物数据:
/**
* 加载建筑物 3D tiles 数据
*/
async function addTilesetData() {
try {
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343);
viewer.scene.primitives.add(tileset);
} catch (err) {
ElMessage.error(`tileset数据加载出错: ${err}`);
}
}
关于悬停或点击高亮要素,代码实现如下:我们需要分别处理
支持轮廓
(
silhouette
)效果和
不支持轮廓
(
silhouette
)效果两种情况。经过我的测试,手机上的edge和chrome都是支持的。这里我们用到了
PostProcessStageLibrary
[2]
类的
createEdgeDetectionStage()
和
createSilhouetteStage()
方法来实现高亮的效果,在代码中我作了相应的注释。
注:关于Cesium中的后处理阶段,请阅读我在知乎上找到的一篇技术文章,进行进一步的学习:
https://zhuanlan.zhihu.com/p/491788997
。
function init() {
clickHandler = viewer.screenSpaceEventHandler.getInputAction(
Cesium.ScreenSpaceEventType.LEFT_CLICK
);
/**
* 检查当前场景是否支持轮廓(silhouette)效果
* 若支持,鼠标悬停于要素上方时,轮廓显示为蓝色,鼠标单击要素时,轮廓显示为绿色;
* 否则,鼠标悬停于要素上方时,轮廓显示为黄色,鼠标单击要素时,轮廓显示为绿色。
*/
if (Cesium.PostProcessStageLibrary.isSilhouetteSupported(viewer.scene)) {
ElMessage({
message: "当前场景支持轮廓效果",
type: "success",
});
//创建一个检测轮廓的后处理阶段
const silhouetteBlue =
Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
silhouetteBlue.uniforms.color = Cesium.Color.BLUE; //高亮轮廓显示的颜色,默认值为Color.Black
silhouetteBlue.uniforms.length = 0.01; //边缘的长度(以像素为单位)。默认值为 0.5
silhouetteBlue.selected = [];
const silhouetteGreen =
Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
silhouetteGreen.uniforms.color = Cesium.Color.LIME; //酸橙绿色
silhouetteGreen.uniforms.length = 0.01;
silhouetteGreen.selected = [];
//创建一个应用轮廓效果的后处理阶段(复合的后处理阶段)
const postProcessStageComposite =
Cesium.PostProcessStageLibrary.createSilhouetteStage([
silhouetteBlue,
silhouetteGreen,
]);
//将后期处理阶段添加到集合中
viewer.scene.postProcessStages.add(postProcessStageComposite);
//在鼠标悬停于要素上时将其轮廓颜色设置为蓝色
viewer.screenSpaceEventHandler.setInputAction((movement) => {
//若当前要素之前被高亮了,取消其高亮显示
silhouetteBlue.selected = [];
const pickedFeature = viewer.scene.pick(movement.endPosition);
if (!Cesium.defined(pickedFeature)) {
hoverRef.value.style.left = "-400px";
hoverRef.value.style.bottom = "-200px";
return;
}
//TODO:更新悬浮窗信息
// updateHoverWindow(pickedFeature, movement.endPosition);
if (pickedFeature !== selected.feature) {
silhouetteBlue.selected = [pickedFeature];
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//在鼠标点击要素时将其轮廓颜色设置为绿色
viewer.screenSpaceEventHandler.setInputAction((movement) => {
silhouetteGreen.selected = [];
const pickedFeature = viewer.scene.pick(movement.position);
if (!Cesium.defined(pickedFeature)) {
clickHandler(movement);
return;
}
//TODO:更新弹窗位置和信息
updatePopup(pickedFeature, movement);
//若单击选择的对象已被选择了,则直接返回
if (silhouetteGreen.selected[0] === pickedFeature) {
return;
}
//我们先获取悬停高亮的要素
const highlightedFeature = silhouetteBlue.selected[0];
if (pickedFeature === highlightedFeature) {
silhouetteBlue.selected = []; //若点击的要素和悬停的要素相同,则取消悬停高亮的效果
}
//添加点击高亮的效果
silhouetteGreen.selected = [pickedFeature];
//设置要显示选择指示器的对象实例,这里我们不用选择指示器的话可以省略这行代码
// viewer.selectedEntity = selectedEntity;
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
} else {
ElMessage({
message: "当前场景不支持轮廓效果",
type: "warning",
});
//若不支持上述轮廓效果,则直接更改要素颜色
const highlighted = {
feature: undefined,
originalColor: new Cesium.Color(),
};
viewer.screenSpaceEventHandler.setInputAction((movement) => {
if (Cesium.defined(highlighted.feature)) {
highlighted.feature.color = highlighted.originalColor;
highlighted.feature = undefined;
}
const pickedFeature = viewer.scene.pick(movement.endPosition);
if (!Cesium.defined(pickedFeature)) {
return;
}
//TODO:更新悬浮窗信息
// updateHoverWindow(pickedFeature, movement.endPosition);
if (pickedFeature !== selected.feature) {
highlighted.feature = pickedFeature;
//将选择的要素本身的颜色存储在高亮要素的originalColor,以便鼠标不在该要素悬停时恢复原有的颜色
Cesium.Color.clone(pickedFeature.color, highlighted.originalColor);
pickedFeature.color = Cesium.Color.YELLOW; //将要素颜色改为黄色
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
viewer.screenSpaceEventHandler.setInputAction((movement) => {
if (Cesium.defined(selected.feature)) {
selected.feature.color = selected.originalColor;
selected.feature = undefined;
}
const pickedFeature = viewer.scene.pick(movement.position);
if (!Cesium.defined(pickedFeature)) {
clickHandler(movement);
return;
}
//TODO:更新属性弹窗
updatePopup(pickedFeature, movement);
if (selected.feature === pickedFeature) {
return;
}
selected.feature = pickedFeature;
if (pickedFeature === highlighted.feature) {
Cesium.Color.clone(highlighted.originalColor, selected.originalColor);
highlighted.feature = undefined;
} else {
Cesium.Color.clone(pickedFeature.color, selected.originalColor);
}
pickedFeature.color = Cesium.Color.LIME;
//设置要显示选择指示器的对象实例,这里我们不用选择指示器的话可以省略这行代码
// viewer.selectedEntity = selectedEntity;
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
}
}
我们在解析
Cesium3DTileFeature
[3]
获取其所有属性名,然后再借助
getPropertyIds()
可以获取其所有属性名,然后再借助
getProperty(propertyId)
获取对应的属性值。
const propertyIds = pickedFeature.getPropertyIds();
const length = propertyIds.length;
for (let i = 0; i < length; ++i) {
const propertyId = propertyIds[i];
console.log(`${propertyId}: ${pickedFeature.getProperty(propertyId)}`);
}
处理弹窗属性代码如下:
function updatePopup(pickedFeature, e) {
const