Explorar o código

优化台风路径,地图添加地名搜索定位的功能

WQQ hai 3 semanas
pai
achega
1186d0584f

+ 218 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/search/search.vue

@@ -0,0 +1,218 @@
+<template>
+  <div class="search-container">
+    <div class="search-box">
+      <input
+        v-model="searchText"
+        type="text"
+        placeholder="搜索地名,例如:北京"
+        @keyup.enter="search"
+        @input="handleInput"
+      />
+      <button @click="search">搜索</button>
+    </div>
+    <div class="search-results" v-if="showResults && searchResults.length > 0">
+      <div
+        v-for="(result, index) in searchResults"
+        :key="index"
+        class="search-result-item"
+        @click="selectResult(result)"
+      >
+        {{ result.name }}
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onUnmounted } from 'vue';
+
+const props = defineProps({
+  viewer: {
+    type: Object,
+    required: true
+  }
+});
+
+const searchText = ref('');
+const showResults = ref(false);
+const searchResults = ref([]);
+const isSearching = ref(false);
+
+// 天地图API密钥
+const tiandituKey = '3fb1e9fda20ee995dc815c8243553ce8';
+
+// 处理输入
+const handleInput = async () => {
+  if (searchText.value.trim()) {
+    try {
+      isSearching.value = true;
+      // 调用天地图API获取搜索结果
+      const results = await getLocationByTianditu(searchText.value.trim());
+      searchResults.value = results;
+      showResults.value = true;
+    } catch (error) {
+      console.error('搜索失败:', error);
+      searchResults.value = [];
+      showResults.value = false;
+    } finally {
+      isSearching.value = false;
+    }
+  } else {
+    searchResults.value = [];
+    showResults.value = false;
+  }
+};
+
+// 搜索
+const search = async () => {
+  if (searchText.value.trim()) {
+    try {
+      // 调用天地图API获取位置信息
+      const location = await getLonLatByTianditu(searchText.value.trim());
+      if (location) {
+        flyToLocation(location);
+        showResults.value = false;
+      }
+    } catch (error) {
+      console.error('搜索失败:', error);
+      alert('搜索失败: ' + error.message);
+    }
+  }
+};
+
+// 选择搜索结果
+const selectResult = (result) => {
+  searchText.value = result.name;
+  flyToLocation(result);
+  showResults.value = false;
+};
+
+// 天地图地理编码 API
+const getLonLatByTianditu = async (keyword) => {
+  const url = `https://api.tianditu.gov.cn/geocoder?ds={"keyWord":"${encodeURIComponent(keyword)}"}&tk=${tiandituKey}`;
+
+  const res = await fetch(url);
+  const data = await res.json();
+
+  // 天地图返回成功
+  if (data.status === "0" && data.location) {
+    const lon = parseFloat(data.location.lon);
+    const lat = parseFloat(data.location.lat);
+    return { name: keyword, lng: lon, lat: lat, height: 3000 };
+  } else {
+    throw new Error("未找到该地点");
+  }
+};
+
+// 天地图搜索 API
+const getLocationByTianditu = async (keyword) => {
+  // 由于天地图的搜索API可能需要不同的接口,这里使用地理编码API作为示例
+  // 实际项目中可以根据天地图API文档使用专门的搜索接口
+  try {
+    const location = await getLonLatByTianditu(keyword);
+    return [location];
+  } catch (error) {
+    return [];
+  }
+};
+
+// 飞行到指定位置
+const flyToLocation = (location) => {
+  if (props.viewer) {
+    // 设置一个更高的高度,确保能够看到周围区域
+    const height = 200000; // 100公里高度
+    
+    props.viewer.camera.flyTo({
+      destination: Cesium.Cartesian3.fromDegrees(
+        location.lng,
+        location.lat,
+        height
+      ),
+      orientation: {
+        heading: Cesium.Math.toRadians(0),
+        pitch: Cesium.Math.toRadians(-90), // 俯仰角-45度
+        roll: 0.0
+      },
+      duration: 1.8 // 飞行时间
+    });
+  }
+};
+
+// 点击外部关闭搜索结果
+const handleClickOutside = (event) => {
+  const searchContainer = document.querySelector('.search-container');
+  if (searchContainer && !searchContainer.contains(event.target)) {
+    showResults.value = false;
+  }
+};
+
+// 监听点击事件
+window.addEventListener('click', handleClickOutside);
+
+// 组件销毁时移除事件监听
+onUnmounted(() => {
+  window.removeEventListener('click', handleClickOutside);
+});
+</script>
+
+<style scoped>
+.search-container {
+  position: absolute;
+  top: 20px;
+  left: 20px;
+  z-index: 1000;
+  width: 300px;
+}
+
+.search-box {
+  display: flex;
+  background: white;
+  border-radius: 4px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+}
+
+.search-box input {
+  flex: 1;
+  padding: 10px 15px;
+  border: none;
+  outline: none;
+  font-size: 14px;
+}
+
+.search-box button {
+  padding: 0 20px;
+  background: #409eff;
+  color: white;
+  border: none;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.search-box button:hover {
+  background: #66b1ff;
+}
+
+.search-results {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  background: white;
+  border-radius: 0 0 4px 4px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  max-height: 200px;
+  overflow-y: auto;
+  margin-top: 2px;
+}
+
+.search-result-item {
+  padding: 10px 15px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.search-result-item:hover {
+  background: #f5f7fa;
+}
+</style>

+ 156 - 28
RuoYi-Vue3/src/supermap-cesium-module/components/typhoon-visualization/typhoon-visualization.vue

@@ -6,6 +6,13 @@
     <li><span class="dot yellow"></span><span>强热带风暴</span></li>
     <li><span class="dot orange"></span><span>台风</span></li>
     <li><span class="dot red"></span><span>超强台风</span></li>
+    <li style="margin:6px 0;color:#ccc;font-size:12px;">── 叠加图层 ──</li>
+    <li @click="toggleCurrent" style="cursor:pointer;display:flex;align-items:center;">
+      <span class="dot" style="background:#0ff;"></span> 洋流 {{ showCurrent ? '✅' : '⏹' }}
+    </li>
+    <li @click="toggleTemp" style="cursor:pointer;display:flex;align-items:center;">
+      <span class="dot" style="background:#f80;"></span> 海温 {{ showTemp ? '✅' : '⏹' }}
+    </li>
   </ul>
 
   <!-- 信息弹窗 -->
@@ -48,6 +55,12 @@ const popupUpdateCallback = ref(null);
 let lastHoverEntity = null;
 let handler = null;
 
+// 洋流 + 温度
+const showCurrent = ref(false);
+const showTemp = ref(false);
+const currentEntities = ref([]);
+const tempEntities = ref([]);
+
 const getViewer = () => props.viewer || window.viewer;
 
 // 安全清理
@@ -81,8 +94,12 @@ const updatePopupPosition = (entity) => {
       if (!cont) return;
       const pos = v.scene.cartesianToCanvasCoordinates(entity.position.getValue(v.clock.currentTime));
       if (!pos) return;
+      
       nextTick(() => {
-        popupPosition.value = { x: pos.x + 10, y: pos.y + 10 };
+        popupPosition.value = { 
+          x: pos.x + 10, 
+          y: pos.y + 10 
+        };
       });
     } catch {}
   };
@@ -95,7 +112,7 @@ const getColorByStrength = (s) => {
     case "热带低压":return Cesium.Color.GREEN;
     case "热带风暴":return Cesium.Color.BLUE;
     case "强热带风暴":return Cesium.Color.YELLOW;
-    case "台风":return Cesium.Color.fromCssColorString("#FBC712");
+    case "台风":return Cesium.Color.fromCssColor("#FBC712");
     case "强台风":return Cesium.Color.PLUM;
     case "超强台风":return Cesium.Color.RED;
     default:return Cesium.Color.RED;
@@ -126,10 +143,8 @@ const getWindPoints = (c, rs) => {
       const x=c[0]+r*Math.sin(rad);
       const y=c[1]+r*Math.cos(rad);
       p.push(x,y);
-      if(i==0&&j==0){fx=x;fy=y;}
     }
   });
