吉安感知网项目-前端
chenyao
2026-01-29 f083281ab75f61390a04daa2484f330f8df4178f
applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js
New file
@@ -0,0 +1,314 @@
import * as Cesium from 'cesium'
import { MapTooltip } from '../Tooltip'
import { ToolBase } from '../ToolBase'
import { buildEllipsePositions, cartesianToLocal } from '../shapeUtils'
const normalizePoints = points =>
   (points || []).map(point => ({
      lng: point.lng ?? point.longitude,
      lat: point.lat ?? point.latitude,
   }))
export class EditBufferCircleTool extends ToolBase {
   constructor(viewer) {
      super(viewer)
      this.tooltip = new MapTooltip(viewer)
      this.dataSource = null
      this.centerCartesian = null
      this.radiusLevel1 = 0
      this.radiusLevel2 = 0
      this.radiusLevel3 = 0
      this.dragType = null
      this.circleLevel1Entity = null
      this.circleLevel2Entity = null
      this.circleLevel3Entity = null
      this.circleLevel1OutlineEntity = null
      this.circleLevel2OutlineEntity = null
      this.circleLevel3OutlineEntity = null
      this.centerEntity = null
      this.radiusLevel1Entity = null
      this.radiusLevel2Entity = null
      this.radiusLevel3Entity = null
   }
   start(data = []) {
      const inputPoints = Array.isArray(data?.points) ? data.points : data
      const normalized = normalizePoints(inputPoints)
      if (!normalized.length) return
      const centerLng = normalized.reduce((sum, p) => sum + p.lng, 0) / normalized.length
      const centerLat = normalized.reduce((sum, p) => sum + p.lat, 0) / normalized.length
      this.centerCartesian = Cesium.Cartesian3.fromDegrees(centerLng, centerLat)
      const radii = Array.isArray(data?.meta?.bufferRadii) ? data.meta.bufferRadii : null
      if (radii?.length) {
         const [r1, r2, r3] = radii
         this.radiusLevel1 = Number.isFinite(r1) && r1 > 0 ? r1 : 1
         this.radiusLevel2 = Number.isFinite(r2) && r2 > 0 ? Math.max(r2, this.radiusLevel1) : this.radiusLevel1
         this.radiusLevel3 = Number.isFinite(r3) && r3 > 0 ? Math.max(r3, this.radiusLevel2) : this.radiusLevel2
      } else {
         this.radiusLevel3 = this.estimateRadius(normalized)
         this.radiusLevel2 = this.radiusLevel3
         this.radiusLevel1 = this.radiusLevel3
      }
      this.dataSource = new Cesium.CustomDataSource('edit-buffer-circle')
      this.viewer.dataSources.add(this.dataSource)
      this.createEntities()
      this.initHandler()
   }
   estimateRadius(points) {
      const center = Cesium.Cartesian3.clone(this.centerCartesian)
      let max = 1
      points.forEach(point => {
         const cartesian = Cesium.Cartesian3.fromDegrees(point.lng, point.lat)
         const distance = Cesium.Cartesian3.distance(center, cartesian)
         max = Math.max(max, distance)
      })
      return max
   }
   initHandler() {
      this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
      this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN)
      this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
      this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
      this.tooltip.show('拖动控制点编辑', { x: 0, y: 0 })
   }
   handleLeftDown(evt) {
      const picked = this.viewer.scene.pick(evt.position)?.id
      if (!picked) return
      if (picked.customType === 'circle-center') this.dragType = 'center'
      if (picked.customType === 'circle-radius-1') this.dragType = 'radius-1'
      if (picked.customType === 'circle-radius-2') this.dragType = 'radius-2'
      if (picked.customType === 'circle-radius-3') this.dragType = 'radius-3'
      if (this.dragType) this.disableMapControl()
   }
   handleMouseMove(evt) {
      this.tooltip.move(evt.endPosition)
      if (!this.dragType) return
      const position = this.getPositionFromScreen(evt.endPosition)
      if (!position) return
      if (this.dragType === 'center') {
         this.centerCartesian = position
         this.updateRadiusPoints()
         return
      }
      const local = cartesianToLocal(this.centerCartesian, position)
      const radius = Math.max(1, Math.sqrt(local.x * local.x + local.y * local.y))
      if (this.dragType === 'radius-1') {
         this.radiusLevel1 = radius
         if (this.radiusLevel2 < this.radiusLevel1) this.radiusLevel2 = this.radiusLevel1
         if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2
      }
      if (this.dragType === 'radius-2') {
         this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
         if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2
      }
      if (this.dragType === 'radius-3') {
         this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
      }
      this.updateRadiusPoints()
   }
   handleLeftUp() {
      if (!this.dragType) return
      this.dragType = null
      this.enableMapControl()
      const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
      this.notify('getPolygonPositions', {
         positions,
         meta: {
            center: this.centerCartesian,
            bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
         },
      })
   }
   createEntities() {
      this.circleLevel1Entity = this.dataSource.entities.add({
         position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
         ellipse: {
            semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
            semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
            material: Cesium.Color.fromBytes(247, 20, 20, 128),
            outline: false,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
         },
      })
      this.circleLevel1OutlineEntity = this.dataSource.entities.add({
         polyline: {
            positions: new Cesium.CallbackProperty(() => {
               if (!this.centerCartesian || this.radiusLevel1 <= 0) return []
               const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel1, this.radiusLevel1)
               return positions.length ? [...positions, positions[0]] : positions
            }, false),
            clampToGround: true,
            width: 2,
            material: Cesium.Color.fromBytes(255, 0, 0, 255),
         },
      })
      this.circleLevel2Entity = this.dataSource.entities.add({
         position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
         ellipse: {
            semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
            semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
            material: Cesium.Color.fromBytes(255, 235, 59, 128),
            outline: false,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
         },
      })
      this.circleLevel2OutlineEntity = this.dataSource.entities.add({
         polyline: {
            positions: new Cesium.CallbackProperty(() => {
               if (!this.centerCartesian || this.radiusLevel2 <= 0) return []
               const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel2, this.radiusLevel2)
               return positions.length ? [...positions, positions[0]] : positions
            }, false),
            clampToGround: true,
            width: 2,
            material: Cesium.Color.fromBytes(255, 235, 59, 255),
         },
      })
      this.circleLevel3Entity = this.dataSource.entities.add({
         position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
         ellipse: {
            semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
            semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
            material: Cesium.Color.fromBytes(25, 178, 230, 128),
            outline: false,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
         },
      })
      this.circleLevel3OutlineEntity = this.dataSource.entities.add({
         polyline: {
            positions: new Cesium.CallbackProperty(() => {
               if (!this.centerCartesian || this.radiusLevel3 <= 0) return []
               const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
               return positions.length ? [...positions, positions[0]] : positions
            }, false),
            clampToGround: true,
            width: 2,
            material: Cesium.Color.fromBytes(0, 251, 255, 255),
         },
      })
      this.centerEntity = this.dataSource.entities.add({
         position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
         point: {
            pixelSize: 12,
            color: Cesium.Color.WHITE,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
         },
         customType: 'circle-center',
      })
      this.radiusLevel1Entity = this.dataSource.entities.add({
         position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel1), false),
         point: {
            pixelSize: 10,
            color: Cesium.Color.fromBytes(255, 255, 255, 200),
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
         },
         customType: 'circle-radius-1',
      })
      this.radiusLevel2Entity = this.dataSource.entities.add({
         position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel2), false),
         point: {
            pixelSize: 10,
            color: Cesium.Color.fromBytes(255, 255, 255, 200),
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
         },
         customType: 'circle-radius-2',
      })
      this.radiusLevel3Entity = this.dataSource.entities.add({
         position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel3), false),
         point: {
            pixelSize: 10,
            color: Cesium.Color.fromBytes(255, 255, 255, 200),
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
         },
         customType: 'circle-radius-3',
      })
   }
   updateRadiusPoints() {
      if (this.radiusLevel1Entity) this.radiusLevel1Entity.position = this.getRadiusPoint(this.radiusLevel1)
      if (this.radiusLevel2Entity) this.radiusLevel2Entity.position = this.getRadiusPoint(this.radiusLevel2)
      if (this.radiusLevel3Entity) this.radiusLevel3Entity.position = this.getRadiusPoint(this.radiusLevel3)
   }
   getPositions() {
      if (!this.centerCartesian) return []
      return {
         positions: buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3),
         meta: {
            center: this.centerCartesian,
            bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
         },
      }
   }
   getRadiusPoint(radius) {
      const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian)
      const local = new Cesium.Cartesian3(radius, 0, 0)
      return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3())
   }
   getPositionFromScreen(screenPosition) {
      const scene = this.viewer.scene
      const cartesian = scene.pickPosition(screenPosition)
      if (cartesian) return cartesian
      return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
   }
   disableMapControl() {
      const controller = this.viewer.scene.screenSpaceCameraController
      controller.enableRotate = false
      controller.enableTranslate = false
      controller.enableZoom = false
   }
   enableMapControl() {
      const controller = this.viewer.scene.screenSpaceCameraController
      controller.enableRotate = true
      controller.enableTranslate = true
      controller.enableZoom = true
   }
   destroy() {
      if (this.dataSource) {
         this.dataSource.entities.removeAll()
         this.viewer.dataSources.remove(this.dataSource)
         this.dataSource = null
      }
      if (this.handler) {
         this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
         this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
         this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
         this.handler.destroy()
         this.handler = null
      }
      this.tooltip?.destroy()
      this.tooltip = null
      this.centerCartesian = null
      this.radiusLevel1 = 0
      this.radiusLevel2 = 0
      this.radiusLevel3 = 0
      this.circleLevel1OutlineEntity = null
      this.circleLevel2OutlineEntity = null
      this.circleLevel3OutlineEntity = null
      this.dragType = null
   }
}