jihongshun
8 天以前 c954cdb51f93585b58b761c2663688c36e6b044f
src/views/system/shootPoint/components/shootPointDialog.vue
@@ -12,29 +12,67 @@
                <div class="chooseModel">
                  <el-button type="primary" @click='chooseModel'>选择模型</el-button>
                  <div class="modelType">
                    模型名称:{{ templateName }}
                    模型名称:{{ modelName }}
                  </div>
                  <div>
                    模型类型:{{ templateType }}
                  </div>
                  <el-form label-width="70px" :model="form">
                    <el-form-item label="模板名称">
                      <el-input v-model="form.templateName"></el-input>
                    </el-form-item>
                  </el-form>
                </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>
                  <el-tabs v-model="activeName">
                    <el-tab-pane label="航点树" name="first">
                        <el-tree
                          class="filter-tree"
                          :data="treeData"
                          :props="defaultProps"
                          draggable
                          :allow-drop="allowDrop"
                          @node-drop="handleDrop"
                          default-expand-all
                          @node-click="handleNodeClick"
                          ref="tree">
                        </el-tree>
                    </el-tab-pane>
                    <el-tab-pane label="航点列表" name="second">
                      <div class="waypoint-panel">
                        <!-- 航点列表 -->
                        <div class="waypoints">
                          <div
                            v-for="(wp, index) in waypoints"
                            :key="index"
                            class="waypoint-item"
                            :class="{ active: selectedIndex === index }"
                            @click="toggleSelect(wp,index)"
                          >
                            <div class="waypoint-header">
                              <i
                                class="el-icon-success"
                              ></i>
                              <span>
                                <span class="waypoint-index">{{ wp.label }}</span>
                                <i class="el-icon-camera"></i>
                                <i class="el-icon-video-camera"></i>
                                <i class="el-icon-picture-outline"></i>
                              </span>
                            </div>
                          </div>
                        </div>
                      </div>
                    </el-tab-pane>
                  </el-tabs>
                </div>
              </el-card>
          </el-col>
          <el-col :span="20">
              <InitMap v-if="showMap" @mergePoint="mergePoint" :towerUrl="towerUrl"></InitMap>
              <InitMap v-if="showMap" @mergePoint="mergePoint" :towerUrl="towerUrl"  :deviceData="deviceData"  @dealTreeData="dealTreeData" ref="initMap"></InitMap>
          </el-col>
      </el-row>
      <span slot="footer" class="dialog-footer">
@@ -42,25 +80,31 @@
        <el-button type="primary" @click="submit()" >确 定</el-button>
      </span>
    </el-dialog>
    <ChooseModelDialog v-if="showModel" @cancel ='cancel' @getRowData="getRowData" ></ChooseModelDialog>
    <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';
import { addPoint} from "@/api/system/template"
 let globalon  = 0
