From efb3b5e84ed4493f82f30ed3c3dcdff0fd5a8083 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Fri, 16 Jan 2026 10:55:55 +0800
Subject: [PATCH] feat:任务工单后台地图
---
applications/task-work-order/src/utils/cesium/DrawPolygon.js | 803 +++++++++++
applications/task-work-order/src/utils/cesium/createRouteLine.js | 923 ++++++++-----
applications/task-work-order/src/utils/cesium/frustum/CesiumVideoFrustum.js | 712 ++++++++++
applications/task-work-order/src/utils/cesium/use-kmz-tsa.js | 675 +++++++++
applications/task-work-order/src/utils/cesium/mapUtil.js | 207 ++
applications/task-work-order/src/utils/cesium/compressImage.js | 55
/dev/null | 5
applications/task-work-order/src/utils/cesium/kmz.js | 10
applications/task-work-order/src/utils/cesium/publicCesium.js | 168 +
applications/task-work-order/src/utils/cesium/useBoundary.js | 210 ++
applications/task-work-order/src/utils/cesium/common.js | 8
applications/task-work-order/src/utils/cesium/frustum/CoordinateTranslate.js | 229 +++
applications/task-work-order/src/utils/cesium/Material/index.js | 18
applications/task-work-order/src/utils/cesium/compressImage2.js | 46
14 files changed, 3,550 insertions(+), 519 deletions(-)
diff --git a/applications/task-work-order/src/utils/cesium/DrawPolygon.js b/applications/task-work-order/src/utils/cesium/DrawPolygon.js
new file mode 100644
index 0000000..6c37a35
--- /dev/null
+++ b/applications/task-work-order/src/utils/cesium/DrawPolygon.js
@@ -0,0 +1,803 @@
+import * as Cesium from 'cesium'
+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.isPureSpotPreview = false
+ // 是否删除测区
+ this.isDeleteTheArea = true
+ //是否可以编辑图斑
+ this.isPreviewMode = true
+ // Cesium 视图对象
+ this.viewer = null
+ // 当前绘制的多边形
+ this.curPolygon = null
+ // 绘制模式标识
+ this.drawingMode = false
+ // 编辑模式标识
+ this.editingMode = false
+ // 是否正在拖拽端点
+ this.isDragging = false
+ // 当前拖拽的点实体
+ this.draggedEntity = null
+ // 多边形实体
+ this.polygonEntity = null
+ // 存储端点的 DataSource
+ this.editPolygonDataSource = null
+ this.editPolygonPointDataSource = null
+ // 存储中点(边中点)的 DataSource:用于编辑态插入新端点
+ this.editPolygonMidPointDataSource = null
+ // 鼠标事件处理器
+ this.handler = null
+ // 右键菜单 DOM
+ this.menuPopup = null
+ // 被右键选中的点
+ this.delPolygonPoint = null
+ // 是否显示警告提示(自交)
+ this.isShowWaringTip = false
+ // 当前拖拽点是否合法
+ this.currentDragPointIsValid = false
+ // 当前拖拽点的坐标
+ this.currentDragPointPosition = null
+
+ // 事件回调函数绑定 this
+ this.handleLeftDown = this.handleLeftDown.bind(this)
+ this.handleLeftUp = this.handleLeftUp.bind(this)
+ this.handleMouseMove = this.handleMouseMove.bind(this)
+ this.handleLeftClick = this.handleLeftClick.bind(this)
+ this.handleRightClick = this.handleRightClick.bind(this)
+
+ this.delPolygon = this.delPolygon.bind(this)
+ 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',
+ }
+
+ // 颜色常量
+ static COLORS = {
+ DEFAULT_POLYGON: Cesium.Color.fromBytes(45, 140, 240, 99), // 默认面颜色
+ DEFAULT_LINE: Cesium.Color.fromBytes(45, 140, 240, 255), // 默认边界线颜色
+ ERROR_POLYGON: Cesium.Color.fromCssColorString('rgba(255, 0, 0, .3)'), // 错误(自交)面颜色
+ ERROR_LINE: Cesium.Color.fromCssColorString('rgba(255, 0, 0, 1)'), // 错误(自交)线颜色
+ }
+
+ // ============ 发布订阅机制 ============
+
+ // 外部订阅数据变化
+ subscribe(key, listener) {
+ this.listeners.push({ key, listener })
+ }
+
+ // 通知订阅者
+ notify(key, data) {
+ this.listeners.filter(subscriber => subscriber.key === key).forEach(subscriber => subscriber.listener(data))
+ }
+
+ // ============ 绘制相关 ============
+ // 编辑图斑
+ editThePatch(data) {
+ this.isPreviewMode = 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) {
+ this.isDeleteTheArea = data
+ }
+
+ // 开始绘制
+ startDrawing() {
+ this.drawingMode = true
+ this.curPolygon = new Cesium.PolygonHierarchy()
+
+ // 如果还没有 DataSource,就新建一个
+ if (!this.editPolygonDataSource) {
+ this.editPolygonDataSource = new Cesium.CustomDataSource('editPolygonDataSource')
+ this.viewer?.dataSources.add(this.editPolygonDataSource)
+ }
+
+ if (!this.editPolygonPointDataSource) {
+ this.editPolygonPointDataSource = new Cesium.CustomDataSource('editPolygonPointDataSource')
+ 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()
+ }
+
+ // 创建多边形实体(含边界线)
+ createPolygonEntity() {
+ this.polygonEntity = this.editPolygonDataSource.entities.add({
+ name: DrawPolygon.ENTITY_NAMES.POLYGON,
+ id: DrawPolygon.ENTITY_NAMES.POLYGON_ID,
+ polygon: {
+ hierarchy: new Cesium.CallbackProperty(() => this.curPolygon, false),
+ material: DrawPolygon.COLORS.DEFAULT_POLYGON,
+ outline: false,
+ outlineWidth: 2,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ polyline: {
+ width: 2,
+ material: DrawPolygon.COLORS.DEFAULT_LINE,
+ clampToGround: true,
+ positions: new Cesium.CallbackProperty(
+ () => [...this.curPolygon.positions, this.curPolygon.positions[0]], // 闭合线
+ false
+ ),
+ },
+ })
+ }
+
+ // 创建端点实体
+ createPointEntity(position, isAdd) {
+ const pointIndex = isAdd ? this.curPolygon.positions.length - 2 : this.curPolygon.positions.length - 1
+
+ this.editPolygonPointDataSource.entities.add({
+ name: DrawPolygon.ENTITY_NAMES.POINT,
+ id: `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}${pointIndex}`,
+ position: position.clone(),
+ point: {
+ pixelSize: 14,
+ color: Cesium.Color.WHITE,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customData: {
+ 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) {
+ // 第一个点要重复压入一次,形成动态绘制效果
+ if (this.curPolygon.positions.length === 0 && isAdd) {
+ this.curPolygon.positions.push(position.clone())
+ }
+
+ this.curPolygon.positions.push(position.clone())
+
+ // 如果没有实体则创建
+ if (!this.polygonEntity) {
+ this.createPolygonEntity()
+ }
+
+ // 创建端点实体
+ if (this.isPreviewMode) {
+ this.createPointEntity(position, isAdd)
+ }
+
+ this.notify('getPoints', position)
+ }
+
+ // ============ 鼠标事件 ============
+
+ // 鼠标左键按下(选中端点拖动)
+ handleLeftDown(movement) {
+ if (!this.editingMode) return
+
+ 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)
+ }
+ }
+
+ // 鼠标左键抬起(拖拽结束)
+ handleLeftUp() {
+ if (!(this.editingMode && this.curPolygon?.positions && this.draggedEntity)) return
+
+ if (this.currentDragPointIsValid && this.isDragging) {
+ // 更新点位置
+ this.draggedEntity.position = this.currentDragPointPosition
+ this.curPolygon.positions[this.draggedEntity?.customData.ind] = this.currentDragPointPosition
+
+ // 校验多边形是否自交
+ if (!this.curDragPointIsValid(this.curPolygon.positions)) {
+ this.isShowWaringTip = true
+ this.currentDragPointIsValid = true
+ this.updatePolygonAppearance(DrawPolygon.COLORS.ERROR_POLYGON, DrawPolygon.COLORS.ERROR_LINE)
+ } else {
+ this.isShowWaringTip = false
+ this.currentDragPointIsValid = false
+ this.updatePolygonAppearance(DrawPolygon.COLORS.DEFAULT_POLYGON, DrawPolygon.COLORS.DEFAULT_LINE)
+ }
+
+ this.notify('getShowWaringTip', this.isShowWaringTip)
+ }
+
+ // 拖拽结束,恢复交互
+ if (this.isDragging) {
+ this.notify('getPolygonPositions', this.curPolygon.positions)
+ this.isDragging = false
+ this.draggedEntity = null
+ this.enableMapControl()
+ this.rebuildMidPoints()
+ }
+ }
+
+ // 鼠标移动
+ handleMouseMove(movement) {
+ if (!this.drawingMode && !this.editingMode) return
+
+ const cartesian = this.viewer.scene.pickPosition(movement.endPosition)
+ if (!cartesian) return
+
+ // 编辑模式下,拖拽点实时更新
+ if (this.editingMode && this.draggedEntity?.customData) {
+ this.draggedEntity.position = cartesian
+ this.curPolygon.positions[this.draggedEntity?.customData.ind] = cartesian
+ }
+
+ // 绘制模式下,实时更新最后一个点
+ if (this.drawingMode && this.polygonEntity && this.curPolygon.positions.length >= 1) {
+ this.curPolygon.positions.pop()
+ this.curPolygon.positions.push(cartesian)
+ }
+
+ // 实时检查是否自交
+ if (
+ (this.editingMode && this.draggedEntity?.customData) ||
+ (this.drawingMode && this.polygonEntity && this.curPolygon.positions.length >= 3)
+ ) {
+ if (!this.curDragPointIsValid(this.curPolygon.positions)) {
+ this.isShowWaringTip = true
+ this.currentDragPointIsValid = true
+ this.updatePolygonAppearance(DrawPolygon.COLORS.ERROR_POLYGON, DrawPolygon.COLORS.ERROR_LINE)
+ } else {
+ this.isShowWaringTip = false
+ this.currentDragPointIsValid = false
+ this.updatePolygonAppearance(DrawPolygon.COLORS.DEFAULT_POLYGON, DrawPolygon.COLORS.DEFAULT_LINE)
+ }
+
+ this.notify('getShowWaringTip', this.isShowWaringTip)
+ }
+ }
+
+ // 鼠标左键点击
+ 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 (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
+ }
+
+ // 点击闭合多边形
+ if (this.curPolygon.positions.length < 4 && isPolygonPoint) {
+ return
+ }
+
+ if (this.curPolygon.positions.length >= 4 && isPolygonPoint) {
+ if (!this.drawingMode) return
+
+ this.finishDrawing()
+ 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) {
+ const that = this
+ if (that.drawingMode) return
+
+ that.removeMenuPopup()
+
+ const pickedAllEntity = that.viewer.scene.drillPick(click.position).filter(i => i.id)
+ const isPolygon = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POLYGON)
+ const isEditPoint = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POINT)
+ const {
+ position: { x, y },
+ } = click
+
+ let pickedEntity, tooltipEvent, menuType
+ if (isEditPoint) {
+ pickedEntity = isEditPoint
+ tooltipEvent = that.delPoint
+ menuType = 'edit-point'
+ } else if (isPolygon) {
+ pickedEntity = isPolygon
+ tooltipEvent = that.delPolygon
+ menuType = 'polygon'
+ }
+
+ if (pickedEntity && this.isDeleteTheArea) {
+ that.delPolygonPoint = pickedEntity.id
+ that.menuPopup = that.createMenuPopup(menuType)
+ that.menuPopup.style.transform = `translate3d(${x - 10}px, ${y - 10}px, 0)`
+ that.viewer.container.appendChild(that.menuPopup)
+ that.menuPopup.addEventListener('click', tooltipEvent)
+ }
+ }
+
+ // ============ 删除相关 ============
+
+ // 删除所有实体
+ removeEntities() {
+ if (this.editPolygonDataSource) {
+ this.editPolygonDataSource.entities.removeAll()
+ this.editPolygonDataSource = null
+ }
+
+ if (this.editPolygonPointDataSource) {
+ 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
+ }
+
+ // 完成绘制
+ finishDrawing() {
+ this.curPolygon.positions.pop()
+
+ if (this.curPolygon.positions.length >= 3) {
+ 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.currentDragPointIsValid = true
+ this.updatePolygonAppearance(DrawPolygon.COLORS.ERROR_POLYGON, DrawPolygon.COLORS.ERROR_LINE)
+ } else {
+ this.isShowWaringTip = false
+ this.currentDragPointIsValid = false
+ this.updatePolygonAppearance(DrawPolygon.COLORS.DEFAULT_POLYGON, DrawPolygon.COLORS.DEFAULT_LINE)
+ }
+
+ this.notify('getShowWaringTip', this.isShowWaringTip)
+ this.notify('getPolygonPositions', this.curPolygon.positions)
+ }
+ }
+
+ // 删除多边形
+ delPolygon() {
+ this.removeEntities()
+ this.removeMenuPopup()
+ this.notify('getPolygonPositions', [])
+ this.startDrawing()
+ }
+ // 删除图斑
+ delSpot() {
+ this.removeEntities()
+ this.isPreviewMode = true
+ this.startDrawing()
+ }
+ // 删除端点
+ delPoint() {
+ if (this.curPolygon.positions.length <= 3) {
+ this.removeMenuPopup()
+ return ElMessage.warning('端点不可少于3个')
+ }
+ if (!this.delPolygonPoint) return
+
+ this.curPolygon.positions.splice(this.delPolygonPoint.customData.ind, 1)
+ this.removeMenuPopup()
+ this.rebuildEditPoints()
+ this.rebuildMidPoints()
+
+ this.notify('getPolygonPositions', this.curPolygon.positions)
+ }
+
+ // ============ 工具方法 ============
+
+ // 创建右键菜单
+ createMenuPopup(type = 'polygon') {
+ const menuPopupVBox = document.createElement('div')
+ menuPopupVBox.id = 'planarPolygonEdit'
+ menuPopupVBox.className = 'planar-polygon-edit-tooltip'
+
+ const menuPopup = document.createElement('div')
+ menuPopup.id = 'planarPolygonEditMenu'
+ menuPopup.className = 'planar-polygon-edit-menu'
+
+ const menuItems =
+ type === 'polygon'
+ ? [{ title: '删除测区', class: 'del-planar-polygon' }]
+ : [{ title: '删除端点', class: 'del-planar-point' }]
+
+ menuItems.forEach(item => {
+ const titleDiv = document.createElement('div')
+ titleDiv.innerText = item.title
+ titleDiv.className = item.class
+ menuPopup.appendChild(titleDiv)
+ })
+ this.isPreviewMode = true
+ menuPopupVBox.appendChild(menuPopup)
+ return menuPopupVBox
+ }
+
+ // 移除菜单
+ removeMenuPopup() {
+ const that = this
+ if (that.menuPopup) {
+ that.menuPopup.removeEventListener('click', that.delPolygon)
+ that.menuPopup.removeEventListener('click', that.delPoint)
+ that.viewer.container.removeChild(that.menuPopup)
+ that.menuPopup = null
+ }
+ that.delPolygonPoint = null
+ }
+
+ // 更新多边形样式(正常/错误)
+ updatePolygonAppearance(polygonColor, lineColor) {
+ this.polygonEntity.polygon.material = polygonColor
+ this.polygonEntity.polyline.material = lineColor
+ }
+
+ // 禁用地图交互
+ 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
+ }
+
+ // 检查多边形是否自交
+ curDragPointIsValid(positions) {
+ if (positions.length < 3) return true
+
+ const cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray(positions)
+ const latLngPoints = cartographics.map(cartographic => [
+ Cesium.Math.toDegrees(cartographic.longitude),
+ Cesium.Math.toDegrees(cartographic.latitude),
+ ])
+
+ // 用 turf.js 检查自交
+ const poly = turf.polygon([[...latLngPoints, latLngPoints[0]]])
+ const intersections = turf.kinks(poly)
+
+ return intersections.features.length === 0
+ }
+ // 初始化已有多边形
+ async initPolygon(viewer, positions, isPurePreview = false) {
+ this.initHandler(viewer)
+ this.isPureSpotPreview = isPurePreview
+ this.startDrawing()
+ let newPosition = positions.map(item => {
+ 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)
+ })
+
+ // 视角飞入区域
+ const newBox = boxTransformScale(
+ positions.map(item => [item.lng, item.lat]),
+ 5
+ )
+ viewer.camera.flyTo({
+ destination: Cesium.Rectangle.fromDegrees(...newBox),
+ offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
+ 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
+ this.startDrawing()
+
+ if (!this.handler) {
+ this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
+
+ // 注册鼠标事件
+ const events = [
+ [Cesium.ScreenSpaceEventType.LEFT_DOWN, this.handleLeftDown],
+ [Cesium.ScreenSpaceEventType.LEFT_UP, this.handleLeftUp],
+ [Cesium.ScreenSpaceEventType.MOUSE_MOVE, this.handleMouseMove],
+ [Cesium.ScreenSpaceEventType.LEFT_CLICK, this.handleLeftClick],
+ [Cesium.ScreenSpaceEventType.RIGHT_CLICK, this.handleRightClick],
+ ]
+
+ events.forEach(([type, handler]) => {
+ this.handler.setInputAction(handler, type)
+ })
+ }
+ }
+
+ // 移除事件处理器
+ removeHandler() {
+ if (this.handler) {
+ const eventTypes = [
+ Cesium.ScreenSpaceEventType.LEFT_DOWN,
+ Cesium.ScreenSpaceEventType.LEFT_UP,
+ Cesium.ScreenSpaceEventType.MOUSE_MOVE,
+ Cesium.ScreenSpaceEventType.LEFT_CLICK,
+ Cesium.ScreenSpaceEventType.RIGHT_CLICK,
+ ]
+
+ eventTypes.forEach(type => {
+ this.handler.removeInputAction(type)
+ })
+
+ this.handler = null
+ }
+ }
+
+ /**
+ * 销毁实例,释放资源
+ */
+ destroy() {
+ if (!this.viewer) return
+
+ this.removeMenuPopup()
+ this.removeEntities()
+ this.removeHandler()
+ this.enableMapControl()
+ }
+}
diff --git a/applications/task-work-order/src/utils/cesium/Material/index.js b/applications/task-work-order/src/utils/cesium/Material/index.js
index f96f7a8..d5aff41 100644
--- a/applications/task-work-order/src/utils/cesium/Material/index.js
+++ b/applications/task-work-order/src/utils/cesium/Material/index.js
@@ -54,6 +54,7 @@
this._opacity = undefined
this._alphaPower = undefined
this._speed = undefined
+ this._xRepeatCount = undefined
this.color = options.color || Cesium.Color.fromBytes(0, 255, 255, 255)
this.glowPower = .25
@@ -64,6 +65,9 @@
this.opacity = options.opacity || 0.5
this.alphaPower = options.alphaPower || 1.5
this.speed = options.speed || 5
+ this.xRepeatCount = options.xRepeatCount || 1
+
+
}
get isConstant () {
@@ -132,15 +136,17 @@
color: new Cesium.Color(1.0, 1.0, 0.0, 0.7),
bgColor: new Cesium.Color(0.0, 1.0, 0.0, 0.0),
speed: 5,
- globalAlpha: 1.0
+ globalAlpha: 1.0,
+ xRepeatCount: 1.0
},
source: `
uniform vec4 bgColor;
uniform vec4 color;
uniform float speed;
uniform float globalAlpha;
+ uniform float xRepeatCount; // 新增的 uniform 在着色器中
- czm_material czm_getMaterial(czm_materialInput materialInput) {
+ czm_material czm_getMaterial(czm_materialInput materialInput) {
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float time = fract(czm_frameNumber * speed / 1000.0);
@@ -150,7 +156,7 @@
colorMars3D = vec3(1.0);
}
- material.alpha = color.a * 1.5 * smoothstep(0.0, 1.0, fract(st.s - time));
+ material.alpha = color.a * 1.5 * smoothstep(0.0, 1.0, fract(st.s * xRepeatCount - time)); // 使用 xRepeatCount 来调整纹理重复
material.diffuse = max(colorMars3D.rgb * material.alpha, colorMars3D.rgb);
if (material.alpha < bgColor.a) {
@@ -182,6 +188,8 @@
result.color = Cesium.Property.getValueOrUndefined(this._color, time)
result.globalAlpha = Cesium.Property.getValueOrUndefined(this._opacity, time)
result.speed = Cesium.Property.getValueOrUndefined(this._speed, time)
+ result.xRepeatCount = Cesium.Property.getValueOrUndefined(this._xRepeatCount, time)
+
return result
}
@@ -191,7 +199,8 @@
(other instanceof LineTrailMaterial &&
Cesium.Property.equals(this._color, other._color) &&
Cesium.Property.equals(this._opacity, other._opacity) &&
- Cesium.Property.equals(this._speed, other._speed))
+ Cesium.Property.equals(this._speed, other._speed) &&
+ Cesium.Property.equals(this._xRepeatCount, other._xRepeatCount))
)
}
}
@@ -200,6 +209,7 @@
color: Cesium.createPropertyDescriptor('color'),
opacity: Cesium.createPropertyDescriptor('opacity'),
speed: Cesium.createPropertyDescriptor('speed'),
+ xRepeatCount: Cesium.createPropertyDescriptor('xRepeatCount'),
})
diff --git a/applications/task-work-order/src/utils/cesium/common.js b/applications/task-work-order/src/utils/cesium/common.js
index 0e1aefc..5c8ce3e 100644
--- a/applications/task-work-order/src/utils/cesium/common.js
+++ b/applications/task-work-order/src/utils/cesium/common.js
@@ -131,22 +131,22 @@
let newLat = lat
switch (direction) {
- case 'W': // 向前(heading 方向)
+ case 'KeyW': // 向前(heading 方向)
newLon += moveStep * Math.sin(headingRad)
newLat += moveStep * Math.cos(headingRad)
break
- case 'S': // 向后(反方向)
+ case 'KeyS': // 向后(反方向)
newLon -= moveStep * Math.sin(headingRad)
newLat -= moveStep * Math.cos(headingRad)
break
- case 'D': // 向右(heading + 90°)
+ case 'KeyD': // 向右(heading + 90°)
newLon += moveStep * Math.cos(headingRad)
newLat -= moveStep * Math.sin(headingRad)
break
- case 'A': // 向左(heading - 90°)
+ case 'KeyA': // 向左(heading - 90°)
newLon -= moveStep * Math.cos(headingRad)
newLat += moveStep * Math.sin(headingRad)
break
diff --git a/applications/task-work-order/src/utils/cesium/compressImage.js b/applications/task-work-order/src/utils/cesium/compressImage.js
new file mode 100644
index 0000000..0b09680
--- /dev/null
+++ b/applications/task-work-order/src/utils/cesium/compressImage.js
@@ -0,0 +1,55 @@
+/**
+ * @description: 图片压缩
+ * @param {*} src 图片地址
+ * @param {*} quality 压缩质量
+ * @param {*} maxWidth 最大宽度
+ * @param {*} callback 回调函数
+ * @return {*}
+ */
+function compressImage(src, quality, maxWidth, callback) {
+ const img = new Image()
+ img.setAttribute('crossOrigin', 'Anonymous')
+ img.onload = function () {
+ const canvas = document.createElement('canvas')
+ const ctx = canvas.getContext('2d')
+ let width = img.width
+ let height = img.height
+
+ if (width > maxWidth) {
+ height *= maxWidth / width
+ width = maxWidth
+ }
+
+ canvas.width = width
+ canvas.height = height
+ ctx.drawImage(img, 0, 0, width, height)
+
+ const newData = canvas.toDataURL('image/jpeg', quality)
+ // 使用atob()将base64转换为二进制字符串
+ const binaryString = window.atob(newData.split(',')[1])
+ const mimeString = newData.split(',')[0].split(':')[1].split(';')[0]
+ // 创建Blob对象
+ const array = []
+ for (let i = 0; i < binaryString.length; i++) {
+ array.push(binaryString.charCodeAt(i))
+ }
+ const blob = new Blob([new Uint8Array(array)], { type: mimeString })
+
+ callback(blob)
+ }
+ img.src = src
+
+ return src
+}
+
+export default function imageCompress(src, quality, maxWidth) {
+ // 使用例子
+ return compressImage(src, quality, maxWidth, function (blob) {
+ // 创建一个新的图片URL
+ const imageUrl = URL.createObjectURL(blob)
+ // 可以在这里使用imageUrl,比如设置为img元素的src
+ // document.getElementById('your-image-element').src = imageUrl;
+ // 当不需要这个URL的时候,释放它
+ URL.revokeObjectURL(imageUrl)
+ })
+}
diff --git a/applications/task-work-order/src/utils/cesium/compressImage2.js b/applications/task-work-order/src/utils/cesium/compressImage2.js
new file mode 100644
index 0000000..e28a427
--- /dev/null
+++ b/applications/task-work-order/src/utils/cesium/compressImage2.js
@@ -0,0 +1,46 @@
+/*
+ * @Author: GuLiMmo 2820890765@qq.com
+ * @Date: 2024-05-21 10:14:11
+ * @LastEditors: GuLiMmo 2820890765@qq.com
+ * @LastEditTime: 2024-05-21 10:20:34
+ * @FilePath: /bigScreen/src/utils/cesium/compressImage2.js
+ * @Description:
+ * Copyright (c) 2024 by GuLiMmo, All Rights Reserved.
+ */
+export default function compressImage(
+ src,
+ maxWidth,
+ maxHeight,
+ outputFormat = 'image/jpeg',
+ quality = 0.7
+) {
+ return new Promise((resolve, reject) => {
+ const image = new Image()
+ image.setAttribute('crossOrigin', 'Anonymous')
+ image.src = src
+ image.onload = () => {
+ let width = image.width
+ let height = image.height
+
+ if (width > maxWidth) {
+ height *= maxWidth / width
+ width = maxWidth
+ }
+
+ if (height > maxHeight) {
+ width *= maxHeight / height
+ height = maxHeight
+ }
+
+ const canvas = document.createElement('canvas')
+ canvas.width = width
+ canvas.height = height
+ const ctx = canvas.getContext('2d')
+ ctx.drawImage(image, 0, 0, width, height)
+
+ const newDataUrl = canvas.toDataURL(outputFormat, quality)
+ resolve(newDataUrl)
+ }
+ image.onerror = reject
+ })
+}
diff --git a/applications/task-work-order/src/utils/cesium/createRouteLine.js b/applications/task-work-order/src/utils/cesium/createRouteLine.js
index 2c8195e..6ec1c1f 100644
--- a/applications/task-work-order/src/utils/cesium/createRouteLine.js
+++ b/applications/task-work-order/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)
}
-}
\ No newline at end of file
+}
diff --git a/applications/task-work-order/src/utils/cesium/eventBus.js b/applications/task-work-order/src/utils/cesium/eventBus.js
deleted file mode 100644
index 674e1dc..0000000
--- a/applications/task-work-order/src/utils/cesium/eventBus.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import mitt from 'mitt'
-
-const emitter = mitt()
-
-export default emitter
diff --git a/applications/task-work-order/src/utils/cesium/frustum/CesiumVideoFrustum.js b/applications/task-work-order/src/utils/cesium/frustum/CesiumVideoFrustum.js
new file mode 100644
index 0000000..6dd75b8
--- /dev/null
+++ b/applications/task-work-order/src/utils/cesium/frustum/CesiumVideoFrustum.js
@@ -0,0 +1,712 @@
+/*
+ * @Author : yuan
+ * @Date : 2025-08-16 17:01:46
+ * @LastEditors : yuan
+ * @LastEditTime : 2025-09-26 16:27:20
+ * @FilePath : \src\utils\cesium\frustum\CesiumVideoFrustum.js
+ * @Description :
+ * Copyright 2025 OBKoro1, All Rights Reserved.
+ * 2025-08-16 17:01:46
+ */
+import * as Cesium from 'cesium'
+
+var videoShed3dShader = `
+uniform float ztzf_frustum_opacity;
+
+uniform sampler2D ztzf_frustum_videoTexture;
+uniform sampler2D ztzf_frustum_maskTexture;
+uniform vec4 ztzf_frustum_hiddenAreaColor;
+
+uniform sampler2D shadowMap_texture;
+uniform mat4 shadowMap_matrix;
+uniform vec4 shadowMap_lightPositionEC;
+uniform vec4 shadowMap_texelSizeDepthBias;
+uniform vec4 shadowMap_normalOffsetScale;
+uniform bool ztzf_frustum_flipx;
+uniform bool ztzf_frustum_flipy;
+
+// 新增边框参数
+uniform vec4 ztzf_frustum_borderColor; // 边框颜色(RGBA)
+uniform float ztzf_frustum_borderWidth; // 边框宽度(0.0-1.0)
+
+uniform sampler2D colorTexture;
+uniform sampler2D depthTexture;
+in vec2 v_textureCoordinates;
+
+vec4 toEye(in vec2 uv, in float depth) {
+ vec2 xy = vec2((uv.x * 2.0 - 1.0), (uv.y * 2.0 - 1.0));
+ vec4 posInCamera = czm_inverseProjection * vec4(xy, depth, 1.0);
+ posInCamera = posInCamera / posInCamera.w;
+ return posInCamera;
+}
+
+float getDepthMars3D(in vec4 depth) {
+ float z_window = czm_unpackDepth(depth);
+ z_window = czm_reverseLogDepth(z_window);
+ float n_range = czm_depthRange.near;
+ float f_range = czm_depthRange.far;
+ return (2.0 * z_window - n_range - f_range) / (f_range - n_range);
+}
+float _czm_sampleShadowMap(sampler2D shadowMap, vec2 uv) {
+ return texture(shadowMap, uv).r;
+}
+float _czm_shadowDepthCompare(sampler2D shadowMap, vec2 uv, float depth) {
+ return step(depth, _czm_sampleShadowMap(shadowMap, uv));
+}
+float _czm_shadowVisibility(sampler2D shadowMap, czm_shadowParameters shadowParameters) {
+ float depthBias = shadowParameters.depthBias;
+ float depth = shadowParameters.depth;
+ float nDotL = shadowParameters.nDotL;
+ float normalShadingSmooth = shadowParameters.normalShadingSmooth;
+ float darkness = shadowParameters.darkness;
+ vec2 uv = shadowParameters.texCoords;
+ depth -= depthBias;
+ vec2 texelStepSize = shadowParameters.texelStepSize;
+ float radius = 1.0;
+ float dx0 = -texelStepSize.x * radius;
+ float dy0 = -texelStepSize.y * radius;
+ float dx1 = texelStepSize.x * radius;
+ float dy1 = texelStepSize.y * radius;
+ float visibility = (_czm_shadowDepthCompare(shadowMap, uv, depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy0), depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy0), depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy0), depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, 0.0), depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, 0.0), depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy1), depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy1), depth) +
+ _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy1), depth)) * (1.0 / 9.0);
+ return visibility;
+}
+
+vec3 pointProjectOnPlane(in vec3 planeNormal, in vec3 planeOrigin, in vec3 point) {
+ vec3 v01 = point - planeOrigin;
+ float d = dot(planeNormal, v01);
+ return (point - planeNormal * d);
+}
+float ptm(vec3 pt) {
+ return sqrt(pt.x * pt.x + pt.y * pt.y + pt.z * pt.z);
+}
+
+void main() {
+ const float PI = 3.141592653589793;
+ vec4 color = texture(colorTexture, v_textureCoordinates);
+ vec4 currD = texture(depthTexture, v_textureCoordinates);
+
+ float depth = getDepthMars3D(currD);
+ vec4 positionEC = toEye(v_textureCoordinates, depth);
+ vec3 normalEC = vec3(1.0);
+ czm_shadowParameters shadowParameters;
+ shadowParameters.texelStepSize = shadowMap_texelSizeDepthBias.xy;
+ shadowParameters.depthBias = shadowMap_texelSizeDepthBias.z;
+ shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBias.w;
+ shadowParameters.darkness = shadowMap_normalOffsetScale.w;
+ shadowParameters.depthBias *= max(depth * 0.01, 1.0);
+
+ vec3 directionEC = normalize(positionEC.xyz - shadowMap_lightPositionEC.xyz);
+ float nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0);
+ vec4 shadowPosition = shadowMap_matrix * positionEC;
+ shadowPosition /= shadowPosition.w;
+ if(any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) {
+ out_FragColor = color;
+ return;
+ }
+
+ shadowParameters.texCoords = shadowPosition.xy;
+ shadowParameters.depth = shadowPosition.z;
+ shadowParameters.nDotL = nDotL;
+ float visibility = _czm_shadowVisibility(shadowMap_texture, shadowParameters);
+
+ //视频投射
+ if(visibility == 1.0) {
+ if(ztzf_frustum_flipx){
+ shadowPosition.x = shadowPosition.x + (0.5 - shadowPosition.x) * 2.0;
+ }
+ if(ztzf_frustum_flipy){
+ shadowPosition.y = shadowPosition.y + (0.5 - shadowPosition.y) * 2.0;
+ }
+
+ vec4 videoColor = texture(ztzf_frustum_videoTexture, shadowPosition.xy);
+ vec4 maskColor = texture(ztzf_frustum_maskTexture, shadowPosition.xy);
+ videoColor *= maskColor;
+
+ // 计算到边缘的距离
+ float distToEdge = min(
+ min(shadowPosition.x, 1.0 - shadowPosition.x),
+ min(shadowPosition.y, 1.0 - shadowPosition.y)
+ );
+
+ // 混合视频颜色和背景
+ vec4 finalColor = mix(color, vec4(videoColor.xyz, 1.0), ztzf_frustum_opacity * videoColor.a);
+
+ // 添加边框效果
+ if(distToEdge < ztzf_frustum_borderWidth && videoColor.a > 0.0) {
+ float borderFactor = smoothstep(0.0, ztzf_frustum_borderWidth, distToEdge);
+ finalColor = mix(ztzf_frustum_borderColor, finalColor, borderFactor);
+ }
+
+ out_FragColor = finalColor;
+ } else {
+ if(abs(shadowPosition.z - 0.0) < 0.01) {
+ return;
+ }
+ out_FragColor = vec4(mix(color.rgb, ztzf_frustum_hiddenAreaColor.rgb, ztzf_frustum_hiddenAreaColor.a), ztzf_frustum_hiddenAreaColor.a);
+ }
+}
+ `
+
+class CesiumVideoFrustum {
+ constructor(viewer, param) {
+ this.param = param
+
+ var option = this._initCameraParam()
+
+ if (option || (option = {}), this.viewer = viewer, this._cameraPosition = option.cameraPosition, this._position = option.position,
+ this.type = option.type, this._alpha = option.alpha || 0, this.element = option.element, this.color = option.color,
+ this._debugFrustum = Cesium.defaultValue(option.debugFrustum, !0), this._aspectRatio = option.aspectRatio || this._getWinWidHei(),
+ this._camerafov = option.fov || Cesium.Math.toDegrees(this.viewer.scene.camera.frustum.fov),
+ this._isShadowMap = option.isShadowMap || false,
+ this.texture = option.texture || new Cesium.Texture({
+ context: this.viewer.scene.context,
+ source: {
+ width: 1,
+ height: 1,
+ arrayBufferView: new Uint8Array([255, 255, 255, 255])
+ },
+ flipY: !1
+ }), this.defaultShow = Cesium.defaultValue(option.show, !0), !this.cameraPosition || !this.position) return void console.log('初始化失败:请确认相机位置与视点位置正确!')
+
+ this.activeVideo()
+ this._createShadowMap()
+ this._getOrientation()
+ this._addCameraFrustum()
+ this._addPostProcess()
+ this.viewer.scene.primitives.add(this)
+ }
+
+ get alpha () {
+ return this._alpha
+ }
+
+ set alpha (e) {
+ this._alpha = e
+ this._changeViewPos()
+ }
+
+ get aspectRatio () {
+ return this._aspectRatio
+ }
+
+ set aspectRatio (e) {
+ this._aspectRatio = e
+ this._changeVideoWidHei()
+ }
+
+ get debugFrustum () {
+ return this._debugFrustum
+ }
+
+ set debugFrustum (e) {
+ this._debugFrustum = e
+ this.cameraFrustum.show = e
+ this.cameraFrustumOutline.show = e
+ }
+
+ get fov () {
+ return this._camerafov
+ }
+
+ set fov (e) {
+ this._camerafov = e
+ this._changeCameraFov()
+ }
+
+ get cameraPosition () {
+ return this._cameraPosition
+ }
+
+ set cameraPosition (e) {
+ e && (this._cameraPosition = e, this._changeCameraPos())
+ }
+
+ get isShadowMap () {
+ return this._isShadowMap
+ }
+
+ set isShadowMap (e) {
+ this._isShadowMap = e
+ this._changeCameraIsShadowMap()
+ }
+
+ get position () {
+ return this._position
+ }
+
+ set position (e) {
+ e && (this._position = e, this._changeViewPos())
+ }
+
+ get params () {
+ var t = {}
+ return t.element = this.element,
+ t.position = this.position,
+ t.cameraPosition = this.cameraPosition,
+ t.fov = this.fov,
+ t.isShadowMap = this.isShadowMap,
+ t.aspectRatio = this.aspectRatio,
+ t.alpha = this.alpha,
+ t.debugFrustum = this.debugFrustum,
+ t.show = this.show
+ }
+
+ get show () {
+ return this.defaultShow
+ }
+
+ set show (e) {
+ this.defaultShow = Boolean(e),
+ this._switchShow()
+ }
+
+ isDestroyed () {
+ return false
+ }
+
+ _initCameraParam () {
+ var cameraPosition = Cesium.Cartesian3.fromDegrees(this.param.position.x * 1, this.param.position.y * 1, this.param.position.z * 1)
+
+ const hpr = Cesium.HeadingPitchRoll.fromDegrees(this.param.rotation.y * 1, this.param.rotation.x * 1, 0)
+ const orientation = Cesium.Transforms.headingPitchRollQuaternion(cameraPosition, hpr)
+
+ let matrix = Cesium.Matrix3.fromQuaternion(
+ orientation,
+ new Cesium.Matrix3()
+ )
+
+ let direction = Cesium.Matrix3.getColumn(matrix, 2, new Cesium.Cartesian3())
+
+ let frontDirect = Cesium.Cartesian3.multiplyByScalar(
+ direction,
+ 250,
+ new Cesium.Cartesian3()
+ )
+ let position = Cesium.Cartesian3.add(cameraPosition, frontDirect, new Cesium.Cartesian3())
+
+ return {
+ cameraPosition: cameraPosition,
+ position: position,
+ alpha: this.param.alpha,
+ near: this.param.near,
+ fov: this.param.fov,
+ isShadowMap: this.param.isShadowMap,
+ debugFrustum: this.param.debugFrustum,
+ aspectRatio: this.param.aspectRatio,
+ element: this.param.element,
+ }
+ }
+
+ _changeAlpha (e) {
+ this.alpha = e
+ }
+
+ _changeAspectRatio (e) {
+ this.aspectRatio = e
+ }
+
+ _changeRotation (e) {
+ if (e) {
+ this.param.rotation = e
+ var option = this._initCameraParam()
+ this.position = option.position
+ }
+ }
+
+ _changeCameraPosition (e) {
+ if (e) {
+ this.param.position = e
+ var option = this._initCameraParam()
+ this.cameraPosition = option.cameraPosition
+ }
+ }
+
+ _changeFov (e) {
+ if (e) {
+ this.param.fov = e
+ var option = this._initCameraParam()
+ this.fov = option.fov
+ }
+ }
+
+ _changeIsShadowMap (e) {
+ this.param.isShadowMap = e
+ var option = this._initCameraParam()
+ this.isShadowMap = option.isShadowMap
+ }
+
+ _changeFar (e) {
+ if (e) {
+ this.param.far = e
+ var option = this._initCameraParam()
+ this.position = option.position
+ }
+ }
+
+ _changeNear (e) {
+ if (e) {
+ this.param.near = e
+ this.near = this.param.near
+ this._changeCameraPos()
+ }
+ }
+
+ _getWinWidHei () {
+ var viewer = this.viewer.scene
+ return viewer.canvas.clientWidth / viewer.canvas.clientHeight
+ }
+
+ _changeCameraFov () {
+ this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess)
+
+ this.viewer.scene.primitives.remove(this.cameraFrustum),
+ this.viewer.scene.primitives.remove(this.cameraFrustumOutline),
+ this._createShadowMap(this.cameraPosition, this.position),
+ this._getOrientation(),
+ this._addCameraFrustum(),
+ this._addPostProcess()
+ }
+
+ _changeCameraIsShadowMap () {
+ this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess)
+
+ this.viewShadowMap && this.viewShadowMap.destroy()
+ this.viewShadowMap = null
+
+ this.viewer.scene.primitives.remove(this.cameraFrustum),
+ this.viewer.scene.primitives.remove(this.cameraFrustumOutline),
+ this._createShadowMap(this.cameraPosition, this.position),
+ this._getOrientation(),
+ this._addCameraFrustum(),
+ this._addPostProcess()
+ }
+
+ _changeVideoWidHei () {
+ this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess)
+
+ this.viewer.scene.primitives.remove(this.cameraFrustum),
+ this.viewer.scene.primitives.remove(this.cameraFrustumOutline),
+ this._createShadowMap(this.cameraPosition, this.position),
+ this._getOrientation(),
+ this._addCameraFrustum(),
+ this._addPostProcess()
+ }
+
+ _changeCameraPos () {
+ this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess)
+
+ this.viewShadowMap && this.viewShadowMap.destroy()
+ this.viewShadowMap = null
+
+ this.viewer.scene.primitives.remove(this.cameraFrustum),
+ this.viewer.scene.primitives.remove(this.cameraFrustumOutline),
+ // this.cameraFrustum.destroy(),
+ this._createShadowMap(this.cameraPosition, this.position),
+ this._getOrientation(),
+ this._addCameraFrustum(),
+ this._addPostProcess()
+ }
+
+ _changeViewPos () {
+ this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess)
+
+ this.viewShadowMap && this.viewShadowMap.destroy()
+ this.viewShadowMap = null
+
+ this.viewer.scene.primitives.remove(this.cameraFrustum),
+ this.viewer.scene.primitives.remove(this.cameraFrustumOutline),
+ // this.cameraFrustum.destroy(),
+ this._createShadowMap(this.cameraPosition, this.position),
+ this._getOrientation(),
+ this._addCameraFrustum(),
+ this._addPostProcess()
+ }
+
+ _switchShow () {
+ this.show ? !this.postProcess && this._addPostProcess() : (this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess), delete this.postProcess, this.postProcess = null),
+ this.cameraFrustum.show = this.show,
+ this.cameraFrustumOutline.show = this.show
+ }
+
+ activeVideo () {
+ var video = this.element,
+ that = this
+ if (video) {
+ var viewer = this.viewer
+ this.activeVideoListener || (this.activeVideoListener = function () {
+ that.videoTexture && that.videoTexture.destroy(),
+ that.videoTexture = new Cesium.Texture({
+ context: viewer.scene.context,
+ source: video,
+ width: 1,
+ height: 1,
+ pixelFormat: Cesium.PixelFormat.RGBA,
+ pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE
+ })
+ }),
+ viewer.clock.onTick.addEventListener(this.activeVideoListener)
+ }
+ }
+
+ locate () {
+ var cameraPosition = Cesium.clone(this.cameraPosition),
+ position = Cesium.clone(this.position)
+ this.viewer.Camera.position = cameraPosition,
+ this.viewer.camera.direction = Cesium.Cartesian3.subtract(position, cameraPosition, new Cesium.Cartesian3(0, 0, 0)),
+ this.viewer.camera.up = Cesium.Cartesian3.normalize(cameraPosition, new Cesium.Cartesian3(0, 0, 0))
+ }
+
+ update (e) {
+ this.viewShadowMap && this.viewer.scene.frameState.shadowMaps.push(this.viewShadowMap) // *重点* 多投影
+ }
+
+ _createShadowMap () {
+ var e = Cesium.Cartesian3.fromDegrees(this.param.position.x * 1, this.param.position.y * 1, this.param.position.z * 1)
+ var t = this.position
+ var i = this.viewer.scene
+ var a = new Cesium.Camera(i)
+ a.position = e
+
+ this.customFrustum = new Cesium.PerspectiveFrustum({
+ fov: Cesium.Math.toRadians(this.fov),
+ aspectRatio: this.aspectRatio,
+ near: this.near,
+ far: 250
+ })
+
+ a.frustum = new Cesium.PerspectiveFrustum({
+ fov: Cesium.Math.toRadians(this.fov),
+ aspectRatio: this.aspectRatio,
+ near: this.near,
+ far: 250
+ })
+
+ let curOrientation = {}
+
+ const headingPitchRoll = new Cesium.HeadingPitchRoll(
+ Cesium.Math.toRadians(this.param.rotation.y, 0),
+ Cesium.Math.toRadians(this.param.rotation.x, 0),
+ Cesium.Math.toRadians(0),
+ )
+
+ curOrientation['heading'] = headingPitchRoll['heading']
+ curOrientation['pitch'] = headingPitchRoll['pitch']
+ curOrientation['roll'] = headingPitchRoll['roll']
+
+ let curDestination = {}
+
+ curDestination.destination = e
+ curDestination.orientation = curOrientation
+
+ a.setView(curDestination)
+
+ var n = Cesium.Cartesian3.distance(t, e)
+ this.viewDis = n
+
+ if (!this.isShadowMap) return
+
+ this.viewShadowMap = new Cesium.ShadowMap({
+ lightCamera: a,
+ enable: !1,
+ isPointLight: !1,
+ isSpotLight: !0,
+ cascadesEnabled: !1,
+ context: i.context,
+ pointLightRadius: n,
+ })
+ }
+
+ _getOrientation () {
+ const hpr = Cesium.HeadingPitchRoll.fromDegrees(180 + this.param.rotation.y * 1, 0, 90 - this.param.rotation.x * 1 || 0)
+ const orientation = Cesium.Transforms.headingPitchRollQuaternion(this.cameraPosition, hpr)
+
+ return this.orientation = orientation,
+ orientation
+ }
+
+ _addCameraFrustum () {
+ var e = this
+
+ this.cameraFrustum = new Cesium.Primitive({
+ geometryInstances: new Cesium.GeometryInstance({
+ geometry: new Cesium.FrustumGeometry({
+ origin: e.cameraPosition,
+ orientation: e.orientation,
+ frustum: e.customFrustum,
+ _drawNearPlane: !0
+ }),
+ attributes: {
+ color: Cesium.ColorGeometryInstanceAttribute.fromColor(
+ Cesium.Color.fromBytes(0, 213, 144, 20)
+ ),
+ }
+ }),
+ releaseGeometryInstances: false,
+ appearance: new Cesium.PerInstanceColorAppearance({
+ translucent: true,
+ flat: !0,
+ closed: true,
+ }),
+ asynchronous: false,
+ show: this.debugFrustum && this.show
+ })
+
+ this.cameraFrustumOutline = new Cesium.Primitive({
+ geometryInstances: new Cesium.GeometryInstance({
+ geometry: new Cesium.FrustumOutlineGeometry({
+ origin: e.cameraPosition,
+ orientation: e.orientation,
+ frustum: e.customFrustum,
+ _drawNearPlane: !0
+ }),
+ attributes: {
+ color: Cesium.ColorGeometryInstanceAttribute.fromColor(
+ Cesium.Color.fromBytes(0, 213, 144, 255)
+ ),
+ }
+ }),
+ appearance: new Cesium.PerInstanceColorAppearance({
+ closed: true,
+ flat: true,
+ translucent: true
+ }),
+ asynchronous: false,
+ show: this.debugFrustum && this.show
+ })
+
+ this.viewer.scene.primitives.add(this.cameraFrustum)
+ this.viewer.scene.primitives.add(this.cameraFrustumOutline)
+ }
+
+ _addPostProcess () {
+ if (!this.isShadowMap) return
+
+ var e = this,
+ t = videoShed3dShader
+
+ const i = e.viewShadowMap['_primitiveBias']
+
+ this.postProcess = new Cesium.PostProcessStage({
+ fragmentShader: t,
+ uniforms: {
+ ztzf_frustum_videoTexture: function () {
+ return e.videoTexture
+ },
+
+ ztzf_frustum_maskTexture: function () {
+ return new Cesium['Texture']({
+ 'context': e.viewer.scene.context,
+ 'source': {
+ 'width': 0x1,
+ 'height': 0x1,
+ 'arrayBufferView': new Uint8Array([255, 255, 255, 255])
+ },
+ 'flipY': ![]
+ })
+ },
+
+ ztzf_frustum_opacity: function () {
+ return e.alpha
+ },
+
+ ztzf_frustum_hiddenAreaColor: () => {
+ return new Cesium.Color(0, 0, 0, 0.5)
+ },
+
+ shadowMap_texture: function () {
+ if (!e.viewShadowMap || !e.viewShadowMap['_shadowMapTexture'] || e.viewShadowMap['_shadowMapTexture']['isDestroyed']()) {
+ return new Cesium['Texture']({
+ 'context': e.viewer.scene.context,
+ 'source': {
+ 'width': 0x1,
+ 'height': 0x1,
+ 'arrayBufferView': new Uint8Array([0x0, 0x0, 0x0, 0x0])
+ },
+ 'flipY': ![]
+ })
+ }
+
+ return e.viewShadowMap._shadowMapTexture
+ },
+
+ shadowMap_matrix: function () {
+ return e.viewShadowMap._shadowMapMatrix
+ },
+
+ shadowMap_lightPositionEC: function () {
+ return e.viewShadowMap._lightPositionEC
+ },
+
+ shadowMap_texelSizeDepthBias: function () {
+ var t = new Cesium.Cartesian2
+ return t.x = 1 / e.viewShadowMap._textureSize.x,
+ t.y = 1 / e.viewShadowMap._textureSize.y,
+ Cesium.Cartesian4.fromElements(t.x, t.y, i.depthBias, i.normalShadingSmooth, this.combinedUniforms1)
+ },
+
+ shadowMap_normalOffsetScale: function () {
+ return Cesium.Cartesian4.fromElements(i.normalOffsetScale, e.viewShadowMap._distance, e.viewShadowMap.maximumDistance, e.viewShadowMap._darkness, this.combinedUniforms2)
+ },
+
+ ztzf_frustum_flipx: () => {
+ return false
+ },
+
+ ztzf_frustum_flipy: () => {
+ return false
+ },
+
+ ztzf_frustum_borderColor: () => {
+ return new Cesium.Color(0 / 255, 213 / 255, 144 / 255, 255 / 255)
+ },
+
+ ztzf_frustum_borderWidth: () => {
+ return 0.01
+ },
+ }
+ }),
+ this.viewer.scene.postProcessStages.add(this.postProcess)
+ }
+
+ destroy () {
+ this.viewer.scene.postProcessStages.remove(this.postProcess),
+ this.viewer.scene.primitives.remove(this.cameraFrustum),
+ this.viewer.scene.primitives.remove(this.cameraFrustumOutline),
+ //this._videoEle && this._videoEle.parentNode.removeChild(this._videoEle),
+ this.activeVideoListener && this.viewer.clock.onTick.removeEventListener(this.activeVideoListener),
+ this.activeVideoListener && delete this.activeVideoListener,
+ delete this.postProcess,
+ delete this.viewShadowMap,
+ delete this.color,
+ delete this.viewDis,
+ delete this.cameraPosition,
+ delete this.position,
+ delete this.alpha,
+ delete this._camerafov,
+ delete this._isShadowMap,
+ delete this._cameraPosition,
+ delete this.videoTexture,
+ delete this.cameraFrustum,
+ delete this.cameraFrustumOutline,
+ delete this._videoEle,
+ delete this._debugFrustum,
+ delete this._position,
+ delete this._aspectRatio,
+ delete this.element,
+ delete this.orientation,
+ delete this.texture,
+ delete this.videoId,
+ this.viewer.scene.primitives.remove(this),
+ delete this.viewer
+ }
+}
+
+export default CesiumVideoFrustum
diff --git a/applications/task-work-order/src/utils/cesium/frustum/CoordinateTranslate.js b/applications/task-work-order/src/utils/cesium/frustum/CoordinateTranslate.js
new file mode 100644
index 0000000..24e0675
--- /dev/null
+++ b/applications/task-work-order/src/utils/cesium/frustum/CoordinateTranslate.js
@@ -0,0 +1,229 @@
+/*
+ * @Author : yuan
+ * @Date : 2025-08-15 14:06:49
+ * @LastEditors : yuan
+ * @LastEditTime : 2025-08-15 14:06:59
+ * @FilePath : \src\utils\cesium\frustum\CoordinateTranslate.js
+ * @Description :
+ * Copyright 2025 OBKoro1, All Rights Reserved.
+ * 2025-08-15 14:06:49
+ */
+let ECEF = (function () {
+ var _ = function () {
+ this.PI = 3.141592653589793238
+ this.a = 6378137.0
+ this.b = 6356752.3142
+ this.f = (this.a - this.b) / this.a
+ this.e_sq = this.f * (2.0 - this.f)
+ this.ee = 0.00669437999013
+ this.WGSF = 1 / 298.257223563
+ this.WGSe2 = this.WGSF * (2 - this.WGSF)
+ this.WGSa = 6378137.00000
+ this.EPSILON = 1.0e-12
+ }
+ _.prototype.CalculateCoordinates = function (point, azimuth, elevation, distance) {
+ var vertical_height = distance * Math.sin(2 * this.PI / 360 * elevation)//垂直高度
+ var horizontal_distance = distance * Math.cos(2 * this.PI / 360 * elevation)//水平距离
+ if (azimuth > 360) azimuth = azimuth % 360
+ if (azimuth < 0) azimuth = 360 + (azimuth % 360)
+
+ var point1 = this.lonLat2WebMercator(point)
+ var lnglat = null
+
+ var x_length, y_length
+ if (azimuth <= 90) {//第四象限
+ x_length = horizontal_distance * Math.cos(2 * this.PI / 360 * azimuth)
+ y_length = horizontal_distance * Math.sin(2 * this.PI / 360 * azimuth)
+ lnglat = {
+ x: point1.x + x_length,
+ y: point1.y - y_length
+ }
+ } else if (azimuth > 90 && azimuth <= 180) {//第三象限
+ x_length = horizontal_distance * Math.sin(2 * this.PI / 360 * (azimuth - 90))
+ y_length = horizontal_distance * Math.cos(2 * this.PI / 360 * (azimuth - 90))
+ lnglat = {
+ x: point1.x - x_length,
+ y: point1.y - y_length
+ }
+ } else if (azimuth > 180 && azimuth <= 270) {//第二象限
+ x_length = horizontal_distance * Math.cos(2 * this.PI / 360 * (azimuth - 180))
+ y_length = horizontal_distance * Math.sin(2 * this.PI / 360 * (azimuth - 180))
+ lnglat = {
+ x: point1.x - x_length,
+ y: point1.y + y_length
+ }
+ } else {//第一象限
+ x_length = horizontal_distance * Math.sin(2 * this.PI / 360 * (azimuth - 270))
+ y_length = horizontal_distance * Math.cos(2 * this.PI / 360 * (azimuth - 270))
+ lnglat = {
+ x: point1.x + x_length,
+ y: point1.y + y_length
+ }
+ }
+ lnglat = this.webMercator2LonLat(lnglat)
+ return {
+ lng: lnglat.x,
+ lat: lnglat.y,
+ height: vertical_height
+ }
+ }
+ /*
+ *经纬度转Web墨卡托
+ *@lonLat 经纬度
+ */
+ _.prototype.lonLat2WebMercator = function (lonLat) {
+ let x = lonLat.x * this.a / 180
+ let y = Math.log(Math.tan((90 + lonLat.y) * this.PI / 360)) / (this.PI / 180)
+ y = y * this.a / 180
+ return {
+ x: x,
+ y: y
+ }
+ }
+
+ /*
+ *Web墨卡托转经纬度
+ *@mercator 平面坐标
+ */
+ _.prototype.webMercator2LonLat = function (mercator) {
+ let x = mercator.x / this.a * 180
+ let y = mercator.y / this.a * 180
+ y = 180 / this.PI * (2 * (Math.exp(y * this.PI / 180)) - this.PI / 2)
+ return {
+ x: x,
+ y: y
+ }
+ }
+
+ _.prototype.get_atan = function (z, y) {
+ let x
+ if (z == 0) {
+ x = this.PI / 2
+ } else {
+ if (y == 0) {
+ x = this.PI
+ } else {
+ x = Math.atan(Math.abs(y / z))
+ if ((y > 0) && (z < 0)) {
+ x = this.PI - x
+ } else if ((y < 0) && (z < 0)) {
+ x = this.PI + x
+ } else if ((y < 0) && (z > 0)) {
+ x = 2 * this.M_PI - x
+ }
+ }
+ }
+ return x
+ }
+ //WGS84转ECEF坐标系
+ _.prototype.ConvertLLAToXYZ = function (LLACoor) {
+ let lon = this.PI / 180 * LLACoor.longitude
+ let lat = this.PI / 180 * LLACoor.latitude
+ let H = LLACoor.altitude
+ let N0 = this.a / Math.sqrt(1.0 - this.ee * Math.sin(lat) * Math.sin(lat))
+ let x = (N0 + H) * Math.cos(lat) * Math.cos(lon)
+ let y = (N0 + H) * Math.cos(lat) * Math.sin(lon)
+ let z = (N0 * (1.0 - this.ee) + H) * Math.sin(lat)
+ return {
+ x: x,
+ y: y,
+ z: z
+ }
+ }
+
+ //ECEF坐标系转WGS84
+ _.prototype.ConvertXYZToLLA = function (XYZCoor) {
+ let longitude = this.get_atan(XYZCoor.x, XYZCoor.y)
+ if (longitude < 0) {
+ longitude = longitude + this.PI
+ }
+ let latitude = this.get_atan(Math.sqrt(XYZCoor.x * XYZCoor.x + XYZCoor.y * XYZCoor.y), XYZCoor.z)
+
+ let W = Math.sqrt(1 - this.WGSe2 * Math.sin(latitude) * Math.sin(latitude))
+ let N = this.WGSa / W
+ let B1
+ do {
+ B1 = latitude
+ W = Math.sqrt(1 - this.WGSe2 * Math.sin(B1) * Math.sin(B1))
+ N = this.WGSa / W
+ latitude = this.get_atan(Math.sqrt(XYZCoor.x * XYZCoor.x + XYZCoor.y * XYZCoor.y), (XYZCoor.z + N * this.WGSe2 * Math.sin(B1)))
+ }
+ while (Math.abs(latitude - B1) > this.EPSILON)
+
+ var altitude = Math.sqrt(XYZCoor.x * XYZCoor.x + XYZCoor.y * XYZCoor.y) / Math.cos(latitude) - this.WGSa / Math.sqrt(1 - this.WGSe2 * Math.sin(latitude) * Math.sin(latitude))
+
+ return {
+ longitude: longitude * 180 / this.PI,
+ latitude: latitude * 180 / this.PI,
+ altitude: altitude
+ }
+ }
+ /*北东天坐标系转WGS84
+ @ a A点坐标
+ @ p 相对参数,距离、方位角、仰角
+ */
+ // 俯视角pitch -elevation
+ //航向角heading(yaw) -azimuth
+ _.prototype.enu_to_ecef = function (a, p) {
+ //距离
+ let distance = p.distance
+ //方位角
+ let azimuth = p.azimuth
+ //仰角
+ let elevation = p.elevation
+
+ let zUp = elevation >= 0 ? distance * Math.sin(this.PI / 180 * elevation) : (-1) * distance * Math.sin(this.PI / 180 * Math.abs(elevation))
+
+ let d = distance * Math.cos(this.PI / 180 * Math.abs(elevation))
+ let xEast
+ let yNorth
+ if (azimuth <= 90) {
+ xEast = d * Math.sin(this.PI / 180 * azimuth)
+ yNorth = d * Math.cos(this.PI / 180 * azimuth)
+ } else if (azimuth > 90 && azimuth < 180) {
+ xEast = d * Math.cos(this.PI / 180 * (azimuth - 90))
+ yNorth = (-1) * d * Math.sin(this.PI / 180 * (azimuth - 90))
+ } else if (azimuth > 180 && azimuth < 270) {
+ xEast = (-1) * d * Math.sin(this.PI / 180 * (azimuth - 180))
+ yNorth = (-1) * d * Math.cos(this.PI / 180 * (azimuth - 180))
+ } else {
+ xEast = (-1) * d * Math.sin(this.PI / 180 * (360 - azimuth))
+ yNorth = d * Math.cos(this.PI / 180 * (360 - azimuth))
+ }
+
+ let lamb = this.radians(a.latitude)
+ let phi = this.radians(a.longitude)
+ let h0 = a.altitude
+
+ let s = Math.sin(lamb)
+ let N = this.a / Math.sqrt(1.0 - this.e_sq * s * s)
+
+ let sin_lambda = Math.sin(lamb)
+ let cos_lambda = Math.cos(lamb)
+
+ let sin_phi = Math.sin(phi)
+ let cos_phi = Math.cos(phi)
+
+ let x0 = (h0 + N) * cos_lambda * cos_phi
+ let y0 = (h0 + N) * cos_lambda * sin_phi
+ let z0 = (h0 + (1 - this.e_sq) * N) * sin_lambda
+
+ let t = cos_lambda * zUp - sin_lambda * yNorth
+
+ let zd = sin_lambda * zUp + cos_lambda * yNorth
+ let xd = cos_phi * t - sin_phi * xEast
+ let yd = sin_phi * t + cos_phi * xEast
+
+ return this.ConvertXYZToLLA({
+ x: xd + x0,
+ y: yd + y0,
+ z: zd + z0
+ })
+ }
+ _.prototype.radians = function (degree) {
+ return this.PI / 180 * degree
+ }
+ return _
+})()
+
+export default ECEF
\ No newline at end of file
diff --git a/applications/task-work-order/src/utils/cesium/kmz.js b/applications/task-work-order/src/utils/cesium/kmz.js
index a6cf77f..79e56ea 100644
--- a/applications/task-work-order/src/utils/cesium/kmz.js
+++ b/applications/task-work-order/src/utils/cesium/kmz.js
@@ -209,6 +209,11 @@
}
+/**
+ * 递归处理对象,移除 #text 键并返回其值,同时处理数值字符串到数字的转换
+ * @param {any} obj - 需要处理的对象或值
+ * @returns {any} 处理后的对象或值
+ */
export function removeTextKey(obj) {
if (typeof obj !== 'object' || obj === null) {
// 如果是数值字符串,转换为数值
@@ -219,18 +224,21 @@
}
if (Array.isArray(obj)) {
+ // 递归处理数组中的每个元素
return obj.map(item => removeTextKey(item));
}
if (Object.prototype.hasOwnProperty.call(obj, '#text')) {
- // 如果 #text 的值是数值字符串,转换为数值
+ // 如果存在 #text 属性,返回其值(如果是数值字符串则转换为数字)
const textValue = obj['#text'];
return typeof textValue === 'string' && /^-?\d+(\.\d+)?$/.test(textValue) ? Number(textValue) : textValue;
}
+ // 递归处理对象属性
const newObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ // 如果属性值是空对象,则设为空字符串;否则递归处理
newObj[key] = Object.keys(obj[key]).length === 0 ? '' : removeTextKey(obj[key]);
}
}
diff --git a/applications/task-work-order/src/utils/cesium/mapUtil.js b/applications/task-work-order/src/utils/cesium/mapUtil.js
index eabf803..89bf787 100644
--- a/applications/task-work-order/src/utils/cesium/mapUtil.js
+++ b/applications/task-work-order/src/utils/cesium/mapUtil.js
@@ -1,7 +1,8 @@
import * as Cesium from 'cesium'
import * as turf from '@turf/turf'
-const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
+import store from '@/store'
+const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
export const getLngLatDistance = (lat1, lng1, lat2, lng2) => {
const radLat1 = (lat1 * Math.PI) / 180.0
const radLat2 = (lat2 * Math.PI) / 180.0
@@ -190,6 +191,66 @@
const newArr = arr.sort((a, b) => a.distance - b.distance)
return newArr[0]
}
+// 获取多边形内部数据
+export const samplePolygonInterior = (positions, samplingDistance) => {
+ if (!positions.length) return []
+ let minLng = Infinity,
+ maxLng = -Infinity
+ let minLat = Infinity,
+ maxLat = -Infinity
+ positions.forEach(pos => {
+ minLng = Math.min(minLng, pos.lng)
+ maxLng = Math.max(maxLng, pos.lng)
+ minLat = Math.min(minLat, pos.lat)
+ maxLat = Math.max(maxLat, pos.lat)
+ })
+
+ // 将 Cesium 多边形格式转为 Turf 格式
+ const turfPolygonCoords = positions.map(pos => [pos.lng, pos.lat])
+ turfPolygonCoords.push([positions[0].lng, positions[0].lat]) // 闭合多边形
+ const turfPolygon = turf.polygon([turfPolygonCoords])
+
+ // 生成网格点并判断是否在多边形内
+ const sampledPoints = []
+ for (let lng = minLng; lng <= maxLng; lng += samplingDistance) {
+ for (let lat = minLat; lat <= maxLat; lat += samplingDistance) {
+ // 创建 Turf 点
+ const turfPoint = turf.point([lng, lat])
+ // Turf 原生方法判断点是否在多边形内
+ if (turf.booleanPointInPolygon(turfPoint, turfPolygon)) {
+ sampledPoints.push({
+ lng: Number(lng.toFixed(6)),
+ lat: Number(lat.toFixed(6)),
+ height: 0,
+ })
+ }
+ }
+ }
+
+ return sampledPoints
+}
+// 获取多边形面内的最高点(相对地形高度)
+export const getPolygonHighestPoint = async (positions, viewer, samplingDistance = 0.0002) => {
+ try {
+ const interiorPoints = samplePolygonInterior(positions, samplingDistance, viewer)
+ const allPoints = [...positions, ...interiorPoints]
+ // 批量获取所有点的地形高度
+ const pointsWithTerrain = await getPointPositionsHeight(allPoints, viewer)
+ // console.log('所有点的地形高度', pointsWithTerrain)
+
+ // 地形高度(ASL)最高的点
+ const highestTerrainPoint = pointsWithTerrain.reduce(
+ (max, curr) => (curr.ASL > max.ASL ? curr : max),
+ pointsWithTerrain[0]
+ )
+
+ // console.log('地形最高点信息:', highestTerrainPoint)
+ return highestTerrainPoint
+ } catch (error) {
+ console.error('获取地形最高点失败:', error)
+ return null
+ }
+}
// 获取当前经纬度地形数据
export const getLnglatAltitude = (longitude, latitude, viewer) => {
@@ -285,31 +346,24 @@
return new Promise((resolve, reject) => {
if (!data || !data.length) {
resolve([])
-
return
}
-
// 假设 viewer 已经初始化并且 terrainProvider 是有效的
const terrainProvider = viewer?.terrainProvider
-
// 创建 Cartographic 对象
const cartographics = data.map(item => {
const { lng, lat } = item
-
return Cesium.Cartographic.fromDegrees(Number(lng), Number(lat))
})
-
// 获取地形数据的Promise
const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartographics)
-
// 使用 Cesium.when 处理 Promise
promise
.then(function (updatedPositions) {
- // updatedPositions 是一个数组,包含更新后的 Cartographic 对象
+ // 是一个数组,包含更新后的 Cartographic 对象
const newPosition = updatedPositions.map((item, index) => {
const longitude = Cesium.Math.toDegrees(item.longitude)
const latitude = Cesium.Math.toDegrees(item.latitude)
-
let pointData = {
...data[index],
longitude,
@@ -317,18 +371,14 @@
ASL: Number(item.height),
customHeight: Number(item.height),
}
-
if (droneHeight) pointData.TH = Number(item.height) + Number(droneHeight)
-
return pointData
})
-
resolve(newPosition)
- // 在这里,你可以使用 height 值进行后续操作
})
.catch(function (error) {
- // console.error('获取高程时发生错误:', error);
- reject(error)
+ console.log('获取高程时发生错误:', error)
+ resolve(data.map(item => ({ ...item, ASL: 0, customHeight: 0, longitude: item.lng, latitude: item.lat })))
})
})
}
@@ -410,11 +460,15 @@
if (!Array.isArray(positionsData) || positionsData.length === 0) return
// 如果是一个点,加两个点方便后续生成外包围盒
- if (positionsData.length === 1){
- const [lon,lat,height] = positionsData[0]
- positionsData = [[lon+0.001,lat+0.001,height],[lon-0.001,lat-0.001,height],[lon,lat,height]]
+ if (positionsData.length === 1) {
+ const [lon, lat, height = 0] = positionsData[0]
+ positionsData = [
+ [lon + 0.001, lat + 0.001, height],
+ [lon - 0.001, lat - 0.001, height],
+ [lon, lat, height],
+ ]
}
- const positions = positionsData.map(([lon, lat, height]) =>
+ const positions = positionsData.map(([lon, lat, height = 0]) =>
Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat), Number(height || 0))
)
@@ -439,7 +493,7 @@
range: boundingSphere.radius * multiple,
},
complete: () => {
- // 飞行完成后检查相机高度是否小于最高点,如果小于直接飞到最高点
+ // 飞行完成后检查相机高度是否小于最高点
const cameraHeight = Cesium.Cartographic.fromCartesian(viewer.camera.position).height
if (cameraHeight < maxHeight) {
viewer.camera.flyTo({
@@ -529,7 +583,7 @@
// 辅助函数:创建旋转文字的画布
export function createRotatedTextCanvas(time) {
- const prefix = '预计'
+ const prefix = '预计到达'
const suffix = '分钟'
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
@@ -537,7 +591,9 @@
context.font = 'bold 16px Source Han Sans CN'
// 测量各部分文本宽度
const prefixWidth = context.measureText(prefix).width
- const timeWidth = context.measureText(time).width
+ let timeWidth = context.measureText(time).width
+ const timeWidthMore = `${time >= 10 ? 6 : 0}`
+ timeWidth = timeWidth + Number(timeWidthMore)
const suffixWidth = context.measureText(suffix).width
const totalWidth = prefixWidth + timeWidth + suffixWidth
const textHeight = 20 // 估算高度
@@ -550,17 +606,116 @@
context.textAlign = 'left' // 改为左对齐,方便分段绘制
// 清除画布
context.clearRect(0, 0, canvas.width, canvas.height)
- // 绘制“预计”(白色)
+ // 绘制“预计到达”(白色)
context.fillStyle = '#FFFFFF'
context.fillText(prefix, 10, 15)
// 绘制时间(蓝色)
- context.font = 'bold 30px Source Han Sans CN'
+ context.font = 'bold 24px Source Han Sans CN'
context.fillStyle = '#1EE7E7' // 亮蓝色
- context.fillText(time, 10 + prefixWidth, 12)
+ // 设置宽度
+ context.textBaseline = 'middle'
+ context.fillText(time, 10 + prefixWidth, 14)
// 绘制“分钟”(白色)
context.font = 'bold 16px Source Han Sans CN'
context.fillStyle = '#FFFFFF'
- context.fillText(suffix, 20 + prefixWidth + timeWidth, 15)
+ context.fillText(suffix, 14 + prefixWidth + timeWidth, 15)
return canvas
}
+
+
+export const saveCurrentCameraPosition = viewer => {
+ const position = viewer?.camera.position.clone() // 相机位置(Cartesian3)
+ const heading = viewer?.camera.heading // 航向角(弧度)
+ const pitch = viewer?.camera.pitch // 俯仰角(弧度)
+ const roll = viewer?.camera.roll // 翻滚角(弧度)
+ store.commit('setCameraPosition', {
+ duration: 0,
+ destination: position,
+ orientation: { heading, pitch, roll },
+ })
+}
+
+// 连接地面线
+export function getDockPolyLine(data, viewer) {
+ if (!data.longitude || !data.latitude) return {}
+ return {
+ positions: new Cesium.CallbackProperty(() => {
+ const pointPosition = Cesium.Cartesian3.fromDegrees(+data.longitude, +data.latitude, +data.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,
+ }
+}
+
+// 飞向一个机巢中心
+export const flyDockCenter = (viewer, info) => {
+ const { longitude = 115, latitude = 28, height = 0 } = info
+ const position = Cesium.Cartesian3.fromDegrees(+longitude, +latitude, +height)
+ const droneEntity = viewer?.entities.add({ position: position, point: { pixelSize: 0 } })
+ viewer?.flyTo(droneEntity, {
+ offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-60), 22000),
+ duration: 0.5,
+ complete: () => {
+ viewer?.entities.remove(droneEntity)
+ },
+ })
+}
+
+// 在固定的经度上获得高度
+export function getHeightOnFixedLonLat(ray, pointParams, ellipsoid = Cesium.Ellipsoid.WGS84) {
+ const { longitude: lon, latitude: lat } = pointParams
+ // 1. 地表点(高度=0)
+ const p0 = Cesium.Cartesian3.fromDegrees(lon, lat, 0, ellipsoid)
+
+ // 2. 该点处的法线方向(up 向量,单位向量)
+ const up = ellipsoid.geodeticSurfaceNormal(p0, new Cesium.Cartesian3())
+
+ // 3. 射线起点和方向
+ const r0 = ray.origin // 相机位置
+ const rd = ray.direction // 射线方向(已归一化?不一定,但不影响)
+
+ // 4. 解:求直线 L(t) = p0 + t * up 与射线 R(s) = r0 + s * rd 的最近点
+ // 使用向量公式求两条直线的最近点参数 t
+ const w0 = Cesium.Cartesian3.subtract(p0, r0, new Cesium.Cartesian3())
+
+ const a = Cesium.Cartesian3.dot(up, up) // = 1(因为 up 是单位向量)
+ const b = Cesium.Cartesian3.dot(up, rd)
+ const c = Cesium.Cartesian3.dot(rd, rd) // = |rd|^2
+ const d = Cesium.Cartesian3.dot(up, w0)
+ const e = Cesium.Cartesian3.dot(rd, w0)
+
+ const denom = a * c - b * b
+ let t
+
+ if (Math.abs(denom) < Cesium.Math.EPSILON10) {
+ // 两直线平行,取 t = 0
+ t = 0
+ } else {
+ t = (b * e - c * d) / denom
+ }
+
+ // 5. 得到高度线上对应的点
+ const resultPoint = Cesium.Cartesian3.add(
+ p0,
+ Cesium.Cartesian3.multiplyByScalar(up, t, new Cesium.Cartesian3()),
+ new Cesium.Cartesian3()
+ )
+
+ // 6. 转回 Cartographic 获取精确高度
+ const carto = Cesium.Cartographic.fromCartesian(resultPoint, ellipsoid)
+ return {
+ cartesian: resultPoint,
+ height: carto.height,
+ longitude: Cesium.Math.toDegrees(carto.longitude),
+ latitude: Cesium.Math.toDegrees(carto.latitude),
+ }
+}
diff --git a/applications/task-work-order/src/utils/cesium/publicCesium.js b/applications/task-work-order/src/utils/cesium/publicCesium.js
index 7af504f..e0fe177 100644
--- a/applications/task-work-order/src/utils/cesium/publicCesium.js
+++ b/applications/task-work-order/src/utils/cesium/publicCesium.js
@@ -70,6 +70,7 @@
* @param {boolean} [options.flyToContour=false] - 是否飞行到轮廓,默认为 false
* @param {number} [options.multiple=1.4] - 范围倍数
* @param {number} [options.dockOptions={}] - 机巢显示
+ * @param {array} [options.terrainLoadCallback] - 地形加载回调
* @param {array} [options.boundaryChange] - 边界改变事件
* @param {array} [options.boundaryColor] - 边界颜色
* @param {array} [options.dockRangeType] - 机巢范围类型 1显示覆盖圆;2显示覆盖圈
@@ -92,15 +93,14 @@
dom,
flatMode = true,
terrain = false,
- layerMode = 0,
+ layerMode = 17,
contour = true,
flyToContour = false,
multiple = 1.4,
-
dockOptions = {},
-
- boundaryColor = '#7FFFD4',
boundaryChange,
+ terrainLoadCallback,
+ boundaryColor = '#7FFFD4',
dockRangeType = 1,
useDockHeight = false
} = options
@@ -120,6 +120,10 @@
baseLayer: false,
fullscreenButton: false,
}
+
+ // 尝试解决出现多个id相同的dom
+ const domIdList = document.querySelectorAll(`#${dom}`)
+ if (domIdList.length>1) domIdList[1].remove()
this.viewer = new Viewer(dom, viewerOptions)
this.viewerDom = dom
@@ -143,33 +147,47 @@
this.viewer?.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) // 禁用双击
if (terrain) {
- // 正则:第一位非0,第二位任意数字,后10位都是0
- const re = /^[1-9]\d0{10}$/
-
- let result = null
-
- // 1️⃣先判断 areaCode
- if (re.test(store.state.user.userInfo.detail.areaCode)) {
- result = store.state.user.userInfo.detail.areaCode.slice(0, 6)
- } else {
- // 2️⃣如果不满足,从 ancestors 里找
- const matchedItems = store.state.user.userInfo.detail.ancestors
- .split(',')
- .filter(item => re.test(item)) // 过滤出符合条件的
-
- // 取第一个符合条件的前6位(可以改成 last one 看需求)
- if (matchedItems.length > 0) {
- result = matchedItems[0].slice(0, 6)
- }
- }
-
try {
- Cesium.CesiumTerrainProvider.fromUrl(`${import.meta.env.VITE_APP_TERRAIN_URL}${result}`, {
- requestVertexNormals: true, // 启用地形法线增强立体感
- requestWaterMask: true, // 启用水体遮罩效果
- }).then(terrainProvider => {
- this.viewer.terrainProvider = terrainProvider
- })
+ const noTerrainMechanism = ['1962779164650135554']
+ if (noTerrainMechanism.includes(store.state.user.userInfo?.deptId)) {
+ // 使用Cesium World Terrain
+ Cesium.createWorldTerrainAsync({
+ requestWaterMask: false, // 请求水域效果
+ requestVertexNormals: true // 请求光照和地形法线
+ }).then(terrainProvider => {
+ this.viewer.terrainProvider = terrainProvider
+ terrainLoadCallback?.()
+ })
+ } else {
+ /*// 正则:第一位非0,第二位任意数字,后10位都是0
+ const re = /^[1-9]\d0{10}$/
+
+ let result = null
+
+ // 1️⃣先判断 areaCode
+ if (re.test(store.state.user.userInfo.detail.areaCode)) {
+ result = store.state.user.userInfo.detail.areaCode.slice(0, 6)
+ } else {
+ // 2️⃣如果不满足,从 ancestors 里找
+ const matchedItems = store.state.user.userInfo.detail.ancestors
+ .split(',')
+ .filter(item => re.test(item)) // 过滤出符合条件的
+
+ // 取第一个符合条件的前6位(可以改成 last one 看需求)
+ if (matchedItems.length > 0) {
+ result = matchedItems[0].slice(0, 6)
+ }
+ }
+
+ // 使用公司地形
+ Cesium.CesiumTerrainProvider.fromUrl(`${import.meta.env.VITE_APP_TERRAIN_URL}${result}`, {
+ requestVertexNormals: true, // 启用地形法线增强立体感
+ requestWaterMask: true, // 启用水体遮罩效果
+ }).then(terrainProvider => {
+ this.viewer.terrainProvider = terrainProvider
+ terrainLoadCallback?.()
+ })*/
+ }
} catch (error) {
console.error('地形加载失败:', error)
}
@@ -180,6 +198,12 @@
this.switchFlatMode(flatMode)
this.switchContour(contour)
flyToContour && this.flyToContour(contour)
+
+ if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
+ let dpr = window.devicePixelRatio
+ while (dpr >= 2.0) dpr /= 2.0 // 避免过高缩放导致模糊
+ this.viewer.resolutionScale = dpr // 设置分辨率缩放比例
+ }
}
getViewer () {
@@ -188,53 +212,69 @@
// 销毁viewer
viewerDestroy (removeDom = true) {
+ if (!this.viewer) return
+ // // 👇 关键:先停止所有动态行为
+ // this.viewer.clock.shouldAnimate = false;
+ // this.viewer.scene.requestRenderMode = false;
+ // // 取消所有可能的 pending 渲染
+ // if (this.viewer.cesiumWidget) {
+ // this.viewer.cesiumWidget.render = () => {}; // 空函数覆盖
+ // }
+ // // 清理实体和图层
+ // this.viewer?.entities.removeAll();
+ // this.viewer?.imageryLayers.removeAll();
+ // // 清理数据源
+ // try {
+ // const dataSources = this.viewer.dataSources;
+ // while (dataSources.length > 0) {
+ // dataSources.remove(dataSources.get(0));
+ // }
+ // } catch (e) {
+ // console.warn('清理数据源时出错:', e);
+ // }
+ // // 清理场景中的图元
+ // try {
+ // this.viewer?.scene.primitives.removeAll();
+ // } catch (e) {
+ // console.warn('清理图元时出错:', e);
+ // }
+ // // 获取webgl上下文
+ // let gl = this.viewer?.scene.context._originalGLContext
+ // if (gl) {
+ // try {
+ // gl.canvas.width = 1
+ // gl.canvas.height = 1
+ // gl.getExtension("WEBGL_lose_context").loseContext()
+ // } catch (e) {
+ // console.warn('释放WebGL上下文时出错:', e)
+ // }
+ // gl = null
+ // }
+ this.viewer?.destroy() // 销毁Viewer实例
+ this.viewer = null
smallMpaCenterDistance = null
-
- if (this.viewer) {
- this.viewer?.entities.removeAll()
- this.viewer?.imageryLayers.removeAll()
- this.viewer?.dataSources.removeAll()
-
-
- // try {
- // this.viewer?.scene?.primitives?.removeAll()
- // this.viewer?.entities?.removeAll()
- // } catch (e) {
- // console.warn('清理资源时出错:', e)
-
- // console.log(this.viewer, 1111111111111)
- // }
-
- // 获取webgl上下文
- let gl = this.viewer?.scene.context._originalGLContext
- gl.canvas.width = 1
- gl.canvas.height = 1
- gl.getExtension("WEBGL_lose_context").loseContext()
- gl = null
- this.viewer?.destroy() // 销毁Viewer实例
- this.viewer = null
- var cesiumContainer = document.getElementById(this.viewerDom)
- if (cesiumContainer && removeDom) {
- cesiumContainer.remove() // 移除与地图相关的DOM元素
- this.viewerDom = null
- }
+ var cesiumContainer = document.getElementById(this.viewerDom)
+ if (cesiumContainer && removeDom) {
+ cesiumContainer.remove() // 移除与地图相关的DOM元素
+ this.viewerDom = null
}
}
setShowDock (sns) {
this.boundary?.setShowDock(sns)
}
-
+ setDockCoverColor (sns) {
+ this.boundary?.setDockCoverColor(sns)
+ }
// 切换轮廓显示
async switchContour (open) {
if (open) {
await this.boundary?.openContour()
-
} else {
this.boundary?.closeContour()
}
}
- // // 飞向轮廓居中
+ // 飞向轮廓居中
flyToContour () {
this.boundary?.flyToBoundary()
}
@@ -257,7 +297,7 @@
this.globalBaseMapLayers?.forEach(item => {
if (item.mapLayer) item.mapLayer.show = false
})
- // store.state.common.mapSetting.mode = mode
+ store.state.common.mapSetting.mode = mode
// 标准地图(天地图矢量)加载
if (mode === 0) {
mapLayers.push(
@@ -324,9 +364,7 @@
this.viewer?.imageryLayers.lowerToBottom(find.mapLayer)
})
}
-
// 设置或重置滤镜
-
setCurrentLayerFilter (data, mode) {
if (mode === 17) {
data.mapLayer.brightness = 0.6 // 亮度(默认值)
diff --git a/applications/task-work-order/src/utils/cesium/use-kmz-tsa.js b/applications/task-work-order/src/utils/cesium/use-kmz-tsa.js
new file mode 100644
index 0000000..62ba213
--- /dev/null
+++ b/applications/task-work-order/src/utils/cesium/use-kmz-tsa.js
@@ -0,0 +1,675 @@
+import _ from 'lodash'
+import JSZIP from 'jszip'
+import { JSONToXML } from './kmz'
+import { getDateFromTimestamp } from '@/utils/date'
+
+export default function kmzFile () {
+ // 云台信息
+ const droneModel = {
+ 52: '0',
+ 53: '1',
+ 66: '0',
+ 67: '1',
+ 80: '0',
+ 81: '1',
+ }
+ // 创建图斑航线
+ const create = async (waylineBasicInfo, lnglats, spotList) => {
+ try {
+ const date = new Date()
+ const username = window.localStorage.getItem('bs_username')
+ const updateTime = date.getTime()
+ const fileName = '图斑航线规划_' + date.getTime()
+ // 无人机机型、云台型号、航线高度
+ const { droneEnumValue, payloadEnumValue, height } = waylineBasicInfo
+ const defaultHeight = height || 100
+ // 起始点位 (参考点位-具体以机场为准)
+ const startPoint = lnglats[0]
+ const takeOffPoint = [startPoint[1], startPoint[0], 0].join(',')
+ // template.xml对象模版
+ const fileTemplate = {
+ author: { '#text': username },
+ createTime: { '#text': updateTime },
+ updateTime: { '#text': updateTime },
+ missionConfig: {
+ // 飞向首航点模式
+ flyToWaylineMode: { '#text': 'safely' },
+ // 航线结束动作
+ finishAction: { '#text': 'goHome' },
+ // 失控是否继续执行航线
+ exitOnRCLost: { '#text': 'goContinue' },
+ // 失控动作类型
+ executeRCLostAction: { '#text': 'goBack' },
+ // 安全起飞高度
+ takeOffSecurityHeight: { '#text': 20 },
+ // 参考起飞点
+ takeOffRefPoint: { '#text': takeOffPoint },
+ // 参考起飞点绝对高度
+ takeOffRefPointAGLHeight: { '#text': 0 },
+ // 全局航线过渡速度
+ globalTransitionalSpeed: { '#text': 10 },
+ // 全局返航高度
+ globalRTHHeight: { '#text': defaultHeight },
+ // 无人机参数
+ droneInfo: {
+ droneEnumValue: { '#text': droneEnumValue },
+ droneSubEnumValue: {
+ '#text': droneModel[payloadEnumValue],
+ },
+ },
+ // 云台参数
+ payloadInfo: {
+ payloadEnumValue: { '#text': payloadEnumValue },
+ payloadSubEnumValue: {
+ '#text': droneModel[payloadEnumValue],
+ },
+ payloadPositionIndex: { '#text': 0 },
+ },
+ },
+ Folder: {
+ // 预定义模板类型 - 航点飞行
+ templateType: { '#text': 'waypoint' },
+ // 是否使用全局航线过渡速度
+ useGlobalTransitionalSpeed: { '#text': 0 },
+ templateId: { '#text': 0 },
+ // 坐标系参数信息
+ waylineCoordinateSysParam: {
+ // 经纬度坐标系 - 固定值
+ coordinateMode: { '#text': 'WGS84' },
+ // 航点高程参考平面 - 相对高度
+ heightMode: { '#text': 'relativeToStartPoint' },
+ },
+ // 全局航线飞行速度
+ autoFlightSpeed: { '#text': 10 },
+ // 云台俯仰角控制模式
+ gimbalPitchMode: { '#text': 'usePointSetting' },
+ // 全局高度
+ globalHeight: { '#text': '100' },
+ // 全局偏航角模式参数 - 可使用当前默认值
+ globalWaypointHeadingParam: {
+ waypointHeadingMode: { '#text': 'followWayline' },
+ waypointHeadingAngle: { '#text': 0 },
+ waypointPoiPoint: {
+ '#text': '0.000000,0.000000,0.000000',
+ },
+ waypointHeadingPathMode: { '#text': 'followBadArc' },
+ waypointHeadingPoiIndex: { '#text': 0 },
+ },
+ // 全局航点类型 - 直线飞行,飞行器到点停
+ globalWaypointTurnMode: {
+ '#text': 'toPointAndStopWithDiscontinuityCurvature',
+ },
+ // 全局航段轨迹是否尽量贴合直线
+ globalUseStraightLine: { '#text': 1 },
+ // 航点点位信息 - 位置、事件等
+ Placemark: [],
+ // payloadParam: {
+ // payloadPositionIndex: { '#text': 0 },
+ // focusMode: { '#text': 'firstPoint' },
+ // meteringMode: { '#text': 'average' },
+ // returnMode: { '#text': 'singleReturnFirst' },
+ // samplingRate: { '#text': '240000' },
+ // scanningMode: { '#text': 'repetitive' },
+ // imageFormat: { '#text': 'wide,zoom' },
+ // },
+ },
+ }
+ // 航点创建
+ const polygonNo = {
+ no: '',
+ index: 0,
+ }
+ lnglats.forEach((lnglat, index) => {
+ const obj = spotList.find(spot => spot.dkfw.find(item => _.isEqual(item, lnglat)))
+ let fileSuffix = ''
+ if (obj) {
+ if (polygonNo.no === obj.dkbh) {
+ polygonNo.index++
+ } else {
+ polygonNo.no = obj.dkbh
+ polygonNo.index = 1
+ }
+ fileSuffix = `图斑航线拍摄(${obj.dkbh}-${polygonNo.index})`
+ } else {
+ fileSuffix = '图斑航线拍摄(起点)'
+ }
+ const placemark = {
+ Point: {
+ // 航点经纬度
+ coordinates: { '#text': `${lnglat[0]},${lnglat[1]}` },
+ },
+ // 航点index
+ index: { '#text': index },
+ // 全局航线高度(椭球高)
+ ellipsoidHeight: { '#text': defaultHeight },
+ // 全局航线高度 (EGM96海拔高/相对起飞点高度/AGL相对地面高度)
+ height: { '#text': defaultHeight },
+ // 航点飞行速度
+ waypointSpeed: { '#text': 10 },
+ // 沿航线方向 - 可使用当前默认值
+ waypointHeadingParam: {
+ waypointHeadingMode: { '#text': 'followWayline' },
+ waypointHeadingAngle: { '#text': 0 },
+ waypointPoiPoint: {
+ '#text': '0.000000,0.000000,0.000000',
+ },
+ waypointHeadingPathMode: { '#text': 'followBadArc' },
+ waypointHeadingPoiIndex: { '#text': 0 },
+ },
+ // 航点类型
+ waypointTurnParam: {
+ waypointTurnMode: {
+ '#text': 'toPointAndStopWithDiscontinuityCurvature',
+ },
+ waypointTurnDampingDist: { '#text': 0.2 },
+ },
+ // 是否全局参数
+ useGlobalHeight: { '#text': 1 },
+ useGlobalSpeed: { '#text': 1 },
+ useGlobalHeadingParam: { '#text': 1 },
+ useGlobalTurnParam: { '#text': 1 },
+ useStraightLine: { '#text': 1 },
+ // 事件组
+ actionGroup: {
+ actionGroupId: { '#text': index },
+ actionGroupStartIndex: { '#text': index },
+ actionGroupEndIndex: { '#text': index },
+ actionGroupMode: { '#text': 'sequence' },
+ actionTrigger: {
+ actionTriggerType: { '#text': 'reachPoint' },
+ },
+ // 单个事件 - 如果是多个事件action可改成数组 action: [{}]
+ action: {
+ actionId: { '#text': 0 },
+ actionActuatorFunc: { '#text': 'takePhoto' },
+ actionActuatorFuncParam: {
+ fileSuffix: { '#text': fileSuffix },
+ payloadPositionIndex: { '#text': '0' },
+ useGlobalPayloadLensIndex: { '#text': '1' },
+ },
+ },
+ },
+ }
+ fileTemplate.Folder.Placemark.push(placemark)
+ })
+ const fileWaylines = waylinesContext(fileTemplate)
+
+ return {
+ fileName,
+ template: fileTemplate,
+ waylines: fileWaylines,
+ }
+ } catch (error) {
+ console.log(error)
+ return false
+ }
+ }
+
+ // 创建正常航线
+ const createNoramlWaylines = (waylineBasicInfo, lnglats) => {
+ try {
+ const date = new Date()
+ const username = window.localStorage.getItem('bs_username')
+ const updateTime = date.getTime()
+ const { year, month, day, hours, minutes } = getDateFromTimestamp(updateTime)
+ const fileName = '新建航线_' + year + month + day + hours + minutes
+ // 无人机机型、云台型号、航线高度
+ const { droneEnumValue, payloadEnumValue, height, waylineName } = waylineBasicInfo
+ const defaultHeight = height || 100
+ // 起始点位 (参考点位-具体以机场为准)
+ const startPoint = lnglats[0]
+ const takeOffPoint = [startPoint.latitude, startPoint.longitude, startPoint.height].join(',')
+ // template.xml对象模版
+ const fileTemplate = {
+ author: { '#text': username },
+ createTime: { '#text': updateTime },
+ updateTime: { '#text': updateTime },
+ missionConfig: {
+ // 飞向首航点模式
+ flyToWaylineMode: { '#text': 'safely' },
+ // 航线结束动作
+ finishAction: { '#text': 'goHome' },
+ // 失控是否继续执行航线
+ exitOnRCLost: { '#text': 'goContinue' },
+ // 失控动作类型
+ executeRCLostAction: { '#text': 'goBack' },
+ // 安全起飞高度
+ takeOffSecurityHeight: { '#text': 20 },
+ // 参考起飞点
+ takeOffRefPoint: { '#text': takeOffPoint },
+ // 参考起飞点绝对高度
+ takeOffRefPointAGLHeight: { '#text': 0 },
+ // 全局航线过渡速度
+ globalTransitionalSpeed: { '#text': 10 },
+ // 全局返航高度
+ globalRTHHeight: { '#text': defaultHeight },
+ // 无人机参数
+ droneInfo: {
+ droneEnumValue: { '#text': droneEnumValue },
+ droneSubEnumValue: {
+ '#text': droneModel[payloadEnumValue],
+ },
+ },
+ // 是否自动绕行(暂未验证M30T机型使用此参数后会不会无法起飞)
+ autoRerouteInfo: {
+ missionAutoRerouteMode: { '#text': 1 },
+ transitionalAutoRerouteMode: { '#text': 1 },
+ },
+ // 云台参数
+ payloadInfo: {
+ payloadEnumValue: { '#text': payloadEnumValue },
+ payloadSubEnumValue: {
+ '#text': droneModel[payloadEnumValue],
+ },
+ payloadPositionIndex: { '#text': 0 },
+ },
+ },
+ Folder: {
+ // 预定义模板类型 - 航点飞行
+ templateType: { '#text': 'waypoint' },
+ // 是否使用全局航线过渡速度
+ useGlobalTransitionalSpeed: { '#text': 0 },
+ templateId: { '#text': 0 },
+ // 坐标系参数信息
+ waylineCoordinateSysParam: {
+ // 经纬度坐标系 - 固定值
+ coordinateMode: { '#text': 'WGS84' },
+ // 航点高程参考平面 - 相对高度
+ heightMode: { '#text': 'relativeToStartPoint' },
+ },
+ // 全局航线飞行速度
+ autoFlightSpeed: { '#text': 10 },
+ // 云台俯仰角控制模式
+ gimbalPitchMode: { '#text': 'usePointSetting' },
+ // 全局高度
+ globalHeight: { '#text': '100' },
+ // 全局偏航角模式参数 - 可使用当前默认值
+ globalWaypointHeadingParam: {
+ waypointHeadingMode: { '#text': 'followWayline' },
+ waypointHeadingAngle: { '#text': 0 },
+ waypointPoiPoint: {
+ '#text': '0.000000,0.000000,0.000000',
+ },
+ waypointHeadingPathMode: { '#text': 'followBadArc' },
+ waypointHeadingPoiIndex: { '#text': 0 },
+ },
+ // 全局航点类型 - 直线飞行,飞行器到点停
+ globalWaypointTurnMode: {
+ '#text': 'toPointAndStopWithDiscontinuityCurvature',
+ },
+ // 全局航段轨迹是否尽量贴合直线
+ globalUseStraightLine: { '#text': 1 },
+ // 航点点位信息 - 位置、事件等
+ Placemark: [],
+ payloadParam: {
+ payloadPositionIndex: { '#text': 0 },
+ focusMode: { '#text': 'firstPoint' },
+ meteringMode: { '#text': 'average' },
+ returnMode: { '#text': 'singleReturnFirst' },
+ samplingRate: { '#text': '240000' },
+ scanningMode: { '#text': 'repetitive' },
+ imageFormat: { '#text': 'wide,zoom' },
+ },
+ },
+ }
+ lnglats.forEach((lnglat, index) => {
+ const placemark = {
+ Point: {
+ // 航点经纬度
+ coordinates: {
+ '#text': `${lnglat.longitude},${lnglat.latitude}`,
+ },
+ },
+ // 航点index
+ index: { '#text': index },
+ // 全局航线高度(椭球高)
+ ellipsoidHeight: { '#text': lnglat.height },
+ // 全局航线高度 (EGM96海拔高/相对起飞点高度/AGL相对地面高度)
+ height: { '#text': lnglat.height },
+ // 航点飞行速度
+ waypointSpeed: { '#text': lnglat?.speed || 10 },
+ // 沿航线方向 - 可使用当前默认值
+ waypointHeadingParam: {
+ waypointHeadingMode: { '#text': 'followWayline' },
+ waypointHeadingAngle: { '#text': 0 },
+ waypointPoiPoint: {
+ '#text': '0.000000,0.000000,0.000000',
+ },
+ waypointHeadingPathMode: { '#text': 'followBadArc' },
+ waypointHeadingPoiIndex: { '#text': 0 },
+ },
+ // 航点类型
+ waypointTurnParam: {
+ waypointTurnMode: {
+ '#text': 'toPointAndStopWithDiscontinuityCurvature',
+ },
+ waypointTurnDampingDist: { '#text': 0.2 },
+ },
+ // 是否全局参数
+ useGlobalHeight: { '#text': 1 },
+ useGlobalSpeed: { '#text': 0 },
+ useGlobalHeadingParam: { '#text': 1 },
+ useGlobalTurnParam: { '#text': 1 },
+ useStraightLine: { '#text': 1 },
+ }
+ if (lnglat?.actions) {
+ const actionGroup = {
+ actionGroupId: { '#text': index },
+ actionGroupStartIndex: { '#text': index },
+ actionGroupEndIndex: { '#text': index },
+ actionGroupMode: { '#text': 'sequence' },
+ actionTrigger: {
+ actionTriggerType: { '#text': 'reachPoint' },
+ },
+ // 单个事件 - 如果是多个事件action可改成数组 action: [{}]
+ // action: [{
+ // actionId: { '#text': 0 },
+ // actionActuatorFunc: { '#text': '' },
+ // actionActuatorFuncParam: {},
+ // }],
+ action: [],
+ }
+ lnglat?.actions.forEach((action, index) => {
+ const singleAction = {
+ actionId: { '#text': index },
+ actionActuatorFunc: { '#text': '' },
+ actionActuatorFuncParam: {},
+ }
+ // actionGroup.action['actionActuatorFunc'] = { '#text': action.key }
+ // action.params.forEach(p => {
+ // if (p.value || p.value !== '') {
+ // actionGroup.action['actionActuatorFuncParam'][p.key] = { '#text': p.value }
+ // }
+ // })
+ singleAction['actionActuatorFunc'] = {
+ '#text': action.key,
+ }
+ action.params.forEach(p => {
+ if (p.value || p.value !== '') {
+ singleAction['actionActuatorFuncParam'][p.key] = {
+ '#text': p.value,
+ }
+ }
+ })
+ if (lnglat?.actions.length > 1) {
+ actionGroup.action.push(singleAction)
+ } else {
+ actionGroup.action = singleAction
+ }
+ })
+ placemark.actionGroup = actionGroup
+ }
+ fileTemplate.Folder.Placemark.push(placemark)
+ })
+ const fileWaylines = waylinesContext(fileTemplate)
+ return {
+ fileName: waylineName || fileName,
+ template: fileTemplate,
+ waylines: fileWaylines,
+ }
+ } catch (error) {
+ console.log(error)
+ return error
+ }
+ }
+
+ // 创建面状航线
+ const createPlanarWayline = (waylineBasicInfo, lnglats) => { }
+
+ // 创建点状航线
+ const createPointWayLines = (waylineBasicInfo, points, settingInfo, dockPosition) => {
+ try {
+ const { droneEnumValue, payloadEnumValue, waylineName } = waylineBasicInfo
+
+ const {
+ imageFormat,
+ heightMode,
+ globalHeight,
+ autoFlightSpeed,
+ gimbalPitchMode,
+ waypointTurnMode,
+ waypointHeadingMode,
+ finishAction,
+ globalTransitionalSpeed,
+ } = settingInfo
+
+ const date = new Date()
+ const username = window.localStorage.getItem('bs_username')
+ const updateTime = date.getTime()
+ const { year, month, day, hours, minutes } = getDateFromTimestamp(updateTime)
+ const fileName = '新建航线_' + year + month + day + hours + minutes
+ // 无人机机型、云台型号、航线高度
+
+ const defaultHeight = 100
+ // 起始点位 (参考点位-具体以机场为准)
+ const takeOffPoint = [dockPosition.latitude, dockPosition.longitude, 0].join(',')
+
+ // template.xml对象模版
+ const fileTemplate = {
+ author: { '#text': username },
+ createTime: { '#text': updateTime },
+ updateTime: { '#text': updateTime },
+ missionConfig: {
+ // 飞向首航点模式
+ flyToWaylineMode: { '#text': 'safely' },
+ // 航线结束动作
+ finishAction: { '#text': finishAction || 'goHome' },
+ // 失控是否继续执行航线
+ exitOnRCLost: { '#text': 'executeLostAction' },
+ // 失控动作类型
+ executeRCLostAction: { '#text': 'goBack' },
+ // 安全起飞高度
+ takeOffSecurityHeight: { '#text': 120 },
+ // 参考起飞点
+ takeOffRefPoint: { '#text': takeOffPoint },
+ // 参考起飞点绝对高度
+ takeOffRefPointAGLHeight: { '#text': 0 },
+ // 全局航线过渡速度
+ globalTransitionalSpeed: { '#text': globalTransitionalSpeed || 10 },
+ // 全局返航高度
+ globalRTHHeight: { '#text': defaultHeight },
+ // 无人机参数
+ droneInfo: {
+ droneEnumValue: { '#text': droneEnumValue },
+ droneSubEnumValue: {
+ '#text': droneModel[payloadEnumValue],
+ },
+ },
+ // 是否自动绕行(暂未验证M30T机型使用此参数后会不会无法起飞)
+ autoRerouteInfo: {
+ missionAutoRerouteMode: { '#text': 1 },
+ transitionalAutoRerouteMode: { '#text': 1 },
+ },
+ // 云台参数
+ payloadInfo: {
+ payloadEnumValue: { '#text': payloadEnumValue },
+ payloadSubEnumValue: {
+ '#text': droneModel[payloadEnumValue],
+ },
+ payloadPositionIndex: { '#text': 0 },
+ },
+ },
+ Folder: {
+ // 预定义模板类型 - 航点飞行
+ templateType: { '#text': 'waypoint' },
+ // 是否使用全局航线过渡速度
+ useGlobalTransitionalSpeed: { '#text': 0 },
+ templateId: { '#text': 0 },
+ // 坐标系参数信息
+ waylineCoordinateSysParam: {
+ // 经纬度坐标系 - 固定值
+ coordinateMode: { '#text': 'WGS84' },
+ // 航点高程参考平面 - 相对高度
+ heightMode: heightMode,
+ },
+ // 全局航线飞行速度
+ autoFlightSpeed: { '#text': autoFlightSpeed || 10 },
+ // 云台俯仰角控制模式
+ gimbalPitchMode: { '#text': gimbalPitchMode || 'manual' },
+ // 全局高度
+ globalHeight: { '#text': globalHeight || '100' },
+ // 全局偏航角模式参数 - 可使用当前默认值
+ globalWaypointHeadingParam: {
+ waypointHeadingMode: { '#text': waypointHeadingMode || 'followWayline' },
+ waypointHeadingAngle: { '#text': 0 },
+ waypointPoiPoint: {
+ '#text': '0.000000,0.000000,0.000000',
+ },
+ waypointHeadingPathMode: { '#text': 'followBadArc' },
+ waypointHeadingPoiIndex: { '#text': 0 },
+ },
+ // 全局航点类型 - 直线飞行,飞行器到点停
+ globalWaypointTurnMode: {
+ '#text': waypointTurnMode || 'toPointAndStopWithDiscontinuityCurvature',
+ },
+ // 全局航段轨迹是否尽量贴合直线
+ globalUseStraightLine: { '#text': 1 },
+ // 航点点位信息 - 位置、事件等
+ Placemark: [],
+ payloadParam: {
+ payloadPositionIndex: { '#text': 0 },
+ focusMode: { '#text': 'firstPoint' },
+ meteringMode: { '#text': 'average' },
+ returnMode: { '#text': 'singleReturnFirst' },
+ samplingRate: { '#text': '240000' },
+ scanningMode: { '#text': 'repetitive' },
+ imageFormat: imageFormat,
+ },
+ },
+ }
+ points.length &&
+ points.forEach((detail, ind) => {
+ const { Point, index, height, ellipsoidHeight, actionGroup, waypointSpeed } = detail
+
+ const placemark = {
+ Point,
+ // 航点index
+ index,
+ // 全局航线高度(椭球高)
+ ellipsoidHeight,
+ // 全局航线高度 (EGM96海拔高/相对起飞点高度/AGL相对地面高度)
+ height,
+ // 航点飞行速度
+ waypointSpeed: { '#text': waypointSpeed || 10 },
+ // 沿航线方向 - 可使用当前默认值
+ waypointHeadingParam: {
+ waypointHeadingMode: {
+ '#text': waypointHeadingMode || 'followWayline',
+ },
+ waypointHeadingAngle: { '#text': 0 },
+ waypointPoiPoint: {
+ '#text': '0.000000,0.000000,0.000000',
+ },
+ waypointHeadingPathMode: { '#text': 'followBadArc' },
+ waypointHeadingPoiIndex: { '#text': 0 },
+ },
+ // 航点类型
+ waypointTurnParam: {
+ waypointTurnMode: {
+ '#text': waypointTurnMode || 'toPointAndStopWithDiscontinuityCurvature',
+ },
+ waypointTurnDampingDist: { '#text': 0.2 },
+ },
+ // 是否全局参数
+ useGlobalHeight: { '#text': 1 },
+ useGlobalSpeed: { '#text': 0 },
+ useGlobalHeadingParam: { '#text': 1 },
+ useGlobalTurnParam: { '#text': 1 },
+ useStraightLine: { '#text': 1 },
+ }
+ if (actionGroup) {
+ placemark.actionGroup = actionGroup
+ }
+
+ fileTemplate.Folder.Placemark.push(placemark)
+ })
+
+ const fileWaylines = waylinesContext(fileTemplate)
+ return {
+ fileName: waylineName || fileName,
+ template: fileTemplate,
+ waylines: fileWaylines,
+ }
+ } catch (error) {
+ console.log(error)
+ return error
+ }
+ }
+
+ const save = async fileInfo => {
+ const JsZip = new JSZIP()
+ const { fileName, template, waylines } = fileInfo
+ const templateXml = JSONToXML(template, '', true)
+ const waylinesWpml = JSONToXML(waylines, '', true)
+ JsZip.file('wpmz/template.kml', templateXml)
+ JsZip.file('wpmz/waylines.wpml', waylinesWpml)
+ const fileBlob = await JsZip.generateAsync({ type: 'blob' })
+ // saveAs(kmzFile, `${fileName}.kmz`)
+ return fileBlob
+ }
+
+ const waylinesContext = templateJson => {
+ const waylinesObj = {
+ // 标准模版
+ missionConfig: {
+ flyToWaylineMode: null,
+ finishAction: null,
+ exitOnRCLost: null,
+ executeRCLostAction: null,
+ takeOffSecurityHeight: null,
+ globalTransitionalSpeed: null,
+ globalRTHHeight: null,
+ droneInfo: null,
+ payloadInfo: null,
+ },
+ Folder: {
+ templateId: null,
+ executeHeightMode: null,
+ waylineId: null,
+ autoFlightSpeed: null,
+ /*
+ waylines.wpml和template.xml的Placemark通用
+ 不同点:
+ waylineId = templateId
+ ellipsoidHeight、 height 替换成 executeHeight
+ */
+ Placemark: null,
+ },
+ }
+ Object.keys(waylinesObj).forEach(key => {
+ if (Object.prototype.toString.call(waylinesObj[key]) === '[object Object]') {
+ Object.keys(waylinesObj[key]).forEach(item => {
+ waylinesObj[key][item] = templateJson[key][item]
+ if (item === 'executeHeightMode') {
+ waylinesObj[key][item] = templateJson[key].waylineCoordinateSysParam.heightMode
+ }
+ if (item === 'waylineId') {
+ waylinesObj[key][item] = templateJson[key].templateId
+ }
+ if (item === 'Placemark') {
+ const placemarks = _.cloneDeep(templateJson[key][item])
+ placemarks.forEach(placemark => {
+ placemark.executeHeight = {
+ '#text': placemark.ellipsoidHeight?.['#text'] || '',
+ }
+ delete placemark.ellipsoidHeight
+ delete placemark.height
+ })
+ waylinesObj[key][item] = placemarks
+ }
+ })
+ } else {
+ waylinesObj[key] = templateJson[key]
+ }
+ })
+ return waylinesObj
+ }
+
+ return {
+ create,
+ createNoramlWaylines,
+ createPlanarWayline,
+ createPointWayLines,
+ save,
+ }
+}
diff --git a/applications/task-work-order/src/utils/cesium/useBoundary.js b/applications/task-work-order/src/utils/cesium/useBoundary.js
index d04ffb9..053ddd6 100644
--- a/applications/task-work-order/src/utils/cesium/useBoundary.js
+++ b/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,
}
}
--
Gitblit v1.9.3