Skip to content
李小明 edited this page Jun 18, 2019 · 26 revisions

基本功能

坐标系统

一般使用的是WGS84坐标系统, 例如纬度经度: 121.631568,30.041551 . 而Cesium中通常使用Cartesian3(世界坐标)和Cartographic(绘图坐标)坐标系统, 需要注意他们之间的转换. Cartographic基本上等同于WGS84坐标系统, 但纬度和经度单位不同, 高度单位都是米! 纬度和经度的单位在Cartographic中是弧度, 在WGS84中是度, 例如度 121.631568,30.041551 对应的弧度为 2.1228713359633744,0.524323977355795 .

度(degrees)和弧度(radians)之间的转换

  • 度转弧度: Cesium.Math.toRadians(degrees)
  • 弧度转度: Cesium.Math.toDegrees(radians)

度(degrees)和弧度(radians)直接创建Cartesian3

  • 度: Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
  • 弧度: Cesium.Cartesian3.fromRadians(longitude, latitude, height)

Cartographic(绘图坐标)和Cartesian3(世界坐标)之间的转换

  • Cartographic转Cartesian3: Cesium.Cartographic.toCartesian(Cartographic)
  • Cartesian3转Cartographic: Cesium.Cartographic.fromCartesian(Cartesian3)Cesium.Ellipsoid.WGS84.cartesianToCartographic(Cartesian3)

Cartesian3转场景Canvas坐标

viewer.scene.cartesianToCanvasCoordinates(cartesian3)

当需要在指定cartesian3位置叠加显示HTML元素时, 需要添加事件来实现. 因为一旦地图移动, cartesian3对应的场景Canvas坐标是会变化的.

          //注意:有图片时需要在图片加载完毕后再定位,否则位置不准.
          //将元素在视图中定位
          viewer.scene.preRender.addEventListener(() => {
            let canvasPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian3);
            if (Cesium.defined(canvasPosition)) {
              div.style.top = canvasPosition.y + 'px';
              div.style.left = canvasPosition.x + 'px';
              div.style.marginTop = `-${div.clientHeight}px`;
            }
          });

注意: 测试发现如果在3D Tiles模型上选取cartesian3, 当移动地图或旋转相机时无法与预期坐标对应上, 会发生偏移!

举例说明

例如相机Cesium.Camera的positionWC是Cartesian3, 要获取WGS84则需要先将其转为Cartographic, 然后再从弧度转度.

创建视图

let viewer = new Cesium.Viewer(
        '容器元素id',
        {
          homeButton: false,
          geocoder: false,
          baseLayerPicker: false,
          sceneModePicker: false,
          navigationHelpButton: false,
          animation: false,
          timeline: false,
          selectionIndicator: false,
          infoBox: false
        }
    );

隐含设置

  1. 移除所有底图 viewer.scene.imageryLayers.removeAll();
  2. 设置地球颜色 viewer.scene.globe.baseColor = Cesium.Color.GRAY;

相机

获取相机位置

/**
   * 获取相机位置
   * @param camera 相机
   * @returns {Readonly<{destination: {longitude: *, latitude: *, height: *}, orientation: {heading: *, pitch: *, roll: *, direction: any, up: any}}>}
   * 设置位置方法见https://127.0.0.1/Cesium/Build/Documentation/Camera.html?classFilter=camera#setView
   */
