个人笔记|vue+Cesium实现画线测距,画面测面积

编辑器:webstorm
服务:node.js npm
CesiumJS: Cesium-v1.9.1
vue: v 2.9.6

功能

点击地图标点并显示数字,两点之间连线测距,距离显示在线段正中,三点以上连成面,面积显示在围成面积的最后一点的斜上方。

主界面

主界面

画线测距

点击“标点测距”按钮,鼠标移动到地图上,单击左键标点,同时点上出现标号。鼠标移动时对应的点跟着动态移动。有多少点标记多少号,线段中点显示两点间距离,单位为km。
(如果想每条线的起点标号都以1开始需要在draw(type)let pointNum = this.pointNum,然后Ctrl+F查找每个that.pointNum,将that删掉)

画线测距

画面测面积

点击“标点测面”按钮,鼠标移动到地图上,单击左键标点,同时点上出现标号。鼠标移动时对应的点跟着动态移动。有多少点标记多少号,围成面积的最后一点的斜上方显示面积,单位为平方公里。


画面测面积

思路

官方示例

  • Callback Property - 计算线段长度 文字居中
  • Billboards + Points - 往地图上加点
  • Labels + Map Pins + Picking - 往地球上加标签
  • Polyline - 连线
  • Polyline Dash + Polygon - 画面

界面构成

  • 初始化镜头 还是设定为火山口
  • 显示提示界面
  • 提示界面中含有测距、测量面积、清空绘制三个按钮

按钮功能

  • 选择测距,点击地图,出现点并显示标号,两点间以线连接,线端中间显示距离,点击右键结束测量
  • 选择测量面积,点击地图,出现点并显示标号,两点间以线连接,三点及以上成面,围成的面积的具体数据显示在中心,点击右键结束测量
  • 选择全部测量,点击地图,显示两点间距离以及围成的面的面积信息,点击右键结束测量
  • 选择清空绘制,地图上的标记全部消失

功能实现

标点显示序号

参考同文集下基于Cesium绘制点线面文件

要在点上方显示序号 -> Cesium的label功能 规定文字 大小
点和序号同时显示 -> 添加的实体里同时包含point和label

数字为点的序号 -> 将包含位置信息的数组的长度传进方法里 -> 在原方法的基础上修改一下,要新增一个参数作为label的text的值

两点中间显示距离

Cesium.Cartographic.fromCartesian (cartesian, ellipsoid , result ) -> 从笛卡尔位置创建一个新的制图实例

new Cesium.EllipsoidGeodesic ( start , end , ellipsoid ) -> 在连接两个提供的行星点的椭球上初始化一个测地线。

  • setEndPoints (start, end) -> 设置测地线的起点和终点
  • surfaceDistance -> 获取起点和终点之间的表面距离

