三维地球可视化从入门到进阶 - 鼠标键盘事件、相机事件、数据加载事件、场景加载事件

2,568 阅读10分钟

本文为稀土掘金技术社区首发签约文章,14 天内禁止转载,14 天后未获授权禁止转载,侵权必究!

哈喽,大家好 我是 xy👨🏻‍💻。这篇文章是 Cesium 三维地球可视化从入门到进阶专栏的第三篇,Cesium 中的事件数量繁多、类型各异,按照类型进行分类,可以分为如下几种:鼠标键盘事件相机事件数据加载事件场景加载事件

Cesium 中的事件数量繁多、类型各异,本章内容仅介绍一些常用的事件及其使用方法。

Cesium 事件简介

Cesium 中的事件按照类型进行分类,可以分为如下几种:

  • 鼠标键盘事件
  • 相机事件
  • 数据加载事件
  • 场景加载事件

按照事件的使用方式进行分类,可以分为如下两种:

  • 创建事件处理器Handler并指定事件触发类型定义事件,如与鼠标键盘事件相关的屏幕空间事件处理器ScreenSpaceEventHandler
  • Cesium 中已经定义好的事件回调,只需要添加事件监听的回调函数即可,如监听 3D Tiles 的瓦片全部加载完毕的回调函数allTilesLoaded

鼠标键盘事件

要定义鼠标键盘事件,首先要创建一个屏幕空间事件处理器 ScreenSpaceEventHandler,该方法需要传入一个HTMLCanvasElement用于指定事件处理器应用在哪个 Canvas 元素上,一般情况下指定为viewer.scene.canvas,创建一个事件处理器的完整代码如下:

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

这样就成功创建了一个事件处理器,接下来就要使用事件处理器上的 setInputAction 方法来定义事件触发的类型和执行事件触发的回调函数。

其中鼠标的事件类型叫做“屏幕空间事件类型ScreenSpaceEventType”,键盘的事件类型叫做“键盘事件修饰符KeyboardEventModifier

屏幕空间事件类型 ScreenSpaceEventType

因为 Cesium 的操作分为鼠标操作和触摸屏操作两种模式,所以屏幕空间事件类型ScreenSpaceEventType 分为两类:

  1. 鼠标操作
事件名称类型描述
LEFT_DOWNNumber鼠标左键按下
LEFT_UPNumber鼠标左键弹起
LEFT_CLICKNumber鼠标左键单击
LEFT_DOUBLE_CLICKNumber鼠标左键双击
RIGHT_DOWNNumber鼠标右键按下
RIGHT_UPNumber鼠标右键弹起
RIGHT_CLICKNumber鼠标右键单击
MIDDLE_DOWNNumber鼠标中键按下
MIDDLE_UPNumber鼠标中键弹起
MIDDLE_CLICKNumber鼠标中键单击
MOUSE_MOVENumber鼠标移动
WHEELNumber鼠标滚轮滚动
  1. 触摸屏操作(仅作了解,本节不具体介绍)
事件名称类型描述
PINCH_STARTNumber触摸屏开始两指触摸
PINCH_ENDNumber触摸屏结束两指触摸
PINCH_MOVENumber触摸屏两指触摸移动

键盘事件修饰符 KeyboardEventModifier

事件名称类型描述
SHIFTNumberSHIFT键被按住
CTRLNumberCTRL键被按住
ALTNumberALT键被按住

事件注册方法 setInputAction

事件注册方法setInputAction用于根据事件类型定义事件的触发方式并执行对应的回调函数,该方法的描述如下:

属性名称类型描述
action指定不同的type值会触发不同的回调函数,详见官方文档 setInputAction事件回调函数
type屏幕空间事件类型ScreenSpaceEventType屏幕空间事件类型
modifier键盘事件修饰符KeyboardEventModifier可选项,键盘事件修饰符