function getCameraPosition(camera) {
    let cartographicPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(camera._positionWC);
    let longitude = Cesium.Math.toDegrees(cartographicPosition.longitude);
    let latitude = Cesium.Math.toDegrees(cartographicPosition.latitude);
    let height = cartographicPosition.height;
    console.debug('坐标:', longitude, ',', latitude, ',', height);

    let heading = camera.heading;
    let pitch = camera.pitch;
    let roll = camera.roll;
    console.debug(
        '取向( 弧度,转角度Cesium.Math.toDegrees() )-heading:',
        heading,
        ',pitch:',
        pitch,
        ',roll:',
        roll
    );

    console.debug(
        '取向( 世界坐标 )-direction:',
        JSON.stringify(camera.directionWC),
        ',up:',
        JSON.stringify(camera.upWC)
    );

    return Object.freeze({
      destination: {
        longitude: longitude,
        latitude: latitude,
        height: height
      },
      orientation: {
        heading: heading,
        pitch: pitch,
        roll: roll,
        direction: JSON.parse(
            JSON.stringify(camera.directionWC)
        ),
        up: JSON.parse(
            JSON.stringify(camera.upWC)
        )
      }
    });
  }

设置相机位置

https://127.0.0.1/Cesium/Build/Documentation/Camera.html?classFilter=camera#setView

角度

    viewer.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(99.9999, 29.9999, 12),
      orientation: {
        //使用单位为弧度:Radians, 弧度转角度使用https://127.0.0.1/Cesium/Build/Documentation/Math.html?classFilter=Math#.toDegrees
        heading: Cesium.Math.toRadians(42), // 水平, default value is 0.0 (north)
        //使用单位为弧度:Radians, 弧度转角度使用https://127.0.0.1/Cesium/Build/Documentation/Math.html?classFilter=Math#.toDegrees
        pitch: Cesium.Math.toRadians(-16),    // 垂直, default value (looking down)
        roll: 0                             // 倾斜, default value
      }
    });

方位

      viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(-75.60556729521, 40.03367068884268, 10.787266059890012),
        orientation: {
          direction: {x: -0.7972709114482522, y: 0.34092160817878747, z: 0.4981280466258275},
          up: {x: 0.12231562048372338, y: -0.7168662684303597, z: 0.6863975831632955}
        }
      });

获取坐标

Cesium.ScreenSpaceEventType.LEFT_CLICK 事件为例, 如果是 Cesium.ScreenSpaceEventType.MOUSE_MOVE 需将 movement.position 替换为 movement.endPosition .

viewer.camera.pickEllipsoid

let cartesian3 = viewer.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);

viewer.camera.getPickRay

let ray = viewer.camera.getPickRay(movement.position);
let cartesian3 = viewer.scene.globe.pick(ray, viewer.scene);

