吉安感知网项目-前端
张含笑
2026-01-16 754f9b186f9ce43c730d2a80728daba74951e0bf
applications/task-work-order/src/utils/cesium/DrawPolygon.js
File was renamed from applications/task-work-order/src/views/layerManagement/components/utils.js
@@ -1,7 +1,8 @@
import * as Cesium from 'cesium'
import _, { cloneDeep, throttle } from 'lodash'
import * as turf from '@turf/turf'
import { boxTransformScale } from '@/utils/turfFunc'
import { ElMessage } from 'element-plus'
import { flyVisual, getPointPositionsHeight } from '@/utils/cesium/mapUtil'
/**
 * 多边形绘制与编辑工具类
@@ -14,8 +15,6 @@
 */
export class DrawPolygon {
   constructor() {
      // 是否绘制
      this.whetherToDraw =false
      // 图斑预览模式标记
      this.isPureSpotPreview = false
      // 是否删除测区
@@ -39,6 +38,8 @@
      // 存储端点的 DataSource
      this.editPolygonDataSource = null
      this.editPolygonPointDataSource = null
      // 存储中点(边中点)的 DataSource:用于编辑态插入新端点
      this.editPolygonMidPointDataSource = null
      // 鼠标事件处理器
      this.handler = null
      // 右键菜单 DOM
@@ -63,15 +64,17 @@
      this.delPoint = this.delPoint.bind(this)
      // 外部订阅者
      this.listeners = []
   }
   // 实体命名常量
   static ENTITY_NAMES = {
      POLYGON: '区域-面',
      POINT: '区域-端点',
      // 编辑态中点(用于快速插入新端点)
      MID_POINT: '区域-中点',
      POLYGON_ID: 'planar-route-polygon',
      POINT_ID_PREFIX: 'planar-route-point',
      MID_POINT_ID_PREFIX: 'planar-route-mid-point',
   }
   // 颜色常量
@@ -98,10 +101,20 @@
   // 编辑图斑
   editThePatch(data) {
      this.isPreviewMode = data
   }
   // 绘制
   drawTheArea(data){
      this.whetherToDraw = data
      // 关闭编辑能力时隐藏端点/中点,并清空中点,避免残留交互
      if (!this.isPreviewMode) {
         this.editPolygonPointDataSource && (this.editPolygonPointDataSource.entities.show = false)
         this.editPolygonMidPointDataSource && (this.editPolygonMidPointDataSource.entities.show = false)
         this.editPolygonMidPointDataSource?.entities?.removeAll?.()
         return
      }
      if (this.editingMode) {
         this.editPolygonPointDataSource && (this.editPolygonPointDataSource.entities.show = true)
         this.editPolygonMidPointDataSource && (this.editPolygonMidPointDataSource.entities.show = true)
         this.rebuildEditPoints()
         this.rebuildMidPoints()
      }
   }
   // 删除测区
   deleteTheArea(data) {
@@ -110,12 +123,6 @@
   // 开始绘制
   startDrawing() {
      if (!this.whetherToDraw) {
         return;
        }
      this.drawingMode = true
      this.curPolygon = new Cesium.PolygonHierarchy()
@@ -130,9 +137,16 @@
         this.viewer?.dataSources.add(this.editPolygonPointDataSource)
      }
      // 中点数据源:编辑模式下用于“边上插点”
      if (!this.editPolygonMidPointDataSource) {
         this.editPolygonMidPointDataSource = new Cesium.CustomDataSource('editPolygonMidPointDataSource')
         this.viewer?.dataSources.add(this.editPolygonMidPointDataSource)
      }
      // 清空之前的点
      this.editPolygonDataSource?.entities.removeAll()
      this.editPolygonPointDataSource?.entities.removeAll()
      this.editPolygonMidPointDataSource?.entities.removeAll()
   }
   // 创建多边形实体(含边界线)
@@ -177,10 +191,162 @@
            ind: pointIndex, // 索引记录
         },
      })
   }
   createMidPointEntity(startInd, position) {
      // 使用 CallbackProperty 让中点位置随端点拖拽实时计算
      // startInd 表示该中点属于边:(startInd) -> (startInd + 1)
      const updatePosition = () => {
         const positions = this.curPolygon?.positions
         if (!positions || positions.length < 2) return position
         const n = positions.length
         const p1 = positions[startInd]
         const p2 = positions[(startInd + 1) % n]
         if (!p1 || !p2) return position
         return Cesium.Cartesian3.midpoint(p1, p2, new Cesium.Cartesian3())
      }
      this.editPolygonMidPointDataSource.entities.add({
         name: DrawPolygon.ENTITY_NAMES.MID_POINT,
         id: `${DrawPolygon.ENTITY_NAMES.MID_POINT_ID_PREFIX}${startInd}`,
         position: new Cesium.CallbackProperty(updatePosition, false),
         point: {
            pixelSize: 10,
            color: Cesium.Color.fromCssColorString('rgba(255, 255, 255, .7)'),
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
         },
         customData: {
            startInd,
         },
      })
   }
   rebuildEditPoints() {
      // 插入/删除端点后:端点实体 id 与 customData.ind 需要全量重建,保证索引与 positions 一致
      if (!this.isPreviewMode) return
      if (!this.editPolygonPointDataSource || !this.curPolygon?.positions) return
      this.editPolygonPointDataSource.entities.removeAll()
      this.curPolygon.positions.forEach((position, index) => {
         this.editPolygonPointDataSource.entities.add({
            name: DrawPolygon.ENTITY_NAMES.POINT,
            id: `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}${index}`,
            position: position.clone(),
            point: {
               pixelSize: 14,
               color: Cesium.Color.WHITE,
               heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
               disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
            customData: {
               ind: index,
            },
         })
      })
   }
   rebuildMidPoints() {
      // 仅在“顶点数量变化/切换编辑态”时维护中点实体数量
      // 拖拽过程中中点位置由 CallbackProperty 自动更新,不需要重建
      if (!this.isPreviewMode) return
      if (!this.editPolygonMidPointDataSource || !this.curPolygon?.positions) return
      const positions = this.curPolygon.positions
      const n = positions.length
      const entities = this.editPolygonMidPointDataSource.entities
      if (!this.editingMode || n < 2) {
         entities.removeAll()
         return
      }
      const neededIds = new Set()
      for (let i = 0; i < n; i += 1) {
         const id = `${DrawPolygon.ENTITY_NAMES.MID_POINT_ID_PREFIX}${i}`
         neededIds.add(id)
         // 缺少则补齐:保证每条边都有一个中点实体
         if (!entities.getById(id)) {
            const p1 = positions[i]
            const p2 = positions[(i + 1) % n]
            if (!p1 || !p2) continue
            const mid = Cesium.Cartesian3.midpoint(p1, p2, new Cesium.Cartesian3())
            this.createMidPointEntity(i, mid)
         }
      }
      entities.values.slice().forEach(entity => {
         if (entity?.name !== DrawPolygon.ENTITY_NAMES.MID_POINT) return
         // 多余则移除:例如删除端点导致边数量减少
         if (!neededIds.has(entity.id)) {
            entities.remove(entity)
         }
      })
   }
   insertPointFromMidPoint(midPointEntity) {
      // 点击中点:在对应边上插入一个新端点(白点),并立即进入拖拽态
      if (!this.isPreviewMode) return
      if (!this.editingMode) return
      if (!midPointEntity?.customData) return
      const startInd = midPointEntity.customData.startInd
      // 中点位置可能是 CallbackProperty,需要取当前时刻的值
      const positionProperty = midPointEntity.position
      const entityPosition = positionProperty?.getValue
         ? positionProperty.getValue(Cesium.JulianDate.now())
         : positionProperty
      if (!entityPosition) return
      // 插入到 startInd 与 startInd+1 之间
      const insertIndex = Math.min(Math.max(startInd + 1, 0), this.curPolygon.positions.length)
      this.curPolygon.positions.splice(insertIndex, 0, entityPosition.clone ? entityPosition.clone() : entityPosition)
      this.rebuildEditPoints()
      this.rebuildMidPoints()
      const newPointEntity = this.editPolygonPointDataSource?.entities?.getById(
         `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}${insertIndex}`
      )
      if (newPointEntity) {
         this.isDragging = true
         this.draggedEntity = newPointEntity
         this.currentDragPointPosition = this.curPolygon.positions[insertIndex]
         this.disableMapControl()
      }
      this.notify('getPolygonPositions', this.curPolygon.positions)
   }
   // 清除不在范围内的点
   removeLastInvalidPoint() {
      const posLen = this.curPolygon.positions.length
      if (posLen === 0) return
      let targetPointId = ''
      let removeCount = 0
      if (posLen === 2) {
         removeCount = 2
         targetPointId = `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}0`
      } else if (posLen > 2) {
         removeCount = 1
         const pointIndex = posLen - 2
         targetPointId = `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}${pointIndex}`
      }
      this.curPolygon.positions.splice(posLen - removeCount, removeCount)
      const invalidPoint = this.editPolygonPointDataSource.entities.getById(targetPointId)
      if (invalidPoint) {
         this.editPolygonPointDataSource.entities.remove(invalidPoint)
      }
      if (this.curPolygon.positions.length === 0 && this.polygonEntity) {
         this.editPolygonDataSource.entities.remove(this.polygonEntity)
         this.polygonEntity = null
      }
   }
   // 添加一个点
   addPosition(position, isAdd = true) {
      // 第一个点要重复压入一次,形成动态绘制效果
@@ -211,12 +377,18 @@
      const pickedEntity = this.viewer.scene.pick(movement.position)?.id
      const isPoint = pickedEntity?.name === DrawPolygon.ENTITY_NAMES.POINT
      const isMidPoint = pickedEntity?.name === DrawPolygon.ENTITY_NAMES.MID_POINT
      if (pickedEntity && isPoint) {
         this.isDragging = true
         this.draggedEntity = pickedEntity
         this.currentDragPointPosition = this.curPolygon.positions[this.draggedEntity?.customData.ind]
         this.disableMapControl() // 禁止地图交互
         return
      }
      if (pickedEntity && isMidPoint) {
         this.insertPointFromMidPoint(pickedEntity)
      }
   }
@@ -249,6 +421,7 @@
         this.isDragging = false
         this.draggedEntity = null
         this.enableMapControl()
         this.rebuildMidPoints()
      }
   }
