فهرست منبع

修改台风可视化组件

WQQ 1 ماه پیش
والد
کامیت
c89ef77b44

+ 20 - 10
WebVue/TaiHufenglang/src/components/Cesium/CesiumViewer.vue

@@ -24,8 +24,9 @@
       @onPointSelected="handlePointSelected"
     />
 
-    <!-- 引入台风可视化组件 -->
+    <!-- 动态控制台风组件的创建和销毁,而不仅仅是visible属性 -->
     <TyphoonVisualization 
+      v-if="typhoonVisible"  
       :viewer="viewer" 
       :visible="typhoonVisible"
       @onToggle="handleTyphoonToggle"
@@ -34,13 +35,15 @@
 </template>
 
 <script setup>
-import { ref, onMounted, onUnmounted } from 'vue'
+import { ref, onMounted, onUnmounted, nextTick} from 'vue'
 import * as Cesium from 'cesium';
 import "cesium/Build/CesiumUnminified/Widgets/widgets.css";
 import JYLData from '@/assets/Data/THJYL.json'
 import POIVisualization from './POIVisualization.vue';
 import TyphoonVisualization from './TyphoonVisualization.vue';
 
+
+
 // 变量定义
 const TDTTK = "d9e7aa2ad204aba6aeedea6f5ab48ed9";
 let viewer = ref(null);
@@ -75,18 +78,25 @@ const togglePOIDisplay = () => {
   poiVisible.value = !poiVisible.value;
 };
 
