Cesium--测距

1,500 阅读3分钟

在地图开发中,测距是很常见的地图操作,网上也有很多测距的教程,但其实都是硬放代码,看到一大段的方法还是蛮懵的,不知从何下手,因此在此做个记录

先理清思路,测距主要分为鼠标左键点击事件鼠标移动事件创建线计算距离鼠标左键双击事件清除

准备工作

在创建点击事件之前,需要先关闭地图默认的左键双击事件,并创建变量记录点击位置、移动距离等,之所以需要记录点击位置和最终定位,是因为不需要最后一个点,最终定位 = 记录位置[记录位置.length - 1]

        // 取消双击事件
        viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
        var positions = [] // 记录位置
        var poly = null // 记录线
        var distance = 0 // 记录距离
        var cartesian = null // 获取鼠标与地图的交点
        var labelPt // 最终位置

鼠标左键点击事件

首次点击时,为测距的起点,可创建一个起点作为标记,触发鼠标移动事件绘制线后,再次点击可绘制一条线的终点,并根据测量方法返回该线的长度

        // 鼠标左键点击事件
        handler.setInputAction(function (ele) {
            // 创建射线并获取交点
            let ray = viewer.camera.getPickRay(ele.position)
            cartesian = viewer.scene.globe.pick(ray, viewer.scene)
            if (!Cesium.defined(cartesian)) return
            if (positions.length === 0) {
                positions.push(cartesian.clone())
            }
            positions.push(cartesian)
            // 记录鼠标单击时的位置,异步计算贴地距离
            labelPt = positions[positions.length - 1]
            if (positions.length > 2) {
                getSpaceDistance(positions)
            } else if (positions.length == 2) {
                //在三维场景中添加圆点
                viewer.entities.add({
                    name: '_range',
                    id: "range",
                    position: labelPt,
                    point: {
                        pixelSize: 5,
                        color: Cesium.Color.RED,
                        outlineColor: Cesium.Color.WHITE,
                        outlineWidth: 2,
                    },
                    label: {
                        text: '起 点',
                        font: 'normal 18px SimHei',
                        fillColor: Cesium.Color.ORANGE, // 文本颜色
                        backgroundColor: Cesium.Color.WHITE, // 背景色
                        style: Cesium.LabelStyle.FILL, // 文本样式,轮廓
                        outlineWidth: 2, // 轮廓宽度
                        verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 圆点位置
                        horizontalOrigin: Cesium.HorizontalOrigin.LEFT, // 文本的位置
                        pixelOffset: new Cesium.Cartesian2(0, -10), // 文本偏移量,Cartesian2
                    }
                });
            }
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

image.png

image.png

鼠标移动事件

监听此事件,并在移动的过程中创建线

        // 鼠标移动事件
        handler.setInputAction(function (ele) {
            let ray = viewer.camera.getPickRay(ele.endPosition) 
            cartesian = viewer.scene.globe.pick(ray, viewer.scene) 
            // 判断是否定义该对象
            if (!Cesium.defined(cartesian)) {
                return
            }
            if (positions.length >= 2) {
                if (!Cesium.defined(poly)) {
                    // 移动时路径绘制
                    poly = new PolyLinePrimitive(positions)
                } else {
                    positions.pop()
                    positions.push(cartesian)
                }
            }
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
        // 创建线
        var PolyLinePrimitive = (function () {
            function line (positions) {
                this.options = {
                    name: '_range',
                    polyline: {
                        show: true,
                        positions: [],
                        material: Cesium.Color.CORNFLOWERBLUE, 
                        width: 3, 
                        clampToGround: true
                    }
                }
                this.positions = positions
                this.init()
            }

            line.prototype.init = function () {
                var update = () => {
                    return this.positions
                }
                // 实时更改线的位置,更新线
                this.options.polyline.positions = new Cesium.CallbackProperty(update, false)
                viewer.entities.add(this.options)
            }
            return line
        })()

鼠标双击事件

该事件作为结束测距事件,点击该事件时,绘制终点,并关闭掉双击事件。

        //鼠标左键双击事件
        handler.setInputAction(function (ele) {
            // 创建射线并获取交点
            let ray = viewer.camera.getPickRay(ele.position);
            cartesian = viewer.scene.globe.pick(ray, viewer.scene);
            if (!Cesium.defined(cartesian))
                return;
            if (positions.length === 0) {
                positions.push(cartesian.clone());
            }
            positions.push(cartesian);
            // 记录鼠标单击时的节点位置,异步计算贴地距离
            labelPt = positions[positions.length - 1];
            if (positions.length > 2) {
                getSpaceDistance(positions);
            } else if (positions.length === 2) {
                // 在三维场景中添加Label
                viewer.entities.add({
                    name: '_range',
                    position: labelPt,
                    point: {
                        pixelSize: 5,
                        color: Cesium.Color.RED,
                        outlineColor: Cesium.Color.MINTCREAM,
                        outlineWidth: 2,
                    }
                });
            }
            handler.destroy(); // 删除事件
            handler = undefined;
        }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);

image.png

计算距离

该方法是整个测距的灵魂,也是测距和测绘的区别所在,从第二次鼠标左键单击开始调用此方法,为每一条线计算出距离,并绘制在地图中

        // 两点距离计算函数
        function getSpaceDistance (positions) {
            // 只计算最后一截,与前面累加, 因鼠标移动和左键单击事件,最后两个坐标点重复
            let i = positions.length - 3
            // 根据经纬度获取弧度
            let pointCartographic1 = Cesium.Cartographic.fromCartesian(positions[i]) 
            let pointCartographic2 = Cesium.Cartographic.fromCartesian(positions[i + 1])
            getTerrainDistance(pointCartographic1, pointCartographic2)
        }
        // 贴地距离计算函数
        function getTerrainDistance (poin1, poin2) {
            var geodesic = new Cesium.EllipsoidGeodesic()
            geodesic.setEndPoints(poin1, poin2)
            var s = geodesic.surfaceDistance
            var cartoPts = [poin1]
            for (let i = 1000; i < s; i += 1000) {
                var cartoPt = geodesic.interpolateUsingSurfaceDistance(i)
                cartoPts.push(cartoPt)
            }
            cartoPts.push(poin2)
            // 返回两点之间的距离
            var promise = Cesium.sampleTerrain(viewer.terrainProvider, 2, cartoPts)

            Cesium.when(promise, function (updatedPositions) {
                for (let i = 0; i < updatedPositions.length - 1; i++) {
                    var geoD = new Cesium.EllipsoidGeodesic()
                    geoD.setEndPoints(updatedPositions[i], updatedPositions[i + 1])
                    var innerS = geoD.surfaceDistance
                    innerS = Math.sqrt(Math.pow(innerS, 2) + Math.pow(updatedPositions[i + 1].height - updatedPositions[i].height, 2))
                    distance += innerS
                }

                // 在三维场景中添加 label
                var textDisance = distance > 10000 ? (distance / 1000.0).toFixed(2) + '公里' : distance.toFixed(2) + '里'
                viewer.entities.add({
                    name: '_range',
                    position: labelPt,
                    point: {
                        poixeSize: 4,
                        color: Cesium.Color.RED,
                        outlineColor: Cesium.Color.MINTCREAM,
                        outlineWidth: 2
                    },
                    label: {
                        text: textDisance,
                        font: '18px sans-serif',
                        fillColor: Cesium.Color.GOLD,
                        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                        outLineWidth: 2,
                        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                        pixelOffset: new Cesium.Cartesian2(0, -10)
                    }
                })
            })
        }

清除

清除就是是关闭事件且清除图形。当没关闭事件时,就会出现下面场景,第一次测距是正常的,但再次点击按钮时,又会触发点击事件,并以点击按钮的位置作为起点

image.png

image.png

  • 清除几何 在创建线时,我们给赋予一个name,这个值是几何的唯一标识符,我们以此为条件,查找到所有name_range的几何并清除
// 清除线性几何
function removeGeometrys (viewer) {
    let RangeArea = viewer.entities._entities._array
    for (let i = 0; i < RangeArea.length; i++) {
        if (RangeArea[i]._name === "_range") {
            viewer.entities.remove(RangeArea[i]);
            i--;
        }
    }
}
  • 销毁事件 方法执行完后一定记得销毁监听,不然监听会全局存在,消耗性能
this.handler && this.handler.destroy()
this.handler = null