@@ -262,7 +435,7 @@
      // 编辑模式下,拖拽点实时更新
      if (this.editingMode && this.draggedEntity?.customData) {
         this.draggedEntity.position = cartesian
         this.curPolygon.positions[this.draggedEntity?.customData.ind] = cartesian
         this.curPolygon.positions[this.draggedEntity?.customData.ind] = cartesian
      }
      // 绘制模式下,实时更新最后一个点
@@ -293,27 +466,28 @@
   // 鼠标左键点击
   handleLeftClick(click) {
      this.removeMenuPopup()
      const pickedAllEntity = this.viewer.scene.drillPick(click.position).filter(i => i.id)
      const isStartPoint = pickedAllEntity.find(i => i.id.name === '起飞点')
      const isPolygonPoint = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POINT)
      const isPolygon = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POLYGON)
      if (this.drawingMode && !this.whetherToDraw) {
         return; // 阻断后续绘制逻辑
        }
      if (isStartPoint) return
      // 如果不是绘制模式
      if (!this.drawingMode) {
         if (!isPolygon) {
            this.editingMode = false
            this.editPolygonPointDataSource.entities.show = false
            this.editPolygonMidPointDataSource && (this.editPolygonMidPointDataSource.entities.show = false)
            return
         }
         if (isPolygon) {
            this.editingMode = true
            this.editPolygonPointDataSource.entities.show = true
            this.editPolygonMidPointDataSource && (this.editPolygonMidPointDataSource.entities.show = true)
            this.rebuildMidPoints()
         }
         return
      }
