26.cesuim分析之填挖方计算

文章介绍了如何使用Cesium库和turf.js进行前端开发,通过泰森多边形和Voronoi算法计算每个多边形的切方和填方体积,用于地形分析和可视化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

填挖结果

在这里插入图片描述

前端代码

let viewer=null
let baseHeight = 500
let tempEntities = []
const computeCutAndFillVolumeVoronoi = (positions) => {
    const result = {
        minHeight: Number.MAX_VALUE,
        maxHeight: Number.MIN_VALUE,
        cutVolume: 0.0,
        fillVolume: 0.0,
        baseArea: 0.0
    };
    //声明屏幕坐标数组
    const windowPositions = [];
    //先遍历一下多边形节点,获取最低高程点,作为基准高程
    //同时将Cartesian3转为屏幕坐标,存放在数组中
    positions.forEach(element => {
        const cartographic = Cesium.Cartographic.fromCartesian(element);
        baseHeight = Math.min(cartographic.height, baseHeight);
        windowPositions.push(Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene, element));
    })
    //构建泰森多边形的过程
    const bounds = getBounds(windowPositions);
    const points = turf.randomPoint(50, { bbox: [bounds[0], bounds[1], bounds[2], bounds[3]] });
    const mainPoly = Cartesian2turfPolygon(windowPositions);
    const voronoiPolygons = turf.voronoi(points, { bbox: [bounds[0], bounds[1], bounds[2], bounds[3]] });

    //遍历泰森多边形
    voronoiPolygons.features.forEach(element => {
        const intersectPoints = intersect(mainPoly, element.geometry);
        if (intersectPoints.length > 0) {
            //计算每个多边形的面积和高度
            const cubeInfo = computeCubeInfo(intersectPoints);
            //低于基准面,填方
            if (baseHeight > cubeInfo.avgHeight) {
                result.fillVolume += (baseHeight - cubeInfo.avgHeight) * result.baseArea;
            } else { //高于基准面,挖方
                result.cutVolume += (cubeInfo.avgHeight - baseHeight) * result.baseArea;
            }
            result.maxHeight = Math.max(result.maxHeight, cubeInfo.maxHeight);
            result.minHeight = Math.min(result.minHeight, cubeInfo.minHeight);
            result.baseArea += cubeInfo.baseArea;
        }
    });
    return result;
}

const computeCubeInfo = (positions) => {
    let worldPositions = [];
    let minHeight = Number.MAX_VALUE;
    let maxHeight = Number.MIN_VALUE;
    let sumHeight = 0.0;
    positions.forEach(element => {

        const worldPostion = pickCartesian(viewer, element);
        if (worldPostion.cartesian) {
            const cartographic = Cesium.Cartographic.fromCartesian(worldPostion.cartesian);
            worldPositions.push(cartographic);
            minHeight = Math.min(minHeight, cartographic.height);
            maxHeight = Math.max(maxHeight, cartographic.height);
            sumHeight += cartographic.height;
        }
    });
    const avgHeight = sumHeight / positions.length;
    const result = {
        minHeight: Number.MAX_VALUE,
        maxHeight: Number.MIN_VALUE,
        avgHeight: 0.0,
        baseArea: 0.0
    };
    result.minHeight = minHeight;
    result.maxHeight = maxHeight;
    result.avgHeight = avgHeight;
    result.baseArea = getAreaFromCartograohics(worldPositions);
    return result;
}

export function getBounds(points) {
    let bounds = [];
    let left = Number.MAX_VALUE;
    let right = Number.MIN_VALUE;
    let top = Number.MAX_VALUE;
    let bottom = Number.MIN_VALUE;
    points.forEach(element => {
        left = Math.min(left, element.x);
        right = Math.max(right, element.x);
        top = Math.min(top, element.y);
        bottom = Math.max(bottom, element.y);
    });
    bounds.push(left);
    bounds.push(top);
    bounds.push(right);
    bounds.push(bottom);
    return bounds;
}


export function Cartesian2turfPolygon(positions) {
    var coordinates = [[]];
    positions.forEach(element => {
        coordinates[0].push([element.x, element.y]);
    });
    coordinates[0].push([positions[0].x, positions[0].y]);
    const polygon = turf.polygon(coordinates);
    return polygon.geometry;
}

