Просмотр исходного кода

添加鹰眼和拉框放大功能

WQQ 15 часов назад
Родитель
Сommit
819bf645c7
100 измененных файлов с 1821 добавлено и 112 удалено
  1. BIN
      RuoYi-Vue3/public/img/componentsImg/eagle-eye-map.png
  2. 0 0
      RuoYi-Vue3/public/img/componentsImg/overview.png
  3. 7 0
      RuoYi-Vue3/public/img/componentsImg/overview.svg
  4. 2 0
      RuoYi-Vue3/src/supermap-cesium-module/components/components.js
  5. 3 3
      RuoYi-Vue3/src/supermap-cesium-module/components/custom-service/custom-service.vue
  6. 3 3
      RuoYi-Vue3/src/supermap-cesium-module/components/layer/custom-service/custom-service.vue
  7. 7 0
      RuoYi-Vue3/src/supermap-cesium-module/components/scene/overview-map/index.js
  8. 309 0
      RuoYi-Vue3/src/supermap-cesium-module/components/scene/overview-map/overview-map.js
  9. 557 0
      RuoYi-Vue3/src/supermap-cesium-module/components/scene/overview-map/overview-map.vue
  10. 5 0
      RuoYi-Vue3/src/supermap-cesium-module/components/scene/rectangle-zoom/index.js
  11. 345 0
      RuoYi-Vue3/src/supermap-cesium-module/components/scene/rectangle-zoom/rectangle-zoom.vue
  12. 4 1
      RuoYi-Vue3/src/supermap-cesium-module/components/viewer/viewer.vue
  13. 5 5
      RuoYi-Vue3/src/supermap-cesium-module/config/views_config.js
  14. 7 3
      RuoYi-Vue3/src/supermap-cesium-module/js/common/camera.js
  15. 532 79
      RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js
  16. 15 3
      RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue
  17. 15 15
      RuoYi-Vue3/src/views/front/content/ShuiliGongcheng.vue
  18. BIN
      ruoyi-admin/target/classes/com/ruoyi/RuoYiApplication.class
  19. BIN
      ruoyi-admin/target/classes/com/ruoyi/RuoYiServletInitializer.class
  20. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/cesium/CesiumMapConfigController.class
  21. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CaptchaController.class
  22. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CommonController.class
  23. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/CacheController.class
  24. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/ServerController.class
  25. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysLogininforController.class
  26. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysOperlogController.class
  27. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysUserOnlineController.class
  28. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysConfigController.class
  29. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDeptController.class
  30. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDictDataController.class
  31. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDictTypeController.class
  32. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysIndexController.class
  33. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysLoginController.class
  34. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysMenuController.class
  35. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysNoticeController.class
  36. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysPostController.class
  37. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysProfileController.class
  38. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysRegisterController.class
  39. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysRoleController.class
  40. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysUserController.class
  41. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/TestController.class
  42. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/UserEntity.class
  43. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedModelController.class
  44. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedServiceController.class
  45. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/core/config/SwaggerConfig.class
  46. BIN
      ruoyi-admin/uploads/models/2026/05/07/场景1_20260507165507A004.glb
  47. 0 0
      ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7.geojson
  48. BIN
      ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.dbf
  49. 1 0
      ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.prj
  50. BIN
      ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.shp
  51. BIN
      ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.shx
  52. 0 0
      ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59.geojson
  53. BIN
      ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.dbf
  54. 1 0
      ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.prj
  55. BIN
      ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.shp
  56. BIN
      ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.shx
  57. 1 0
      ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281.geojson
  58. BIN
      ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.dbf
  59. 1 0
      ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.prj
  60. BIN
      ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.shp
  61. BIN
      ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.shx
  62. 0 0
      ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210.geojson
  63. BIN
      ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.dbf
  64. 1 0
      ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.prj
  65. BIN
      ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.shp
  66. BIN
      ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.shx
  67. BIN
      ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$ColumnType.class
  68. BIN
      ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$Type.class
  69. BIN
      ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel.class
  70. BIN
      ruoyi-common/target/classes/com/ruoyi/common/config/RuoYiConfig.class
  71. BIN
      ruoyi-common/target/classes/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.class
  72. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/CacheConstants.class
  73. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/Constants.class
  74. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/GenConstants.class
  75. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/HttpStatus.class
  76. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants$Status.class
  77. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants.class
  78. BIN
      ruoyi-common/target/classes/com/ruoyi/common/constant/UserConstants.class
  79. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController$1.class
  80. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController.class
  81. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/AjaxResult.class
  82. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/BaseEntity.class
  83. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/R.class
  84. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeEntity.class
  85. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeSelect.class
  86. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDept.class
  87. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictData.class
  88. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictType.class
  89. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysMenu.class
  90. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysRole.class
  91. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysUser.class
  92. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginBody.class
  93. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginUser.class
  94. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/RegisterBody.class
  95. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/page/PageDomain.class
  96. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/page/TableDataInfo.class
  97. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/page/TableSupport.class
  98. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/redis/RedisCache.class
  99. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/text/CharsetKit.class
  100. BIN
      ruoyi-common/target/classes/com/ruoyi/common/core/text/Convert.class

BIN
RuoYi-Vue3/public/img/componentsImg/eagle-eye-map.png


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
RuoYi-Vue3/public/img/componentsImg/overview.png


+ 7 - 0
RuoYi-Vue3/public/img/componentsImg/overview.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+  <rect x="4" y="4" width="24" height="24" rx="2" fill="none" stroke="#666" stroke-width="1.5"/>
+  <line x1="4" y1="16" x2="28" y2="16" stroke="#999" stroke-width="0.5"/>
+  <line x1="16" y1="4" x2="16" y2="28" stroke="#999" stroke-width="0.5"/>
+  <rect x="10" y="10" width="12" height="12" rx="1" fill="rgba(239,68,68,0.2)" stroke="#ef4444" stroke-width="1.5"/>
+  <circle cx="16" cy="16" r="3" fill="#ef4444"/>
+</svg>

+ 2 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/components.js

@@ -58,6 +58,7 @@ import geologicalBody from "./model/geological-body/index.js"
 import volume from "./scene/volume-render/index.js"
 import particle from "./special-effects/particle-system/index.js"
 import typhoonVisualization from "./typhoon-visualization/index.js"
