Cesium 深入浅出

1,897 阅读12分钟

基本概念

Viewer界面介绍及组件显隐

每一个组件的描述如下:

  • Geocoder:**查找位置工具,查找到之后会将镜头对准找到的地址,默认使用微软的Bing地图
  • HomeButton:**首页位置,点击之后将视图跳转到默认全球视角
  • SceneModePicker:**选择视角的模式,3D,2D,哥伦布视图(CV)
  • BaseLayerPicker:**图层选择器,选择要显示的地图服务和地形服务
  • NavigationHelpButton:**导航帮助按钮,显示默认的地图控制帮助
  • Animation:**动画器件,控制视图动画的播放速度
  • CreditsDisplay:**展示商标版权和数据归属
  • Timeline:**时间轴,指示当前时间,并允许用户跳到特定的时间
  • FullscreenButton:**全屏按钮

坐标系

cesium中坐标系统分为地理坐标、世界坐标(X,Y,Z)、屏幕坐标三种

  • 默认使用WGS84作为空间参考,地理坐标又分为两种经纬度和弧度两种表达方式
  • 采用右手系的笛卡尔空间直角坐标系,也叫世界坐标、原点就是椭球的中心。这里的Cartesian3,有点类似于三维系统中的Point3D对象,new Cesium.Cartesian3(x, y, z)
  • 屏幕坐标即屏幕显示的二维坐标,左上角为坐标原点,鼠标点击即可获取,new Cesium.Cartesian2(x, y)

坐标转换

# 角度和弧度互转
var radians=Cesium.Math.toRadians(degrees);//经纬度转弧度
var degrees=Cesium.Math.toDegrees(radians);//弧度转经纬度

# 地理经纬度转地理弧度
//方法一:
var longitude = Cesium.Math.toRadians(lng); //其中 lng为经度
var latitude= Cesium.Math.toRadians(lat); //其中 lat为纬度
var cartographic = new Cesium.Cartographic(longitude, latitude, height);

//方法二:
var cartographic= Cesium.Cartographic.fromDegrees(lng, lat, height);//其中,lng和lat为经纬度

//方法三:
var cartographic= Cesium.Cartographic.fromRadians(longitude, latitude, height);//其中,longitude和latitude为弧度

# 经纬度转世界坐标
lnglatToCartesian(lng,lat,height){
     //直接转换
    var cartesian3 = Cesium.Cartesian3.fromDegrees(lng, lat, height); 
    //先转弧度后转笛卡尔
    //var cartographic = Cesium.Cartographic.fromDegrees(lng, lat, height); //单位:度,度,米 
    //var ellipsoid=viewer.scene.globe.ellipsoid;
    //var cartesian3 = ellipsoid.cartographicToCartesian(cartographic); 
    return cartesian3    
}
# 世界坐标转经纬度
Cesium不支持笛卡尔坐标直接转经纬度,需要先转换为弧度,再由弧度转化为经纬度
cart3Tolnglat(cartesian3){
   var ellipsoid=this._viewer.scene.globe.ellipsoid;
   const cartograhphic=ellipsoid.cartesianToCartographic(cartesian3);
    var lat=Cesium.Math.toDegrees(cartograhphic.latitude);
   var lng=Cesium.Math.toDegrees(cartograhphic.longitude);
    var height=cartographic.height
   return [lng,lat,height]; 
},

# 世界坐标转屏幕坐标
var cartesian2= Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene,cartesian3)

# 屏幕坐标转世界坐标