export function turfPloygon2CartesianArr(geometry) {
    const positionArr = [];
    geometry.coordinates.forEach((pointArr) => {
        pointArr.forEach(point => {
            positionArr.push(new Cesium.Cartesian2(point[0], point[1]));
        });
    });
    positionArr.push(new Cesium.Cartesian2(geometry.coordinates[0][0][0], geometry.coordinates[0][0][1]));
    return positionArr;
}

export function intersect(poly1, poly2) {
    var intersection = turf.intersect(poly1, poly2);
    if (intersection && intersection.geometry !== undefined) {
        return turfPloygon2CartesianArr(intersection.geometry);
    } else {
        return [];
    }
}

export function getAreaFromCartograohics(positions) {
    const x = [0];
    const y = [0];
    const geodesic = new Cesium.EllipsoidGeodesic();
    const radiansPerDegree = Math.PI / 180.0; //角度转化为弧度(rad)
    //数组x,y分别按顺序存储各点的横、纵坐标值
    for (let i = 0; i < positions.length - 1; i++) {
        const p1 = positions[i];
        const p2 = positions[i + 1];
        geodesic.setEndPoints(p1, p2);
        //   const s = Math.sqrt(Math.pow(geodesic.surfaceDistance, 2) +
        //     Math.pow(p2.height - p1.height, 2));
        const s = Math.sqrt(Math.pow(geodesic.surfaceDistance, 2));
        // console.log(s, p2.y - p1.y, p2.x - p1.x)
        const lat1 = p2.latitude * radiansPerDegree;
        const lon1 = p2.longitude * radiansPerDegree;
        const lat2 = p1.latitude * radiansPerDegree;
        const lon2 = p1.longitude * radiansPerDegree;
        let angle = -Math.atan2(
            Math.sin(lon1 - lon2) * Math.cos(lat2),
            Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon1 - lon2)
        );
        if (angle < 0) {
            angle += Math.PI * 2.0;
        }
        // console.log('角度:' + (angle * 180) / Math.PI);

        y.push(Math.sin(angle) * s + y[i]);
        x.push(Math.cos(angle) * s + x[i]);
    }

    let sum = 0;
    for (let i = 0; i < x.length - 1; i++) {
        sum += x[i] * y[i + 1] - x[i + 1] * y[i];
    }
    // console.log(x, y)

    return Math.abs(sum + x[x.length - 1] * y[0] - x[0] * y[y.length - 1]) / 2;
}


