无论是前端系统,还是二维/三维GIS应用系统,都离不开各种事件的应用,尤其是鼠标的单击、双击事件。 Cesium 根据事件的类型、用途,将事件应用分成了三大类。一种是以鼠标操作(左键、中键、右键操作等)为主的 ScreenSpaceEventHandler 类,另一种是通用的事件类 Event ,该类通常在容器类内部实例化,并作为某个属性的类型直接被调用,比如 viewer.clock.onTick、viewer.selectedEntityChanged、camera.moveStart、camera.moveEnd、scene.preRender、cesium3DTileset.allTilesLoaded等这些属性都是 Event 类型;最后一种则是相机控制方面的事件类 screenSpaceCameraController,该类通过与CameraEventType类配合实现相机的控制。下面我主要介绍几个比较常用的事件应用。
鼠标事件
鼠标事件可以说是GIS系统里面关于事件应用最常用的一个了,点击地图上的某一个 graphic,并获取其属性信息,就是鼠标事件应用最熟悉的一个场景了。Cesium为实现这一功能,分成了两个过程。首先,传递viewer.canvas参数实例化ScreenSpaceEventHandler类,比如实例化后的名称为handler;其次,为 handler 注册鼠标事件的监听;最后,在监听事件的回调方法中获取 event.position ,并将其作为参数执行scene.pick 方法获取对应的选中对象。
1)ScreenSpaceEventHandler
对 ScreenSpaceEventHandler 类进行实例化,注册事件、注销事件代码如下:
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
let eventType = Cesium.ScreenSpaceEventType.LEFT_CLICK;
//注册事件
handler.setInputAction((event) => {
console.log(event);
}, eventType);
//注销事件
handler.removeInputAction(eventType);
上面代码中的事件类型 eventType 直接采用了 ScreenSpaceEventType 中的常量,示例中注册鼠标左击事件。根据实际场景需求,eventType 的值可按需赋值,包括以下几种:
2)要素拾取
假如应用场景是点击要素获取其属性信息,这个时候就需要在鼠标左键的注册事件中获取 event 结果,核心代码如下:
var picked = viewer.scene.pick(event.position);
这个时候就可以根据获取到的对象类型进行操作了。
if (Cesium.defined(picked)) {
if (picked.id && picked.id instanceof Cesium.Entity) {
console.log("选中了Entity");
}
if (picked.primitive instanceof Cesium.Primitive) {
console.log("选中了Primitive");
}
if (picked.primitive instanceof Cesium.Model) {
console.log("选中了模型");
}
if (picked instanceof Cesium.Cesium3DTileFeature) {
console.log("选中了3DTile");
}
}
3)Entity选择
Cesium 针对于通过 Entity 方式添加的几何图形,提供了一个非常方便的属性selectedEntityChanged(viewer类事件类型的属性)来帮助我们获取选中的Entity,通过这个属性,用户无需再写注册鼠标事件了。示例代码如下:
viewer.selectedEntityChanged.addEventListener(function (entity) {
console.log(entity.id);
});
在某些场景中,我们可能需要跟踪某一辆车或某一个人员,这是我们可以把车辆或人员Entity赋给viewer.trackedEntity,相机就会自动跟踪你绑定的Entity了。实际场景中,我们并不是始终跟踪某一个车辆,有时需要切换到另一个车辆,当你切换正在跟踪的车辆时,其实我们触发了viewer.trackedEntityChanged事件,这样我们就可以在此事件中实时获取车辆行驶状态了。
viewer.trackedEntityChanged.addEventListener(function (entity) {
console.log(entity.id);
});
相机事件
相机控制事件类 screenSpaceCameraController 并不是像鼠标事件相关类 ScreenSpaceEventHandler 那样需要提前实例化。Cesium在Viewer类的实例化过程中,也实例化了其他很多类,其中就包括ScreenSpaceCameraController类,并把实例化结果赋值给了viewer.scene.screenSpaceCameraController。所以,我们直接去操作viewer.scene.screenSpaceCameraController就可以了。
1)通过鼠标控制
通过鼠标控制相机的方式取决于CameraEventType的常量,包括以下几种:
其中,鼠标的默认操作如下:
下面我们修改默认的鼠标操作,实现中键缩放、右键旋转。核心代码如下:
viewer.scene.screenSpaceCameraController.tiltEventTypes = [
Cesium.CameraEventType.RIGHT_DRAG,
Cesium.CameraEventType.PINCH,
{
eventType: Cesium.CameraEventType.LEFT_DRAG,
modifier: Cesium.KeyboardEventModifier.CTRL,
},
{
eventType: Cesium.CameraEventType.RIGHT_DRAG,
modifier: Cesium.KeyboardEventModifier.CTRL,
},
];
viewer.scene.screenSpaceCameraController.zoomEventTypes = [
Cesium.CameraEventType.MIDDLE_DRAG,
Cesium.CameraEventType.WHEEL,
Cesium.CameraEventType.PINCH,
];
2)通过键盘控制
主要是通过操作键盘实现相机的漫游,比如前进、后退、向上、向下等等,是不是感觉自己在玩穿越火线游戏,响起熟悉的声音:headshot、double kill、multi kill、fire in the hole、......,打住!!!狂点鼠标也不会发射子弹的。好了,我们把感觉拉回来现场,继续学习Cesium。实现键盘漫游主要是通过键盘调用相机的moveForward、moveBackward、moveLeft、moveRight、moveUp、moveDown方法。下面为部分核心代码,查看完整代码请浏览GitHub地址https://github.com/ls870061011/cesium_training/tree/main/examples中的3_2部分。
viewer.clock.onTick.addEventListener(function (clock) {
var camera = viewer.camera;
if (flags.looking) {
var width = canvas.clientWidth;
var height = canvas.clientHeight;
// Coordinate (0.0, 0.0) will be where the mouse was clicked.
var x = (mousePosition.x - startMousePosition.x) / width;
var y = -(mousePosition.y - startMousePosition.y) / height;
var lookFactor = 0.05;
camera.lookRight(x * lookFactor);
camera.lookUp(y * lookFactor);
}
// Change movement speed based on the distance of the camera to the surface of the ellipsoid.
var cameraHeight = ellipsoid.cartesianToCartographic(camera.position)
.height;
var moveRate = cameraHeight / 100.0;
if (flags.moveForward) {
camera.moveForward(moveRate);
}
if (flags.moveBackward) {
camera.moveBackward(moveRate);
}
if (flags.moveUp) {
camera.moveUp(moveRate);
}
if (flags.moveDown) {
camera.moveDown(moveRate);
}
if (flags.moveLeft) {
camera.moveLeft(moveRate);
}
if (flags.moveRight) {
camera.moveRight(moveRate);
}
});
场景渲染事件
场景渲染事件主要包括以下四种:
- scene.preUpdate: 更新或呈现场景之前将引发的事件
- scene.postUpdate: 场景更新后以及渲染场景之前立即引发的事件
- scene.preRender: 场景更新后以及渲染场景之前将引发的事件
- scene.postRender: 渲染场景后立即引发的事件
事件的添加和移除代码示例如下:
viewer.scene.preUpdate.addEventListender(callbackFunc);
viewer.scene.preUpdate.removeEventListender(callbackFunc);
比如我们自己写了一个指北针、标签,都可以在scene.preRender监听事件的回调函数中更新指北针状态或者是标签的位置信息。下面的部分核心代码,为场景重新选然后更新自定义标签位置。
viewer.scene.scene-preRender.addEventListener(() => {
if (positions instanceof Array && htmlSize instanceof Array) {
positions.map((ele, index) => {
const html = document.getElementById(`infoTip${index}`);
if (html) {
const canvasPosition = ConversionUtil.degreesToCartesian2(ele.x, ele.y, ele.z);
if (canvasPosition) {
html.style.top = `${canvasPosition.y - htmlSize[index].offsetHeight}px`;
html.style.left = `${canvasPosition.x - htmlSize[index].offsetWidth}px`;
}
}
});
}
)