cesium中测距测面

参考:cesium中测距_疆~的博客-CSDN博客_cesium 测距MeasureMangner.js 测试:https://blog.csdn.net/qq_40323256/article/details/128171194
cesium实现面积测量_疆~的博客-CSDN博客_cesuim 测量 空间面积MeasureManager.js 测试:https://blog.csdn.net/qq_40323256/article/details/128178560

空间距离

 表面距离

空间面积

表面面积

 实现代码:

<template>
  <div id="cesiumContainer"></div>
  <div style="position: absolute; top: 10px; left: 10px; z-index: 99">
    <el-button @click="state.measureTool.distance(0)">空间距离</el-button>
    <el-button @click="state.measureTool.distance(1)">表面距离</el-button>
    <el-button @click="state.measureTool.area(0)">空间面积</el-button>
    <el-button @click="state.measureTool.area(1)">表面面积</el-button>
    <el-button @click="state.measureTool.clearAll()">清除</el-button>
  </div>
</template>
  
  <script setup>
import * as Cesium from "cesium";
import { onMounted, reactive } from "vue";

import MeasureTool from "./MeasureTool";

const state = reactive({
  measureTool: null,
});
onMounted(() => {
  const viewer = new Cesium.Viewer("cesiumContainer", {
    infoBox: false, // 禁用沙箱,解决控制台报错
    selectionIndicator: false, //选择指示器
    terrainProvider: Cesium.createWorldTerrain(),
  });
  viewer._cesiumWidget._creditContainer.style.display = "none"; //隐藏logo版权

  viewer.camera.lookAt(
    Cesium.Cartesian3.fromDegrees(-122.2058, 46.1955, 1000.0),
    new Cesium.Cartesian3(5000.0, 5000.0, 5000.0)
  );
  viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);

  state.measureTool = new MeasureTool(viewer);
});
</script>
  
  <style scoped>
#cesiumContainer {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0px;
  left: 0px;
  margin: 0;
  padding: 0;
  overflow: hidden;
}
</style>

封装MeasureTool.js

/**
 * @Description: 测量工具类
 * @author MrKuang
 * @VX k792794653
 * @date 2022/8/9 0009
 */
import * as Cesium from "cesium";
import { helpers, area, random, bbox, tin } from '@turf/turf'
import booleanPointInPolygon from "@turf/boolean-point-in-polygon"

export default class MeasureTool {
    /**
     *
     * @param viewer
     */
    constructor(viewer) {
        this.viewer = viewer;
        this._distance_handler = null;
        //测线的变量
        this._lineParams = {
            lineDataSource: null,
            vertexCollection: [],//存储的折线顶点信息
            mousePos: null,
            lastLine: null,
            totalLength: 0,
        };
        this._area_handler = null;
        //测面的变量
        this._areaParams = {//测面的变量
            areaDataSource: null,
            vertexCollection: [],
            lastArea: null,
            mousePos: null,
        };
    }