export const CutAndFillAnalysis = (_viewer,h) => {
    viewer=_viewer
    baseHeight = h
    clearDrawEntities();
    let position = [];
    let tempPoints = [];
    let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);

    //左键点击操作
    handler.setInputAction(function (click) {
        // 从相机位置通过windowPosition 世界坐标中的像素创建一条射线。返回Cartesian3射线的位置和方向。
        let ray = viewer.camera.getPickRay(click.position);
        // 查找射线与渲染的地球表面之间的交点。射线必须以世界坐标给出。返回Cartesian3对象
        position = viewer.scene.globe.pick(ray, viewer.scene);

        tempPoints.push(position);
        let tempLength = tempPoints.length;
        //调用绘制点的接口
        let point = drawPoint(position);
        tempEntities.push(point);
        if (tempLength > 1) {
            let pointline = drawPolyline([tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1]]);
            tempEntities.push(pointline);
        } else {
            // tooltip.innerHTML = "请绘制下一个点,右键结束";
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    //右键点击操作
    handler.setInputAction(function (click) {
        let cartesian = viewer.camera.pickEllipsoid(click.position, viewer.scene.globe.ellipsoid);

        if (cartesian) {
            let tempLength = tempPoints.length;
            if (tempLength < 3) {
                alert('请选择3个以上的点再执行闭合操作命令');
            } else {
                clearDrawEntities();
                tempPoints.push(tempPoints[0])
                let m = drawPolygon(tempPoints);
                tempEntities.push(m);

                let r = computeCutAndFillVolumeVoronoi(tempPoints)
                var polyPositions = m.polygon.hierarchy.getValue(Cesium.JulianDate.now()).positions;
                var polyCenter = Cesium.BoundingSphere.fromPoints(polyPositions).center;//中心点
                polyCenter = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(polyCenter);
                m.position = polyCenter;

                m.label = {
                   
                    text: '最大高度:'+Number(r.maxHeight).toFixed(2)+'m\n最小高度:'+Number(r.minHeight).toFixed(2)+'m\n整平高度:'+Number(baseHeight).toFixed(2)+'m\n平面面积:'+Number(r.baseArea).toFixed(2)+'㎡\n填方:'+Number(r.fillVolume).toFixed(2)+'m³\n挖方:'+Number(r.cutVolume).toFixed(2)+"m³",
                    color: Cesium.Color.fromCssColorString('#fff'),
                    font: 'normal 32px MicroSoft YaHei',
                    showBackground: true,
                    scale: 0.5,
                    horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    disableDepthTestDistance: 10000.0
                };
                tempEntities=[]
                handler.destroy();//关闭事件句柄
                handler = null;

            }
        }
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}
const drawPoint = (position) => {
    return viewer.entities.add({
        name: "点几何对象",
        position: position,
        point: {
            color: Cesium.Color.RED,
            pixelSize: 5,
            outlineColor: Cesium.Color.WHITE,
            outlineWidth: 1,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
        }
    });
}
const drawPolyline = (positions) => {

    if (positions.length < 1) return;
    return viewer.entities.add({
        name: "线几何对象",
        polyline: {
            positions: positions,
            width: 2.0,
            material: new Cesium.PolylineGlowMaterialProperty({
                color: Cesium.Color.RED,
            }),
            depthFailMaterial: new Cesium.PolylineGlowMaterialProperty({
                color: Cesium.Color.RED,
            }),
            clampToGround: true,
        }
    });
}
const drawPolygon = (positions) => {

    if (positions.length < 2) return;
    return viewer.entities.add({
        name: "面几何对象",
        polygon: {
            hierarchy: positions,
            fill: true, // 面是否有填充
            material: Cesium.Color.RED.withAlpha(.5), // 面的填充颜色
            outline: true, // 面是否有边框
            outlinColor: Cesium.Color.WHITE, // 边框的颜色
            outlineWidth: 1,
            clampToGround: true,
            // material: new Cesium.Color.fromCssColorString("#FFD700").withAlpha(.2),
        },
    });
}
const clearDrawEntities = () => {
    tempEntities = [];
    // 清除之前的实体
    const entitys = viewer.entities._entities._array;
    let length = entitys.length
    // 倒叙遍历防止实体减少之后entitys[f]不存在
    for (let f = length - 1; f >= 0; f--) {
        if (entitys[f]._name && (entitys[f]._name === '点几何对象' || entitys[f]._name === '线几何对象' || entitys[f]._name === '面几何对象')) {
            viewer.entities.remove(entitys[f]);
        }
    }
}

const pickCartesian = (viewer, windowPosition) => {
    //根据窗口坐标,从场景的深度缓冲区中拾取相应的位置,返回笛卡尔坐标。
    const cartesianModel = viewer.scene.pickPosition(windowPosition);

    //场景相机向指定的鼠标位置(屏幕坐标)发射射线
    const ray = viewer.camera.getPickRay(windowPosition);
    //获取射线与三维球相交的点(即该鼠标位置对应的三维球坐标点,因为模型不属于球面的物体,所以无法捕捉模型表面)
    const cartesianTerrain = viewer.scene.globe.pick(ray, viewer.scene);

    const result = {};
    if (typeof (cartesianModel) !== 'undefined' && typeof (cartesianTerrain) !== 'undefined') {
        result.cartesian = cartesianModel || cartesianTerrain;
        result.CartesianModel = cartesianModel;
        result.cartesianTerrain = cartesianTerrain;
        result.windowCoordinates = windowPosition.clone();
        //坐标不一致,证明是模型,采用绝对高度。否则是地形,用贴地模式。
        result.altitudeMode = cartesianModel.z.toFixed(0) !== cartesianTerrain.z.toFixed(0) ? Cesium.HeightReference.NONE : Cesium.HeightReference.CLAMP_TO_GROUND;
    }
    return result;
}
export const clearAll = () => {
    clearDrawEntities();
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

紫雪giser

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

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

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

打赏作者

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

抵扣说明:

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

余额充值