(九)Cesium实现坐标、距离和面积测量

坐标测量

import * as Cesium from 'cesium'
import { useCesiumStore } from '@/stores/useCesiumStore.js'
import { toRaw } from 'vue'

export const MeasurePoint = () =>{
    const viewer = toRaw(useCesiumStore().viewer) //获取viewer对象
    const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas) //创建屏幕空间事件处理器
    const annotations = viewer.scene.primitives.add(new Cesium.LabelCollection()) //创建标注集合
    viewer.scene.canvas.style.cursor = 'crosshair'
    //添加点
    const createPoint = (worldPosition) => {
        const point = viewer.entities.add({
            position: worldPosition,
            name: 'measure_point',
            point: {
                pixelSize: 10,
                color:Cesium.Color.CRIMSON,
                outlineColor: Cesium.Color.ALICEBLUE,
                outlineWidth: 2,
                disableDepthTestDistance: 4000
            }
        })
        return point
    }

    //添加点和标注框
    const annotate = (cartesian,lng,lat,height) => {
        //创建点
        createPoint(cartesian)
        //创建标注框
        annotations.add({
            position: cartesian,
            text:
                'Lon:' + lng.toFixed(5) + '\u00B0' +
                '\nLat:' + lat.toFixed(5) + '\u00B0' +
                '\nHeight:' + height.toFixed(2) +'m',
            showBackground: true,
            font: '22px monospace',
            horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            disableDepthTestDistance:Number.POSITIVE_INFINITY
        })
    }

    //鼠标左键单击事件
    handler.setInputAction((event) => {
        const pickedObject = viewer.scene.pick(event.position)  //判断是否拾取到模型
        //如果拾取到模型
        if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)){
            const cartesian = viewer.scene.pickPosition(event.position)  //获取点击位置的笛卡尔坐标
            if(Cesium.defined(cartesian)){
                const cartographic = Cesium.Cartographic.fromCartesian(cartesian)  //将笛卡尔坐标转为经纬度高度
                const lng = Cesium.Math.toDegrees(cartographic.longitude)  //获取经度
                const lat = Cesium.Math.toDegrees(cartographic.latitude)  //获取纬度
                const height = cartographic.height  //获取高度
                annotate(cartesian,lng,lat,height)  //添加点和标注框
            }
        }
        //如果未拾取到模型而拾取到地形
        else{
            const ray = viewer.camera.getPickRay(event.position)  //获取射线
            const cartesian = viewer.scene.globe.pick(ray, viewer.scene)  //获取地形的笛卡尔坐标
            if(Cesium.defined(cartesian)){
                const cartographic = Cesium.Cartographic.fromCartesian(cartesian)  //将笛卡尔坐标转为经纬度高度
                const lng = Cesium.Math.toDegrees(cartographic.longitude)  //获取经度
                const lat = Cesium.Math.toDegrees(cartographic.latitude)  //获取纬度
                const height = cartographic.height  //获取高度
                annotate(cartesian,lng,lat,height)  //添加点和标注框
            }
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

    //使用鼠标右键单击,删除点和标注框
    handler.setInputAction(() => {
        // viewer.entities.removeAll()
        for (let i = 0; i < viewer.entities.values.length; i++) {
            if (viewer.entities.values[i].name === 'measure_point' ) {
                viewer.entities.remove(viewer.entities.values[i])
                i-- //删除后索引减一,数组塌陷
            }
        }
        annotations.removeAll()
        viewer.scene.canvas.style.cursor = 'default'
        handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)

}

空间距离测量,不是贴地距离。

import * as Cesium from 'cesium'
import { useCesiumStore } from '@/stores/useCesiumStore.js'
import { toRaw } from 'vue'

export const MeasureDistance = () => {
    const viewer = toRaw(useCesiumStore().viewer) //获取viewer对象
    const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas) //创建屏幕空间事件处理器
    viewer.scene.canvas.style.cursor = 'crosshair'

    //计算空间距离函数
    const getSpaceDistance = (position) => {
        let distance = 0
        for (let i = 0; i < position.length - 1; i++){
            distance += Cesium.Cartesian3.distance(position[i], position[i+1]) //计算两点之间的距离
        }
        return distance.toFixed(2) //保留两位小数
    }

    let positions = [] //记录鼠标点击的位置
    let activeShapePoints = [] //记录鼠标点击的位置
    let activeShape = null //动态图形
    let floatingPoint = null //第一个点,判断是否开始获取鼠标移动位置并添加至activeShapePoints

    //绘制点与标签
    const drawPoint = (position,textDitance) => {
        const pointGeometry = viewer.entities.add({
            name:'distancePoint',
            position: position,
            point: {
                color:Cesium.Color.SKYBLUE,
                pixelSize:6,
                outlineColor:Cesium.Color.RED,
                outlineWidth:2,
                disableDepthTestDistance:4000
            },
            label: {
                text: textDitance + '米',
                font:'18px sans-serif',
                fillColor:Cesium.Color.GOLD,
                style : Cesium.LabelStyle.FILL_AND_OUTLINE,
                outlineWidth:2, //外边框宽度
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM, //垂直方向居下
                pixelOffset: new Cesium.Cartesian2(20, -20), //偏移量
                heightReference: Cesium.HeightReference.NONE, //高度参考
                disableDepthTestDistance: Number.POSITIVE_INFINITY
            }
        })
        return pointGeometry
    }

    //绘制图形
    const drawShape = (positionData) => {
        const shape =   viewer.entities.add({
            name:'distanceShape',
            polyline:{
                positions:positionData,
                width:5,
                material:new Cesium.PolylineOutlineMaterialProperty({
                    color:Cesium.Color.RED,
                }),
                //贴地
                // clampToGround: true,
                // disableDepthTestDistance: Number.POSITIVE_INFINITY,
            }
        })
        return shape
    }

    //鼠标左键单击事件
    handler.setInputAction((event) => {
        const earthPosition = viewer.scene.pickPosition(event.position) //获取点击位置的经纬度坐标
        //如果鼠标指针不在地球上,则earthPosition为undefined
        if(Cesium.defined(earthPosition)){
            //计算距离
            positions.push(earthPosition) //记录点击位置
            let disance = getSpaceDistance(positions) //计算距离
             if (activeShapePoints.length === 0) {
                activeShapePoints.push(earthPosition) //记录第一个点
                const dynamicPosition = new Cesium.CallbackProperty(() => {
                    return activeShapePoints
                }, false) //动态位置
                activeShape = drawShape(dynamicPosition) //绘制动态图形
             }
             //添加当前点到activeShapePoints,实时渲染动态图
             activeShapePoints.push(earthPosition)
             floatingPoint = drawPoint(earthPosition,disance) //绘制点与标签
        }else{
            window.alert('请点击地球上的位置')
            return
        }
    },Cesium.ScreenSpaceEventType.LEFT_CLICK)

    //鼠标移动事件
    handler.setInputAction((event) => {
        if(Cesium.defined(floatingPoint)){
            let newPosition = viewer.scene.pickPosition(event.endPosition) //获取鼠标移动位置的经纬度坐标
            if(Cesium.defined(newPosition)){
                activeShapePoints.pop() //删除最后一个点
                activeShapePoints.push(newPosition) //添加新的点
           }
        }
    },Cesium.ScreenSpaceEventType.MOUSE_MOVE)

    //鼠标右键单击事件
    handler.setInputAction(() => {
        activeShapePoints.pop() //删除最后一个点
        if(activeShapePoints.length) {
            drawShape(activeShapePoints) //绘制最终图形
        }
        viewer.entities.remove(activeShape) //去除动态图形
        floatingPoint = undefined
        activeShape = undefined
        activeShapePoints = []
        positions = []
        viewer.scene.canvas.style.cursor = 'default'
        handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
    },Cesium.ScreenSpaceEventType.RIGHT_CLICK)
}

