jihongshun
2025-09-11 bcb67f336c2b352177884b8db6decc3fcf382bb0
航线模拟  差视锥体的head值和时间轴
已添加2个文件
已修改2个文件
12222 ■■■■■ 文件已修改
src/utils/CesiumNavigation.js 11901 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/components/Compass.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/components/cesium-map.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/flightRouteSimulation/index.vue 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/CesiumNavigation.js
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
src/utils/components/Compass.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
<template>
  <div class="cesium-compass" ref="compass" @mousedown.prevent="startDrag">
    <!-- æŒ‡é’ˆ -->
    <div class="needle" :style="needleStyle"></div>
    <!-- æ–¹å‘æ ‡ -->
    <div class="dir-label dir-n">N</div>
    <div class="dir-label dir-e">E</div>
    <div class="dir-label dir-s">S</div>
    <div class="dir-label dir-w">W</div>
    <!-- å½“前度数 -->
    <div class="heading-label">{{ headingDegRounded }}°</div>
  </div>
</template>
<script>
export default {
  name: 'CesiumCompass',
  props: {
    viewer: { required: true } // ä¼ å…¥ Cesium.Viewer å®žä¾‹
  },
  data() {
    return {
      headingDeg: 0,
      dragging: false,
      dragStartX: 0,
      startHeading: 0,
      rafId: null
    };
  },
  computed: {
    needleStyle() {
      return {
        transform: `rotate(${this.headingDeg}deg)`
      };
    },
    headingDegRounded() {
      // ä¿è¯è§’度在0~360
      let deg = (this.headingDeg % 360 + 360) % 360;
      return deg.toFixed(0);
    }
  },
  mounted() {
    const update = () => {
      if (this.viewer && this.viewer.camera) {
        try {
          const rad = this.viewer.camera.heading;
          if (typeof rad === 'number') {
            this.headingDeg = -Cesium.Math.toDegrees(rad);
          }
        } catch (e) {}
      }
      this.rafId = requestAnimationFrame(update);
    };
    this.rafId = requestAnimationFrame(update);
    window.addEventListener('mouseup', this.stopDrag);
    window.addEventListener('mousemove', this.onMouseMove);
  },
  beforeDestroy() {
    if (this.rafId) cancelAnimationFrame(this.rafId);
    window.removeEventListener('mouseup', this.stopDrag);
    window.removeEventListener('mousemove', this.onMouseMove);
  },
  methods: {
    startDrag(e) {
      this.dragging = true;
      this.dragStartX = e.clientX;
      this.startHeading = this.headingDeg;
      if (this.viewer && this.viewer.scene && this.viewer.scene.screenSpaceCameraController) {
        this.viewer.scene.screenSpaceCameraController.enableRotate = false;
      }
    },
    onMouseMove(e) {
      if (!this.dragging) return;
      const dx = e.clientX - this.dragStartX;
      const newHeading = this.startHeading + dx;
      this.setCameraHeading(-newHeading);
    },
    stopDrag() {
      if (!this.dragging) return;
      this.dragging = false;
      if (this.viewer && this.viewer.scene && this.viewer.scene.screenSpaceCameraController) {
        this.viewer.scene.screenSpaceCameraController.enableRotate = true;
      }
    },
    setCameraHeading(deg) {
      if (!this.viewer) return;
      const rad = Cesium.Math.toRadians(deg);
      const camera = this.viewer.camera;
      const orientation = {
        heading: rad,
        pitch: camera.pitch,
        roll: camera.roll
      };
      this.viewer.camera.setView({ orientation });
      this.headingDeg = -Cesium.Math.toDegrees(this.viewer.camera.heading || 0);
    }
  }
};
</script>
<style scoped>
.cesium-compass {
  position: absolute;
  top: 100px;
  right: 50px;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: rgba(255,255,255,0.9);
  box-shadow: 0 2px 6px rgba(0,0,0,0.25);
  display:flex;
  align-items:center;
  justify-content:center;
  cursor: grab;
  z-index: 9999;
  font-family: sans-serif;
}
.cesium-compass:active { cursor: grabbing; }
/* æŒ‡é’ˆ */
.needle {
  width: 2px;
  height: 30px;
  background: red;
  transform-origin: 50% 80%;
  position: absolute;
  top: 15px;
  transition: transform 0.05s linear;
  border-radius: 1px;
}
/* æ–¹å‘文字 */
.dir-label {
  position: absolute;
  font-weight: bold;
  font-size: 12px;
  color: black;
  user-select: none;
}
.dir-n { top: 4px; left: 50%; transform: translateX(-50%); }
.dir-e { right: 4px; top: 50%; transform: translateY(-50%); }
.dir-s { bottom: 4px; left: 50%; transform: translateX(-50%); }
.dir-w { left: 4px; top: 50%; transform: translateY(-50%); }
/* å½“前角度文字 */
.heading-label {
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 12px;
  font-weight: bold;
  color: black;
}
</style>
src/utils/components/cesium-map.vue
@@ -1,12 +1,13 @@
<template>
  <div :style="{height: cesiumContainer}">
    <div id="cesiumContainer"></div>
     <CesiumCompass v-if="viewer" :viewer="viewer" />
  </div>
