Cesium学习笔记——Entity点线面的绘制及封装(二)

前言

        在Cesium的学习中,学会读文档十分重要!!!在这里附上中文文档Cesium中文文档1.95

        上一篇笔记讲解了点和线的绘制,这次我们来学习一下如何绘制面及点线面绘制工具的封装。面的绘制过程与前面线的大致一致,都是先注册鼠标左击事件,左击更新绘制面,鼠标移动动态绘制面,右击结束绘制。

        而点线面的绘制代码的封装则是为了代码的复用,后续许多地方将用到代码此代码,如小车的运动轨迹绘制,积云的大量生成等。

一、面的绘制

1.1、面的鼠标左击事件

        与线一样,通过CallbackProperty事件来实现线的动态更新,为了方便显示,我们在面的边缘添加了线,因此,我们要判断,当只绘制了一个点时,只绘制这个点到鼠标的线,不绘制面,当大于一个点时,才开始绘制面。

drawHandler.setInputAction((e) => {
  let p = viewer.scene.pickPosition(e.position);
  if (!p) return;
  points.push(p);
  if (points.length == 1) {
    //一个顶点+移动点
    viewer.entities.add({
      polyline: {
        positions: new Cesium.CallbackProperty(() => {
          let c = Array.from(points);
          if (mousePos) {
            c.push(mousePos);
          }
          return c;
        }, false),
        clampToGround: true, //贴地
        width: 3,
        material: new Cesium.PolylineDashMaterialProperty({
          color: Cesium.Color.YELLOW,
        }),
      },
    });
  } else {
    viewer.entities.removeAll();
    //两个顶点+移动点
    viewer.entities.add({
      polygon: {
        hierarchy: new Cesium.CallbackProperty(() => {
          let poss = Array.from(points);
          if (mousePos) {
            poss.push(mousePos);
          }
          return new Cesium.PolygonHierarchy(poss);
        }, false),
        material: Cesium.Color.RED.withAlpha(0.4),
        clampToGround: true,
      },
      polyline: {
        positions: new Cesium.CallbackProperty(() => {
          let c = Array.from(points);
          if (mousePos) {
            c.push(mousePos);
            c.push(c[0]); //与第一个点相连
          }
          return c;
        }, false),
        clampToGround: true, //贴地
        width: 3,
        material: new Cesium.PolylineDashMaterialProperty({
          color: Cesium.Color.YELLOW,
        }),
      },
    });
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

        面的属性与线也类似。

1.2、面的鼠标移动事件

        这里和线一样,更改鼠标的坐标就好了。

drawEventHandler.setInputAction((e) => {
  let p = this.viewer.scene.pickPosition(e.endPosition);
  if (!p) return;
  mousePosition = p;//存鼠标的位置
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

1.3、面的鼠标右击事件

        鼠标右击时则把前面两个事件清除,把之前的面和线也清除,最后重新再绘制points中的点。

drawHandler.setInputAction((e) => {
  let p = viewer.scene.pickPosition(e.position);
  if (!p) return;
  points.push(points[0]);
  drawEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
  drawEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  viewer.entities.removeAll();
  const polygonEntity = viewer.entities.add({
    polyline: {
      positions: points,
      clampToGround: true, //贴地
      width: 3,
      material: new Cesium.PolylineDashMaterialProperty({
        color: Cesium.Color.YELLOW,
      }),
      depthFailMaterial: new Cesium.PolylineDashMaterialProperty({
        color: Cesium.Color.YELLOW,
      }),
    },
    polygon: {
      material: Cesium.Color.RED.withAlpha(0.4),
      clampToGround: true,
    },
  });
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);

二、点线面的绘制代码的封装

        前面写了这么久,我们发现点线面的绘制其实不难实现,但是代码量却异常的多,以后如果再调用,则还需写这么多代码。这样写代码,不不仅繁琐,还不利于我们维护,因此我们将其封装成一个绘制工具类。后面使用的时候直接引入该类名,就可以通过其创建一个绘制对象了。

import * as Cesium from 'cesium';

export default class DrawEntityGraphic {
    }        

2.1构造函数

        在每个类里面都有一个构造器,即初始化时传的参数。其中dataSource用来存放CustomDataSource对象,相当于一个新空间,与原来的entities隔离开来,方便数据管理,比如当我们需要删除所有的线对象是,调用removeAll时,就不会把其他实体给移除了。

  constructor(viewer) {
    this.viewer = viewer;// Cesium.Viewer实例
    this._drawEventHandler = null;// 绘制事件处理器
    this._drawType = '';// 绘制类型
    this._dataSource = null;// 数据容器
    this._points = [];// 点集合
    this._mousePosition = null;// 鼠标位置
    this._dynamicEntity = null; // 用于存储动态绘制的临时实体
  }

2.2、激活绘制功能。

        紧接着,我们需要一个激活函数,用来将上面的值赋值,以及初始化事件。

   /**
  * 激活绘制模式
  * @param { 'point' | 'line' | 'polygon' } drawType 绘制类型
    * @param { Function } [callback] 绘制完成后的回调函数
      */
  activate(drawType, callback) {
    this.clearAll();
    this._drawType = drawType;
    this._dataSource = new Cesium.CustomDataSource('_drawDataSource');
    this.viewer.dataSources.add(this._dataSource);
    this._initDrawEvents(callback);
  }

        上面这里的初始化函数还没有实现,我们这里是要根据不同的绘制类型,来初始化不同的事件。其中,参数为回调函数,这个回调函数是为了当右击完成后,即绘制完成时,触发一个事件,比如,小车开始运动,绘制积云等。

/**
  * 初始化绘图事件监听
  * @param { Function } callback 完成回调
    */
  _initDrawEvents(callback) {
    this.viewer.scene.globe.depthTestAgainstTerrain = true;

    this._drawEventHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);

    // 公共事件处理
    const eventConfig = {
      point: {
        leftClick: this._handlePointLeftClick.bind(this),
      },
      line: {
        leftClick: this._handleLineLeftClick.bind(this),
        mouseMove: this._handleMouseMove.bind(this),
        rightClick: this._handleLineRightClick.bind(this, callback),
      },
      polygon: {
        leftClick: this._handlePolygonLeftClick.bind(this),
        mouseMove: this._handleMouseMove.bind(this),
        rightClick: this._handlePolygonRightClick.bind(this, callback),
      }
    };

    const config = eventConfig[this._drawType];
    Object.entries(config).forEach(([event, handler]) => {
      const cesiumEvent = this._getCesiumEventType(event);
      this._drawEventHandler.setInputAction(handler, cesiumEvent);
    });
  }

  /**
  * 获取Cesium事件类型
  * @param { String } event 事件名称
    */
  _getCesiumEventType(event) {
    const eventMap = {
      leftClick: Cesium.ScreenSpaceEventType.LEFT_CLICK,
      rightClick: Cesium.ScreenSpaceEventType.RIGHT_CLICK,
      mouseMove: Cesium.ScreenSpaceEventType.MOUSE_MOVE,
    };
    return eventMap[event];
  }

2.3、处理左击事件

        接下来,则是分别实现点线面的左击事件。其中,每个事件都有一个creat...函数,这里我们根据不同实现不同的函数。(后面实现)。在面的绘制中,如果当前点==1,则是绘制线。大于1才开始绘制面。

/**
  * 处理点绘制的左键点击
  */
  _handlePointLeftClick(e) {
    const position = this.viewer.scene.pickPosition(e.position);
    if (!position) return;

    const cartographic = Cesium.Cartographic.fromCartesian(position);
    const point = [
      Cesium.Math.toDegrees(cartographic.longitude),
      Cesium.Math.toDegrees(cartographic.latitude),
      cartographic.height + 10000 // 可配置化高度偏移
    ];

    this._dataSource.entities.add(this._createPointEntity(point));
  }

  /**
  * 处理线绘制的左键点击
  */
  _handleLineLeftClick(e) {
    const position = this.viewer.scene.pickPosition(e.position);
    if (!position) return;

    this._points.push(position);

    if (this._points.length === 1) {
      this._dynamicEntity = this._dataSource.entities.add(this._createDynamicPolyline());
    }
  }

  /**
  * 处理多边形绘制的左键点击
  */
  _handlePolygonLeftClick(e) {
    const position = this.viewer.scene.pickPosition(e.position);
    if (!position) return;

    this._points.push(position);

    if (this._points.length === 1) {
      this._dynamicEntity = this._dataSource.entities.add(this._createDynamicPolyline());
    } else {
      this._updatePolygonEntities();
    }
  }

2.4、处理鼠标移动事件

        面和线的鼠标移动事件都相同,使用我们只需要一个事件。

  /**
  * 处理鼠标移动事件
  */
  _handleMouseMove(e) {
    this._mousePosition = this.viewer.scene.pickPosition(e.endPosition);
  }

2.5、处理右击事件

        在右击事件中,不需要把鼠标移动到点加上去,因此,我们需要把之前的绘制删去,如何重新绘制最终的形状。回调函数的使用也是在这里。

 /**
  * 处理线绘制的右键点击
  */
  _handleLineRightClick(callback) {
    if (this._points.length < 2) return;

    this._cleanup();
    const lineEntity = this._dataSource.entities.add(this._createFinalPolyline());
    callback?.(this._points, lineEntity);
  }

  /**
  * 处理多边形绘制的右键点击
  */
  _handlePolygonRightClick(callback) {
    if (this._points.length < 3) return;

    this._points.push(this._points[0]); // 闭合多边形
    this._cleanup();

    const polygonEntity = this._dataSource.entities.add(this._createFinalPolygon());
    callback?.(this._points, polygonEntity);
  }

2.6、点线面的绘制

        然后,我们将上面没有实现的绘制事件给实现。

  /**
  * 创建点实体
  */
  _createPointEntity(position) {
    return {
      position: Cesium.Cartesian3.fromDegrees(...position),
      point: {
        color: Cesium.Color.WHITE,
        pixelSize: 10,
        outlineColor: Cesium.Color.BLUE,
        outlineWidth: 1,
      }
    };
  }

  /**
  * 创建动态折线实体
  */
  _createDynamicPolyline() {
    return {
      polyline: {
        positions: new Cesium.CallbackProperty(() =>
          this._points.concat(this._mousePosition || []), false),
        clampToGround: true,
        width: 3,
        material: new Cesium.PolylineGlowMaterialProperty({
          color: Cesium.Color.AQUA,
        }),
      }
    };
  }

  /**
  * 创建最终折线实体
  */
  _createFinalPolyline() {
    return {
      polyline: {
        positions: this._points,
        clampToGround: true,
        width: 3,
        material: new Cesium.PolylineGlowMaterialProperty({
          color: Cesium.Color.AQUA,
        }),
      }
    };
  }

  /**
  * 更新多边形相关实体
  */
  _updatePolygonEntities() {
    this._dataSource.entities.remove(this._dynamicEntity);

    this._dynamicEntity = this._dataSource.entities.add({
      polygon: {
        hierarchy: new Cesium.CallbackProperty(() =>
          new Cesium.PolygonHierarchy(this._points.concat(this._mousePosition || [])), false),
        material: Cesium.Color.SKYBLUE.withAlpha(0.4),
        clampToGround: true,
      },
      polyline: {
        positions: new Cesium.CallbackProperty(() => {
          const pts = this._points.concat(this._mousePosition || []);
          return pts.length > 1 ? pts.concat([pts[0]]) : pts;
        }, false),
        clampToGround: true,
        width: 3,
        material: new Cesium.PolylineGlowMaterialProperty({
          color: Cesium.Color.AQUA,
        }),
      },
    });
  }

  /**
  * 创建最终多边形实体
  */
  _createFinalPolygon() {
    return {
      polygon: {
        hierarchy: this._points,
        material: Cesium.Color.SKYBLUE.withAlpha(0.4),
        clampToGround: true,
      },
      polyline: {
        positions: this._points,
        clampToGround: true,
        width: 3,
        material: new Cesium.PolylineGlowMaterialProperty({
          color: Cesium.Color.AQUA,
        }),
      }
    };
  }

2.7、资源释放

        当entities里的实体不再需要时,要及时释放掉,不然会可能导致cesium很卡。这里就是把实体及相关事件全部释放。

  /**
  * 清理临时数据
  */
  _cleanup() {
    this._drawEventHandler?.destroy();
    this._drawEventHandler = null;
    this._mousePosition = null;
  }

  /**
  * 清除所有绘制内容
  */
  clearAll() {
    this._cleanup();
    if (this._dataSource) {
      this.viewer.dataSources.remove(this._dataSource);
      this._dataSource = null;
    }
    this._points = [];
    this._dynamicEntity = null;
  }

三、封装类使用案例

        这个类的使用步骤就三步,首先新建对象,然后调用激活函数,最后释放资源。

        这里我用vue3及element的按钮来演示,先新建对象,然后当点击按钮时调用激活函数,即可以开始绘制,最后当切换页面时释放资源(也可以是其他时候,如清除按钮)。这里我用pinia管理了viewer,在其他组件里将其引入。

<template>
    <el-button :icon="Location" @click="addPolygon()"></el-button>

</template>
<script setup>
import useStore from '@/stores';
import DrawEntityGraphic from '@/utils/drawEntityGraphic';
import { Location } from '@element-plus/icons-vue';
import { onUnmounted } from 'vue';
const { viewer } = useStore();
const drawEntityGraphic = new DrawEntityGraphic(viewer);
onUnmounted(() => {
    drawEntityGraphic.clearAll();
});
const addPolygon = () => {
    drawEntityGraphic.activate('polygon');
};
</script>
<style scoped></style>

        最终运行效果如下。

        如果喜欢我的文章的话,请点一个免费的赞和关注吧!

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凕雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值