问题

  • 报错value.replace is not a function

    • 问题描述:vue中添加方法drawPointLabel (position, pointNum),希望单击鼠标左键在地图上标点同时出现序号

      引用时代码为let point = that.drawPointLabel(tempPoints[tempPoints.length - 1], tempPoints.length),报错value.replace is not a function

    • 问题分析:方法drawPointLabel具体代码:

      drawPointLabel (position, pointNum) {
            let viewer = this.viewer
            // 本质上就是添加一个点的实体
            return viewer.entities.add({
              name: '点几何对象',
              position: position,
              point: {
                color: Cesium.Color.WHEAT,
                pixelSize: 5,
                outlineWidth: 3,
                disableDepthTestDistance: Number.POSITIVE_INFINITY,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 规定贴地
              },
              label: {
                text: pointNum,
                font: '30px sans-serif',
                fillColor: Cesium.Color.WHITE,
                outlineWidth: 2,
                backgroundColor: Cesium.Color.BLACK,
                showBackground: true,
                style: Cesium.LabelStyle.FILL,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                horizontalOrigin: Cesium.HorizontalOrigin.CENTER
              }
            })
          }
      

      其中label的text属性需求的数据类型为字符串,但起初传给它的pointNum的数据类型是number,并不匹配

    • 解决办法:用JSON.stingify方法转换数据为字符串,即将调用代码改为let point = that.drawPointLabel(tempPoints[tempPoints.length - 1], JSON.stringify(tempPoints.length))

  • 世界坐标、地表坐标区别

  • 计算面积函数返回NaN,对参数使用JSON.stringify方法后返回null

    问题分析:已知getArea方法返回值出错,方法中使用了自己写的getLength,pointAngle,Bearing方法,则需要排查这些方法返回值的类型并将相应返回值修改为number型,再通过JSON.stringify转换为可被label的text识别的字符串

    问题排查:getLength没问题,getArea调用pointAngle, pointAngle调用Bearing。这一部分借鉴的是网上代码,稀奇古怪,没看明白。但是看明白了最后Math.sin()里不应该是角度值而是弧度值,搞不清之前角度转弧度然后再转角度是在干嘛,自己改成了直接用Cesium.Cartographic.fromCartesian转弧度。然后好像就没问题了。

    解决办法(改后代码):

    // 方向
        Bearing (from, to) {
          let fromCartographic = Cesium.Cartographic.fromCartesian(from)
          let toCartographic = Cesium.Cartographic.fromCartesian(to)
          let lat1 = fromCartographic.latitude
          let lon1 = fromCartographic.longitude
          let lat2 = toCartographic.latitude
          let lon2 = toCartographic.longitude
          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
          }
          return angle
        },
        // 角度
        pointAngle (point1, point2, point3) {
          let bearing21 = this.Bearing(point2, point1)
          let bearing23 = this.Bearing(point2, point3)
          let angle = bearing21 - bearing23
          if (angle < 0) {
            angle += Math.PI * 2.0
          }
          return angle
        }
    

    另外getArea返回的时候自己加了返回值.toFixed(2),但是用了这个方法以后就变成了string类型,不符合后来用的math.abs()的number参数类型,所以在中间又用parseFloat转了一道,同样的也可以先Math.abs()toFixed(2),这样其实更高效,后续也不用再JSON.stringify转成字符串了

    解决办法一:

    getArea (positions) {
          let res = 0
          for (let i = 0; i < positions.length - 2; i++) {
            let j = (i + 1) % positions.length
            let k = (i + 2) % positions.length
            let totalAngle = this.pointAngle(positions[i], positions[j], positions[k])
            let tempLength1 = this.getLength(positions[j], positions[0])
            let tempLength2 = this.getLength(positions[k], positions[0])
            res += tempLength1 * tempLength2 * Math.sin(totalAngle) / 2
          }
          res = res.toFixed(2)
          // console.log(res)
          res = parseFloat(res)
          // console.log(Math.abs(res))
          return Math.abs(res)
        }
    

    解决办法二:

    getArea (positions) {
          let res = 0
          for (let i = 0; i < positions.length - 2; i++) {
            let j = (i + 1) % positions.length
            let k = (i + 2) % positions.length
            let totalAngle = this.pointAngle(positions[i], positions[j], positions[k])
            let tempLength1 = this.getLength(positions[j], positions[0])
            let tempLength2 = this.getLength(positions[k], positions[0])
            res += tempLength1 * tempLength2 * Math.sin(totalAngle) / 2
          }
          res = Math.abs(res)res.toFixed(2)
          return res.toFixed(2)
        }
    

    然后将调用该方法的对应代码从

    that.addArea(JSON.stringify(pointArea), tempPoints)
    

    改成

    that.addArea(pointArea, tempPoints)
    

具体代码

总代码

配置部分见同文集下环境搭建

<template>
  <div id="app">
    <div id="cesiumContainer"></div>
    <div class="btnContainer">
      <button @click="draw('Polyline')">标点测距</button>
      <button @click="draw('Polygon')">标点测面</button>
      <button @click="clearAllDrawn()">清空数据</button>
      <div class="tip">
        <p>点击按钮后,在场景内单击左键标点,单击右键结束测量。</p>
        <p>点击“标点测距”按钮后在场景内单击鼠标左键加点,在两点间显示距离,单击右键结束测量。</p>
        <p>点击“标点测面”按钮后在场景内单击鼠标左键加点,单击右键在勾出的范围中显示面积。</p>
        <p>点击“清空数据”按钮删除所有数据。</p>
        <p>剩下的我慢慢写</p>
      </div>
    </div>
  </div>
</template>

<script>
import * as Cesium from 'cesium/Cesium'
import * as widgets from 'cesium/Widgets/widgets.css'