</template>
<script>
import {mapState} from "vuex";
import CesiumCompass from './Compass.vue';
let viewer = null;
let googleimagery;
let arcgisimagery;
@@ -28,6 +29,7 @@
let bdtvectoranoimagery;
export default {
  name: "cesium-map",
  components: { CesiumCompass },
  data() {
    return {
      // æ—¥æœŸèŒƒå›´
@@ -40,6 +42,7 @@
        configKey: undefined,
        configType: undefined
      },
      viewer:null
    };
  },
  methods: {
@@ -167,6 +170,7 @@
        direction: new Cesium.Cartesian3(0.354925, -0.890918, -0.283358)
      })
      console.log(viewer)
      this.viewer  = viewer
      window.viewer = viewer;
      this.$emit('mapReady');
    },
src/views/system/flightRouteSimulation/index.vue
@@ -1,6 +1,6 @@
<template>
   <div class="app-container">
    <el-button type="primary" @click="showGZ">主要按钮</el-button>
    <el-button type="primary" @click="showGZ">显示视锥体</el-button>
    <input @change="handleKMZUpload" type="file" id="kmzFile" accept=".kmz" class="block w-full text-sm text-gray-500
    file:mr-4 file:py-2 file:px-4
    file:rounded-md file:border-0
@@ -93,36 +93,33 @@
      //   // æ›´å¤šç‚¹...
      // ],
      trajectoryData:[],
      c3Position:Cesium.Cartesian3.fromDegrees(125.14349012556997, 46.55684464616254,33.157873665937274),
      enableChange:true,
      trajectoryData:[],//数据数组
      c3Position:Cesium.Cartesian3.fromDegrees(125.04159166971326, 46.63755601510411,33.13806141449017),//初始化位置
      camera: null,      // Cesium Camera å®žä¾‹
      frustumOutline: null, // è§†é”¥ä½“
      heading: 0,        // ç›¸æœºçš„ heading (朝向)
      pitch: -90, // ç›¸æœºçš„ pitch (俯仰)
      heading: -90,        // ç›¸æœºçš„ heading (朝向)
      pitch: 0, // ç›¸æœºçš„ pitch (俯仰)
      roll: 0,           // ç›¸æœºçš„ roll (滚转)
      aspectRatio: 1.0,  // çºµæ¨ªæ¯”
      color: '#FF0000',  // è§†é”¥ä½“颜色
      id: 'frustumOutline',
      positionProperty : new Cesium.SampledPositionProperty(),
      orientation : new Cesium.SampledProperty(Cesium.Quaternion)
      orientation : new Cesium.SampledProperty(Cesium.Quaternion),//无人机朝向
      sztOrientation : new Cesium.SampledProperty(Cesium.Quaternion),//云台朝向
    }
  },
  mounted() {
    console.log(window.viewer)
    console.log(viewer)
    // this.showGZ()
  },
  methods: {
    async handleKMZUpload(event) {
      const file = event.target.files[0];
      if (!file) return;
      try {
        const arrayBuffer = await this.readFileAsArrayBuffer(file);
        this.waypoints = await this.parseKMZ(arrayBuffer);
        //加点逻辑 æ¯”如向第2-n-1中插入元素  ç»çº¬åº¦ä¸ºå½“前的  heading pitch roll ä¸‹ä¸€ä¸ªçš„
        const dealArr = [];
        console.log(">> this.waypoints:", this.waypoints);
        // console.log(">> this.waypoints:", this.waypoints);
        for (let i = 0; i < this.waypoints.length - 1; i++) {
            dealArr.push(this.waypoints[i]); // Push the current element
            const current = this.waypoints[i];
@@ -144,7 +141,7 @@
        dealArr.push(this.waypoints[this.waypoints.length - 1]);
        console.log(dealArr)
        // this.waypoints = dealArr
        this.waypoints = dealArr
        console.log(">> this.waypoints:", this.waypoints);
        this.fileInfo = `
@@ -157,9 +154,12 @@
        const speeds = [10, 10, 20, 30, 80, 100, 60, 50, 40, 30, 20, 10]; // è‡ªå®šä¹‰é€Ÿåº¦
        // const result = this.waypoints.map((item, index) => {
        console.log(result)
        const result = dealArr.map((item, index) => {
        // const result = this.waypoints.map((item, index) => {
          // æ¯æ¬¡åР䏀天
          const time = new Date(startDate.getTime() + index * 60 * 1000).toISOString();
          // const time = Cesium.JulianDate.addSeconds(startDate, 3600, new Cesium.JulianDate());
          return {
            time,
            position: {
@@ -171,11 +171,12 @@
            pitch:item.pitch|| 0,
            roll:item.roll || 0,
            speed: speeds[index] || 0,
            flyHeading:item.flyHeading
            // flyHeading:item.flyHeading
          };
        });
        this.trajectoryData = result
        console.log(result);
        console.log(this.trajectoryData)
        // console.log(result);
        this.loadTimeLine()
      } catch (error) {
        console.error(error);
@@ -199,7 +200,7 @@
      const kmlFile = Object.keys(zip.files).find(name =>
        name.endsWith(".kml")
      );
      console.log(">> kmlFile:", kmlFile);
      // console.log(">> kmlFile:", kmlFile);
      if (!kmlFile) throw new Error("未找到KML文件");
      const kmlContent = await zip.file(kmlFile).async("text");
@@ -238,35 +239,18 @@
                totalPitch += gimbalPitch;
                totalRoll += gimbalRoll;
                totalHeading += gimbalYaw;
                console.log('totalHeading里面循环' + totalHeading)
            }
            // å¤„理 rotateYaw å‡½æ•°
            if (action['wpml:actionActuatorFunc'].includes("rotateYaw")) {
                // èŽ·å– aircraftHeading
                const aircraftHeading = parseFloat(action['wpml:actionActuatorFuncParam'][0]["wpml:aircraftHeading"][0]);
                console.log(aircraftHeading)
                flyHeadingData = aircraftHeading
                // å°† aircraftHeading ç´¯åŠ åˆ° totalHeading
                totalHeading += aircraftHeading;
                console.log('totalHeading外层外层外层外层外层循环' + totalHeading)
            }
            // action['wpml:actionActuatorFuncParam'][0].['wpml:gimbalPitchRotateAngle']
            // let params = action["wpml:actionActuatorFuncParam"]?.[0];
            // if (!params) return;
            // // é£žæœºèˆªå‘è§’
            // if (action["wpml:actionActuatorFunc"]?.[0] === "rotateYaw" &&
            //     params["wpml:aircraftHeading"]) {
            //   heading = Number(params["wpml:aircraftHeading"][0]);
            // }
            // // äº‘台角度
            // if (action["wpml:actionActuatorFunc"]?.[0] === "gimbalRotate") {
            //   if (params["wpml:gimbalPitchRotateAngle"])
            //     gimbalPitch = Number(params["wpml:gimbalPitchRotateAngle"][0]);
            //   if (params["wpml:gimbalRollRotateAngle"])
            //     gimbalRoll = Number(params["wpml:gimbalRollRotateAngle"][0]);
            //   if (params["wpml:gimbalYawRotateAngle"])
            //     gimbalYaw = Number(params["wpml:gimbalYawRotateAngle"][0]);
            // }
          });
        }
        // è¾“出结果
@@ -276,7 +260,6 @@
        if (coords) {
          coords.trim().split(" ").forEach(coord => {
            const [lng, lat] = coord.split(",").map(Number);
            //,heading:gimbalYaw || 0,pitch:gimbalPitch|| 0,roll:gimbalRoll|| 0
            points.push({ lng, lat, alt: height,heading:totalHeading  || 0,pitch:totalPitch || 0,roll:totalRoll|| 0 ,flyHeading:flyHeadingData});
          });
        }
@@ -318,9 +301,6 @@
    showFlightPath() {
      console.log(">> this.waypoints:", this.waypoints);
      if (this.waypoints.length === 0) return alert('请先上传KMZ文件');
      // viewer.entities.removeAll();
      // è½¬æ¢åæ ‡
      const positions = this.waypoints.map(wp =>
        Cesium.Cartesian3.fromDegrees(wp.lng, wp.lat, wp.alt)
@@ -367,7 +347,6 @@
      //精度维度高度
      getScopeTower(lon,lat,distance).then(res=>{
        if(res.code == 200) {
          console.log(res.rows)
          res.rows.forEach((data) => {
            this.createModel(data);
          });
@@ -376,7 +355,6 @@
      })
    },
    createModel(data){
      console.log(data)
      viewer.entities.add({
        id: data.id,
        position: Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude, data.altitude),
@@ -404,7 +382,11 @@
    loadTimeLine(){
      // è®¾ç½®æ—¶é—´èŒƒå›´
      const allTimes = this.trajectoryData.map(item => Cesium.JulianDate.fromIso8601(item.time))
      console.log(allTimes)
      // èŽ·å–æ‰€æœ‰æ—¶é—´ï¼Œå¹¶å‡åŽ»8小时
      // const allTimes = this.trajectoryData.map(item => {
      //     const time = Cesium.JulianDate.fromIso8601(item.time)
      //     return Cesium.JulianDate.addHours(time, +8, new Cesium.JulianDate())
      // })
      const startTime = allTimes[0]
      const stopTime = allTimes[allTimes.length - 1]
      // const startTime = Cesium.JulianDate.minimum(...allTimes)
@@ -433,45 +415,38 @@
        speedProperty.addSample(time, data.speed)
            // è®¡ç®—朝向四元数
        // const hpr = new Cesium.HeadingPitchRoll(
        //     Cesium.Math.toRadians(data.heading),
        //     Cesium.Math.toRadians(data.flyHeading),
        //     Cesium.Math.toRadians(data.pitch),
        //     Cesium.Math.toRadians(data.roll)
        // );
        const hpr = new Cesium.HeadingPitchRoll(
            Cesium.Math.toRadians(data.flyHeading),
            Cesium.Math.toRadians(0),
            Cesium.Math.toRadians(0),
            Cesium.Math.toRadians(0)
        );
        const quaternion = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
        console.log(Cesium.JulianDate.toDate(time))
        // // æ·»åŠ é£žæœºæœå‘é‡‡æ ·
        this.orientation.addSample(time, quaternion);
        //云台
        const szthpr = new Cesium.HeadingPitchRoll(
            Cesium.Math.toRadians(data.heading),
            Cesium.Math.toRadians(data.pitch),
            Cesium.Math.toRadians(data.roll)
        );
        const sztquaternion = Cesium.Transforms.headingPitchRollQuaternion(position, szthpr);
        
        // // æ·»åŠ æœå‘é‡‡æ ·
        this.orientation.addSample(data.time, quaternion);
        this.sztOrientation.addSample(time, sztquaternion);
      })
      console.log(">> positionProperty:",this.positionProperty );
      // console.log(">> positionProperty:",this.positionProperty );
      let entity = viewer.entities.add({
        show: true,
        position: this.positionProperty,
        id:'lineFly',
        // label: {
        //   show: true,
        //   font: '18px å¾®è½¯é›…黑',
        //   style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        //   showBackground: false,
        //   backgroundColor: Cesium.Color.fromCssColorString('#123456').withAlpha(0.5),
        //   outlineColor: Cesium.Color.fromCssColorString('#00ff00'),
        //   outlineWidth: 3,
        //   fillColor: Cesium.Color.fromCssColorString("#000000"),
        //   horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        //   verticalOrigin: Cesium.VerticalOrigin.TOP,
        //   text: '测试文字',
        //   // pixelOffset: new Cesium.Cartesian2(0, -1 * 100),
        //   pixelOffset: new Cesium.Cartesian2(0, -90)
        // },
        model: {
          uri: "/Model/M300.glb", // æ³¨æ„entitits.add方式加载gltf文件时,这里是uri,不是url,并且这种方式只能加载.glb格式的文件
          scale:  10, // ç¼©æ”¾æ¯”例
          // minimumPixelSize: this.minSize, // æœ€å°åƒç´ å¤§å°ï¼Œå¯ä»¥é¿å…å¤ªå°çœ‹ä¸è§
          minimumPixelSize: 0, // æœ€å°åƒç´ å¤§å°ï¼Œå¯ä»¥é¿å…å¤ªå°çœ‹ä¸è§
          maximumScale: 20000, // æ¨¡åž‹çš„æœ€å¤§æ¯”例尺大小。minimumPixelSize的上限
          incrementallyLoadTextures: true, // åŠ è½½æ¨¡åž‹åŽçº¹ç†æ˜¯å¦å¯ä»¥ç»§ç»­æµå…¥
@@ -493,24 +468,12 @@
          leadTime: 60,
          trailTime: 60
        },
        // orientation: Cesium.Quaternion.fromHeadingPitchRoll(
        //     new Cesium.HeadingPitchRoll(
        //         Cesium.Math.toRadians(45), // èˆªå‘è§’(正北为0,顺时针增加)
        //         Cesium.Math.toRadians(45), // ä¿¯ä»°è§’
        //         Cesium.Math.toRadians(0)  // ç¿»æ»šè§’
        //     )
        // ),
        orientation:this.orientation
      })
    },
     // æ›´æ–°è§†é”¥ä½“位置和朝向
    updateFrustum() {
      console.log(this.c3Position)
      console.log(this.pitch)
      console.log(this.heading)
      console.log(this.roll)
      // æ›´æ–°è§†é”¥ä½“位置
      // this.c3Position = Cesium.Cartesian3.fromDegrees(114.169, 22.278, 500);  // åŠ¨æ€æ›´æ–°ä½ç½®
      if (this.frustumOutline) {
        // æ›´æ–°ç›¸æœºå’Œè§†é”¥ä½“位置
        this.camera.setView({
@@ -525,9 +488,6 @@
      } else {
        console.warn('Frustum outline is not initialized');
      }
      // æ›´æ–°è§†é”¥ä½“边框
      console.log(this.frustumOutline)
      // this.frustumOutline.update();
    },
    listenChange() {
      let viewer =  window.viewer;
@@ -535,21 +495,27 @@
        if (viewer.clock.shouldAnimate) {
          this.getCurrentPosition()
        // æ‰§è¡ŒåŠ¨ä½œ
        console.log("Clock is running, performing action...");
        // ä½ çš„代码逻辑
        // console.log("Clock is running, performing action...");
        // ä½ çš„代码逻
        } else {
            // æ—¶é’Ÿæš‚停时不执行
            console.log("Clock is paused, no action performed.");
            // console.log("Clock is paused, no action performed.");
        }
      });
    },
    getCurrentPosition() {
      // èŽ·å–å½“å‰æ—¶é—´
      const currentTime = viewer.clock.currentTime;
      console.log(Cesium.JulianDate.toDate(currentTime))
      console.log('start:', Cesium.JulianDate.toDate(this.positionProperty._property._times[0]));
      console.log('stop:', Cesium.JulianDate.toDate(this.positionProperty._property._times.slice(-1)[0]));
      // const currentTime = Cesium.JulianDate.addHours(currentTime, -8, new Cesium.JulianDate());
      
      console.log(currentTime)
      // ä»Ž positionProperty ä¸­èŽ·å–å½“å‰ä½ç½®
      const currentPosition = this.positionProperty.getValue(currentTime);
      console.log('currentPosition>>>>>>>>>>>>>>>>>>>>>>>' + currentPosition)
      // console.log('currentPosition>>>>>>>>>>>>>>>>>>>>>>>' + currentPosition)
      // å¦‚果获取到位置
      if (currentPosition) {
        // å°†ä½ç½®ä»Ž Cartesian è½¬æ¢ä¸º Cartographic
@@ -557,18 +523,32 @@
        // å°†ç»çº¬åº¦ä»Žå¼§åº¦è½¬æ¢ä¸ºåº¦
        this.currentLongitude = Cesium.Math.toDegrees(cartographicPosition.longitude);
        console.log(Cesium.Math.toDegrees(cartographicPosition.longitude))
        console.log(Cesium.Math.toDegrees(cartographicPosition.latitude))
        this.currentLatitude = Cesium.Math.toDegrees(cartographicPosition.latitude);
        this.currentHeight = cartographicPosition.height;
        this.c3Position = Cesium.Cartesian3.fromDegrees(this.currentLongitude, this.currentLatitude,this.currentHeight)
        // console.log(`当前位置 - ç»åº¦: ${this.currentLongitude}, çº¬åº¦: ${this.currentLatitude}, é«˜åº¦: ${this.currentHeight}`);
        //相机变化
        const quaternion = this.orientation.getValue(currentTime)
        if (quaternion) {
          const hpr = Cesium.HeadingPitchRoll.fromQuaternion(quaternion)
        // if(quaternion) {
        //   const hpr111 = Cesium.HeadingPitchRoll.fromQuaternion(quaternion)
        //   console.log('相机的   ' +Cesium.Math.toDegrees(hpr111.heading).toFixed(2))
        // }
        //云台变化
        const sztquaternion = this.sztOrientation.getValue(currentTime)
        console.log(sztquaternion)
        if (sztquaternion) {
          const hpr = Cesium.HeadingPitchRoll.fromQuaternion(sztquaternion)
          console.log(hpr)
          this.heading = Cesium.Math.toDegrees(hpr.heading).toFixed(2)
          // this.heading = 180
          this.pitch = Cesium.Math.toDegrees(hpr.pitch).toFixed(2)
          this.roll = Cesium.Math.toDegrees(hpr.roll).toFixed(2)
          console.log(`当前位置 - heading: ${hpr.heading}, pitch: ${hpr.pitch}, roll: ${hpr.roll}`);
          console.log('当前位置 - heading ' + this.heading)
          console.log(`当前位置 - heading: ${this.heading}, pitch: ${this.pitch}, roll: ${this.roll}`);
        }
        this.updateFrustum();
      } else {
        console.log("无法获取当前位置");
@@ -588,16 +568,12 @@
        up: Cesium.Cartesian3.UNIT_Z,
        destination: this.c3Position,
        orientation: {
          // heading: Cesium.Math.toRadians(this.heading),
          // pitch: Cesium.Math.toRadians(this.pitch),
          // roll: Cesium.Math.toRadians(this.roll),
          heading: Cesium.Math.toRadians(this.heading),
          pitch: Cesium.Math.toRadians(this.pitch),
          roll: Cesium.Math.toRadians(this.roll),
        },
      })
      this.frustumOutline = new Cesium.DebugCameraPrimitive({
        // id: this.id,
        id: 111111,
        camera: this.camera,
        frustumSplits: [0.01, 10000],
@@ -605,7 +581,6 @@
        // updateOnChange: false,
        updateOnChange: true,
      })
      console.log(this.frustumOutline)
      viewer.scene.primitives.add(this.frustumOutline);
      this.listenChange()
    }