CesiumJS 高频功能 —— 事件绑定&坐标拾取

486 阅读6分钟

在CesiumJS库中,事件绑定和坐标拾取功能是构建交互式三维地球可视化应用的关键工具。它们使得用户能够与场景中的元素进行互动,从而增强用户体验。

事件绑定

在CesiumJS中,事件绑定可以通过 ScreenSpaceEventHandler 类来轻松实现。该类提供了一种机制,让你能够监听和响应用户的输入事件,例如鼠标点击、拖动等。

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (clickEvent) {
    // 处理点击事件
    var pickedObject = viewer.scene.pick(clickEvent.position);
    if (Cesium.defined(pickedObject)) {
        console.log('你点击了:', pickedObject);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

可以打开 Picking - Cesium Sandcastle 并运行相关示例。

点击拾取

在 Cesium 中,拾取是指通过用户输入(如鼠标点击)来确定场景中某个对象的位置和信息的过程。确定用户点击位置通常涉及到几种不同的拾取(pick)方法,每种方法适用于不同的场景和需求。以下是Cesium中常用的几种拾取方法及其原理:

方法

  1. viewer.scene.pick:这是最基本的拾取方法,它根据屏幕坐标拾取场景中的实体(Entity)或图元(Primitive)。它返回拾取点的最顶层对象,如果没有对象被拾取,则返回undefined。这种方法适用于需要获取用户点击的3D对象时。

  2. viewer.scene.drillPick:这是一种穿透拾取方法,可以获取点击位置下的所有对象。这在多个对象重叠的情况下非常有用。

handler.setInputAction(function (clickEvent) {
    var pickedObjects = viewer.scene.drillPick(clickEvent.position);
    pickedObjects.forEach(function (pickedObject) {
        console.log('拾取对象:', pickedObject);
    });
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  1. viewer.scene.pickPosition:这种方法拾取的是鼠标点击位置对应的世界坐标(笛卡尔坐标)。它适用于需要精确位置的场景,如模型表面的拾取。需要注意的是,为了提高精度,通常需要开启深度检测。
handler.setInputAction(function (clickEvent) {
    var position = viewer.scene.pickPosition(clickEvent.position);
    if (Cesium.defined(position)) {
        console.log('世界坐标:', position);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  1. viewer.scene.globe.pick:此方法返回鼠标点击位置与地球表面的交点坐标。它适用于需要地形坐标的场景,如地形分析。开启深度检测可以提高拾取精度。
handler.setInputAction(function (clickEvent) {
    var ray = viewer.camera.getPickRay(clickEvent.position);
    var position = viewer.scene.globe.pick(ray, viewer.scene);
    if (Cesium.defined(position)) {
        console.log('地形坐标:', position);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  1. viewer.scene.camera.pickEllipsoid:通过屏幕坐标获取椭球体表面的坐标。这种方法适用于不需要地形高程信息的场景,如简单的地球表面拾取。
handler.setInputAction(function (clickEvent) {
    var position = viewer.scene.camera.pickEllipsoid(clickEvent.position, viewer.scene.globe.ellipsoid);
    if (Cesium.defined(position)) {
        console.log('椭球面坐标:', position);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

原理

拾取操作通常涉及以下几个步骤:

  • 屏幕空间拾取:用户在屏幕上点击,Cesium 将这个点击转换为屏幕空间坐标。
  • 射线生成:Cesium 使用相机和屏幕空间坐标来生成一个射线,这个射线从相机通过用户点击的屏幕点。
  • 射线与对象的交点计算:Cesium 计算这个射线与场景中各个对象的交点。
  • 深度检测:如果开启了深度检测,Cesium 会考虑地形和其他对象的深度信息,以确保拾取的准确性。
  • 拾取结果:Cesium 返回最顶层与射线相交的对象的信息。

按照步骤来说,其实并不复杂,那为什么需要这么多方法呢?这是因为 Cesium 的场景由多个层次的容器组成,每个容器都有其特定的角色和用途。

  1. Scene

    • Scene 是 Cesium 中最顶层的容器,它代表了整个渲染场景。
    • 它包含了 Globe,以及所有添加到场景中的 Primitive(原始图形对象,如几何体)、Entity(实体,用于表示和描述场景中的对象,如点、线、面等)和 DataSource(数据源,用于动态加载和更新数据)。
    • 在拾取过程中,Scene 提供了全局的上下文,使得可以从用户输入的位置出发,逐层向下进行拾取。
  2. Globe

    • Globe 代表地球,是 Scene 的一个子容器。
    • 它基于 Ellipsoid,即一个数学上定义的旋转椭球体,模拟地球的形状。
    • Globe 包括了地形瓦片和影像图层,这些图层像是地球的“皮肤”,提供了地球表面的纹理和高度信息。
    • 在拾取过程中,如果用户点击的位置在地球表面上,Globe 会参与计算,提供准确的地球表面坐标。
  3. Ellipsoid

    • Ellipsoid 是一个数学模型,用来描述地球的形状。
    • 在 Cesium 中,Ellipsoid 通常用于计算地球表面上的点,以及处理与地球表面相关的位置和方向。
    • 在拾取过程中,如果需要获取地球表面的坐标,Ellipsoid 相关的计算会被用到。
  4. PrimitiveEntity

    • Primitive 是构成场景中三维图形的基础对象,如多边形、线条、点等。
    • Entity 是一种更高级别的抽象,它可以用来描述和存储关于场景中对象的信息,如位置、外观、标签等。
    • 在拾取过程中,如果用户点击的位置在这些对象上,Cesium 会返回与这些对象相关的信息。

在 Cesium 中,不同的拾取函数对应不同的拾取需求:

  • Scene.prototype.pick:拾取屏幕坐标下最顶层的 PrimitiveEntity
  • Scene.prototype.pickPosition:拾取屏幕坐标下的三维世界坐标。
  • Globe.prototype.pick:拾取屏幕坐标与地球表面的交点,通常用于获取地形坐标。
  • Camera.prototype.pickEllipsoid:拾取屏幕坐标与椭球体表面的交点,通常用于没有地形数据时的拾取。

解决拾取不准确的问题

在 CesiumJS 中,拾取操作的准确性可以通过开启深度检测来提高。深度检测(或深度测试)是一种图形渲染技术,用于确定一个物体是否应该被另一个物体遮挡。在 3D 图形中,这通常是通过比较每个像素点的深度值来实现的。在 CesiumJS 中,可以通过设置 viewer.scene.globe.depthTestAgainstTerrain = true 来开启深度检测。

默认情况下,CesiumJS 可能不会考虑地形的深度来进行拾取,这可能导致拾取结果不准确,特别是在地形起伏较大或者有复杂建筑物的区域。开启深度检测后,CesiumJS 会使用地形数据来确定拾取点,从而提高拾取的准确性。

例如,Scene.prototype.pickPosition 方法用于拾取用户点击位置的三维坐标,而 Globe.prototype.pick 方法则用于拾取地球表面的点。如果没有开启深度检测,这些方法可能会返回一个不准确的坐标,因为它们没有考虑到地形的深度信息。开启深度检测后,拾取操作会考虑到地形的深度,从而返回更准确的坐标。

需要注意的是,开启深度检测可能会对性能产生一定影响,因为它需要额外的计算来处理地形和对象之间的遮挡关系。因此,在不需要深度检测的情况下,可以将其关闭以提高性能。

此外,深度检测的开启对于 Scene.prototype.pick 方法也很重要,该方法用于拾取场景中的实体(Entity)或图元(Primitive)。如果没有开启深度检测,可能会导致拾取到错误的实体或者无法拾取到任何实体。

总结来说,开启深度检测可以提高 CesiumJS 中拾取操作的准确性,尤其是在地形复杂的区域。但是,也需要考虑到可能带来的性能影响,并根据实际需要决定是否开启。