import * as Cesium from 'cesium'
|
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/newRoutePlan/routeUtils'
|
|
import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
|
import { ArrowLineMaterialProperty, LineTrailMaterial, PolylineGlowMaterial } from '@/utils/cesium/Material'
|
import CreateFrustum from '@/utils/cesium/frustum/CreateFrustum'
|
import { ElMessage } from 'element-plus'
|
|
import rwqfdImg from '@/assets/images/signMachineNest/rwqfd.png'
|
import endingOnlineImg from '@/assets/images/aiNowFly/ending-online.png'
|
import newEndPointImg from '@/assets/images/newEndPointicon.png'
|
import endPointImg from '@/assets/images/EndPointicon.png'
|
import aircraftGltf from '@/assets/gltf/aircraft.gltf'
|
import newNumPoint from '@/assets/images/newStartPoint.png'
|
|
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 runningTextColor = Cesium.Color.fromCssColorString('#1FFF69')
|
let runningTextOffset = new Cesium.Cartesian2(0, -32)
|
let pendingTextColor = Cesium.Color.fromCssColorString('#FFB81D')
|
let pendingTextOffset = new Cesium.Cartesian2(0, 32)
|
let MapPopUpBox = HistoryDronePopup
|
|
function getZoomFactor (camerasInfo, type) {
|
let zoom_factor
|
const irLevel = camerasInfo?.ir_zoom_factor
|
const zoomLevel = camerasInfo?.zoom_factor
|
if (type === 'wide') {
|
zoom_factor = 1
|
} else if (type === 'ir') {
|
zoom_factor = irLevel
|
} else {
|
zoom_factor = zoomLevel
|
}
|
zoom_factor = _.round(zoom_factor, 1)
|
|
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) {
|
this.type = options?.type || 'single'
|
this.isShowVideoPlan = options?.isShowVideoPlan || false
|
|
this.viewer = null
|
this.currentWaypointIndex = null
|
this.droneSpeedArr = []
|
|
this.multiNestData = []
|
|
this.detailsMapPopupData = []
|
|
this.labelBoxRenderHandler = this.labelBoxRender.bind(this)
|
this.removeLabelHandler = this.removeLabel.bind(this)
|
}
|
|
/**
|
* 初始化viewer
|
* @param {*} viewer
|
*/
|
initCreateRoute (viewer) {
|
this.viewer = viewer
|
}
|
|
/**
|
* 设置当前所在点位
|
* @param {*} data flighttaskProgressInfo 信息
|
*/
|
setCurrentWaypointIndex (data) {
|
const output = data?.output
|
const sn = data?.sn
|
|
if (output?.ext) {
|
const currentWaypointIndex = output?.ext['current_waypoint_index']
|
|
this.updataMultiNestData(sn, {
|
currentWaypointIndex,
|
})
|
}
|
}
|
|
/**
|
* 请求航线文件数据并绘制解析处理
|
* @param {*} data 包含文件url等信息
|
* @param {*} dockPosition 无人机位置
|
* @returns
|
*/
|
async parsingFiles (data, dockPosition = null, isShowDock = false, isShowPointBillboard = true) {
|
console.log(data,'888')
|
const { url, wayline_type, device_sn } = data
|
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()}`)
|
|
const templateXML = await res.fileInfoObj['wpmz/template.kml']
|
const waylinesXML = await res.fileInfoObj['wpmz/waylines.wpml']
|
|
const templateXmlJson = XMLToJSON(templateXML)?.['Document']
|
const waylinesXmlJson = XMLToJSON(waylinesXML)?.['Document']
|
|
const templateXMLObj = removeTextKey(templateXmlJson.Folder)
|
const { takeOffRefPoint } = removeTextKey(templateXmlJson.missionConfig)
|
|
const [latitude, longitude, height] = takeOffRefPoint.split(',').map(item => _.round(item, 6))
|
|
const startPoint = { latitude, longitude, height: Number.isNaN(height) ? 0 : height }
|
|
const { templateType } = templateXMLObj
|
|
const missionConfig = removeTextKey(waylinesXmlJson.missionConfig)
|
const waylinesXMLObj = removeTextKey(waylinesXmlJson.Folder)
|
|
if (templateType === 'mapping3d') {
|
if (!waylinesXMLObj[0].Placemark.length) return ElMessage.error('没有航线点位')
|
} else {
|
if (!waylinesXMLObj.Placemark.length) return ElMessage.error('没有航线点位')
|
}
|
|
if ([2, 4, 5, 7, 10].includes(Number(wayline_type))) {
|
if (templateType === 'mapping3d') {
|
let morePolygonData = waylinesXMLObj.map(i => ({
|
data: i.Placemark,
|
}))
|
|
let newPolygonData = getNewPolygonData(morePolygonData)
|
|
let newPolygonDisposePositions = newPolygonData.map(i =>
|
Cesium.Cartesian3.fromDegrees(Number(i[0]), Number(i[1]))
|
)
|
|
this.viewer.entities.add({
|
name: 'work-drone-route-planar-outer-polygon',
|
|
polygon: {
|
hierarchy: new Cesium.CallbackProperty(() => {
|
return new Cesium.PolygonHierarchy(newPolygonDisposePositions)
|
}, false),
|
material: Cesium.Color.fromBytes(255, 228, 22, 44),
|
outline: false,
|
outlineWidth: 2,
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
},
|
})
|
|
return this.drawPlanarRoute(
|
waylinesXMLObj[0],
|
missionConfig,
|
templateXmlJson,
|
dockPosition,
|
data,
|
startPoint,
|
isShowDock,
|
isShowPointBillboard,
|
wayline_type
|
)
|
}
|
|
return this.drawPlanarRoute(
|
waylinesXMLObj,
|
missionConfig,
|
templateXmlJson,
|
dockPosition,
|
data,
|
startPoint,
|
isShowDock,
|
isShowPointBillboard,
|
wayline_type
|
)
|
} else {
|
return this.drawPointRoute(
|
waylinesXMLObj,
|
missionConfig,
|
dockPosition,
|
data,
|
startPoint,
|
isShowDock,
|
isShowPointBillboard,
|
wayline_type
|
)
|
}
|
}
|
|
// 拆分航线
|
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 {*} dockPosition 无人机位置
|
* @param {*} data
|
* @param {*} startPoint
|
* @returns
|
*/
|
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 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') || []
|
// 数组转换
|
coordArr = coordinates.map(coordinate =>
|
coordinate
|
.replace(/\s+/g, '')
|
.split(',')
|
.map(v => Number(v))
|
)
|
|
// 获取当前经纬度绝对高度
|
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,
|
material: Cesium.Color.CHARTREUSE,
|
zIndex: 1,
|
},
|
|
customData: {
|
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)
|
})
|
|
// 面状航线第一个点突出展示
|
isShowPointBillboard && this.startingIncreasePoint(positionArray)
|
|
// 面状判断当前点位数量
|
const cloneCoordArr = [...newCoordArr]
|
|
if (cloneCoordArr.length >= 3) {
|
cloneCoordArr.push(newCoordArr[0])
|
}
|
|
// 绘制面状边框线--------------------
|
this.viewer.entities.add({
|
name: 'work-drone-route-planar-border',
|
|
polyline: {
|
positions: Cesium.Cartesian3.fromDegreesArrayHeights(cloneCoordArr.flat()),
|
width: 4,
|
material: new Cesium.PolylineOutlineMaterialProperty({
|
color: new Cesium.Color.fromBytes(74, 138, 233),
|
outlineWidth: 2,
|
outlineColor: Cesium.Color.WHITE,
|
}),
|
zIndex: -1,
|
clampToGround: true,
|
},
|
})
|
|
// 绘制面状地块--------------------
|
this.viewer.entities.add({
|
name: 'work-drone-route-planar-polygon',
|
polygon: {
|
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),
|
zIndex: -1,
|
},
|
})
|
|
if (this.type === 'clusterScheduling') {
|
this.viewer.entities.add({
|
name: 'work-drone-route-point-start',
|
position: Cesium.Cartesian3.fromDegrees(filePositions[0].lng, filePositions[0].lat, filePositions[0].alt),
|
|
billboard: {
|
image: rwqfdImg,
|
outlineWidth: 0,
|
width: 50,
|
height: 50,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
},
|
|
label: {
|
text: data.job_name || '',
|
font: 'bold 16px sans-serif',
|
fillColor: Cesium.Color.WHITE,
|
pixelOffset: new Cesium.Cartesian2(0, -50),
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
outlineWidth: 2,
|
outlineColor: Cesium.Color.BLACK,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
},
|
})
|
} else {
|
// 显示机巢位置
|
isShowDock && renderingMachineNest({
|
viewer: this.viewer,
|
dockTransformPosition,
|
dockPosition,
|
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:
|
dockPosition != null
|
? [{ lng: dockPosition.longitude, lat: dockPosition.latitude, alt: dockPosition.height }, ...filePositions]
|
: filePositions,
|
}
|
}
|
|
planarBillboard (color) {
|
const billboard = document.createElement('canvas')
|
const ctx = billboard.getContext('2d')
|
const radius = 4
|
ctx.beginPath()
|
ctx.arc(radius, radius, radius, 0, 2 * Math.PI, false)
|
ctx.fillStyle = color // 设置填充颜色
|
ctx.fill()
|
return billboard
|
}
|
|
startingIncreasePoint (wayData) {
|
let positions = wayData[3]
|
|
let setting = {
|
name: 'work-drone-route-planar-start',
|
position: positions,
|
billboard: {
|
image: this.planarBillboard('#2D8CF0'),
|
pixelOffset: new Cesium.Cartesian2(146, 72),
|
zIndex: 2,
|
},
|
}
|
|
this.viewer.entities.add(setting)
|
}
|
|
/**
|
* 绘制点航线
|
* @param {*} lineObj 航线文件中的
|
* @param {*} missionConfig 航线文件中的
|
* @param {*} dockPosition 无人机位置
|
* @param {*} data 任务信息,航线地址等
|
* @param {*} startPoint
|
* @param {*} isShowDock 是否显示机巢位置
|
* @returns
|
*/
|
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,
|
width: 50,
|
height: 50,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
},
|
label: {
|
text: data.job_name || '',
|
font: 'bold 16px sans-serif',
|
fillColor: data.type === 2 ? runningTextColor : pendingTextColor,
|
pixelOffset: data.type === 2 ? runningTextOffset : pendingTextOffset,
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
outlineWidth: 2,
|
outlineColor: Cesium.Color.BLACK,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
},
|
})
|
} else if (index === filePositions.length - 1) {
|
this.viewer.entities.add({
|
name: 'work-drone-route-point-end',
|
position,
|
billboard: {
|
image: newEndPointImg,
|
outlineWidth: 0,
|
width: 40,
|
height: 40,
|
scale: 1.0,
|
verticalOrigin: Cesium.VerticalOrigin.TOP, // 添加这行确保图标正确显示
|
pixelOffset: new Cesium.Cartesian2(0, -20), // 根据需要调整偏移量
|
},
|
})
|
} else {
|
this.viewer.entities.add({
|
name: 'work-drone-route-point-approach',
|
position,
|
billboard: {
|
image: newNumPoint,
|
width: 50,
|
height: 50,
|
},
|
|
label: {
|
text: `${index}`,
|
font: 'bold 14px serif',
|
fillColor: Cesium.Color.WHITE,
|
pixelOffset: new Cesium.Cartesian2(1, 0), // 根据需要调整偏移量
|
eyeOffset: new Cesium.Cartesian3(0, 0, -10), // 使标签在点的上方
|
},
|
|
offset: new Cesium.Cartesian2(10, 30),
|
})
|
}
|
})
|
polyline = this.viewer.entities.add({
|
name: 'work-drone-route-point-polyline',
|
polyline: {
|
width: 4,
|
positions: positionArray,
|
material: data.type === 2 ? getLineColor(indexLog) : arrowLineMaterialPropertyGray,
|
clampToGround: false,
|
},
|
})
|
} else {
|
// 显示机巢位置
|
isShowDock && renderingMachineNest({
|
viewer: this.viewer,
|
dockTransformPosition,
|
dockPosition
|
})
|
// 起点
|
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, // 底部对齐
|
},
|
})
|
console.log(data.status, '失败')
|
polyline = this.viewer.entities.add({
|
name: 'work-drone-route-point-polyline',
|
polyline: {
|
width: 4,
|
positions: positionArray,
|
material: data.status === 5 ? arrowLineMaterialPropertyGray : getLineColor(indexLog),
|
clampToGround: false,
|
},
|
customData: {
|
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--point-polyline',
|
position: topPosition,
|
polyline: getPolyLine(this.viewer, { value: droppointPositions }, index),
|
}
|
this.viewer.entities.add(setting)
|
})
|
|
return {
|
entity: polyline,
|
routeLinePositions: positionArray,
|
filePositions:
|
dockPosition !== null
|
? [{ lng: dockPosition.longitude, lat: dockPosition.latitude, alt: dockPosition.height }, ...filePositions]
|
: filePositions,
|
}
|
}
|
|
/**
|
* 通用处理数据
|
* @param {*} lineObj
|
* @param {*} missionConfig
|
* @param {*} dronePosition
|
* @param {*} startPoint
|
* @returns {} {positionArray: 带拼接点位置, filePositions:光航线点位置}
|
*/
|
disposeData (lineObj, missionConfig, dronePosition, data, startPoint, wayline_type) {
|
const { device_sn } = data
|
const executeHeightMode = lineObj.executeHeightMode === 'WGS84'
|
|
let filePositions = lineObj.Placemark.map(item => {
|
const [lng, lat] = item.Point.coordinates.split(',')
|
const height = item.executeHeight
|
|
return {
|
lng: Number(lng),
|
lat: Number(lat),
|
alt: Number(height),
|
}
|
})
|
|
let safetyHeight
|
|
if (executeHeightMode) {
|
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
|
}
|
|
let safetyPositions = []
|
|
if (dronePosition != null) {
|
safetyPositions = [
|
{
|
lng: Number(dronePosition.longitude),
|
lat: Number(dronePosition.latitude),
|
alt: Number(dronePosition.height),
|
},
|
|
{
|
lng: Number(dronePosition.longitude),
|
lat: Number(dronePosition.latitude),
|
alt: Number(safetyHeight),
|
},
|
|
{
|
lng: filePositions[0].lng,
|
lat: filePositions[0].lat,
|
alt: Number(safetyHeight),
|
},
|
]
|
}
|
|
let pointAll = [...safetyPositions, ...filePositions]
|
|
const currentRoutePositions = pointAll.map((item, index) => {
|
let height = executeHeightMode ? item.alt : item.alt + (dronePosition != null ? dronePosition.height : 0)
|
|
if (index == 0) height = item.alt
|
|
return Cesium.Cartesian3.fromDegrees(item.lng, item.lat, height)
|
})
|
|
this.updataMultiNestData(device_sn, {
|
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)
|
|
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()
|
),
|
})
|
} 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)
|
|
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()
|
),
|
})
|
}
|
|
return {
|
positionArray: currentRoutePositions,
|
filePositions: filePositions.map(item => {
|
let height = executeHeightMode ? item.alt : item.alt + (dronePosition != null ? dronePosition.height : 0)
|
|
return {
|
...item,
|
alt: Number(height),
|
}
|
}),
|
}
|
}
|
|
/**
|
* 删除entity
|
*/
|
removeAllRouteEntity () {
|
const entities = this.viewer?.entities.values.filter(i => {
|
return i?.name && i?.name.includes('work-drone-route-')
|
})
|
|
Array.isArray(entities) &&
|
entities.forEach(item => {
|
this.viewer?.entities.remove(item)
|
})
|
}
|
|
// 当前无人机视椎体相关
|
setCreateFrustum (host, zoomFactor = {}, cameraParams = {}) {
|
if (!host) return
|
|
const parent_sn = host?.parent_sn
|
|
if (parent_sn) {
|
const curDroneData = this.getCurDroneData(parent_sn)
|
|
curDroneData.viewInfoFrustum?.destroyed()
|
|
const attitude_head = 180 + host?.attitude_head
|
const gimbal_pitch = 90 - Number(host?.payloads[0]?.gimbal_pitch) || 0
|
|
let fov = 60
|
|
if (
|
zoomFactor?.value?.set === true &&
|
cameraParams?.value?.set === true &&
|
curDroneData?.camera_type &&
|
['wide', 'ir', 'zoom'].includes(curDroneData?.camera_type)
|
) {
|
if (curDroneData.camera_type === 'wide') {
|
fov = 65
|
} else {
|
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
|
}
|
}
|
}
|
|
this.updataMultiNestData(parent_sn, {
|
viewInfoFrustum: new CreateFrustum(this.viewer, {
|
position: {
|
longitude: host?.longitude,
|
latitude: host?.latitude,
|
altitude: host?.height,
|
},
|
width: store.state.common.liveResolution.width || 960,
|
height: store.state.common.liveResolution.height || 720,
|
fov: fov,
|
near: 0.01,
|
far: 250,
|
roll: gimbal_pitch,
|
pitch: 0,
|
heading: attitude_head,
|
|
isShowVideoPlan: this.isShowVideoPlan,
|
}),
|
})
|
}
|
}
|
|
setLiveStatusWs (data) {
|
const { device_sn, live_status } = data
|
|
if (device_sn && live_status) {
|
this.updataMultiNestData(device_sn, {
|
camera_type: live_status?.[0].video_type || 'wide',
|
})
|
}
|
}
|
|
removeCreateFrustum (host) {
|
const parent_sn = host?.parent_sn
|
|
if (parent_sn) {
|
const curDroneData = this.getCurDroneData(parent_sn)
|
|
curDroneData.viewInfoFrustum?.clear()
|
}
|
}
|
|
// 设置当前无人机视椎体信息面板
|
setFrustumInfoDetails (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 => {
|
return i?.name && i?.name.includes(`aircraft-glf-${dock_sn}`)
|
})
|
|
if (aircraftEntity) {
|
// 预计到达下一个航点时间
|
let arrivalTime = estimated_time_to_next_point
|
|
if (arrivalTime > 60) {
|
const minute = Math.floor(arrivalTime / 60)
|
const second = Math.round(arrivalTime % 60)
|
arrivalTime = `${minute}m${second}s`
|
} else {
|
arrivalTime = Math.round(arrivalTime) + 's'
|
}
|
|
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`,
|
font: '13px monospace',
|
showBackground: true,
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
pixelOffset: new Cesium.Cartesian2(0, -40),
|
show: true,
|
})
|
|
return
|
}
|
}
|
}
|
|
removeFrustumInfoDetails (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) {
|
aircraftEntity.label.show = false
|
}
|
}
|
}
|
|
// 设置无人机模型
|
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 => {
|
return i?.name && i?.name.includes(`aircraft-glf-${parent_sn}`)
|
})
|
|
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
|
}
|
|
if (parent_sn) {
|
this.viewer?.entities.add({
|
name: `aircraft-glf-${parent_sn}`,
|
position,
|
orientation,
|
label: {},
|
model: {
|
uri: aircraftGltf, // 或 .glb
|
scale: 64, // 缩放比例
|
minimumPixelSize: 64, // 最小像素尺寸(保证模型远处可见)
|
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) {
|
item.viewInfoFrustum?.clear()
|
|
delete item.viewInfoFrustum
|
}
|
})
|
}
|
|
mapEntityRemove (host) {
|
const parent_sn = host?.parent_sn
|
|
if (parent_sn) {
|
const curDroneData = this.getCurDroneData(parent_sn)
|
|
curDroneData.viewInfoFrustum?.clear()
|
|
delete curDroneData.viewInfoFrustum
|
|
const entities = this.viewer?.entities.values.filter(i => {
|
return i?.name && i?.name.includes(`aircraft-glf-${parent_sn}`)
|
})
|
|
Array.isArray(entities) &&
|
entities.forEach(item => {
|
this.viewer?.entities.remove(item)
|
})
|
}
|
}
|
|
mapEntityRemoveAll () {
|
const entities = this.viewer?.entities.values.filter(i => {
|
return i?.name && i?.name.includes(`aircraft-glf-`)
|
})
|
|
Array.isArray(entities) &&
|
entities.forEach(item => {
|
this.viewer?.entities.remove(item)
|
})
|
|
this.multiNestData.length > 0 &&
|
this.multiNestData.forEach(item => {
|
item.viewInfoFrustum?.clear()
|
})
|
}
|
|
/**
|
* 更新多机场数据
|
* @param {*} device_sn 机巢SN
|
* @param {*} data 机巢对应需绑定数据
|
*/
|
updataMultiNestData (device_sn, data) {
|
const isHave = this.multiNestData.find(item => item.device_sn === device_sn)
|
const isHaveInd = this.multiNestData.findIndex(item => item.device_sn === device_sn)
|
|
if (isHave) {
|
this.multiNestData[isHaveInd] = {
|
...this.multiNestData[isHaveInd],
|
|
...data,
|
}
|
} else {
|
this.multiNestData.push({
|
device_sn,
|
...data,
|
})
|
}
|
}
|
|
/**
|
* 根据机巢sn获取当前机巢对应的相关信息
|
* @param {*} device_sn
|
*/
|
getCurDroneData (device_sn) {
|
const isHave = this.multiNestData.find(item => item.device_sn === device_sn)
|
|
if (isHave) return this.multiNestData.find(item => item.device_sn === device_sn)
|
|
return false
|
}
|
|
showDetailMapPopup (data) {
|
const that = this
|
this.detailsMapPopupData = data.map(item => {
|
return {
|
...item,
|
show: true,
|
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)
|
)
|
}
|
|
getLabelDom (data) {
|
const that = this
|
const vNode = h(MapPopUpBox, { data, removeLabel: that.removeLabelHandler })
|
const tooltipContainer = document.createElement('div')
|
|
tooltipContainer.id = data.uuid
|
tooltipContainer.style.position = 'absolute'
|
tooltipContainer.style.transform = 'translate(-50%,10%)'
|
tooltipContainer.style.pointerEvents = 'none'
|
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)
|
|
let dom = document.querySelector(`[id="${item.uuid}"]`)
|
|
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'
|
}
|
})
|
}
|
|
// 移除弹框标签
|
removeLabel (data) {
|
const that = this
|
|
that.removeDom(data)
|
}
|
|
removeDom (data) {
|
const that = this
|
|
const dom = document.querySelector(`[id="${data.uuid}"]`)
|
|
if (dom && dom.parentNode) {
|
that.detailsMapPopupData.forEach(i => i.uuid === data.uuid && (i.show = false))
|
|
dom.parentNode.removeChild(dom)
|
}
|
}
|
|
destroyedCustomDom () {
|
const that = this
|
that.viewer?.scene.postRender.removeEventListener(that.labelBoxRenderHandler)
|
}
|
}
|