Cesium地形开挖-附带源码

2,138 阅读4分钟

实现思路

  1. 创建TerrainCutting类;
  2. 使用Cesium的ScreenSpaceEventHandler对象实现鼠标事件监听,获取地形开挖的区域坐标;
  3. 使用Cesium的clippingPlanes对象,构建挖掘区域;
  4. 使用corridor来绘制开挖区域侧边墙体;
  5. 使用polygon绘制开挖区域地面;

WX20230112-141343.png

一、创建TerrainCutting类

1、定义TerrainCutting的属性,用于储存挖掘区域的坐标数据以及已经绘制到地图上的图层对象。

export default class TerrainCutting {
    //场景
    viewer: any;
    //裁剪区域数组
    clippingPlanesArray: Array<any>;
    //多边形对象
    temporayrPolygonEntity: any;
    //挖掘墙体
    clippingWallEntities: any;
    //挖掘墙体底部区域
    clippingBootomWallEntities: any;
    //点位
    clippingPoint: Array<any>;
    //鼠标对象
    cesiumEvent: any;
    //鼠标按压状态
    cesiumEventState: string;
    //当前点击的覆盖物
    activeClickPick: any;
    //信息窗体Dom
    infoWindowElement: any;
    //定义挖掘深度
    clippingDeepValue: number;
    //窗口更新事件
    preRenderEvent: any;
    constructor(viewer: any) {
        this.viewer = viewer;
        //注册区域挖掘
        this.clippingPlanesArray = [];
        this.clippingPoint = [];
        this.temporayrPolygonEntity = null;
        this.clippingWallEntities = null;
        this.clippingBootomWallEntities = null;
        this.activeClickPick = null;
        this.infoWindowElement = null;
        this.preRenderEvent = null;
        this.clippingDeepValue = 20;
        this.cesiumEventState = "";
    }
}
//开始挖掘
create() {

}
//停止挖掘
//解绑事件,销毁挖掘图层,用于在其他操作之前,关闭挖掘功能
stop(){

}
//销毁挖掘图层,不解绑事件,用于重新绘制
destroy() {

}
//保存鼠标点击获取到的挖掘区域,并且根据坐标绘制出开挖区域
saveClippingPlaneCollectionData(){
}
//实现挖掘功能,用于挖掘区域的创建和更新
clippingPlaneCollection(){
}
//修改定位点选中状态
selectClickPoint(){
}
//修改选中点的位置,更新矩形边框位置
changeLayerPointPostion(event) {
}

2、实现create方法,完成鼠标事件绑定,开启挖掘功能

  1. 绑定LEFT_CLICK事件,记录开挖坐标;
  2. 绑定RIGHT_CLICK事件,完成开挖坐标记录,绘制开挖区域;
  3. 绑定LEFT_DOWN事件,并且判断是否点击了开挖坐标点,更新坐标点的高亮状态;
  4. 绑定MOUSE_MOVE事件,并切判断移动的是否为开挖坐标点,用于更新坐标点的位置和重新计算开挖区域;(实现挖掘区域的动态调整功能)