ar handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(function (event) {
  //event.position为屏幕坐标 
  console.log(event.position);
  //获取包含了地形、倾斜摄影表面、模型的世界坐标
  //解决在没有3dTile模型下的笛卡尔坐标不准问题,viewer.scene.globe.depthTestAgainstTerrain = true; //默认为false
  var pickedPosition = viewer.scene.pickPosition(event.position);
  if (Cesium.defined(pickedPosition)) {
    console.log('1',pickedPosition);
  }
  //获取地球表面的世界坐标,包含地形,不包含其他模型
  //Create a ray from the camera position through the pixel at windowPosition in world coordinates.
  var ray = viewer.camera.getPickRay(event.position);
  //Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates.
  var position2 = viewer.scene.globe.pick(ray, viewer.scene);
  console.log('2',position2);
  //获取参考椭球的世界坐标
  var position3 = viewer.scene.camera.pickEllipsoid(event.position, viewer.scene.globe.ellipsoid);
  console.log('3',position3);
                                         
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

内部对象

image.png

支持的模型格式

目前,glTF 3D模型格式有两种:

  • *.gltf: 基于JSON的文本文件,可使用文本编辑器轻松编辑,通常会引用外部文件,例如纹理贴图、二进制网格数据等;

       一个glTF模型可包括以下三部分内容:
    
    • JSON格式的文件(.gltf),其中包含完整的场景描述,并通过场景结点引用网格进行定义 。包括:节点层次结构、材质(定义了3D对象的外观)、相机(定义义了渲染程序的视锥体设置 )、mesh(网格)、动画(定义了3D对象的变换操作,比如选择、平移操作)、蒙皮(定义了3D对象如何进行骨骼变换)等;
    • .bin包含几何和动画数据以及其他基于缓冲区的数据的二进制文件;
    • 图像文件(.jpg,.png)的纹理。

image.png

  • *.glb: 是二进制格式,通常文件较小且自包含所有资源,但不容易编辑。

glTF相关工具推荐:

glTF在线验证:github.khronos.org/glTF-Valida… 浏览-Sketchfab:sketchfab.com/(需要注册账号,并且要上传模型才能浏览) 浏览-PlayCanvas查看器:playcanvas.com/viewer 浏览-ThreeJS查看器:gltf-viewer.donmccurdy.com/ 浏览-BabylonJS查看器:sandbox.babylonjs.com/ gltf转glb:sbtron.github.io/makeglb/ obj2gltf:github.com/CesiumGS/ob… FBX2glTF:github.com/facebookinc… COLLADA2GLTF:github.com/KhronosGrou…

加载代码

var position = Cesium.Cartesian3.fromDegrees(-120.05, 44, 0);
    var heading = Cesium.Math.toRadians(45);
    var pitch = 0;
    var roll = 0;
    var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
    var orientation = Cesium.Transforms.headingPitchRollQuaternion(
      position,
      hpr
    );
    var model_entity = viewer.entities.add({
      name: "gltf模型",
      position: position,
      // 默认情况下,模型是直立的并面向东。
      // 通过 Quaternion 为 Entity.orientation 属性指定值来控制模型的方向,控制模型的航向,俯仰和横滚。
      orientation: orientation,
      model: {
        show: true,
        uri: "./data/models/DracoCompressed/CesiumMilkTruck.gltf",
        scale: 1.0, // 缩放比例
        minimumPixelSize: 128, // 最小像素大小
        maximumScale: 20000, // 模型的最大比例尺大小。 minimumPixelSize的上限
        incrementallyLoadTextures: true, // 加载模型后纹理是否可以继续流入
        runAnimations: true, // 是否应启动模型中指定的glTF动画
        clampAnimations: true, // 指定glTF动画是否应在没有关键帧的持续时间内保持最后一个姿势

        // 指定模型是否投射或接收来自光源的阴影 type:ShadowMode
        // DISABLED 对象不投射或接收阴影;ENABLED 对象投射并接收阴影;CAST_ONLY  对象仅投射阴影;RECEIVE_ONLY  对象仅接收阴影
        shadows: Cesium.ShadowMode.ENABLED,
        heightReference: Cesium.HeightReference.NONE,
      },
    });
    // viewer.trackedEntity = entity; // 相机保持在实体上
var origin = Cesium.Cartesian3.fromDegrees(-120, 44.0, 0);
    // 创建一个本地的东北向上坐标系,其原点为经度-120度,纬度44.0度。
    // 可以随时更改模型的modelMatrix属性以移动或旋转模型。
    var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
    var model = viewer.scene.primitives.add(
      Cesium.Model.fromGltf({
        url: "./data/models/DracoCompressed/CesiumMilkTruck.gltf",
        modelMatrix: modelMatrix,
        minimumPixelSize: 128,
        maximumScale: 20000,
      })
    );
    model.readyPromise.then(function (model) {
      // Play all animations when the model is ready to render
      model.activeAnimations.addAll();
    });

空间数据可视化元素

实体entity

//方法一
var entity = new Entity({
    id : 'uniqueId'
});
viewer.entities.add(entity);

//方法一 简写
viewer.entities.add({
    id : 'uniqueId'
});

//方法二
var entity = viewer.entities.getOrCreateEntity('uniqueId');
//方法一,先查后删
var entity = viewer.entities.getById('uniqueId');
viewer.entities.remove(entity) 
//方法二,直接删除
viewer.entities.removeById('uniqueId')
//方法三,删除所有
viewer.entities.removeAll()
//方法一,先查后删
var entity = viewer.entities.getById('uniqueId');
viewer.entities.remove(entity) 
//方法二,直接删除
viewer.entities.removeById('uniqueId')
//方法三,删除所有
viewer.entities.removeAll()
  • 实体集体化
function onChanged(collection, added, removed, changed){
  var msg = 'Added ids';
  for(var i = 0; i < added.length; i++) {
    msg += '\n' + added[i].id;
  }
  console.log(msg);
}
viewer.entities.collectionChanged.addEventListener(onChanged);

primitive

primitive主要由两部分组成:Geometry【几何体】和Appearance【着色器】

 for (let lon = -180.0; lon < 180.0; lon += 5.0) {
      for (let lat = -85.0; lat < 85.0; lat += 5.0) {
        instances.push(
          new Cesium.GeometryInstance({
            geometry: new Cesium.RectangleGeometry({
              rectangle: Cesium.Rectangle.fromDegrees(
                lon,
                lat,
                lon + 5.0,
                lat + 5.0
              ),
              vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
            }),
            attributes: {
              color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                Cesium.Color.fromRandom({ alpha: 0.5 })
              ),
            },
          })
        );
      }
    }

    this.viewer.scene.primitives.add(
      new Cesium.Primitive({
        geometryInstances: instances,
        appearance: new Cesium.PerInstanceColorAppearance({
          translucent: false,
          closed: true,
        }),
      })
    );

entity与primitive区别

entity偏向数据,primitive偏向图形.primitive更底层 entity用法简单,primitive用法复杂。我们会有这样的疑问:entity已经封装的如此完美,调用如此便 > 捷,为何还要primitive接口呢?区别就是加载效率。primitive更接近webgl底层,没有entity各种各样 > 的附加属性,因此在加载时效率会更高

界面交互

配置视窗

相机是viewer.scene中的属性,用来控制当前可见的域。我们可以通过直接设置它的位置和方向来控制相机,或者通过使用Cesium提供的API来控制相机,它被设计成指定相机的位置和方向随时间的变化。 一些最常用的方法如下:

进一步获得API功能,看看这些相机演示:

相机漫游

默认地,场景支持鼠标(电脑端)和手指触摸(移动端)交互,并且支持以下相机漫游方式:

  • 按住鼠标左键拖拽 - 让相机在数字地球平面平移
  • 按住鼠标右键拖拽 - 放缩相机
  • 鼠标滚轮滑动 - 放缩相机
  • 按住鼠标中键拖拽 - 在当前地球的屏幕中间点,旋转相机

事件系统

  • 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);