-  if(fx!==undefined)p.push(fx,fy);
   return p;
 };
 
@@ -153,15 +168,14 @@ const startAnim = (points, data)=>{
   typhoonInterval=setInterval(()=>{
     if(!props.visible||i>=points.length){clearInterval(typhoonInterval);return;}
     const p=points[i];
-    const k=i*2;
     const c={lng:Number(p.lng),lat:Number(p.lat)};
-    const rs=[Math.max(0,350-k),Math.max(0,450-k),Math.max(0,400-k),Math.max(0,350-k)];
+    const rs=[350,450,400,350];
     drawWind(c,rs);
     i++;
   },250);
 };
 
-// 绘制点(无描边、不埋地)
+// 绘制点
 const processPoints = (points, data)=>{
   const v=getViewer();if(!v||!points.length)return;
   entityCollection.value.entities.removeAll();
@@ -178,10 +192,9 @@ const processPoints = (points, data)=>{
       point: {
         pixelSize:10,
         color:getColorByStrength(p.strong),
-        // 无白边
         outlineWidth: 0,
-        disableDepthTestDistance: Number.POSITIVE_INFINITY,
-        heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
+        disableDepthTestDistance:Number.POSITIVE_INFINITY,
+        heightReference:Cesium.HeightReference.CLAMP_TO_GROUND
       }
     });
     pointDataMap.value.set(id,{...p,name:data.name,enname:data.enname});