前面提到想要定义一个事件,首先要创建一个事件处理器handler,再使用事件处理器handler上的setInputAction方法来指定事件的类型和事件的回调,如下面代码:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((movement) => {
  // do something
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)

至于在回调函数中内做些什么,是由触发的回调函数所决定的。

在方法描述中可以看到键盘事件修饰符modifier为可选项,在 Cesium 中键盘事件类型是无法单独使用的,需要和鼠标事件类型一起使用,如下代码表示按住键盘CTRL键并且点击鼠标左键才能触发事件回调函数:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((movement) => {
  // do something
}, Cesium.ScreenSpaceEventType.LEFT_CLICK, Cesium.KeyboardEventModifier.CTRL)

事件回调函数

由于触发事件的类型各异,所以相应的也有不同的事件回调来处理不同的事件,事件回调函数分为如下几种:

  • PositionedEventCallback(event)PositionedEventCallback(event),所触发的事件类型为所有鼠标点击事件:LEFT_DOWNLEFT_UPLEFT_CLICKLEFT_DOUBLE_CLICKRIGHT_DOWNRIGHT_UPRIGHT_CLICKMIDDLE_DOWNMIDDLE_UPMIDDLE_CLICK
  • MotionEventCallback(event)MotionEventCallback(event),所触发的事件类型为鼠标移动事件:MOUSE_MOVE
  • WheelEventCallback(delta)WheelEventCallback(delta),所触发的事件类型为鼠标滚轮滚动事件:WHEEL
  • TwoPointEventCallback(event)(仅作了解,本节不具体介绍):TwoPointEventCallback(event),所触发的事件类型为触摸屏两指触摸事件:PINCH_STARTPINCH_END
  • TwoPointMotionEventCallback(event)(仅作了解,本节不具体介绍):TwoPointMotionEventCallback(event),所触发的事件类型为触摸屏两指移动事件:PINCH_MOVE

PositionedEventCallback(event)

该回调函数会返回一个Cartesian2对象:

参数名称类型描述
positionCartesian2鼠标点击的笛卡尔平面直角坐标

代码如下:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((position) => {
  // position中包含一个Cartesian2对象
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)

MotionEventCallback(event)

该回调函数会返回一个移动起点的Cartesian2对象和一个移动终点的Cartesian2对象:

参数名称类型描述
startPositionCartesian2鼠标移动起点的笛卡尔平面直角坐标
endPositionCartesian2鼠标移动终点的笛卡尔平面直角坐标

代码如下:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((positions) => {
  // positions中包含startPosition和endPosition两个Cartesian2对象
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

WheelEventCallback(delta)

该回调函数会返回一个delta值,为鼠标滚轮滚动的值:

参数名称类型描述
deltaNumber鼠标滚轮滚动的值

代码如下:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((delta) => {
  // delta为鼠标滚轮滚动的值
}, Cesium.ScreenSpaceEventType.WHEEL)

移除事件 removeInputAction

定义 Cesium 中的事件往往是执行某一个具体的操作,如鼠标点击拾取,当操作执行结束后需要移除该事件,避免对后续操作造成影响,移除事件的方法为removeInputAction,该方法的描述如下:

参数名称类型描述
typeScreenSpaceEventType屏幕空间事件类型
modifierKeyboardEventModifier可选项,键盘事件修饰符

使用方法如下:

// 移除鼠标左键单击事件
handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)

相机事件

与相机相关的事件有三个:

由于这三个事件都为 Cesium.Event() 类型,因此需要使用addEventListener方法添加事件监听并指定回调函数,如下面代码:

viewer.camera.changed.addEventListener(() => {
  // camera changed
})

viewer.camera.moveStart.addEventListener(() => {
  // camera moveStart
})

viewer.camera.moveEnd.addEventListener(() => {
  // camera moveEnd
})

数据加载事件

在 Cesium 中,常见的数据加载类型和相关事件包含以下几种:

  • Cesium3DTileset:常见事件如下:

    • allTilesLoaded:当所有满足屏幕空间误差的瓦片集加载完成后触发,此时瓦片集已经完全加载;
    • initialTilesLoaded:当所有满足屏幕空间误差的瓦片集加载完成后触发,此时初始视图已经加载完成,该方法的触发在allTilesLoaded之后;
    • loadProgress:当请求瓦片时触发,该方法会提供请求挂起的瓦片数量numberOfPendingRequests和正在处理的瓦片数量numberOfTilesProcessing两个参数;
    • tileFailed:当一个瓦片请求失败时触发,该方法会提供一个error对象,包含请求失败的瓦片 url 和请求失败的原因message
    • tileUnload:当一个瓦片卸载后触发,卸载指的是从内存中删除,被卸载的原因为设置的maximumMemoryUsage大小不足,或者是trimLoadedTiles()方法被调用。
    • tileLoad:当一个瓦片加载后触发;
    • tileVisible:当一个瓦片可见时触发。
  • Entity:常见事件如下:

  • DataSource:常见事件如下:

Cesium3DTileset 加载示例

本例中将使用allTilesLoadedinitialTilesLoadedloadProgresstileLoad这四个事件说明 3D tiles 的加载过程。

主要代码如下:

// 加载3D tiles
const tileset = viewer.scene.primitives.add(
  new Cesium.Cesium3DTileset({
    url: '../3dtiles/tilesset/tileset.json',
  })
)

// 视角定位到3D tiles
viewer.zoomTo(tileset)

// allTilesLoaded 瓦片集加载完成
tileset.allTilesLoaded.addEventListener(() => {
  console.log('allTilesLoaded,瓦片集加载完成')
})

// initialTilesLoaded 瓦片集加载完成,并且场景初始化完成
tileset.initialTilesLoaded.addEventListener(() => {
  console.log('initialTilesLoaded,瓦片集加载完成,并且场景初始化完成')
})

// loadProgress 瓦片加载进度
tileset.loadProgress.addEventListener((numberOfPendingRequests, numberOfTilesProcessing) => {
  if ((numberOfPendingRequests === 0) && (numberOfTilesProcessing === 0)) {
    console.log('loadProgress,加载完成')
    return
  }
  console.log('loadProgress,请求挂起的瓦片数量' + numberOfPendingRequests + ', 正在处理的瓦片数量: ' + numberOfTilesProcessing);
})

// tileLoad 一个瓦片被加载
tileset.tileLoad.addEventListener(tile => {
  console.log('tileLoad,一个瓦片被加载了', tile)
})

// 显示3D tiles的瓦片包围盒
tileset.debugShowContentBoundingVolume = true

页面效果如下:

根据控制台输出可以得到如下结论:

  • 最先被调用的是loadProgress,表示当前正处于瓦片请求阶段;
  • 第二个被调用的是tileLoad,表示当前正处于单个瓦片加载阶段,根据 3D tiles 瓦片包围盒可以看到共有 5 个Cesium3Dtiles,因此该事件被触发了 5 次;
  • 第三个被调用的是allTilesLoaded,表示满足当前屏幕空间误差的瓦片集已经完全加载;
  • 最后一个被调用的是initialTilesLoaded,表示满足当前屏幕空间误差的瓦片集已经完全加载,并且初始化视图已经完成。

场景渲染事件

在 Cesium 中,与场景渲染有关的事件都在 Scene 对象上,常见的事件如下:

场景渲染示例

本例中将使用postRenderpostUpdatepreRenderpreUpdate这四个事件说明场景 Scene 的渲染过程。

主要代码如下:

viewer.scene.postRender.addEventListener(() => {
  console.log('postRender')
})

viewer.scene.postUpdate.addEventListener(() => {
  console.log('postUpdate')
})

viewer.scene.preRender.addEventListener(() => {
  console.log('preRender')
})

viewer.scene.preUpdate.addEventListener(() => {
  console.log('preUpdate')
})

由于 Cesium 会自动开启渲染循环RenderLoop,所以在使用该示例时,要关闭 Cesium 的自动渲染,关闭方法如下:

viewer.useDefaultRenderLoop = false // 关闭Cesium场景自动渲染

关闭后需要主动触发渲染,触发的方法如下:

viewer.render() // 主动触发Ceisum场景渲染

页面效果如下,首次打开时场景还没有被渲染:

点击 6 次触发渲染按钮后,地球将会被渲染出来:

点击 7 次触发渲染按钮后,地球外围的大气层将会被渲染出来:

根据控制台输出可以得到如下结论,在每一次渲染周期下:

  • 最先被调用的是preUpdate,表示当前正处于场景更新之前阶段;
  • 第二个被调用的是postUpdate,表示当前正处于场景更新之后阶段;
  • 第三个被调用的是preRender,表示当前正处于场景渲染之前阶段;
  • 最后一个被调用的是postRender,表示当前正处于场景渲染之后阶段。

总得来说,更新Update操作是先于渲染Render操作的,可以使用这些事件将一些外部 DOM 操作放在事件内,避免渲染后操作造成页面抖动。

🎯 这篇文章是 Cesium 三维地球可视化从入门到进阶 专栏的第三篇文章,主要是对 Cesium 中常用的事件及其使用方法:鼠标键盘事件相机事件数据加载事件场景加载事件 做了详解

🎯 在后续的文章中, 将会分享更多实践案例,如果你也对 三维可视化比较感兴趣的话,欢迎关注我一起学习

🎯 Github 仓库地址:https://github.com/xushanpei/Cesium_Study_Cases

写在最后

公众号前端开发爱好者 专注分享 web 前端相关技术文章视频教程资源、热点资讯等,如果喜欢我的分享,给 🐟🐟 点一个 👍 或者 ➕关注 都是对我最大的支持。

大家好,我 xy,是一名前端 🤫 爱好:瞎折腾

如果你也是一名瞎折腾的前端欢迎加我微信交流哦...

🤫 一定要点我