create() {
    //开始之前清除数据
    this.cesiumEvent = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
    this.cesiumEvent.setInputAction((event) => {
        //更新鼠标状态
        this.cesiumEventState = "leftClick";
        //当前点击覆盖物
        this.activeClickPick = this.viewer.scene.pick(event.position);
        //点击空白区域重新绘制
        if (!this.activeClickPick || !this.activeClickPick.id) {
            //恢复地形开挖状态
            if (this.clippingWallEntities) {
                    this.destroy();
            }

            //绘制地形开挖边界
            this.saveClippingPlaneCollectionData(event);
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    //鼠标按下事件
    this.cesiumEvent.setInputAction((event) => {
            //更新鼠标状态
            this.cesiumEventState = "leftDown";
            //当前点击覆盖物
            this.activeClickPick = this.viewer.scene.pick(event.position);
            if (this.clippingWallEntities) {
                    if (this.activeClickPick && this.activeClickPick.id) {
                            //选中点击位置
                            this.selectClickPoint();
                    }
            }

    }, Cesium.ScreenSpaceEventType.LEFT_DOWN);

    //鼠标抬起事件
    this.cesiumEvent.setInputAction((event) => {
            //更新鼠标状态
            this.cesiumEventState = "leftUp";
    }, Cesium.ScreenSpaceEventType.LEFT_UP);

    //鼠标移动事件
    this.cesiumEvent.setInputAction((event) => {

            if (this.activeClickPick && this.activeClickPick.id) {
                    //判断鼠标是否为按压状态
                    if (this.cesiumEventState == "leftDown") {
                            this.changeLayerPointPostion(event);
                    }
            }

    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

    this.cesiumEvent.setInputAction((event) => {
            //更新鼠标状态
            this.cesiumEventState = "rightClick";

            this.clippingPlaneCollection();
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}

2、实现saveClippingPlaneCollectionData方法,实现鼠标点击地图绘制坐标点和开挖区域多边形效果。

WX20230112-151553.png

//保存区域挖掘数据
saveClippingPlaneCollectionData(event) {
    var cartesian = this.viewer.camera.pickEllipsoid(event.position, this.viewer.scene.globe.ellipsoid);
    this.clippingPlanesArray.push(cartesian);
    //绘制点
    var clippingPoint = this.viewer.entities.add({
            name: "定位点",
            position: cartesian,
            point: {
                    color: Cesium.Color.SKYBLUE,
                    pixelSize: 10,
                    outlineColor: Cesium.Color.YELLOW,
                    outlineWidth: 3,
                    disableDepthTestDistance: Number.POSITIVE_INFINITY,
                    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            }
    });
    this.clippingPoint.push(clippingPoint);
    //绘制开挖区域                             
    if (!this.temporayrPolygonEntity) {
        this.temporayrPolygonEntity = this.viewer.entities.add({
            polygon: {
                hierarchy: new Cesium.CallbackProperty(() => {
                        return new Cesium.PolygonHierarchy(this.clippingPlanesArray);
                }),
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                material: Cesium.Color.RED.withAlpha(0.5)
            }
        });
    }
}

3、实现clippingPlaneCollection方法,绘制挖掘效果。

WX20230112-152805.png

WX20230112-152842.png

  1. 在挖掘之前我们需要判断是否已经存在挖掘区域图层,如果存在需要使用entities.remove来移除原有图层,这样我们在第一次开挖和更新开挖区域时才不会出现图层冲突的问题。简单来说就是,每次修改都是修改数据,在使用修改过的数据重新绘制图层。
  2. 划重点开挖效果的实现原理其实是使用Cesium的图层裁剪clippingPlanes来形成一个镂空区域,然后使用corridor来绘制周围的墙体,然后用polygon来绘制地面,未添加这两个图层之前的效果如下

WX20230112-153410.png

//开始挖掘
clippingPlaneCollection() {

        //开始挖掘之前移除已经挖掘过的区域
        if (this.clippingWallEntities) {
                this.viewer.entities.remove(this.clippingWallEntities);
                this.clippingWallEntities = null;
        }

        if (this.clippingBootomWallEntities) {
                this.viewer.entities.remove(this.clippingBootomWallEntities);
                this.clippingBootomWallEntities = null;
        }

        this.viewer.scene.globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
                planes: [],
                edgeWidth: 0
        });

        var points = this.clippingPlanesArray;



        var pointsLength = points.length;
        var clippingPlanes = [];
        for (var i = 0; i < pointsLength; ++i) {
                var nextIndex = (i + 1) % pointsLength;

                var midpoint = Cesium.Cartesian3.add(points[i], points[nextIndex], new Cesium.Cartesian3());
                midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint);

                var up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3());
                var right = Cesium.Cartesian3.subtract(points[nextIndex], midpoint, new Cesium.Cartesian3());
                right = Cesium.Cartesian3.normalize(right, right);

                var normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
                normal = Cesium.Cartesian3.normalize(normal, normal);

                var originCenteredPlane = new Cesium.Plane(normal, 0.0);
                var distance = Cesium.Plane.getPointDistance(originCenteredPlane, midpoint);

                clippingPlanes.push(new Cesium.ClippingPlane(normal, distance));
        }

        this.viewer.scene.globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
                planes: clippingPlanes,
                edgeWidth: 1
        });

        // 侧边墙体
        this.clippingWallEntities = this.viewer.entities.add({
                corridor: {
                        positions: [
                                ...this.clippingPlanesArray,
                                this.clippingPlanesArray[0],
                                this.clippingPlanesArray[1]
                        ],
                        height: this.clippingDeepValue * -1,
                        extrudedHeight: 0,
                        width: 1,
                        cornerType: Cesium.CornerType.ROUNDED,
                        material: Cesium.Color.fromCssColorString(`rgba(66,187,133,1)`),

                        outline: false
                },
        });
        //底部墙体
        this.clippingBootomWallEntities = this.viewer.entities.add({
                polygon: {
                        hierarchy: this.clippingPlanesArray,
                        height: this.clippingDeepValue * -1,
                        extrudedHeight: this.clippingDeepValue * -1 + 1,
                        material: Cesium.Color.YELLOW
                },
        });

}

4、selectClickPoint和changeLayerPointPostion方法,实现挖掘区域跟随鼠标移动动态更新

鼠标拖动红色高亮点,即可实现开挖区域的编辑

WechatIMG907.png

//修改定位点选中状态
selectClickPoint() {
        if (this.activeClickPick.id.name == "定位点") {
            for (var i = 0; i < this.clippingPoint.length; i++) {
                    this.clippingPoint[i].point.color = Cesium.Color.SKYBLUE;
            }
            this.activeClickPick.id.point.color = Cesium.Color.RED;
        }
}

//修改选中点的位置,更新矩形边框位置
changeLayerPointPostion(event) {
        var cartesian = this.viewer.camera.pickEllipsoid(event.endPosition, this.viewer.scene.globe.ellipsoid);
        this.activeClickPick.id.position = cartesian;
        //根据ID得到修改的index
        var activeIndex = -1;
        for (var i = 0; i < this.clippingPoint.length; i++) {
            if (this.activeClickPick.id._id == this.clippingPoint[i]._id) {
                    activeIndex = i;
            }
        }
        this.clippingPlanesArray[activeIndex] = cartesian;
        //修改挖掘区域
        if (this.clippingWallEntities) {
            this.clippingPlaneCollection();
        }
}

二、至此,TerrainCutting类的核心功能开发结束,我们在页面中则可以使用如下方法调用工具类。

//引入工具类
import TerrainCutting from "../../Cesium/TerrainCutting";
//此代码为我本地Cesium基类,可忽略
this.CesiumBasic=new CesiumBasic("cesiumContainer");
//实例化TerrainCutting类
this.TerrainCutting=new TerrainCutting(this.CesiumBasic.viewer);
//开始挖掘
this.TerrainCutting.create();
//停止挖掘
this.TerrainCutting.stop();

三、写在最后

完整工具类引入可直接使用,代码地址gitee.com/desever/ter…