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 = ` ${index + 1} ` 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) } }