image.pngimage.png

  • 要素拾取

假如应用场景是点击要素获取其属性信息,这个时候就需要在鼠标左键的注册事件中获取 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");
        }
      }
  • Entity选择

** **Cesium 针对于通过 Entity 方式添加的几何图形,提供了一个非常方便的属性selectedEntityChanged(viewer类事件类型的属性)来帮助我们获取选中的Entity,通过这个属性,用户无需再写注册鼠标事件了。示例代码如下: :::info viewer.selectedEntityChanged.addEventListener(function (entity) { console.log(entity.id); }); ::: 在某些场景中,我们可能需要跟踪某一辆车或某一个人员,这是我们可以把车辆或人员Entity赋给viewer.trackedEntity,相机就会自动跟踪你绑定的Entity了。实际场景中,我们并不是始终跟踪某一个车辆,有时需要切换到另一个车辆,当你切换正在跟踪的车辆时,其实我们触发了viewer.trackedEntityChanged事件,这样我们就可以在此事件中实时获取车辆行驶状态了。 :::info viewer.trackedEntityChanged.addEventListener(function (entity) { console.log(entity.id); }); :::

相机事件

相机的三个参数heading、pitch、roll的值是针对于坐标轴旋转的弧度数,示意图如下所示:

相机控制事件类 screenSpaceCameraController 并不是像鼠标事件相关类 ScreenSpaceEventHandler 那样需要提前实例化。Cesium在Viewer类的实例化过程中,也实例化了其他很多类,其中就包括ScreenSpaceCameraController类,并把实例化结果赋值给了viewer.scene.screenSpaceCameraController。所以,我们直接去操作viewer.scene.screenSpaceCameraController就可以了。

