utils/Measure.js

import { Cesium } from '../../namespace'
import { LatLon } from './ThirdPart'
import {
  getCatesian3FromPX,
  getPointByAngleDistance,
  getPolygonCentroid,
  getRegionExtreme,
  interPoints,
  radiansToDegrees,
  subTriangle,
  transformCartesianArrayToWGS84Array,
  transformCartesianToWGS84,
  transformWGS84ToCartographic,
} from './Coordinate'
import CreateRemindertip from './ReminderTip'
import { destroyHandler } from './Handler'
import CreatePolygon from '../core/creator/polygon/CreatePolygon'

/**
 * 空间量测相关方法集
 * @module Measurement
 */

/***
 * 距离测量
 * @param viewer
 * @param type - line:空间,project:投影,onground:地表,elliptic:椭球
 * @param parent
 * @param handler
 * @param callback
 */
export const distanceMeasure = (viewer, type, parent, handler, callback) => {
  let positions = []
  let processObj = []
  let distance = 0
  let sdkToolTip = '左键点击开始量测'

  // 左键单击
  handler.setInputAction((e) => {
    sdkToolTip = '左键单击添加点,左键双击结束,右键单击回撤'
    let cartesian = getCatesian3FromPX(viewer, e.position)
    if (cartesian) {
      if (positions.length == 0) {
        positions.push(cartesian.clone())
        viewer.entities.add({
          parent: parent,
          polyline: {
            positions: new Cesium.CallbackProperty(function () {
              return positions
            }, false),
            width: 5,
            material: Cesium.Color.BLUE.withAlpha(0.8),
            clampToGround: type != 'line',
          },
        })
      }
      positions.push(cartesian)
      // 添加量测信息点
      updateProcess(cartesian, positions[positions.length - 3])
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

  // 鼠标移动
  handler.setInputAction((e) => {
    let endPos = e.endPosition
    CreateRemindertip(sdkToolTip, endPos, true)
    if (positions.length === 0) return
    let cartesian = getCatesian3FromPX(viewer, endPos)
    if (cartesian && positions.length >= 2) {
      positions.pop()
      positions.push(cartesian)
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

  // 左键双击
  handler.setInputAction(() => {
    positions.pop()
    if (positions.length < 2) return
    removeProcess()
    destroyHandler(handler)
    CreateRemindertip(sdkToolTip, null, false)
    callback && typeof callback === 'function' && callback(distance)
  }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)

  // 右键点击
  handler.setInputAction(() => {
    positions.length > 2 && removeProcess()
  }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)

  function removeProcess() {
    positions.pop()
    viewer.entities.remove(processObj[processObj.length - 1])
    processObj.pop()
  }
  function calculateDis(PosArr) {
    let points = []
    switch (type) {
      case 'line':
        points = transformCartesianArrayToWGS84Array(PosArr)
        break
      case 'onground':
      case 'project':
      case 'elliptic':
        points = interPoints(viewer, type, PosArr, [])
        break

      default:
        break
    }
    return type === 'elliptic'
      ? getGeodesyDistance(points)
      : getPositionDistance(points)
  }
  function updateProcess(position, prePos) {
    let _distance = prePos ? calculateDis([prePos, position]) : 0
    distance += _distance
    const text =
      distance > 1000
        ? `${(distance / 1000).toFixed(4)}公里`
        : `${distance.toFixed(4)}米`
    const entity = viewer.entities.add({
      parent: parent,
      position: position,
      point: {
        pixelSize: 10,
        outlineColor: Cesium.Color.BLUE,
        outlineWidth: 5,
      },
      label: {
        text: text,
        show: true,
        showBackground: true,
        backgroundColor: new Cesium.Color(0, 0, 0, 1.0),
        font: '14px monospace',
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        pixelOffset: new Cesium.Cartesian2(0, -25),
      },
    })
    processObj.push(entity)
  }
}

/***
 * 面积测量
 * @param viewer
 * @param type - project:投影,onground:地表,elliptic:椭球
 * @param parent
 * @param handler
 * @param callback
 */
export const areaMeasure = (viewer, type, parent, handler, callback) => {
  let anchorpoints = []
  let polygon = undefined
  let sdkToolTip = '左键点击开始绘制'
  // 左键点击事件
  handler.setInputAction((event) => {
    let pixPos = event.position
    let cartesian = getCatesian3FromPX(viewer, pixPos)
    if (!cartesian) return

    if (anchorpoints.length == 0) {
      sdkToolTip = '左键添加第二个顶点'
      anchorpoints.push(cartesian)
      let dynamicPositions = new Cesium.CallbackProperty(function () {
        return new Cesium.PolygonHierarchy(anchorpoints)
      }, false)
      //设置颗粒度(来源于大神的思路)
      let granularity = Math.PI / Math.pow(2, 11)
      granularity = granularity / 64

      polygon = viewer.entities.add({
        name: 'Polygon',
        parent: parent,
        polygon: {
          hierarchy: dynamicPositions,
          material: Cesium.Color.BLUE.withAlpha(0.6),
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
        },
        polyline: {
          positions: new Cesium.CallbackProperty(function () {
            const linePos = anchorpoints.concat(anchorpoints[0])
            return linePos
          }, false),
          width: 5,
          material: Cesium.Color.GREEN,
          clampToGround: true,
        },
      })
    } else {
      sdkToolTip = '左键添加点,右键撤销,左键双击完成绘制'
    }
    anchorpoints.push(cartesian)
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
  // 鼠标移动事件
  handler.setInputAction((movement) => {
    let endPos = movement.endPosition
    CreateRemindertip(sdkToolTip, endPos, true)
    if (Cesium.defined(polygon)) {
      anchorpoints.pop()
      let cartesian = getCatesian3FromPX(viewer, endPos)
      if (!cartesian) {
        return
      }
      anchorpoints.push(cartesian)
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
  // 左键双击事件
  handler.setInputAction((event) => {
    anchorpoints.pop()
    anchorpoints.pop() //因为是双击结束,所以要pop两次,一次是move的结果,一次是单击结果
    destroyHandler(handler)
    CreateRemindertip(sdkToolTip, event.position, false)
    let areaNum =
      type === 'project'
        ? getArea(anchorpoints)
        : getSurfaceArea(anchorpoints, type)
    const text =
      areaNum > 1000000
        ? `${(areaNum / 1000000).toFixed(4)} 平方公里`
        : `${areaNum.toFixed(4)}平方米`
    const pos = getPolygonCentroid(anchorpoints)
    polygon.position = Cesium.Cartesian3.fromDegrees(pos[0], pos[1])
    polygon.label = {
      text: text,
      font: '24px Helvetica',
      showBackground: true,
      fillColor: Cesium.Color.WHITE,
      outlineColor: Cesium.Color.BLACK,
      outlineWidth: 2,
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
      disableDepthTestDistance: 10000000,
      scaleByDistance: new Cesium.NearFarScalar(1.0e2, 1.0, 2.0e5, 0.3),
    }
    if (typeof callback == 'function') callback(areaNum)
  }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
  // 右键摁下事件
  handler.setInputAction(() => {
    anchorpoints.pop()
  }, Cesium.ScreenSpaceEventType.RIGHT_DOWN)
}

/***
 * 高差量测
 * @param viewer
 * @param parent
 * @param handler
 * @param callback
 */
export const heightMeasure = (viewer, parent, handler, callback) => {
  let positions = []
  let sdkToolTip = '左键点击开始量测'
  handler.setInputAction((e) => {
    let cartesian = getCatesian3FromPX(viewer, e.position)
    if (!cartesian) return
    positions.push(cartesian)
    if (positions.length === 1) {
      sdkToolTip = '左键单击完成量测'
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 10,
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 5,
        },
      })
    } else {
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 10,
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 5,
        },
        polyline: {
          positions: positions,
          width: 5,
          material: Cesium.Color.BLUE.withAlpha(0.8),
          clampToGround: false,
        },
      })
      const pos0 = positions[0]
      const pos1 = positions[1]
      const midP = Cesium.Cartesian3.midpoint(
        pos0,
        pos1,
        new Cesium.Cartesian3()
      )
      const graphic0 = Cesium.Cartographic.fromCartesian(pos0)
      const graphic1 = Cesium.Cartographic.fromCartesian(pos1)
      const height = Math.abs(graphic0.height - graphic1.height)
      viewer.entities.add({
        parent: parent,
        position: midP,
        label: {
          text: '高差:' + height.toFixed(2) + 'm',
          show: true,
          showBackground: true,
          backgroundColor: new Cesium.Color(0, 0, 0, 1.0),
          font: '14px monospace',
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          pixelOffset: new Cesium.Cartesian2(0, -25),
        },
      })
      destroyHandler(handler)
      CreateRemindertip(sdkToolTip, null, false)
      if (typeof callback == 'function') callback(height)
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

  handler.setInputAction((e) => {
    let endPos = e.endPosition
    CreateRemindertip(sdkToolTip, endPos, true)
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}

/***
 * 三角量测
 * @param viewer
 * @param parent
 * @param handler
 * @param callback
 */
export const triangleMeasure = (viewer, parent, handler, callback) => {
  let positions = []
  let sdkToolTip = '左键点击开始量测'
  viewer.entities.add({
    parent: parent,
    polyline: {
      positions: new Cesium.CallbackProperty(function () {
        if (positions.length > 1) {
          const points = updatePoints(positions)
          points.push(points[0])
          return points
        } else {
          return []
        }
      }, false),
      width: 3,
      material: Cesium.Color.BLUE.withAlpha(0.5),
      clampToGround: false,
    },
  })
  handler.setInputAction((e) => {
    let cartesian = getCatesian3FromPX(viewer, e.position)
    if (!cartesian) return
    positions.push(cartesian)
    if (positions.length === 1) {
      positions.push(cartesian.clone())
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 10,
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 5,
        },
      })
      sdkToolTip = '左键单击完成量测'
    } else {
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 10,
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 5,
        },
      })
      const points = updatePoints(positions)
      drawLabel(points[0], points[1], '水平距离')
      drawLabel(points[1], points[2], '高度差')
      drawLabel(points[0], points[2], '空间距离')

      destroyHandler(handler)
      CreateRemindertip(sdkToolTip, null, false)
      if (typeof callback == 'function') callback(points)
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

  handler.setInputAction((e) => {
    let endPos = e.endPosition
    CreateRemindertip(sdkToolTip, endPos, true)
    let cartesian = getCatesian3FromPX(viewer, endPos)
    if (cartesian && positions.length >= 2) {
      positions.pop()
      positions.push(cartesian)
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

  function updatePoints(points) {
    const pos0 = points[0]
    const pos1 = points[points.length - 1]
    const graphic0 = Cesium.Cartographic.fromCartesian(pos0)
    const graphic1 = Cesium.Cartographic.fromCartesian(pos1)
    const h = graphic0.height - graphic1.height
    const graphic =
      h > 0
        ? new Cesium.Cartographic(
            graphic1.longitude,
            graphic1.latitude,
            graphic0.height
          )
        : new Cesium.Cartographic(
            graphic0.longitude,
            graphic0.latitude,
            graphic1.height
          )
    const car3 = Cesium.Cartographic.toCartesian(graphic)
    return h > 0 ? [pos0, car3, pos1] : [pos1, car3, pos0]
  }
  function drawLabel(pos0, pos1, text) {
    const pos = Cesium.Cartesian3.midpoint(pos0, pos1, new Cesium.Cartesian3())
    const dis = Cesium.Cartesian3.distance(pos0, pos1)
    viewer.entities.add({
      parent: parent,
      position: pos,
      label: {
        text: `${text}:${dis.toFixed(4)}m`,
        show: true,
        showBackground: true,
        backgroundColor: new Cesium.Color(0, 0, 0, 1.0),
        font: '14px monospace',
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        pixelOffset: new Cesium.Cartesian2(0, 0),
      },
    })
  }
}

/**
 * 坡角量测
 * @param {*} viewer
 * @param {*} parent
 * @param {*} handler
 * @param {*} callback
 */
export const slopeAngleMeasure = (viewer, parent, handler, callback) => {
  let positions = []
  let sdkToolTip = '左键点击开始量测'
  viewer.entities.add({
    parent: parent,
    polyline: {
      positions: new Cesium.CallbackProperty(function () {
        if (positions.length > 1) {
          const points = updatePoints(positions)
          points.push(points[0])
          return points
        } else {
          return []
        }
      }, false),
      width: 3,
      material: Cesium.Color.BLUE.withAlpha(0.5),
      clampToGround: false,
    },
  })
  handler.setInputAction((e) => {
    let cartesian = getCatesian3FromPX(viewer, e.position)
    if (!cartesian) return
    positions.push(cartesian)
    if (positions.length === 1) {
      positions.push(cartesian.clone())
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 10,
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 5,
        },
      })
      sdkToolTip = '左键单击完成量测'
    } else {
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 10,
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 5,
        },
      })
      const points = updatePoints(positions)
      const dis1 = Cesium.Cartesian3.distance(points[0], points[1])
      const dis3 = Cesium.Cartesian3.distance(points[0], points[2])
      const angle = calAngle(dis1, dis3, points[0])
      destroyHandler(handler)
      CreateRemindertip(sdkToolTip, null, false)
      if (typeof callback == 'function') callback({ points, angle })
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

  handler.setInputAction((e) => {
    let endPos = e.endPosition
    CreateRemindertip(sdkToolTip, endPos, true)
    let cartesian = getCatesian3FromPX(viewer, endPos)
    if (cartesian && positions.length >= 2) {
      positions.pop()
      positions.push(cartesian)
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

  function updatePoints(points) {
    const pos0 = points[0]
    const pos1 = points[points.length - 1]
    const graphic0 = Cesium.Cartographic.fromCartesian(pos0)
    const graphic1 = Cesium.Cartographic.fromCartesian(pos1)
    const h = graphic0.height - graphic1.height
    const graphic =
      h > 0
        ? new Cesium.Cartographic(
            graphic0.longitude,
            graphic0.latitude,
            graphic1.height
          )
        : new Cesium.Cartographic(
            graphic1.longitude,
            graphic1.latitude,
            graphic0.height
          )
    const car3 = Cesium.Cartographic.toCartesian(graphic)
    return h > 0 ? [pos1, car3, pos0] : [pos0, car3, pos1]
  }

  function calAngle(dis1, dis2, point) {
    const angle = radiansToDegrees(Math.acos(dis1 / dis2))
    viewer.entities.add({
      parent: parent,
      position: point,
      label: {
        text: `${angle.toFixed(2)}°`,
        show: true,
        showBackground: true,
        backgroundColor: new Cesium.Color(0, 0, 0, 1.0),
        font: '14px monospace',
        // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        pixelOffset: new Cesium.Cartesian2(0, -20),
      },
    })
    return angle
  }
}

/***
 * 方位角量测
 * @param viewer
 * @param parent
 * @param handler
 * @param callback
 */
export const azimuthMeasure = (viewer, parent, handler, callback) => {
  let positions = []
  let sdkToolTip = '左键点击开始量测'
  viewer.entities.add({
    parent: parent,
    polyline: {
      positions: new Cesium.CallbackProperty(function () {
        return positions
      }, false),
      width: 10,
      material: new Cesium.PolylineArrowMaterialProperty(
        Cesium.Color.BLUE.withAlpha(0.8)
      ),
      clampToGround: true,
    },
  })
  viewer.entities.add({
    parent: parent,
    position: new Cesium.CallbackProperty(function () {
      const num = positions.length
      return num > 1 ? positions[num - 1] : positions[0]
    }, false),
    label: {
      text: new Cesium.CallbackProperty(function () {
        if (positions.length > 1) {
          const pos0 = transformCartesianToWGS84(positions[0])
          const pos1 = transformCartesianToWGS84(
            positions[positions.length - 1]
          )
          const angle = getAngle(pos0.x, pos0.y, pos1.x, pos1.y)
          return `方位角:${angle.toFixed(2)}°`
        } else {
          return ''
        }
      }, false),
      show: true,
      showBackground: true,
      backgroundColor: new Cesium.Color(0, 0, 0, 1.0),
      font: '14px monospace',
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      pixelOffset: new Cesium.Cartesian2(0, -25),
    },
    polyline: {
      positions: new Cesium.CallbackProperty(function () {
        if (positions.length > 1) {
          const pos0 = positions[0]
          const pos1 = positions[positions.length - 1]
          const graphic0 = Cesium.Cartographic.fromCartesian(pos0)
          const h = Cesium.Cartesian3.distance(pos0, pos1)
          const graphic1 = new Cesium.Cartographic(
            graphic0.longitude,
            graphic0.latitude,
            graphic0.height + h
          )
          const pos2 = Cesium.Cartographic.toCartesian(graphic1)
          return [pos0, pos2]
        } else {
          return []
        }
      }, false),

      width: 2,
      material: new Cesium.PolylineDashMaterialProperty({
        color: Cesium.Color.RED.withAlpha(0.5),
      }),
      clampToGround: false,
    },
  })
  viewer.entities.add({
    parent: parent,
    polyline: {
      positions: new Cesium.CallbackProperty(function () {
        if (positions.length > 1) {
          const pos0 = positions[0]
          const pos1 = positions[positions.length - 1]
          const dis = Cesium.Cartesian3.distance(pos0, pos1)
          const sourceP = transformCartesianToWGS84(pos0)
          const pos2 = getPointByAngleDistance(sourceP.x, sourceP.y, 0, dis)
          const point = Cesium.Cartesian3.fromDegrees(pos2.x, pos2.y)
          return [pos0, point]
        } else {
          return []
        }
      }, false),
      width: 2,
      material: Cesium.Color.YELLOW.withAlpha(0.5),
      clampToGround: true,
    },
  })

  handler.setInputAction((e) => {
    let cartesian = getCatesian3FromPX(viewer, e.position)
    if (!cartesian) return
    positions.push(cartesian)
    if (positions.length === 1) {
      positions.push(cartesian.clone())
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 10,
          outlineColor: Cesium.Color.BLUE,
          outlineWidth: 5,
        },
      })
      sdkToolTip = '左键单击完成量测'
    } else {
      viewer.entities.add({
        parent: parent,
        position: cartesian,
        point: {
          pixelSize: 8,
          outlineColor: Cesium.Color.GREEN,
          outlineWidth: 3,
        },
      })
      destroyHandler(handler)
      CreateRemindertip(sdkToolTip, null, false)
      if (typeof callback == 'function') callback(positions)
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)

  handler.setInputAction((e) => {
    let endPos = e.endPosition
    CreateRemindertip(sdkToolTip, endPos, true)
    let cartesian = getCatesian3FromPX(viewer, endPos)
    if (cartesian && positions.length >= 2) {
      positions.pop()
      positions.push(cartesian)
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}

/***
 * 体积量测
 * @param viewer
 * @param parent
 * @param handler
 * @param callback
 */
export const volumeMeasure = (viewer, parent, handler, callback) => {
  CreatePolygon(
    viewer,
    handler,
    { outline: true, outlineColor: '#ffff00' },
    (polygon) => {
      const points = polygon.PottingPoint
      let minX = 10000
      let minY = 10000
      let maxX = -10000
      let maxY = -10000
      for (let i = 0; i < points.length; i++) {
        const point = points[i]
        const pos = transformCartesianToWGS84(point)
        const { x, y } = pos
        minX = Math.min(x, minX)
        maxX = Math.max(x, maxX)
        minY = Math.min(y, minY)
        maxY = Math.max(y, maxY)
      }
      const d_x = Math.abs(maxX - minX)
      const d_y = Math.abs(maxY - minY)
      const s = d_x > d_y ? d_x : d_y
      const triangles = subTriangle(points, 1.5 / s)
      const extreme = getRegionExtreme(viewer, points)
      const pos = getPolygonCentroid(points)

      const positions = points.concat(points[0])
      const num = positions.length
      const { minH, maxH } = extreme

      const wall = viewer.entities.add({
        parent: parent,
        position: Cesium.Cartesian3.fromDegrees(pos[0], pos[1], maxH),

        polygon: {
          hierarchy: positions,
          material: Cesium.Color.BLUE.withAlpha(0.3),
        },
        wall: {
          positions: positions,
          maximumHeights: Array(num).fill(maxH),
          minimumHeights: Array(num).fill(minH),
          outline: true,
          outlineColor: Cesium.Color.LIGHTGRAY.withAlpha(0.5),
          outlineWidth: 4,
          material: Cesium.Color.YELLOW.withAlpha(0.5),
        },
      })
      viewer.entities.remove(polygon)
      const volume = calculateVolume(viewer, triangles, minH)
      wall.label = {
        text: `区域高值:${maxH.toFixed(3)} m\n区域低值:${minH.toFixed(
          3
        )} m\n计算体积:${volume.toFixed(2)} m³`,
        show: true,
        showBackground: true,
        backgroundColor: new Cesium.Color(0, 0, 0, 1.0),
        font: '14px monospace',
        horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        pixelOffset: new Cesium.Cartesian2(-25, -25),
      }
      if (typeof callback == 'function') callback({ minH, maxH, volume })
    }
  )
}

/**
 * 计算体积(三角网算法)
 * @method
 * @param {Object} viewer 地图场景对象
 * @param {Array<DegreePosZ[]>} triangles 三角网集合,WGS84坐标
 * @param {Number} height 计算起始高度
 * @returns {Number} 体积,单位:立方米(m³)
 */
export const calculateVolume = (viewer, triangles, height) => {
  let curVolume = 0
  for (let index = 0; index < triangles.length; index++) {
    const triangle = triangles[index]
    let heightSum = 0
    const basicTriangle = []
    for (let i = 0; i < triangle.length; i++) {
      const cartesian = triangle[i]
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
      let h = viewer.scene.sampleHeightSupported
        ? viewer.scene.sampleHeight(cartographic)
        : viewer.scene.globe.getHeight(cartographic)
      const graphic1 = new Cesium.Cartographic(
        cartographic.longitude,
        cartographic.latitude,
        h
      )
      heightSum += h
      const basicCartesian3 = Cesium.Cartographic.toCartesian(graphic1)
      basicTriangle.push(basicCartesian3)
    }
    const area = getArea(basicTriangle)
    const curHeight = heightSum / 3
    const _h = curHeight - height
    if (_h > 0) {
      const volume = area * (curHeight - height)
      curVolume += volume
    }
  }

  return curVolume
}

/**
 * 计算两点对于正北方向的朝向角度 [0,360]
 * @method
 * @param {Number} x0 起始点经度,WGS84坐标
 * @param {Number} y0 起始点纬度,WGS84坐标
 * @param {Number} x1 目标点经度,WGS84坐标
 * @param {Number} y1 目标点纬度,WGS84坐标
 * @returns {Number} 角度值,单位度(°)
 */
export const getAngle = (x0, y0, x1, y1) => {
  let rad = Math.PI / 180,
    lat1 = y0 * rad,
    lat2 = y1 * rad,
    lon1 = x0 * rad,
    lon2 = x1 * rad
  const a = Math.sin(lon2 - lon1) * Math.cos(lat2)
  const b =
    Math.cos(lat1) * Math.sin(lat2) -
    Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)
  return radiansToDegrees(Math.atan2(a, b))
}
/**
 * 计算面积(表面or椭球)
 * @method
 * @param {Array<Cartesian3>} positions 计算区域边界点集合,笛卡尔坐标
 * @param {String} type 计算模式:onground-地表面积,project-投影面积
 * @returns {Number} 面积值,单位:平方米(㎡)
 */
export const getSurfaceArea = (positions, type) => {
  let granularity = Math.PI / Math.pow(2, 11)
  granularity = granularity / 64
  let sum = 0
  const polygonGeometry = Cesium.PolygonGeometry.fromPositions({
    positions: positions,
    vertexFormat: Cesium.PerInstanceColorAppearance.FLAT_VERTEX_FORMAT,
    granularity: granularity,
  })
  const geom = Cesium.PolygonGeometry.createGeometry(polygonGeometry)
  for (let i = 0; i < geom.indices.length; i += 3) {
    const i0 = geom?.indices[i]
    const i1 = geom?.indices[i + 1]
    const i2 = geom?.indices[i + 2]

    const stp = geom?.attributes.position.values
    const pos0 = {
      x: stp[i0 * 3],
      y: stp[i0 * 3 + 1],
      z: stp[i0 * 3 + 2],
    }
    const pos1 = {
      x: stp[i1 * 3],
      y: stp[i1 * 3 + 1],
      z: stp[i1 * 3 + 2],
    }
    const pos2 = {
      x: stp[i2 * 3],
      y: stp[i2 * 3 + 1],
      z: stp[i2 * 3 + 2],
    }
    const points = [pos0, pos1, pos2]
    const _area = type === 'onground' ? getArea(points) : getGeodesyArea(points)
    sum += _area
  }
  return sum
}

/***
 * 计算面积(椭球)
 * @param positions
 * @returns
 */
export const getGeodesyArea = (positions) => {
  const pos = positions.map((p) => {
    const _pos = transformCartesianToWGS84(p)
    return new LatLon(_pos.x, _pos.y)
  })
  return LatLon.areaOf(pos)
}
/***
 * 计算面积
 * @param positions
 * @returns
 */
export const getArea = (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]
    const point1cartographic = Cesium.Cartographic.fromCartesian(p1)
    const point2cartographic = Cesium.Cartographic.fromCartesian(p2)
    geodesic.setEndPoints(point1cartographic, point2cartographic)
    const s = Math.sqrt(
      Math.pow(geodesic.surfaceDistance, 2) +
        Math.pow(point2cartographic.height - point1cartographic.height, 2)
    )
    const lat1 = point2cartographic.latitude * radiansPerDegree
    const lon1 = point2cartographic.longitude * radiansPerDegree
    const lat2 = point1cartographic.latitude * radiansPerDegree
    const lon2 = point1cartographic.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
    }
    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]
  }
  return Math.abs(sum + x[x.length - 1] * y[0] - x[0] * y[y.length - 1]) / 2
}

/**
 * 获取多点总距离(椭球表面)
 * @method
 * @param {Array<DegreePos>} positions 待计算点集合,WGS84坐标
 * @returns {Number} 距离,单位:米(m)
 */
export const getGeodesyDistance = (positions) => {
  let distance = 0
  for (let i = 0; i < positions.length - 1; i++) {
    const p1 = new LatLon(positions[i].x, positions[i].y)
    const p2 = new LatLon(positions[i + 1].x, positions[i + 1].y)
    const s = p1.distanceTo(p2)
    distance = distance + s
  }
  return distance
}

/**
 * 获取多点总距离
 * @method
 * @param {Array<DegreePosZ>} positions 待计算点集合,WGS84坐标
 * @returns {Number} 距离,单位:米(m)
 */
export const getPositionDistance = (positions) => {
  let distance = 0
  for (let i = 0; i < positions.length - 1; i++) {
    const s = getDistanceTwo(positions[i], positions[i + 1])
    distance = distance + s
  }
  return distance
}

/**
 * 获取两点间距离
 * @method
 * @param {DegreePosZ} pos1 起始点坐标,WGS84坐标
 * @param {DegreePosZ} pos2 目标点坐标,WGS84坐标
 * @returns {Number} 距离,单位:米(m)
 */
export const getDistanceTwo = (pos1, pos2) => {
  let cartographic1 = transformWGS84ToCartographic(pos1)
  let cartographic2 = transformWGS84ToCartographic(pos2)
  let geodesic = new Cesium.EllipsoidGeodesic()
  geodesic.setEndPoints(cartographic1, cartographic2)
  let s = geodesic.surfaceDistance
  return Math.sqrt(
    Math.pow(s, 2) + Math.pow(cartographic1.height - cartographic2.height, 2)
  )
}