0


【WebGis开发 - Cesium】三维可视化项目教程---图层管理基础

目录

引言

本教程主要是围绕Cesium这一开源三维框架开展的可视化项目教程。总结一下相关从业经验,如果有什么疑问或更好的见解,欢迎评论、私聊探讨,共同进步。教程依托于vue3前端框架,参考初始化内容:【WebGis开发 - Cesium】三维可视化项目教程—初始化场景

本篇主要讨论如何管理二三维图层,仅以wmts和3dtiles做图层管理示例。我会在接下来的教程里专门针对其他各类图层加载方式做详细介绍。

二三维图层管理是三维可视化项目里重要的组成部分,通过树结构管理图层,根据模块设计加载对应二三维数据。

先看效果:

在这里插入图片描述


一、功能设计

1. 主体功能

  1. 设计图层数据结构,使用任意树形结构插件或是自行开发图层树。
  2. 编写图层分类加载、卸载方法,暴露统一接口,配合图层数据进行图层操作。
  3. 存储数据至全局状态,方便管理。

2. 细节问题

图层数据大致上可以分为二维图层和三维图层

在三维引擎中针对这两者的表达方式有所不同

  • 二维图层没有高度,按照加载先后顺序重叠在一起。如果内容存在遮挡,则只显示最后加载的图层。(好比一摞画册)
  • 三维图层具备高度,没有加载先后顺序重叠遮挡一说,只会根据三维模型的实际物理尺寸决定遮挡关系。
  • 所以在开发图层树的时候,要注意区分二三维图层。

二、代码实现

1. 树形控件

树形控件采用

element-plus

组件库的

tree

组件。细节查阅:Tree 树形控件

几个需要注意的点:

  • 根据需求设置 check-on-click-node ,我希望不论点击选择框或是点击树节点内容,都可以触发选中节点。
  • 根据需求设置 :expand-on-click-node="false" ,我不希望在点击包含有子节点的节点时,一边加载子节点图层内容,一边把树折叠起来。或是反过来卸载子节点图层内容,同时把树伸展开。
  • 点击事件我选择 check-change ,只有这一个点击事件返回的数据符合我的需求。根据不同的设计理念,可以查阅组件库文档,选择适合自己项目的点选事件。
  • 图层管理核心在图层加载、卸载和全局数据管理,图层树可以根据自己喜好选择任意UI框架的树形结构,或是自行开发。满足展示树形图层以及选中、取消功能即可。

html部分

<divclass="layerManager"><div>图层管理</div><el-tree:data="dataSource"show-checkboxnode-key="id"default-expand-allcheck-on-click-node@check-change="checkChange":expand-on-click-node="false"></el-tree></div>

数据部分

二维数据使用天地图影像、矢量、注记三种。三维数据使用本地3dtiles数据(有在线数据替换一下)。
天地图wmts图层引用示例可以参考:天地图服务 ,在页面最下方有请求示例。
在这里插入图片描述

树节点数据主要包含几个内容

  • 只有叶子节点存在url属性,实际控制图层的加载、卸载。其父级节点只作为文件夹管理功能,不实际控制图层。
  • type属性,表面当前图层属于哪种类型,根据类型分类后,调用指定方法加载、卸载图层。
  • id属性,值必须唯一。用于检索全局状态中存储的图层重要信息。
  • label属性,图层树显示名称。
const dataSource =ref([{
    id:"1",
    label:"二维地图",
    children:[{
        id:"1-1",
        label:"天地图影像",
        type:"wmts",
        url:"https://t0.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你申请的天地图key",},{
        id:"1-2",
        label:"天地图矢量",
        type:"wmts",
        url:"https://t0.tianditu.gov.cn/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你申请的天地图key",},{
        id:"1-3",
        label:"天地图矢量注记",
        type:"wmts",
        url:"https://t0.tianditu.gov.cn/cva_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cva&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你申请的天地图key",},],},{
    id:"2",
    label:"三维地图",
    children:[{
        id:"2-1",
        label:"测试3dtiles模型",
        type:"3dtiles",// 我这里准备一份本地3dtiles数据在 `public/3dtiles/` 目录下
        url:"/3dtiles/test/tileset.json",},],},]);

2. 全局状态准备

使用

pinia

来管理全局状态

pinia的安装引入操作不过多赘述,自行查阅。

src/stores/

目录下创建文件 layer.js

