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

06-01 1261阅读

目录

  • 引言
  • 一、功能设计
    • 1. 主体功能
    • 2. 细节问题
    • 二、代码实现
      • 1. 树形控件
      • 2. 全局状态准备
      • 3. 创建图层控制方法
        • 3.1 加载、卸载方法编写
        • 3.2 统一对外暴露入口
        • 3.3 提供图层类别的可拓展性
        • 3.1 完整代码
        • 4. hooks函数使用方法
        • 三、总结

          引言

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

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

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

          先看效果:

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


          一、功能设计

          1. 主体功能

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

          2. 细节问题

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

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

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

            二、代码实现

            1. 树形控件

            树形控件采用 element-plus 组件库的 tree 组件。细节查阅:Tree 树形控件

            几个需要注意的点:

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

              html部分

                  
              图层管理

              数据部分

              二维数据使用天地图影像、矢量、注记三种。三维数据使用本地3dtiles数据(有在线数据替换一下)。

              天地图wmts图层引用示例可以参考:天地图服务 ,在页面最下方有请求示例。

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

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

              • 只有叶子节点存在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 形式存储在全局变量 viewer 的 imageryLayers 属性中。
                • 3dtiles图层是以 primitive 形式存储在 viewer.scene 的 primitives 属性中。

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

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

                  3. 创建图层控制方法

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

                  主要包含几个内容:

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

                  3.1 加载、卸载方法编写

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

                  描述一下我的编写逻辑:

                  1. 加载方法
                    • 先通过树节点 id 到全局状态中查询图层信息是否已经存在。
                    • 如果已经加载过了,就通过保存的特殊标识查询到对应图层数据,将 show 属性改写为 true 即可。
                    • 如果没有加载过,则调用对应api加载图层,加载完成后返回对应图层数据。
                    • 创建图层特殊标识,用于查询到指定图层,将特殊标识添加到返回图层数据的自定义属性中,我这里设置的是layerId,确保标识值唯一。
                    • 将 layerId 以及树节点信息保存至全局状态中。
                    • 卸载方法
                      • 先通过树节点 id 到全局状态中查询图层信息是否已经存在。
                      • 如果存在,就通过保存的特殊标识查询到对应图层数据,将 show 属性改写为 false 即可。
                      • 不存在则不进行操作。
                    import { useLayerStore } from "@/stores/layer.js";
                    import { GenerateId } from "@/utils/cesium/common.js";
                    const layerStore = useLayerStore();
                    /**
                     * @description: 添加wmts图层
                     * @param {*} data
                     * @return {*}
                     */
                    const addWmtsLayer = (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 = new Cesium.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 {*}
                     */
                    const removeWmtsLayer = (data) => {
                      const layerData = layerStore.getImageryLayer(data.id);
                      if (layerData) {
                        //  获取图层数据并设置显示为false
                        const layer = viewer.imageryLayers._layers.find(
                          (item) => item.layerId === layerData.layerId
                        );
                        layer.show = false;
                        layerData.show = false;
                      }
                    };
                    /**
                     * @description: 添加3dtiles图层
                     * @param {*} data
                     * @return {*}
                     */
                    const add3dtilesLayer = 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 {*}
                     */
                    const remove3dtilesLayer = (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 {*}
                     */
                    const addLayer = (data) => {
                      return addLayerFunctions[data.type](data);
                    };
                    /**
                     * @description: 移除图层入口函数,根据图层类型,分配对应卸载函数
                     * @param {*} data
                     * @return {*}
                     */
                    const removeLayer = (data) => {
                      return removeLayerFunctions[data.type](data);
                    };
                  

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

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

                  提供查询和注入两个方法

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

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

                      /**
                       * @description: 获取已有的图层加载类型
                       * @return {*}
                       */
                      const getAvialableLayerTypes = () => {
                        return Object.keys(addLayerFunctions);
                      };
                      /**
                       * @description: 手动添加特殊图层加载、卸载方法以及图层类别
                       * @param {*} type
                       * @param {*} addFunc
                       * @param {*} removeFunc
                       * @return {*}
                       */
                      const addLayerType = (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";
                    export const useLayerManager = () => {
                      const layerStore = useLayerStore();
                      /**
                       * @description: 添加wmts图层
                       * @param {*} data
                       * @return {*}
                       */
                      const addWmtsLayer = (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 = new Cesium.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 {*}
                       */
                      const removeWmtsLayer = (data) => {
                        const layerData = layerStore.getImageryLayer(data.id);
                        if (layerData) {
                          //  获取图层数据并设置显示为false
                          const layer = viewer.imageryLayers._layers.find(
                            (item) => item.layerId === layerData.layerId
                          );
                          layer.show = false;
                          layerData.show = false;
                        }
                      };
                      /**
                       * @description: 添加3dtiles图层
                       * @param {*} data
                       * @return {*}
                       */
                      const add3dtilesLayer = async (data) => {
                        // 先查询是否已经加载图层
                        const layerData = layerStore.getPrimitiveLayer(data.id);
                        // 存在图层数据直接显示图层, 并返回
                        if (layerData) {
                          //  获取图层数据并设置显示为false
                          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 {*}
                       */
                      const remove3dtilesLayer = (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 {*}
                       */
                      const addLayer = (data) => {
                        return addLayerFunctions[data.type](data);
                      };
                      /**
                       * @description: 移除图层入口函数,根据图层类型,分配对应卸载函数
                       * @param {*} data
                       * @return {*}
                       */
                      const removeLayer = (data) => {
                        return removeLayerFunctions[data.type](data);
                      };
                      /**
                       * @description: 获取已有的图层加载类型
                       * @return {*}
                       */
                      const getAvialableLayerTypes = () => {
                        return Object.keys(addLayerFunctions);
                      };
                      /**
                       * @description: 手动添加特殊图层加载、卸载方法以及图层类别
                       * @param {*} type
                       * @param {*} addFunc
                       * @param {*} removeFunc
                       * @return {*}
                       */
                      const addLayerType = (type, addFunc, removeFunc) => {
                        if (addLayerFunctions[type]) {
                          console.warn("图层方法已存在: " + type);
                          return;
                        }
                        addLayerFunctions[type] = addFunc;
                        removeLayerFunctions[type] = removeFunc;
                      };
                      return {
                        addLayer,
                        removeLayer,
                        getAvialableLayerTypes,
                        addLayerType,
                      };
                    };
                    

                    4. hooks函数使用方法

                      
                    图层管理
                    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(); }); const checkChange = (data, isCheck) => { // 判断是否为叶子节点 if (data?.children && data?.children?.length > 0) { console.log("非子叶节点"); return; } else { if (isCheck) { addLayer(data); } else { removeLayer(data); } } };

                    三、总结

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

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

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

                      再接再厉~

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码