    /**
     * 测距
     * @param type 0-空间 1 表面
     */
    distance(type) {
        //默认测空间
        type = type === undefined ? 0 : type;
        this._reset();
        this._lineParams.lineDataSource = new Cesium.CustomDataSource('lineDataSource');
        this.viewer.dataSources.add(this._lineParams.lineDataSource);
        this.viewer.scene.globe.depthTestAgainstTerrain = true;//开启深度测试
        this._distance_handler = new Cesium.ScreenSpaceEventHandler(this.viewer.canvas);
        //绑定鼠标左击事件
        //尝试过把下面的方法拿出来,但是总是找不到this对象。。。有缘人试试吧
        this._distance_handler.setInputAction((e) => {
            let pos = this.viewer.scene.pickPosition(e.position);//笛卡尔坐标
            if (pos) {
                this._lineParams.vertexCollection.push(pos);
            }
            //添加线
            let currentLine = this._lineParams.lineDataSource.entities.add({
                polyline: {
                    positions: new Cesium.CallbackProperty(() => {
                        let c = Array.from(this._lineParams.vertexCollection);
                        if (this._lineParams.mousePos) {
                            c.push(this._lineParams.mousePos);
                        }
                        return c
                    }, false),
                    clampToGround: true,//折线固定在地面
                    width: 3,
                    material: new Cesium.PolylineOutlineMaterialProperty({
                        color: new Cesium.Color(255 / 255, 118 / 255, 0 / 255, 1.0),
                        outlineColor: new Cesium.Color(255 / 255, 180 / 255, 115 / 255, 1.0),
                        outlineWidth: 2
                    })
                }
            })
            if (type == 0) {
                if (this._lineParams.vertexCollection.length >= 2) {
                    this._lineParams.totalLength += this._surfaceDistance(this._lineParams.vertexCollection[this._lineParams.vertexCollection.length - 2], pos);
                    this._lineParams.totalLength = parseFloat((this._lineParams.totalLength).toFixed(2));
                }
                this._addDistancePoint(pos, this._lineParams.totalLength)
            } else {
                if (this._lineParams.vertexCollection.length == 1) {
                    this._addDistancePoint(pos, this._lineParams.totalLength);
                } else {
                    let left = this._lineParams.vertexCollection[this._lineParams.vertexCollection.length - 2];
                    let right = pos;
                    //求两个顶点的直线距离,用于判断插值精度
                    let t = Cesium.Cartesian3.distance(left, right);
                    let vertexLength = this._terrianDistance(this._lineParams.vertexCollection[this._lineParams.vertexCollection.length - 2], pos, t);
                    this._lineParams.totalLength += vertexLength;
                    this._lineParams.totalLength = parseFloat((this._lineParams.totalLength).toFixed(2));
                    this._addDistancePoint(pos, this._lineParams.totalLength);
                }
            }
            if (this._lineParams.lastLine) {
                this._lineParams.lineDataSource.entities.remove(this._lineParams.lastLine)
            }
            this._lineParams.lastLine = currentLine;
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

        //鼠标移动事件
        this._distance_handler.setInputAction((e) => {
            let endPosition = e.endPosition;
            let endPos = this.viewer.scene.pickPosition(endPosition);
            if (endPos) {//鼠标移出地球,undifined
                this._lineParams.mousePos = endPos;
                //entity polyline 中使用了Cesium CallbackProperty 免去移动再画线
            }
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

        //鼠标右键事件,右键取消不在添加点计算面积
        this._distance_handler.setInputAction((e) => {
            let endPostion = e.position;
            let endPos = this.viewer.scene.pickPosition(endPostion);
            if (endPos) {
                this.cancel();
                // vertexCollection.push(endPos);
                // if(type==0){
                //     totalLength+=this._surfaceDistance(vertexCollection[vertexCollection.length-2],endPos);
                //     this._addDistancePoint(endPos,totalLength);
                // }else{
                //     let left = vertexCollection[vertexCollection.length-2];
                //     let right = endPos;
                //     //求两个顶点的直线距离,用于判断插值精度
                //     let t = Cesium.Cartesian3.distance(left,right);
                //     let vertexLength = this._terrianDistance(vertexCollection[vertexCollection.length-2],endPos,t);
                //     totalLength+=vertexLength;
                //     this._addDistancePoint(endPos,totalLength);
                // }
                this._lineParams.lineDataSource.entities.add({
                    polyline: {
                        clampToGround: true,
                        positions: this._lineParams.vertexCollection,
                        width: 3,
                        material: new Cesium.PolylineOutlineMaterialProperty({
                            color: new Cesium.Color(255 / 255, 118 / 255, 0 / 255, 1.0),
                            outlineColor: new Cesium.Color(255 / 255, 180 / 255, 115 / 255, 1.0),
                            outlineWidth: 2
                        })
                    }
                })
                if (this._lineParams.lastLine) {
                    this._lineParams.lineDataSource.entities.remove(this._lineParams.lastLine);
                }
                this._lineParams.vertexCollection = [];
                this._lineParams.mousePos = null;
            }
        }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
    }

    /**
     * 空间距离计算
     * @param start
     * @param end
     * @returns {number}
     * @private
     */
    _surfaceDistance(start, end) {//测地线(椭球上距离)
        let geodesic = new Cesium.EllipsoidGeodesic();
        let startGeodesic = Cesium.Cartographic.fromCartesian(start)//笛卡尔系转经纬度
        let endGeodesic = Cesium.Cartographic.fromCartesian(end)
        geodesic.setEndPoints(startGeodesic, endGeodesic)
        let lengthInMeters = (geodesic.surfaceDistance) / 1000;
        return parseFloat(lengthInMeters)
    }

    /**
     * 表面距离
     * @param start
     * @param end
     * @param t
     * @private
     */
    _terrianDistance(start, end, t) {
        let level = Math.ceil(Math.log10(t));
        let count = level * 5;
        let lerp = [];
        let vertexLength = 0;
        //两个端点加上count个插值点
        for (let i = 0; i <= count + 1; i++) {
            let pt = Cesium.Cartesian3.lerp(start, end, i / (count + 1), new Cesium.Cartesian3())//插值点
            let samplePt = this.viewer.scene.clampToHeight(pt)//高度采样点
            lerp.push(samplePt)
        }
        //计算折线长度
        for (let j = 1; j < lerp.length; j++) {
            let left = lerp[j - 1]
            let right = lerp[j]
            let length = Cesium.Cartesian3.distance(left, right)
            vertexLength += length
        }
        vertexLength = vertexLength / 1000;
        return vertexLength;
    }

    /**
     * 添加顶点长度信息等
     * @param pos
     * @param totalLength
     * @private
     */
    _addDistancePoint(pos, totalLength) {
        this._lineParams.lineDataSource.entities.add({
            position: pos,
            point: {
                color: new Cesium.Color(93 / 255, 200 / 255, 205 / 255, 1.0),
                pixelSize: 10,
                heightReference: Cesium.CLAMP_TO_GROUND,
                disableDepthTestDistance: 15000000
            },
            label: {
                text: totalLength + "千米",
                fillColor: Cesium.Color.WHITE,
                outlineColor: Cesium.Color.BLACK,
                outlineWidth: 2,
                pixelOffset: new Cesium.Cartesian2(10, -10),
                font: 'normal 16px',
                disableDepthTestDistance: 15000000
            }
        })
    }

    /**
     * 测面
     * @param type 0-空间 1 表面
     */
    area(type) {
        this._reset();
        //默认测空间
        type = type === undefined ? 0 : type;
        this._areaParams.areaDataSource = new Cesium.CustomDataSource('areaDataSource');
        this.viewer.dataSources.add(this._areaParams.areaDataSource);
        this.viewer.scene.globe.depthTestAgainstTerrain = true;
        this._area_handler = new Cesium.ScreenSpaceEventHandler(this.viewer.canvas);
        //鼠标左键点击事件
        this._area_handler.setInputAction((e) => {
            let pos = this.viewer.scene.pickPosition(e.position)
            let current;
            if (pos) {
                this._areaParams.vertexCollection.push(pos)
            }
            if (this._areaParams.vertexCollection.length == 1) {//一个顶点加上移动点
                current = this._areaParams.areaDataSource.entities.add({
                    polyline: {
                        clampToGround: true,
                        positions: new Cesium.CallbackProperty(() => {
                            let c = Array.from(this._areaParams.vertexCollection)
                            if (this._areaParams.mousePos) {
                                c.push(this._areaParams.mousePos)
                            }
                            return c
                        }, false),
                        width: 3,
                        material: new Cesium.PolylineOutlineMaterialProperty({
                            color: new Cesium.Color(255 / 255, 118 / 255, 0 / 255, 1.0),
                            outlineColor: new Cesium.Color(255 / 255, 180 / 255, 115 / 255, 1.0),
                            outlineWidth: 2
                        })
                    }
                })
            } else {//2个顶点加上移动点
                current = this._areaParams.areaDataSource.entities.add({
                    polygon: {
                        hierarchy: new Cesium.CallbackProperty(() => {
                            let positions = Array.from(this._areaParams.vertexCollection)
                            positions.push(this._areaParams.mousePos)
                            return new Cesium.PolygonHierarchy(positions)
                        }, false),
                        material: new Cesium.Color(255 / 255, 208 / 255, 115 / 255, 0.5),
                        extrudedHeightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                        outline: true,
                        outlineColor: new Cesium.Color(255 / 255, 180 / 255, 115 / 255, 1.0),
                        outlineWidth: 1
                    },
                    polyline: {
                        clampToGround: true,
                        positions: new Cesium.CallbackProperty(() => {
                            let c = Array.from(this._areaParams.vertexCollection)
                            if (this._areaParams.mousePos) {
                                c.push(this._areaParams.mousePos)
                            }
                            c.push(this._areaParams.vertexCollection[0])
                            return c
                        }, false),
                        width: 3,
                        material: new Cesium.PolylineOutlineMaterialProperty({
                            color: new Cesium.Color(255 / 255, 118 / 255, 0 / 255, 1.0),
                            outlineColor: new Cesium.Color(255 / 255, 180 / 255, 115 / 255, 1.0),
                            outlineWidth: 2
                        })
                    }
                })
            }
            if (this._areaParams.lastArea) {
                this._areaParams.areaDataSource.entities.remove(this._areaParams.lastArea)
            }
            this._areaParams.lastArea = current
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
        //鼠标移动事件
        this._area_handler.setInputAction((e) => {
            let endPosition = e.endPosition
            let endPos = this.viewer.scene.pickPosition(endPosition)
            if (endPos) {//鼠标移出地球,undifined
                this._areaParams.mousePos = endPos
            }
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
        //鼠标右键事件
        this._area_handler.setInputAction((e) => {
            let endPosition = e.position;
            let endPos = this.viewer.scene.pickPosition(endPosition);
            if (endPos) {
                this.cancel();
                this._areaParams.vertexCollection.push(endPos);
                let area = this._surfaceArea(this._areaParams.vertexCollection);
                if (type == 0) {
                    this._addAreaPoint(endPos, area)
                } else {
                    this._terrianArea(this._areaParams.vertexCollection, area)
                }
            }
        }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
    }

    /**
     * 椭球体空间面积
     * @param vertexCollection
     * @private
     */
    _surfaceArea(vertexCollection) {
        let rings = [];
        vertexCollection.map((vertex) => {
            //笛卡尔转弧度
            let carto_pt = Cesium.Cartographic.fromCartesian(vertex);
            //弧度转经纬度
            rings.push([Cesium.Math.toDegrees(carto_pt.longitude), Cesium.Math.toDegrees(carto_pt.latitude)]);
        })
        rings.push(rings[0]);
        //转成feature Polygon
        let polygon_json = helpers.polygon([rings]);
        //计算面积
        let surface = area(polygon_json);
        surface = parseFloat((surface / 1000000).toFixed(3))
        return surface;
    }

    /**
     * 添加面积标注
     * @param pos
     * @param area
     * @private
     */
    _addAreaPoint(pos, area) {
        this._areaParams.areaDataSource.entities.add({
            //顶点,以及长度信息
            position: pos,
            point: {
                color: new Cesium.Color(93 / 255, 200 / 255, 205 / 255, 1.0),
                pixelSize: 10,
                // heightReference:Cesium.HeightReference.RELATIVE_TO_GROUND
                heightReference: Cesium.CLAMP_TO_GROUND,
                disableDepthTestDistance: 15000000
            },
            label: {
                text: area + "平方千米",
                fillColor: Cesium.Color.WHITE,
                outlineColor: Cesium.Color.BLACK,
                outlineWidth: 2,
                pixelOffset: new Cesium.Cartesian2(10, -10),
                font: 'normal 16px',
                disableDepthTestDistance: 15000000
            }
        })
    }

    /**
     * 表面面积计算
     * @param vertexCollection
     * @param surface_area
     * @private
     */
    _terrianArea(vertexCollection, surface_area) {
        let rings = [];
        let totalArea = 0;
        let level = Math.ceil(Math.log10(surface_area));
        let count = 0;
        if (level < 1) {
            count = 10;
        } else {
            count = 10 * level;
        }
        let random_pt = {
            features: []
        }
        vertexCollection.map((vertex) => {
            //转弧度
            let carto_pt = Cesium.Cartographic.fromCartesian(vertex);
            //转经纬度
            rings.push([Cesium.Math.toDegrees(carto_pt.longitude), Cesium.Math.toDegrees(carto_pt.latitude)]);
        })
        rings.push(rings[0]);
        let polygon_json = helpers.polygon([rings]);
        //获取多边形外边界内的随机点
        let bbox_random_pt = random.randomPoint(count, { bbox: bbox(polygon_json) });
        bbox_random_pt.features.map((pt) => {
            //获取多边形内的随机点
            let coordinates = pt.geometry.coordinates;
            let turf_pt = helpers.point([coordinates[0], coordinates[1]]);
            if (booleanPointInPolygon(turf_pt, polygon_json)) {
                random_pt.features.push(pt)
            }
        })
        let random_tin = tin(random_pt);//生成三角网
        //进行高度采样,计算三角形面积
        random_tin.features.map((tin) => {
            let [point_1, point_2, point_3] = [tin.geometry.coordinates[0][0], tin.geometry.coordinates[0][1], tin.geometry.coordinates[0][2]];
            let sample_pt1 = this._sampleHeightFromCoordinate(point_1);
            let sample_pt2 = this._sampleHeightFromCoordinate(point_2);
            let sample_pt3 = this._sampleHeightFromCoordinate(point_3);
            let distance_1 = Cesium.Cartesian3.distance(sample_pt1, sample_pt2);
            let distance_2 = Cesium.Cartesian3.distance(sample_pt2, sample_pt3);
            let distance_3 = Cesium.Cartesian3.distance(sample_pt1, sample_pt3);
            let p = (distance_1 + distance_2 + distance_3) / 2;
            let tin_area = Math.sqrt(p * (p - distance_1) * (p - distance_2) * (p - distance_3));
            this._areaParams.areaDataSource.entities.add({
                polyline: {
                    clampToGround: true,
                    positions: [sample_pt1, sample_pt2, sample_pt3],
                    width: 3,
                    material: new Cesium.PolylineOutlineMaterialProperty({
                        color: new Cesium.Color(255 / 255, 118 / 255, 0 / 255, 1.0),
                        outlineColor: new Cesium.Color(255 / 255, 180 / 255, 115 / 255, 1.0),
                        outlineWidth: 2
                    })
                }
            })
            totalArea += tin_area;
        })
        totalArea = parseFloat((totalArea / 1000000).toFixed(3))
        this._addAreaPoint(this.viewer.scene.clampToHeight(this._areaParams.vertexCollection[0]), totalArea);
    }

    /**
     * 三角形高度
     * @param coordinate
     * @returns {Cartesian3}
     * @private
     */
    _sampleHeightFromCoordinate(coordinate) {
        let c3_pt = Cesium.Cartesian3.fromDegrees(coordinate[0], coordinate[1]);
        let sample_pt = this.viewer.scene.clampToHeight(c3_pt);
        return sample_pt;
    }

    /**
     * 移除所有事件
     */
    cancel() {
        this._distance_handler && (
            this._distance_handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN),
            this._distance_handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE),
            this._distance_handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_DOWN),
            this._distance_handler.destroy(),
            this._distance_handler = null
        );
        this._area_handler && (
            this._area_handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN),
            this._area_handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE),
            this._area_handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_DOWN),
            this._area_handler.destroy(),
            this._area_handler = null
        );
    }

    /**
     * 重置
     * @private
     */
    _reset() {
        if (this._lineParams.lineDataSource != null) {
            this._lineParams.lineDataSource.entities.removeAll();
            this.viewer.dataSources.remove(this._lineParams.lineDataSource);
        }
        if (this._areaParams.areaDataSource != null) {
            this._areaParams.areaDataSource.entities.removeAll();
            this.viewer.dataSources.remove(this._areaParams.areaDataSource);
        }
        this._lineParams = {//测线的变量
            lineDataSource: null,
            vertexCollection: [],//存储的折线顶点信息
            mousePos: null,
            lastLine: null,
            totalLength: 0,
        }
        this._areaParams = {//测面的变量
            areaDataSource: null,
            vertexCollection: [],
            lastArea: null,
            mousePos: null,
        };
    }

    /**
     * 清除
     */
    clearAll() {
        this.cancel();
        this._reset()
    }
}


版权声明:本文为qq_40323256原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。