@@ -194,6 +207,115 @@ const processPoints = (points, data)=>{
   startAnim(points,data);
 };
 
+// ==============================================
+// 箭头洋流(完全保留你现在的效果)
+// ==============================================
+const addOceanCurrent = () => {
+  const v = getViewer();
+  if (!v) return;
+
+  removeOceanCurrent();
+  const step = 1.2;
+
+  for (let lng = 110; lng <= 135; lng += step) {
+    for (let lat = 15; lat <= 35; lat += step) {
+      const angle = (lng * 3 + lat * 2) % 360;
+      const rad = angle * Math.PI / 180;
+      const len = 0.4;
+
+      const x1 = lng;
+      const y1 = lat;
+      const x2 = lng + len * Math.sin(rad);
+      const y2 = lat + len * Math.cos(rad);
+
+      const arrowLine = v.entities.add({
+        polyline: {
+          positions: Cesium.Cartesian3.fromDegreesArray([x1, y1, x2, y2]),
+          material: Cesium.Color.CYAN.withAlpha(0.8),
+          width: 2,
+          clampToGround: true,
+        }
+      });
+      currentEntities.value.push(arrowLine);
+
+      const headSize = 0.08;
+      const dirX = Math.sin(rad);
+      const dirY = Math.cos(rad);
+
+      const p1 = [x2, y2];
+      const p2 = [x2 - dirX * headSize - dirY * headSize, y2 - dirY * headSize + dirX * headSize];
+      const p3 = [x2 - dirX * headSize + dirY * headSize, y2 - dirY * headSize - dirX * headSize];
+
+      const arrowHead = v.entities.add({
+        polygon: {
+          hierarchy: Cesium.Cartesian3.fromDegreesArray([...p1, ...p2, ...p3]),
+          material: Cesium.Color.CYAN.withAlpha(0.9),
+          clampToGround: true,
+        }
+      });
+      currentEntities.value.push(arrowHead);
+    }
+  }
+};
+
+const removeOceanCurrent = () => {
+  const v = getViewer();
+  if (!v) return;
+  currentEntities.value.forEach(e => {
+    try { v.entities.remove(e); } catch {}
+  });
+  currentEntities.value = [];
+};
+
+const toggleCurrent = () => {
+  showCurrent.value = !showCurrent.value;
+  showCurrent.value ? addOceanCurrent() : removeOceanCurrent();
+};
+
+// ==============================================
+// 【极简测试版温度场】极少实体 · 秒加载 · 秒卸载
+// ==============================================
+const addTemperature = () => {
+  const v = getViewer();
+  if (!v) return;
+  removeTemperature();
+
+  // 超大网格,极少实体
+  const step = 2.0;
+  for (let lng = 110; lng <= 135; lng += step) {
+    for (let lat = 15; lat <= 35; lat += step) {
+      const temp = 22 + (30 - lat) * 0.4;
+      let color;
+      if (temp < 24) color = Cesium.Color.BLUE.withAlpha(0.2);
+      else if (temp < 27) color = Cesium.Color.ORANGE.withAlpha(0.2);
+      else color = Cesium.Color.RED.withAlpha(0.2);
+
+      const rect = v.entities.add({
+        rectangle: {
+          coordinates: Cesium.Rectangle.fromDegrees(lng, lat, lng + step, lat + step),
+          material: color,
+          clampToGround: true
+        }
+      });
+      tempEntities.value.push(rect);
+    }
+  }
+};
+
+const removeTemperature = () => {
+  const v = getViewer();
+  if (!v) return;
+  tempEntities.value.forEach(e => {
+    try { v.entities.remove(e); } catch {}
+  });
+  tempEntities.value = [];
+};
+
+const toggleTemp = () => {
+  showTemp.value = !showTemp.value;
+  showTemp.value ? addTemperature() : removeTemperature();
+};
+
 // 加载
 const load = async ()=>{
   try{
@@ -202,27 +324,29 @@ const load = async ()=>{
   }catch(e){console.error(e);}
 };
 
-// 只保留悬浮,去掉点击飞行
+// 鼠标悬浮
 const initEvents = ()=>{
   const v=getViewer();if(!v)return;
   handler=new Cesium.ScreenSpaceEventHandler(v.scene.canvas);
 
-  // 悬浮显示信息
   handler.setInputAction((e)=>{
-    const pick=v.scene.pick(e.endPosition);
-    if(!pick?.id||!pick.id.id.startsWith('tp-')){
+    const pick = v.scene.pick(e.endPosition);
+    if (!pick || !pick.id || !pick.id.id.startsWith('tp-')) {
       hidePopup();
       return;
     }
-    const ent=pick.id;
-    if(lastHoverEntity===ent)return;
+
+    const ent = pick.id;
+    if (lastHoverEntity === ent) return;
+
     hidePopup();
-    lastHoverEntity=ent;
-    ent.point.pixelSize=14;
-    const d=pointDataMap.value.get(ent.id);
-    if(d){
-      info.value=d;
-      infoVisible.value=true;
+    lastHoverEntity = ent;
+    ent.point.pixelSize = 14;
+
+    const d = pointDataMap.value.get(ent.id);
+    if (d) {
+      info.value = d;
+      infoVisible.value = true;
       updatePopupPosition(ent);
     }
   },Cesium.ScreenSpaceEventType.MOUSE_MOVE);
@@ -249,6 +373,10 @@ const cleanup = ()=>{
   relatedEntities.value.paths.forEach(e=>v.entities.remove(e));
   relatedEntities.value.warnings.forEach(e=>v.entities.remove(e));
   fengquanLayers.value.forEach(e=>v.entities.remove(e));
+  
+  removeOceanCurrent();
+  removeTemperature();
+  
   entityCollection.value=null;
   relatedEntities.value={paths:[],warnings:[]};
   fengquanLayers.value=[];
@@ -273,9 +401,9 @@ onUnmounted(cleanup);
 </script>
 
 <style scoped>
-.typhoon-legend{position:absolute;top:20px;right:20px;background:#00000099;padding:12px;border-radius:6px;color:white;z-index:1000;list-style:none;margin:0;}
-.typhoon-legend li{display:flex;align-items:center;margin:4px 0;}
-.typhoon-legend .dot{width:12px;height:12px;border-radius:50%;margin-right:8px;}
+.typhoon-legend{position:absolute;bottom:20px;right:20px;background:#00000099;padding:12px;border-radius:6px;color:white;z-index:1000;list-style:none;margin:0;min-width:160px;}
+.typhoon-legend li{display:flex;align-items:center;margin:6px 0;font-size:13px;}
+.typhoon-legend .dot{width:12px;height:12px;border-radius:50%;margin-right:8px;flex-shrink:0;}
 .typhoon-legend .green{background:green;}
 .typhoon-legend .blue{background:blue;}
 .typhoon-legend .yellow{background:yellow;}
@@ -283,9 +411,9 @@ onUnmounted(cleanup);
 .typhoon-legend .red{background:red;}
 
 .typhoon-popup{
-  position:absolute;z-index:1001;background:white;padding:10px;border-radius:6px;
-  box-shadow:0 0 10px #00000033;font-size:12px;min-width:260px;pointer-events:none;
+  position:fixed;z-index:1001;background:white;padding:10px 12px;border-radius:8px;
+  box-shadow:0 0 12px rgba(0,0,0,0.2);font-size:12px;min-width:260px;pointer-events:none;
 }
 .typhoon-popup h3{margin:0 0 6px 0;font-size:14px;}
-.typhoon-popup p{margin:2px 0;}
+.typhoon-popup p{margin:3px 0;line-height:1.4;}
 </style>

+ 63 - 4
RuoYi-Vue3/src/supermap-cesium-module/components/viewer/viewer.js

@@ -88,15 +88,74 @@ function initViewer(props, callback) {
   if (!window.tooltip) {
     window.tooltip = createTooltip(viewer._element);
   }
+  
+  // 设置默认视角
+  viewer.camera.setView({
+    destination: Cesium.Cartesian3.fromDegrees(119.290381, 26.10377, 50000), // 指定位置,高度50公里
+    orientation: {
+      heading: Cesium.Math.toRadians(0), // 方向角
+      pitch: Cesium.Math.toRadians(0), // 俯仰角
+      roll: 0 // 翻滚角
+    }
+  });
+
+  // 添加天地图影像底图和注记
+  const tiandituKey = '3fb1e9fda20ee995dc815c8243553ce8';
+  
+  // 添加天地图影像底图(使用HTTPS)
+  const imageryProvider = new Cesium.WebMapTileServiceImageryProvider({
+    url: `https://t0.tianditu.gov.cn/img_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${tiandituKey}`,
+    layer: 'img',
+    style: 'default',
+    format: 'image/jpeg',
+    tileMatrixSetID: 'c',
+    subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
+    tilingScheme: new Cesium.GeographicTilingScheme(),
+    tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
+    maximumLevel: 15,
+    show: true
+  });
+  
+  // 添加天地图影像注记(使用HTTPS)
+  const labelProvider = new Cesium.WebMapTileServiceImageryProvider({
+    url: `https://t0.tianditu.gov.cn/cia_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${tiandituKey}`,
+    layer: 'cia',
+    style: 'default',
+    format: 'image/png',
+    tileMatrixSetID: 'c',
+    subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
+    tilingScheme: new Cesium.GeographicTilingScheme(),
+    tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
+    maximumLevel: 15,
+    show: true
+  });
+  
+  // 添加图层到viewer
+  viewer.imageryLayers.addImageryProvider(imageryProvider);
+  viewer.imageryLayers.addImageryProvider(labelProvider);
 
   function openingAnimation() {
-    viewer.camera.setView({
-      destination: Cesium.Cartesian3.fromDegrees(117.5, 26.0, 500000),
+    const lon = 118.247854;
+    const lat = 26.107280;
+    const height = 300000;
+    const pitch = Cesium.Math.toRadians(-50);
+    
+    // 计算相机位置,使得在俯仰角为-50度时,指定位置在视图中心
+    // 相机应该在指定位置的南方(假设heading为0)
+    // 水平距离 = 高度 * tan(俯仰角)
+    const horizontalDistance = height * Math.tan(-pitch);
+    // 纬度偏移 = 水平距离 / 地球半径 * 180 / π
+    const latOffset = horizontalDistance / 6371000 * 180 / Math.PI;
+    const cameraLat = lat - latOffset;
+    
+    viewer.camera.flyTo({
+      destination: Cesium.Cartesian3.fromDegrees(lon, cameraLat, height),
       orientation: {
         heading: 0.0,
-        pitch: Cesium.Math.toRadians(-45),
+        pitch: pitch,
         roll: 0.0
-      }
+      },
+      duration: 0 // 立即完成,不使用动画
     });
   }
 

+ 11 - 3
RuoYi-Vue3/src/supermap-cesium-module/components/viewer/viewer.vue

@@ -1,5 +1,6 @@
 <template>
   <div id="cesiumContainer" ref="viewer">
+    <SearchComponent v-if="viewerInstance" :viewer="viewerInstance" />
     <slot></slot>
   </div>
 </template>
@@ -7,10 +8,14 @@
 <script>
 import { onMounted, ref, provide } from "vue";
 import initViewer from "./viewer.js";
+import SearchComponent from "../search/search.vue";
 // import {initViewer} from "../../../dist/vue-webgl2.min";
 
 export default {
   name: "Sm3dViewer",
+  components: {
+    SearchComponent
+  },
   props: {
     sceneUrl: {
       //场景接口
@@ -31,16 +36,19 @@ export default {
   },
   setup(props) {
     const viewerRef = ref(null);
+    const viewerInstance = ref(null);
     
     onMounted(() => {
       // 初始化viewer并获取实例
-      const viewerInstance = initViewer(props);
+      const instance = initViewer(props);
+      viewerInstance.value = instance;
       // 通过provide提供viewer实例给子组件
-      provide('viewer', viewerInstance);
+      provide('viewer', instance);
     });
     
     return {
-      viewerRef
+      viewerRef,
+      viewerInstance
     };
   }
 };

+ 117 - 7
RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue

@@ -175,8 +175,24 @@
             <img src="/img/typhoon.png" alt="" class="typhoon-icon" />
             <span>台风</span>
           </template>
-          <el-menu-item index="typhoon-202508" @click="loadTyphoon('202508')">
-            <span>竹节草</span>
+          <el-menu-item index="typhoon-202508" class="model-menu-item" @click="!isTyphoonLoaded('202508') && loadTyphoon('202508')">
+            <span class="typhoon-name" :class="{ 'typhoon-loaded': isTyphoonLoaded('202508') }">竹节草</span>
+            <el-popconfirm
+              v-if="isTyphoonLoaded('202508')"
+              title="确定卸载吗?"
+              confirmButtonText="确认"
+              cancelButtonText="取消"
+              @confirm="removeTyphoon"
+            >
+              <template #reference>
+                <el-icon 
+                  class="delete-typhoon-icon" 
+                  title="卸载台风"
+                >
+                  <CircleClose />
+                </el-icon>
+              </template>
+            </el-popconfirm>
           </el-menu-item>
         </el-sub-menu>
       </el-menu>
@@ -277,6 +293,7 @@ export default {
       lastSaveTime: 0,  //上次保存时间戳
       showTyphoon: false,  //是否显示台风可视化
       typhoonDataUrl: '',  //台风数据URL
+      loadedTyphoonId: null,  //已加载的台风ID
     };
   },
 
@@ -650,6 +667,18 @@ export default {
       return this.loadedModelEntities.some(entity => entity.name === modelId)
     },
 
+    isTyphoonLoaded(typhoonId) {
+      return this.loadedTyphoonId === typhoonId
+    },
+
+    removeTyphoon() {
+      console.log('卸载台风')
+      this.showTyphoon = false
+      this.typhoonDataUrl = ''
+      this.loadedTyphoonId = null
+      ElMessage.success('台风路径已卸载')
+    },
+
     removeModel(model) {
       const index = this.loadedModelEntities.findIndex(entity => entity.name === model.id)
       if (index !== -1) {
@@ -670,14 +699,15 @@ export default {
         return
       }
 
-      // 使用本地静态文件路径 - 通过 import.meta.url 构建正确路径
-      const typhoonJsonUrl = new URL('/src/assets/Data/202508.json', import.meta.url).href
+      // 使用API地址获取台风数据
+      const typhoonJsonUrl = `https://typhoon.slt.zj.gov.cn/Api/TyphoonInfo/${typhoonId}`
       
       console.log('台风数据URL:', typhoonJsonUrl)
       
       // 显示台风可视化组件
       this.showTyphoon = true
       this.typhoonDataUrl = typhoonJsonUrl
+      this.loadedTyphoonId = typhoonId
       
       ElMessage.success('台风路径加载成功')
     },
@@ -751,6 +781,18 @@ export default {
       let imageryLayerCollection = viewer.scene.globe._imageryLayerCollection;
       let layer = imageryLayerCollection.get(0);
       let imageryProvider;
+      
+      // 保存注记图层(如果存在)
+      let labelLayer = null;
+      for (let i = 0; i < imageryLayerCollection.length; i++) {
+        const currentLayer = imageryLayerCollection.get(i);
+        if (currentLayer && currentLayer.imageryProvider && currentLayer.imageryProvider.url && currentLayer.imageryProvider.url.includes('cia_c')) {
+          labelLayer = currentLayer;
+          imageryLayerCollection.remove(currentLayer);
+          break;
+        }
+      }
+      
       if (imageryLayerCollection.get(2)) {
         imageryLayerCollection.remove(imageryLayerCollection.get(2));
       }
@@ -766,9 +808,18 @@ export default {
           });
           break;
         case "TIANDITU":
-          imageryProvider = new Cesium.TiandituImageryProvider({
-            url: url,
-            token: "3fb1e9fda20ee995dc815c8243553ce8"
+          // 使用天地图影像底图(使用HTTPS)
+          imageryProvider = new Cesium.WebMapTileServiceImageryProvider({
+            url: `https://t0.tianditu.gov.cn/img_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=3fb1e9fda20ee995dc815c8243553ce8`,
+            layer: 'img',
+            style: 'default',
+            format: 'image/jpeg',
+            tileMatrixSetID: 'c',
+            subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
+            tilingScheme: new Cesium.GeographicTilingScheme(),
+            tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
+            maximumLevel: 15,
+            show: true
           });
           break;
         case "IMAGE":
@@ -808,6 +859,48 @@ export default {
       if (type != "GRIDIMAGERY") {
         imageryLayerCollection.addImageryProvider(imageryProvider, 0);
         imageryLayerCollection.remove(layer);
+        
+        // 重新添加注记图层
+        if (type === "TIANDITU") {
+          // 当天地图作为底图时,总是添加影像注记图层
+          const tiandituKey = '3fb1e9fda20ee995dc815c8243553ce8';
+          labelLayer = imageryLayerCollection.addImageryProvider(
+            new Cesium.WebMapTileServiceImageryProvider({
+              url: `https://t0.tianditu.gov.cn/cia_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${tiandituKey}`,
+              layer: 'cia',
+              style: 'default',
+              format: 'image/png',
+              tileMatrixSetID: 'c',
+              subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
+              tilingScheme: new Cesium.GeographicTilingScheme(),
+              tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
+              maximumLevel: 15,
+              show: true
+            })
+          );
+          console.log('为天地图添加影像注记图层');
+        } else if (!labelLayer) {
+          // 其他底图时,如果没有注记图层,则添加
+          const tiandituKey = '3fb1e9fda20ee995dc815c8243553ce8';
+          labelLayer = imageryLayerCollection.addImageryProvider(
+            new Cesium.WebMapTileServiceImageryProvider({
+              url: `https://t0.tianditu.gov.cn/cia_c/wmts?service=WMTS&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${tiandituKey}`,
+              layer: 'cia',
+              style: 'default',
+              format: 'image/png',
+              tileMatrixSetID: 'c',
+              subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6', 't7'],
+              tilingScheme: new Cesium.GeographicTilingScheme(),
+              tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19'],
+              maximumLevel: 15,
+              show: true
+            })
+          );
+          console.log('添加天地图影像注记图层');
+        } else {
+          imageryLayerCollection.addImageryProvider(labelLayer);
+          console.log('恢复天地图影像注记图层');
+        }
       }
       if (type == "GRIDIMAGERY") {
         imageryLayerCollection.addImageryProvider(
@@ -1985,6 +2078,23 @@ export default {
   color: #f56c6c;
 }
 
+/* 台风加载状态 */
+.el-menu-item .typhoon-name.typhoon-loaded {
+  color: #409eff;
+  font-weight: 500;
+}
+
+.el-menu-item .delete-typhoon-icon {
+  margin-left: 8px;
+  cursor: pointer;
+  color: #909399;
+  transition: color 0.3s;
+}
+
+.el-menu-item .delete-typhoon-icon:hover {
+  color: #f56c6c;
+}
+
 /* 滚动条隐藏 */
 .el-aside::-webkit-scrollbar {
   display: none;