根据cesium提供的加载图层方法,可以发现

  • 常规二维图层都是以 imageryLayers 形式存储在全局变量 viewerimageryLayers 属性中。
  • 3dtiles图层是以 primitive 形式存储在 viewer.sceneprimitives 属性中。

所以我们创建两个全局状态数组

imageryLayers

primitiveLayers

,分别存储二维图层和三维图层。同时对应创建添加和查询两个action方法,用于保存和查询图层的重要信息(不需要把所有信息都存起来,只要存储特征值可以确保能在viewer中查询到对应图层即可)

import{ defineStore }from"pinia";exportconst useLayerStore =defineStore("LayerStore",{state:()=>({ imageryLayers:[], primitiveLayers:[]}),
  actions:{addImageryLayer(data){this.imageryLayers.push(data);},getImageryLayer(id){returnthis.imageryLayers.find((item)=> item.id === id);},addPrimitiveLayer(data){this.primitiveLayers.push(data);},getPrimitiveLayer(id){returnthis.primitiveLayers.find((item)=> item.id === id);},},});

3. 创建图层控制方法

出于功能的可复用性考虑,将图层管理的方法封装为hooks函数

主要包含几个内容:

  1. 编写各类图层的加载和卸载方法。
  2. 集成加载和卸载的入口方法,对外暴露行为统一。
  3. 提供hooks函数的可拓展性。

3.1 加载、卸载方法编写

这里不深究对应图层加载api的使用方法,我会在之后的文章里详细介绍各图层加载方法的api使用方法以及常用属性填写。
描述一下我的编写逻辑:

  1. 加载方法 - 先通过树节点 id 到全局状态中查询图层信息是否已经存在。- 如果已经加载过了,就通过保存的特殊标识查询到对应图层数据,将 show 属性改写为 true 即可。- 如果没有加载过,则调用对应api加载图层,加载完成后返回对应图层数据。- 创建图层特殊标识,用于查询到指定图层,将特殊标识添加到返回图层数据的自定义属性中,我这里设置的是layerId,确保标识值唯一。- 将 layerId 以及树节点信息保存至全局状态中。
  2. 卸载方法 - 先通过树节点 id 到全局状态中查询图层信息是否已经存在。- 如果存在,就通过保存的特殊标识查询到对应图层数据,将 show 属性改写为 false 即可。- 不存在则不进行操作。
import{ useLayerStore }from"@/stores/layer.js";import{ GenerateId }from"@/utils/cesium/common.js";const layerStore =useLayerStore();/**
   * @description: 添加wmts图层
   * @param {*} data
   * @return {*}
   */constaddWmtsLayer=(data)=>{// 先查询是否已经加载图层const layerData = layerStore.getImageryLayer(data.id);// 存在图层数据直接显示图层, 并返回if(layerData){const layer = viewer.imageryLayers._layers.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =true;
      layerData.show =true;return;}// 不存在图层数据则重新加载图层const imageMap =newCesium.WebMapTileServiceImageryProvider({
      url: data.url
    });// 添加图层const layer = viewer.imageryLayers.addImageryProvider(imageMap);// 添加图层标识
    layer.layerId =GenerateId(18);// 向全局状态输入图层数据
    layerStore.addImageryLayer({...data, show:true, layerId: layer.layerId });};/**
   * @description: 移除wmts图层
   * @param {*} data
   * @return {*}
   */constremoveWmtsLayer=(data)=>{const layerData = layerStore.getImageryLayer(data.id);if(layerData){//  获取图层数据并设置显示为falseconst layer = viewer.imageryLayers._layers.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =false;
      layerData.show =false;}};/**
   * @description: 添加3dtiles图层
   * @param {*} data
   * @return {*}
   */constadd3dtilesLayer=async(data)=>{// 先查询是否已经加载图层const layerData = layerStore.getPrimitiveLayer(data.id);// 存在图层数据直接显示图层, 并返回if(layerData){const layer = viewer.scene.primitives._primitives.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =true;
      layerData.show =true;return;}const tileset =await Cesium.Cesium3DTileset.fromUrl(data.url);// 添加图层标识
    tileset.layerId =GenerateId(18);
    viewer.scene.primitives.add(tileset);// 向全局状态输入图层数据
    layerStore.addPrimitiveLayer({...data,
      show:true,
      layerId: tileset.layerId,});
    viewer.flyTo(tileset);};/**
   * @description: 移除3dtiles图层
   * @param {*} data
   * @return {*}
   */constremove3dtilesLayer=(data)=>{const layerData = layerStore.getPrimitiveLayer(data.id);if(layerData){const layer = viewer.scene.primitives._primitives.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =false;
      layerData.show =false;}};

