问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Cesium空间距离量测:从基础到进阶的完整指南

创作时间:
作者:
@小白创作中心

Cesium空间距离量测:从基础到进阶的完整指南

引用
CSDN
1.
https://blog.csdn.net/m0_67946993/article/details/136839992

Cesium是一个强大的开源WebGIS框架,广泛应用于三维地理信息系统开发。本文将详细介绍如何在Cesium中实现空间距离量测功能,包括非贴地形距离测量和贴地线距离测量两种方法。

非贴地形距离测量

在Cesium中进行非贴地形距离测量,首先需要获取两点的屏幕坐标,并将其转换为笛卡尔空间直角坐标。可以使用Cesium.Cartesian3.distance()函数计算两点之间的距离。这种方法不考虑地球的曲率和地表的曲面,适用于简单的距离测量场景。

如果需要更精确的距离测量,可以使用椭球体测地线对象(EllipsoidGeodesic)来计算两点之间的曲面距离。这种方法考虑了地球的曲率,通常用于测量地球上两个点之间的实际距离。为了进一步提高精度,还需要考虑两点之间的高度差,可以使用直角三角形求斜边的方法来计算三维空间中两点的直线距离。

下面是具体的代码实现:

/* 空间两点距离计算函数 */
const getSpaceDistance = (positions: any) => {
  let lengthAll = 0;
  for (let i = 0; i < positions.length - 1; i++) {
    // 1.将起点与终点位置信息从笛卡尔坐标形式转换为Cartographic形式
    const point1cartographic = Cesium.Cartographic.fromCartesian(positions[i]);
    const point2cartographic = Cesium.Cartographic.fromCartesian(positions[i + 1]);
    // 2.设置测地线起点和终点,EllipsoidGeodesic中setEndPoints常与surfaceDistance搭配使用
    const geodesic = new Cesium.EllipsoidGeodesic();
    geodesic.setEndPoints(point1cartographic, point2cartographic);
    // 3. 得到空间中点投影到地球表面的曲面距离
    let s = geodesic.surfaceDistance; // surfaceDistance返回number 单位为m,带小数
    // 4. 考虑两点的高度 利用直角三角形求斜边来求实际距离
    s = Math.sqrt(Math.pow(s, 2) + Math.pow(point2cartographic.height - point1cartographic.height, 2));
    // 5.每段距离求和
    lengthAll += s
  }
  return lengthAll
}

贴地线距离测量

贴地线距离测量需要考虑地形要素,使用Cesium提供的sampleTerrainMostDetailed函数获取每个插值点在地形表面上的采样点高度。为了实现贴地效果,需要进行以下设置:

  • 定义地形提供者
  • 开启地形深度检测
  • 设置clampToGround属性为true
  • 设置实体的高度参考为Cesium.HeightReference.CLAMP_TO_GROUND

下面是具体的代码实现:

const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: await Cesium.createWorldTerrainAsync() // createWorldTerrainAsync是个异步函数
})
viewer.scene.globe.depthTestAgainstTerrain = true; //开启地形深度检测
clampToGround: true,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND

贴地线距离测量的具体步骤如下:

  1. 根据两个点的坐标计算直线距离
  2. 使用Cesium.Math.lerp函数进行插值,每1米插一个点
  3. 使用Cesium.sampleTerrainMostDetailed函数获取每个插值点的地形高度
  4. 计算所有相邻插值点之间的距离并求和

下面是具体的代码实现:

const getTerrainDistance = (position: Cartesian3[]) => {
  return new Promise((resolve)=>{
    let i = position.length - 2
    let startCart3 = position[i] // 起点
    let endCart3 = position[i + 1] // 终点
    let linearDistance = Cesium.Cartesian3.distance(startCart3, endCart3)
    // 插值数量,我这里根据两点间直线距离来插,每1米插一下,插的越多越精准
    let splitNum = Math.floor(linearDistance)
    // 拿到这个直线间每个点的经度纬度然后转为Cartographic的数组包括起点终点
    const positions = []
    let startCartographic = Cesium.Cartographic.fromCartesian(startCart3)
    let endCartographic = Cesium.Cartographic.fromCartesian(endCart3)
    // 不附带起点终点的地形而插值
    let startDegrees = [startCartographic.longitude, startCartographic.latitude] // 经度纬度弧度制
    let endDegrees = [endCartographic.longitude, endCartographic.latitude] // 经度纬度弧度制
    positions.push(new Cesium.Cartographic(startDegrees[0], startDegrees[1]))
    for (let i = 0; i < splitNum; i++) {
      // 分别在经度纬度方向插值
      let x = Cesium.Math.lerp(startDegrees[0], endDegrees[0], i / splitNum)
      let y = Cesium.Math.lerp(startDegrees[1], endDegrees[1], i / splitNum)
      positions.push(new Cesium.Cartographic(x, y))
    }
    // 地形细节采样:传入 目标地形 和 制图坐标插值组(不贴附地形意思就是height = 0) 获取 贴地形的制图坐标插值组,经度纬度高,不过经纬度是弧度制 再计算距离
    Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, positions).then(cartographicArr => {
      // console.log(cartographicArr)
      addDistance(cartographicArr).then(distance=>{
        // console.log(result)
        resolve(distance)
      })
    })
  })
}

/**
 * @description 根据制图坐标计算距离
 * @param {Array.<Cesium.Cartographic>} cartographicArr 制图坐标数组
 * @returns {number} 距离值
 */
const addDistance = (cartographicArr:Cartographic[]) =>{
  return new Promise((resolve)=>{
    let terrainDistance = 0;
    cartographicArr.map((item,index)=>{
      if (index === cartographicArr.length - 1) return
      let nextItem = cartographicArr[index+1]
      let currentPosition = Cesium.Cartesian3.fromRadians(item.longitude, item.latitude, item.height)
      let nextPosition = Cesium.Cartesian3.fromRadians(nextItem.longitude, nextItem.latitude, nextItem.height)
      terrainDistance += Cesium.Cartesian3.distance(currentPosition,nextPosition)
    })
    // 异步完成后返回
    resolve(terrainDistance)
  })
}

绘制线和动态显示距离

在实现距离量测功能时,还需要处理绘制线和动态显示距离的问题。具体实现如下:

  1. 定义一个绘制线的点的数组
  2. 对于鼠标左击事件,判断数组长度,如果长度大于等于3,则调用getTerrainDistance函数计算距离
  3. 对于鼠标移动事件,动态修改数组中的最后一个元素,并实时计算和显示距离

下面是具体的代码实现:

if (polylinePoints.length >=3){
  getTerrainDistance(polylinePoints).then((result:any) => {
    console.log(result)
  })
const getTerrainDistance = (position: Cartesian3[]) => {
  return new Promise((resolve)=>{
    let i = position.length - 3
    let startCart3 = position[i] // 起点
    let endCart3 = position[i + 1] // 终点
    })
}

// 鼠标移动
handler.setInputAction((movement: any) => {
  const worldPoint = viewer.scene.pickPosition(movement.endPosition);
  if (entity) {
    polylinePoints.pop()
    polylinePoints.push(worldPoint)
    // 动态计算距离
    getTerrainDistance(polylinePoints).then((result:any) => {
      // console.log(result)
      distance = result
      viewer.entities.remove(labelEntity); // 标签动态变化
      labelEntity = drawLabel(polylinePoints[polylinePoints.length - 1], result.toFixed(2) + "米");
    })
  }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

异步问题处理

在实现过程中,需要注意处理异步问题。距离的插值计算和最后插值点距离求和都是异步函数,需要使用Promise和then语法来处理。如果嵌套多个异步函数,逻辑处理方式相同。

结果展示

下面是两种距离测量方法的结果展示:

  1. 非动态变化的距离标签
  2. 可以随鼠标移动动态变化的距离标签

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号