实体选取

  //选取
  let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
  //鼠标左键点击时
  handler.setInputAction((movement) => {
    let feature = viewer.scene.pick(movement.position);
    console.debug('鼠标左键点击', feature);

    if (!Cesium.defined(feature) || !(feature.id instanceof Cesium.Entity)) {
      return;
    }

    let entity = feature.id;
    //显示属性
    console.debug('模型属性:', entity.properties);
    entity.propertyNames.forEach(name => {
//注意:这里暂时存在问题.
      console.debug(name, entity.properties(name));
    });
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

模型选取(事件捕捉,模型拾取)

    //模型选取
    let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
    let handlerTemp = {};
    //鼠标左键点击模型时
    handler.setInputAction((movement) => {
      let feature = viewer.scene.pick(movement.position);
      if (!Cesium.defined(feature) || !(feature instanceof Cesium.Cesium3DTileFeature)) {
        return;
      }

      //颜色设为亮绿
      if (handlerTemp.lastClickFeature) {
        handlerTemp.lastClickFeature.color = feature.color;
      }
      feature.color = Cesium.Color.GREENYELLOW;
      viewer.scene.requestRender(); //当viewer.scene.requestRenderMode = true时必须调用才立即生效
      handlerTemp.lastClickFeature = feature;

      //显示属性
      console.debug('模型属性:');
      feature.getPropertyNames().forEach(name => {
        console.debug(name, feature.getProperty(name));
      });
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

3D Tiles

模型转换工具

OBJ to 3D Tiles

https://github.com/PrincessGod/objTo3d-tiles

注意:安装后Ubuntu无法运行,提示/usr/bin/env: "node\r": 没有那个文件或目录. 解决方法(参考 https://github.com/PrincessGod/objTo3d-tiles/issues/19#issuecomment-415023540 )如下:

$ sudo vim /usr/lib/node_modules/obj23dtiles/bin/obj23dtiles.js
输入
:se ff=unix
:wq

处理之后就OK了.

使用方法

注意:需要真实地理坐标时, 必须设置customTilesetOptions.json.

{
    "longitude":      1.998183827377008,     // Tile origin's(models' point (0,0,0)) longitude in radian.
    "latitude":       0.5319920639711395,     // Tile origin's latitude(models' point (0,0,0)) in radian.
    "transHeight":    0.0,          // Tile origin's height in meters.
    "region":         true,         // Using region bounding volume.
    "box":            false,        // Using box bounding volume.
    "sphere":         false         // Using sphere bounding volume.
}

注意:纬度经度单位为弧度. transHeight为原点的高度, 如果不设置模型将放置在地球表面(即height=0). 上面的注释需要从json文件中去掉, 仅仅是为了说明.

obj23dtiles -p customTilesetOptions.json --tileset --outputBatchTable --checkTransparency  -i /path/to/box.obj

如包含透明纹理, 应加参数 --checkTransparency. 如使用PBR材质, 应加 --useOcclusion 参数.

其它位置设置方法

特别注意, 完全可以在OBJ to 3D Tiles(使用objTo3d-tiles)时设置地理位置(通过customTilesetOptions.json). 这样就无需下面两种方案了, 模型可以正确摆放. terrainProvider方案时必须联网使用Cesium地形, modelMatrix方案模型会出现翻转(试过很多种方法都无法掰正...).

结合terrainProvider设置

注意: 此方式必须联网!!!感谢https://github.com/AnalyticalGraphicsInc/3d-tiles-tools/issues/20#issuecomment-399687836, 解决了通过modelMatrix设置位置时模型翻转问题. 但Cesium.createWorldTerrain()需要联网加载cesium的资源.

创建Cesium.Viewer时设置terrainProvider: Cesium.createWorldTerrain()

    let viewer = new Cesium.Viewer(
        containerId,
        {
          terrainProvider: Cesium.createWorldTerrain()
        }
    );
    //不设置3D Tiles将自动位于地上(位于地下时容易丢失模型) 文档:Documentation/Globe.html#depthTestAgainstTerrain
    viewer.scene.globe.depthTestAgainstTerrain = true;

tileset就绪后设置制图坐标

      tileset.readyPromise
          .then((tileset) => {
            let cartographic = Cesium.Cartographic.fromDegrees(114.4875, 30.4809);
            Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic]).then(function() {
              // cartographic.height is updated
              let cartesian = Cesium.Cartographic.toCartesian(cartographic);
              let transform = Cesium.Transforms.headingPitchRollToFixedFrame(cartesian, new Cesium.HeadingPitchRoll());
              tileset._root.transform = transform;
              viewer.zoomTo(tileset);
            });
          });

这样3D Tiles模型就显示在预期位置114.4875, 30.4809了.

通过modelMatrix设置位置

这种方式存在的问题时,模型的xyz方位不对. 测试模型垂直戳在地上, 试过多种方式均没能成功调整. 后来发现根本原因是使用objTo3d-tiles转换时没有设置地理位置, 于是工具自己给了一个默认transform. 这个transform很难被掰正...

      tileset.readyPromise
          .then((tileset) => {
            let boundingSphere = tileset.boundingSphere;
            console.debug('当前原点笛卡尔:', boundingSphere.center);

            //获取原3d笛卡尔点后,转换为制图实例
            let centerCartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
            console.debug('当前原点制图:', centerCartographic);

            //将高度设置为0后,创建3d笛卡尔点
            let centerCartesian3 = Cesium.Cartesian3.fromRadians(centerCartographic.longitude, centerCartographic.latitude, 0);
            console.debug('当前原点笛卡尔(高度为0):', centerCartesian3);

            //创建需要放置地点3d笛卡尔点
            let realPlaceCartesian3 = Cesium.Cartesian3.fromDegrees(114.4875, 30.4809, 0);
            console.debug('实际原点笛卡尔:', realPlaceCartesian3);

            //计算两个3d笛卡尔点的分量差(注意:调整后位置为left参数.)
            let translationCartesian3 = Cesium.Cartesian3.subtract(realPlaceCartesian3, centerCartesian3, new Cesium.Cartesian3());
            console.debug('当前与实际原点笛卡尔分量差:', translationCartesian3);

            //设置4x4变换矩阵
            tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translationCartesian3);

            // console.warn('m:', tileset.modelMatrix);
            // console.warn('c:', tileset.boundingSphere.center);
            // let modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
            //     tileset.boundingSphere.center
            // );
            // console.warn('m:', modelMatrix);
            // tileset.modelMatrix = modelMatrix;
            // console.warn('c:', tileset.boundingSphere.center);

            // //获取角度
            // let centerMatrix3 = new Cesium.Matrix3();
            // Cesium.Matrix4.getRotation(tileset.modelMatrix, centerMatrix3);
            // console.warn('当前:', centerMatrix3);
            //
            // //准备旋转角度
            // let realMatrix3 = Cesium.Matrix3.fromRotationX(
            //     Cesium.Math.toRadians(10)
            // );
            // console.warn('实际:', realMatrix3);
            //
            // //计算
            // let translationMatrix3 = Cesium.Matrix3.subtract(realMatrix3, centerMatrix3, new Cesium.Matrix3());
            // console.warn('分量差:', translationMatrix3);
            //
            // //设置
            // let m = tileset.modelMatrix;
            // Cesium.Matrix4.multiplyByMatrix3(m, centerMatrix3, m);
            // tileset.modelMatrix = m;



            // //旋转模型角度
            // let mx = Cesium.Matrix3.fromRotationX(
            //     Cesium.Math.toRadians(0.05)
            // );
            // tileset.modelMatrix = Cesium.Matrix4.fromRotationTranslation(mx);

            // let m = tileset.modelMatrix;
            // let rotateMatrix = Cesium.Matrix3.fromRotationZ(
            //     Cesium.Math.toRadians(0.05)
            // );
            // Cesium.Matrix4.multiplyByMatrix3(m, rotateMatrix, m);
            // tileset.modelMatrix = m;

            //显示到瓦片
            viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius * 2.0));
          });