1)通过鼠标控制 通过鼠标控制相机的方式取决于CameraEventType的常量,包括以下几种: image.pngimage.png

 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地址github.com/ls870061011…中的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: 渲染场景后立即引发的事件 事件的添加和移除代码示例如下: :::info 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`;
            }
          }
        });
      }
   )

地图数据

影像数据

无论是二维地图还是三维地图,如果缺少了底图影像或电子地图,都是不完整的。Cesium为我们提供了ImageryLayerCollection、ImageryLayer以及相关的ImageryProvider类来加载不同的影像图层。虽然Cesium把此类图层叫做Imagery*,但并不是特指卫星影像数据,还包括一些互联网地图、TMS、WMS、WMTS、单个图片等。

  • ImageryLayer类

Cesium.ImageryLayer类用于表示Cesium中的影像图层,它就相当于皮毛、衣服,将数据源包裹在内,它需要数据源(imageryProvider)为其提供内在丰富的地理空间信息和属性信息。同时,通过该类还能设置影像图层相关属性,比如透明度、亮度、对比度、色调等。

  • ImageryProvider类

Cesium.ImageryProvider类及其子类封装了加载各种影像图层的方法,其中Cesium.ImageryProvider类是抽象类、基类或者可将其理解为接口,它不能被直接实例化。我们可以把ImageryProvider看作是影像图层的数据源(包裹在ImageryLayer类内部),我们想使用哪种影像图层数据或服务就用对应的ImageryProvider子类去加载,目前Cesium提供了以下14种ImageryProvider。

  • ImageryLayerCollection类

Cesium.ImageryLayerCollection类是ImageryLayer类对象的容器,它可以装载、放置多个ImageryLayer或ImageryProvider类对象,而且它内部放置的ImageryLayer或ImageryProvider类对象是有序的。 Cesium.Viewer类对象中包含的imageryLayers属性就是ImageryLayerCollection类的实例,它包含了当前Cesium应用程序所有的ImageryLayer类对象,即所有影像图层,所以Cesium种的影像图层可以添加多个。

3d Tiles

通过官方cesiumion 可以在线将模型转换 3d tiles

cesium.com/ion/assets/…

矢量格式

包含有GeoJson和KML,专门开发用于描述Cesium场景的[]CZML](github.com/AnalyticalG…)

api 分类

Viewer类属性

imageryLayers 影像数据 terrainProvider 地形数据 dataSources 矢量数据 entities 几何实体集合(用于空间数据可视化) Widgets 组件,即Viewer初始化界面上的组件 Camera 相机 Event 事件,鼠标事件、实体选中事件等

Scene类属性

primitives 图元集合(几何体和外观) postProcessStages 场景后期处理 环境对象,大气圈、天空盒、太阳、月亮等 Event事件,更新、渲染事件等 Camera类属性 位置、方位角、俯仰角、翻滚角

空间计算

三维矩阵、四元数、四维矩阵、转换等 Cesium API结构.png

工具类

Cesium for Unreal

准备工作

  • 安装虚幻引擎(至少需要4.26或以上版本)。关于如何安装虚幻引擎,请访问虚幻引擎下载页面
  • 一个Cesium ion帐号,用于将地形和建筑物资产流式传输到虚幻引擎中。如果您还没有免费的Cesium ion帐号,请注册一个。

Cesium ion是一个用于流式传输和托管3D内容的开放平台,并且包含全球的、精准的GIS数据,可用于创建展示真实三维地球的应用程序。

安装Cesium for Unreal插件

在虚幻引擎市场上打开Cesium for Unreal插件页面。

  • image.png