吉安感知网项目-前端
shuishen
2026-01-16 efb3b5e84ed4493f82f30ed3c3dcdff0fd5a8083
applications/task-work-order/src/utils/cesium/mapUtil.js
@@ -1,7 +1,8 @@
import * as Cesium from 'cesium'
import * as turf from '@turf/turf'
const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
import store from '@/store'
const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
export const getLngLatDistance = (lat1, lng1, lat2, lng2) => {
   const radLat1 = (lat1 * Math.PI) / 180.0
   const radLat2 = (lat2 * Math.PI) / 180.0
@@ -190,6 +191,66 @@
   const newArr = arr.sort((a, b) => a.distance - b.distance)
   return newArr[0]
}
// 获取多边形内部数据
export const samplePolygonInterior = (positions, samplingDistance) => {
   if (!positions.length) return []
   let minLng = Infinity,
      maxLng = -Infinity
   let minLat = Infinity,
      maxLat = -Infinity
   positions.forEach(pos => {
      minLng = Math.min(minLng, pos.lng)
      maxLng = Math.max(maxLng, pos.lng)
      minLat = Math.min(minLat, pos.lat)
      maxLat = Math.max(maxLat, pos.lat)
   })
   //  将 Cesium 多边形格式转为 Turf 格式
   const turfPolygonCoords = positions.map(pos => [pos.lng, pos.lat])
   turfPolygonCoords.push([positions[0].lng, positions[0].lat]) // 闭合多边形
   const turfPolygon = turf.polygon([turfPolygonCoords])
   //  生成网格点并判断是否在多边形内
   const sampledPoints = []
   for (let lng = minLng; lng <= maxLng; lng += samplingDistance) {
      for (let lat = minLat; lat <= maxLat; lat += samplingDistance) {
         // 创建 Turf 点
         const turfPoint = turf.point([lng, lat])
         // Turf 原生方法判断点是否在多边形内
         if (turf.booleanPointInPolygon(turfPoint, turfPolygon)) {
            sampledPoints.push({
               lng: Number(lng.toFixed(6)),
               lat: Number(lat.toFixed(6)),
               height: 0,
            })
         }
      }
   }
   return sampledPoints
}
// 获取多边形面内的最高点(相对地形高度)
export const getPolygonHighestPoint = async (positions, viewer, samplingDistance = 0.0002) => {
   try {
      const interiorPoints = samplePolygonInterior(positions, samplingDistance, viewer)
      const allPoints = [...positions, ...interiorPoints]
      // 批量获取所有点的地形高度
      const pointsWithTerrain = await getPointPositionsHeight(allPoints, viewer)
      // console.log('所有点的地形高度', pointsWithTerrain)
      // 地形高度(ASL)最高的点
      const highestTerrainPoint = pointsWithTerrain.reduce(
         (max, curr) => (curr.ASL > max.ASL ? curr : max),
         pointsWithTerrain[0]
      )
      // console.log('地形最高点信息:', highestTerrainPoint)
      return highestTerrainPoint
   } catch (error) {
      console.error('获取地形最高点失败:', error)
      return null
   }
}
// 获取当前经纬度地形数据
export const getLnglatAltitude = (longitude, latitude, viewer) => {
@@ -285,31 +346,24 @@
   return new Promise((resolve, reject) => {
      if (!data || !data.length) {
         resolve([])
         return
      }
      // 假设 viewer 已经初始化并且 terrainProvider 是有效的
      const terrainProvider = viewer?.terrainProvider
      // 创建 Cartographic 对象
      const cartographics = data.map(item => {
         const { lng, lat } = item
         return Cesium.Cartographic.fromDegrees(Number(lng), Number(lat))
      })
      // 获取地形数据的Promise
      const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartographics)
      // 使用 Cesium.when 处理 Promise
      promise
         .then(function (updatedPositions) {
            // updatedPositions 是一个数组,包含更新后的 Cartographic 对象
            //  是一个数组,包含更新后的 Cartographic 对象
            const newPosition = updatedPositions.map((item, index) => {
               const longitude = Cesium.Math.toDegrees(item.longitude)
               const latitude = Cesium.Math.toDegrees(item.latitude)
               let pointData = {
                  ...data[index],
                  longitude,
@@ -317,18 +371,14 @@
                  ASL: Number(item.height),
                  customHeight: Number(item.height),
               }
               if (droneHeight) pointData.TH = Number(item.height) + Number(droneHeight)
               return pointData
            })
            resolve(newPosition)
            // 在这里,你可以使用 height 值进行后续操作
         })
         .catch(function (error) {
            // console.error('获取高程时发生错误:', error);
            reject(error)
            console.log('获取高程时发生错误:', error)
            resolve(data.map(item => ({ ...item, ASL: 0, customHeight: 0, longitude: item.lng, latitude: item.lat })))
         })
   })
}
@@ -410,11 +460,15 @@
   if (!Array.isArray(positionsData) || positionsData.length === 0) return
   // 如果是一个点,加两个点方便后续生成外包围盒
   if (positionsData.length === 1){
      const [lon,lat,height] = positionsData[0]
      positionsData = [[lon+0.001,lat+0.001,height],[lon-0.001,lat-0.001,height],[lon,lat,height]]
   if (positionsData.length === 1) {
      const [lon, lat, height = 0] = positionsData[0]
      positionsData = [
         [lon + 0.001, lat + 0.001, height],
         [lon - 0.001, lat - 0.001, height],
         [lon, lat, height],
      ]
   }
   const positions = positionsData.map(([lon, lat, height]) =>
   const positions = positionsData.map(([lon, lat, height = 0]) =>
      Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat), Number(height || 0))
   )