export default {
  name: 'App',
  data () {
    return {
      viewer: undefined,
      tempEntities: [],
      pointNum: 0,
      floatingPoint: undefined,
      activeShape: undefined
    }
  },
  mounted () {
    this.Init()
  },
  methods: {
    /* 初始化 */
    Init () {
      // 引入个人token
      Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkYmJkNWQ3Mi0zOGVkLTQ5N2YtYTBmMy0wMDAyODZiMDMyZWYiLCJpZCI6ODQ2NzQsImlhdCI6MTY0NjQ0NTYxNX0.XkHX3rdysM4uUe5VTKDVEV3W2An33zyh4qAkFUac2fk'
      // 设置取景器
      this.viewer = new Cesium.Viewer('cesiumContainer', {
        terrainProvider: Cesium.createWorldTerrain(),
        selectionIndicator: false, // 不显示指示器小部件
        infoBox: false, //  不显示信息框
        sceneModePicker: false, // 不显示模式切换选项
        baseLayerPicker: false,
        navigationHelpButton: false,
        animation: false,
        shouldAnimate: false,
        timeline: false,
        geocoder: false,
        homeButton: false,
        // 添加ArcGIS在线影像底图
        imageryProvider: new Cesium.UrlTemplateImageryProvider({
          url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
          subdomains: ['0', '1', '2', '3'],
          tilingScheme: new Cesium.WebMercatorTilingScheme()
        })
      })
      // 若浏览器不支持pickPosition,显示报错信息
      if (!this.viewer.scene.pickPositionSupported) {
        window.alert('This browser does not support pickPosition.')
      }
      // 载入OSM建筑物
      // const osmBuildings = this.viewer.scene.primitives.add(Cesium.createOsmBuildings()) // eslint-disable-line no-unused-vars
      // 初始化镜头
      this.viewer.camera.lookAt(
        Cesium.Cartesian3.fromDegrees(-122.2058, 46.1955, 1000.0),
        new Cesium.Cartesian3(5000.0, 5000.0, 5000.0)
      )
      this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
    },
    /* 空间两点距离计算函数 */
    getLength (start, end) {
      // 将起点与终点位置信息从笛卡尔坐标形式转换为Cartographic形式
      let startCartographic = Cesium.Cartographic.fromCartesian(start)
      let endCartographic = Cesium.Cartographic.fromCartesian(end)
      // 初始化测地线
      let geodesic = new Cesium.EllipsoidGeodesic()
      // 设置测地线起点和终点,EllipsoidGeodesic中setEndPoints常与surfaceDistance搭配使用
      geodesic.setEndPoints(startCartographic, endCartographic)
      // 获取起点和终点之间的表面距离,单位为km,规定四舍五入保留两位小数
      // surfaceDistance返回number 单位为m,带小数
      // console.log((geodesic.surfaceDistance / 1000).toFixed(2))
      return (geodesic.surfaceDistance / 1000).toFixed(2)
    },
    /* 空间两点计算中点函数 */
    getMidpoint (start, end) {
      let startPoint = Cesium.Cartographic.fromCartesian(start)
      let endPoint = Cesium.Cartographic.fromCartesian(end)
      let geodesic = new Cesium.EllipsoidGeodesic()
      geodesic.setEndPoints(startPoint, endPoint)
      let geoPoint = geodesic.interpolateUsingFraction(0.5)
      console.log(Cesium.Ellipsoid.WGS84.cartographicToCartesian(geoPoint))
      return Cesium.Ellipsoid.WGS84.cartographicToCartesian(geoPoint)
    },
    addLabel (midPoint, labelLength) {
      let viewer = this.viewer
      return viewer.entities.add({
        name: '中点',
        position: midPoint,
        label: {
          text: labelLength + 'km',
          font: '20px sans-serif',
          fillColor: Cesium.Color.WHITE,
          outlineWidth: 2,
          backgroundColor: Cesium.Color.BLACK,
          showBackground: true,
          style: Cesium.LabelStyle.FILL,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
          disableDepthTestDistance: Number.POSITIVE_INFINITY
        }
      })
    },
    /* 测量空间面积 */
    // 方向
    Bearing (from, to) {
      let fromCartographic = Cesium.Cartographic.fromCartesian(from)
      let toCartographic = Cesium.Cartographic.fromCartesian(to)
      let lat1 = fromCartographic.latitude
      let lon1 = fromCartographic.longitude
      let lat2 = toCartographic.latitude
      let lon2 = toCartographic.longitude
      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
      }
      return angle
    },
    // 角度
    pointAngle (point1, point2, point3) {
      let bearing21 = this.Bearing(point2, point1)
      let bearing23 = this.Bearing(point2, point3)
      let angle = bearing21 - bearing23
      if (angle < 0) {
        angle += Math.PI * 2.0
      }
      return angle
    },
    getArea (positions) {
      let res = 0
      for (let i = 0; i < positions.length - 2; i++) {
        let j = (i + 1) % positions.length
        let k = (i + 2) % positions.length
        let totalAngle = this.pointAngle(positions[i], positions[j], positions[k])
        let tempLength1 = this.getLength(positions[j], positions[0])
        let tempLength2 = this.getLength(positions[k], positions[0])
        res += tempLength1 * tempLength2 * Math.sin(totalAngle) / 2
      }
      res = res.toFixed(2)
      // console.log(res)
      res = parseFloat(res)
      // console.log(Math.abs(res))
      return Math.abs(res)
    },
    addArea (area, positions) {
      let viewer = this.viewer
      return viewer.entities.add({
        name: '多边形面积',
        position: positions[positions.length - 1],
        label: {
          text: area + '平方公里',
          font: '20px sans-serif',
          fillColor: Cesium.Color.WHITE,
          outlineWidth: 2,
          backgroundColor: Cesium.Color.BLACK,
          showBackground: true,
          style: Cesium.LabelStyle.FILL,
          pixelOffset: new Cesium.Cartesian2(60, -60),
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
          disableDepthTestDistance: Number.POSITIVE_INFINITY
        }
      })
    },
    /* 绘制函数 */
    drawPointLabel (position, pointNum) {
      let viewer = this.viewer
      // 本质上就是添加一个点的实体
      return viewer.entities.add({
        name: '点几何对象',
        position: position,
        point: {
          color: Cesium.Color.WHEAT,
          pixelSize: 5,
          outlineWidth: 3,
          disableDepthTestDistance: Number.POSITIVE_INFINITY, //
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 规定贴地
        },
        label: {
          text: pointNum,
          font: '30px sans-serif',
          fillColor: Cesium.Color.WHITE,
          outlineWidth: 2,
          backgroundColor: Cesium.Color.BLACK,
          showBackground: true,
          style: Cesium.LabelStyle.FILL,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER
        }
      })
    },
    drawPoint (position) {
      let viewer = this.viewer
      // 本质上就是添加一个点的实体
      return viewer.entities.add({
        position: position,
        point: {
          color: Cesium.Color.WHEAT,
          pixelSize: 5,
          outlineWidth: 3,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 规定贴地
        }
      })
    },
    drawPolyline (positions) {
      let viewer = this.viewer
      if (positions.length < 1) return
      return viewer.entities.add({
        name: '线几何对象',
        polyline: {
          positions: positions,
          width: 5.0,
          material: new Cesium.PolylineGlowMaterialProperty({
            // eslint-disable-next-line new-cap
            color: Cesium.Color.WHEAT
          }),
          depthFailMaterial: new Cesium.PolylineGlowMaterialProperty({
            // eslint-disable-next-line new-cap
            color: Cesium.Color.WHEAT
          }),
          clampToGround: true
        }
      })
    },
    drawPolygon (positions) {
      let viewer = this.viewer
      if (positions.length < 2) return
      return viewer.entities.add({
        name: '面几何对象',
        polygon: {
          hierarchy: positions,
          // eslint-disable-next-line new-cap
          material: new Cesium.ColorMaterialProperty(
            Cesium.Color.WHEAT.withAlpha(0.4)
          )
        }
      })
    },
    /* 清除实体 */
    clearAllDrawn () {
      let viewer = this.viewer
      this.tempEntities = []
      this.pointNum = 0
      viewer.entities.removeAll()
    },
    created () {
    },
    /* 根据类型绘制对象
    * @param type point polyline polygon */
    draw (type) {
      let that = this
      let viewer = this.viewer
      // let pointNum = this.pointNum
      // console.log(pointNum)
      let tempEntities = this.tempEntities
      let floatingPoint = this.floatingPoint
      let activeShape = this.activeShape
      let position = []
      let tempPoints = []
      let activeShapePoints = []
      // 开启深度检测
      viewer.scene.globe.depthTestAgainstTerrain = true
      // 创建场景的HTML canvas元素
      let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
      switch (type) {
        // 绘制线
        case 'Polyline':
          // 取消鼠标双击事件
          viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
          // 监听鼠标移动
          handler.setInputAction(function (movement) {
            if (Cesium.defined(floatingPoint)) {
              let newPosition = viewer.scene.pickPosition(movement.endPosition)
              if (Cesium.defined(newPosition)) {
                floatingPoint.position.setValue(newPosition)
                activeShapePoints.pop()
                activeShapePoints.push(newPosition)
              }
            }
          }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
          // 左键单击开始画线
          handler.setInputAction(function (click) {
            let earthPosition = viewer.scene.pickPosition(click.position)
            if (Cesium.defined(earthPosition)) {
              floatingPoint = that.drawPoint(earthPosition)
            }
            // 获取位置信息
            // 从相机位置创建一条射线,这条射线通过世界中movement.position像素所在的坐标,返回Cartesian3坐标
            let ray = viewer.camera.getPickRay(click.position)
            // 找到射线与渲染的地球表面之间的交点。射线必须以世界坐标给出。返回Cartesian3坐标
            position = viewer.scene.globe.pick(ray, viewer.scene)
            tempPoints.push(position) // 记录点位
            that.pointNum += 1
            let tempLength = tempPoints.length // 记录点数
            // 调用绘制点的接口
            let point = that.drawPointLabel(tempPoints[tempPoints.length - 1], JSON.stringify(that.pointNum))
            tempEntities.push(point)
            // 存在超过一个点时
            if (tempLength > 1) {
              // 绘制线
              let pointLength = that.getLength(tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1])
              let midPosition = that.getMidpoint(tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1])
              let pointline = that.drawPolyline([tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1]])
              let pointLabel = that.addLabel(midPosition, pointLength)
              tempEntities.push(pointline) // 保存记录
              tempEntities.push(pointLabel)
            }
          }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
          // 右键单击结束画线
          handler.setInputAction(function (click) {
            console.log(that.pointNum)
            activeShapePoints.pop()
            viewer.entities.remove(activeShapePoints)
            viewer.entities.remove(floatingPoint)
            tempPoints = [] // 清空点位记录
            handler.destroy()
            handler = null
            floatingPoint = undefined
            activeShape = undefined
            activeShapePoints = []
            console.log(that.pointNum)
          }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
          break
        // 绘制面
        case 'Polygon':
          // 取消鼠标双击事件
          viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
          // 监听鼠标移动
          handler.setInputAction(function (movement) {
            if (Cesium.defined(floatingPoint)) {
              let newPosition = viewer.scene.pickPosition(movement.endPosition)
              if (Cesium.defined(newPosition)) {
                floatingPoint.position.setValue(newPosition)
                activeShapePoints.pop()
                activeShapePoints.push(newPosition)
              }
            }
          }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
          // 左键单击开始画线
          handler.setInputAction(function (click) {
            let earthPosition = viewer.scene.pickPosition(click.position)
            if (Cesium.defined(earthPosition)) {
              if (activeShapePoints.length === 0) {
                floatingPoint = that.drawPoint(earthPosition)
                activeShapePoints.push(earthPosition)
                const dynamicPositions = new Cesium.CallbackProperty(function () {
                  return new Cesium.PolygonHierarchy(activeShapePoints)
                }, false)
                activeShape = that.drawPolygon(dynamicPositions)
              }
              activeShapePoints.push(earthPosition)
            }
            // 获取位置信息
            let ray = viewer.camera.getPickRay(click.position)
            position = viewer.scene.globe.pick(ray, viewer.scene)
            tempPoints.push(position) // 记录点位
            let tempLength = tempPoints.length // 记录点数
            that.pointNum += 1
            // 调用绘制点的接口
            let point = that.drawPointLabel(tempPoints[tempPoints.length - 1], JSON.stringify(that.pointNum))
            tempEntities.push(point)
            // 存在超过一个点时
            if (tempLength > 1) {
              // 绘制线
              let pointline = that.drawPolyline([tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1]])
              tempEntities.push(pointline) // 保存记录
            }
          }, 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 {
                // 闭合最后一条线
                let pointline = that.drawPolyline([tempPoints[0], tempPoints[tempPoints.length - 1]])
                tempEntities.push(pointline)
                that.drawPolygon(tempPoints)
                let pointArea = that.getArea(tempPoints)
                that.addArea(JSON.stringify(pointArea), tempPoints)
                tempEntities.push(tempPoints)
                handler.destroy()
                handler = null
              }
            }
            activeShapePoints.pop()
            viewer.entities.remove(activeShapePoints)
            viewer.entities.remove(floatingPoint)
            floatingPoint = undefined
            activeShapePoints = []
          }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
          break
      }
    }
  }
}
</script>