注意下面注释掉片段, 为试过的4种调整模型方位方法. 基本上都是将模型的位置改了, 而且移动模型就不见了.

获取指定的mesh(Feature)

使用Cesium3DTileContent.getFeature(batchId)即可获取. Cesium3DTileContent可以通过Cesium3DTile.content获取. 这个信息可以在转换b3dm时输出batchTable.json来查看, batchId就是每个mesh在b3dm中的索引位置. 一般有一个name数组与其对应, name数组中存储着模型的名称. 所以我们通过模型名称找到其在name数组中的索引, 然后就可以确定batchId. 也可以直接遍历, 通过Cesium3DTileContent.featuresLength可以获取mesh总数.

Batch Table https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification/TileFormats/BatchTable#batch-table

objTo3d-tiles https://github.com/PrincessGod/objTo3d-tiles

流程如下:

      tileset.tileLoad.addEventListener((tile) => {
        let featuresLength = tile.content.featuresLength;
        console.debug('瓦片Feature数量', featuresLength);
      });

这样就拿到Cesium3DTile了, 然后通过tile.content.getFeature(batchId)就可以获取指定mesh了. 然后我们可以设置颜色, 比如tile.content.getFeature(batchId).color = Cesium.Color.GREENYELLOW;

Clone this wiki locally