面积测量

import * as Cesium from 'cesium'
import { useCesiumStore } from '@/stores/useCesiumStore.js'
import { toRaw } from 'vue'

export const MeasureArea = () => {
    const viewer = toRaw(useCesiumStore().viewer) //获取viewer对象
    const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas) //创建屏幕空间事件处理器
    viewer.scene.canvas.style.cursor = 'crosshair'

    let p = []  //存储要计算面积的点
    let pCartographic = [] //存储计算距离与添加label的点
    let activeShapePoints = [] //存储当前正在绘制的多边形顶点坐标
    let activeShape = null //当前正在绘制的多边形
    let floatingPoint = null //作为第一个点,用于判断是否开始获取鼠标移动结束位置

    //定义变量,进行角度和弧度之间的转换;弧度=pi/180*度数,角度=弧度*180/pi
    const radiansPerDegree = Math.PI / 180.0 //角度转弧度
    const degreesPerRadian = 180.0 / Math.PI //弧度转角度

    //计算两点朝向,从A点到B点相对于平面X轴的平面角度的函数getBearing
    //from:起点
    //to:终点
    const  getBearing = (from, to) => {
        from = Cesium.Cartographic.fromCartesian(from) //将Cartesian3坐标转换为Cartographic坐标
        to = Cesium.Cartographic.fromCartesian(to)
        const lat1 = from.latitude * radiansPerDegree //起点纬度弧度
        const lon1 = from.longitude * radiansPerDegree //起点经度弧度
        const lat2 = to.latitude * radiansPerDegree //终点纬度弧度
        const lon2 = to.longitude * radiansPerDegree //终点经度弧度
        //返回从原点(0,0)到(x,y)点的线段与x轴正方向夹角的弧度值,转为角度
        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 += 2 * Math.PI
        }
        return angle * degreesPerRadian //返回角度值
    }

    //计算3个点直接连线的角度的函数getAngle
    /* p1,p2,p3:三点坐标 ,分别计算相邻两点的朝向,并根据两个线段朝向计算,得到3个点连城的夹角*/
    const getAngle = (p1, p2, p3) => {
        const bearing1 = getBearing(p2, p1) //相邻两点的朝向
        const bearing2 = getBearing(p2, p3)
        let angle = bearing2 - bearing1 //两个线段的夹角
        if (angle < 0) {
            angle += 360
        }
        return angle
    }
    
    //封装计算相邻两点距离函数getDistance,根据相邻两点之间经纬度、高度计算两点之间的空间距离
    const getDistance = (p1, p2) => {
        const geodesic = new Cesium.EllipsoidGeodesic() //创建椭球体几何对象
        geodesic.setEndPoints(p1, p2) //设置起点和终点
        //获取起点和终点之间的表面距离
        let s = geodesic.surfaceDistance
        //获取起点和终点之间的表面距离的平方
        s = Math.sqrt(Math.pow(s, 2) + Math.pow(p1.height - p2.height, 2))
        return s //返回距离值
    }

    //计算多边形面积的函数getArea
    /* points:多边形顶点坐标数组 
    将多边形拆分成三角曲面,通过计算每3个点组成的线段的夹角及两条边的长度分别计算三角形的面积,然后将所有三角形面积相加*/
    const getArea = (points) => {
        let res = 0
        for (let i = 0; i < points.length - 2; i++) {
            const j = (i+1) % points.length //相邻点的第二个点
            const k = (i+2) % points.length //相邻点的第三个点
            const totalAngle = getAngle(points[i], points[j], points[k]).toFixed() //计算相邻两点的夹角
            const distance1 = getDistance(pCartographic[i], pCartographic[j]) //计算两点之间的距离
            const distance2 = getDistance(pCartographic[j], pCartographic[k])
            // console.log(j,k,distance1,distance2,totalAngle);
            //计算三角形面积
            res += distance1 * distance2 * Math.abs(Math.round(Math.sin(totalAngle * radiansPerDegree ) * 1000000) / 1000000)
            // console.log(res)
        }
        return res.toFixed(2) //返回面积值,单位是平方米
    }

    //封装addLabel,用于完成面积计算后显示结果,绘制到最后一个点上
    const addLabel = (pCartographic, text) => {
        const position = Cesium.Cartesian3.fromRadians(
            pCartographic[pCartographic.length - 1].longitude,
            pCartographic[pCartographic.length - 1].latitude,
            pCartographic[pCartographic.length - 1].height
        )
        const label = viewer.entities.add({
            name: 'areaLabel',
            position: position,
            label: {
                text: text + '平方米',
                font: '18px sans-serif',
                fillColor: Cesium.Color.GOLD,
                style:Cesium.LabelStyle.FILL_AND_OUTLINE,
                outlineWidth: 2,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM, //垂直方向上对齐
                pixelOffset: new Cesium.Cartesian2(20, -20), //偏移量
                heightReference: Cesium.HeightReference.NONE, //高度参考
                disableDepthTestDistance: Number.POSITIVE_INFINITY
            }
        })
        return label
    }

    //绘制点
    const drawPoint = (position) => {
        const pointGeometry = viewer.entities.add({
            name: 'areaPoint',
            position: position,
            point: {
                pixelSize: 6,
                Color: Cesium.Color.SKYBLUE,
                outlineColor: Cesium.Color.YELLOW,
                outlineWidth: 2,
                disableDepthTestDistance: 4000 //距离4000以下不被遮挡
            }
        })
        return pointGeometry
    }

    //绘制多边形
    const drawShape = (positionData) => {
        const shape = viewer.entities.add({
            name: 'areaShape',
            polygon: {
                hierarchy: positionData,
                material: new Cesium.ColorMaterialProperty(Cesium.Color.BLUE.withAlpha(0.5))
            }
        })
        return shape
    }

    //鼠标左键单击事件
    handler.setInputAction((event) => {
        const earthPosition = viewer.scene.pickPosition(event.position) //获取点击位置的地理坐标
        if (Cesium.defined(earthPosition)) {
            pCartographic.push(Cesium.Cartographic.fromCartesian(earthPosition)) //将点击位置的地理坐标添加到pCartographic数组
            p.push(earthPosition) //将点击位置的地理坐标添加到p数组
            //第一次单击时
            if (activeShapePoints.length === 0) {
                floatingPoint = drawPoint(earthPosition) //绘制第一个点
                activeShapePoints.push(earthPosition) //将第一个点添加到activeShapePoints数组
                const dynamicPositions = new Cesium.CallbackProperty(() => {
                    return new Cesium.PolygonHierarchy(activeShapePoints)
                }, false)
                activeShape = drawShape(dynamicPositions) //绘制第一个多边形
            }
            //添加当前点到activeShapePoints数组,实时渲染动态图
            activeShapePoints.push(earthPosition)
            drawPoint(earthPosition)
        }
        else {
            alert('请点击地球上的位置')
            return
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

    //鼠标移动事件
    handler.setInputAction((event) => {
        if(Cesium.defined(floatingPoint)) {
            const newPosition = viewer.scene.pickPosition(event.endPosition) //获取鼠标移动结束位置的地理坐标
            if(Cesium.defined(newPosition)){
                activeShapePoints.pop() //删除最后一个点
                activeShapePoints.push(newPosition) //添加新的点
            }
        }        
    },Cesium.ScreenSpaceEventType.MOUSE_MOVE)

    //鼠标右键单击事件
    handler.setInputAction(() => {
        activeShapePoints.pop() //删除最后一个点
        if (activeShapePoints.length ) {
            drawShape(activeShapePoints)
        }
        const text = getArea(p)
        addLabel(pCartographic, text) //计算面积并显示结果
        viewer.entities.remove(activeShape) //删除多边形
        viewer.entities.remove(floatingPoint) //删除第一个点
        floatingPoint = undefined //清空变量
        activeShape = undefined //清空变量
        activeShapePoints = [] //清空数组
        p = [] //清空数组
        pCartographic = [] //清空数组
        viewer.scene.canvas.style.cursor = 'default'
        // handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
        handler.destroy() //销毁事件处理器
    },Cesium.ScreenSpaceEventType.RIGHT_CLICK)
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值