-// 处理台风切换事件
-const handleToggleTyphoon = () => {
+// 处理台风切换事件 - 增加延迟确保清理完成
+const handleToggleTyphoon = async () => {
   const newState = !typhoonVisible.value;
-  typhoonVisible.value = newState;
   
-  if (newState && viewer.value) {
-    viewer.value.camera.flyTo({
-      destination: Cesium.Cartesian3.fromDegrees(120, 20, 4025692.0),
-    });
+  // 如果是从显示切换到隐藏,先等待一段时间再更新状态
+  if (!newState) {
+    typhoonVisible.value = newState;
+  } else {
+    // 如果是从隐藏切换到显示,先确保状态更新后再飞行到视角
+    typhoonVisible.value = newState;
+    // 等待组件重新创建
+    await nextTick();
+    if (viewer.value) {
+      viewer.value.camera.flyTo({
+        destination: Cesium.Cartesian3.fromDegrees(120, 20, 4025692.0),
+      });
+    }
   }
 };
-
 // 处理台风组件状态变化
 const handleTyphoonToggle = (newState) => {
   typhoonVisible.value = newState;

+ 241 - 152
WebVue/TaiHufenglang/src/components/Cesium/TyphoonVisualization.vue

@@ -61,7 +61,7 @@ const props = defineProps({
   },
   dataUrl: {
     type: String,
-    default: '../assets/Data/202508.json'
+    default: '/src/assets/Data/202508.json'
   }
 });
 
@@ -98,60 +98,61 @@ const getCesiumContainerRect = () => {
   return container ? container.getBoundingClientRect() : { left: 0, top: 0, width: 0, height: 0 };
 };
 
-// 更新台风弹窗位置
+// 修正updatePopupPosition函数
 const updatePopupPosition = (entity) => {
   if (popupUpdateCallback.value) {
-    props.viewer.scene.postRender.removeEventListener(popupUpdateCallback.value);
+    props.viewer?.scene.postRender.removeEventListener(popupUpdateCallback.value);
     popupUpdateCallback.value = null;
   }
 
-  if (!entity || !entity.position || !infoVisible.value) return;
+  if (!entity || !entity.position || !infoVisible.value || !props.viewer) return;
 
   popupUpdateCallback.value = () => {
-    const container = getCesiumContainer();
-    const containerRect = container.getBoundingClientRect();
-    const entityPosition = props.viewer.scene.cartesianToCanvasCoordinates(
-      entity.position.getValue(props.viewer.clock.currentTime)
-    );
-
-    if (entityPosition) {
-      nextTick(() => {
-        const popupEl = document.querySelector('.typhoon-popup .popup-content');
-        if (!popupEl) return;
-
-        // 获取弹窗尺寸和样式信息
-        const popupRect = popupEl.getBoundingClientRect();
-        const popupWidth = popupRect.width;
-        const popupHeight = popupRect.height;
-        const arrowHeight = 10; // 箭头高度
-
-        // 获取台风点大小
-        const pointSize = entity.point?.pixelSize || getDynamicPointSize();
-        const pointRadius = pointSize / 2;
-
-        // 计算弹窗水平位置
-        const offsetX = -15; // 向左微调
-        let x = (entityPosition.x - containerRect.left) - (popupWidth / 2) + offsetX;
-
-        // 计算弹窗垂直位置
-        let y, bottom = false;
-        const topSpacing = 35;
-        y = (entityPosition.y - containerRect.top) - popupHeight - arrowHeight - pointRadius - topSpacing;
-
-        // 如果上方空间不足,显示在点的下方
-        if (y < 0) {
-          y = (entityPosition.y - containerRect.top) + pointRadius + arrowHeight;
-          bottom = true;
-        }
+    try {
+      const container = getCesiumContainer();
+      if (!container) return;
+      
+      const containerRect = container.getBoundingClientRect();
+      const entityPosition = props.viewer.scene.cartesianToCanvasCoordinates(
+        entity.position.getValue(props.viewer.clock.currentTime)
+      );
 
-        // 边界检查
-        if (x < 0) x = 0;
-        if (x + popupWidth > containerRect.width) {
-          x = containerRect.width - popupWidth;
-        }
+      if (entityPosition) {
+        nextTick(() => {
+          const popupEl = document.querySelector('.typhoon-popup .popup-content');
+          if (!popupEl) return;
 
-        popupPosition.value = { x, y, bottom };
-      });
+          // 后续逻辑保持不变...
+          const popupRect = popupEl.getBoundingClientRect();
+          const popupWidth = popupRect.width;
+          const popupHeight = popupRect.height;
+          const arrowHeight = 10;
+
+          const pointSize = entity.point?.pixelSize || getDynamicPointSize();
+          const pointRadius = pointSize / 2;
+
+          const offsetX = -15;
+          let x = (entityPosition.x - containerRect.left) - (popupWidth / 2) + offsetX;
+
+          let y, bottom = false;
+          const topSpacing = 35;
+          y = (entityPosition.y - containerRect.top) - popupHeight - arrowHeight - pointRadius - topSpacing;
+
+          if (y < 0) {
+            y = (entityPosition.y - containerRect.top) + pointRadius + arrowHeight;
+            bottom = true;
+          }
+
+          if (x < 0) x = 0;
+          if (x + popupWidth > containerRect.width) {
+            x = containerRect.width - popupWidth;
+          }
+
+          popupPosition.value = { x, y, bottom };
+        });
+      }
+    } catch (error) {
+      console.error('更新弹窗位置时出错:', error);
     }
   };
 
@@ -202,26 +203,37 @@ const isPointInClickRange = (clickPosition, pointPosition, radius) => {
   return Math.sqrt(dx * dx + dy * dy) <= radius;
 };
 
-// 检查台风点点击
+// 修正checkPointClick函数
 const checkPointClick = (clickPosition) => {
-  if (!entityCollection.value || !entityCollection.value.entities) return null;
-
-  const radius = getDynamicPickRadius();
-  const pixelRatio = getPixelRatio();
-  const scaledClickPos = {
-    x: clickPosition.x * pixelRatio,
-    y: clickPosition.y * pixelRatio
-  };
+  if (!props.viewer || !entityCollection.value || !entityCollection.value.entities) return null;
+
+  try {
+    const radius = getDynamicPickRadius();
+    const pixelRatio = getPixelRatio();
+    const scaledClickPos = {
+      x: clickPosition.x * pixelRatio,
+      y: clickPosition.y * pixelRatio
+    };
 
-  const entities = entityCollection.value.entities.values;
-  for (let i = 0; i < entities.length; i++) {
-    const entity = entities[i];
-    if (entity.id && entity.id.startsWith('typhoon-point-') && entity.position) {
-      const pointPosition = props.viewer.scene.cartesianToCanvasCoordinates(entity.position._value);
-      if (pointPosition && isPointInClickRange(scaledClickPos, pointPosition, radius)) {
-        return entity;
+    const entities = entityCollection.value.entities.values;
+    for (let i = 0; i < entities.length; i++) {
+      const entity = entities[i];
+      // 确保实体及其属性都存在
+      if (!entity || !entity.id || !entity.position) continue;
+      
+      if (entity.id.startsWith('typhoon-point-')) {
+        // 安全获取位置值
+        const positionValue = entity.position._value || entity.position.getValue(props.viewer.clock.currentTime);
+        if (!positionValue) continue;
+        
+        const pointPosition = props.viewer.scene.cartesianToCanvasCoordinates(positionValue);
+        if (pointPosition && isPointInClickRange(scaledClickPos, pointPosition, radius)) {
+          return entity;
+        }
       }
     }
+  } catch (error) {
+    console.error('检查点击点时出错:', error);
   }
 
   return null;
@@ -742,67 +754,127 @@ const loadTyphoonData = () => {
 
 // 初始化台风可视化
 const initVisualization = () => {
-  // 初始化实体集合
+  // 确保viewer存在
+  if (!props.viewer) return;
+  
+  // 清除可能存在的旧实体集合
   if (entityCollection.value) {
     props.viewer.dataSources.remove(entityCollection.value);
+    entityCollection.value = null;
   }
+  
+  // 重新创建实体集合
   entityCollection.value = new Cesium.CustomDataSource("typhoonPoints");
   props.viewer.dataSources.add(entityCollection.value);
 
-  // 初始化警戒线
+  // 清除并重新初始化警戒线
+  relatedEntities.value.warnings.forEach(entity => {
+    if (entity && props.viewer && Cesium.defined(entity.id)) {
+      props.viewer.entities.remove(entity);
+    }
+  });
+  relatedEntities.value.warnings = [];
   initWarningLines();
 
-  // 加载台风数据
+  // 重新加载台风数据
   loadTyphoonData();
 };
 
-// 监听visible属性变化
-watch(() => props.visible, (newVal, oldVal) => {
-  if (newVal !== oldVal) {
-    // 更新所有实体的可见性
-    if (entityCollection.value) {
-      entityCollection.value.show = newVal;
-    }
-
-    fengquanLayers.value.forEach(entity => {
-      if (entity) {
-        entity.show = newVal;
+const cleanupEntities = () => {
+  if (!props.viewer) return;
+  
+  // 使用try-catch确保每个清理步骤都能执行
+  try {
+    // 清理路径实体
+    relatedEntities.value.paths.forEach(entity => {
+      if (entity && Cesium.defined(entity.id)) {
+        props.viewer.entities.remove(entity);
       }
     });
-
-    relatedEntities.value.paths.forEach(entity => {
-      if (entity) {
-        entity.show = newVal;
+    relatedEntities.value.paths = [];
+  } catch (error) {
+    console.error("清理路径实体时出错:", error);
+  }
+  
+  try {
+    // 清理警戒线实体
+    relatedEntities.value.warnings.forEach(entity => {
+      if (entity && Cesium.defined(entity.id)) {
+        props.viewer.entities.remove(entity);
       }
     });
-
+    relatedEntities.value.warnings = [];
+  } catch (error) {
+    console.error("清理警戒线实体时出错:", error);
+  }
+  
+  try {
+    // 清理预报实体
     relatedEntities.value.forecasts.forEach(entity => {
-      if (entity) {
-        entity.show = newVal;
+      if (entity && Cesium.defined(entity.id)) {
+        props.viewer.entities.remove(entity);
       }
     });
-
-    relatedEntities.value.warnings.forEach(entity => {
-      if (entity) {
-        entity.show = newVal;
+    relatedEntities.value.forecasts = [];
+  } catch (error) {
+    console.error("清理预报实体时出错:", error);
+  }
+  
+  try {
+    // 清理风圈实体
+    fengquanLayers.value.forEach(entity => {
+      if (entity && Cesium.defined(entity.id)) {
+        props.viewer.entities.remove(entity);
       }
     });
-
-    if (tbentity.value) {
-      tbentity.value.show = newVal;
+    fengquanLayers.value = [];
+  } catch (error) {
+    console.error("清理风圈实体时出错:", error);
+  }
+  
+  try {
+    // 清理台风眼实体
+    if (tbentity.value && Cesium.defined(tbentity.value.id)) {
+      props.viewer.entities.remove(tbentity.value);
+      tbentity.value = null;
+    }
+  } catch (error) {
+    console.error("清理台风眼实体时出错:", error);
+  }
+  
+  try {
+    // 清理实体集合
+    if (entityCollection.value) {
+      props.viewer.dataSources.remove(entityCollection.value);
+      entityCollection.value = null;
     }
+  } catch (error) {
+    console.error("清理实体集合时出错:", error);
+  }
+};
 
-    // 如果显示,则重新初始化
-    if (newVal) {
-      initVisualization();
-    } else {
-      // 如果隐藏,清除定时器和弹窗
+
+// 监听visible属性变化
+watch(() => props.visible, async (newVal, oldVal) => {
+  if (newVal !== oldVal) {
+    // 如果是从显示切换到隐藏,先清理所有实体
+    if (!newVal) {
+      cleanupEntities();
       hidePopup();
       if (typhoonInterval) {
         clearInterval(typhoonInterval);
         typhoonInterval = null;
       }
-      removeTFLayer();
+    } else {
+      // 如果是从隐藏切换到显示,等待viewer准备就绪后重新初始化
+      if (props.viewer) {
+        // 确保清理残留实体
+        cleanupEntities();
+        // 等待一帧确保清理完成
+        await nextTick();
+        // 重新初始化
+        initVisualization();
+      }
     }
   }
 });
@@ -815,77 +887,94 @@ const initEventListeners = () => {
   // 点击事件处理
   const handler = new Cesium.ScreenSpaceEventHandler(props.viewer.scene.canvas);
   
-  // 左键点击事件
+  // 左键点击事件 - 增加更严格的空值检查
   handler.setInputAction((click) => {
-    if (!props.visible) return;
+    if (!props.visible || !props.viewer) return;
     
-    const scaleRatio = getScaleRatio();
-    const correctedX = click.position.x / scaleRatio;
-    const correctedY = click.position.y / scaleRatio;
-
-    // 尝试标准拾取
-    const pickedObject = props.viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
-    // 尝试自定义范围拾取台风点
-    const typhoonPoint = pickedObject ? null : checkPointClick(click.position);
-    const finalPickedObject = pickedObject || typhoonPoint;
-
-    // 隐藏弹窗
-    hidePopup();
-
-    if (Cesium.defined(finalPickedObject) && Cesium.defined(finalPickedObject.id)) {
-      const entityId = finalPickedObject.id.id;
-      const data = pointDataMap.value.get(entityId);
-
-      if (data && entityId.startsWith('typhoon-point-')) {
-        // 处理台风点点击
-        currentEntity.value = finalPickedObject.id;
-        info.value = data;
-        infoVisible.value = true;
-        updatePopupPosition(finalPickedObject.id);
-        
-        props.viewer.flyTo(finalPickedObject.id, {
-          offset: new Cesium.HeadingPitchRange(0, -0.5, 1500)
-        });
+    try {
+      const scaleRatio = getScaleRatio();
+      const correctedX = click.position.x / scaleRatio;
+      const correctedY = click.position.y / scaleRatio;
+
+      // 尝试标准拾取
+      const pickedObject = props.viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
+      // 尝试自定义范围拾取台风点
+      const typhoonPoint = pickedObject ? null : checkPointClick(click.position);
+      const finalPickedObject = pickedObject || typhoonPoint;
+
+      // 隐藏弹窗
+      hidePopup();
+
+      // 严格检查所有可能为undefined的环节
+      if (Cesium.defined(finalPickedObject) && 
+          Cesium.defined(finalPickedObject.id) && 
+          typeof finalPickedObject.id === 'object' && 
+          'id' in finalPickedObject.id) {
+          
+        const entityId = finalPickedObject.id.id;
+        // 确保entityId是字符串且符合我们的命名规范
+        if (typeof entityId === 'string' && entityId.startsWith('typhoon-point-')) {
+          const data = pointDataMap.value.get(entityId);
+          if (data) {
+            currentEntity.value = finalPickedObject.id;
+            info.value = data;
+            infoVisible.value = true;
+            updatePopupPosition(finalPickedObject.id);
+            
+            props.viewer.flyTo(finalPickedObject.id, {
+              offset: new Cesium.HeadingPitchRange(0, -0.5, 1500)
+            });
+          }
+        }
+      } else {
+        currentEntity.value = null;
       }
-    } else {
+    } catch (error) {
+      console.error('处理点击事件时出错:', error);
       currentEntity.value = null;
+      hidePopup();
     }
   }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
 
-  // 鼠标移动事件
+  // 鼠标移动事件 - 增加异常捕获
   handler.setInputAction((movement) => {
-    if (!props.visible) {
+    if (!props.visible || !props.viewer) {
       hidePopup();
       return;
     }
 
-    const scaleRatio = getScaleRatio();
-    const correctedX = movement.endPosition.x / scaleRatio;
-    const correctedY = movement.endPosition.y / scaleRatio;
-
-    // 尝试标准拾取
-    const pickedObject = props.viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
-    // 尝试自定义范围拾取台风点
-    const typhoonPoint = pickedObject ? null : checkPointClick(movement.endPosition);
-    const finalPickedObject = pickedObject || typhoonPoint;
-
-    // 检查是否悬停在台风路径点上
-    if (Cesium.defined(finalPickedObject) && Cesium.defined(finalPickedObject.id)) {
-      const entityId = finalPickedObject.id.id;
-      if (entityId && entityId.startsWith('typhoon-point-')) {
-        const typhoonData = pointDataMap.value.get(entityId);
-        if (typhoonData) {
-          currentEntity.value = finalPickedObject.id;
-          info.value = typhoonData;
-          infoVisible.value = true;
-          updatePopupPosition(finalPickedObject.id);
-          return;
+    try {
+      const scaleRatio = getScaleRatio();
+      const correctedX = movement.endPosition.x / scaleRatio;
+      const correctedY = movement.endPosition.y / scaleRatio;
+
+      const pickedObject = props.viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
+      const typhoonPoint = pickedObject ? null : checkPointClick(movement.endPosition);
+      const finalPickedObject = pickedObject || typhoonPoint;
+
+      if (Cesium.defined(finalPickedObject) && 
+          Cesium.defined(finalPickedObject.id) && 
+          typeof finalPickedObject.id === 'object' && 
+          'id' in finalPickedObject.id) {
+          
+        const entityId = finalPickedObject.id.id;
+        if (typeof entityId === 'string' && entityId.startsWith('typhoon-point-')) {
+          const typhoonData = pointDataMap.value.get(entityId);
+          if (typhoonData) {
+            currentEntity.value = finalPickedObject.id;
+            info.value = typhoonData;
+            infoVisible.value = true;
+            updatePopupPosition(finalPickedObject.id);
+            return;
+          }
         }
       }
-    }
 
-    // 不是台风路径点,隐藏弹窗
-    hidePopup();
+      hidePopup();
+    } catch (error) {
+      console.error('处理鼠标移动事件时出错:', error);
+      hidePopup();
+    }
   }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
 
   return handler;

+ 64 - 45
WebVue/TaiHufenglang/src/components/TyphoonViewer.vue

@@ -678,66 +678,85 @@ const initTyphoonVisualization = () => {
   initPoints();
 };
 
+// 在initPoints函数中修改测试数据部分
 const initPoints = () => {
   const jsonUrl = new URL('../assets/Data/202508.json', import.meta.url).href;
 
   axios.get(jsonUrl)
     .then(response => {
       const typhoonData = response.data;
-      processPoints(typhoonData.points, typhoonData);
-      updateTyphoonPointSizes();
+      // 验证数据结构是否正确
+      if (typhoonData && typhoonData.points && Array.isArray(typhoonData.points)) {
+        processPoints(typhoonData.points, typhoonData);
+        updateTyphoonPointSizes();
+      } else {
+        console.error('台风数据结构不正确,使用测试数据');
+        useTestData();
+      }
     })
     .catch(error => {
       console.error('加载台风JSON数据失败,使用测试数据', error);
-      const testTyphoonData = {
-        "name": "竹节草",
-        "enname": "CO-MAY",
-        "points": [
+      useTestData();
+    });
+};
+
+// 新增一个专门的函数来提供测试数据
+const useTestData = () => {
+  const testTyphoonData = {
+    "name": "竹节草",
+    "enname": "CO-MAY",
+    "points": [
+      {
+        "time": "2025-07-23 14:00:00",
+        "lng": "119.20",
+        "lat": "18.40",
+        "strong": "热带低压",
+        "power": "7",
+        "speed": "15",
+        "pressure": "998",
+        "movespeed": "14",
+        "movedirection": "南西",
+        "radius7": "180|120|200|150",
+        "radius10": "",
+        "radius12": "",
+        "forecast": [
           {
-            "time": "2025-07-23 14:00:00",
-            "lng": "119.20",
-            "lat": "18.40",
-            "strong": "热带低压",
-            "power": "7",
-            "speed": "15",
-            "pressure": "998",
-            "movespeed": "14",
-            "movedirection": "南西",
-            "radius7": "180|120|200|150",
-            "radius10": "",
-            "radius12": "",
-            "forecast": [
-              {
-                "tm": "中国",
-                "forecastpoints": [
-                  { "lng": 121, "lat": 19 },
-                  { "lng": 122, "lat": 18 }
-                ]
-              }
+            "tm": "中国",
+            "forecastpoints": [
+              { "lng": 121, "lat": 19 },
+              { "lng": 122, "lat": 18 }
             ]
-          },
-          {
-            "time": "2025-07-23 17:00:00",
-            "lng": "119.10",
-            "lat": "18.00",
-            "strong": "热带低压",
-            "power": "7",
-            "speed": "15",
-            "pressure": "996",
-            "movespeed": "13",
-            "movedirection": "南南西",
-            "radius7": "180|120|200|150",
-            "radius10": "",
-            "radius12": "",
-            "forecast": []
           }
         ]
-      };
-      processPoints(testTyphoonData.points, testTyphoonData);
-      updateTyphoonPointSizes();
-    });
+      },
+      {
+        "time": "2025-07-23 17:00:00",
+        "lng": "119.10",
+        "lat": "18.00",
+        "strong": "热带低压",
+        "power": "7",
+        "speed": "15",
+        "pressure": "996",
+        "movespeed": "13",
+        "movedirection": "南南西",
+        "radius7": "180|120|200|150",
+        "radius10": "",
+        "radius12": "",
+        "forecast": []
+      }
+    ]
+  };
+  
+  // 确保points是数组
+  if (!testTyphoonData.points || !Array.isArray(testTyphoonData.points)) {
+    testTyphoonData.points = [];
+  }
+  
+  processPoints(testTyphoonData.points, testTyphoonData);
+  updateTyphoonPointSizes();
 };
 
+
 // 修改processPoints函数,确保路径正确创建
 const processPoints = (points, typhoonData) => {
   const lineArr = [];