<style>
html,
body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}
#app,#cesiumContainer {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.btnContainer {
  position: absolute;
  left: 15px;
  top: 80px;
  padding: 10px 15px;
  /*添加圆角边框*/
  border-radius: 5px;
  border: 1px solid rgba(128,128,128, 0.5);
  color: #ffffff;
  background: rgba(0, 0, 0,0.4);
  box-shadow: 0 4px 8px rgb(128 128 128 / 50%);
  max-width: 300px;
}
button {
  background: transparent;
  border: 1px solid #00d0ffb8;
  color: white;
  padding: 7px 9px;
  border-radius: 3px;
  cursor: pointer;
}
.tip p{
  margin: 2px 0px;
  padding: 5px 1px;
}
</style>

初始化部分

Init () {
      // 引入个人token
      Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkYmJkNWQ3Mi0zOGVkLTQ5N2YtYTBmMy0wMDAyODZiMDMyZWYiLCJpZCI6ODQ2NzQsImlhdCI6MTY0NjQ0NTYxNX0.XkHX3rdysM4uUe5VTKDVEV3W2An33zyh4qAkFUac2fk'
      // 设置取景器
      this.viewer = new Cesium.Viewer('cesiumContainer', {
        terrainProvider: Cesium.createWorldTerrain(),
        selectionIndicator: false, // 不显示指示器小部件
        infoBox: false, //  不显示信息框
        sceneModePicker: false, // 不显示模式切换选项
        baseLayerPicker: false,
        navigationHelpButton: false,
        animation: false,
        shouldAnimate: false,
        timeline: false,
        geocoder: false,
        homeButton: false,
        // 添加ArcGIS在线影像底图
        imageryProvider: new Cesium.UrlTemplateImageryProvider({
          url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
          subdomains: ['0', '1', '2', '3'],
          tilingScheme: new Cesium.WebMercatorTilingScheme()
        })
      })
      // 若浏览器不支持pickPosition,显示报错信息
      if (!this.viewer.scene.pickPositionSupported) {
        window.alert('This browser does not support pickPosition.')
      }
      // 载入OSM建筑物
      // const osmBuildings = this.viewer.scene.primitives.add(Cesium.createOsmBuildings()) // eslint-disable-line no-unused-vars
      // 初始化镜头
      this.viewer.camera.lookAt(
        Cesium.Cartesian3.fromDegrees(-122.2058, 46.1955, 1000.0),
        new Cesium.Cartesian3(5000.0, 5000.0, 5000.0)
      )
      this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
    }