@@ -439,7 +493,7 @@
         range: boundingSphere.radius * multiple,
      },
      complete: () => {
         // 飞行完成后检查相机高度是否小于最高点,如果小于直接飞到最高点
         // 飞行完成后检查相机高度是否小于最高点
         const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height
         if (cameraHeight < maxHeight) {
            viewer.camera.flyTo({
@@ -529,7 +583,7 @@
// 辅助函数:创建旋转文字的画布
export function createRotatedTextCanvas(time) {
   const prefix = '预计'
   const prefix = '预计到达'
   const suffix = '分钟'
   const canvas = document.createElement('canvas')
   const context = canvas.getContext('2d')
@@ -537,7 +591,9 @@
   context.font = 'bold 16px Source Han Sans CN'
   // 测量各部分文本宽度
   const prefixWidth = context.measureText(prefix).width
   const timeWidth = context.measureText(time).width
   let timeWidth = context.measureText(time).width
   const timeWidthMore = `${time >= 10 ? 6 : 0}`
   timeWidth = timeWidth + Number(timeWidthMore)
   const suffixWidth = context.measureText(suffix).width
   const totalWidth = prefixWidth + timeWidth + suffixWidth
   const textHeight = 20 // 估算高度
@@ -550,17 +606,116 @@
   context.textAlign = 'left' // 改为左对齐,方便分段绘制
   // 清除画布
   context.clearRect(0, 0, canvas.width, canvas.height)
   // 绘制“预计”(白色)
   // 绘制“预计到达”(白色)
   context.fillStyle = '#FFFFFF'
   context.fillText(prefix, 10, 15)
   // 绘制时间(蓝色)
   context.font = 'bold 30px Source Han Sans CN'
   context.font = 'bold 24px Source Han Sans CN'
   context.fillStyle = '#1EE7E7' // 亮蓝色
   context.fillText(time, 10 + prefixWidth, 12)
   // 设置宽度
   context.textBaseline = 'middle'
   context.fillText(time, 10 + prefixWidth, 14)
   // 绘制“分钟”(白色)
   context.font = 'bold 16px Source Han Sans CN'
   context.fillStyle = '#FFFFFF'
   context.fillText(suffix, 20 + prefixWidth + timeWidth, 15)
   context.fillText(suffix, 14 + prefixWidth + timeWidth, 15)
   return canvas
}
export const saveCurrentCameraPosition = viewer => {
   const position = viewer?.camera.position.clone() // 相机位置(Cartesian3)
   const heading = viewer?.camera.heading // 航向角(弧度)
   const pitch = viewer?.camera.pitch // 俯仰角(弧度)
   const roll = viewer?.camera.roll // 翻滚角(弧度)
   store.commit('setCameraPosition', {
      duration: 0,
      destination: position,
      orientation: { heading, pitch, roll },
   })
}
// 连接地面线
export function getDockPolyLine(data, viewer) {
   if (!data.longitude || !data.latitude) return {}
   return {
      positions: new Cesium.CallbackProperty(() => {
         const pointPosition = Cesium.Cartesian3.fromDegrees(+data.longitude, +data.latitude, +data.height)
         if (!pointPosition) return []
         // 获取点的位置
         const cartographic = Cesium.Cartographic.fromCartesian(pointPosition)
         // 获取地形高度
         const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0
         // 创建地面点位置
         const groundPosition = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, terrainHeight)
         return [pointPosition, groundPosition]
      }, false),
      width: 0.5,
      material: Cesium.Color.WHITE,
   }
}
// 飞向一个机巢中心
export const flyDockCenter = (viewer, info) => {
   const { longitude = 115, latitude = 28, height = 0 } = info
   const position = Cesium.Cartesian3.fromDegrees(+longitude, +latitude, +height)
   const droneEntity = viewer?.entities.add({ position: position, point: { pixelSize: 0 } })
   viewer?.flyTo(droneEntity, {
      offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-60), 22000),
      duration: 0.5,
      complete: () => {
         viewer?.entities.remove(droneEntity)
      },
   })
}
// 在固定的经度上获得高度
export function getHeightOnFixedLonLat(ray, pointParams, ellipsoid = Cesium.Ellipsoid.WGS84) {
   const { longitude: lon, latitude: lat } = pointParams
   // 1. 地表点(高度=0)
   const p0 = Cesium.Cartesian3.fromDegrees(lon, lat, 0, ellipsoid)
   // 2. 该点处的法线方向(up 向量,单位向量)
   const up = ellipsoid.geodeticSurfaceNormal(p0, new Cesium.Cartesian3())
   // 3. 射线起点和方向
   const r0 = ray.origin // 相机位置
   const rd = ray.direction // 射线方向(已归一化?不一定,但不影响)
   // 4. 解:求直线 L(t) = p0 + t * up 与射线 R(s) = r0 + s * rd 的最近点
   // 使用向量公式求两条直线的最近点参数 t
   const w0 = Cesium.Cartesian3.subtract(p0, r0, new Cesium.Cartesian3())
   const a = Cesium.Cartesian3.dot(up, up) // = 1(因为 up 是单位向量)
   const b = Cesium.Cartesian3.dot(up, rd)
   const c = Cesium.Cartesian3.dot(rd, rd) // = |rd|^2
   const d = Cesium.Cartesian3.dot(up, w0)
   const e = Cesium.Cartesian3.dot(rd, w0)
   const denom = a * c - b * b
   let t
   if (Math.abs(denom) < Cesium.Math.EPSILON10) {
      // 两直线平行,取 t = 0
      t = 0
   } else {
      t = (b * e - c * d) / denom
   }
   // 5. 得到高度线上对应的点
   const resultPoint = Cesium.Cartesian3.add(
      p0,
      Cesium.Cartesian3.multiplyByScalar(up, t, new Cesium.Cartesian3()),
      new Cesium.Cartesian3()
   )
   // 6. 转回 Cartographic 获取精确高度
   const carto = Cesium.Cartographic.fromCartesian(resultPoint, ellipsoid)
   return {
      cartesian: resultPoint,
      height: carto.height,
      longitude: Cesium.Math.toDegrees(carto.longitude),
      latitude: Cesium.Math.toDegrees(carto.latitude),
   }
}