3.2 统一对外暴露入口

继续在3.1编写的hooks函数内添加子方法及变量。

将不同类型的方法按照加载和卸载分为两类,以图层类别和方法作为 键值对 存储起来。
编写统一加载、卸载入口,通过图层类别去选择指定方法对图层进行加载和卸载操作。

/**
   * @description: 添加图层方法map,用于存储不同图层加载方法
   * @return {*}
   */const addLayerFunctions ={"wmts": addWmtsLayer,"3dtiles": add3dtilesLayer,// 其他类型对应的类别和方法// ...};/**
   * @description: 移除图层方法map,用于存储不同图层卸载方法
   * @return {*}
   */const removeLayerFunctions ={"wmts": removeWmtsLayer,"3dtiles": remove3dtilesLayer,// 其他类型对应的类别和方法// ...};/**
   * @description: 添加图层入口函数,根据图层类型,分配对应加载函数
   * @param {*} data
   * @return {*}
   */constaddLayer=(data)=>{return addLayerFunctions[data.type](data);};/**
   * @description: 移除图层入口函数,根据图层类型,分配对应卸载函数
   * @param {*} data
   * @return {*}
   */constremoveLayer=(data)=>{return removeLayerFunctions[data.type](data);};

3.3 提供图层类别的可拓展性

继续在3.1编写的hooks函数内添加子方法及变量。

提供查询和注入两个方法

  • 通过查询方法获取已经支持的图层类别。
  • 通过注入方法,可以自定义类别和加载、卸载方法,将自定义内容按照统一格式注入 addLayerFunctionsremoveLayerFunctions

这样做的好处是保证了hooks函数的封闭性同时可拓展,遵循了封装函数的开闭原则。

/**
   * @description: 获取已有的图层加载类型
   * @return {*}
   */constgetAvialableLayerTypes=()=>{return Object.keys(addLayerFunctions);};/**
   * @description: 手动添加特殊图层加载、卸载方法以及图层类别
   * @param {*} type
   * @param {*} addFunc
   * @param {*} removeFunc
   * @return {*}
   */constaddLayerType=(type, addFunc, removeFunc)=>{if(addLayerFunctions[type]){
      console.warn("图层方法已存在: "+ type);return;}
    addLayerFunctions[type]= addFunc;
    removeLayerFunctions[type]= removeFunc;};

3.1 完整代码

import{ useLayerStore }from"@/stores/layer.js";import{ GenerateId }from"@/utils/cesium/common.js";exportconstuseLayerManager=()=>{const layerStore =useLayerStore();/**
   * @description: 添加wmts图层
   * @param {*} data
   * @return {*}
   */constaddWmtsLayer=(data)=>{// 先查询是否已经加载图层const layerData = layerStore.getImageryLayer(data.id);// 存在图层数据直接显示图层, 并返回if(layerData){const layer = viewer.imageryLayers._layers.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =true;
      layerData.show =true;return;}// 不存在图层数据则重新加载图层const imageMap =newCesium.WebMapTileServiceImageryProvider({
      url: data.url
    });// 添加图层const layer = viewer.imageryLayers.addImageryProvider(imageMap);
    layer.layerId =GenerateId(18);// 向全局状态输入图层数据
    layerStore.addImageryLayer({...data, show:true, layerId: layer.layerId });};/**
   * @description: 移除wmts图层
   * @param {*} data
   * @return {*}
   */constremoveWmtsLayer=(data)=>{const layerData = layerStore.getImageryLayer(data.id);if(layerData){//  获取图层数据并设置显示为falseconst layer = viewer.imageryLayers._layers.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =false;
      layerData.show =false;}};/**
   * @description: 添加3dtiles图层
   * @param {*} data
   * @return {*}
   */constadd3dtilesLayer=async(data)=>{// 先查询是否已经加载图层const layerData = layerStore.getPrimitiveLayer(data.id);// 存在图层数据直接显示图层, 并返回if(layerData){//  获取图层数据并设置显示为falseconst layer = viewer.scene.primitives._primitives.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =true;
      layerData.show =true;return;}const tileset =await Cesium.Cesium3DTileset.fromUrl(data.url);// 添加标识
    tileset.layerId =GenerateId(18);
    viewer.scene.primitives.add(tileset);// 向全局状态输入图层数据
    layerStore.addPrimitiveLayer({...data,
      show:true,
      layerId: tileset.layerId,});
    viewer.flyTo(tileset);};/**
   * @description: 移除3dtiles图层
   * @param {*} data
   * @return {*}
   */constremove3dtilesLayer=(data)=>{const layerData = layerStore.getPrimitiveLayer(data.id);if(layerData){const layer = viewer.scene.primitives._primitives.find((item)=> item.layerId === layerData.layerId
      );
      layer.show =false;
      layerData.show =false;}};/**
   * @description: 添加图层方法map,用于存储不同图层加载方法
   * @return {*}
   */const addLayerFunctions ={"wmts": addWmtsLayer,"3dtiles": add3dtilesLayer,};/**
   * @description: 移除图层方法map,用于存储不同图层卸载方法
   * @return {*}
   */const removeLayerFunctions ={"wmts": removeWmtsLayer,"3dtiles": remove3dtilesLayer,};/**
   * @description: 添加图层入口函数,根据图层类型,分配对应加载函数
   * @param {*} data
   * @return {*}
   */constaddLayer=(data)=>{return addLayerFunctions[data.type](data);};/**
   * @description: 移除图层入口函数,根据图层类型,分配对应卸载函数
   * @param {*} data
   * @return {*}
   */constremoveLayer=(data)=>{return removeLayerFunctions[data.type](data);};/**
   * @description: 获取已有的图层加载类型
   * @return {*}
   */constgetAvialableLayerTypes=()=>{return Object.keys(addLayerFunctions);};/**
   * @description: 手动添加特殊图层加载、卸载方法以及图层类别
   * @param {*} type
   * @param {*} addFunc
   * @param {*} removeFunc
   * @return {*}
   */constaddLayerType=(type, addFunc, removeFunc)=>{if(addLayerFunctions[type]){
      console.warn("图层方法已存在: "+ type);return;}
    addLayerFunctions[type]= addFunc;
    removeLayerFunctions[type]= removeFunc;};return{
    addLayer,
    removeLayer,
    getAvialableLayerTypes,
    addLayerType,};};