测距部分

/* 空间两点距离计算函数 */
    getLength (start, end) {
      // 将起点与终点位置信息从笛卡尔坐标形式转换为Cartographic形式
      let startCartographic = Cesium.Cartographic.fromCartesian(start)
      let endCartographic = Cesium.Cartographic.fromCartesian(end)
      // 初始化测地线
      let geodesic = new Cesium.EllipsoidGeodesic()
      // 设置测地线起点和终点,EllipsoidGeodesic中setEndPoints常与surfaceDistance搭配使用
      geodesic.setEndPoints(startCartographic, endCartographic)
      // 获取起点和终点之间的表面距离,单位为km,规定四舍五入保留两位小数
      // surfaceDistance返回number 单位为m,带小数
      // console.log((geodesic.surfaceDistance / 1000).toFixed(2))
      return (geodesic.surfaceDistance / 1000).toFixed(2)
    },
    /* 空间两点计算中点函数 */
    getMidpoint (start, end) {
      let startPoint = Cesium.Cartographic.fromCartesian(start)
      let endPoint = Cesium.Cartographic.fromCartesian(end)
      let geodesic = new Cesium.EllipsoidGeodesic()
      geodesic.setEndPoints(startPoint, endPoint)
      let geoPoint = geodesic.interpolateUsingFraction(0.5)
      console.log(Cesium.Ellipsoid.WGS84.cartographicToCartesian(geoPoint))
      return Cesium.Ellipsoid.WGS84.cartographicToCartesian(geoPoint)
    },
    /* 在线段中点处添加标签,显示长度 */
    addLabel (midPoint, labelLength) {
      let viewer = this.viewer
      return viewer.entities.add({
        name: '中点',
        position: midPoint,
        label: {
          text: labelLength + 'km',
          font: '20px sans-serif',
          fillColor: Cesium.Color.WHITE,
          outlineWidth: 2,
          backgroundColor: Cesium.Color.BLACK,
          showBackground: true,
          style: Cesium.LabelStyle.FILL,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
          disableDepthTestDistance: Number.POSITIVE_INFINITY
        }
      })
    },

