吉安感知网项目-前端
shuishen
2026-01-16 efb3b5e84ed4493f82f30ed3c3dcdff0fd5a8083
applications/task-work-order/src/utils/cesium/useBoundary.js
@@ -6,19 +6,17 @@
import { useStore } from 'vuex'
import { getDeviceRegion, getDeviceRegionCount } from '@/api/home/aggregation'
import userStore from '@/store/modules/user'
import { areaCodeToArr, getContourByCode } from '@/utils/cesium/mapUtil'
// import { getEllipse } from '@/hooks/useSingleDroneMap/useSingleDroneMap'
import { areaCodeToArr, getContourByCode, getDockPolyLine, getLnglatAltitude } from '@/utils/cesium/mapUtil'
import getBaseConfig from '@/buildConfig/config'
import { getDroneStatusImage } from '@/utils/stateToImageMap/drone'
import {
   GroundCirclePrimitiveManager
} from '@/utils/mapUtils'
import { GroundCirclePrimitiveManager } from '@/utils/mapUtils'
import lowBattery from '@/assets/images/aiNowFly/low-battery.svg'
import mediumBattery from '@/assets/images/aiNowFly/medium-battery.svg'
import fullBattery from '@/assets/images/aiNowFly/full-charge.svg'
const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
// const { singleDockSystem } = getBaseConfig()
const { singleDockSystem } = getBaseConfig()
/**
 * 使用通用的边界
@@ -26,8 +24,15 @@
 * @param options
 */
export const useBoundary = (viewer, options = {}) => {
   const { multiple = 1.4, dockOptions = {}, boundaryChange, boundaryColor, dockRangeType, useDockHeight = false } = options
   const { scrollShowDock = false, showDock = false } = dockOptions
   const {
      multiple = 1.4,
      dockOptions = {},
      boundaryChange,
      boundaryColor,
      dockRangeType,
      useDockHeight = false,
   } = options
   const { scrollShowDock = false, showDock = false, showDockNameAndBattery = false } = dockOptions
   const manager = new GroundCirclePrimitiveManager()
   manager.init(viewer)
@@ -56,7 +61,7 @@
   }
   // 获取设备列表
   async function getDeviceRegionFun () {
   async function getDeviceRegionFun() {
      const res = await getDeviceRegion({ areaCode: selectedAreaCode })
      return res?.data?.data || []
   }
@@ -100,60 +105,127 @@
      return await res.json()
   }
   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
      })
   }
   function batteryLevelSvg(batteryNum) {
      if (batteryNum <= 30) {
         return lowBattery
      } else if (batteryNum > 30 && batteryNum <= 60) {
         return mediumBattery
      } else {
         return fullBattery
      }
   }
   async function nestSVG(nickname, batteryImgUrl, batteryNum) {
      let txt = nickname.length < 4 ? 130 : nickname.length * 38
      let tranx = nickname.length < 4 ? 86 : nickname.length * 26 + 3
      const batteryBase64Image = await getBase64Image(batteryImgUrl)
      const svg = `
        <svg width="${txt}" height="100" xmlns="http://www.w3.org/2000/svg">
         <g transform="translate(${tranx}, 10)">
            <!-- 电池图标 -->
            <image href="${batteryBase64Image}" x="0" y="2" width="12" height="13"/>
            <!-- 电量值 -->
            <text x="12" y="10" font-size="14" fill="${
               batteryNum <= 30 ? '#FF604B' : batteryNum >= 60 ? '#40FF5C' : '#00FFF2'
            }"  text-anchor="start" dominant-baseline="middle" font-weight="bolder" font-family="Source Han Sans CN" text-rendering="geometricPrecision">
               ${batteryNum}%
            </text>
         </g>
        </svg>
      `
      return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
   }
   // 无人机散点渲染
   const droneSplashed = () => {
      let showDockList = showDockSnList === null
         ? _.cloneDeep(initDockList)
         : initDockList.filter(item => showDockSnList.includes(item.device_sn))
      manager.removeAll()
      let showDockList =
         showDockSnList === null
            ? _.cloneDeep(initDockList)
            : initDockList.filter(item => showDockSnList.includes(item.device_sn))
      showDockList.forEach((item, index) => {
      showDockList.forEach(async (item, index) => {
         if (item.status === 'OFFLINE') return
         let polyline = {}
         if (dockRangeType === 2) {
            manager.addCircleOutline({
               data: {
                  lng: item.longitude,
                  lat: item.latitude
                  lat: item.latitude,
               },
               frameColor: Cesium.Color.fromCssColorString('#7FFFD4')
               frameColor: Cesium.Color.fromCssColorString('#7FFFD4'),
            })
         } else {
            manager.addCircle({
               data: {
                  lng: item.longitude,
                  lat: item.latitude
                  lat: item.latitude,
               },
               materialColor: Cesium.Color.CORNFLOWERBLUE.withAlpha(0.3)
               materialColor: item.color ? item.color : Cesium.Color.CORNFLOWERBLUE.withAlpha(0.3),
            })
         }
         if (useDockHeight) {
            polyline = {
               positions: new Cesium.CallbackProperty(() => {
                  const pointPosition = Cesium.Cartesian3.fromDegrees(+item.longitude, +item.latitude, +item.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,
            }
            polyline = getDockPolyLine(item, viewer)
         }
         const batteryImgUrl = batteryLevelSvg(item.capacity_percent)
         const combinedImage = await nestSVG(item.nickname, batteryImgUrl, item.capacity_percent)
         let pointObj = {}
         try {
            pointObj = await getLnglatAltitude(item.longitude, item.latitude, viewer)
         } catch (e) {}
         const position = Cesium.Cartesian3.fromDegrees(
            +item.longitude,
            +item.latitude,
            useDockHeight ? +item.height : pointObj?.height || 0
         )
         if (showDockNameAndBattery) {
            // 带电量
            dockSource.entities.add({
               position,
               billboard: {
                  // new Cesium.ConstantProperty(pointData.status === 'OFFLINE' ? endingImg : unSelectedOnline),
                  image: combinedImage,
               },
            })
         }
         dockSource.entities.add({
            position: Cesium.Cartesian3.fromDegrees(+item.longitude, +item.latitude, !useDockHeight ? 0 : +item.height),
            label: {
               text: item.nickname,
               font: 'bold 16px Source Han Sans CN',
               fillColor: Cesium.Color.WHITE,
               outlineColor: Cesium.Color.BLACK,
               outlineWidth: 2,
               style: Cesium.LabelStyle.FILL_AND_OUTLINE,
               verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
               pixelOffset: new Cesium.Cartesian2(0, -24),
            },
            position,
            polyline,
            billboard: {
               image: getDroneStatusImage(item.status),
               width: 60,
               height: 60,
               eyeOffset: new Cesium.Cartesian3(0, 0, -5)
               disableDepthTestDistance: Number.POSITIVE_INFINITY,
               eyeOffset: new Cesium.Cartesian3(0, 0, -5),
            },
         })
      })
@@ -186,7 +258,7 @@
                     positions: positions,
                     width: 5, // 直接设置宽度
                     clampToGround: true,
                     material: Cesium.Color.fromCssColorString(boundaryColor)
                     material: Cesium.Color.fromCssColorString(boundaryColor),
                  },
                  polygon,
               })
@@ -205,7 +277,7 @@
                  positions: positions,
                  width: 1, // 直接设置宽度
                  clampToGround: true,
                  material: Cesium.Color.fromCssColorString(boundaryColor)
                  material: Cesium.Color.fromCssColorString(boundaryColor),
               },
            })
         })
@@ -217,7 +289,6 @@
   // 打开边界
   const openContour = async () => {
      const areaCode = selectedAreaCode || userAreaCode
      viewer.scene.postRender.removeEventListener(determineScaling)
      if (!areaCode) return
      const hierarchy = areaCodeToArr(areaCode.slice(0, 6))
@@ -258,25 +329,29 @@
         const outlineGJson = await getContourByCode(areaCode.slice(0, 6))
         scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson))
         renderOutline(scalingJudgment[(hierarchy.length - 3) * -1])
      } catch (e) { }
      viewer.scene.postRender.addEventListener(determineScaling)
      } catch (e) {}
      try {
         viewer?.scene?.postRender?.addEventListener(determineScaling)
      }catch (e) {
      }
   }
   // 关闭边界
   function closeContour () {
   function closeContour() {
      removeAllEntities()
      viewer.scene.postRender.removeEventListener(determineScaling)
   }
   // 飞向边界
   async function flyToBoundary () {
   async function flyToBoundary() {
      // 单机巢系统
      if (singleDockSystem) {
         if (!initDockList.length) {
            initDockList = await getDeviceRegionFun()
         }
         viewer?.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(initDockList[0].longitude, initDockList[0].latitude, 24000),
            destination: Cesium.Cartesian3.fromDegrees(initDockList[0].longitude, initDockList[0].latitude, 22000),
            duration: 0,
         })
         return
@@ -286,7 +361,8 @@
      const dataSource = await Cesium.GeoJsonDataSource.load(gJson)
      if (!dataSource) return
      viewer.dataSources.add(dataSource)
      // 获取多边形边界所有点
      let positionList = []
      dataSource.entities.values.forEach(function (entity) {
         if (entity.polygon) {
            // 获取多边形的边界球
@@ -295,18 +371,16 @@
               let cartographic = Cesium.Cartographic.fromCartesian(item)
               let lng = Cesium.Math.toDegrees(cartographic.longitude) // 经度
               let lat = Cesium.Math.toDegrees(cartographic.latitude) // 纬度
               return [_.round(lng, 6), _.round(lat, 6)]
            })
            const newBox = boxTransformScale(curPolygonPosition, multiple)
            viewer.camera.flyTo({
               destination: Cesium.Rectangle.fromDegrees(...newBox),
               offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
               duration: 0.5,
            })
            positionList = positionList.concat(curPolygonPosition)
         }
      })
      const newBox = boxTransformScale(positionList, multiple)
      viewer.camera.flyTo({
         destination: Cesium.Rectangle.fromDegrees(...newBox),
         offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
         duration: 0.5,
      })
      dataSource.entities.values.forEach(entity => {
@@ -320,7 +394,7 @@
    * 设置要显示的机巢列表
    * @param dockSns - 要显示的机巢设备序列号数组。如果为 undefined,则显示所有已初始化的机巢;如果为空数组,则不显示任何机巢。
    */
   function setShowDock (dockSns) {
   function setShowDock(dockSns) {
      if (dockSns === undefined) {
         showDockSnList = null
      } else if (dockSns.length === 0) {
@@ -328,21 +402,31 @@
      } else {
         showDockSnList = dockSns
      }
      removeDockCover()
      if (active === '县') droneSplashed()
      if (!showDock) return
      if (scrollShowDock ? active === '县' : true) {
         droneSplashed()
      }
   }
   function setDockCoverColor(arr) {
      initDockList.forEach(item => {
         arr.forEach(dock => {
            item.color = dock.device_sn === item.device_sn ? dock.color : null
         })
      })
      if (scrollShowDock ? active === '县' : true) {
         droneSplashed()
      }
   }
   onBeforeUnmount(() => {
      manager.destroy()
   })
   return {
      openContour,
      closeContour,
      flyToBoundary,
      setShowDock
      setShowDock,
      setDockCoverColor,
   }
}