Bootstrap

Cesium之3DTileset实例管理

最近使用Cesium结合ts和react自己手动搭建了一个基本界面,加载3dTiles数据和geojson数据,动态控制图层的显隐。本来以为是非常简单的功能,但是实际操作中发现有一些地方值得注意。

搭建的效果就是这个样子,经典的顶部header+左侧布局,主视窗显示地图:

加载geojson数据源的方法很简单,Cesium.GeoJsonDataSource.load方法会返回Promise<Cesium.GeoJsonDataSource> 类型的Promise(resolve),给其指定一个固定的标识name。
代码:

  const shandongJson = Cesium.GeoJsonDataSource.load(
    '../mock/shandong.geojson',
    {
      clampToGround:true
    }
  )
  shandongJson.then((shandongJson)=>{
    shandongJson.name = '山东';
    viewRef.dataSources.add(shandongJson)
  })

因为有 Cesium.Viewer.dataSources.getByName() 方法,直接通过name属性获取对应的geojson对象。

// 涉及业务代码的部分就不放上了 重点是getByName方法
if (info.node.type === 'geojson' && viewer){
        viewer.dataSources.getByName(info.node.title as string)[0].show = info.checked;
      }

但是在加载3dTile数据时,就没这么方便了,当我向Viewer中同时添加json数据源和3dTile数据时,通过打印其存储位置,发现是这样的:
在这里插入图片描述
在Cesium官网上查阅没有一个可以直接获取Cesium3DTileset对象实例的方法,通过index获取获取实例显然不太可行,没办法,只能想一个其它的方法用来存储Cesium3DTileset对象。于是使用Mobx建立全局store,思路如下,Redux和Vuex也是同理,只是语法不同。

import { action, extendObservable, runInAction } from "mobx";
import * as Cesium from 'cesium'
interface tileSetListProp {
  [key: string]: Cesium.Cesium3DTileset
}
interface OBSERVABLE_PROP {
  tileSetList: tileSetListProp
}
// 可观察属性
const OBSERVABLE: OBSERVABLE_PROP = {
  tileSetList: {},
};
// 建立下面的数据结构存储
//  {
//   key1: Cesium3DTileset1,
//   key2: Cesium3DTileset2,
// }
class Tiles {
  tileSetList: tileSetListProp = {}

  constructor() {
    extendObservable(this, {
      ...OBSERVABLE
    });
  }
	// 向容器中添加新的Cesium3DTileset
  @action.bound addTileSet = (key: string, tileSet: Cesium.Cesium3DTileset) => {
    runInAction(() => {
      this.tileSetList[key] = tileSet;
    });

  }

  @action.bound update(data: any) {
    Object.assign(this, data);
  }

}

export default new Tiles();

页面加载时:引入全局变量,在数据加载时把它存进去:addTileSet(‘Building’, tileBuilding), useStores是一个工具函数,直接采用mobx的inject引用是一样的,全局store如何使用就不赘述了,网上资料很多。

const Pages: FC = () => {
  const [viewer, setViewer] = useState<null | Cesium.Viewer>(null)
  const {
    tiles: {
      tileSetList,
      addTileSet
    }
  } = useStores()

  // const viewerRef = useRef<null | Cesium.Viewer>(null);
  useEffect(() => {
    const view = initViewer();
    initTile(view)
    setViewer(view);
    const MP = new MousePosition(view);
  }, [])

  const initTile = (viewer:Cesium.Viewer) => {
    const tileBuilding = viewer!.scene.primitives.add(
      new Cesium.Cesium3DTileset({ // 3d titles
        url: '../mock/tileset.json',
      })
    )

    tileBuilding.readyPromise
      .then(function (tileBuilding: Cesium.Cesium3DTileset) {
        addTileSet('Building', tileBuilding)
      })
      .catch(function (error: any) {
        console.log(error);
      });

    const tileBIM = viewer!.scene.primitives.add(
      new Cesium.Cesium3DTileset({ // 3d titles
        url: Cesium.IonResource.fromAssetId(8564),
      })
    )

    tileBIM.readyPromise
      .then(function (tileBIM: Cesium.Cesium3DTileset) {
        addTileSet('BIM', tileBIM)
      })
      .catch(function (error: any) {
        console.log(error);
      });
  }

  const add3DTile = (tileObj: TileProp) => {
    if (!viewer) return message.error('图层未加载');      
      viewer.zoomTo(
        tileSetList[tileObj.title],
        new Cesium.HeadingPitchRange( // heading pitch roll
          0.5,
          -0.2,
          tileSetList[tileObj.title].boundingSphere.radius * 4.0
        )
      );
      return
    }
  return (
    <div className={styles.cesiumPage}>
      <MapHeader
        viewer={viewer}
        onRadioChange={add3DTile}
      />
      <div className={styles.cesiumPageContainer}>
        <LayerEdit
          viewer={viewer}
        />
        <div className={styles.baseMapWrapper}>
          <Titles />
        </div>
      </div>
    </div>
  );
}
export default Pages

同样的,在左侧组件中通过Antd的Tree勾选节点可以很方便控制显隐: tileSetList[key].show = info.checked

// 引入全局Store,只用到了tileSetList
const {
    tiles: {
      tileSetList,
      addTileSet
    }
  } = useStores()
  
 ···
 ··· // 省略无用代码
 
// Tree选中节点的触发的事件
 const onCheck = (checkedKeys:any, info:CheckInfo) => {

      const layers = getLayersById(info)
      console.log('onCheck', checkedKeys, info, layers);
      // @ts-ignore
      if (info.node.type === 'geojson' && viewer){
        viewer.dataSources.getByName(info.node.title as string)[0].show = info.checked;
      }
      // @ts-ignore
      if (info.node.type === '3dtiles' && viewer){
        const key = info.node.title as string
        console.log('3dtiles',tileSetList,tileSetList[key])
        // get方法通过索引获取数组元素,实践证明不大好用
        // viewer.scene.primitives.get(info.node.key as number).show = info.checked; 
        tileSetList[key].show = info.checked // 设置显隐
      }
    };

这样子就达到目的了。
模型显示:
在这里插入图片描述
模型隐藏:
在这里插入图片描述

;