测面部分

/* 测量空间面积 */
    // 方向
    Bearing (from, to) {
      let fromCartographic = Cesium.Cartographic.fromCartesian(from)
      let toCartographic = Cesium.Cartographic.fromCartesian(to)
      let lat1 = fromCartographic.latitude
      let lon1 = fromCartographic.longitude
      let lat2 = toCartographic.latitude
      let lon2 = toCartographic.longitude
      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
      }
      return angle
    },
    // 角度
    pointAngle (point1, point2, point3) {
      let bearing21 = this.Bearing(point2, point1)
      let bearing23 = this.Bearing(point2, point3)
      let angle = bearing21 - bearing23
      if (angle < 0) {
        angle += Math.PI * 2.0
      }
      return angle
    },
    /* 计算空间面积 */
    getArea (positions) {
      let res = 0
      for (let i = 0; i < positions.length - 2; i++) {
        let j = (i + 1) % positions.length
        let k = (i + 2) % positions.length
        let totalAngle = this.pointAngle(positions[i], positions[j], positions[k])
        let tempLength1 = this.getLength(positions[j], positions[0])
        let tempLength2 = this.getLength(positions[k], positions[0])
        res += tempLength1 * tempLength2 * Math.sin(totalAngle) / 2
      }
      res = res.toFixed(2)
      // console.log(res)
      res = parseFloat(res)
      // console.log(Math.abs(res))
      return Math.abs(res)
    },
    /* 在最后一个点处添加标签,显示面积 */
    addArea (area, positions) {
      let viewer = this.viewer
      return viewer.entities.add({
        name: '多边形面积',
        position: positions[positions.length - 1],
        label: {
          text: area + '平方公里',
          font: '20px sans-serif',
          fillColor: Cesium.Color.WHITE,
          outlineWidth: 2,
          backgroundColor: Cesium.Color.BLACK,
          showBackground: true,
          style: Cesium.LabelStyle.FILL,
          pixelOffset: new Cesium.Cartesian2(60, -60),
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
          disableDepthTestDistance: Number.POSITIVE_INFINITY
        }
      })
    }

