| | |
| | | <el-col :span=6> |
| | | <el-card class="noScroll"> |
| | | <div class="chooseModel"> |
| | | <!-- <el-button type="primary" @click='chooseModel'>添加设备</el-button> --> |
| | | <div class="fontJust">设备列表</div> |
| | | <el-button @click="addDevice"> 新增</el-button> |
| | | <el-table |
| | |
| | | width="150"> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="muban" |
| | | prop="mode" |
| | | label="模板" |
| | | width="150"> |
| | | width="120"> |
| | | </el-table-column> |
| | | <el-table-column |
| | | fixed="right" |
| | | label="操作" |
| | | width="100"> |
| | | width="130"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" size="small" @click="chooseModel(scope.row)">模板</el-button> |
| | | <el-button type="text" size="small" @click="flyToLocal(scope.row)">定位</el-button> |
| | | <el-button type="text" size="small" @click="deleteData(scope.row)">移除</el-button> |
| | | </template> |
| | |
| | | class="filter-tree" |
| | | :data="treeData" |
| | | :props="defaultProps" |
| | | draggable |
| | | default-expand-all |
| | | ref="tree"> |
| | | </el-tree> |
| | |
| | | <el-button type="primary" @click="submit()" >生成航线任务</el-button> |
| | | </span> |
| | | </el-dialog> |
| | | <chooseDeviceDialog v-if="showModel" @cancel ='cancel' @dealChooseArr="dealChooseArr" ></chooseDeviceDialog> |
| | | <chooseDeviceDialog v-if="showDevice" @cancel ='cancel' @dealChooseArr="dealChooseArr" ></chooseDeviceDialog> |
| | | <chooseModelDialog v-if="showModel" @cancelModel ='cancelModel' @receiveModel="receiveModel" :deviceId="deviceId"></chooseModelDialog> |
| | | </div> |
| | | </template> |
| | | <script> |
| | | import CesiumMap from "../../../../utils/components/cesium-map.vue"; |
| | | import chooseDeviceDialog from './chooseDeviceDialog.vue'; |
| | | import chooseModelDialog from './chooseModelDialog.vue'; |
| | | import { obtainRealData ,buildKmz} from "@/api/system/template" |
| | | |
| | | export default{ |
| | | components: { |
| | | CesiumMap, |
| | | chooseDeviceDialog |
| | | chooseDeviceDialog, |
| | | chooseModelDialog |
| | | }, |
| | | data(){ |
| | | return{ |
| | |
| | | children: 'children', |
| | | label: 'label' |
| | | }, |
| | | showDevice:false, |
| | | multipleSelection: [], |
| | | showModel:false, |
| | | multipleSelection: [] |
| | | deviceId:null |
| | | } |
| | | }, |
| | | methods:{ |
| | | handleClose(){ |
| | | console.log("close") |
| | | this.dialogVisible = false |
| | | this.$emit('close') |
| | | }, |
| | | flyToLocal(row){ |
| | | console.log(row) |
| | | console.log(viewer) |
| | | const position = Cesium.Cartesian3.fromDegrees(row.longitude,row.latitude, row.altitude); |
| | | const position = Cesium.Cartesian3.fromDegrees(row.longitude,row.latitude, row.deviceHeight); |
| | | |
| | | // 设置模型方向(可选) |
| | | const heading = Cesium.Math.toRadians(row.face); // 朝东南方向 |
| | |
| | | position: position, |
| | | orientation: orientation, |
| | | model: { |
| | | uri: "http://192.168.1.5:9000/tower/2025/07/01/tower_20250701145739A004.glb", // 替换成你的模型路径 |
| | | scale: 1000, |
| | | uri: row?.ardTowerModel.modelRoute, // 替换成你的模型路径 |
| | | scale: 1, |
| | | }, |
| | | label: { |
| | | show: true, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
| | | font: '28px Helvetica', |
| | | outlineColor: Cesium.Color.BLUE, |
| | | outlineWidth: 3, |
| | | fillColor: Cesium.Color.fromCssColorString('#FFFFFF'), //44c3cc |
| | | text: row.deviceName, |
| | | style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | pixelOffset: new Cesium.Cartesian2(0.0, -56.0), |
| | | scaleByDistance: new Cesium.NearFarScalar(1000, 0.6, 10000, 0.4), |
| | | pixelOffsetScaleByDistance: new Cesium.NearFarScalar(1000, 0.4, 10000, 0.4), |
| | | disableDepthTestDistance: 100000000 |
| | | } |
| | | }); |
| | | console.log(entity) |
| | | // 飞行到模型位置 |
| | | console.log(this.treeData) |
| | | viewer.flyTo(entity) |
| | | }, |
| | | addDevice(){ |
| | | this.showModel = true |
| | | this.showDevice = true |
| | | }, |
| | | cancel(){ |
| | | this.showModel = false |
| | | this.showDevice = false |
| | | }, |
| | | hasSameId(array1, array2) { |
| | | const ids1 = new Set(array1.map(item => item.id)); |
| | |
| | | type: 'warning' |
| | | }) |
| | | } |
| | | this.tableData = this.tableData.concat(arr) |
| | | this.tableData = this.treeData.concat(arr) |
| | | } |
| | | const dealTreeData =this.dealTee() |
| | | this.treeData = dealTreeData |
| | | console.log(this.treeData) |
| | | }, |
| | | dealTee(){ |
| | | console.log(this.tableData) |
| | | return this.tableData.map(item => ({ |
| | | ...item, |
| | | label: item.deviceName, |
| | | children: [] |
| | | })); |
| | | }, |
| | | deleteData(row){ |
| | | // 找到 id 为 "1" 的元素索引 |
| | | const index = this.tableData.findIndex(item => item.id === row.id); |
| | | |
| | | // 如果找到了,删除该元素 |
| | | if (index !== -1) { |
| | | this.tableData.splice(index, 1); |
| | | } |
| | | const dealTreeData = this.dealTee() |
| | | this.treeData = dealTreeData |
| | | }, |
| | | chooseModel(row){ |
| | | this.deviceId = row.id |
| | | this.showModel = true |
| | | }, |
| | | cancelModel(){ |
| | | this.showModel = false |
| | | }, |
| | | receiveModel(obj){ |
| | | //deal逻辑 |
| | | console.log(obj) |
| | | let flightTemplateId =obj.modelObj.id |
| | | let deviceId = obj.deviceId |
| | | this.dealTableTemplate(obj) |
| | | // let |
| | | obtainRealData(flightTemplateId,deviceId).then(res=>{ |
| | | console.log(res) |
| | | let treeDealData = this.transformFlightData(res.data?.coordinateSystemVoS || []) |
| | | console.log(treeDealData) |
| | | this.dealTreeMerge(obj,treeDealData) |
| | | // const aaa = this.rotateAllPoints(this.treeData || [],res.data.longitude,res.data.latitude,res.data.height) |
| | | this.drawConnectionsWithLabels(this.treeData) |
| | | }) |
| | | // this.dealTreeMerge(obj) |
| | | }, |
| | | //返回数据转换树结构 |
| | | transformFlightData(data) { |
| | | const result = []; |
| | | |
| | | data.forEach(item => { |
| | | // 找到第一个 groundPoint,作为代表 |
| | | const groundPoint = item.groundPointVo?.[0]; |
| | | |
| | | // 生成 children 节点 |
| | | const children = groundPoint ? [{ |
| | | id: groundPoint.id, |
| | | label: groundPoint.targetName, |
| | | longitude: groundPoint.longitude, |
| | | latitude: groundPoint.latitude, |
| | | height: groundPoint.height |
| | | }] : []; |
| | | |
| | | // 生成空中点 |
| | | result.push({ |
| | | id: item.id, |
| | | label: item.targetName, |
| | | longitude: item.longitude, |
| | | latitude: item.latitude, |
| | | height: item.altitude, |
| | | children |
| | | }); |
| | | }); |
| | | |
| | | return result; |
| | | }, |
| | | dealTableTemplate(obj){ |
| | | const targetTableData = this.tableData.find(item => item.id === obj.deviceId) |
| | | const targetTreeData = this.treeData.find(item => item.id === obj.deviceId) |
| | | if (targetTableData) { |
| | | this.$set(targetTableData, 'mode', obj.modelObj.templateName) // ✅ Vue2 需要使用 $set 以确保响应式 |
| | | } |
| | | if (targetTreeData) { |
| | | this.$set(targetTreeData, 'mode', obj.modelObj.templateName) // ✅ Vue2 需要使用 $set 以确保响应式 |
| | | } |
| | | }, |
| | | dealTreeMerge(obj,data){ |
| | | this.treeData.forEach(item => { |
| | | if(item.id == obj.deviceId) { |
| | | // 替换第一层每个节点的 children |
| | | this.$set(item, 'children', JSON.parse(JSON.stringify(data))); |
| | | } |
| | | }); |
| | | }, |
| | | drawConnectionsWithLabels(devices) { |
| | | const airPoints = []; |
| | | |
| | | devices.forEach(device => { |
| | | if (!Array.isArray(device.children)) return; |
| | | |
| | | device.children.forEach(airPoint => { |
| | | // 空中点位置 |
| | | const airPos = Cesium.Cartesian3.fromDegrees( |
| | | airPoint.longitude, |
| | | airPoint.latitude, |
| | | airPoint.height |
| | | ); |
| | | |
| | | // ✅ 添加空中点实体和 label |
| | | viewer.entities.add({ |
| | | position: airPos, |
| | | point: { |
| | | pixelSize: 6, |
| | | color: Cesium.Color.YELLOW |
| | | }, |
| | | label: { |
| | | text: airPoint.label || '', |
| | | font: '14px sans-serif', |
| | | fillColor: Cesium.Color.YELLOW, |
| | | style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | outlineColor: Cesium.Color.BLACK, |
| | | outlineWidth: 2, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | pixelOffset: new Cesium.Cartesian2(0, -12) |
| | | } |
| | | }); |
| | | |
| | | airPoints.push(airPos); |
| | | |
| | | // 遍历地面点(子项) |
| | | if (Array.isArray(airPoint.children)) { |
| | | airPoint.children.forEach(groundPoint => { |
| | | const groundPos = Cesium.Cartesian3.fromDegrees( |
| | | groundPoint.longitude, |
| | | groundPoint.latitude, |
| | | groundPoint.height |
| | | ); |
| | | |
| | | // ✅ 添加地面点实体和 label |
| | | viewer.entities.add({ |
| | | position: groundPos, |
| | | point: { |
| | | pixelSize: 6, |
| | | color: Cesium.Color.BLUE |
| | | }, |
| | | label: { |
| | | text: groundPoint.label || '', |
| | | font: '14px sans-serif', |
| | | fillColor: Cesium.Color.WHITE, |
| | | style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
| | | outlineColor: Cesium.Color.BLACK, |
| | | outlineWidth: 2, |
| | | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
| | | pixelOffset: new Cesium.Cartesian2(0, -12) |
| | | } |
| | | }); |
| | | |
| | | // ✅ 蓝色虚线连接:空中点 ➜ 地面点 |
| | | viewer.entities.add({ |
| | | polyline: { |
| | | positions: [airPos, groundPos], |
| | | width: 2, |
| | | material: new Cesium.PolylineDashMaterialProperty({ |
| | | color: Cesium.Color.BLUE, |
| | | dashLength: 8 |
| | | }) |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | // ✅ 黄色实线连接所有空中点 |
| | | if (airPoints.length > 1) { |
| | | viewer.entities.add({ |
| | | polyline: { |
| | | positions: airPoints, |
| | | width: 3, |
| | | material: Cesium.Color.YELLOW |
| | | } |
| | | }); |
| | | } |
| | | }, |
| | | rotateAllPoints(dataList,towerLongitude,towerLatitude,towerhHight) { |
| | | return dataList.map(item => { |
| | | console.log(item) |
| | | console.log(towerLongitude) |
| | | console.log(towerLatitude) |
| | | console.log(towerhHight) |
| | | const groundPoint = Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude, item.height); |
| | | const towerPoint = Cesium.Cartesian3.fromDegrees(towerLongitude, towerLatitude, towerhHight); |
| | | let rotationAngle = Cesium.Math.toRadians(135) |
| | | // 旋转地面点本身(会变,但绕自身旋转不会移动) |
| | | const newGroundPoint = this.rotateAroundPoint( groundPoint,towerPoint, rotationAngle); |
| | | const newGroundCarto = Cesium.Cartographic.fromCartesian(newGroundPoint); |
| | | const newGround = { |
| | | ...item, |
| | | longitude: Cesium.Math.toDegrees(newGroundCarto.longitude), |
| | | latitude: Cesium.Math.toDegrees(newGroundCarto.latitude), |
| | | height: newGroundCarto.height |
| | | }; |
| | | console.log(newGround) |
| | | // 处理子空中点 |
| | | newGround.children = item.children.map(child => { |
| | | const airPoint = Cesium.Cartesian3.fromDegrees(child.longitude, child.latitude, child.height); |
| | | const rotatedAirPoint = this.rotateAroundPoint( airPoint,towerPoint, rotationAngle); |
| | | const rotatedAirCarto = Cesium.Cartographic.fromCartesian(rotatedAirPoint); |
| | | |
| | | return { |
| | | ...child, |
| | | longitude: Cesium.Math.toDegrees(rotatedAirCarto.longitude), |
| | | latitude: Cesium.Math.toDegrees(rotatedAirCarto.latitude), |
| | | height: rotatedAirCarto.height |
| | | }; |
| | | }); |
| | | |
| | | return newGround; |
| | | }); |
| | | }, |
| | | // 计算点A绕点B逆时针旋转指定角度后的新位置 |
| | | // 空中点或者地面点笛卡尔坐标 塔的笛卡尔坐标 塔的朝向值 |
| | | rotateAroundPoint(startPoint,pivotPoint,rotationAngle) { |
| | | // 创建一个从B点到本地坐标系的转换矩阵(东方向为X轴,北方向为Y轴,垂直方向为Z轴) |
| | | const transformationMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(pivotPoint); |
| | | // 获取世界坐标系到本地坐标系的转换矩阵 |
| | | const inverseTransformationMatrix = Cesium.Matrix4.inverse(transformationMatrix,new Cesium.Matrix4()); |
| | | // 将A点转换到局部坐标系中 |
| | | const localStartPoint = Cesium.Matrix4.multiplyByPoint(inverseTransformationMatrix, startPoint,new Cesium.Cartesian3()); |
| | | // 计算A点在局部坐标系中逆时针旋转指定角度后的新位置 |
| | | const rotatedX = localStartPoint.x * Math.cos(rotationAngle) + localStartPoint.y * Math.sin(rotationAngle); |
| | | const rotatedY = localStartPoint.y * Math.cos(rotationAngle) - localStartPoint.x * Math.sin(rotationAngle); |
| | | const rotatedZ = localStartPoint.z; // Z轴坐标保持不变 |
| | | // 将旋转后的局部坐标转换回世界坐标系 |
| | | return Cesium.Matrix4.multiplyByPoint(transformationMatrix, new Cesium.Cartesian3(rotatedX, rotatedY, rotatedZ), new Cesium.Cartesian3()); |
| | | }, |
| | | submit(){ |
| | | console.log(this.treeData) |
| | | |
| | | const routePointList = []; |
| | | |
| | | this.treeData.forEach(device => { |
| | | device.children.forEach((airPoint, index) => { |
| | | routePointList.push({ |
| | | routePointIndex: routePointList.length, |
| | | longitude: airPoint.longitude, |
| | | latitude: airPoint.latitude, |
| | | height: airPoint.height, |
| | | isStartAndEndPoint: false, |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | // 标记首尾为 true |
| | | if (routePointList.length > 0) { |
| | | routePointList[0].isStartAndEndPoint = true; |
| | | routePointList[routePointList.length - 1].isStartAndEndPoint = true; |
| | | } |
| | | |
| | | console.log(routePointList); |
| | | let params = { |
| | | routePointList:routePointList |
| | | } |
| | | console.log('生成航线') |
| | | buildKmz(params).then(res=>{ |
| | | if(res.code == 200) { |
| | | this.$message({ |
| | | message: '生成航线成功', |
| | | type: 'success' |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } |