| File was renamed from applications/task-work-order/src/views/layerManagement/components/utils.js |
| | |
| | | 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' |
| | | |
| | | /** |
| | | * 多边形绘制与编辑工具类 |
| | |
| | | */ |
| | | export class DrawPolygon { |
| | | constructor() { |
| | | // 是否绘制 |
| | | this.whetherToDraw =false |
| | | // 图斑预览模式标记 |
| | | this.isPureSpotPreview = false |
| | | // 是否删除测区 |
| | |
| | | // 存储端点的 DataSource |
| | | this.editPolygonDataSource = null |
| | | this.editPolygonPointDataSource = null |
| | | // 存储中点(边中点)的 DataSource:用于编辑态插入新端点 |
| | | this.editPolygonMidPointDataSource = null |
| | | // 鼠标事件处理器 |
| | | this.handler = null |
| | | // 右键菜单 DOM |
| | |
| | | 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', |
| | | } |
| | | |
| | | // 颜色常量 |
| | |
| | | // 编辑图斑 |
| | | 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) { |
| | |
| | | |
| | | // 开始绘制 |
| | | startDrawing() { |
| | | if (!this.whetherToDraw) { |
| | | |
| | | return; |
| | | } |
| | | |
| | | |
| | | this.drawingMode = true |
| | | this.curPolygon = new Cesium.PolygonHierarchy() |
| | | |
| | |
| | | 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() |
| | | } |
| | | |
| | | // 创建多边形实体(含边界线) |
| | |
| | | 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) { |
| | | // 第一个点要重复压入一次,形成动态绘制效果 |
| | |
| | | |
| | | 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) |
| | | } |
| | | } |
| | | |
| | |
| | | this.isDragging = false |
| | | this.draggedEntity = null |
| | | this.enableMapControl() |
| | | this.rebuildMidPoints() |
| | | } |
| | | } |
| | | |
| | |
| | | // 编辑模式下,拖拽点实时更新 |
| | | 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 |
| | | } |
| | | |
| | | // 绘制模式下,实时更新最后一个点 |
| | |
| | | // 鼠标左键点击 |
| | | 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 |
| | | } |
| | |
| | | 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) |
| | |
| | | } = 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) { |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | this.notify('getPolygonPositions', []) |
| | | this.startDrawing() |
| | | } |
| | | |
| | | // 删除图斑 |
| | | delSpot() { |
| | | this.removeEntities() |
| | | this.isPreviewMode = true |
| | | this.startDrawing() |
| | | } |
| | | // 删除端点 |
| | | delPoint() { |
| | | if (this.curPolygon.positions.length <= 3) { |
| | |
| | | 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) |
| | | } |
| | |
| | | menuPopup.id = 'planarPolygonEditMenu' |
| | | menuPopup.className = 'planar-polygon-edit-menu' |
| | | |
| | | |
| | | const menuItems = |
| | | type === 'polygon' |
| | | ? [{ title: '删除测区', class: 'del-planar-polygon' }] |
| | |
| | | titleDiv.className = item.class |
| | | menuPopup.appendChild(titleDiv) |
| | | }) |
| | | |
| | | this.isPreviewMode = true |
| | | menuPopupVBox.appendChild(menuPopup) |
| | | return menuPopupVBox |
| | |
| | | |
| | | 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) |
| | |
| | | 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) |