画线画面部分

/* 绘制函数 */
    drawPointLabel (position, pointNum) {
      let viewer = this.viewer
      // 本质上就是添加一个点的实体
      return viewer.entities.add({
        name: '点几何对象',
        position: position,
        point: {
          color: Cesium.Color.WHEAT,
          pixelSize: 5,
          outlineWidth: 3,
          disableDepthTestDistance: Number.POSITIVE_INFINITY, //
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 规定贴地
        },
        label: {
          text: pointNum,
          font: '30px sans-serif',
          fillColor: Cesium.Color.WHITE,
          outlineWidth: 2,
          backgroundColor: Cesium.Color.BLACK,
          showBackground: true,
          style: Cesium.LabelStyle.FILL,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER
        }
      })
    },
    drawPoint (position) {
      let viewer = this.viewer
      // 本质上就是添加一个点的实体
      return viewer.entities.add({
        position: position,
        point: {
          color: Cesium.Color.WHEAT,
          pixelSize: 5,
          outlineWidth: 3,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 规定贴地
        }
      })
    },
    drawPolyline (positions) {
      let viewer = this.viewer
      if (positions.length < 1) return
      return viewer.entities.add({
        name: '线几何对象',
        polyline: {
          positions: positions,
          width: 5.0,
          material: new Cesium.PolylineGlowMaterialProperty({
            // eslint-disable-next-line new-cap
            color: Cesium.Color.WHEAT
          }),
          depthFailMaterial: new Cesium.PolylineGlowMaterialProperty({
            // eslint-disable-next-line new-cap
            color: Cesium.Color.WHEAT
          }),
          clampToGround: true
        }
      })
    },
    drawPolygon (positions) {
      let viewer = this.viewer
      if (positions.length < 2) return
      return viewer.entities.add({
        name: '面几何对象',
        polygon: {
          hierarchy: positions,
          // eslint-disable-next-line new-cap
          material: new Cesium.ColorMaterialProperty(
            Cesium.Color.WHEAT.withAlpha(0.4)
          )
        }
      })
    },

清除绘制部分

/* 清除实体 */
    clearAllDrawn () {
      let viewer = this.viewer
      this.tempEntities = []
      this.pointNum = 0
      viewer.entities.removeAll()
    },
    created () {
    },

按钮绑定代码