+import overviewMap from "./scene/overview-map/index.js"
 
 
 const components = [
@@ -107,6 +108,7 @@ const components = [
     volume,
     particle,
     typhoonVisualization,
+    overviewMap,
 ];
 
 const install = (app, locale) => {

+ 3 - 3
RuoYi-Vue3/src/supermap-cesium-module/components/custom-service/custom-service.vue

@@ -6,9 +6,9 @@
         <select class="sm-select" style="width:58%" v-model="layersType">
           <option value="SCENE">场景</option>
           <option value="S3M">S3M</option>
-          <option value="IMG">影像</option>
-          <option value="TERRAIN">地形</option>
-          <option value="MVT">矢量瓦片</option>
+          <option value="IMG">地图服务</option>
+          <option value="TERRAIN">地形服务</option>
+          <option value="MVT">矢量要素</option>
           <option value="GEOJSON">GeoJSON</option>
           <option value="SHP">SHP上传</option>
         </select>

+ 3 - 3
RuoYi-Vue3/src/supermap-cesium-module/components/layer/custom-service/custom-service.vue

@@ -6,9 +6,9 @@
         <select class="sm-select" style="width:58%" v-model="layersType">
           <option value="SCENE">场景</option>
           <option value="S3M">S3M</option>
-          <option value="IMG">影像</option>
-          <option value="TERRAIN">地形</option>
-          <option value="MVT">矢量瓦片</option>
+          <option value="IMG">地图服务</option>
+          <option value="TERRAIN">地形服务</option>
+          <option value="MVT">矢量要素</option>
           <option value="GEOJSON">GeoJSON</option>
           <option value="SHP">SHP上传</option>
         </select>

+ 7 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/scene/overview-map/index.js

@@ -0,0 +1,7 @@
+import overviewMap from './overview-map.vue';
+
+overviewMap.install = function(app) {
+  app.component(overviewMap.name, overviewMap);
+};
+
+export default overviewMap;

+ 309 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/scene/overview-map/overview-map.js

@@ -0,0 +1,309 @@
+function overviewMap(viewer, containerWidth, containerHeight) {
+  const viewRectStyle = {
+    left: '0px',
+    top: '0px',
+    width: '50px',
+    height: '50px'
+  };
+
+  let ctx = null;
+  let canvas = null;
+  let isUpdating = false;
+
+  // 世界地图简化轮廓数据(经纬度坐标)
+  const worldOutline = [
+    // 北美洲
+    { lat: 70, lng: -170 }, { lat: 70, lng: -100 }, { lat: 50, lng: -80 },
+    { lat: 30, lng: -120 }, { lat: 25, lng: -80 }, { lat: 10, lng: -60 },
+    { lat: 5, lng: -100 }, { lat: 5, lng: -170 },
+    
+    // 南美洲
+    { lat: 15, lng: -80 }, { lat: 5, lng: -60 }, { lat: -10, lng: -45 },
+    { lat: -30, lng: -60 }, { lat: -55, lng: -70 }, { lat: -35, lng: -80 },
+    { lat: -15, lng: -75 },
+    
+    // 欧洲
+    { lat: 70, lng: -10 }, { lat: 70, lng: 30 }, { lat: 55, lng: 40 },
+    { lat: 45, lng: 30 }, { lat: 40, lng: 5 }, { lat: 55, lng: -5 },
+    
+    // 非洲
+    { lat: 35, lng: -15 }, { lat: 35, lng: 35 }, { lat: 10, lng: 50 },
+    { lat: -10, lng: 45 }, { lat: -35, lng: 20 }, { lat: -35, lng: -15 },
+    { lat: 0, lng: -20 },
+    
+    // 亚洲
+    { lat: 75, lng: 30 }, { lat: 75, lng: 180 }, { lat: 50, lng: 180 },
+    { lat: 35, lng: 140 }, { lat: 20, lng: 120 }, { lat: 10, lng: 100 },
+    { lat: 20, lng: 80 }, { lat: 40, lng: 60 }, { lat: 55, lng: 40 },
+    
+    // 澳大利亚
+    { lat: -10, lng: 115 }, { lat: -10, lng: 155 }, { lat: -45, lng: 155 },
+    { lat: -45, lng: 115 },
+    
+    // 南极洲
+    { lat: -60, lng: -180 }, { lat: -60, lng: 180 }, { lat: -90, lng: 0 }
+  ];
+
+  // 将经纬度转换为canvas坐标
+  function latLngToCanvas(lat, lng, width, height) {
+    const x = ((lng + 180) / 360) * width;
+    const y = ((90 - lat) / 180) * height;
+    return { x, y };
+  }
+
+  // 更新小地图绘制
+  function updateMiniMap(canvasEl) {
+    if (!canvasEl) return;
+    
+    canvas = canvasEl;
+    ctx = canvas.getContext('2d');
+    
+    const dpr = window.devicePixelRatio || 1;
+    const rect = canvas.parentElement.getBoundingClientRect();
+    
+    canvas.width = rect.width * dpr;
+    canvas.height = rect.height * dpr;
+    canvas.style.width = rect.width + 'px';
+    canvas.style.height = rect.height + 'px';
+    ctx.scale(dpr, dpr);
+    
+    const width = rect.width;
+    const height = rect.height;
+    
+    // 清除画布
+    ctx.fillStyle = '#1e293b';
+    ctx.fillRect(0, 0, width, height);
+    
+    // 绘制网格线
+    ctx.strokeStyle = 'rgba(148, 163, 184, 0.1)';
+    ctx.lineWidth = 1;
+    
+    // 绘制经线
+    for (let i = -180; i <= 180; i += 30) {
+      const pos = latLngToCanvas(0, i, width, height);
+      ctx.beginPath();
+      ctx.moveTo(pos.x, 0);
+      ctx.lineTo(pos.x, height);
+      ctx.stroke();
+    }
+    
+    // 绘制纬线
+    for (let i = -90; i <= 90; i += 30) {
+      const start = latLngToCanvas(i, -180, width, height);
+      const end = latLngToCanvas(i, 180, width, height);
+      ctx.beginPath();
+      ctx.moveTo(start.x, start.y);
+      ctx.lineTo(end.x, end.y);
+      ctx.stroke();
+    }
+    
+    // 绘制大陆轮廓
+    ctx.strokeStyle = '#475569';
+    ctx.lineWidth = 1.5;
+    
+    worldOutline.forEach((point, index) => {
+      const pos = latLngToCanvas(point.lat, point.lng, width, height);
+      if (index === 0) {
+        ctx.beginPath();
+        ctx.moveTo(pos.x, pos.y);
+      } else {
+        ctx.lineTo(pos.x, pos.y);
+      }
+    });
+    ctx.stroke();
+    
+    // 绘制赤道
+    ctx.strokeStyle = 'rgba(239, 68, 68, 0.3)';
+    ctx.setLineDash([5, 5]);
+    const equatorStart = latLngToCanvas(0, -180, width, height);
+    const equatorEnd = latLngToCanvas(0, 180, width, height);
+    ctx.beginPath();
+    ctx.moveTo(equatorStart.x, equatorStart.y);
+    ctx.lineTo(equatorEnd.x, equatorEnd.y);
+    ctx.stroke();
+    ctx.setLineDash([]);
+  }
+
+  // 获取当前相机视口的边界经纬度
+  function getCameraViewBounds() {
+    if (!viewer || !viewer.scene) return null;
+    
+    const scene = viewer.scene;
+    const camera = viewer.camera;
+    
+    try {
+      // 获取相机的目标点(lookAt点),即相机正在看向的点
+      // 通过从相机位置向视口中心发射射线,与地球椭球相交得到目标点
+      const ray = camera.getPickRay(new Cesium.Cartesian2(scene.canvas.width / 2, scene.canvas.height / 2));
+      const targetPosition = scene.globe.pick(ray, scene);
+      
+      if (!targetPosition) {
+        // 如果无法获取目标点,回退到相机位置正下方
+        const cartographic = Cesium.Cartographic.fromCartesian(camera.position);
+        const lat = Cesium.Math.toDegrees(cartographic.latitude);
+        const lng = Cesium.Math.toDegrees(cartographic.longitude);
+        const height = cartographic.height;
+        
+        const viewAngle = Cesium.Math.toDegrees(camera.frustum.fov);
+        const viewRange = height * Math.tan(Cesium.Math.toRadians(viewAngle / 2));
+        
+        const earthRadius = 6371000;
+        const latDelta = (viewRange / earthRadius) * (180 / Math.PI);
+        const lngDelta = latDelta / Math.cos(Cesium.Math.toRadians(lat));
+        
+        return {
+          minLat: Math.max(-90, lat - latDelta),
+          maxLat: Math.min(90, lat + latDelta),
+          minLng: lng - lngDelta,
+          maxLng: lng + lngDelta
+        };
+      }
+      
+      // 使用目标点作为视口中心
+      const targetCartographic = Cesium.Cartographic.fromCartesian(targetPosition);
+      const targetLat = Cesium.Math.toDegrees(targetCartographic.latitude);
+      const targetLng = Cesium.Math.toDegrees(targetCartographic.longitude);
+      
+      // 计算相机到目标点的实际距离(这是真正的观察高度,不受相机倾斜影响)
+      const cameraDistance = Cesium.Cartesian3.distance(camera.position, targetPosition);
+      
+      // 计算可视范围(根据相机距离和视角估算)
+      const viewAngle = Cesium.Math.toDegrees(camera.frustum.fov);
+      const viewRange = cameraDistance * Math.tan(Cesium.Math.toRadians(viewAngle / 2));
+      
+      // 地球半径约6371公里
+      const earthRadius = 6371000;
+      const latDelta = (viewRange / earthRadius) * (180 / Math.PI);
+      const lngDelta = latDelta / Math.cos(Cesium.Math.toRadians(targetLat));
+      
+      return {
+        minLat: Math.max(-90, targetLat - latDelta),
+        maxLat: Math.min(90, targetLat + latDelta),
+        minLng: targetLng - lngDelta,
+        maxLng: targetLng + lngDelta
+      };
+    } catch (e) {
+      console.warn('获取相机边界失败:', e);
+      return null;
+    }
+  }
+
+  // 更新视口红框位置和大小
+  function updateViewRect() {
+    if (!viewer || !canvas || isUpdating) return;
+    
+    const bounds = getCameraViewBounds();
+    if (!bounds) return;
+    
+    const rect = canvas.parentElement.getBoundingClientRect();
+    const width = rect.width;
+    const height = rect.height;
+    
+    // 将经纬度范围转换为canvas坐标
+    const topLeft = latLngToCanvas(bounds.maxLat, bounds.minLng, width, height);
+    const bottomRight = latLngToCanvas(bounds.minLat, bounds.maxLng, width, height);
+    
+    const rectWidth = Math.max(10, bottomRight.x - topLeft.x);
+    const rectHeight = Math.max(10, bottomRight.y - topLeft.y);
+    
+    Object.assign(viewRectStyle, {
+      left: topLeft.x + 'px',
+      top: topLeft.y + 'px',
+      width: rectWidth + 'px',
+      height: rectHeight + 'px'
+    });
+  }
+
+  // 开始拖拽
+  function handleDragStart(startPos) {
+    // 检查点击是否在红框内
+    const left = parseFloat(viewRectStyle.left);
+    const top = parseFloat(viewRectStyle.top);
+    const width = parseFloat(viewRectStyle.width);
+    const height = parseFloat(viewRectStyle.height);
+    
+    const isInside = startPos.x >= left && startPos.x <= left + width &&
+                     startPos.y >= top && startPos.y <= top + height;
+    
+    if (!isInside) {
+      // 如果点击在红框外,直接定位到点击位置
+      const rect = canvas.parentElement.getBoundingClientRect();
+      const lng = (startPos.x / rect.width) * 360 - 180;
+      const lat = 90 - (startPos.y / rect.height) * 180;
+      
+      flyToPosition(lng, lat);
+    }
+  }
+
+  // 拖拽中
+  function handleDragMove(currentPos, startPos, rectStart) {
+    const rect = canvas.parentElement.getBoundingClientRect();
+    const width = rect.width;
+    const height = rect.height;
+    
+    const deltaX = currentPos.x - startPos.x;
+    const deltaY = currentPos.y - startPos.y;
+    
+    let newLeft = rectStart.x + deltaX;
+    let newTop = rectStart.y + deltaY;
+    
+    // 限制在画布范围内
+    newLeft = Math.max(0, Math.min(width - parseFloat(viewRectStyle.width), newLeft));
+    newTop = Math.max(0, Math.min(height - parseFloat(viewRectStyle.height), newTop));
+    
+    Object.assign(viewRectStyle, {
+      left: newLeft + 'px',
+      top: newTop + 'px'
+    });
+  }
+
+  // 拖拽结束
+  function handleDragEnd() {
+    const left = parseFloat(viewRectStyle.left);
+    const top = parseFloat(viewRectStyle.top);
+    const width = parseFloat(viewRectStyle.width);
+    const height = parseFloat(viewRectStyle.height);
+    
+    const rect = canvas.parentElement.getBoundingClientRect();
+    
+    // 计算红框中心的经纬度
+    const centerX = left + width / 2;
+    const centerY = top + height / 2;
+    
+    const lng = (centerX / rect.width) * 360 - 180;
+    const lat = 90 - (centerY / rect.height) * 180;
+    
+    flyToPosition(lng, lat);
+  }
+
+  // 飞转到指定位置
+  function flyToPosition(lng, lat) {
+    if (!viewer) return;
+    
+    isUpdating = true;
+    
+    viewer.camera.flyTo({
+      destination: Cesium.Cartesian3.fromDegrees(lng, lat, viewer.camera.positionCartographic.height),
+      orientation: {
+        heading: viewer.camera.heading,
+        pitch: viewer.camera.pitch,
+        roll: 0
+      },
+      duration: 0.5,
+      complete: () => {
+        isUpdating = false;
+      }
+    });
+  }
+
+  return {
+    viewRectStyle,
+    updateViewRect,
+    updateMiniMap,
+    handleDragStart,
+    handleDragMove,
+    handleDragEnd
+  };
+}
+
+export default overviewMap;

+ 557 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/scene/overview-map/overview-map.vue

@@ -0,0 +1,557 @@
+<template>
+  <div 
+    v-if="visible" 
+    class="overview-map-container"
+  >
+    <div class="overview-map-header">
+      <span class="overview-title">鹰眼</span>
+      <span class="overview-close" @click="close">×</span>
+    </div>
+    <div 
+      class="overview-map-canvas"
+      ref="canvasContainer"
+      @click="onCanvasClick"
+    >
+      <div ref="cesiumContainer" class="cesium-mini-map"></div>
+      <!-- 半透明红框 -->
+      <div 
+        class="view-rect"
+        :style="viewRectStyle"
+      ></div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from "vue";
+
+export default {
+  name: "Sm3dOverviewMap",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['close'],
+  setup(props, { emit }) {
+    // 获取主场景viewer(优先从window获取)
+    const getMainViewer = () => {
+      return window.viewer || null;
+    };
+    
+    const canvasContainer = ref(null);
+    const cesiumContainer = ref(null);
+    
+    // 福建行政外接矩形(WGS84/EPSG:4326)
+    const FUJIAN_EXTENT = {
+      minLng: 115.7,
+      maxLng: 120.8,
+      minLat: 23.5,
+      maxLat: 28.3
+    };
+    
+    // 红框样式
+    const viewRectStyle = reactive({
+      left: '0px',
+      top: '0px',
+      width: '40px',
+      height: '40px',
+      display: 'block'
+    });
+
+    let miniViewer = null;
+    let updateInterval = null;
+
+    // 将经纬度转换为容器坐标(福建范围映射)
+    function latLngToContainer(lat, lng, width, height) {
+      // 福建范围:经度115.7~120.8,纬度23.5~28.3
+      const lngRange = FUJIAN_EXTENT.maxLng - FUJIAN_EXTENT.minLng;
+      const latRange = FUJIAN_EXTENT.maxLat - FUJIAN_EXTENT.minLat;
+      
+      const x = ((lng - FUJIAN_EXTENT.minLng) / lngRange) * width;
+      const y = ((FUJIAN_EXTENT.maxLat - lat) / latRange) * height;
+      
+      return { x, y };
+    }
+
+    // 将容器坐标转换为经纬度(福建范围映射)
+    function containerToLatLng(x, y, width, height) {
+      const lngRange = FUJIAN_EXTENT.maxLng - FUJIAN_EXTENT.minLng;
+      const latRange = FUJIAN_EXTENT.maxLat - FUJIAN_EXTENT.minLat;
+      
+      const lng = (x / width) * lngRange + FUJIAN_EXTENT.minLng;
+      const lat = FUJIAN_EXTENT.maxLat - (y / height) * latRange;
+      
+      return { lng, lat };
+    }
+
+    // 获取当前相机视线指向的地面位置(视口中心点对应的地面位置)
+    function getCameraPosition() {
+      const mainViewer = getMainViewer();
+      if (!mainViewer || !mainViewer.scene || !mainViewer.camera) return null;
+      
+      try {
+        const scene = mainViewer.scene;
+        const camera = mainViewer.camera;
+        const canvas = scene.canvas;
+        
+        // 创建从视口中心发射的射线(这是获取视口中心点对应地面位置的正确方式)
+        const centerScreenPos = new Cesium.Cartesian2(canvas.width / 2, canvas.height / 2);
+        const ray = camera.getPickRay(centerScreenPos);
+        
+        // 获取射线与地面的交点
+        const intersection = scene.globe.pick(ray, scene);
+        
+        if (intersection) {
+          // 将交点转换为经纬度
+          const cartographic = Cesium.Cartographic.fromCartesian(intersection);
+          // 计算相机到目标点的实际距离(这是真正的观察高度,不受相机倾斜影响)
+          const distance = Cesium.Cartesian3.distance(camera.position, intersection);
+          return {
+            lat: Cesium.Math.toDegrees(cartographic.latitude),
+            lng: Cesium.Math.toDegrees(cartographic.longitude),
+            height: distance
+          };
+        }
+        
+        // 如果无法获取交点,使用相机位置的经纬度(正下方地面)
+        const cameraCartographic = Cesium.Cartographic.fromCartesian(camera.position);
+        if (cameraCartographic) {
+          return {
+            lat: Cesium.Math.toDegrees(cameraCartographic.latitude),
+            lng: Cesium.Math.toDegrees(cameraCartographic.longitude),
+            height: cameraCartographic.height
+          };
+        }
+        
+        return null;
+      } catch (e) {
+        console.warn('获取相机位置失败:', e);
+        return null;
+      }
+    }
+    
+    // 获取当前视口范围
+    function getViewportBounds() {
+      const mainViewer = getMainViewer();
+      if (!mainViewer || !mainViewer.scene) return null;
+      
+      try {
+        const scene = mainViewer.scene;
+        const camera = mainViewer.camera;
+        
+        // 获取视口四角的地面交点
+        const canvas = scene.canvas;
+        const width = canvas.width;
+        const height = canvas.height;
+        
+        const corners = [
+          new Cesium.Cartesian2(0, 0),
+          new Cesium.Cartesian2(width, 0),
+          new Cesium.Cartesian2(width, height),
+          new Cesium.Cartesian2(0, height)
+        ];
+        
+        let minLat = 90, maxLat = -90, minLng = 180, maxLng = -180;
+        
+        for (const corner of corners) {
+          const ray = camera.getPickRay(corner);
+          const intersection = scene.globe.pick(ray, scene);
+          
+          if (intersection) {
+            const cartographic = Cesium.Cartographic.fromCartesian(intersection);
+            const lat = Cesium.Math.toDegrees(cartographic.latitude);
+            const lng = Cesium.Math.toDegrees(cartographic.longitude);
+            
+            minLat = Math.min(minLat, lat);
+            maxLat = Math.max(maxLat, lat);
+            minLng = Math.min(minLng, lng);
+            maxLng = Math.max(maxLng, lng);
+          }
+        }
+        
+        // 如果有有效范围,返回边界
+        if (minLat <= maxLat && minLng <= maxLng) {
+          return { minLat, maxLat, minLng, maxLng };
+        }
+        
+        return null;
+      } catch (e) {
+        console.warn('获取视口范围失败:', e);
+        return null;
+      }
+    }
+
+    // 更新红框位置
+    function updateViewRect() {
+      if (!canvasContainer.value || !miniViewer) {
+        return;
+      }
+      
+      const containerRect = canvasContainer.value.getBoundingClientRect();
+      const width = containerRect.width;
+      const height = containerRect.height;
+      
+      // 如果没有主场景viewer,隐藏红框
+      const mainViewer = getMainViewer();
+      if (!mainViewer || !mainViewer.scene) {
+        viewRectStyle.display = 'none';
+        return;
+      }
+      
+      const cameraPos = getCameraPosition();
+      if (!cameraPos) {
+        viewRectStyle.display = 'none';
+        return;
+      }
+      
+      // 使用Cesium的精确坐标转换获取红框中心位置
+      try {
+        const cartesian = Cesium.Cartesian3.fromDegrees(cameraPos.lng, cameraPos.lat);
+        const screenPos = miniViewer.scene.cartesianToCanvasCoordinates(cartesian);
+        
+        if (screenPos && !isNaN(screenPos.x) && !isNaN(screenPos.y)) {
+          // 使用Cesium转换后的屏幕坐标作为红框中心
+          const centerX = screenPos.x;
+          const centerY = screenPos.y;
+          
+          // 计算红框尺寸(基于相机高度估算视口范围)
+          // 相机高度越高(越远)→ 视口范围越大 → 红框越大
+          // 相机高度越低(越近)→ 视口范围越小 → 红框越小
+          const maxRectSize = Math.min(width, height) * 0.35;
+          const minRectSize = 15;
+          // 使用高度比例:高度越高,红框越大
+          const rectSize = Math.max(minRectSize, Math.min(maxRectSize, 
+            (cameraPos.height / 50000) * Math.min(width, height) * 0.2));
+          
+          // 设置红框位置(以转换后的坐标为中心)
+          viewRectStyle.left = (centerX - rectSize / 2) + 'px';
+          viewRectStyle.top = (centerY - rectSize / 2) + 'px';
+          viewRectStyle.width = rectSize + 'px';
+          viewRectStyle.height = rectSize + 'px';
+          viewRectStyle.display = 'block';
+          return;
+        }
+      } catch (e) {
+        console.warn('Cesium坐标转换失败:', e);
+      }
+      
+      // 回退方案:使用手动计算
+      const center = latLngToContainer(cameraPos.lat, cameraPos.lng, width, height);
+      const maxRectSize = Math.min(width, height) * 0.35;
+      const minRectSize = 15;
+      // 使用高度比例:高度越高,红框越大
+      const rectSize = Math.max(minRectSize, Math.min(maxRectSize, 
+        (cameraPos.height / 50000) * Math.min(width, height) * 0.2));
+      
+      viewRectStyle.left = (center.x - rectSize / 2) + 'px';
+      viewRectStyle.top = (center.y - rectSize / 2) + 'px';
+      viewRectStyle.width = rectSize + 'px';
+      viewRectStyle.height = rectSize + 'px';
+      viewRectStyle.display = 'block';
+    }
+
+    // 更新鹰眼地图视图(固定到福建范围)
+    function updateMiniViewerView() {
+      if (!miniViewer) return;
+      
+      // 固定缩放到福建范围
+      miniViewer.camera.setView({
+        destination: Cesium.Rectangle.fromDegrees(
+          FUJIAN_EXTENT.minLng, 
+          FUJIAN_EXTENT.minLat, 
+          FUJIAN_EXTENT.maxLng, 
+          FUJIAN_EXTENT.maxLat
+        )
+      });
+    }
+
+    // 飞转到指定位置
+    function flyToPosition(lng, lat) {
+      const mainViewer = getMainViewer();
+      if (!mainViewer) return;
+      
+      const height = mainViewer.camera.positionCartographic ? 
+        mainViewer.camera.positionCartographic.height : 10000;
+      
+      mainViewer.camera.flyTo({
+        destination: Cesium.Cartesian3.fromDegrees(lng, lat, height),
+        orientation: {
+          heading: mainViewer.camera.heading,
+          pitch: mainViewer.camera.pitch,
+          roll: 0
+        },
+        duration: 0.5
+      });
+    }
+
+    // 点击鹰眼地图任意位置,飞转到对应经纬度
+    const onCanvasClick = (event) => {
+      if (!canvasContainer.value) return;
+      
+      const rect = canvasContainer.value.getBoundingClientRect();
+      const clickX = event.clientX - rect.left;
+      const clickY = event.clientY - rect.top;
+      
+      // 使用线性映射获取经纬度
+      const { lng, lat } = containerToLatLng(clickX, clickY, rect.width, rect.height);
+      
+      // 立即更新红框位置到点击位置(居中显示)
+      const maxRectSize = Math.min(rect.width, rect.height) * 0.35;
+      const minRectSize = 15;
+      
+      // 获取当前相机高度用于计算红框尺寸
+      const mainViewer = getMainViewer();
+      const currentHeight = mainViewer && mainViewer.camera.positionCartographic ? 
+        mainViewer.camera.positionCartographic.height : 10000;
+      
+      const rectSize = Math.max(minRectSize, Math.min(maxRectSize, 
+        (currentHeight / 50000) * Math.min(rect.width, rect.height) * 0.2));
+      
+      // 将红框居中到点击位置(确保红框中心始终在点击位置)
+      viewRectStyle.left = (clickX - rectSize / 2) + 'px';
+      viewRectStyle.top = (clickY - rectSize / 2) + 'px';
+      viewRectStyle.width = rectSize + 'px';
+      viewRectStyle.height = rectSize + 'px';
+      viewRectStyle.display = 'block';
+      
+      // 让三维场景飞转到对应位置
+      flyToPosition(lng, lat);
+    };
+
+    const close = () => {
+      emit('close');
+    };
+
+    // 创建小型Cesium视图
+    function createMiniViewer() {
+      if (!cesiumContainer.value) return;
+      
+      const tiandituKey = '3fb1e9fda20ee995dc815c8243553ce8';
+      
+      // 使用地理坐标系(EPSG:4326)的瓦片方案
+      const geographicTilingScheme = new Cesium.GeographicTilingScheme();
+      
+      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: 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
+      });
+      
+      // 创建自定义的2D视图投影,使用地理坐标系
+      const mapProjection = new Cesium.GeographicProjection();
+      
+      // 创建场景模式配置,强制使用地理坐标系
+      const sceneMode = Cesium.SceneMode.SCENE2D;
+      
+      miniViewer = new Cesium.Viewer(cesiumContainer.value, {
+        imageryProvider: imageryProvider,
+        baseLayerPicker: false,
+        timeline: false,
+        animation: false,
+        geocoder: false,
+        homeButton: false,
+        sceneModePicker: false,
+        navigationHelpButton: false,
+        navigationInstructionsInitiallyVisible: false,
+        fullscreenButton: false,
+        shadows: false,
+        terrainProvider: new Cesium.EllipsoidTerrainProvider(),
+        creditContainer: document.createElement('div')
+      });
+      
+      // 设置为2D模式
+      miniViewer.scene.morphTo2D(0);
+      
+      // 完全禁用所有相机控制(锁定视图)
+      miniViewer.scene.screenSpaceCameraController.enableRotate = false;
+      miniViewer.scene.screenSpaceCameraController.enableZoom = false;
+      miniViewer.scene.screenSpaceCameraController.enablePan = false;
+      miniViewer.scene.screenSpaceCameraController.enableTilt = false;
+      miniViewer.scene.screenSpaceCameraController.enableLook = false;
+      
+      // 添加注记图层
+      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: 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
+      });
+      
+      miniViewer.imageryLayers.addImageryProvider(labelProvider);
+      
+      // 缩放到全球视图(使用地理坐标系)
+      miniViewer.camera.setView({
+        destination: Cesium.Rectangle.fromDegrees(-180, -90, 180, 90)
+      });
+      
+      // 延迟resize确保尺寸正确
+      setTimeout(() => {
+        if (miniViewer) {
+          miniViewer.resize();
+          updateViewRect();
+          updateMiniViewerView();
+        }
+      }, 150);
+      
+      // 启动定时更新
+      startUpdateLoop();
+    }
+
+    function startUpdateLoop() {
+      stopUpdateLoop();
+      
+      updateInterval = setInterval(() => {
+        updateViewRect();
+        updateMiniViewerView();
+      }, 50);
+    }
+
+    function stopUpdateLoop() {
+      if (updateInterval) {
+        clearInterval(updateInterval);
+        updateInterval = null;
+      }
+    }
+
+    onMounted(() => {
+      nextTick(() => {
+        createMiniViewer();
+      });
+    });
+
+    onUnmounted(() => {
+      stopUpdateLoop();
+      if (miniViewer) {
+        miniViewer.destroy();
+        miniViewer = null;
+      }
+    });
+
+    watch(() => props.visible, (newVal) => {
+      if (newVal) {
+        nextTick(() => {
+          if (!miniViewer) {
+            createMiniViewer();
+          } else {
+            miniViewer.resize();
+            updateViewRect();
+            updateMiniViewerView();
+          }
+        });
+      } else {
+        stopUpdateLoop();
+        if (miniViewer) {
+          miniViewer.destroy();
+          miniViewer = null;
+        }
+      }
+    });
+
+    return {
+      canvasContainer,
+      cesiumContainer,
+      viewRectStyle,
+      onCanvasClick,
+      close
+    };
+  }
+};
+</script>
+
+<style scoped>
+.overview-map-container {
+  position: fixed;
+  right: 20px;
+  bottom: 120px;
+  width: 350px;
+  height: 280px;
+  background: rgba(30, 41, 59, 0.95);
+  border-radius: 10px;
+  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
+  z-index: 1000;
+  overflow: hidden;
+  border: 1px solid rgba(148, 163, 184, 0.1);
+}
+
+.overview-map-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 14px;
+  background: rgba(51, 65, 85, 0.85);
+  border-bottom: 1px solid rgba(148, 163, 184, 0.15);
+  flex-shrink: 0;
+}
+
+.overview-title {
+  color: #e2e8f0;
+  font-size: 13px;
+  font-weight: 500;
+}
+
+.overview-close {
+  color: #94a3b8;
+  font-size: 20px;
+  cursor: pointer;
+  line-height: 1;
+  transition: color 0.2s;
+  font-weight: 300;
+}
+
+.overview-close:hover {
+  color: #f1f5f9;
+}
+
+.overview-map-canvas {
+  position: relative;
+  width: 100%;
+  height: calc(100% - 42px);
+  cursor: crosshair;
+  overflow: hidden;
+}
+
+.cesium-mini-map {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.view-rect {
+  position: absolute;
+  border: 2px solid #ef4444;
+  background: rgba(239, 68, 68, 0.25);
+  cursor: default;
+  box-sizing: border-box;
+  transition: all 0.1s ease;
+  z-index: 10;
+}
+
+.view-rect:hover {
+  box-shadow: 0 0 15px rgba(239, 68, 68, 0.6);
+  border-color: #f87171;
+  background: rgba(239, 68, 68, 0.35);
+}
+
+.view-rect.dragging {
+  border-color: #f97316;
+  background: rgba(249, 115, 22, 0.4);
+  box-shadow: 0 0 20px rgba(249, 115, 22, 0.6);
+}
+</style>

+ 5 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/scene/rectangle-zoom/index.js

@@ -0,0 +1,5 @@
+import RectangleZoom from './rectangle-zoom.vue';
+
+export default RectangleZoom;
+
+export { RectangleZoom };

+ 345 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/scene/rectangle-zoom/rectangle-zoom.vue

@@ -0,0 +1,345 @@
+<template>
+  <div class="rectangle-zoom-container" ref="containerRef"></div>
+</template>
+
+<script>
+import { ref, onMounted, onUnmounted, watch } from 'vue';
+
+export default {
+  name: 'RectangleZoom',
+  props: {
+    viewer: {
+      type: Object,
+      required: true
+    },
+    enabled: {
+      type: Boolean,
+      default: true
+    },
+    lineColor: {
+      type: String,
+      default: '#00aaff'
+    },
+    fillColor: {
+      type: String,
+      default: 'rgba(0, 170, 255, 0.2)'
+    },
+    lineWidth: {
+      type: Number,
+      default: 2
+    },
+    minSize: {
+      type: Number,
+      default: 10
+    }
+  },
+  setup(props) {
+    const containerRef = ref(null);
+    let canvas = null;
+    let ctx = null;
+    let isDrawing = false;
+    let isShiftDown = false;
+    let startX = 0;
+    let startY = 0;
+    let currentX = 0;
+    let currentY = 0;
+
+    const initCanvas = () => {
+      if (!props.viewer || !props.viewer.container) return;
+      
+      const container = props.viewer.container;
+      
+      const existingCanvas = document.getElementById('rectangle-zoom-canvas');
+      if (existingCanvas) {
+        existingCanvas.parentNode.removeChild(existingCanvas);
+      }
+      
+      canvas = document.createElement('canvas');
+      canvas.id = 'rectangle-zoom-canvas';
+      canvas.style.position = 'absolute';
+      canvas.style.top = '0';
+      canvas.style.left = '0';
+      canvas.style.width = '100%';
+      canvas.style.height = '100%';
+      canvas.style.pointerEvents = 'none';
+      canvas.style.zIndex = '1000';
+      container.appendChild(canvas);
+      
+      ctx = canvas.getContext('2d');
+      resizeCanvas();
+    };
+
+    const resizeCanvas = () => {
+      if (!canvas || !props.viewer || !props.viewer.container) return;
+      const rect = props.viewer.container.getBoundingClientRect();
+      canvas.width = rect.width;
+      canvas.height = rect.height;
+    };
+
+    const drawRectangle = () => {
+      if (!ctx) return;
+      
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+      
+      if (!isDrawing) return;
+      
+      const x = Math.min(startX, currentX);
+      const y = Math.min(startY, currentY);
+      const width = Math.abs(currentX - startX);
+      const height = Math.abs(currentY - startY);
+      
+      ctx.beginPath();
+      ctx.strokeStyle = props.lineColor;
+      ctx.lineWidth = props.lineWidth;
+      ctx.setLineDash([8, 4]);
+      ctx.strokeRect(x, y, width, height);
+      
+      ctx.fillStyle = props.fillColor;
+      ctx.fillRect(x, y, width, height);
+      
+      ctx.setLineDash([]);
+    };
+
+    const getRectangleExtent = () => {
+      if (!props.viewer || !props.viewer.scene || !props.viewer.scene.globe) return null;
+
+      const x1 = Math.min(startX, currentX);
+      const y1 = Math.min(startY, currentY);
+      const x2 = Math.max(startX, currentX);
+      const y2 = Math.max(startY, currentY);
+
+      const scene = props.viewer.scene;
+      const camera = props.viewer.camera;
+      const globe = scene.globe;
+
+      const corners = [
+        [x1, y1], [x2, y1], [x2, y2], [x1, y2]
+      ];
+
+      let lons = [];
+      let lats = [];
+
+      for (const [cx, cy] of corners) {
+        const ray = camera.getPickRay(new Cesium.Cartesian2(cx, cy));
+        if (!ray) continue;
+
+        let cartesian;
+        if (globe) {
+          cartesian = globe.pick(ray, scene);
+        }
+        if (!cartesian) {
+          cartesian = camera.pickEllipsoid(new Cesium.Cartesian2(cx, cy), Cesium.Ellipsoid.WGS84);
+        }
+        if (!cartesian) continue;
+
+        const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
+        lons.push(cartographic.longitude);
+        lats.push(cartographic.latitude);
+      }
+
+      if (lons.length === 0) return null;
+
+      return {
+        west: Math.min(...lons),
+        east: Math.max(...lons),
+        south: Math.min(...lats),
+        north: Math.max(...lats)
+      };
+    };
+
+    const flyToExtent = (extent) => {
+      if (!extent || !props.viewer) return;
+
+      const rectangle = Cesium.Rectangle.fromRadians(
+        extent.west, extent.south, extent.east, extent.north
+      );
+
+      props.viewer.camera.flyTo({
+        destination: rectangle,
+        duration: 1.0,
+        easingFunction: Cesium.EasingFunction.QUADRATIC_IN_OUT
+      });
+    };
+
+    const setCanvasCapture = (capture) => {
+      if (!canvas) return;
+      canvas.style.pointerEvents = capture ? 'auto' : 'none';
+    };
+
+    const setCursor = (cursor) => {
+      if (canvas) {
+        canvas.style.cursor = cursor;
+      }
+      if (!props.viewer) return;
+      if (props.viewer.container) {
+        props.viewer.container.style.cursor = cursor;
+      }
+      if (props.viewer.scene && props.viewer.scene.canvas) {
+        props.viewer.scene.canvas.style.cursor = cursor;
+      }
+    };
+
+    const clearCursorOverride = () => {
+      if (canvas) {
+        canvas.style.cursor = '';
+      }
+      if (!props.viewer) return;
+      if (props.viewer.container) {
+        props.viewer.container.style.cursor = '';
+      }
+      if (props.viewer.scene && props.viewer.scene.canvas) {
+        props.viewer.scene.canvas.style.cursor = '';
+      }
+    };
+
+    const getCanvasPos = (clientX, clientY) => {
+      if (!canvas) return null;
+      const rect = canvas.getBoundingClientRect();
+      return new Cesium.Cartesian2(clientX - rect.left, clientY - rect.top);
+    };
+
+    const handleMouseDown = (event) => {
+      if (!props.enabled) return;
+      if (event.button !== 0) return;
+      if (!isShiftDown) return;
+
+      const pos = getCanvasPos(event.clientX, event.clientY);
+      if (!pos) return;
+
+      startX = pos.x;
+      startY = pos.y;
+      currentX = pos.x;
+      currentY = pos.y;
+      isDrawing = true;
+    };
+
+    const handleMouseMove = (event) => {
+      if (!isDrawing) return;
+
+      const pos = getCanvasPos(event.clientX, event.clientY);
+      if (!pos) return;
+
+      currentX = pos.x;
+      currentY = pos.y;
+
+      drawRectangle();
+    };
+
+    const endDrawing = () => {
+      if (!isDrawing) return;
+      if (!ctx) return;
+
+      const width = Math.abs(currentX - startX);
+      const height = Math.abs(currentY - startY);
+
+      if (width >= props.minSize && height >= props.minSize) {
+        const extent = getRectangleExtent();
+        if (extent) {
+          flyToExtent(extent);
+        }
+      }
+
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+      isDrawing = false;
+    };
+
+    const handleMouseUp = (event) => {
+      if (!isDrawing) return;
+      if (event.button !== 0) return;
+
+      const pos = getCanvasPos(event.clientX, event.clientY);
+      if (pos) {
+        currentX = pos.x;
+        currentY = pos.y;
+      }
+
+      endDrawing();
+    };
+
+    const handleKeyDown = (event) => {
+      if (event.key === 'Shift' || event.key === 'ShiftLeft' || event.key === 'ShiftRight') {
+        isShiftDown = true;
+        setCanvasCapture(true);
+        setCursor('crosshair');
+      }
+    };
+
+    const handleKeyUp = (event) => {
+      if (event.key === 'Shift' || event.key === 'ShiftLeft' || event.key === 'ShiftRight') {
+        isShiftDown = false;
+        endDrawing();
+        setCanvasCapture(false);
+        clearCursorOverride();
+      }
+    };
+
+    const addEventListeners = () => {
+      if (!canvas) return;
+
+      canvas.addEventListener('mousedown', handleMouseDown);
+      canvas.addEventListener('mousemove', handleMouseMove);
+      canvas.addEventListener('mouseup', handleMouseUp);
+      canvas.addEventListener('mouseleave', endDrawing);
+      window.addEventListener('mouseup', handleMouseUp);
+
+      window.addEventListener('keydown', handleKeyDown);
+      window.addEventListener('keyup', handleKeyUp);
+      window.addEventListener('resize', resizeCanvas);
+    };
+
+    const removeEventListeners = () => {
+      if (canvas) {
+        canvas.removeEventListener('mousedown', handleMouseDown);
+        canvas.removeEventListener('mousemove', handleMouseMove);
+        canvas.removeEventListener('mouseup', handleMouseUp);
+        canvas.removeEventListener('mouseleave', endDrawing);
+      }
+
+      window.removeEventListener('mouseup', handleMouseUp);
+      window.removeEventListener('keydown', handleKeyDown);
+      window.removeEventListener('keyup', handleKeyUp);
+      window.removeEventListener('resize', resizeCanvas);
+    };
+
+    const destroyCanvas = () => {
+      if (canvas && canvas.parentNode) {
+        canvas.parentNode.removeChild(canvas);
+        canvas = null;
+        ctx = null;
+      }
+    };
+
+    onMounted(() => {
+      if (props.enabled) {
+        initCanvas();
+        addEventListeners();
+      }
+    });
+
+    onUnmounted(() => {
+      removeEventListeners();
+      destroyCanvas();
+    });
+
+    watch(() => props.enabled, (newVal) => {
+      if (newVal) {
+        initCanvas();
+        addEventListeners();
+      } else {
+        removeEventListeners();
+        destroyCanvas();
+      }
+    });
+
+    return {
+      containerRef
+    };
+  }
+};
+</script>
+
+<style scoped>
+.rectangle-zoom-container {
+  display: none;
+}
+</style>

+ 4 - 1
RuoYi-Vue3/src/supermap-cesium-module/components/viewer/viewer.vue

@@ -3,6 +3,7 @@
     <SearchComponent v-if="viewerInstance" :viewer="viewerInstance" />
     <ScaleBar v-if="viewerInstance" :viewer="viewerInstance" />
     <ModelTransformPanel v-if="viewerInstance" :viewer="viewerInstance" />
+    <RectangleZoom v-if="viewerInstance" :viewer="viewerInstance" />
     <slot></slot>
   </div>
 </template>
@@ -13,6 +14,7 @@ import initViewer from "./viewer.js";
 import SearchComponent from "../search/search.vue";
 import ScaleBar from "../scale-bar/scale-bar.vue";
 import ModelTransformPanel from "../model-transform/model-transform-panel.vue";
+import RectangleZoom from "../scene/rectangle-zoom/index.js";
 // import {initViewer} from "../../../dist/vue-webgl2.min";
 
 export default {
@@ -20,7 +22,8 @@ export default {
   components: {
     SearchComponent,
     ScaleBar,
-    ModelTransformPanel
+    ModelTransformPanel,
+    RectangleZoom
   },
   props: {
     sceneUrl: {

+ 5 - 5
RuoYi-Vue3/src/supermap-cesium-module/config/views_config.js

@@ -207,6 +207,11 @@ export default [
                 component: "Sm3dVolumeRender",
                 imgSrc: "/img/componentsImg/volume.png",
                 name: "带时序的体元栅格",
+            },
+            {
+                component: "Sm3dOverviewMap",
+                imgSrc: "/img/componentsImg/eagle-eye-map.png",
+                name: "鹰眼"
             }
         ]
     },
@@ -247,11 +252,6 @@ export default [
                 imgSrc: "/img/componentsImg/particle.png",  
                 name: "粒子"
             },
-            {
-                component: "WeatherControl",  //组件名称
-                imgSrc: "/img/componentsImg/weather.png",  
-                name: "天气系统"
-            },
             
         ]
     },

+ 7 - 3
RuoYi-Vue3/src/supermap-cesium-module/js/common/camera.js

@@ -62,13 +62,17 @@ const flyByLayerName = function (type, id_name) {
             break;
         case "IMG":
             let img_layer;
-            if (typeof (id_name) === 'nunber') {
+            if (typeof (id_name) === 'number') {
                 img_layer = viewer.imageryLayers.get(id_name);
             } else {
                 let img_layers = viewer.imageryLayers._layers
                 for (let i = 0; i < img_layers.length; i++) {
-                    if (img_layers[i].imageryProvider.tablename && img_layers[i].imageryProvider.tablename === id_name) {
-                        img_layer = img_layers[i];
+                    const layer = img_layers[i];
+                    const provider = layer.imageryProvider;
+                    if ((provider.tablename && provider.tablename === id_name) || 
+                        (layer._loadedLayerName && layer._loadedLayerName === id_name)) {
+                        img_layer = layer;
+                        break;
                     }
                 };
             }

+ 532 - 79
RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js

@@ -1,11 +1,10 @@
 import { actions, storeDate } from '../store/store.js'   //局部状态管理
 
 // 添加s3m
-function addS3mLayers(scps, callback) {  //scps:[{ url, options:{name}]}  无返回值
+function addS3mLayers(scps, callback) {
     let promiseArray = [];
     try {
         if (scps) {
-            //加载scps
             scps.forEach(scp => {
                 promiseArray.push(
                     viewer.scene.addS3MTilesLayerByScp(scp.url, scp.options)
@@ -22,9 +21,8 @@ function addS3mLayers(scps, callback) {  //scps:[{ url, options:{name}]}  无返
     }
 };
 
-
 // 添加场景
-function addScene(url, options, callback) {  //无返回值options:{SceneToken,autoSetView}
+function addScene(url, options, callback) {
     if (options && options.SceneToken) {
         Cesium.Credential.CREDENTIAL = new Cesium.Credential(options.SceneToken);
     }
@@ -51,7 +49,7 @@ function addTerrainLayer(LayerURL, isSct) {
     try {
         const terrainProvider = new Cesium.CesiumTerrainProvider({
             url: LayerURL,
-            isSct: isSct, //地形服务源自SuperMap iServer发布时需设置isSct为true
+            isSct: isSct,
         });
         viewer.terrainProvider = terrainProvider;
         return terrainProvider;
@@ -65,27 +63,110 @@ function addTerrainLayer(LayerURL, isSct) {
     }
 };
 
-// 添加影像
-function addImageLayer(LayerURL) {    // 返回img图层layer
+// ======================= ✅ 已修复:万能自动坐标系转换 =======================
+function addImageLayer(LayerURL, layerName) {
     try {
+        let url = LayerURL;
+        let mapName = '';
+
+        // 清理URL参数,不手动解析投影
+        if (LayerURL.includes('prjCoordSys')) {
+            try {
+                const urlObj = new URL(LayerURL);
+                url = urlObj.origin + urlObj.pathname;
+            } catch (e) {
+                console.warn('解析URL失败:', e);
+            }
+        }
+
+        // 提取地图名称
+        if (url.includes('/maps/')) {
+            const mapsIndex = url.lastIndexOf('/maps/');
+            mapName = url.substring(mapsIndex + 6);
+        }
+
+        let options = {
+            url: url,
+        };
+
+        if (mapName) {
+            options.name = mapName;
+        }
+
+        // ✅ 核心:自动将任意坐标系 → 输出 WGS84
+        options.outputPrjCoordSys = {
+            epsgCode: 4326,
+            name: "WGS 84",
+            type: "EPSG",
+            coordinateSystemType: "GEODETIC"
+        };
+
+        // ✅ 不手动指定原投影,不硬编码范围,交给超图自动处理
         let layer = viewer.imageryLayers.addImageryProvider(
-            new Cesium.SuperMapImageryProvider({
-                url: LayerURL,
-            })
+            new Cesium.SuperMapImageryProvider(options)
         );
-        return layer
+
+        viewer.imageryLayers.raiseToTop(layer);
+        layer.show = true;
+        layer._loadedUrl = LayerURL;
+        layer._loadedLayerName = layerName || '';
+
+        // 自动定位到图层范围
+        checkLayerExtentAndAdjust(layer);
+
+        return layer;
     } catch (e) {
+        console.error('加载影像图层失败:', e);
         let widget = viewer.cesiumWidget;
         if (widget._showRenderLoopErrors) {
             let title = "渲染时发生错误,已停止渲染。";
             widget.showErrorPanel(title, undefined, e);
         }
     }
-
 };
 
+// 检查图层范围并自动定位
+function checkLayerExtentAndAdjust(layer) {
+    try {
+        const provider = layer.imageryProvider;
+        Cesium.when(provider.readyPromise, function () {
+            const rectangle = provider.rectangle;
+            if (rectangle) {
+                const west = Cesium.Math.toDegrees(rectangle.west);
+                const south = Cesium.Math.toDegrees(rectangle.south);
+                const east = Cesium.Math.toDegrees(rectangle.east);
+                const north = Cesium.Math.toDegrees(rectangle.north);
+                console.log('图层真实范围:', { west, south, east, north });
+
+                // 范围无效时飞到中国区域
+                const isInvalidExtent = (south < -90 || north > 90) ||
+                    (south > north) || (west > east);
+
+                if (isInvalidExtent) {
+                    console.warn('范围无效,自动定位到中国区域');
+                    flyToChinaRegion();
+                } else {
+                    // 有效范围 → 自动定位到图层
+                    viewer.flyTo(layer, { duration: 1.5 });
+                }
+            }
+        });
+    } catch (e) {
+        console.error('检查图层范围失败:', e);
+    }
+}
+
+// 定位到中国
+function flyToChinaRegion() {
+    viewer.scene.camera.flyTo({
+        destination: Cesium.Cartesian3.fromDegrees(104.0, 35.0, 4000000),
+        duration: 2,
+        orientation: { heading: 0, pitch: -60, roll: 0 }
+    });
+}
+
 // 添加mvt
-function addMvtLayer(LayerURL, name, callback) {    // 返回img图层layer
+function addMvtLayer(LayerURL, name, callback) {
     try {
         let mvtMap = viewer.scene.addVectorTilesMap({
             url: LayerURL,
@@ -101,11 +182,8 @@ function addMvtLayer(LayerURL, name, callback) {    // 返回img图层layer
                     (bounds.north + bounds.south) * 0.5,
                     10000
                 ),
-                duration:0,
-                orientation: {
-                    heading: 0,
-                    roll: 0
-                }
+                duration: 0,
+                orientation: { heading: 0, roll: 0 }
             });
             actions.setChangeLayers();
             callback(mvtMap)
@@ -118,31 +196,418 @@ function addMvtLayer(LayerURL, name, callback) {    // 返回img图层layer
             widget.showErrorPanel(title, undefined, e);
         }
     }
-
 };
 
-// 加载GeoJson图层 已修复:面填充不被地形遮挡
+// 加载GeoJson图层
 function addGeoJsonLayer(url, name, callback) {
     try {
+        // 判断是否为超图REST数据服务
+        if (url.includes('/iserver/services/') && url.includes('/rest/data/datasources')) {
+            loadSuperMapRestDataService(url, name, callback);
+            return;
+        }
+
         const loadPromise = Cesium.GeoJsonDataSource.load(url, {
             stroke: Cesium.Color.fromCssColorString('#0055FF').withAlpha(1),
             fill: Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.4),
             strokeWidth: 3,
             clampToGround: true
         });
-        
-        Cesium.when(loadPromise, function(dataSource) {
+
+        Cesium.when(loadPromise, function (dataSource) {
             viewer.dataSources.add(dataSource);
-            let entities = dataSource.entities.values;
+            processGeoJsonEntities(dataSource, name);
+            viewer.flyTo(dataSource, { duration: 2 });
+            actions.setChangeLayers();
+            if (callback) callback(dataSource);
+        }, function (error) {
+            console.error("加载GeoJSON失败:", error);
+        });
+    } catch (e) {
+        console.error("加载GeoJSON异常:", e);
+    }
+};
+
+// 从超图REST服务获取要素并转换为标准GeoJSON
+function fetchFeaturesFromSuperMap(url, timeout) {
+    const timeoutPromise = new Promise((_, reject) => {
+        setTimeout(() => reject(new Error('请求超时')), timeout);
+    });
+    
+    return Promise.race([
+        fetch(url, { timeout: timeout }),
+        timeoutPromise
+    ])
+    .then(response => {
+        if (!response.ok) {
+            throw new Error(`HTTP错误: ${response.status}`);
+        }
+        return response.text();
+    })
+    .then(text => {
+        try {
+            return JSON.parse(text);
+        } catch (e) {
+            console.error('JSON解析失败:', text.substring(0, 200));
+            throw new Error('服务返回的不是有效的JSON数据');
+        }
+    })
+    .then(superMapData => {
+        // 超图返回的格式是: { features: [...], totalCount: N, ... }
+        // 需要转换为标准GeoJSON格式: { type: 'FeatureCollection', features: [...] }
+        
+        // 调试:查看完整返回数据结构
+        console.log('超图服务返回的数据:', JSON.stringify(superMapData, null, 2).substring(0, 2000));
+        console.log('数据类型:', typeof superMapData);
+        console.log('数据键:', Object.keys(superMapData || {}));
+        
+        let features = [];
+        
+        // 尝试多种可能的数据结构
+        if (superMapData.features && Array.isArray(superMapData.features)) {
+            features = superMapData.features;
+            console.log('从 features 属性获取要素');
+        } else if (superMapData.recordset && Array.isArray(superMapData.recordset)) {
+            features = superMapData.recordset;
+            console.log('从 recordset 属性获取要素');
+        } else if (superMapData.result && superMapData.result.features && Array.isArray(superMapData.result.features)) {
+            features = superMapData.result.features;
+            console.log('从 result.features 属性获取要素');
+        } else if (Array.isArray(superMapData)) {
+            features = superMapData;
+            console.log('数据本身是数组');
+        } else {
+            console.warn('未找到要素数据,尝试查找其他属性');
+            // 遍历所有属性查找数组
+            for (const key of Object.keys(superMapData || {})) {
+                if (Array.isArray(superMapData[key])) {
+                    console.log(`发现数组属性 ${key},长度:`, superMapData[key].length);
+                    if (superMapData[key].length > 0 && superMapData[key][0].geometry) {
+                        features = superMapData[key];
+                        console.log(`使用 ${key} 作为要素数组`);
+                        break;
+                    }
+                }
+            }
+        }
+        
+        // 转换每个要素的坐标格式(超图可能返回平面坐标,需要转换为经纬度)
+        console.log('原始要素数量:', features.length);
+        if (features.length > 0) {
+            console.log('第一个要素示例:', JSON.stringify(features[0], null, 2).substring(0, 1000));
+        }
+        
+        features = features.map((feature, index) => {
+            if (!feature) {
+                console.warn(`要素 ${index} 为空`);
+                return null;
+            }
+            if (!feature.geometry) {
+                console.warn(`要素 ${index} 没有geometry`);
+                return null;
+            }
+            if (!feature.geometry.coordinates) {
+                console.warn(`要素 ${index} 没有coordinates`);
+                return null;
+            }
+            
+            // 尝试转换坐标
+            try {
+                feature.geometry.coordinates = convertCoordinates(feature.geometry.coordinates);
+                return feature;
+            } catch (e) {
+                console.error(`要素 ${index} 坐标转换失败:`, e);
+                return null;
+            }
+        }).filter(Boolean);
+        
+        console.log('转换后的要素数量:', features.length);
+        
+        // 返回标准GeoJSON FeatureCollection
+        return {
+            type: 'FeatureCollection',
+            features: features
+        };
+    });
+}
+
+// 坐标转换:尝试将平面坐标转换为经纬度
+function convertCoordinates(coordinates) {
+    if (!Array.isArray(coordinates)) {
+        return coordinates;
+    }
+    
+    // 检查是否是经纬度范围(-180到180,-90到90)
+    const isLatLng = (coord) => {
+        return Math.abs(coord[0]) <= 180 && Math.abs(coord[1]) <= 90;
+    };
+    
+    // 检查是否是Web Mercator或其他大坐标值
+    const isLargeCoord = (coord) => {
+        return Math.abs(coord[0]) > 10000 || Math.abs(coord[1]) > 10000;
+    };
+    
+    // 处理点坐标
+    if (coordinates.length >= 2 && typeof coordinates[0] === 'number') {
+        if (isLargeCoord(coordinates)) {
+            console.log('检测到大坐标值,尝试Web Mercator转WGS84:', coordinates);
+            try {
+                const cartesian3 = Cesium.Cartesian3.fromArray(coordinates);
+                const cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
+                return [
+                    Cesium.Math.toDegrees(cartographic.longitude),
+                    Cesium.Math.toDegrees(cartographic.latitude),
+                    cartographic.height || 0
+                ];
+            } catch (e) {
+                console.warn('坐标转换失败:', e);
+                return coordinates;
+            }
+        }
+        return coordinates;
+    }
+    
+    // 处理线坐标(二维数组)
+    if (coordinates.length > 0 && Array.isArray(coordinates[0])) {
+        return coordinates.map(coord => convertCoordinates(coord));
+    }
+    
+    // 处理多边形坐标(三维数组)
+    if (coordinates.length > 0 && Array.isArray(coordinates[0][0])) {
+        return coordinates.map(ring => ring.map(coord => convertCoordinates(coord)));
+    }
+    
+    return coordinates;
+}
+
+// 加载超图REST数据服务
+function loadSuperMapRestDataService(baseUrl, name, callback) {
+    console.log('开始加载超图REST数据服务:', baseUrl);
+    
+    // 设置超时时间
+    const timeout = 30000; // 30秒
+    
+    // 创建超时Promise
+    const timeoutPromise = new Promise((_, reject) => {
+        setTimeout(() => reject(new Error('请求超时')), timeout);
+    });
+    
+    // 修正URL:添加.json后缀(如果缺少的话)
+    let datasourcesUrl = baseUrl;
+    if (!datasourcesUrl.endsWith('.json')) {
+        datasourcesUrl = `${baseUrl}.json`;
+    }
+    console.log('修正后的数据源URL:', datasourcesUrl);
+    
+    // 获取数据源列表
+    Promise.race([
+        fetch(datasourcesUrl, { timeout: timeout }),
+        timeoutPromise
+    ])
+    .then(response => {
+        if (!response.ok) {
+            // 如果.json后缀失败,尝试不加后缀
+            if (datasourcesUrl !== baseUrl) {
+                console.log(`HTTP错误: ${response.status},尝试不加.json后缀`);
+                return fetch(baseUrl, { timeout: timeout });
+            }
+            throw new Error(`HTTP错误: ${response.status}`);
+        }
+        return response;
+    })
+    .then(response => {
+        if (!response.ok) {
+            throw new Error(`HTTP错误: ${response.status}`);
+        }
+        return response.text();
+    })
+    .then(text => {
+        // 尝试解析JSON
+        try {
+            return JSON.parse(text);
+        } catch (e) {
+            // 如果JSON解析失败,检查是否是HTML错误页面
+            console.error('JSON解析失败,响应内容:', text.substring(0, 200));
+            throw new Error('服务返回的不是有效的JSON数据');
+        }
+    })
+    .then(dataSources => {
+        console.log('获取到数据源列表:', dataSources);
+        
+        // 处理超图REST数据服务返回的格式
+        // 超图返回的格式是: { datasourceNames: [...], childUriList: [...], datasourceCount: N }
+        let dataSourceNames = [];
+        if (Array.isArray(dataSources)) {
+            // 如果返回的是数组,直接使用
+            dataSourceNames = dataSources.map(ds => ds.name || ds);
+        } else if (dataSources && dataSources.datasourceNames && Array.isArray(dataSources.datasourceNames)) {
+            // 超图REST数据服务格式
+            dataSourceNames = dataSources.datasourceNames;
+        }
+        
+        if (!dataSourceNames || dataSourceNames.length === 0) {
+            console.warn('未找到数据源');
+            if (callback) callback(null);
+            return;
+        }
+
+        // 遍历所有数据源
+        const loadPromises = [];
+        
+        dataSourceNames.forEach(dataSourceName => {
+            // 确保数据源名称是字符串
+            const name = typeof dataSourceName === 'object' ? (dataSourceName.name || dataSourceName) : dataSourceName;
+            let datasetsUrl = `${baseUrl}/${encodeURIComponent(name)}/datasets`;
+            if (!datasetsUrl.endsWith('.json')) {
+                datasetsUrl += '.json';
+            }
             
-            let boundingSphere = new Cesium.BoundingSphere();
-            let entitiesLength = entities.length;
+            // 获取数据集列表
+            const datasetPromise = Promise.race([
+                fetch(datasetsUrl, { timeout: timeout }),
+                timeoutPromise
+            ])
+            .then(response => {
+                if (!response.ok) {
+                    if (datasetsUrl !== `${baseUrl}/${encodeURIComponent(name)}/datasets`) {
+                        return fetch(`${baseUrl}/${encodeURIComponent(name)}/datasets`, { timeout: timeout });
+                    }
+                    throw new Error(`HTTP错误: ${response.status}`);
+                }
+                return response;
+            })
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error(`HTTP错误: ${response.status}`);
+                }
+                return response.text();
+            })
+            .then(text => {
+                try {
+                    return JSON.parse(text);
+                } catch (e) {
+                    console.error(`数据集列表JSON解析失败 (${name}):`, text.substring(0, 200));
+                    throw new Error('数据集列表不是有效的JSON');
+                }
+            })
+            .then(datasets => {
+                console.log(`数据源 ${name} 的数据集列表:`, datasets);
+                
+                // 处理数据集格式
+                let datasetNames = [];
+                if (Array.isArray(datasets)) {
+                    datasetNames = datasets.map(ds => ds.name || ds);
+                } else if (datasets && datasets.datasetNames && Array.isArray(datasets.datasetNames)) {
+                    datasetNames = datasets.datasetNames;
+                }
+                
+                if (!datasetNames || datasetNames.length === 0) {
+                    return Promise.resolve([]);
+                }
+
+                // 加载每个数据集的要素(限制同时加载的数量)
+                const featurePromises = [];
+                const maxConcurrent = 3; // 最大并发数
+                
+                for (let i = 0; i < datasetNames.length; i += maxConcurrent) {
+                    const batch = datasetNames.slice(i, i + maxConcurrent);
+                    const batchPromises = batch.map(dsName => {
+                        // 确保数据集名称是字符串
+                        const datasetName = typeof dsName === 'object' ? (dsName.name || dsName) : dsName;
+                        let featuresUrl = `${baseUrl}/${encodeURIComponent(name)}/datasets/${encodeURIComponent(datasetName)}/features.json?returnContent=true`;
+                        console.log('加载要素URL:', featuresUrl);
+                        
+                        // 先获取超图格式的要素数据,转换为标准GeoJSON后再加载
+                        return fetchFeaturesFromSuperMap(featuresUrl, timeout)
+                            .then(geoJsonData => {
+                                if (!geoJsonData) {
+                                    throw new Error('未获取到有效要素数据');
+                                }
+                                // 使用Cesium加载GeoJSON
+                                return Cesium.GeoJsonDataSource.load(geoJsonData, {
+                                    stroke: Cesium.Color.fromCssColorString('#0055FF').withAlpha(1),
+                                    fill: Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.4),
+                                    strokeWidth: 3,
+                                    clampToGround: true
+                                });
+                            })
+                            .then(geoJsonDataSource => {
+                                viewer.dataSources.add(geoJsonDataSource);
+                                processGeoJsonEntities(geoJsonDataSource, `${name}-${datasetName}`);
+                                return geoJsonDataSource;
+                            })
+                            .catch(error => {
+                                console.error(`加载数据集 ${datasetName} 失败:`, error);
+                                return null;
+                            });
+                    });
+                    
+                    featurePromises.push(Promise.all(batchPromises));
+                }
+                
+                return Promise.all(featurePromises).then(results => results.flat());
+            })
+            .catch(error => {
+                console.error(`获取数据集列表失败 (${name}):`, error);
+                return [];
+            });
             
-            for (let i = 0; i < entitiesLength; i++) {
+            loadPromises.push(datasetPromise);
+        });
+
+        Promise.all(loadPromises)
+            .then(results => {
+                const allDataSources = results.flat().filter(Boolean);
+                console.log('所有加载的GeoJSON数据源:', allDataSources);
+                
+                if (allDataSources.length > 0) {
+                    viewer.flyTo(allDataSources[0], { duration: 2 });
+                }
+                
+                actions.setChangeLayers();
+                if (callback) callback(allDataSources.length > 0 ? allDataSources : null);
+            })
+            .catch(error => {
+                console.error('加载超图REST数据服务失败:', error);
+                if (callback) callback(null);
+            });
+    })
+    .catch(error => {
+        console.error('获取数据源列表失败:', error);
+        if (callback) callback(null);
+    });
+}
+
+// 处理GeoJSON实体(异步方式,避免阻塞主线程)
+function processGeoJsonEntities(dataSource, name) {
+    try {
+        let entities = dataSource.entities.values;
+        let entitiesLength = entities.length;
+        
+        // 限制处理的实体数量,防止大量数据导致卡死
+        const maxEntities = 5000;
+        if (entitiesLength > maxEntities) {
+            console.warn(`实体数量过多(${entitiesLength}),仅处理前${maxEntities}个`);
+            entitiesLength = maxEntities;
+        }
+
+        // 使用异步批量处理
+        const batchSize = 50; // 每批处理50个实体
+        let currentIndex = 0;
+
+        const processBatch = () => {
+            if (currentIndex >= entitiesLength) {
+                return; // 处理完成
+            }
+
+            const endIndex = Math.min(currentIndex + batchSize, entitiesLength);
+            
+            for (let i = currentIndex; i < endIndex; i++) {
                 let entity = entities[i];
-                entity.name = name || 'GeoJSON';
+                if (!entity) continue;
                 
-                // 面配置 完全贴地 不被遮挡
+                entity.name = name || 'GeoJSON';
+
                 if (entity.polygon) {
                     entity.polygon.fill = true;
                     entity.polygon.outline = true;
@@ -155,16 +620,14 @@ function addGeoJsonLayer(url, name, callback) {
                     entity.polygon.outlineColor = Cesium.Color.fromCssColorString('#0055FF');
                     entity.polygon.outlineWidth = 4;
                 }
-                
-                // 线配置
+
                 if (entity.polyline) {
                     entity.polyline.clampToGround = true;
                     entity.polyline.classificationType = Cesium.ClassificationType.TERRAIN;
                     entity.polyline.material = Cesium.Color.fromCssColorString('#0055FF');
                     entity.polyline.width = 4;
                 }
-                
-                // 点配置
+
                 if (entity.point) {
                     entity.point.clampToGround = true;
                     entity.point.color = Cesium.Color.fromCssColorString('#FF0000');
@@ -174,8 +637,7 @@ function addGeoJsonLayer(url, name, callback) {
                     entity.point.disableDepthTestDistance = Number.POSITIVE_INFINITY;
                     entity.isGeoJsonPoint = true;
                 }
-                
-                // 图标转点
+
                 if (entity.billboard) {
                     entity.billboard.show = false;
                     entity.point = {
@@ -188,41 +650,31 @@ function addGeoJsonLayer(url, name, callback) {
                     };
                     entity.isGeoJsonPoint = true;
                 }
-                
-                // 计算视图范围
-                if (entity.boundingSphere) {
-                    if (i === 0) {
-                        boundingSphere = entity.boundingSphere;
-                    } else {
-                        boundingSphere = Cesium.BoundingSphere.union(boundingSphere, entity.boundingSphere);
-                    }
-                }
             }
-            
-            viewer.flyTo(dataSource, { duration: 2 });
-            
-            actions.setChangeLayers();
-            if (callback) callback(dataSource);
-        }, function(error) {
-            console.error("加载GeoJSON失败:", error);
-        });
-    } catch (e) {
-        console.error("加载GeoJSON异常:", e);
+
+            currentIndex = endIndex;
+            // 使用 requestAnimationFrame 让出主线程给渲染
+            requestAnimationFrame(processBatch);
+        };
+
+        // 开始处理
+        processBatch();
+    } catch (error) {
+        console.error('处理GeoJSON实体失败:', error);
     }
-};
+}
 
-// 加载s3m和场景函数
+// 加载s3m和场景
 function promiseWhen(promiseArray, callback, type) {
     Cesium.when.all(
         promiseArray,
         function (layers) {
-            console.log("layers:",layers)
             storeDate.layers = viewer.scene.layers.layerQueue;
             actions.setChangeLayers();
             callback(layers, type);
             storeDate.layers.forEach((s3mlayer) => {
                 if (!s3mlayer.visibleDistanceMax || s3mlayer.visibleDistanceMax > 12000) {
-                    s3mlayer.visibleDistanceMax = 12000   //设置模型最可见距离
+                    s3mlayer.visibleDistanceMax = 12000
                 }
             })
         },
@@ -236,7 +688,7 @@ function promiseWhen(promiseArray, callback, type) {
     );
 };
 
-//   检验url地址
+// 检验url
 function checkURL(url) {
     if (url === null || url === "") {
         return false;
@@ -248,18 +700,14 @@ function checkURL(url) {
     return true
 };
 
-//   删除图层
+// 删除图层
 function layersDelete(type, id_name, callback) {
     switch (type) {
         case "SCENE":
-            console.log('开始删除SCENE图层, id_name:', id_name);
             if (viewer.scene.layers && viewer.scene.layers.layerQueue) {
                 const layers = viewer.scene.layers.layerQueue;
-                console.log('场景中的图层数量:', layers.length);
                 for (let i = layers.length - 1; i >= 0; i--) {
-                    const layer = layers[i];
-                    console.log('删除图层:', i, layer.name);
-                    viewer.scene.layers.remove(layer);
+                    viewer.scene.layers.remove(layers[i]);
                 }
             }
             actions.setChangeLayers();
@@ -271,16 +719,29 @@ function layersDelete(type, id_name, callback) {
             if (callback) callback();
             break;
         case "IMG":
+        case "IMAGE":
             let img_layer;
-            if (typeof (id_name) === 'nunber') {
+            if (typeof (id_name) === 'number') {
                 img_layer = viewer.imageryLayers.get(id_name);
             } else {
-                let img_layers = viewer.imageryLayers._layers
+                let img_layers = viewer.imageryLayers._layers;
                 for (let i = 0; i < img_layers.length; i++) {
-                    if (img_layers[i].imageryProvider.tablename && img_layers[i].imageryProvider.tablename === id_name) {
-                        img_layer = img_layers[i];
+                    const layer = img_layers[i];
+                    const provider = layer.imageryProvider;
+                    let matched = false;
+                    if (provider.tablename && provider.tablename === id_name) matched = true;
+                    if (!matched && layer._loadedLayerName && (layer._loadedLayerName === id_name || layer._loadedLayerName.includes(id_name) || id_name.includes(layer._loadedLayerName))) matched = true;
+                    if (!matched && layer._loadedUrl && (layer._loadedUrl === id_name || layer._loadedUrl.includes(id_name) || id_name.includes(layer._loadedUrl))) matched = true;
+                    if (!matched && provider.url) {
+                        const urlStr = typeof provider.url === 'string' ? provider.url : JSON.stringify(provider.url);
+                        if (urlStr.includes(id_name) || id_name.includes(urlStr)) matched = true;
                     }
-                };
+                    if (!matched && provider.name && (provider.name === id_name || provider.name.includes(id_name))) matched = true;
+                    if (matched) {
+                        img_layer = layer;
+                        break;
+                    }
+                }
             }
             if (img_layer) {
                 viewer.imageryLayers.remove(img_layer);
@@ -300,18 +761,14 @@ function layersDelete(type, id_name, callback) {
             break;
         case "GEOJSON":
         case "SHP":
-            console.log('开始删除GEOJSON/SHP图层, id_name:', id_name);
             if (viewer.dataSources) {
                 const dataSources = viewer.dataSources._dataSources;
-                console.log('当前数据源数量:', dataSources.length);
                 let removed = false;
                 for (let i = dataSources.length - 1; i >= 0; i--) {
                     const ds = dataSources[i];
-                    console.log('检查数据源:', i, 'name:', ds.name, '_name:', ds._name);
-                    if (ds.name === id_name || ds._name === id_name || 
-                        (ds.name && ds.name.includes(id_name)) || 
+                    if (ds.name === id_name || ds._name === id_name ||
+                        (ds.name && ds.name.includes(id_name)) ||
                         (ds._name && ds._name.includes(id_name))) {
-                        console.log('匹配到数据源,删除:', ds.name);
                         viewer.dataSources.remove(ds);
                         removed = true;
                         break;
@@ -320,7 +777,6 @@ function layersDelete(type, id_name, callback) {
                     for (let j = 0; j < entities.length; j++) {
                         const entity = entities[j];
                         if (entity.name === id_name || (entity.name && entity.name.includes(id_name))) {
-                            console.log('通过entity匹配到数据源,删除:', ds.name);
                             viewer.dataSources.remove(ds);
                             removed = true;
                             break;
@@ -328,9 +784,6 @@ function layersDelete(type, id_name, callback) {
                     }
                     if (removed) break;
                 }
-                if (!removed) {
-                    console.warn('未找到匹配的数据源:', id_name);
-                }
             }
             actions.setChangeLayers();
             if (callback) callback();

+ 15 - 3
RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue

@@ -254,6 +254,13 @@
           </div>
         </el-dialog>
       </teleport>
+      
+      <!-- 鹰眼组件 -->
+      <Sm3dOverviewMap 
+        v-if="view === 'Sm3dOverviewMap'" 
+        :visible="view === 'Sm3dOverviewMap'"
+        @close="view = ''"
+      ></Sm3dOverviewMap>
     </el-main>
   </el-container>
 </template>
@@ -1353,8 +1360,9 @@ export default {
             });
             break;
           case 'IMG':
-            let imgLayer = layerManagement.addImageLayer(url);
-            console.log('IMG加载成功:', imgLayer);
+          case 'IMAGE':
+            let imgLayer = layerManagement.addImageLayer(url, layerName);
+            console.log('IMG/IMAGE加载成功:', imgLayer);
             markAsLoaded();
             if (flyTo) {
               camera.flyByLayerName('IMG', layerName);
@@ -1369,12 +1377,16 @@ export default {
             layerManagement.addMvtLayer(url, layerName, (mvtlayer) => {
               console.log('MVT加载成功:', mvtlayer);
               markAsLoaded();
+              if (flyTo) {
+                viewer.flyTo(mvtlayer, { duration: 2 });
+              }
             });
             break;
           case 'SHP':
           case 'GEOJSON':
+          case 'VECTOR_TILE':
             layerManagement.addGeoJsonLayer(url, layerName, (geoJsonLayer) => {
-              console.log('SHP/GEOJSON加载成功:', geoJsonLayer);
+              console.log('SHP/GEOJSON/VECTOR_TILE加载成功:', geoJsonLayer);
               markAsLoaded();
               if (flyTo) {
                 viewer.flyTo(geoJsonLayer, { duration: 2 });

+ 15 - 15
RuoYi-Vue3/src/views/front/content/ShuiliGongcheng.vue

@@ -141,9 +141,9 @@
             <el-select v-model="filterServiceType" style="width: 150px;" placeholder="请选择服务类型" clearable @change="handleServiceFilterChange">
               <el-option label="场景" value="SCENE" />
               <el-option label="S3M" value="S3M" />
-              <el-option label="影像" value="IMAGE" />
-              <el-option label="地形" value="TERRAIN" />
-              <el-option label="矢量瓦片" value="VECTOR_TILE" />
+              <el-option label="地图服务" value="IMAGE" />
+              <el-option label="地形服务" value="TERRAIN" />
+              <el-option label="矢量要素" value="VECTOR_TILE" />
               <el-option label="SHP数据" value="SHP" />
             </el-select>
           </div>
@@ -370,9 +370,9 @@
             <el-select v-model="serviceFormData.type" placeholder="请选择服务类型" style="width: 100%;" @change="handleServiceTypeChange">
               <el-option label="场景" value="SCENE" />
               <el-option label="S3M" value="S3M" />
-              <el-option label="影像" value="IMAGE" />
-              <el-option label="地形" value="TERRAIN" />
-              <el-option label="矢量瓦片" value="VECTOR_TILE" />
+              <el-option label="地图服务" value="IMAGE" />
+              <el-option label="地形服务" value="TERRAIN" />
+              <el-option label="矢量要素" value="VECTOR_TILE" />
               <el-option label="SHP数据" value="SHP" />
             </el-select>
           </el-form-item>
@@ -431,9 +431,9 @@
             <el-select v-model="editServiceFormData.type" placeholder="请选择服务类型" style="width: 100%;" @change="handleEditServiceTypeChange">
               <el-option label="场景" value="SCENE" />
               <el-option label="S3M" value="S3M" />
-              <el-option label="影像" value="IMAGE" />
-              <el-option label="地形" value="TERRAIN" />
-              <el-option label="矢量瓦片" value="VECTOR_TILE" />
+              <el-option label="地图服务" value="IMAGE" />
+              <el-option label="地形服务" value="TERRAIN" />
+              <el-option label="矢量要素" value="VECTOR_TILE" />
               <el-option label="SHP数据" value="SHP" />
             </el-select>
           </el-form-item>
@@ -953,16 +953,16 @@ const getServiceTypeName = (type) => {
   const typeMapping = {
     'SCENE': '场景',
     'S3M': 'S3M',
-    'IMAGE': '影像',
-    'TERRAIN': '地形',
-    'VECTOR_TILE': '矢量瓦片',
+    'IMAGE': '地图服务',
+    'TERRAIN': '地形服务',
+    'VECTOR_TILE': '矢量要素',
     'SHP': 'SHP数据',
     'GEOJSON': 'GeoJSON',
     '2-1': '场景',
     '2-2': 'S3M',
-    '2-3': '影像',
-    '2-4': '地形',
-    '2-5': '矢量瓦片'
+    '2-3': '地图服务',
+    '2-4': '地形服务',
+    '2-5': '矢量要素'
   }
   return typeMapping[type] || type || '未知类型'
 }

BIN
ruoyi-admin/target/classes/com/ruoyi/RuoYiApplication.class


BIN
ruoyi-admin/target/classes/com/ruoyi/RuoYiServletInitializer.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/cesium/CesiumMapConfigController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CaptchaController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/common/CommonController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/CacheController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/ServerController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysLogininforController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysOperlogController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/monitor/SysUserOnlineController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysConfigController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDeptController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDictDataController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysDictTypeController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysIndexController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysLoginController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysMenuController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysNoticeController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysPostController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysProfileController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysRegisterController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysRoleController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/system/SysUserController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/TestController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/tool/UserEntity.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedModelController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedServiceController.class


BIN
ruoyi-admin/target/classes/com/ruoyi/web/core/config/SwaggerConfig.class


BIN
ruoyi-admin/uploads/models/2026/05/07/场景1_20260507165507A004.glb


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7.geojson


BIN
ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.dbf


+ 1 - 0
ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.prj

@@ -0,0 +1 @@
+GEOGCS["GCS_China_Geodetic_Coordinate_System_2000",DATUM["D_China_2000",SPHEROID["CGCS2000",6378137.0,298.257222101004]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]

BIN
ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.shp


BIN
ruoyi-admin/uploads/models/geojson/3f69dd6d8af944d9a7cad072c90070c7/水闸.shx


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59.geojson


BIN
ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.dbf


+ 1 - 0
ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.prj

@@ -0,0 +1 @@
+GEOGCS["GCS_China_Geodetic_Coordinate_System_2000",DATUM["D_China_2000",SPHEROID["CGCS2000",6378137.0,298.257222101004]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]

BIN
ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.shp


BIN
ruoyi-admin/uploads/models/geojson/5121c72b9172421aaa969bcd2b39ee59/河道管理范围.shx


+ 1 - 0
ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281.geojson

@@ -0,0 +1 @@
+{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[119.2913907,25.700352300000098]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.24011109999996,25.610555600000094]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.5144722,25.767583300000098]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.2403056,25.6120832999999]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.4936944,25.7410000000001]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.1639167,25.7015278]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.2913907,25.700352300000098]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.1497778,25.6423889000001]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.1947222,25.5806389000001]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[119.550211111111,25.7431916666667]},"properties":{}}]}

BIN
ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.dbf


+ 1 - 0
ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.prj

@@ -0,0 +1 @@
+GEOGCS["GCS_China_Geodetic_Coordinate_System_2000",DATUM["D_China_2000",SPHEROID["CGCS2000",6378137.0,298.257222101004]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]

BIN
ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.shp


BIN
ruoyi-admin/uploads/models/geojson/b49f1a0b935d4164b940c1433b10a281/水电站.shx


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210.geojson


BIN
ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.dbf


+ 1 - 0
ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.prj

@@ -0,0 +1 @@
+GEOGCS["GCS_China_Geodetic_Coordinate_System_2000",DATUM["D_China_2000",SPHEROID["CGCS2000",6378137.0,298.257222101004]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]

BIN
ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.shp


BIN
ruoyi-admin/uploads/models/geojson/f8a25ad6f6604101b93e2a40a6050210/水库大坝.shx


BIN
ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$ColumnType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel$Type.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/annotation/Excel.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/config/RuoYiConfig.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/CacheConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/Constants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/GenConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/HttpStatus.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants$Status.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/ScheduleConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/constant/UserConstants.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController$1.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/controller/BaseController.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/AjaxResult.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/BaseEntity.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/R.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeEntity.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/TreeSelect.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDept.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictData.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysDictType.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysMenu.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysRole.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/entity/SysUser.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginBody.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/LoginUser.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/domain/model/RegisterBody.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/page/PageDomain.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/page/TableDataInfo.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/page/TableSupport.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/redis/RedisCache.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/text/CharsetKit.class


BIN
ruoyi-common/target/classes/com/ruoyi/common/core/text/Convert.class


Некоторые файлы не были показаны из-за большого количества измененных файлов