<template>
|
<div>
|
<el-dialog
|
title="拍摄点编辑"
|
:visible="dialogVisible"
|
append-to-body
|
fullscreen
|
:before-close="handleClose">
|
<el-row :gutter="6">
|
<el-col :span="4">
|
<el-card class="noScroll">
|
<div class="chooseModel">
|
<el-button type="primary" @click='chooseModel'>选择模型</el-button>
|
<div class="modelType">
|
模型名称:模型塔1
|
</div>
|
<div>
|
模型类型:电塔
|
</div>
|
</div>
|
<div class="modelTree">
|
<div class='modelTreeTitle'>巡检点目录</div>
|
<el-tree
|
class="filter-tree"
|
:data="treeData"
|
:props="defaultProps"
|
draggable
|
:allow-drop="allowDrop"
|
@node-drop="handleDrop"
|
default-expand-all
|
ref="tree">
|
</el-tree>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="20">
|
<!-- <el-card class="noScroll"> -->
|
<InitMap v-if="showMap" @mergePoint="mergePoint"></InitMap>
|
<!-- </el-card> -->
|
</el-col>
|
<!-- <el-col :span="8">
|
<div>
|
<div class="cameraView">
|
<a>相机视角</a>
|
</div>
|
<div class="uavView">
|
<a>无人机视角</a>
|
</div>
|
<div class="operationControl">
|
<a>操控相关(使用以下键控制相机视角)</a>
|
<div>
|
<el-button type="primary">地面点</el-button>
|
<el-button type="primary">空中点</el-button>
|
<el-button type="primary">悬停时间</el-button>
|
</div>
|
<div>
|
<div class="container">
|
<div class="keypad">
|
<div class="row">
|
<div class="key">Q-上升</div>
|
<div class="key">W-向前</div>
|
<div class="key">E-下降</div>
|
</div>
|
<div class="row">
|
<div class="key">A- 向左</div>
|
<div class="key">S- 向后</div>
|
<div class="key">D- 向右</div>
|
</div>
|
</div>
|
<div class="arrow-pad">
|
<div class="arrow-row">
|
<div class="arrow empty"></div>
|
<div class="arrow">↑ - 向上看</div>
|
<div class="arrow empty"></div>
|
</div>
|
<div class="arrow-row">
|
<div class="arrow">← - 向左转</div>
|
<div class="arrow">↓ - 向下看</div>
|
<div class="arrow">→ - 向右转</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-col> -->
|
</el-row>
|
<span slot="footer" class="dialog-footer">
|
<el-button @click="closeModal">取 消</el-button>
|
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
|
</span>
|
</el-dialog>
|
<ChooseModelDialog v-if="showModel" @cancel ='cancel' @getRowData="getRowData" ></ChooseModelDialog>
|
</div>
|
</template>
|
<script>
|
import CesiumMap from "../../../../utils/components/cesium-map.vue";
|
import InitMap from "../../../../utils/components/init-map.vue";
|
import ChooseModelDialog from './chooseModelDialog.vue';
|
|
export default{
|
name:'shootPointDialog',
|
components: {
|
CesiumMap,
|
ChooseModelDialog,
|
InitMap
|
},
|
data(){
|
return{
|
dialogVisible :true,
|
showModel:false,
|
// treeData: [{
|
// id: 1,
|
// label: '一级 1',
|
// children: [{
|
// id: 4,
|
// label: '二级 1-1',
|
// }]
|
// }, {
|
// id: 2,
|
// label: '一级 2',
|
// children: [{
|
// id: 5,
|
// label: '二级 2-1'
|
// }]
|
// }, {
|
// id: 3,
|
// label: '一级 3',
|
// children: [{
|
// id: 7,
|
// label: '二级 3-1'
|
// }]
|
// }],
|
treeData:[],
|
defaultProps: {
|
children: 'children',
|
label: 'label'
|
},
|
// 相机初始状态
|
initialCameraPosition: null,
|
initialCameraOrientation: null,
|
animationFrameId: null,
|
showMap:false
|
}
|
},
|
methods:{
|
handleClose(){
|
this.dialogVisible = false
|
},
|
chooseModel(){
|
this.showModel = true
|
},
|
closeModal(){
|
this.$emit('close')
|
},
|
cancel(){
|
this.showModel = false
|
},
|
mergePoint(arr,mergeNumber,viewer){
|
this.treeData = arr
|
console.log(mergeNumber)
|
const result = this.replaceCloseChildrenWithHighestPoint(this.treeData,mergeNumber);
|
console.log(result)
|
console.log(viewer)
|
console.log(window.viewer)
|
const updated = this.insertRaisedPoints(result, 125.1541, 46.5542, 236, 130);
|
this.treeData = updated
|
|
console.log(updated)
|
const airPoints = []
|
this.treeData.forEach(item => {
|
const ground = item
|
const air = item.children[0]
|
|
// 地面点到空中点 — 白色虚线
|
viewer.entities.add({
|
polyline: {
|
positions: Cesium.Cartesian3.fromDegreesArrayHeights([
|
ground.longitude, ground.latitude, ground.height,
|
air.longitude, air.latitude, air.height
|
]),
|
width: 2,
|
material: new Cesium.PolylineDashMaterialProperty({
|
color: Cesium.Color.WHITE,
|
dashLength: 16.0
|
})
|
}
|
})
|
|
airPoints.push(air)
|
})
|
if (airPoints.length > 1) {
|
const positions = airPoints.map(p => [p.longitude, p.latitude, p.height]).flat()
|
viewer.entities.add({
|
polyline: {
|
positions: Cesium.Cartesian3.fromDegreesArrayHeights(positions),
|
width: 2,
|
material: Cesium.Color.YELLOW
|
}
|
})
|
}
|
},
|
insertRaisedPoints(data, centerLon, centerLat, centerHeight, radius = 130) {
|
const center = Cesium.Cartesian3.fromDegrees(centerLon, centerLat, centerHeight);
|
const result = JSON.parse(JSON.stringify(data)); // 深拷贝避免污染原数据
|
const inserts = []; // 用于存储插入项及其目标位置
|
|
for (let i = 0; i < result.length - 1; i++) {
|
const pointA = result[i].children?.[0];
|
const pointB = result[i + 1].children?.[0];
|
|
if (!pointA || !pointB) continue;
|
|
const cartA = Cesium.Cartesian3.fromDegrees(pointA.longitude, pointA.latitude, pointA.height);
|
const cartB = Cesium.Cartesian3.fromDegrees(pointB.longitude, pointB.latitude, pointB.height);
|
|
const closest = this.closestPointOnSegment(cartA, cartB, center);
|
const distance = Cesium.Cartesian3.distance(closest, center);
|
console.log(distance)
|
if (distance < radius) {
|
// 对第 i 个点插入新数据
|
const groundA = result[i];
|
const raisedA = {
|
id: this.generateId(),
|
label: groundA.label+ "-加高",
|
longitude: groundA.longitude,
|
latitude: groundA.latitude,
|
height: groundA.height,
|
children: [
|
{
|
...pointA,
|
id: this.generateId(),
|
label: pointA.label + "-加高",
|
height: pointA.height + 50
|
}
|
]
|
};
|
inserts.push({ index: i + 1, item: raisedA });
|
|
// 对第 i+1 个点插入新数据
|
const groundB = result[i + 1];
|
const raisedB = {
|
id: this.generateId(),
|
label: groundB.label,
|
longitude: groundB.longitude,
|
latitude: groundB.latitude,
|
height: groundB.height,
|
children: [
|
{
|
...pointB,
|
id: this.generateId(),
|
label: pointB.label + "-加高",
|
height: pointB.height + 50
|
}
|
]
|
};
|
inserts.unshift({ index: i + 2, item: raisedB }); // 注意插入后下标需要 +1
|
i++; // 跳过已处理对,避免重复
|
}
|
}
|
|
// 逆序插入,避免下标错位
|
inserts.reverse().forEach(({ index, item }) => {
|
result.splice(index, 0, item);
|
});
|
|
return result;
|
},
|
|
closestPointOnSegment(A, B, P) {
|
const AB = Cesium.Cartesian3.subtract(B, A, new Cesium.Cartesian3());
|
const AP = Cesium.Cartesian3.subtract(P, A, new Cesium.Cartesian3());
|
const ab2 = Cesium.Cartesian3.dot(AB, AB);
|
const ap_ab = Cesium.Cartesian3.dot(AP, AB);
|
const t = Cesium.Math.clamp(ap_ab / ab2, 0.0, 1.0);
|
const result = new Cesium.Cartesian3();
|
Cesium.Cartesian3.multiplyByScalar(AB, t, result);
|
return Cesium.Cartesian3.add(A, result, result);
|
},
|
generateId() {
|
return 'id_' + Math.random().toString(36).substring(2, 10);
|
},
|
replaceCloseChildrenWithHighestPoint(data, maxDistance ) {
|
const childrenPoints = [];
|
|
// 收集所有 children 点及其父引用
|
data.forEach(parent => {
|
parent.children.forEach(child => {
|
childrenPoints.push({
|
parent,
|
child
|
});
|
});
|
});
|
|
const groups = [];
|
|
// 标记是否被分组
|
const visited = new Array(childrenPoints.length).fill(false);
|
|
for (let i = 0; i < childrenPoints.length; i++) {
|
if (visited[i]) continue;
|
|
const group = [childrenPoints[i]];
|
visited[i] = true;
|
|
const pointA = Cesium.Cartesian3.fromDegrees(
|
childrenPoints[i].child.longitude,
|
childrenPoints[i].child.latitude
|
);
|
|
for (let j = i + 1; j < childrenPoints.length; j++) {
|
if (visited[j]) continue;
|
|
const pointB = Cesium.Cartesian3.fromDegrees(
|
childrenPoints[j].child.longitude,
|
childrenPoints[j].child.latitude
|
);
|
|
const distance = Cesium.Cartesian3.distance(pointA, pointB);
|
if (distance <= maxDistance) {
|
group.push(childrenPoints[j]);
|
visited[j] = true;
|
}
|
}
|
|
groups.push(group);
|
}
|
|
// 替换为最高的点
|
groups.forEach(group => {
|
let highest = group[0].child;
|
group.forEach(item => {
|
if (item.child.height > highest.height) {
|
highest = item.child;
|
}
|
});
|
|
// 替换所有该组中的 children
|
group.forEach(item => {
|
item.parent.children = [Object.assign({}, highest)]; // 深拷贝防止引用混乱
|
});
|
});
|
|
return data;
|
},
|
getRowData(row){
|
console.log(row)
|
// //选择好模型 并且自动飞到模型位置
|
// const position = Cesium.Cartesian3.fromDegrees(0, 0, 0);
|
// // 设置模型方向(可选)
|
// // const heading = Cesium.Math.toRadians(135); // 朝东南方向
|
// let model = viewer.entities.getById("modelList");
|
// const heading = Cesium.Math.toRadians(120); // 朝东南方向
|
// const pitch = 0;
|
// const roll = 0;
|
// const orientation = Cesium.Transforms.headingPitchRollQuaternion(
|
// position,
|
// new Cesium.HeadingPitchRoll(heading, pitch, roll)
|
// );
|
// if(!model) {
|
// // 加载 glTF 模型
|
// const entity = viewer.entities.add({
|
// id: "modelList",
|
// name: "modelList",
|
// position: position,
|
// orientation: orientation,
|
// model: {
|
// uri: "/Model/tower.glb", // 替换成你的模型路径
|
// scale: 1000,
|
// },
|
// });
|
// viewer.flyTo(entity)
|
// }else {
|
// model.orientation = orientation
|
// }
|
this.showMap = true
|
},
|
beforeDestroy() {
|
// 清理 RAF 和事件
|
cancelAnimationFrame(this.animationFrameId);
|
document.removeEventListener("keydown", this.onKeyDown);
|
document.removeEventListener("keyup", this.onKeyUp);
|
},
|
allowDrag (draggingNode) {
|
// 例如根节点也允许拖动,这里直接返回 true
|
return true
|
},
|
allowDrop (draggingNode, dropNode, dropType) {
|
if (dropType === 'inner') return false
|
|
const dragParent = draggingNode.parent
|
const dropParent = dropNode.parent
|
|
// 如果都是顶级节点(parent 为 null),允许互换
|
if (!dragParent && !dropParent) {
|
return dropType === 'prev' || dropType === 'next'
|
}
|
|
// 如果有父节点,必须是同一个父节点
|
if (dragParent && dropParent && dragParent === dropParent) {
|
return dropType === 'prev' || dropType === 'next'
|
}
|
|
// 其他情况不允许
|
return false
|
},
|
handleDrop (draggingNode, dropNode, dropType, ev) {
|
console.log('拖拽完成', { draggingNode, dropNode, dropType })
|
// Element-UI 已经帮你把 treeData 的顺序调好了,
|
// 如果要保存顺序到后端,遍历 treeData 发请求即可
|
}
|
}
|
}
|
</script>
|
<style scoped>
|
.noScroll{
|
height: calc(100vh - 185px);
|
overflow: auto;
|
position: relative;
|
}
|
.modelType{
|
margin-top: 10px;
|
}
|
.modelTreeTitle {
|
text-align: center;
|
}
|
.chooseModel{
|
height: 100px;
|
/* border: 1px solid #dddddd; */
|
}
|
.cameraView{
|
height: 250px;
|
text-align: center;
|
border: 1px solid #dddddd;
|
}
|
.uavView{
|
height: 250px;
|
text-align: center;
|
border: 1px solid #dddddd;
|
|
}
|
.operationControl{
|
text-align: center;
|
height: 220px;
|
border: 1px solid #dddddd;
|
|
}
|
.container {
|
text-align: left;
|
/* margin-top: 60px; */
|
font-family: Arial, sans-serif;
|
}
|
|
.keypad {
|
display: inline-block;
|
padding:5px;
|
background: #f2f2f2;
|
border-radius: 16px;
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
}
|
|
.row {
|
display: flex;
|
justify-content: center;
|
margin: 10px 0;
|
}
|
|
.key {
|
width: 60px;
|
height: 60px;
|
margin: 0 10px;
|
background-color: white;
|
border-radius: 10px;
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
|
font-size: 12x;
|
font-weight: bold;
|
text-align: center;
|
line-height: 60px;
|
transition: all 0.2s ease;
|
}
|
|
.key.active {
|
background-color: #42b983;
|
color: white;
|
font-size: 12px;
|
box-shadow: 0 4px 15px rgba(66, 185, 131, 0.6);
|
}
|
.arrow-pad {
|
display: inline-block;
|
padding: 10px;
|
float: right;
|
/* margin-left: 50px; */
|
background: #f2f2f2;
|
border-radius: 16px;
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
margin-bottom: 20px;
|
}
|
|
.arrow-row {
|
display: flex;
|
justify-content: center;
|
margin: 5px 0;
|
}
|
|
.arrow {
|
width: 60px;
|
height: 60px;
|
margin: 0 5px;
|
background-color: white;
|
border-radius: 10px;
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
|
font-size: 12px;
|
font-weight: bold;
|
text-align: center;
|
line-height: 60px;
|
transition: all 0.2s ease;
|
}
|
|
.arrow.active {
|
background-color: #42b983;
|
color: white;
|
transform: scale(1.1);
|
box-shadow: 0 4px 15px rgba(66, 185, 131, 0.6);
|
}
|
|
.arrow.empty {
|
background: transparent;
|
box-shadow: none;
|
}
|
</style>
|