@@ -334,13 +508,22 @@
      const cartesian = this.viewer.scene.pickPosition(click.position)
      if (!cartesian) return
      let arr = [...this.curPolygon.positions]
      arr.pop()
      // 校验多边形是否自交
      if (arr.length > 2 && !this.curDragPointIsValid([...arr, cartesian])) {
         ElMessage.warning('测区不支持交叉绘制,请重新绘制')
         return
      }
      this.addPosition(cartesian)
   }
   // 鼠标右键点击(弹出菜单)
   handleRightClick(click) {
   handleRightClick(click) {
      const that = this
      if (that.drawingMode) return
      that.removeMenuPopup()
      const pickedAllEntity = that.viewer.scene.drillPick(click.position).filter(i => i.id)
@@ -351,16 +534,14 @@
      } = click
      let pickedEntity, tooltipEvent, menuType
      if (isPolygon) {
         pickedEntity = isPolygon
         tooltipEvent = that.delPolygon
         menuType = 'polygon'
      } else if (isEditPoint) {
      if (isEditPoint) {
         pickedEntity = isEditPoint
         tooltipEvent = that.delPoint
         menuType = 'edit-point'
      } else if (isPolygon) {
         pickedEntity = isPolygon
         tooltipEvent = that.delPolygon
         menuType = 'polygon'
      }
      if (pickedEntity && this.isDeleteTheArea) {
@@ -385,6 +566,11 @@
         this.editPolygonPointDataSource.entities.removeAll()
         this.editPolygonPointDataSource = null
      }
      if (this.editPolygonMidPointDataSource) {
         this.editPolygonMidPointDataSource.entities.removeAll()
         this.editPolygonMidPointDataSource = null
      }
      this.editingMode = false
      this.polygonEntity = null
      this.curPolygon = null
@@ -398,6 +584,8 @@
         this.drawingMode = false
         this.editingMode = true
         this.editPolygonPointDataSource.entities.show = true
         this.editPolygonMidPointDataSource && (this.editPolygonMidPointDataSource.entities.show = true)
         this.rebuildMidPoints()
         if (!this.curDragPointIsValid(this.curPolygon.positions)) {
            this.isShowWaringTip = true
@@ -421,7 +609,12 @@
      this.notify('getPolygonPositions', [])
      this.startDrawing()
   }
   // 删除图斑
   delSpot() {
      this.removeEntities()
      this.isPreviewMode = true
      this.startDrawing()
   }
   // 删除端点
   delPoint() {
      if (this.curPolygon.positions.length <= 3) {
@@ -431,13 +624,9 @@
      if (!this.delPolygonPoint) return
      this.curPolygon.positions.splice(this.delPolygonPoint.customData.ind, 1)
      this.editPolygonPointDataSource.entities.remove(this.delPolygonPoint)
      this.removeMenuPopup()
      // 更新剩余点索引
      this.editPolygonPointDataSource.entities.values.forEach((item, index) => {
         item.customData.ind = index
      })
      this.rebuildEditPoints()
      this.rebuildMidPoints()
      this.notify('getPolygonPositions', this.curPolygon.positions)
   }
@@ -454,7 +643,6 @@
      menuPopup.id = 'planarPolygonEditMenu'
      menuPopup.className = 'planar-polygon-edit-menu'
      const menuItems =
         type === 'polygon'
            ? [{ title: '删除测区', class: 'del-planar-polygon' }]
@@ -466,7 +654,6 @@
         titleDiv.className = item.class
         menuPopup.appendChild(titleDiv)
      })
      this.isPreviewMode = true
      menuPopupVBox.appendChild(menuPopup)
      return menuPopupVBox
@@ -522,23 +709,18 @@
      return intersections.features.length === 0
   }
   // 初始化已有多边形
   initPolygon(viewer, positions, isPurePreview = false) {
   async initPolygon(viewer, positions, isPurePreview = false) {
      this.initHandler(viewer)
      if (this.whetherToDraw) {
         this.startDrawing();
        } else {
         // 新增:不允许绘制时,确保绘制模式为 false,避免误触发
         this.drawingMode = false;
        }
      this.isPureSpotPreview = isPurePreview
      this.startDrawing()
      let newPosition = positions.map(item => {
         return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item.height))
         return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item?.height || 0))
      })
      // 预览航线的时候调用
      if (!this.isPureSpotPreview) {
         this.notify('getPolygonPositions', newPosition)
      }
      newPosition.forEach(item => {
         this.addPosition(item, false)
@@ -555,17 +737,20 @@
         duration: 0.5,
      })
      let pointList = await getPointPositionsHeight(positions, viewer)
      flyVisual({ positionsData: pointList.map(item => [item.lng, item.lat, item.ASL]), viewer })
      this.drawingMode = false
      this.editingMode = true
      this.editPolygonPointDataSource && (this.editPolygonPointDataSource.entities.show = true)
      this.editPolygonMidPointDataSource && (this.editPolygonMidPointDataSource.entities.show = true)
      this.rebuildMidPoints()
   }
   // 初始化事件处理器
   initHandler(viewer) {
      this.viewer = viewer
      if(this.whetherToDraw){
         this.startDrawing()
      }
      this.startDrawing()
      if (!this.handler) {
         this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)