知者何南 发表于 2024-12-4 22:49:38

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

引言

   本教程主要是围绕Cesium这一开源三维框架开展的可视化项目教程。总结一下相干从业经验,如果有什么疑问或更好的见解,接待批评、私聊探讨,共同进步。教程依托于vue3前端框架,参考初始化内容:【WebGis开发 - Cesium】三维可视化项目教程—初始化场景
本篇主要讨论如何管理二三维图层,仅以wmts和3dtiles做图层管理示例。我会在接下来的教程里专门针对其他各类图层加载方式做详细介绍。
二三维图层管理是三维可视化项目里紧张的组成部分,通过树结构管理图层,根据模块计划加载对应二三维数据。
先看效果:
https://i-blog.csdnimg.cn/direct/8fc8f5de13f746739b9537279dbd7ebc.gif
一、功能计划

1. 主体功能


[*]计划图层数据结构,使用任意树形结构插件或是自行开发图层树。
[*]编写图层分类加载、卸载方法,袒露统一接口,共同图层数据进行图层操作。
[*]存储数据至全局状态,方便管理。
2. 细节题目

   图层数据大抵上可以分为二维图层和三维图层
在三维引擎中针对这两者的表达方式有所差别


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

1. 树形控件

树形控件采用 element-plus 组件库的 tree 组件。细节查阅:Tree 树形控件
几个必要注意的点:


[*]根据需求设置 check-on-click-node ,我希望不论点击选择框或是点击树节点内容,都可以触发选中节点。
[*]根据需求设置 :expand-on-click-node="false" ,我不希望在点击包含有子节点的节点时,一边加载子节点图层内容,一边把树折叠起来。或是反过来卸载子节点图层内容,同时把树伸睁开。
[*]点击事件我选择 check-change ,只有这一个点击事件返回的数据符合我的需求。根据差别的计划理念,可以查阅组件库文档,选择适合本身项目的点选事件。
[*]图层管理核心在图层加载、卸载和全局数据管理,图层树可以根据本身喜好选择任意UI框架的树形结构,或是自行开发。满意展示树形图层以及选中、取消功能即可。
html部分
    <div class="layerManager">
      <div>图层管理</div>
      <el-tree
      :data="dataSource"
      show-checkbox
      node-key="id"
      default-expand-all
      check-on-click-node
      @check-change="checkChange"
      :expand-on-click-node="false"
      >
      </el-tree>
    </div>
数据部分
二维数据使用天地图影像、矢量、注记三种。三维数据使用本地3dtiles数据(有在线数据替换一下)。
天地图wmts图层引用示例可以参考:天地图服务 ,在页面最下方有请求示例。
https://i-blog.csdnimg.cn/direct/f764cef2063d4a25a104864ba94e47c5.png
树节点数据主要包含几个内容


[*]只有叶子节点存在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函数
主要包含几个内容:

[*]编写各类图层的加载和卸载方法。
[*]集成加载和卸载的入口方法,对外袒露行为统一。
[*]提供hooks函数的可拓展性。
3.1 加载、卸载方法编写

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

[*]加载方法

[*]先通过树节点 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);
};
/**
   * @description: 移除图层入口函数,根据图层类型,分配对应卸载函数
   * @param {*} data
   * @return {*}
   */
const removeLayer = (data) => {
    return removeLayerFunctions(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) {
      console.warn("图层方法已存在: " + type);
      return;
    }
    addLayerFunctions = addFunc;
    removeLayerFunctions = 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);};/**   * @description: 移除图层入口函数,根据图层类型,分配对应卸载函数   * @param {*} data   * @return {*}   */const removeLayer = (data) => {    return removeLayerFunctions(data);};/**
   * @description: 获取已有的图层加载类型
   * @return {*}
   */
const getAvialableLayerTypes = () => {
    return Object.keys(addLayerFunctions);
};
/**
   * @description: 手动添加特殊图层加载、卸载方法以及图层类别
   * @param {*} type
   * @param {*} addFunc
   * @param {*} removeFunc
   * @return {*}
   */
const addLayerType = (type, addFunc, removeFunc) => {
    if (addLayerFunctions) {
      console.warn("图层方法已存在: " + type);
      return;
    }
    addLayerFunctions = addFunc;
    removeLayerFunctions = removeFunc;
};
return {    addLayer,    removeLayer,    getAvialableLayerTypes,    addLayerType,};}; 4. hooks函数使用方法

<template>
<div id="mapContainer">
    <div class="layermanager">
      <div>图层管理</div>
      <el-tree
      :data="dataSource"
      show-checkbox
      node-key="id"
      default-expand-all
      check-on-click-node
      @check-change="checkChange"
      :expand-on-click-node="false"
      >
      </el-tree>
    </div>
</div>
</template>

<script setup>
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);
    }
}
};
</script>

三、总结

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


[*]二维图层的相互遮挡题目,必要拓展图层调换次序功能。
[*]图层叠加使用场景,必要拓展图层透明度调解功能。
[*]当前场景下图层保存功能,用于场景切换时,关闭及初始化图层树。
[*]拓展其他紧张图层类型的加载卸载方式。
[*]其他。
所以说一个完备的模块是亿点点优化而来的。由于篇幅题目,遗留的几个题目我将会在之后的文章中逐步闭环。
再接再厉~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【WebGis开发 - Cesium】三维可视化项目教程---图层管理基础