4. hooks函数使用方法

<template><divid="mapContainer"><divclass="layermanager"><div>图层管理</div><el-tree:data="dataSource"show-checkboxnode-key="id"default-expand-allcheck-on-click-node@check-change="checkChange":expand-on-click-node="false"></el-tree></div></div></template><scriptsetup>import{ ref, onMounted }from"vue";import{ initCesiumMap }from"@/utils/cesium/index.js";import{ useLayerManager }from"@/hooks/useLayerManager.js";const dataSource =ref([{
    id:"1",
    label:"二维地图",
    children:[{
        id:"1-1",
        label:"天地图影像",
        type:"wmts",
        url:"https://t0.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你申请的key",},{
        id:"1-2",
        label:"天地图矢量",
        type:"wmts",
        url:"https://t0.tianditu.gov.cn/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你申请的key",},{
        id:"1-3",
        label:"天地图矢量注记",
        type:"wmts",
        url:"https://t0.tianditu.gov.cn/cva_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cva&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你申请的key",},],},{
    id:"2",
    label:"三维地图",
    children:[{
        id:"2-1",
        label:"测试3dtiles模型",
        type:"3dtiles",
        url:"/3dtiles/test/tileset.json",},],},]);const{ addLayer, removeLayer}=useLayerManager();onMounted(()=>{initCesiumMap();});constcheckChange=(data, isCheck)=>{// 判断是否为叶子节点if(data?.children && data?.children?.length >0){
    console.log("非子叶节点");return;}else{if(isCheck){addLayer(data);}else{removeLayer(data);}}};</script>

三、总结

至此图层管理的基础实现脉络已经梳理完毕,但是这些还远远不够,我们仍需关心以下几个问题:

  • 二维图层的互相遮挡问题,需要拓展图层调换顺序功能。
  • 图层叠加使用场景,需要拓展图层透明度调整功能。
  • 当前场景下图层保存功能,用于场景切换时,关闭及初始化图层树。
  • 拓展其他重要图层类型的加载卸载方式。
  • 其他。

所以说一个完整的模块是亿点点优化而来的。由于篇幅问题,遗留的几个问题我将会在之后的文章中逐步闭环。

再接再厉~

标签: 前端 vue3.0 webgis

本文转载自: https://blog.csdn.net/weixin_44666711/article/details/142723977
版权归原作者 qbbmnnnnnn 所有, 如有侵权,请联系我们删除。

“【WebGis开发 - Cesium】三维可视化项目教程---图层管理基础”的评论:

还没有评论