<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">
|
<InitMap v-if="showMap" @mergePoint="mergePoint" @renderData="renderData" :towerUrl="towerUrl"></InitMap>
|
</el-col>
|
</el-row>
|
<span slot="footer" class="dialog-footer">
|
<el-button @click="closeModal">取 消</el-button>
|
<el-button type="primary" @click="submit()" >确 定</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';
|
// const centerCartesian = Cesium.Cartesian3.fromDegrees(0,0, 236)
|
// const centerCartesian = Cesium.Cartesian3.fromDegrees(125.1949, 46.5143, 0)
|
let globalon = 125.1949
|
let globalat = 46.5143
|
const centerCartesian = Cesium.Cartesian3.fromDegrees(globalon,globalat , 236)
|
|
let relativeData
|
export default{
|
name:'shootPointDialog',
|
components: {
|
CesiumMap,
|
ChooseModelDialog,
|
InitMap
|
},
|
data(){
|
return{
|
dialogVisible :true,
|
showModel:false,
|
treeData:[],
|
defaultProps: {
|
children: 'children',
|
label: 'label'
|
},
|
// 相机初始状态
|
initialCameraPosition: null,
|
initialCameraOrientation: null,
|
animationFrameId: null,
|
showMap:false,
|
towerUrl:null,
|
chooseModelId:null
|
}
|
},
|
mounted(){
|
},
|
methods:{
|
renderRelativePoints(viewer, data) {
|
const airPoints = [];
|
|
data.forEach(item => {
|
const groundRel = item.relativePosition;
|
const groundPos = Cesium.Cartesian3.add(
|
centerCartesian,
|
new Cesium.Cartesian3(groundRel.x, groundRel.y, groundRel.z),
|
new Cesium.Cartesian3()
|
);
|
this.addPoint(viewer, groundPos, Cesium.Color.BLUE, item.label);
|
|
item.children?.forEach(child => {
|
const airRel = child.relativePosition;
|
const airPos = Cesium.Cartesian3.add(
|
centerCartesian,
|
new Cesium.Cartesian3(airRel.x, airRel.y, airRel.z),
|
new Cesium.Cartesian3()
|
);
|
this.addPoint(viewer, airPos, Cesium.Color.YELLOW, child.label);
|
|
// 地面点到空中点用虚线
|
this.drawLine(viewer, [groundPos, airPos], Cesium.Color.GRAY, true);
|
|
airPoints.push(airPos);
|
});
|
});
|
|
// 空中点之间连黄线
|
if (airPoints.length > 1) {
|
this.drawLine(viewer, airPoints, Cesium.Color.YELLOW);
|
}
|
},
|
addPoint(viewer, position, color, label) {
|
viewer.entities.add({
|
name: label,
|
position,
|
point: {
|
pixelSize: 10,
|
color: color,
|
heightReference: Cesium.HeightReference.NONE
|
},
|
label: {
|
text: label,
|
font: '12px sans-serif',
|
pixelOffset: new Cesium.Cartesian2(10, -10),
|
style: Cesium.LabelStyle.FILL,
|
fillColor: color,
|
showBackground: true,
|
}
|
});
|
},
|
drawLine(viewer, positions, color, dashed = false) {
|
viewer.entities.add({
|
polyline: {
|
positions,
|
width: 2,
|
material: dashed
|
? new Cesium.PolylineDashMaterialProperty({
|
color: color,
|
dashLength: 8,
|
})
|
: color,
|
}
|
});
|
},
|
//空间坐标反渲染
|
renderData(viewerM){
|
let data = [
|
{
|
"id": "197ced8ed37d58af",
|
"label": "地面点0",
|
"longitude": 125.15396706149502,
|
"latitude": 46.5542193007498,
|
"height": 110.34582699579633,
|
"children": [
|
{
|
"id": "197ced8ed37d12bf",
|
"label": "空中点0",
|
"longitude": 125.15269101779384,
|
"latitude": 46.554404555297005,
|
"height": 110.34669117272557,
|
"relativePosition": {
|
"x": 147.6013223491609,
|
"y": -21.93333655036986,
|
"z": -75.59047747310251
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 58.98418062273413,
|
"y": -66.05175063293427,
|
"z": -89.7526711942628
|
}
|
},
|
{
|
"id": "197ced8ed372ae58",
|
"label": "地面点1",
|
"longitude": 125.15396775899208,
|
"latitude": 46.55421205599431,
|
"height": 129.0598176992881,
|
"children": [
|
{
|
"id": "197ced8ed3796b97",
|
"label": "空中点1",
|
"longitude": 125.15267500755259,
|
"latitude": 46.55432990371885,
|
"height": 129.06068032390132,
|
"relativePosition": {
|
"x": 137.72684245044366,
|
"y": -5.778328162617981,
|
"z": -67.71026689186692
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 51.19412293517962,
|
"y": -55.08268230734393,
|
"z": -76.7196572907269
|
}
|
},
|
{
|
"id": "197ced8ed373aacd",
|
"label": "地面点2",
|
"longitude": 125.15398622045456,
|
"latitude": 46.55420774953827,
|
"height": 147.7039314240401,
|
"children": [
|
{
|
"id": "197ced8ed3744bed",
|
"label": "空中点2",
|
"longitude": 125.15268854335208,
|
"latitude": 46.5542961263164,
|
"height": 147.70478259476405,
|
"relativePosition": {
|
"x": 127.92680113529786,
|
"y": 6.335606334730983,
|
"z": -56.756238551810384
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 42.45445924857631,
|
"y": -45.13116115285084,
|
"z": -63.51276256516576
|
}
|
},
|
{
|
"id": "id_v5g7eyc8",
|
"label": "地面点2-加高",
|
"longitude": 125.15398622045456,
|
"latitude": 46.55420774953827,
|
"height": 147.7039314240401,
|
"children": [
|
{
|
"id": "id_6w8bt6y5",
|
"label": "空中点2-加高",
|
"longitude": 125.15268854335208,
|
"latitude": 46.5542961263164,
|
"height": 197.70478259476405,
|
"relativePosition": {
|
"x": 108.13033775659278,
|
"y": 34.448136328253895,
|
"z": -20.454920462332666
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 42.45445924857631,
|
"y": -45.13116115285084,
|
"z": -63.51276256516576
|
}
|
},
|
{
|
"id": "id_wztyoy7w",
|
"label": "地面点3",
|
"longitude": 125.15421307297069,
|
"latitude": 46.55421685854876,
|
"height": 148.48833812454288,
|
"children": [
|
{
|
"id": "id_cu78b9f1",
|
"label": "空中点3-加高",
|
"longitude": 125.15548763490776,
|
"latitude": 46.55440688016316,
|
"height": 198.48919020619783,
|
"relativePosition": {
|
"x": -62.53922047605738,
|
"y": -96.01450884295627,
|
"z": -11.418861056677997
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 28.343377863056958,
|
"y": -55.307947248686105,
|
"z": -62.246930407360196
|
}
|
},
|
{
|
"id": "197ced8ed37d0c3c",
|
"label": "地面点3",
|
"longitude": 125.15421307297069,
|
"latitude": 46.55421685854876,
|
"height": 148.48833812454288,
|
"children": [
|
{
|
"id": "197ced8ed376225d",
|
"label": "空中点3",
|
"longitude": 125.15548763490776,
|
"latitude": 46.55440688016316,
|
"height": 148.48919020619783,
|
"relativePosition": {
|
"x": -42.741424133535475,
|
"y": -124.12601430807263,
|
"z": -47.72024560905993
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 28.343377863056958,
|
"y": -55.307947248686105,
|
"z": -62.246930407360196
|
}
|
},
|
{
|
"id": "197ced8ed378f22b",
|
"label": "地面点4",
|
"longitude": 125.15423241198647,
|
"latitude": 46.55421195661101,
|
"height": 129.07723014528972,
|
"children": [
|
{
|
"id": "197ced8ed37bbaff",
|
"label": "空中点4",
|
"longitude": 125.1555253737729,
|
"latitude": 46.55432870085922,
|
"height": 129.07809286219634,
|
"relativePosition": {
|
"x": -41.05480555212125,
|
"y": -131.5471526850015,
|
"z": -67.78957635723054
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 34.58874865854159,
|
"y": -66.75211867084727,
|
"z": -76.71461268886924
|
}
|
},
|
{
|
"id": "197ced8ed372f8ad",
|
"label": "地面点5",
|
"longitude": 125.15423571155253,
|
"latitude": 46.55422768619577,
|
"height": 111.12748104722147,
|
"children": [
|
{
|
"id": "197ced8ed373e993",
|
"label": "空中点5",
|
"longitude": 125.15551594222936,
|
"latitude": 46.554398406019274,
|
"height": 112.12216624922397,
|
"relativePosition": {
|
"x": -30.5103326481767,
|
"y": -145.26342066843063,
|
"z": -74.77150713000447
|
}
|
}
|
],
|
"relativePosition": {
|
"x": 42.219932773150504,
|
"y": -78.02784454869106,
|
"z": -88.54415378533304
|
}
|
}
|
]
|
this.renderRelativePoints(viewerM,data)
|
},
|
handleClose(){
|
this.dialogVisible = false
|
},
|
chooseModel(){
|
this.showModel = true
|
},
|
closeModal(){
|
this.$emit('close')
|
},
|
cancel(){
|
this.showModel = false
|
},
|
mergePoint(arr,mergeNumber,viewer){
|
this.treeData = arr
|
const result = this.replaceCloseChildrenWithHighestPoint(this.treeData,mergeNumber);
|
// const updated = this.insertRaisedPoints(result, 0,0, 236, 130);
|
const updated = this.insertRaisedPoints(result, globalon, globalat, 236, 130);
|
this.treeData = 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: 5,
|
material: new Cesium.PolylineDashMaterialProperty({
|
color: Cesium.Color.RED,
|
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: 5,
|
material: Cesium.Color.RED
|
}
|
})
|
}
|
console.log(this.treeData)
|
// this.conversionPosition()
|
this.dealData()
|
},
|
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)
|
this.chooseModelId = row.id
|
this.showMap =false
|
this.$nextTick(()=>{
|
this.towerUrl = row.modelRoute
|
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 })
|
},
|
toRelativePosition(lon, lat, height, centerCartesian) {
|
const worldPos = Cesium.Cartesian3.fromDegrees(lon, lat, height);
|
const relative = new Cesium.Cartesian3();
|
Cesium.Cartesian3.subtract(worldPos, centerCartesian, relative);
|
return relative;
|
},
|
wgs84ToCartesian(longitude, latitude, height) {
|
return Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
|
},
|
getOffsetFromTower(lon, lat, height, tower) {
|
const point = this.wgs84ToCartesian(lon, lat, height);
|
const towerPoint = this.wgs84ToCartesian(tower.longitude, tower.latitude, tower.height);
|
|
const offset = Cesium.Cartesian3.subtract(point, towerPoint, new Cesium.Cartesian3());
|
return {
|
x: offset.x,
|
y: offset.y,
|
z: offset.z
|
};
|
},
|
dealData(){
|
const rawData = this.treeData
|
let tower ={
|
longitude:globalon,
|
latitude:globalat,
|
height:0
|
}
|
relativeData= rawData.forEach(item => {
|
item.offset = this.getOffsetFromTower(item.longitude, item.latitude, item.height, tower);
|
|
item.children?.forEach(child => {
|
child.offset = this.getOffsetFromTower(child.longitude, child.latitude, child.height, tower);
|
});
|
})
|
console.log(relativeData)
|
},
|
// conversionPosition(){
|
// const rawData = this.treeData
|
// // const centerCartesian = Cesium.Cartesian3.fromDegrees(0,0, 236);
|
// const centerCartesian = Cesium.Cartesian3.fromDegrees(globalon, globalat, 236);
|
// relativeData= rawData.map(ground => {
|
// const relativeGround = this.toRelativePosition(
|
// ground.longitude,
|
// ground.latitude,
|
// ground.height,
|
// centerCartesian
|
// );
|
|
// const children = ground.children?.map(child => {
|
// const relativeChild = this.toRelativePosition(
|
// child.longitude,
|
// child.latitude,
|
// child.height,
|
// centerCartesian
|
// );
|
// return {
|
// ...child,
|
// relativePosition: {
|
// x: relativeChild.x,
|
// y: relativeChild.y,
|
// z: relativeChild.z,
|
// }
|
// };
|
// });
|
|
// return {
|
// ...ground,
|
// relativePosition: {
|
// x: relativeGround.x,
|
// y: relativeGround.y,
|
// z: relativeGround.z,
|
// },
|
// children
|
// };
|
// });
|
// console.log(relativeData)
|
// },
|
submit(){
|
// this.dialogVisible = false
|
this.$emit('close')
|
console.log(this.chooseModelId)
|
console.log(relativeData)
|
}
|
}
|
}
|
</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>
|