import { addPoint , getPointInfo} from "@/api/system/template"
let globalon  = 0
let globalat = 0
const centerCartesian = Cesium.Cartesian3.fromDegrees(globalon,globalat , 0)
let rotationAngle = Cesium.Math.toRadians(60)
let relativeData
let towerHeight = 45
//塔的朝向算法所用到的 旋转度数
let rotationAngle = Cesium.Math.toRadians(0)
export default{
  name:'shootPointDialog',
   components: {
    CesiumMap,
    ChooseModelDialog,
    InitMap
  },
  props: {
    templateId: {
      type: String,
      defaule: null
    }
  },
  data(){
    return{
@@ -78,13 +122,194 @@
        showMap:false,
        towerUrl:null,
        chooseModelId:null,
        templateName:null,
        templateType:null
        modelName:null,
        templateType:null,
        form:{},
        deviceData:null,
        activeName: 'first',
        totalDistance: 3054.9,
        totalTime: '5m 56s',
        totalPoints: 11,
        totalPhotos: 3,
        waypoints:[],
        selectedIndex:null
    }
  },
  mounted(){
    console.log(this.templateId)
    if(this.templateId) {
      //预览逻辑
      getPointInfo(this.templateId).then(res=>{
        if(res.code == 200 ) {
          console.log(res.data)
          this.getRowData(res.data)
          let drawArr = this.convertToTree(res.data.ardListWayPointsLS)
          setTimeout(() => {
            //渲染时间问题 加个延时器
            this.drawLines(drawArr)
          }, 500);
        }
      })
    }
  },  
  methods:{
    toggleSelect(wq,index) {
      console.log(this.treeData)
      console.log(wq)
      // 如果点击的是当前行,则取消选中
      if (this.selectedIndex === index) {
        this.selectedIndex = null
      } else {
        this.selectedIndex = index
      }
      const result = this.treeData.filter(item =>
          item.children && item.children.some(child => child.label === wq.label)
      );
      const dataObj = result[0]
      const from = [dataObj.children[0]?.longitude,dataObj.children[0]?.latitude, dataObj.children[0]?.height];
      const to = [dataObj.longitude,dataObj.latitude, dataObj.height];
      this.flyToAndLookAt(from, to);
      this.$refs.initMap.DealVisualCone(dataObj)
    },
   // 计算 heading(偏航角)
    computeHeading(fromCartesian, toCartesian) {
      const transform = Cesium.Transforms.eastNorthUpToFixedFrame(fromCartesian);
      const direction = Cesium.Cartesian3.subtract(
        toCartesian,
        fromCartesian,
        new Cesium.Cartesian3()
      );
      const directionLocal = Cesium.Matrix4.multiplyByPointAsVector(
        Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4()),
        direction,
        new Cesium.Cartesian3()
      );
      Cesium.Cartesian3.normalize(directionLocal, directionLocal);
      return Math.atan2(directionLocal.x, directionLocal.y);
    },
    // 计算 pitch(俯仰角)
    computePitch(fromCartesian, toCartesian) {
      const transform = Cesium.Transforms.eastNorthUpToFixedFrame(fromCartesian);
      const direction = Cesium.Cartesian3.subtract(
        toCartesian,
        fromCartesian,
        new Cesium.Cartesian3()
      );
      const directionLocal = Cesium.Matrix4.multiplyByPointAsVector(
        Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4()),
        direction,
        new Cesium.Cartesian3()
      );
      Cesium.Cartesian3.normalize(directionLocal, directionLocal);
      return Math.asin(directionLocal.z); // z轴向上
    },
    // 飞到 from 看向 to
    flyToAndLookAt(from, to) {
      const fromCartesian = Cesium.Cartesian3.fromDegrees(from[0], from[1], from[2]);
      const toCartesian = Cesium.Cartesian3.fromDegrees(to[0], to[1], to[2]);
      const heading = this.computeHeading(fromCartesian, toCartesian);
      const pitch = this.computePitch(fromCartesian, toCartesian);
      let viewer = window.viewerM
      viewer.camera.flyTo({
        destination: fromCartesian,
        orientation: {
          heading: heading,
          pitch: pitch,
          roll: 0.0
        },
        duration: 3
      });
    },
    handleNodeClick(data,node) {
      if(data.children && data.children.length >0) {
        //点击父级
        const from = [data.children[0].longitude,data.children[0].latitude, data.children[0].height];
        const to = [data.longitude,data.latitude, data.height];
        // this.flyToAndLookAt(from, to);
      } else  {
        //点击子级
        const from = [data.longitude,data.latitude, data.height];
        const to = [node.parent?.data?.longitude,node.parent?.data?.latitude, node.parent?.data?.height];
        // this.flyToAndLookAt(from, to);
      }
      this.$refs.initMap.DealVisualCone(data,node)
    },
    dealTreeData(arrList){
      this.treeData = arrList
      const childrenArr = arrList
        .filter(item => Array.isArray(item.children))
        .map(item => item.children)
        .reduce((acc, cur) => acc.concat(cur), []);
      this.waypoints = childrenArr
      console.log(childrenArr)
      console.log(this.treeData)
    },
    convertToTree(data) {
      return data.map((item, index) => {
        const parentId = 'air_' + index;
        return {
          id: parentId,
          label: item.targetName,
          longitude: item.longitude,
          latitude: item.latitude,
          height: item.altitude,
          children: (item.ardGroundPoint || []).map((g, i) => {
            return {
              id: parentId + '_ground_' + i,
              label: g.targetName,
              longitude: g.longitude,
              latitude: g.latitude,
              height: g.height
            }
          })
        }
      });
    },
    drawLines(treeData) {
      console.log(treeData)
      console.log(window)
      console.log(window.viewerM)
      let viewer = window.viewerM
      // 收集空中点位置
      const airPositions = treeData.map(point => {
        return Cesium.Cartesian3.fromDegrees(
          point.longitude,
          point.latitude,
          point.height
        );
      });
      // 1. 空中点之间用黄色实线连接
      viewer.entities.add({
        polyline: {
          positions: airPositions,
          width: 2,
          material: Cesium.Color.YELLOW
        }
      });
      // 2. 每个空中点与地面点用蓝色虚线连接
      treeData.forEach(point => {
        const airPos = Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height);
        point.children.forEach(child => {
          const groundPos = Cesium.Cartesian3.fromDegrees(child.longitude, child.latitude, child.height);
          viewer.entities.add({
            polyline: {
              positions: [airPos, groundPos],
              width: 1,
              material: new Cesium.PolylineDashMaterialProperty({
                color: Cesium.Color.BLUE,
                dashLength: 8
              })
            }
          });
        });
      });
    },
    addPoint(viewer, position, color, label) {
      viewer.entities.add({
        name: label,
@@ -163,18 +388,19 @@
        return newGround;
      });
    },
    //合并逻辑  将附近n米范围内的点合并 并且处理成数据
    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);
      const updated = this.insertRaisedPoints(result, globalon, globalat, towerHeight);
      this.treeData = updated
      const airPoints = []
      this.treeData.forEach(item => {
        const ground = item
        const air = item.children[0]
        // 地面点到空中点 — 白色虚线
        // 地面点到7空中点 — 白色虚线
        viewer.entities.add({
          polyline: {
            positions: Cesium.Cartesian3.fromDegreesArrayHeights([
@@ -204,7 +430,7 @@
      console.log(this.treeData)
    },
    //数组 塔的精度 塔的纬度  塔的高度  半径
    insertRaisedPoints(data, centerLon, centerLat, centerHeight, radius = 130) {
    insertRaisedPoints(data, centerLon, centerLat, centerHeight, radius = 10) {
      const center = Cesium.Cartesian3.fromDegrees(centerLon, centerLat, centerHeight);
      const result = JSON.parse(JSON.stringify(data)); // 深拷贝避免污染原数据
      const inserts = []; // 用于存储插入项及其目标位置
@@ -350,12 +576,21 @@
    },
    getRowData(row){
      console.log(row)
      this.templateName = row.modelName
      this.modelName = row.modelName
      this.templateType = row.modelType
      this.deviceData = row
      if(this.templateId){
        this.form.templateName = row.templateName || ''
      }
      this.chooseModelId = row.id
      towerHeight = row.modelHeight || 45
      this.showMap =false
      this.$nextTick(()=>{
        this.towerUrl = row.modelRoute
        if(this.templateId){
          this.towerUrl = row.ardTowerModel.modelRoute
        }else {
          this.towerUrl = row.modelRoute
        }
        this.showMap = true
      })
    },
@@ -392,34 +627,40 @@
      console.log('拖拽完成', { draggingNode, dropNode, dropType })
    },
    submit(){
      if(!this.form.templateName){
        return  this.$message({
          message: '请先输入模板名称再保存',
          type: 'warning'
        })
      }
      // this.dialogVisible = false
      // const dealArr = this.submitDealData()
      const transformed = this.treeData.map((groundPoint, index) => {
      const pointNumber = index + 1;
      const airPoints = groundPoint.children.map(child => ({
        altitude: child.height,
        flightTemplateId: "",
        latitude: child.latitude,
        longitude: child.longitude,
        pointNumber: 1, // 可根据需要改为 child 编号
        targetName:child.label
      }));
      return {
        ardAirPointLs: airPoints,
        ardGroundPoint: {
          flightTemplateId: "",
          height: groundPoint.height,
          latitude: groundPoint.latitude,
          longitude: groundPoint.longitude,
          pointNumber: 1,
          targetName: groundPoint.label
        },
        pointNumber: pointNumber
      };
    });
      const transformed = this.treeData.map((ground, index) => {
        const pointNumber = index + 1;
        const child = ground.children?.[0] || {};
        return {
          altitude: child.height,
          ardGroundPoint: [
            {
              height: ground.height,
              latitude: ground.latitude,
              longitude: ground.longitude,
              pointNumber: 1,
              targetName: ground.label
            }
          ],
          latitude: child.latitude,
          longitude: child.longitude,
          pointNumber: pointNumber,
          targetName: child.label
        };
    })
    console.log(transformed)
    let parmas = {
      templateName:this.form.templateName,
      modelId:this.chooseModelId,
      ard3DCoordinateSystemLs:transformed
      ardListWayPointsLS:transformed
    }
    addPoint(parmas).then(res=>{
      console.log(res)
@@ -455,7 +696,7 @@
</script>
<style scoped>
.noScroll{
    height: calc(100vh - 185px);
    height: calc(100vh - 175px);
    overflow: auto;
    position: relative;
}
@@ -466,7 +707,7 @@
  text-align: center;
}
.chooseModel{
  height: 100px;
  height: 120px;
  /* border: 1px solid #dddddd; */
}
.cameraView{
@@ -568,4 +809,68 @@
  background: transparent;
  box-shadow: none;
}
</style>
</style>
<style scoped>
.waypoint-panel {
  width: 300px;
  /* background: #1e1e1e;
  color: white; */
  font-size: 14px;
  height: 500px;
  overflow-y: scroll;
  /* overflow: scroll; */
}
.stats {
  display: flex;
  justify-content: space-around;
  padding: 8px;
  border-bottom: 1px solid #333;
}
.stat-item {
  display: flex;
  align-items: center;
}
.stat-item i {
  margin-right: 4px;
}
.waypoint-item {
  border-bottom: 1px solid #333;
  height: 40px;
  line-height: 40px;
}
.waypoint-item.active {
  background-color: rgba(0, 122, 255, 0.3); /* 高亮色 */
}
.waypoint-header {
  display: flex;
  align-items: center;
  cursor: pointer;
  padding: 4px 8px;
}
.waypoint-header i {
  margin-right: 6px;
  height: 36px;
  line-height: 36x;
  transition: transform 0.2s;
  font-size: 24px;
}
.waypoint-header .waypoint-index {
  font-size: 24px;
}
.rotated {
  transform: rotate(-90deg);
}
.waypoint-content {
  padding: 4px 8px;
  display: flex;
  gap: 8px;
}
</style>