吉安感知网项目-前端
shuishen
2026-01-07 3a3cbd22dac6945039ba44e4ca14b1adaaaaa7ed
applications/drone-command/src/utils/cesium/createRouteLine.js
@@ -2,17 +2,16 @@
import { Decimal } from 'decimal.js'
import _, { cloneDeep, throttle } from 'lodash'
import { v4 as uuidv4 } from 'uuid'
import { render } from 'vue'
import { analysisPointLineKmz, handlePointListForKmz } from '@/views/newRoutePlan/Waypoint/pointWayLineUtils'
import HistoryDronePopup from '@/components/DeviceJobDetails/HistoryDronePopup.vue'
import { getNewPolygonData } from '@/views/RoutePlan/PlanarAirLine/planarRouteUtils'
import {
   getNewPolygonData,
} from '@/views/newRoutePlan/routeUtils'
import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
import { ArrowLineMaterialProperty, LineTrailMaterial, PolylineGlowMaterial, } from '@/utils/cesium/Material'
import { ArrowLineMaterialProperty, LineTrailMaterial, PolylineGlowMaterial } from '@/utils/cesium/Material'
import CreateFrustum from '@/utils/cesium/frustum/CreateFrustum'
import { ElMessage } from 'element-plus'
@@ -23,61 +22,21 @@
import aircraftGltf from '@/assets/gltf/aircraft.gltf'
import newNumPoint from '@/assets/images/newStartPoint.png'
import {
   getPolyLine,
} from '@/views/RoutePlan/PointAirLine/pointWayLineUtils'
import { getPolyLine } from '@/views/newRoutePlan/Waypoint/pointWayLineUtils'
import store from '@/store/index'
import newStartPoint from '@/assets/images/newStartPoint.png'
import newStartPointMore from '@/assets/images/newStartPointMore.png'
import {
   arrowLineMaterialProperty, arrowLineMaterialPropertyGray, arrowLineMaterialPropertyGrayT,
   arrowLineMaterialPropertyGreen,
   arrowLineMaterialPropertyOrange,
} from '@/hooks/useMorePointLine/useMorePointLine'
let arrowLineMaterialProperty = new ArrowLineMaterialProperty({
   color: new Cesium.Color(128 / 255, 215 / 255, 255 / 255, 1),
   directionColor: new Cesium.Color(1, 1, 1, 1),
   outlineColor: new Cesium.Color(1, 1, 1, 1),
   outlineWidth: 0,
   speed: 5
})
let arrowLineMaterialPropertyOrange = new ArrowLineMaterialProperty({
   color: new Cesium.Color(255 / 255, 185 / 255, 58 / 255, 1),
   directionColor: new Cesium.Color(1, 1, 1, 1),
   outlineColor: new Cesium.Color(1, 1, 1, 1),
   outlineWidth: 0,
   speed: 5,
})
let arrowLineMaterialPropertyGreen = new ArrowLineMaterialProperty({
   color: new Cesium.Color(6 / 255, 217 / 255, 87 / 255, 1),
   directionColor: new Cesium.Color(1, 1, 1, 1),
   outlineColor: new Cesium.Color(1, 1, 1, 1),
   outlineWidth: 0,
   speed: 5,
})
// 记录index
let indexLog = 0
// 颜色
function getLineColor(lineNumber) {
   // 颜色顺序: 蓝(0), 黄(1), 绿(2)
   const colors = [arrowLineMaterialProperty, arrowLineMaterialPropertyOrange, arrowLineMaterialPropertyGreen]
   // 计算颜色索引 (从0开始)
   const colorIndex = (lineNumber - 1) % 3
   // 返回对应颜色
   return colors[colorIndex]
}
let runningLineMaterial = new LineTrailMaterial({
   color: Cesium.Color.fromCssColorString('#1FFF69'),
   opacity: 1,
   speed: 10,
})
let runningTextColor = Cesium.Color.fromCssColorString('#1FFF69')
let runningTextOffset = new Cesium.Cartesian2(0, -32)
let pendingLineMaterial = new PolylineGlowMaterial({
   color: Cesium.Color.fromCssColorString('#FFB81D'),
})
let pendingTextColor = Cesium.Color.fromCssColorString('#FFB81D')
let pendingTextOffset = new Cesium.Cartesian2(0, 32)
let MapPopUpBox = HistoryDronePopup
function getZoomFactor (camerasInfo, type) {
@@ -95,10 +54,108 @@
   return zoom_factor
}
// 记录index
let indexLog = 0
// 颜色
function getPendingExecutionLineColor (lineNumber) {
   // 颜色顺序: 蓝(灰), 更灰
   const colors = [arrowLineMaterialPropertyGray, arrowLineMaterialPropertyGrayT]
   // 计算颜色索引 (从0开始)
   const colorIndex = (lineNumber - 1) % 2
   // 返回对应颜色
   return colors[colorIndex]
}
function getLineColor (lineNumber) {
   // 颜色顺序: 蓝(0), 黄(1), 绿(2)
   const colors = [arrowLineMaterialProperty, arrowLineMaterialPropertyOrange, arrowLineMaterialPropertyGreen]
   // 计算颜色索引 (从0开始)
   const colorIndex = (lineNumber - 1) % 3
   // 返回对应颜色
   return colors[colorIndex]
}
function getBase64Image (imgUrl) {
   const img = new Image()
   const canvas = document.createElement('canvas')
   const ctx = canvas.getContext('2d')
   return new Promise(resolve => {
      img.onload = function () {
         canvas.width = img.width
         canvas.height = img.height
         ctx.drawImage(img, 0, 0)
         const base64 = canvas.toDataURL('image/png')
         resolve(base64)
      }
      img.src = imgUrl
   })
}
async function createCombinedSVG (index, billboardImageUrl) {
   const base64Image = await getBase64Image(billboardImageUrl)
   const svg = `
     <svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
      <image href="${base64Image}" x="10" y="10" width="80" height="80"/>
      <text x="50" y="54" font-size="26" fill="white" text-anchor="middle" dominant-baseline="middle">
        ${index + 1}
      </text>
     </svg>
   `
   return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
}
function findHeightByCoordinates (data, targetLng, targetLat) {
   // 展平二维数组,然后查找第一个匹配的点
   const allPoints = data.flat()
   const point = allPoints.find(point =>
      point.longitude === targetLng && point.latitude === targetLat
   )
   return point ? point.height : null
}
function renderingMachineNest (options) {
   const { viewer, dockTransformPosition, dockPosition, data } = options
   viewer.entities.add({
      name: 'work-drone-route-point-dock',
      position: dockTransformPosition,
      billboard: {
         image: endingOnlineImg,
         width: 50,
         height: 50,
         disableDepthTestDistance: Number.POSITIVE_INFINITY,
      },
      customData: {
         data,
      },
   })
   viewer.entities.add({
      name: 'work-drone-route--point-polyline',
      position: dockTransformPosition,
      polyline: getPolyLine(viewer, { value: [dockPosition] }, 0),
   })
}
function renderingStartingPoint (options) {
   const { viewer, startPosition, data } = options
   viewer.entities.add({
      name: 'work-drone-route-point-start',
      position: startPosition,
      billboard: {
         image: rwqfdImg,
         width: 50,
         height: 50,
         disableDepthTestDistance: Number.POSITIVE_INFINITY,
      },
      customData: {
         data,
      },
   })
}
export default class CreateRouteLine {
   /**
    *
    *
    * @param {*} options 参数
    */
   constructor(options) {
@@ -119,10 +176,9 @@
   /**
    * 初始化viewer
    * @param {*} viewer
    * @param {*} viewer
    */
   initCreateRoute (viewer) {
      indexLog = 0
      this.viewer = viewer
   }
@@ -138,24 +194,27 @@
         const currentWaypointIndex = output?.ext['current_waypoint_index']
         this.updataMultiNestData(sn, {
            currentWaypointIndex
            currentWaypointIndex,
         })
      }
   }
   /**
    * 请求航线文件数据并解析处理
    * 请求航线文件数据并绘制解析处理
    * @param {*} data 包含文件url等信息
    * @param {*} dronePosition 无人机位置
    * @returns
    * @param {*} dockPosition 无人机位置
    * @returns
    */
   async parsingFiles (data, dronePosition = null, isShowDock = false, isShowPointBillboard = true) {
   async parsingFiles (data, dockPosition = null, isShowDock = false, isShowPointBillboard = true) {
      console.log(data,'888')
      const { url, wayline_type, device_sn } = data
      if (!url) return dronePosition != null ? {
         dronePosition: [{ lng: dronePosition.longitude, lat: dronePosition.latitude, alt: dronePosition.height }]
      } : {}
      indexLog = 0
      if (!url)
         return dockPosition != null
            ? {
               dronePosition: [{ lng: dockPosition.longitude, lat: dockPosition.latitude, alt: dockPosition.height }],
            }
            : {}
      this.updataMultiNestData(device_sn)
      const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`)
@@ -178,16 +237,16 @@
      const missionConfig = removeTextKey(waylinesXmlJson.missionConfig)
      const waylinesXMLObj = removeTextKey(waylinesXmlJson.Folder)
      if (templateType === "mapping3d") {
      if (templateType === 'mapping3d') {
         if (!waylinesXMLObj[0].Placemark.length) return ElMessage.error('没有航线点位')
      } else {
         if (!waylinesXMLObj.Placemark.length) return ElMessage.error('没有航线点位')
      }
      if ([2, 4, 5, 6, 7, 10].includes(Number(wayline_type))) {
         if (templateType === "mapping3d") {
      if ([2, 4, 5, 7, 10].includes(Number(wayline_type))) {
         if (templateType === 'mapping3d') {
            let morePolygonData = waylinesXMLObj.map(i => ({
               data: i.Placemark
               data: i.Placemark,
            }))
            let newPolygonData = getNewPolygonData(morePolygonData)
@@ -214,7 +273,7 @@
               waylinesXMLObj[0],
               missionConfig,
               templateXmlJson,
               dronePosition,
               dockPosition,
               data,
               startPoint,
               isShowDock,
@@ -227,7 +286,7 @@
            waylinesXMLObj,
            missionConfig,
            templateXmlJson,
            dronePosition,
            dockPosition,
            data,
            startPoint,
            isShowDock,
@@ -238,7 +297,7 @@
         return this.drawPointRoute(
            waylinesXMLObj,
            missionConfig,
            dronePosition,
            dockPosition,
            data,
            startPoint,
            isShowDock,
@@ -248,50 +307,231 @@
      }
   }
   // 拆分航线
   async splitWayLine (data, dronePosition = null, isShowDock = false, isShowPointBillboard = true) {
      const {
         pointPlacemark,
         polygonList,
         pointList,
         templateType,
         startPoint,
         execute_height_mode,
         auto_flight_speed,
         take_off_security_height,
         buffer_distance_meters,
         missionConfig,
         waylinesXMLObjR,
      } = await analysisPointLineKmz(`${data.domain_url}${data.object_key}`)
      const { positionArray, filePositions } = this.disposeData(
         waylinesXMLObjR,
         missionConfig,
         null,
         data,
         startPoint,
         0
      )
      let bigPointList = handlePointListForKmz({
         placemark: pointPlacemark,
         startPoint: startPoint,
         execute_height_mode: execute_height_mode,
         auto_flight_speed: auto_flight_speed,
      })
      // 路径线
      let entityConfig
      bigPointList.shift()
      let flyPositions = bigPointList.map(i => [Number(i.longitude), Number(i.latitude), Number(i.height)])
      // 渲染多个点
      let firstTopPosition = null
      let resultPosotions = []
      await Promise.all(data.wayline_file_list.map(async (item, index) => {
         // 小航线渲染线
         const kmzUrl = import.meta.env.VITE_APP_AIRLINE_URL + item.object_key
         const { pointList } = await analysisPointLineKmz(kmzUrl)
         resultPosotions.push(pointList)
      })).then(res => {
         // 目的是区分大航线点属于哪条小航线
         bigPointList.forEach(bigObj => {
            let foundIndex = -1
            // 遍历 resultPositions 查找匹配项
            resultPosotions.some((subArray, subIndex) => {
               const found = subArray.some(obj =>
                  obj.latitude === bigObj.latitude &&
                  obj.longitude === bigObj.longitude
               )
               if (found) {
                  foundIndex = subIndex
                  return true // 找到就停止搜索
               }
               return false
            })
            // 如果找到了,给 bigPointList 的对象添加标记
            if (foundIndex !== -1) {
               bigObj.log = foundIndex // 或 bigObj.index = foundIndex
            }
         })
         console.log(bigPointList, '查看结果值') // 现在 bigPointList 会有 log 属性
         bigPointList.map(async (i, index) => {
            firstTopPosition = i
            const position = Cesium.Cartesian3.fromDegrees(Number(i.longitude), Number(i.latitude), Number(findHeightByCoordinates(resultPosotions, i.longitude, i.latitude)))
            const combinedImage = await createCombinedSVG(index, i.log % 2 ? newStartPoint : newStartPointMore)
            entityConfig = {
               name: 'work-drone-route-point-split',
               position: position,
               billboard: {
                  disableDepthTestDistance: Number.POSITIVE_INFINITY,
                  image: combinedImage,
                  width: 50,
                  height: 50,
                  verticalOrigin: Cesium.VerticalOrigin.CENTER,
                  horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
               }
            }
            // 只在第一个点添加 label
            if (index === 0) {
               entityConfig.label = {
                  text: data.task_name || '',
                  font: 'bold 16px sans-serif',
                  fillColor: data.status === 2 ? runningTextColor : pendingTextColor,
                  pixelOffset: data.status === 2 ? runningTextOffset : pendingTextOffset,
                  style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                  outlineWidth: 2,
                  outlineColor: Cesium.Color.BLACK,
                  disableDepthTestDistance: Number.POSITIVE_INFINITY,
               }
            }
            this.viewer.entities.add(entityConfig)
         })
      })
      // 渲染航线
      data.wayline_file_list.map(async (item, index) => {
         // 小航线渲染线
         const kmzUrl = import.meta.env.VITE_APP_AIRLINE_URL + item.object_key
         const { pointList } = await analysisPointLineKmz(kmzUrl)
         // if (curRouteLineData.value.droneData && splitLines.length === 1) {
         //    // 把pointList的第一个点替换为无人机位置
         //    pointList[0] = curRouteLineData.value.droneData[0]
         // }
         if (take_off_security_height + pointList[0].height > firstTopPosition.height) {
            // 安全高度+机巢高度 > 第一个点高度
            const positionTest = {
               longitude: pointList[0].longitude,
               latitude: pointList[0].latitude,
               height: take_off_security_height + pointList[0].height,
            }
            const positionTest1 = {
               longitude: pointList[1].longitude,
               latitude: pointList[1].latitude,
               height: take_off_security_height + pointList[0].height,
            }
            pointList.splice(1, 0, positionTest)
            // 往数组中插入元素
            pointList.splice(2, 0, positionTest1)
            // console.log(pointList, 'pointList')
         } else if (take_off_security_height + pointList[0].height < firstTopPosition.height) {
            const positionTest = {
               longitude: pointList[0].longitude,
               latitude: pointList[0].latitude,
               height: firstTopPosition.height,
            }
            pointList.splice(1, 0, positionTest)
         }
         const routePositions = pointList.map(i =>
            Cesium.Cartesian3.fromDegrees(Number(i.longitude), Number(i.latitude), Number(i.height))
         )
         this.viewer.entities.add({
            name: 'work-drone-route-line-split',
            polyline: {
               width: 4,
               positions: routePositions,
               material: data.status === 1 ? arrowLineMaterialPropertyGray : getLineColor(index + 1),
               clampToGround: false,
            },
         })
      })
         // 落点线
         ; (bigPointList || [])?.forEach((item, index) => {
            const topPosition = Cesium.Cartesian3.fromDegrees(
               Number(item.longitude),
               Number(item.latitude),
               Number(item.height)
            )
            let setting = {
               name: 'work-drone-route--planar-polyline-split',
               position: topPosition,
               polyline: getPolyLine(this.viewer, { value: bigPointList }, index),
            }
            this.viewer.entities.add(setting)
         })
      return {
         entity: entityConfig,
         routeLinePositions: positionArray,
         filePositions:
            dronePosition != null
               ? [{ lng: dronePosition.longitude, lat: dronePosition.latitude, alt: dronePosition.height }, ...filePositions]
               : filePositions,
      }
   }
   /**
    * 绘制面状航线
    * @param {*} lineObj 航线文件中的
    * @param {*} missionConfig 航线文件中的
    * @param {*} templateXmlJson 航线文件中的
    * @param {*} dronePosition 无人机位置
    * @param {*} data
    * @param {*} startPoint
    * @param {*} isShowPointBillboard 是否显示,航线起飞点,终点等标注
    * @returns
    * @param {*} dockPosition 无人机位置
    * @param {*} data
    * @param {*} startPoint
    * @returns
    */
   async drawPlanarRoute (lineObj, missionConfig, templateXmlJson, dronePosition, data, startPoint, isShowDock, isShowPointBillboard, wayline_type) {
      const { positionArray, filePositions } = this.disposeData(lineObj, missionConfig, dronePosition, data, startPoint, wayline_type)
   async drawPlanarRoute (
      lineObj,
      missionConfig,
      templateXmlJson,
      dockPosition,
      data,
      startPoint,
      isShowDock,
      isShowPointBillboard,
      wayline_type
   ) {
      const { positionArray, filePositions } = this.disposeData(
         lineObj,
         missionConfig,
         dockPosition,
         data,
         startPoint,
         wayline_type
      )
      const { device_sn } = data
      const droneTransformPosition = Cesium.Cartesian3.fromDegrees(
         Number(dronePosition.longitude),
         Number(dronePosition.latitude),
         Number(dronePosition.height),
      const dockTransformPosition = Cesium.Cartesian3.fromDegrees(
         Number(dockPosition.longitude),
         Number(dockPosition.latitude),
         Number(dockPosition.height)
      )
      let coordArr = null
      const placemark = templateXmlJson.Folder?.Placemark
      // 取出点位
      const coordinates =
         placemark.Polygon?.outerBoundaryIs.LinearRing.coordinates?.[
            '#text'
         ]?.split('\n') || []
      const coordinates = placemark.Polygon?.outerBoundaryIs.LinearRing.coordinates?.['#text']?.split('\n') || []
      // 数组转换
      coordArr = coordinates.map((coordinate) =>
      coordArr = coordinates.map(coordinate =>
         coordinate
            .replace(/\s+/g, '')
            .split(',')
            .map((v) => Number(v)),
            .map(v => Number(v))
      )
      // 获取当前经纬度绝对高度
      let newCoordArr = coordArr.map(item => ([item[0], item[1], 0]))
      let newCoordArr = coordArr.map(item => [item[0], item[1], 0])
      let planarRouteEntity = this.viewer.entities.add({
         name: 'work-drone-route-planar-polyline',
         polyline: {
            width: 3,
            positions: positionArray,
@@ -300,30 +540,30 @@
         },
         customData: {
            data
         }
            data,
         },
      })
      let droppointPositions = (filePositions || [])?.map(i => ({
         longitude: Number(i.lng),
         latitude: Number(i.lat),
         height: Number(i.alt),
      }));
      }))
      // 落点线
      (droppointPositions || [])?.forEach((item, index) => {
         const topPosition = Cesium.Cartesian3.fromDegrees(
            Number(item.longitude),
            Number(item.latitude),
            Number(item.height)
         )
         let setting = {
            name: 'work-drone-route-planar-polyline',
            position: topPosition,
            polyline: getPolyLine(this.viewer, { value: droppointPositions }, index),
         }
         this.viewer.entities.add(setting)
      })
         // 落点线
         ; (droppointPositions || [])?.forEach((item, index) => {
            const topPosition = Cesium.Cartesian3.fromDegrees(
               Number(item.longitude),
               Number(item.latitude),
               Number(item.height)
            )
            let setting = {
               name: 'work-drone-route--planar-polyline',
               position: topPosition,
               polyline: getPolyLine(this.viewer, { value: droppointPositions }, index),
            }
            this.viewer.entities.add(setting)
         })
      // 面状航线第一个点突出展示
      isShowPointBillboard && this.startingIncreasePoint(positionArray)
@@ -340,9 +580,7 @@
         name: 'work-drone-route-planar-border',
         polyline: {
            positions: Cesium.Cartesian3.fromDegreesArrayHeights(
               cloneCoordArr.flat(),
            ),
            positions: Cesium.Cartesian3.fromDegreesArrayHeights(cloneCoordArr.flat()),
            width: 4,
            material: new Cesium.PolylineOutlineMaterialProperty({
               color: new Cesium.Color.fromBytes(74, 138, 233),
@@ -357,11 +595,8 @@
      // 绘制面状地块--------------------
      this.viewer.entities.add({
         name: 'work-drone-route-planar-polygon',
         polygon: {
            hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(
               newCoordArr.flat(),
            ),
            hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(newCoordArr.flat()),
            material: new Cesium.Color.fromBytes(75, 159, 221, 100),
            outline: true,
            outlineColor: new Cesium.Color.fromBytes(35, 85, 216, 255),
@@ -384,7 +619,7 @@
            label: {
               text: data.job_name || '',
               font: 'bold 16px YouSheBiaoTiHei',
               font: 'bold 16px sans-serif',
               fillColor: Cesium.Color.WHITE,
               pixelOffset: new Cesium.Cartesian2(0, -50),
               style: Cesium.LabelStyle.FILL_AND_OUTLINE,
@@ -395,64 +630,31 @@
         })
      } else {
         // 显示机巢位置
         if (isShowDock) {
            // 起点
            this.viewer.entities.add({
               name: 'work-drone-route-point-dock',
               position: droneTransformPosition,
               billboard: {
                  image: endingOnlineImg,
                  width: 50,
                  height: 50,
                  disableDepthTestDistance: Number.POSITIVE_INFINITY,
               },
               customData: {
                  data
               }
            })
            this.viewer.entities.add({
               name: 'work-drone-route--point-polyline',
               position: droneTransformPosition,
               polyline: getPolyLine(this.viewer, { value: [dronePosition] }, 0),
            })
         }
         const startPosition = Cesium.Cartesian3.fromDegrees(
            Number(filePositions[0].lng),
            Number(filePositions[0].lat),
            Number(filePositions[0].alt)
         )
         isShowDock && renderingMachineNest({
            viewer: this.viewer,
            dockTransformPosition,
            dockPosition,
            data
         })
         // 起点
         isShowPointBillboard && this.viewer.entities.add({
            name: 'work-drone-route-point-start',
            position: startPosition,
            billboard: {
               image: rwqfdImg,
               width: 50,
               height: 50,
               disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
            customData: {
               data
            }
         isShowPointBillboard && renderingStartingPoint({
            viewer: this.viewer,
            startPosition: Cesium.Cartesian3.fromDegrees(
               Number(filePositions[0].lng),
               Number(filePositions[0].lat),
               Number(filePositions[0].alt)
            ),
            data
         })
      }
      return {
         entity: planarRouteEntity,
         routeLinePositions: positionArray,
         filePositions: dronePosition != null ? [
            { lng: dronePosition.longitude, lat: dronePosition.latitude, alt: dronePosition.height },
            ...filePositions
         ] : filePositions
         filePositions:
            dockPosition != null
               ? [{ lng: dockPosition.longitude, lat: dockPosition.latitude, alt: dockPosition.height }, ...filePositions]
               : filePositions,
      }
   }
@@ -472,9 +674,7 @@
      let setting = {
         name: 'work-drone-route-planar-start',
         position: positions,
         billboard: {
            image: this.planarBillboard('#2D8CF0'),
            pixelOffset: new Cesium.Cartesian2(146, 72),
@@ -489,35 +689,48 @@
    * 绘制点航线
    * @param {*} lineObj 航线文件中的
    * @param {*} missionConfig 航线文件中的
    * @param {*} dronePosition 无人机位置
    * @param {*} dockPosition 无人机位置
    * @param {*} data 任务信息,航线地址等
    * @param {*} startPoint
    * @param {*} startPoint
    * @param {*} isShowDock 是否显示机巢位置
    * @param {*} isShowPointBillboard 是否显示,航线起飞点,终点等标注
    * @returns
    * @returns
    */
   async drawPointRoute (lineObj, missionConfig, dronePosition, data, startPoint, isShowDock, isShowPointBillboard, wayline_type) {
      const { positionArray, filePositions } = this.disposeData(lineObj, missionConfig, dronePosition, data, startPoint, wayline_type)
      const droneTransformPosition = Cesium.Cartesian3.fromDegrees(
         Number(dronePosition.longitude),
         Number(dronePosition.latitude),
         Number(dronePosition.height),
   async drawPointRoute (
      lineObj,
      missionConfig,
      dockPosition,
      data,
      startPoint,
      isShowDock,
      isShowPointBillboard,
      wayline_type
   ) {
      const { positionArray, filePositions } = this.disposeData(
         lineObj,
         missionConfig,
         dockPosition,
         data,
         startPoint,
         wayline_type
      )
      const dockTransformPosition = Cesium.Cartesian3.fromDegrees(
         Number(dockPosition.longitude),
         Number(dockPosition.latitude),
         Number(dockPosition.height)
      )
      // 路径线
      let polyline
      indexLog = indexLog + 1
      if (this.type === 'clusterScheduling') {
         filePositions.forEach((item, index) => {
            let position = Cesium.Cartesian3.fromDegrees(item.lng, item.lat, item.alt)
            if (index === 0) {
               this.viewer.entities.add({
                  name: 'work-drone-route-point-start',
                  position,
                  billboard: {
                     image: rwqfdImg,
                     outlineWidth: 0,
@@ -525,10 +738,9 @@
                     height: 50,
                     disableDepthTestDistance: Number.POSITIVE_INFINITY,
                  },
                  label: {
                     text: data.job_name || '',
                     font: 'bold 16px YouSheBiaoTiHei',
                     font: 'bold 16px sans-serif',
                     fillColor: data.type === 2 ? runningTextColor : pendingTextColor,
                     pixelOffset: data.type === 2 ? runningTextOffset : pendingTextOffset,
                     style: Cesium.LabelStyle.FILL_AND_OUTLINE,
@@ -540,9 +752,7 @@
            } else if (index === filePositions.length - 1) {
               this.viewer.entities.add({
                  name: 'work-drone-route-point-end',
                  position,
                  billboard: {
                     image: newEndPointImg,
                     outlineWidth: 0,
@@ -556,9 +766,7 @@
            } else {
               this.viewer.entities.add({
                  name: 'work-drone-route-point-approach',
                  position,
                  billboard: {
                     image: newNumPoint,
                     width: 50,
@@ -577,96 +785,56 @@
               })
            }
         })
         polyline = this.viewer.entities.add({
            name: 'work-drone-route-point-polyline',
            polyline: {
               width: 4,
               positions: positionArray,
               material: data.type === 2 ? runningLineMaterial : pendingLineMaterial,
               material: data.type === 2 ? getLineColor(indexLog) : arrowLineMaterialPropertyGray,
               clampToGround: false,
            }
            },
         })
      } else {
         // 显示机巢位置
         if (isShowDock) {
            // 起点
            this.viewer.entities.add({
               name: 'work-drone-route-point-dock',
               position: droneTransformPosition,
               billboard: {
                  image: endingOnlineImg,
                  width: 50,
                  height: 50,
                  disableDepthTestDistance: Number.POSITIVE_INFINITY,
               },
               customData: {
                  data
               }
            })
            this.viewer.entities.add({
               name: 'work-drone-route--point-polyline',
               position: droneTransformPosition,
               polyline: getPolyLine(this.viewer, { value: [dronePosition] }, 0),
            })
         }
         const startPosition = Cesium.Cartesian3.fromDegrees(
            Number(filePositions[0].lng),
            Number(filePositions[0].lat),
            Number(filePositions[0].alt)
         )
         isShowDock && renderingMachineNest({
            viewer: this.viewer,
            dockTransformPosition,
            dockPosition
         })
         // 起点
         isShowPointBillboard && this.viewer.entities.add({
            name: 'work-drone-route-point-start',
            position: startPosition,
            billboard: {
               image: rwqfdImg,
               width: 50,
               height: 50,
               disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
            customData: {
               data
            }
         isShowPointBillboard && renderingStartingPoint({
            viewer: this.viewer,
            startPosition: Cesium.Cartesian3.fromDegrees(
               Number(filePositions[0].lng),
               Number(filePositions[0].lat),
               Number(filePositions[0].alt)
            ),
            data
         })
         // 终点
         isShowPointBillboard && this.viewer.entities.add({
            name: 'work-drone-route-point-end',
            position: positionArray[positionArray.length - 1],
            billboard: {
               image: new Cesium.ConstantProperty(endPointImg),
               width: 30,
               height: 30,
               verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐
            },
         })
         isShowPointBillboard &&
            this.viewer.entities.add({
               name: 'work-drone-route-point-end',
               position: positionArray[positionArray.length - 1],
               billboard: {
                  image: new Cesium.ConstantProperty(endPointImg),
                  width: 30,
                  height: 30,
                  verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐
               },
            })
         console.log(data.status, '失败')
         polyline = this.viewer.entities.add({
            name: 'work-drone-route-point-polyline',
            polyline: {
               width: 4,
               positions: positionArray,
               material: getLineColor(indexLog),
               material: data.status === 5 ? arrowLineMaterialPropertyGray : getLineColor(indexLog),
               clampToGround: false,
            },
            customData: {
               data
            }
               data,
            },
         })
      }
@@ -674,47 +842,45 @@
         longitude: Number(i.lng),
         latitude: Number(i.lat),
         height: Number(i.alt),
      }));
      }))
      // 落点线
      (droppointPositions || [])?.forEach((item, index) => {
         const topPosition = Cesium.Cartesian3.fromDegrees(
            Number(item.longitude),
            Number(item.latitude),
            Number(item.height)
         )
         // 落点线
         ; (droppointPositions || [])?.forEach((item, index) => {
            const topPosition = Cesium.Cartesian3.fromDegrees(
               Number(item.longitude),
               Number(item.latitude),
               Number(item.height)
            )
         let setting = {
            name: 'work-drone-route-point-polyline',
            position: topPosition,
            polyline: getPolyLine(this.viewer, { value: droppointPositions }, index),
         }
         this.viewer.entities.add(setting)
      })
            let setting = {
               name: 'work-drone-route--point-polyline',
               position: topPosition,
               polyline: getPolyLine(this.viewer, { value: droppointPositions }, index),
            }
            this.viewer.entities.add(setting)
         })
      return {
         entity: polyline,
         routeLinePositions: positionArray,
         filePositions: dronePosition != null ? [
            { lng: dronePosition.longitude, lat: dronePosition.latitude, alt: dronePosition.height },
            ...filePositions
         ] : filePositions,
         filePositions:
            dockPosition !== null
               ? [{ lng: dockPosition.longitude, lat: dockPosition.latitude, alt: dockPosition.height }, ...filePositions]
               : filePositions,
      }
   }
   /**
    * 通用处理数据
    * @param {*} lineObj
    * @param {*} missionConfig
    * @param {*} dronePosition
    * @param {*} startPoint
    * @param {*} lineObj
    * @param {*} missionConfig
    * @param {*} dronePosition
    * @param {*} startPoint
    * @returns {} {positionArray: 带拼接点位置, filePositions:光航线点位置}
    */
   disposeData (lineObj, missionConfig, dronePosition, data, startPoint, wayline_type) {
      indexLog = indexLog + 1
      const { device_sn } = data
      const executeHeightMode = lineObj.executeHeightMode === "WGS84"
      const executeHeightMode = lineObj.executeHeightMode === 'WGS84'
      let filePositions = lineObj.Placemark.map(item => {
         const [lng, lat] = item.Point.coordinates.split(',')
@@ -730,12 +896,14 @@
      let safetyHeight
      if (executeHeightMode) {
         let startHeight = new Decimal(missionConfig.takeOffSecurityHeight)
            .plus(dronePosition.height).toNumber()
         let startHeight = new Decimal(missionConfig.takeOffSecurityHeight).plus(dronePosition?.height || 0).toNumber()
         safetyHeight = startHeight < filePositions[0].alt ? filePositions[0].alt : startHeight
      } else {
         safetyHeight = missionConfig.takeOffSecurityHeight < filePositions[0].alt ? filePositions[0].alt : missionConfig.takeOffSecurityHeight
         safetyHeight =
            missionConfig.takeOffSecurityHeight < filePositions[0].alt
               ? filePositions[0].alt
               : missionConfig.takeOffSecurityHeight
      }
      let safetyPositions = []
@@ -773,28 +941,38 @@
      })
      this.updataMultiNestData(device_sn, {
         currentRoutePositions
         currentRoutePositions,
      })
      if ([0].includes(wayline_type)) {
         let startHeight = executeHeightMode ? filePositions[0].alt : filePositions[0].alt + (dronePosition != null ? dronePosition.height : 0)
         let endHeight = executeHeightMode ? filePositions[1].alt : filePositions[1].alt + (dronePosition != null ? dronePosition.height : 0)
         let startHeight = executeHeightMode
            ? filePositions[0].alt
            : filePositions[0].alt + (dronePosition != null ? dronePosition.height : 0)
         let endHeight = executeHeightMode
            ? filePositions[1].alt
            : filePositions[1].alt + (dronePosition != null ? dronePosition.height : 0)
         this.updataMultiNestData(device_sn, {
            showPopupPosition: Cesium.Cartesian3.midpoint(
               Cesium.Cartesian3.fromDegrees(filePositions[0].lng, filePositions[0].lat, startHeight),
               Cesium.Cartesian3.fromDegrees(filePositions[1].lng, filePositions[1].lat, endHeight),
               new Cesium.Cartesian3())
               new Cesium.Cartesian3()
            ),
         })
      } else {
         let startHeight = executeHeightMode ? safetyPositions[1].alt : safetyPositions[1].alt + (dronePosition != null ? dronePosition.height : 0)
         let endHeight = executeHeightMode ? safetyPositions[2].alt : safetyPositions[2].alt + (dronePosition != null ? dronePosition.height : 0)
         let startHeight = executeHeightMode
            ? safetyPositions[1].alt
            : safetyPositions[1].alt + (dronePosition != null ? dronePosition.height : 0)
         let endHeight = executeHeightMode
            ? safetyPositions[2].alt
            : safetyPositions[2].alt + (dronePosition != null ? dronePosition.height : 0)
         this.updataMultiNestData(device_sn, {
            showPopupPosition: Cesium.Cartesian3.midpoint(
               Cesium.Cartesian3.fromDegrees(safetyPositions[1].lng, safetyPositions[1].lat, startHeight),
               Cesium.Cartesian3.fromDegrees(safetyPositions[2].lng, safetyPositions[2].lat, endHeight),
               new Cesium.Cartesian3())
               new Cesium.Cartesian3()
            ),
         })
      }
@@ -805,9 +983,9 @@
            return {
               ...item,
               alt: Number(height)
               alt: Number(height),
            }
         })
         }),
      }
   }
@@ -819,9 +997,10 @@
         return i?.name && i?.name.includes('work-drone-route-')
      })
      Array.isArray(entities) && entities.forEach(item => {
         this.viewer?.entities.remove(item)
      })
      Array.isArray(entities) &&
         entities.forEach(item => {
            this.viewer?.entities.remove(item)
         })
   }
   // 当前无人机视椎体相关
@@ -847,11 +1026,22 @@
            ['wide', 'ir', 'zoom'].includes(curDroneData?.camera_type)
         ) {
            if (curDroneData.camera_type === 'wide') {
               fov = 60
               fov = 65
            } else {
               const diffFocal = zoomFactor.value[curDroneData.camera_type].max - getZoomFactor(host?.cameras?.[0], curDroneData.camera_type)
               fov = (60 / zoomFactor.value[curDroneData.camera_type].max) * (diffFocal === 0 ? 1 : diffFocal)
               const cameraMultiplier = getZoomFactor(host?.cameras?.[0], curDroneData.camera_type)
               switch (cameraMultiplier) {
                  case 2: fov = 30; break
                  case 3: fov = 19; break
                  case 4: fov = 15; break
                  case 5: fov = 13; break
                  case 6: fov = 11; break
                  case 7: fov = 9; break
                  case 8: fov = 8.34; break
                  case 9: fov = 7.68; break
                  case 10: fov = 6.9; break
                  case 14: fov = 5; break
                  default: fov = 55.946 * Math.exp(-0.4504 * cameraMultiplier) + 6.323
               }
            }
         }
@@ -871,8 +1061,8 @@
               pitch: 0,
               heading: attitude_head,
               isShowVideoPlan: this.isShowVideoPlan
            })
               isShowVideoPlan: this.isShowVideoPlan,
            }),
         })
      }
   }
@@ -882,7 +1072,7 @@
      if (device_sn && live_status) {
         this.updataMultiNestData(device_sn, {
            camera_type: live_status?.[0].video_type || 'wide'
            camera_type: live_status?.[0].video_type || 'wide',
         })
      }
   }
@@ -897,14 +1087,9 @@
      }
   }
   // 设置当前无人机视椎体信息面板
   setFrustumInfoDetails (data) {
      const {
         dock_sn,
         distance_to_airport,
         distance_to_next_point,
         estimated_time_to_next_point,
         plane_mileage
      } = data
      const { dock_sn, distance_to_airport, distance_to_next_point, estimated_time_to_next_point, plane_mileage } = data
      if (dock_sn) {
         const aircraftEntity = this.viewer?.entities.values.find(i => {
@@ -924,7 +1109,9 @@
            }
            aircraftEntity.label = new Cesium.LabelGraphics({
               text: `离机场平面距离:${distance_to_airport}m\n离下个航点平面距离:${Math.round(distance_to_next_point)}m\n到达下个航点剩余时间:${arrivalTime}\n航线总平面里程:${Math.round(plane_mileage)}m`,
               text: `离机场平面距离:${distance_to_airport}m\n离下个航点平面距离:${Math.round(
                  distance_to_next_point
               )}m\n到达下个航点剩余时间:${arrivalTime}\n航线总平面里程:${Math.round(plane_mileage)}m`,
               font: '13px monospace',
               showBackground: true,
               horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
@@ -936,7 +1123,6 @@
            return
         }
      }
   }
@@ -954,8 +1140,10 @@
      }
   }
   // 设置无人机模型
   setAircraftGltf (host) {
      const parent_sn = host?.parent_sn
      const attitude_head = Number(host?.payloads?.[0]?.gimbal_yaw) - 90 || 0
      if (parent_sn) {
         const aircraftEntity = this.viewer?.entities.values.find(i => {
@@ -963,9 +1151,18 @@
         })
         const position = Cesium.Cartesian3.fromDegrees(host?.longitude, host?.latitude, host?.height)
         // ✅ 新增:Heading / Pitch / Roll(单位:度 -> 弧度)
         const heading = Cesium.Math.toRadians(attitude_head) // 航向角
         const pitch = Cesium.Math.toRadians(0)     // 俯仰角
         const roll = Cesium.Math.toRadians(0)       // 横滚角
         // ✅ 新增:构造 HeadingPitchRoll 和 四元数
         const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll)
         const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr)
         if (aircraftEntity) {
            aircraftEntity.position = new Cesium.ConstantPositionProperty(position)
            aircraftEntity.orientation = new Cesium.ConstantProperty(orientation)
            return
         }
@@ -974,18 +1171,35 @@
            this.viewer?.entities.add({
               name: `aircraft-glf-${parent_sn}`,
               position,
               orientation,
               label: {},
               model: {
                  uri: aircraftGltf, // 或 .glb
                  scale: 1.0, // 缩放比例
                  scale: 64, // 缩放比例
                  minimumPixelSize: 64, // 最小像素尺寸(保证模型远处可见)
                  maximumScale: 128, // 最大缩放(可选)
                  maximumScale: 64, // 最大缩放(可选)
               },
            })
         }
      }
   }
   // 移除无人机模型
   removeAircraftGltf (host) {
      const parent_sn = host?.parent_sn
      if (parent_sn) {
         const aircraftEntity = this.viewer?.entities.values.find(i => {
            return i?.name && i?.name.includes(`aircraft-glf-${parent_sn}`)
         })
         if (aircraftEntity) {
            this.viewer?.entities.remove(aircraftEntity)
         }
      }
   }
   // 移除所有无人机视椎体
   removeViewInfoFrustum () {
      this.multiNestData.forEach(item => {
         if ('viewInfoFrustum' in item && item.viewInfoFrustum) {
@@ -1010,9 +1224,10 @@
            return i?.name && i?.name.includes(`aircraft-glf-${parent_sn}`)
         })
         Array.isArray(entities) && entities.forEach(item => {
            this.viewer?.entities.remove(item)
         })
         Array.isArray(entities) &&
            entities.forEach(item => {
               this.viewer?.entities.remove(item)
            })
      }
   }
@@ -1021,13 +1236,15 @@
         return i?.name && i?.name.includes(`aircraft-glf-`)
      })
      Array.isArray(entities) && entities.forEach(item => {
         this.viewer?.entities.remove(item)
      })
      Array.isArray(entities) &&
         entities.forEach(item => {
            this.viewer?.entities.remove(item)
         })
      this.multiNestData.length > 0 && (this.multiNestData.forEach(item => {
         item.viewInfoFrustum?.clear()
      }))
      this.multiNestData.length > 0 &&
         this.multiNestData.forEach(item => {
            item.viewInfoFrustum?.clear()
         })
   }
   /**
@@ -1043,19 +1260,19 @@
         this.multiNestData[isHaveInd] = {
            ...this.multiNestData[isHaveInd],
            ...data
            ...data,
         }
      } else {
         this.multiNestData.push({
            device_sn,
            ...data
            ...data,
         })
      }
   }
   /**
    * 根据机巢sn获取当前机巢对应的相关信息
    * @param {*} device_sn
    * @param {*} device_sn
    */
   getCurDroneData (device_sn) {
      const isHave = this.multiNestData.find(item => item.device_sn === device_sn)
@@ -1071,14 +1288,16 @@
         return {
            ...item,
            show: true,
            uuid: uuidv4()
            uuid: uuidv4(),
         }
      })
      this.viewer.scene.postRender.addEventListener(that.labelBoxRenderHandler)
   }
   showSingleDetailMapPopup (data) {
      this.detailsMapPopupData.forEach(i => i.job_id === data.job_id && i.device_sn === data.device_sn && (i.show = true))
      this.detailsMapPopupData.forEach(
         i => i?.job_id === data?.job_id && i?.device_sn === data?.device_sn && (i.show = true)
      )
   }
   getLabelDom (data) {
@@ -1090,28 +1309,30 @@
      tooltipContainer.style.position = 'absolute'
      tooltipContainer.style.transform = 'translate(-50%,10%)'
      tooltipContainer.style.pointerEvents = 'none'
      document.querySelector('.content-map-popups')?.append(tooltipContainer)
      document.querySelector('.content-map-popups').append(tooltipContainer)
      render(vNode, tooltipContainer)
      return tooltipContainer
   }
   labelBoxRender () {
      this.detailsMapPopupData.filter(i => i.show === true).forEach(item => {
         const { showPopupPosition } = this.getCurDroneData(item.device_sn)
      this.detailsMapPopupData
         .filter(i => i.show === true)
         .forEach(item => {
            const { showPopupPosition } = this.getCurDroneData(item.device_sn)
         let dom = document.querySelector(`[id="${item.uuid}"]`)
            let dom = document.querySelector(`[id="${item.uuid}"]`)
         if (!dom) {
            dom = this.getLabelDom(item)
         }
            if (!dom) {
               dom = this.getLabelDom(item)
            }
         const screenPosition = this.viewer?.scene.cartesianToCanvasCoordinates(showPopupPosition)
         if (screenPosition) {
            dom.style.left = `${screenPosition.x}px`
            dom.style.top = `${screenPosition.y}px`
            dom.style.display = 'block'
         }
      })
            const screenPosition = this.viewer?.scene.cartesianToCanvasCoordinates(showPopupPosition)
            if (screenPosition) {
               dom.style.left = `${screenPosition.x}px`
               dom.style.top = `${screenPosition.y}px`
               dom.style.display = 'block'
            }
         })
   }
   // 移除弹框标签
@@ -1137,4 +1358,4 @@
      const that = this
      that.viewer?.scene.postRender.removeEventListener(that.labelBoxRenderHandler)
   }
}
}