/* 根据类型绘制对象
    * @param type point polyline polygon */
    draw (type) {
      let that = this
      let viewer = this.viewer
      // let pointNum = this.pointNum
      // console.log(pointNum)
      let tempEntities = this.tempEntities
      let floatingPoint = this.floatingPoint
      let activeShape = this.activeShape
      let position = []
      let tempPoints = []
      let activeShapePoints = []
      // 开启深度检测
      viewer.scene.globe.depthTestAgainstTerrain = true
      // 创建场景的HTML canvas元素
      let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
      switch (type) {
        // 绘制线
        case 'Polyline':
          // 取消鼠标双击事件
          viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
          // 监听鼠标移动
          handler.setInputAction(function (movement) {
            if (Cesium.defined(floatingPoint)) {
              let newPosition = viewer.scene.pickPosition(movement.endPosition)
              if (Cesium.defined(newPosition)) {
                floatingPoint.position.setValue(newPosition)
                activeShapePoints.pop()
                activeShapePoints.push(newPosition)
              }
            }
          }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
          // 左键单击开始画线
          handler.setInputAction(function (click) {
            let earthPosition = viewer.scene.pickPosition(click.position)
            if (Cesium.defined(earthPosition)) {
              floatingPoint = that.drawPoint(earthPosition)
            }
            // 获取位置信息
            // 从相机位置创建一条射线,这条射线通过世界中movement.position像素所在的坐标,返回Cartesian3坐标
            let ray = viewer.camera.getPickRay(click.position)
            // 找到射线与渲染的地球表面之间的交点。射线必须以世界坐标给出。返回Cartesian3坐标
            position = viewer.scene.globe.pick(ray, viewer.scene)
            tempPoints.push(position) // 记录点位
            that.pointNum += 1
            let tempLength = tempPoints.length // 记录点数
            // 调用绘制点的接口
            let point = that.drawPointLabel(tempPoints[tempPoints.length - 1], JSON.stringify(that.pointNum))
            tempEntities.push(point)
            // 存在超过一个点时
            if (tempLength > 1) {
              // 绘制线
              let pointLength = that.getLength(tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1])
              let midPosition = that.getMidpoint(tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1])
              let pointline = that.drawPolyline([tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1]])
              let pointLabel = that.addLabel(midPosition, pointLength)
              tempEntities.push(pointline) // 保存记录
              tempEntities.push(pointLabel)
            }
          }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
          // 右键单击结束画线
          handler.setInputAction(function (click) {
            console.log(that.pointNum)
            activeShapePoints.pop()
            viewer.entities.remove(activeShapePoints)
            viewer.entities.remove(floatingPoint)
            tempPoints = [] // 清空点位记录
            handler.destroy()
            handler = null
            floatingPoint = undefined
            activeShape = undefined
            activeShapePoints = []
            console.log(that.pointNum)
          }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
          break
        // 绘制面
        case 'Polygon':
          // 取消鼠标双击事件
          viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
          // 监听鼠标移动
          handler.setInputAction(function (movement) {
            if (Cesium.defined(floatingPoint)) {
              let newPosition = viewer.scene.pickPosition(movement.endPosition)
              if (Cesium.defined(newPosition)) {
                floatingPoint.position.setValue(newPosition)
                activeShapePoints.pop()
                activeShapePoints.push(newPosition)
              }
            }
          }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
          // 左键单击开始画线
          handler.setInputAction(function (click) {
            let earthPosition = viewer.scene.pickPosition(click.position)
            if (Cesium.defined(earthPosition)) {
              if (activeShapePoints.length === 0) {
                floatingPoint = that.drawPoint(earthPosition)
                activeShapePoints.push(earthPosition)
                const dynamicPositions = new Cesium.CallbackProperty(function () {
                  return new Cesium.PolygonHierarchy(activeShapePoints)
                }, false)
                activeShape = that.drawPolygon(dynamicPositions)
              }
              activeShapePoints.push(earthPosition)
            }
            // 获取位置信息
            let ray = viewer.camera.getPickRay(click.position)
            position = viewer.scene.globe.pick(ray, viewer.scene)
            tempPoints.push(position) // 记录点位
            let tempLength = tempPoints.length // 记录点数
            that.pointNum += 1
            // 调用绘制点的接口
            let point = that.drawPointLabel(tempPoints[tempPoints.length - 1], JSON.stringify(that.pointNum))
            tempEntities.push(point)
            // 存在超过一个点时
            if (tempLength > 1) {
              // 绘制线
              let pointline = that.drawPolyline([tempPoints[tempPoints.length - 2], tempPoints[tempPoints.length - 1]])
              tempEntities.push(pointline) // 保存记录
            }
          }, 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 {
                // 闭合最后一条线
                let pointline = that.drawPolyline([tempPoints[0], tempPoints[tempPoints.length - 1]])
                tempEntities.push(pointline)
                that.drawPolygon(tempPoints)
                let pointArea = that.getArea(tempPoints)
                that.addArea(JSON.stringify(pointArea), tempPoints)
                tempEntities.push(tempPoints)
                handler.destroy()
                handler = null
              }
            }
            activeShapePoints.pop()
            viewer.entities.remove(activeShapePoints)
            viewer.entities.remove(floatingPoint)
            floatingPoint = undefined
            activeShapePoints = []
          }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
          break
      }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容