<template>
|
<div>
|
<el-dialog
|
title="新建项目"
|
:visible="dialogVisible"
|
append-to-body
|
fullscreen
|
:before-close="handleClose">
|
<el-row :gutter="6">
|
<el-col :span=6>
|
<el-card class="noScroll">
|
<div class="chooseModel">
|
<div class="fontJust">设备列表</div>
|
<el-button @click="addDevice"> 新增</el-button>
|
<el-table
|
:data="tableData"
|
max-height = '180'
|
style="width: 100%">
|
<el-table-column
|
prop="deviceName"
|
label="设备名称"
|
width="150">
|
</el-table-column>
|
<el-table-column
|
prop="mode"
|
label="模板"
|
width="120">
|
</el-table-column>
|
<el-table-column
|
fixed="right"
|
label="操作"
|
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>
|
</el-table-column>
|
</el-table>
|
</div>
|
</el-card>
|
<el-card >
|
<div class="chooseModelTree">
|
<!-- <el-button type="primary" @click='chooseModel'>添加设备</el-button> -->
|
<div class="fontJust">巡检点目录</div>
|
<el-tree
|
class="filter-tree"
|
:data="treeData"
|
:props="defaultProps"
|
draggable
|
default-expand-all
|
ref="tree">
|
</el-tree>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span=18>
|
<CesiumMap ></CesiumMap>
|
</el-col>
|
</el-row>
|
<span slot="footer" class="dialog-footer">
|
<el-button @click="handleClose">取 消</el-button>
|
<el-button type="primary" @click="submit()" >生成航线任务</el-button>
|
</span>
|
</el-dialog>
|
<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} from "@/api/system/template"
|
|
export default{
|
components: {
|
CesiumMap,
|
chooseDeviceDialog,
|
chooseModelDialog
|
},
|
data(){
|
return{
|
dialogVisible :true,
|
tableData: [],
|
treeData:[],
|
defaultProps: {
|
children: 'children',
|
label: 'label'
|
},
|
showDevice:false,
|
multipleSelection: [],
|
showModel:false,
|
deviceId:null
|
}
|
},
|
methods:{
|
handleClose(){
|
this.dialogVisible = false
|
},
|
flyToLocal(row){
|
console.log(row)
|
const position = Cesium.Cartesian3.fromDegrees(row.longitude,row.latitude, row.altitude);
|
|
// 设置模型方向(可选)
|
const heading = Cesium.Math.toRadians(row.face); // 朝东南方向
|
const pitch = 0;
|
const roll = 0;
|
const orientation = Cesium.Transforms.headingPitchRollQuaternion(
|
position,
|
new Cesium.HeadingPitchRoll(heading, pitch, roll)
|
);
|
|
// 加载 glTF 模型
|
const entity = viewer.entities.add({
|
name: row.id,
|
position: position,
|
orientation: orientation,
|
model: {
|
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.showDevice = true
|
},
|
cancel(){
|
this.showDevice = false
|
},
|
hasSameId(array1, array2) {
|
const ids1 = new Set(array1.map(item => item.id));
|
const ids2 = new Set(array2.map(item => item.id));
|
return ids1.size !== [...ids1].filter(id => ids2.has(id)).length;
|
},
|
dealChooseArr(arr){
|
if(this.tableData?.length == 0 ){
|
this.tableData = arr
|
}else {
|
const hasSameId = arr.filter(obj1 =>
|
this.tableData.some(obj2 => obj1.id === obj2.id)
|
);
|
if(hasSameId?.length != 0){
|
return this.$message({
|
message: '选择设备数据和已有数据重复',
|
type: 'warning'
|
})
|
}
|
this.tableData = this.treeData.concat(arr)
|
}
|
const dealTreeData =this.dealTee()
|
this.treeData = dealTreeData
|
},
|
dealTee(){
|
console.log(this.tableData)
|
return this.tableData.map(item => ({
|
...item,
|
label: item.deviceName,
|
}));
|
},
|
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());
|
}
|
}
|
}
|
|
</script>
|
<style scoped>
|
.filter-tree{
|
height: 365px;
|
width: 100%;
|
overflow: auto;
|
}
|
.fontJust{
|
text-align: center;
|
}
|
.el-table {
|
height: 180px;
|
}
|
</style>
|