目录
- 引言
- 一、为什么要开发图层顺序调整功能
- 二、开发思路整理
- 1. 拖拽库方案选择
- 2. cesium图层api查询
- 三、代码编写
- 1. 编写拖拽组件代码
- 2. 修改原有图层管理代码
- 2.1 图层加载移除的调整
- 2.2 图层顺序与拖拽列表的矛盾
- 3. 编写图层移动代码
- 四、总结
引言
本教程主要是围绕Cesium这一开源三维框架开展的可视化项目教程。总结一下相关从业经验,如果有什么疑问或更好的见解,欢迎评论、私聊探讨,共同进步。教程依托于vue3前端框架,参考初始化内容:【WebGis开发 - Cesium】三维可视化项目教程—初始化场景
在【WebGis开发 - Cesium】三维可视化项目教程—图层管理基础 文中我们已经搭建了图层管理的基础框架代码。在本篇中我们将针对二维图层的叠加问题,拓展图层顺序调换功能。
一、为什么要开发图层顺序调整功能
cesium中二维图层由 ImageryLayerCollection
对象存储。二维图层没有高度,按照加载先后顺序重叠在一起。如果内容存在遮挡,则只显示最后加载的图层(好比一摞画册只能看到最上边的一幅画)。开发图层顺序调整功能就好比切牌,将最底下的图层切到上层,或者把最上层的图层切到下层,图层在 ImageryLayerCollection
中的 index
值越大,层级越靠上(越优先被看到)。
二、开发思路整理
首先整理一下要实现这个功能需要准备哪些东西:
- 切换图层顺序最直观的操作方式就是鼠标拖拽切换顺序,所以需要一个前端的拖拽功能。
- 直接拖拽图层树节点会导致数据错乱且逻辑复杂,最佳的体验方式是将已经加载的二维图层以列表形式单独展示并支持拖拽。
- 查询cesium官方是否存在调整图层顺序的api接口,方便我们整合为通用方法。
1. 拖拽库方案选择
开发一个拖拽库的成本较高,不如直接选择成熟的开源拖拽库:
- Draggable
- 项目地址:https://github.com/Shopify/draggable
- 在线演示:https://shopify.github.io/draggable/examples/
- Sortablejs
- 项目地址:https://github.com/SortableJS/Sortable
- 在线演示:https://sortablejs.github.io/Sortable/
本项目基于vue3.5.6前端框架开发,为了方便开发,我选择了Sortablejs的vue3版本插件 vue.draggable.next
在项目根目录下打开cmd输入:
yarn add vuedraggable@next
或
npm i -S vuedraggable@next
2. cesium图层api查询
在cesium的api文档页面中搜索 ImageryLayerCollection
查询相关图层方法
文档地址:cesium api 文档
根据我们的需求,我们需要图层查询方法以及排序方法如图所示:
- 查询图层方法
- 排序图层方法
三、代码编写
1. 编写拖拽组件代码
在原有图层管理的页面中继续添加拖拽组件
<template><div id="mapContainer"><div class="layerManager"><!-- 图层管理部分代码略 --><div>已选图层</div><draggablev-model="layerStore.imageryLayers"@change="dragChange"item-key="id"ghostClass="ghost"><template #item="{ element }"><div class="dragItem">{{ element.label }}</div></template></draggable></div></div>
</template><script setup>
import draggable from "vuedraggable";
import { useLayerStore } from "@/stores/layer.js";
import { useLayerManager } from "@/hooks/useLayerManager.js";
const layerStore = useLayerStore();
const { changeImageryLayerOrder } = useLayerManager();
/*** @description: 拖拽方法* @param {*} e 拖拽节点返回信息* @return {*}*/
const dragChange = (e) => {const move = e.moved;// 取出拖拽节点的layerId和index变化。const data = {layerId: move.element.layerId,oldIndex: move.oldIndex,newIndex: move.newIndex,};changeImageryLayerOrder(data);
};
</script>
解释下以上代码:
- 引入
draggable
组件,将组件绑定上pinia
存储的imageryLayers
列表,此处需要联动【WebGis开发 - Cesium】三维可视化项目教程—图层管理基础 的全局状态部分内容,忘记的可以回看一下。 - 绑定
draggable
组件的change事件,将拖拽后的信息收集起来,我们需要imageryLayers
中存储的图层layerId
(用于查询图层),和节点拖拽前后列表节点的index变化(用于判断图层移动了多少层)。 - 将处理后的数据传入
changeImageryLayerOrder
方法中实现图层切换层级顺序。
2. 修改原有图层管理代码
在图层管理基础一文中所写的添加、移除图层的方法只适应图层控制显隐这个单一功能,当添加图层顺序调整时需要对原有代码做出一些调整。
2.1 图层加载移除的调整
在原有的设计逻辑是二维
imageryLayer
图层在移除时不做删除仅作隐藏。当再次加载该图层时,先判断是否存在于pinia
的imageryLayers
列表中,如果存在则直接显示不再进行初始化加载以便节省加载时长。
当新增了图层顺序调整的功能后,原有的设计逻辑就不适用了。取消图层选中后,我希望已选图层的拖拽列表中该项消失。然而拖拽列表的数据绑定了imageryLayers
列表,原有的设计逻辑并不会移除 imageryLayers
列表中的该项,所以在拖拽列表中会存在一个已经取消勾选的图层。
在明白了这个逻辑后,对原有的加载移除方法进行修改:
调整stores/layer.js文件内容:
import { defineStore } from "pinia";
export const useLayerStore = defineStore("LayerStore", {
// 其他内容略actions: {// 其他内容略// 新增移除图层信息方法removeImageryLayer(id) {this.imageryLayers = this.imageryLayers.filter((item) => item.id !== id);},},
});
调整hooks/useLayerManager.js文件内容:
const addWmtsLayer = (data) => {// 直接注释加载前判断是否存在的代码逻辑 *******************************************// // 先查询是否已经加载图层// const layerData = layerStore.getImageryLayer(data.id);// // 存在图层数据直接显示图层, 并返回// if (layerData) {// // 获取图层数据并设置显示为false// // const layer = viewer.imageryLayers.get(layerData.layerId);// // 获取图层数据并设置显示为false// 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) {// 获取图层数据并设置显示为falseconst layer = viewer.imageryLayers._layers.find((item) => item.layerId === layerData.layerId);// 移除图层控制隐藏逻辑// layer.show = false;// layerData.show = false;// 新增图层删除逻辑viewer.imageryLayers.remove(layer);// 新增pinia中removeImageryLayer方法,将图层数据从imageryLayers列表中移除layerStore.removeImageryLayer(data.id);}};
看下此时的效果,已经可以正确的将图层控制与拖拽列表联动
2.2 图层顺序与拖拽列表的矛盾
这里有个逻辑矛盾。当新增图层时,我们会在
imageryLayers
列表中推入一个图层信息数据,正序排列。然而在已选图层列表里我们希望新增加的在最上边,感官上代表该图层在最上方。
调整stores/layer.js文件内容:
import { defineStore } from "pinia";
export const useLayerStore = defineStore("LayerStore", {
// 其他内容略actions: {// 其他内容略addImageryLayer(data) {// 移除末尾推入数据的方法// this.imageryLayers.push(data);// 修改为头部推入数据this.imageryLayers.unshift(data);},},
});
这样拖拽列表的顺序就和感官上保持一致了。
3. 编写图层移动代码
在hooks/useLayerManager.js文件中继续添加方法,注意最后将方法导出:
/*** @description: 二维图层移动方法* @param { Object } data 包含layerId、newIndex、oldIndex* @return {*}*/const changeImageryLayerOrder = (data) => {// 获取图层const layer = viewer.imageryLayers._layers.find((item) => item.layerId === data.layerId);const diff = data.newIndex - data.oldIndex;if (diff > 0) {// 向下移动// 判断移动多少层// if (data.newIndex == layerStore.imageryLayers.length - 1) {// // 移动到最底部// console.log("移动到最底部");// viewer.imageryLayers.lowerToBottom(layer);// } else {// 移动到中间态console.log("移动到中间态");for (let i = 0; i < Math.abs(diff); i++) {viewer.imageryLayers.lower(layer);}// }} else {// 向上移动if (data.newIndex == 0) {// 移动到顶部console.log("移动到顶部");viewer.imageryLayers.raiseToTop(layer);} else {// 移动到中间态console.log("移动到中间态");for (let i = 0; i < Math.abs(diff); i++) {viewer.imageryLayers.raise(layer);}}}};
解释下以上代码:
- 首先通过
layerId
从viewer.imageryLayers
中查询到指定图层。 - 计算图层拖拽前后的index变化
diff
值- 当diff大于0,说明新的图层位置的序号大于原有的位置的序号(注意这里的序号仅为拖拽列表的排序号)需要图层向下移动。
- 当diff小于0,说明新的图层位置的序号小于原有的位置的序号(注意这里的序号仅为拖拽列表的排序号)需要图层向上移动。
- 因为cesium提供的移动图层方法为
lower
和raise
,所以无需关心图层具体在viewer.imageryLayers
的图层序号。这里的图层序号和拖拽列表的图层序号在概念上是完全相反的。不过幸运的是你无需关心这里的差别也可以根据字面含义的方法正确的操作图层顺序。
- diff的数值大小代表需要图层需要移动多少次,以循环的方式完成操作。
- 当newIndex 值为0时表示图层需要移动到最顶部,所以直接使用
raiseToTop
方法直接移动到顶部。 - 一开始我也尝试判断newIndex 值和图层总数的关系,判断是否直接使用
lowerToBottom
移动到最底部。然而我发现,cesium会在场景初始化时默认添加一个imageryLayer。所以判断newIndex 值和图层总数永远不会相等(天然少一个)。
以上即完成了图层移动层级的功能拓展,看下效果:
四、总结
本篇主要是利用 vue.draggable.next
组件库结合cesium图层调整顺序api方法,完成了图层管理已选二维图层顺序调整的功能模块。根据功能拓展的需求,将原有图层管理操作逻辑进行修改完善以适应新功能的接入。重点在于根据业务的需求逻辑,封装cesium提供的原始api方法,形成维度更高的项目级api方法。
再接再厉~