فهرست منبع

模型管理页自定义服务添加增加SHP数据上传

WQQ 1 ماه پیش
والد
کامیت
bc873b0489
61فایلهای تغییر یافته به همراه2055 افزوده شده و 36 حذف شده
  1. 3 12
      RuoYi-Vue3/index.html
  2. 32 0
      RuoYi-Vue3/src/api/cesium/geojson.js
  3. 150 4
      RuoYi-Vue3/src/supermap-cesium-module/components/custom-service/custom-service.vue
  4. 288 6
      RuoYi-Vue3/src/supermap-cesium-module/components/layer/custom-service/custom-service.vue
  5. 11 0
      RuoYi-Vue3/src/supermap-cesium-module/components/scale-bar/scale-bar.vue
  6. 34 1
      RuoYi-Vue3/src/supermap-cesium-module/components/viewer/viewer.js
  7. 69 2
      RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js
  8. 10 0
      RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue
  9. 213 7
      RuoYi-Vue3/src/views/front/content/ShuiliGongcheng.vue
  10. 6 1
      RuoYi-Vue3/vite.config.js
  11. 709 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/cesium/CesiumGeojsonController.java
  12. 45 0
      ruoyi-admin/src/main/resources/shp2geojson.py
  13. 16 0
      ruoyi-admin/src/main/resources/sql/cesium_geojson.sql
  14. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/cesium/CesiumGeojsonController.class
  15. BIN
      ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedModelController.class
  16. 45 0
      ruoyi-admin/target/classes/shp2geojson.py
  17. 16 0
      ruoyi-admin/target/classes/sql/cesium_geojson.sql
  18. 0 3
      ruoyi-admin/target/maven-archiver/pom.properties
  19. 1 0
      ruoyi-admin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  20. 1 0
      ruoyi-admin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  21. BIN
      ruoyi-admin/target/ruoyi-admin.jar
  22. BIN
      ruoyi-admin/target/ruoyi-admin.jar.original
  23. 0 0
      ruoyi-admin/uploads/models/geojson/28bde72dd24946448f8607da98e9b137.geojson
  24. BIN
      ruoyi-admin/uploads/models/geojson/28bde72dd24946448f8607da98e9b137/河道管理范围.dbf
  25. 1 0
      ruoyi-admin/uploads/models/geojson/28bde72dd24946448f8607da98e9b137/河道管理范围.prj
  26. BIN
      ruoyi-admin/uploads/models/geojson/28bde72dd24946448f8607da98e9b137/河道管理范围.shp
  27. BIN
      ruoyi-admin/uploads/models/geojson/28bde72dd24946448f8607da98e9b137/河道管理范围.shx
  28. 0 0
      ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11.geojson
  29. BIN
      ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11/水闸.dbf
  30. 1 0
      ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11/水闸.prj
  31. BIN
      ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11/水闸.shp
  32. BIN
      ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11/水闸.shx
  33. 0 0
      ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c.geojson
  34. BIN
      ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c/水库.dbf
  35. 1 0
      ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c/水库.prj
  36. BIN
      ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c/水库.shp
  37. BIN
      ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c/水库.shx
  38. 1 0
      ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd.geojson
  39. BIN
      ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd/水闸.dbf
  40. 1 0
      ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd/水闸.prj
  41. BIN
      ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd/水闸.shp
  42. BIN
      ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd/水闸.shx
  43. 0 0
      ruoyi-admin/uploads/models/geojson/fd35f1516d9144a3b97a78f55f06f219.geojson
  44. BIN
      ruoyi-admin/uploads/models/geojson/fd35f1516d9144a3b97a78f55f06f219.shp
  45. BIN
      ruoyi-common/target/ruoyi-common-3.9.1.jar
  46. BIN
      ruoyi-framework/target/ruoyi-framework-3.9.1.jar
  47. BIN
      ruoyi-generator/target/ruoyi-generator-3.9.1.jar
  48. BIN
      ruoyi-quartz/target/ruoyi-quartz-3.9.1.jar
  49. 108 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/CesiumGeojson.java
  50. 20 0
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/CesiumGeojsonMapper.java
  51. 19 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/ICesiumGeojsonService.java
  52. 56 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CesiumGeojsonServiceImpl.java
  53. 95 0
      ruoyi-system/src/main/resources/mapper/system/CesiumGeojsonMapper.xml
  54. BIN
      ruoyi-system/target/classes/com/ruoyi/system/domain/CesiumGeojson.class
  55. BIN
      ruoyi-system/target/classes/com/ruoyi/system/mapper/CesiumGeojsonMapper.class
  56. BIN
      ruoyi-system/target/classes/com/ruoyi/system/service/ICesiumGeojsonService.class
  57. BIN
      ruoyi-system/target/classes/com/ruoyi/system/service/impl/CesiumGeojsonServiceImpl.class
  58. 95 0
      ruoyi-system/target/classes/mapper/system/CesiumGeojsonMapper.xml
  59. 4 0
      ruoyi-system/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  60. 4 0
      ruoyi-system/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  61. BIN
      ruoyi-system/target/ruoyi-system-3.9.1.jar

+ 3 - 12
RuoYi-Vue3/index.html

@@ -211,19 +211,10 @@
   </div>
   <!-- 引入 Cesium -->
   <script src="/Cesium/Cesium.js"></script>
-  <!-- 引入天地图扩展包及其依赖 -->
-  <script src="https://api.tianditu.gov.cn/cdn/plugins/cesium/long.min.js"></script>
-  <script src="https://api.tianditu.gov.cn/cdn/plugins/cesium/bytebuffer.min.js"></script>
-  <script src="https://api.tianditu.gov.cn/cdn/plugins/cesium/protobuf.min.js"></script>
-  <script src="https://api.tianditu.gov.cn/cdn/plugins/cesium/Cesium_ext_min.js"></script>
   <script>
-    console.log('=== 天地图扩展包加载检查 ===');
-    console.log('Cesium.TdtPlug:', typeof Cesium.TdtPlug);
-    console.log('Cesium.GeoGlobe:', typeof Cesium.GeoGlobe);
-    console.log('Cesium.GeoWTFS:', typeof Cesium.GeoWTFS);
-    if (typeof Cesium.TdtPlug !== 'undefined') {
-      console.log('TdtPlug 对象:', Cesium.TdtPlug);
-    }
+    console.log('=== Cesium 初始化检查 ===');
+    console.log('Cesium 版本:', Cesium.CESIUM_VERSION);
+    console.log('Cesium 对象:', typeof Cesium);
   </script>
   <link rel="stylesheet" href="/Cesium/Widgets/widgets.css">
   <script type="module" src="/src/main.js"></script>

+ 32 - 0
RuoYi-Vue3/src/api/cesium/geojson.js

@@ -0,0 +1,32 @@
+import request from '@/utils/request'
+
+export function uploadShp(formData) {
+  return request({
+    url: '/cesium/geojson/upload',
+    method: 'post',
+    data: formData,
+    headers: { 'Content-Type': 'multipart/form-data' }
+  })
+}
+
+export function getGeoJsonList(params) {
+  return request({
+    url: '/cesium/geojson/list',
+    method: 'get',
+    params: params
+  })
+}
+
+export function getGeoJsonUrl(id) {
+  return request({
+    url: '/cesium/geojson/url/' + id,
+    method: 'get'
+  })
+}
+
+export function deleteGeoJson(id) {
+  return request({
+    url: '/cesium/geojson/' + id,
+    method: 'delete'
+  })
+}

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

@@ -9,6 +9,8 @@
           <option value="IMG">影像</option>
           <option value="TERRAIN">地形</option>
           <option value="MVT">矢量瓦片</option>
+          <option value="GEOJSON">GeoJSON</option>
+          <option value="SHP">SHP上传</option>
         </select>
       </div>
 
@@ -19,26 +21,40 @@
           style="width:100%"
           placeholder="URL"
           v-model="layerURL"
+          v-show="layersType !== 'SHP'"
         />
 
+        <div v-show="layersType === 'SHP'" class="shp-upload-container">
+          <input
+            type="file"
+            ref="shpFileInput"
+            accept=".zip,.shp"
+            @change="handleShpFileChange"
+            class="sm-input"
+            style="width:100%"
+          />
+          <div class="upload-tip">支持 .zip(包含.shp,.shx,.dbf等) 或单独.shp文件</div>
+        </div>
+
         <input
           type="text"
           class="sm-input sm-margin-M"
           style="width:100%"
           placeholder="自定义名称"
           v-model="layerName"
+          v-show="layersType !== 'SHP'"
         />
           <!-- v-show="layersType === 'S3M'" -->
 
         <label class="label-S">添加token</label>
-        <input checked type="checkbox" v-model="isAddToken" />
+        <input checked type="checkbox" v-model="isAddToken" v-show="layersType !== 'SHP' && layersType !== 'GEOJSON'" />
         <input
           type="text"
           class="sm-input sm-margin-M"
           style="width:100%"
           placeholder="token"
           v-model="token"
-          v-show="isAddToken"
+          v-show="isAddToken && layersType !== 'SHP' && layersType !== 'GEOJSON'"
         />
       </div>
       <div class="sm-half-L flex-start" v-show="layersType === 'TERRAIN'">
@@ -58,6 +74,7 @@
 import layerManagement from "../../js/common/layerManagement.js";
 import tool from "../../js/tool/tool.js";
 import { watch, ref, reactive, toRefs, onBeforeUnmount, onMounted, getCurrentInstance } from "vue";
+import { uploadShp, getGeoJsonList, getGeoJsonUrl } from "../../api/cesium/geojson.js";
 export default {
   name: "Sm3dCustomService",
   props: {
@@ -84,7 +101,11 @@ export default {
       }, 50);
     });
     
-    // 设置默认值数据
+    const shpFileInput = ref(null);
+    const shpFile = ref(null);
+    const uploadProgress = ref(0);
+    const isUploading = ref(false);
+
     let state = reactive({
       layersType: "SCENE",
       layerURL: null,
@@ -94,6 +115,85 @@ export default {
       isSct: false
     });
 
+    async function handleShpFileChange(event) {
+      const file = event.target.files[0];
+      if (!file) return;
+      
+      const fileName = file.name.toLowerCase();
+      if (!fileName.endsWith('.zip') && !fileName.endsWith('.shp')) {
+        tool.Message.warnMsg("请上传 .zip 或 .shp 格式的文件");
+        return;
+      }
+      
+      shpFile.value = file;
+      tool.Message.infoMsg("已选择文件: " + file.name);
+    }
+
+    async function uploadShpFile() {
+      if (!shpFile.value) {
+        tool.Message.warnMsg("请先选择SHP文件");
+        return null;
+      }
+      
+      isUploading.value = true;
+      uploadProgress.value = 0;
+      
+      const formData = new FormData();
+      formData.append('file', shpFile.value);
+      if (state.layerName) {
+        formData.append('layerName', state.layerName);
+      }
+      
+      try {
+        const result = await uploadShp(formData);
+        isUploading.value = false;
+        uploadProgress.value = 100;
+        
+        if (result.code === 200 && result.data) {
+          tool.Message.successMsg("文件上传成功");
+          return result.data;
+        } else {
+          tool.Message.errorMsg(result.msg || "上传失败");
+          return null;
+        }
+      } catch (error) {
+        isUploading.value = false;
+        tool.Message.errorMsg("上传失败: " + error.message);
+        return null;
+      }
+    }
+
+    async function addGeoJsonLayer() {
+      const url = state.layerURL;
+      if (!url) {
+        tool.Message.warnMsg("请输入GeoJSON URL");
+        return;
+      }
+      
+      const serviceInfo = {
+        url: url,
+        type: 'GEOJSON'
+      };
+      
+      layerManagement.addGeoJsonLayer(url, state.layerName || 'GeoJSON', (geoJsonLayer) => {
+        addCallback(geoJsonLayer, "GEOJSON", serviceInfo);
+      });
+    }
+
+    async function addShpLayer() {
+      const geoJsonUrl = await uploadShpFile();
+      if (!geoJsonUrl) return;
+      
+      const serviceInfo = {
+        url: geoJsonUrl,
+        type: 'GEOJSON'
+      };
+      
+      layerManagement.addGeoJsonLayer(geoJsonUrl, state.layerName || 'SHP_Data', (geoJsonLayer) => {
+        addCallback(geoJsonLayer, "GEOJSON", serviceInfo);
+      });
+    }
+
     let addCallback = () => {};
     let clearCallback = () => {};
     let getLayerName = () => {};
@@ -109,6 +209,22 @@ export default {
     }
 
     function check() {
+      if (state.layersType === 'SHP') {
+        if (!shpFile.value) {
+          tool.Message.warnMsg("请先选择SHP文件");
+          return false;
+        }
+        return true;
+      }
+      
+      if (state.layersType === 'GEOJSON') {
+        if (!state.layerURL || state.layerURL === "") {
+          tool.Message.warnMsg("请填写GeoJSON URL");
+          return false;
+        }
+        return true;
+      }
+      
       if (!state.layerURL || state.layerURL === "") {
         tool.Message.warnMsg("请填写正确的URL");
         return false;
@@ -128,6 +244,9 @@ export default {
     }
 
     function checkLayersType(){
+      if (state.layersType === 'SHP' || state.layersType === 'GEOJSON') {
+        return true;
+      }
       let arr = state.layerURL.split('/');
       let layer_type = arr[arr.length -1];
       if(layer_type === 'realspace' && state.layersType != "SCENE") return false;
@@ -199,6 +318,16 @@ export default {
             });
           }
           break;
+        case "GEOJSON":
+          if (check()) {
+            addGeoJsonLayer();
+          }
+          break;
+        case "SHP":
+          if (check()) {
+            addShpLayer();
+          }
+          break;
       }
       clearState();
     }
@@ -219,11 +348,28 @@ export default {
       ...toRefs(state),
       panelStyle,
       addLayer,
-      clear
+      clear,
+      handleShpFileChange,
+      shpFileInput,
+      uploadProgress,
+      isUploading
     };
   }
 };
 </script>
 
+<style scoped>
+.shp-upload-container {
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #999;
+  margin-top: 5px;
+}
+</style>
+
 
 

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

@@ -1,5 +1,5 @@
 <template>
-  <div id="CustomService-panel" class="sm-panel" v-drag>
+  <div id="CustomService-panel" class="sm-panel" v-drag :style="panelStyle">
     <div class="sm-function-module-sub-section" style="margin:0" v-stopdrag>
       <div class="sm-half-L">
         <label style="width:40%">打开图层</label>
@@ -9,6 +9,8 @@
           <option value="IMG">影像</option>
           <option value="TERRAIN">地形</option>
           <option value="MVT">矢量瓦片</option>
+          <option value="GEOJSON">GeoJSON</option>
+          <option value="SHP">SHP上传</option>
         </select>
       </div>
 
@@ -19,26 +21,76 @@
           style="width:100%"
           placeholder="URL"
           v-model="layerURL"
+          v-show="layersType !== 'SHP'"
         />
 
+        <div v-show="layersType === 'SHP'" class="shp-upload-container">
+          <div class="shp-file-item">
+            <span class="file-label">主文件(.shp):</span>
+            <input
+              type="file"
+              ref="shpFileInput"
+              accept=".shp"
+              @change="handleShpFileChange"
+              class="sm-input"
+              style="width:100%"
+            />
+          </div>
+          <div class="shp-file-item">
+            <span class="file-label">索引文件(.shx):</span>
+            <input
+              type="file"
+              ref="shxFileInput"
+              accept=".shx"
+              @change="handleShxFileChange"
+              class="sm-input"
+              style="width:100%"
+            />
+          </div>
+          <div class="shp-file-item">
+            <span class="file-label">属性文件(.dbf):</span>
+            <input
+              type="file"
+              ref="dbfFileInput"
+              accept=".dbf"
+              @change="handleDbfFileChange"
+              class="sm-input"
+              style="width:100%"
+            />
+          </div>
+          <div class="shp-file-item">
+            <span class="file-label">坐标文件(.prj):</span>
+            <input
+              type="file"
+              ref="prjFileInput"
+              accept=".prj"
+              @change="handlePrjFileChange"
+              class="sm-input"
+              style="width:100%"
+            />
+          </div>
+          <div class="upload-tip">必须同时上传 .shp、.shx、.dbf、.prj 四个文件</div>
+        </div>
+
         <input
           type="text"
           class="sm-input sm-margin-M"
           style="width:100%"
           placeholder="自定义名称"
           v-model="layerName"
+          v-show="layersType !== 'SHP'"
         />
           <!-- v-show="layersType === 'S3M'" -->
 
         <label class="label-S">添加token</label>
-        <input checked type="checkbox" v-model="isAddToken" />
+        <input checked type="checkbox" v-model="isAddToken" v-show="layersType !== 'SHP' && layersType !== 'GEOJSON'" />
         <input
           type="text"
           class="sm-input sm-margin-M"
           style="width:100%"
           placeholder="token"
           v-model="token"
-          v-show="isAddToken"
+          v-show="isAddToken && layersType !== 'SHP' && layersType !== 'GEOJSON'"
         />
       </div>
       <div class="sm-half-L flex-start" v-show="layersType === 'TERRAIN'">
@@ -57,7 +109,8 @@
 <script>
 import layerManagement from "../../../js/common/layerManagement.js";
 import tool from "../../../js/tool/tool.js";
-import { watch, ref, reactive, toRefs, onBeforeUnmount, onMounted } from "vue";
+import { watch, ref, reactive, toRefs, onBeforeUnmount, onMounted, getCurrentInstance } from "vue";
+import { uploadShp, getGeoJsonList, getGeoJsonUrl } from "@/api/cesium/geojson.js";
 export default {
   name: "Sm3dCustomService",
   props: {
@@ -74,7 +127,29 @@ export default {
     }
   },
   setup(props) {
-    // 设置默认值数据
+    onMounted(() => {
+      setTimeout(() => {
+        const panel = document.getElementById('CustomService-panel');
+        if (panel) {
+          panel.style.left = '35%';
+          panel.style.top = '25%';
+        }
+      }, 50);
+    });
+    
+    const shpFileInput = ref(null);
+    const shxFileInput = ref(null);
+    const dbfFileInput = ref(null);
+    const prjFileInput = ref(null);
+    
+    const shpFile = ref(null);
+    const shxFile = ref(null);
+    const dbfFile = ref(null);
+    const prjFile = ref(null);
+    
+    const uploadProgress = ref(0);
+    const isUploading = ref(false);
+
     let state = reactive({
       layersType: "SCENE",
       layerURL: null,
@@ -84,6 +159,153 @@ export default {
       isSct: false
     });
 
+    async function handleShpFileChange(event) {
+      const file = event.target.files[0];
+      if (!file) return;
+      
+      const fileName = file.name.toLowerCase();
+      if (!fileName.endsWith('.shp')) {
+        tool.Message.warnMsg("请上传 .shp 格式的文件");
+        return;
+      }
+      
+      shpFile.value = file;
+      tool.Message.infoMsg("已选择主文件: " + file.name);
+    }
+    
+    async function handleShxFileChange(event) {
+      const file = event.target.files[0];
+      if (!file) return;
+      
+      const fileName = file.name.toLowerCase();
+      if (!fileName.endsWith('.shx')) {
+        tool.Message.warnMsg("请上传 .shx 格式的文件");
+        return;
+      }
+      
+      shxFile.value = file;
+      tool.Message.infoMsg("已选择索引文件: " + file.name);
+    }
+    
+    async function handleDbfFileChange(event) {
+      const file = event.target.files[0];
+      if (!file) return;
+      
+      const fileName = file.name.toLowerCase();
+      if (!fileName.endsWith('.dbf')) {
+        tool.Message.warnMsg("请上传 .dbf 格式的文件");
+        return;
+      }
+      
+      dbfFile.value = file;
+      tool.Message.infoMsg("已选择属性文件: " + file.name);
+    }
+    
+    async function handlePrjFileChange(event) {
+      const file = event.target.files[0];
+      if (!file) return;
+      
+      const fileName = file.name.toLowerCase();
+      if (!fileName.endsWith('.prj')) {
+        tool.Message.warnMsg("请上传 .prj 格式的文件");
+        return;
+      }
+      
+      prjFile.value = file;
+      tool.Message.infoMsg("已选择坐标文件: " + file.name);
+    }
+
+    async function uploadShpFile() {
+      if (!shpFile.value) {
+        tool.Message.warnMsg("请先选择SHP主文件");
+        return null;
+      }
+      
+      if (!shxFile.value) {
+        tool.Message.warnMsg("请选择SHX索引文件");
+        return null;
+      }
+      
+      if (!dbfFile.value) {
+        tool.Message.warnMsg("请选择DBF属性文件");
+        return null;
+      }
+      
+      if (!prjFile.value) {
+        tool.Message.warnMsg("请选择PRJ坐标文件");
+        return null;
+      }
+      
+      isUploading.value = true;
+      uploadProgress.value = 0;
+      
+      const formData = new FormData();
+      formData.append('file', shpFile.value);
+      formData.append('shxFile', shxFile.value);
+      formData.append('dbfFile', dbfFile.value);
+      formData.append('prjFile', prjFile.value);
+      if (state.layerName) {
+        formData.append('layerName', state.layerName);
+      }
+      
+      try {
+        const result = await uploadShp(formData);
+        isUploading.value = false;
+        uploadProgress.value = 100;
+        
+        if (result.code === 200 && result.data) {
+          tool.Message.successMsg("文件上传成功");
+          shpFile.value = null;
+          shxFile.value = null;
+          dbfFile.value = null;
+          prjFile.value = null;
+          if (shpFileInput.value) shpFileInput.value.value = '';
+          if (shxFileInput.value) shxFileInput.value.value = '';
+          if (dbfFileInput.value) dbfFileInput.value.value = '';
+          if (prjFileInput.value) prjFileInput.value.value = '';
+          return result.data;
+        } else {
+          tool.Message.errorMsg(result.msg || "上传失败");
+          return null;
+        }
+      } catch (error) {
+        isUploading.value = false;
+        tool.Message.errorMsg("上传失败: " + error.message);
+        return null;
+      }
+    }
+
+    async function addGeoJsonLayer() {
+      const url = state.layerURL;
+      if (!url) {
+        tool.Message.warnMsg("请输入GeoJSON URL");
+        return;
+      }
+      
+      const serviceInfo = {
+        url: url,
+        type: 'GEOJSON'
+      };
+      
+      layerManagement.addGeoJsonLayer(url, state.layerName || 'GeoJSON', (geoJsonLayer) => {
+        addCallback(geoJsonLayer, "GEOJSON", serviceInfo);
+      });
+    }
+
+    async function addShpLayer() {
+      const geoJsonUrl = await uploadShpFile();
+      if (!geoJsonUrl) return;
+      
+      const serviceInfo = {
+        url: geoJsonUrl,
+        type: 'GEOJSON'
+      };
+      
+      layerManagement.addGeoJsonLayer(geoJsonUrl, state.layerName || 'SHP_Data', (geoJsonLayer) => {
+        addCallback(geoJsonLayer, "GEOJSON", serviceInfo);
+      });
+    }
+
     let addCallback = () => {};
     let clearCallback = () => {};
     let getLayerName = () => {};
@@ -99,6 +321,22 @@ export default {
     }
 
     function check() {
+      if (state.layersType === 'SHP') {
+        if (!shpFile.value) {
+          tool.Message.warnMsg("请先选择SHP文件");
+          return false;
+        }
+        return true;
+      }
+      
+      if (state.layersType === 'GEOJSON') {
+        if (!state.layerURL || state.layerURL === "") {
+          tool.Message.warnMsg("请填写GeoJSON URL");
+          return false;
+        }
+        return true;
+      }
+      
       if (!state.layerURL || state.layerURL === "") {
         tool.Message.warnMsg("请填写正确的URL");
         return false;
@@ -118,6 +356,9 @@ export default {
     }
 
     function checkLayersType(){
+      if (state.layersType === 'SHP' || state.layersType === 'GEOJSON') {
+        return true;
+      }
       let arr = state.layerURL.split('/');
       let layer_type = arr[arr.length -1];
       if(layer_type === 'realspace' && state.layersType != "SCENE") return false;
@@ -146,12 +387,14 @@ export default {
       };
       
       console.log('addLayer - serviceInfo:', serviceInfo);
+      console.log('addLayer - currentURL:', currentURL);
       
       switch (currentLayersType) {
         case "SCENE":
           if (check()) {
             let options = {};
             if (currentTokenRequired) options.SceneToken = currentToken;
+            console.log('SCENE回调前serviceInfo:', serviceInfo);
             layerManagement.addScene(currentURL, options, (layers) => {
               console.log('SCENE回调中serviceInfo:', serviceInfo);
               addCallback(layers, "SCENE", serviceInfo);
@@ -187,6 +430,16 @@ export default {
             });
           }
           break;
+        case "GEOJSON":
+          if (check()) {
+            addGeoJsonLayer();
+          }
+          break;
+        case "SHP":
+          if (check()) {
+            addShpLayer();
+          }
+          break;
       }
       clearState();
     }
@@ -205,12 +458,41 @@ export default {
 
     return {
       ...toRefs(state),
+      panelStyle,
       addLayer,
-      clear
+      clear,
+      handleShpFileChange,
+      shpFileInput,
+      uploadProgress,
+      isUploading
     };
   }
 };
 </script>
 
+<style scoped>
+.shp-upload-container {
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.shp-file-item {
+  margin-bottom: 8px;
+}
+
+.file-label {
+  display: block;
+  font-size: 12px;
+  color: #666;
+  margin-bottom: 4px;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #e6a23c;
+  margin-top: 5px;
+}
+</style>
+
 
 

+ 11 - 0
RuoYi-Vue3/src/supermap-cesium-module/components/scale-bar/scale-bar.vue

@@ -26,6 +26,8 @@ export default {
       if (!props.viewer || !props.viewer.camera || !props.viewer.scene) return;
 
       try {
+        // 检查scene是否有效
+        if (!props.viewer.scene || !props.viewer.scene.camera) return;
         // 获取相机高度
         const height = props.viewer.camera.positionCartographic.height;
         
@@ -82,6 +84,15 @@ export default {
       if (updateInterval) {
         clearInterval(updateInterval);
       }
+      
+      // 移除相机变化事件监听器
+      if (props.viewer && props.viewer.camera) {
+        try {
+          props.viewer.camera.changed.removeEventListener(updateScaleBar);
+        } catch (e) {
+          console.warn('Failed to remove camera event listener:', e);
+        }
+      }
     });
 
     return {

+ 34 - 1
RuoYi-Vue3/src/supermap-cesium-module/components/viewer/viewer.js

@@ -229,7 +229,40 @@ function initViewer(props, callback) {
   // 销毁
   onBeforeUnmount(() => {
     if (viewer) {
-      viewer.destroy();
+      try {
+        // 清除所有实体
+        if (viewer.entities) {
+          viewer.entities.removeAll();
+        }
+        
+        // 清除所有数据源
+        if (viewer.dataSources) {
+          viewer.dataSources.removeAll();
+        }
+        
+        // 清除所有primitives
+        if (viewer.scene && viewer.scene.primitives) {
+          viewer.scene.primitives.removeAll();
+        }
+        
+        // 清除所有imagery layers (保留底图)
+        if (viewer.imageryLayers) {
+          while (viewer.imageryLayers.length > 2) {
+            viewer.imageryLayers.remove(viewer.imageryLayers.get(2));
+          }
+        }
+        
+        // 移除事件监听器
+        if (viewer.eventManager) {
+          viewer.eventManager.removeAll();
+        }
+        
+        // 销毁viewer
+        viewer.destroy();
+      } catch (e) {
+        console.error('Error destroying viewer:', e);
+      }
+      
       window.viewer = undefined;
       window.scene = undefined;
     }

+ 69 - 2
RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js

@@ -122,6 +122,56 @@ function addMvtLayer(LayerURL, name, callback) {    // 返回img图层layer
 
 };
 
+function addGeoJsonLayer(url, name, callback) {
+    try {
+        const loadPromise = Cesium.GeoJsonDataSource.load(url, {
+            stroke: Cesium.Color.fromCssColorString('#00FF00').withAlpha(1),
+            fill: Cesium.Color.fromCssColorString('#00FF00').withAlpha(0.3),
+            strokeWidth: 3
+        });
+        
+        Cesium.when(loadPromise, function(dataSource) {
+            viewer.dataSources.add(dataSource);
+            let entities = dataSource.entities.values;
+            
+            let boundingSphere = new Cesium.BoundingSphere();
+            let entitiesLength = entities.length;
+            
+            for (let i = 0; i < entitiesLength; i++) {
+                let entity = entities[i];
+                entity.name = name || 'GeoJSON';
+                
+                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);
+            let widget = viewer.cesiumWidget;
+            if (widget && widget._showRenderLoopErrors) {
+                let title = "加载GeoJSON失败,请检查URL是否正确";
+                widget.showErrorPanel(title, undefined, error);
+            }
+        });
+    } catch (e) {
+        console.error("加载GeoJSON异常:", e);
+        let widget = viewer.cesiumWidget;
+        if (widget && widget._showRenderLoopErrors) {
+            let title = "渲染时发生错误,已停止渲染。";
+            widget.showErrorPanel(title, undefined, e);
+        }
+    }
+};
+
 // 加载s3m和场景函数
 function promiseWhen(promiseArray, callback, type) {
     Cesium.when.all(
@@ -209,6 +259,21 @@ function layersDelete(type, id_name, callback) {
             actions.setChangeLayers();
             if (callback) callback();
             break;
+        case "GEOJSON":
+        case "SHP":
+            if (viewer.dataSources) {
+                const dataSources = viewer.dataSources._dataSources;
+                for (let i = dataSources.length - 1; i >= 0; i--) {
+                    const ds = dataSources[i];
+                    if (ds.name === id_name || ds._name === id_name) {
+                        viewer.dataSources.remove(ds);
+                        break;
+                    }
+                }
+            }
+            actions.setChangeLayers();
+            if (callback) callback();
+            break;
         default:
             if (callback) callback();
     }
@@ -222,7 +287,8 @@ export default {
     addTerrainLayer,
     addImageLayer,
     layersDelete,
-    addMvtLayer
+    addMvtLayer,
+    addGeoJsonLayer
 };
 export {
     addS3mLayers,
@@ -230,5 +296,6 @@ export {
     addTerrainLayer,
     addImageLayer,
     layersDelete,
-    addMvtLayer
+    addMvtLayer,
+    addGeoJsonLayer
 };

+ 10 - 0
RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue

@@ -1354,6 +1354,16 @@ export default {
               markAsLoaded();
             });
             break;
+          case 'SHP':
+          case 'GEOJSON':
+            layerManagement.addGeoJsonLayer(url, layerName, (geoJsonLayer) => {
+              console.log('SHP/GEOJSON加载成功:', geoJsonLayer);
+              markAsLoaded();
+              if (flyTo) {
+                viewer.flyTo(geoJsonLayer, { duration: 2 });
+              }
+            });
+            break;
           default:
             ElMessage.warning('不支持的服务类型: ' + type);
             return;

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

@@ -144,6 +144,7 @@
               <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>
           <el-button @click="resetServiceFilters" style="margin-left: 10px;">重置</el-button>
@@ -366,20 +367,45 @@
       <div class="dialog-content-wrapper">
         <el-form :model="serviceFormData" label-width="100px">
           <el-form-item label="服务类型">
-            <el-select v-model="serviceFormData.type" placeholder="请选择服务类型" style="width: 100%;">
+            <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="SHP数据" value="SHP" />
             </el-select>
           </el-form-item>
           <el-form-item label="服务名称">
             <el-input v-model="serviceFormData.name" placeholder="请输入服务名称" style="width: 100%;" />
           </el-form-item>
-          <el-form-item label="服务地址">
+          <el-form-item label="服务地址" v-if="serviceFormData.type !== 'SHP'">
             <el-input v-model="serviceFormData.url" placeholder="请输入服务URL地址" style="width: 100%;" />
           </el-form-item>
+          <el-form-item label="上传SHP" v-if="serviceFormData.type === 'SHP'">
+            <el-upload
+              ref="shpUploadRef"
+              class="shp-upload"
+              :auto-upload="false"
+              :limit="5"
+              accept=".zip,.shp,.shx,.dbf,.prj"
+              :on-change="handleShpFileChange"
+              :on-exceed="handleShpExceed"
+              :on-remove="handleShpRemove"
+              multiple
+              drag
+            >
+              <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
+              <div class="el-upload__text">
+                拖拽文件到此处或 <em>点击上传</em>
+              </div>
+              <template #tip>
+                <div class="el-upload__tip">
+                  支持 .zip(包含.shp,.shx,.dbf,.prj) 或单独上传 .shp + .shx + .dbf + .prj 文件
+                </div>
+              </template>
+            </el-upload>
+          </el-form-item>
           <el-form-item label="需要Token">
             <el-switch v-model="serviceFormData.tokenRequired" />
           </el-form-item>
@@ -402,12 +428,13 @@
       <div class="dialog-content-wrapper">
         <el-form :model="editServiceFormData" label-width="100px">
           <el-form-item label="服务类型">
-            <el-select v-model="editServiceFormData.type" placeholder="请选择服务类型" style="width: 100%;">
+            <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="SHP数据" value="SHP" />
             </el-select>
           </el-form-item>
           <el-form-item label="服务名称">
@@ -436,7 +463,7 @@
 
 <script setup>
 import { ref, onMounted, computed, watch } from 'vue'
-import { Setting, House, Menu, DataBoard, Upload, Plus } from '@element-plus/icons-vue'
+import { Setting, House, Menu, DataBoard, Upload, Plus, UploadFilled } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox, ElInputNumber } from 'element-plus'
 // 导入项目的axios service实例
 import request from '@/utils/request'
@@ -502,6 +529,65 @@ const editServiceFormData = ref({
   remark: ''
 })
 
+const shpUploadRef = ref(null)
+const shpFiles = ref([])
+
+const handleServiceTypeChange = (value) => {
+  if (value === 'SHP') {
+    serviceFormData.value.url = ''
+  }
+}
+
+const handleEditServiceTypeChange = (value) => {
+  if (value === 'SHP') {
+    editServiceFormData.value.url = ''
+  }
+}
+
+const handleShpFileChange = (file, fileList) => {
+  // 过滤出有效的文件类型
+  const validFiles = fileList.filter(f => {
+    const fileName = f.name.toLowerCase()
+    return fileName.endsWith('.shp') || fileName.endsWith('.shx') || fileName.endsWith('.dbf') || fileName.endsWith('.prj') || fileName.endsWith('.zip')
+  })
+  
+  // 只保留有效的文件
+  shpFiles.value = validFiles.map(f => f.raw)
+  
+  // 如果上传的是单个文件且是zip或shp,自动设置服务名称
+  if (validFiles.length === 1 && !serviceFormData.value.name) {
+    const fileName = validFiles[0].name
+    serviceFormData.value.name = fileName.replace(/\.(zip|shp|shx|dbf|prj)$/i, '')
+  }
+  
+  // 检查是否上传了所有必要的文件
+  if (validFiles.length > 1) {
+    const fileTypes = validFiles.map(f => f.name.toLowerCase().split('.').pop())
+    const hasShp = fileTypes.includes('shp')
+    const hasShx = fileTypes.includes('shx')
+    const hasDbf = fileTypes.includes('dbf')
+    const hasPrj = fileTypes.includes('prj')
+    
+    if (!hasShp) {
+      ElMessage.warning('请上传 .shp 主文件')
+    } else if (!hasShx) {
+      ElMessage.warning('请上传 .shx 索引文件')
+    } else if (!hasDbf) {
+      ElMessage.warning('请上传 .dbf 属性文件')
+    } else if (!hasPrj) {
+      ElMessage.warning('请上传 .prj 坐标文件')
+    }
+  }
+}
+
+const handleShpExceed = () => {
+  ElMessage.warning('最多上传5个文件(.shp, .shx, .dbf, .prj 和可选的 .zip)')
+}
+
+const handleShpRemove = () => {
+  shpFiles.value = []
+}
+
 // 模型类型级联选择器数据
 const modelTypeOptions = [
   {
@@ -581,7 +667,9 @@ const statistics = computed(() => {
       '2-2': 0, // S3M
       '2-3': 0, // 影像
       '2-4': 0, // 地形
-      '2-5': 0  // 矢量瓦片
+      '2-5': 0, // 矢量瓦片
+      '2-6': 0, // SHP数据
+      '2-7': 0  // GeoJSON
     }
     
     // 服务类型映射
@@ -590,7 +678,9 @@ const statistics = computed(() => {
       'S3M': '2-2',
       'IMAGE': '2-3',
       'TERRAIN': '2-4',
-      'VECTOR_TILE': '2-5'
+      'VECTOR_TILE': '2-5',
+      'SHP': '2-6',
+      'GEOJSON': '2-7'
     }
     
     // 统计各服务类型数量
@@ -866,6 +956,8 @@ const getServiceTypeName = (type) => {
     'IMAGE': '影像',
     'TERRAIN': '地形',
     'VECTOR_TILE': '矢量瓦片',
+    'SHP': 'SHP数据',
+    'GEOJSON': 'GeoJSON',
     '2-1': '场景',
     '2-2': 'S3M',
     '2-3': '影像',
@@ -904,12 +996,16 @@ const openAddServiceDialog = () => {
     token: '',
     remark: ''
   }
+  shpFiles.value = []
+  shpUploadRef.value?.clearFiles()
   addServiceDialogVisible.value = true
 }
 
 // 关闭添加服务对话框
 const closeAddServiceDialog = () => {
   addServiceDialogVisible.value = false
+  shpFiles.value = []
+  shpUploadRef.value?.clearFiles()
 }
 
 // 提交添加服务
@@ -919,6 +1015,86 @@ const submitAddService = async () => {
       ElMessage.error('请输入服务名称')
       return
     }
+    
+    if (serviceFormData.value.type === 'SHP') {
+      if (shpFiles.value.length === 0) {
+        ElMessage.error('请上传SHP文件')
+        return
+      }
+      
+      // 检查是否上传了所有必要的文件
+      const fileTypes = shpFiles.value.map(f => f.name.toLowerCase().split('.').pop())
+      const hasZip = fileTypes.includes('zip')
+      const hasShp = fileTypes.includes('shp')
+      const hasShx = fileTypes.includes('shx')
+      const hasDbf = fileTypes.includes('dbf')
+      const hasPrj = fileTypes.includes('prj')
+      
+      if (!hasZip) {
+        if (!hasShp) {
+          ElMessage.error('请上传 .shp 主文件')
+          return
+        } else if (!hasShx) {
+          ElMessage.error('请上传 .shx 索引文件')
+          return
+        } else if (!hasDbf) {
+          ElMessage.error('请上传 .dbf 属性文件')
+          return
+        } else if (!hasPrj) {
+          ElMessage.error('请上传 .prj 坐标文件')
+          return
+        }
+      }
+      
+      const formData = new FormData()
+      // 根据文件类型添加到对应的参数
+      shpFiles.value.forEach(file => {
+        const fileName = file.name.toLowerCase()
+        if (fileName.endsWith('.shp')) {
+          formData.append('file', file)
+        } else if (fileName.endsWith('.shx')) {
+          formData.append('shxFile', file)
+        } else if (fileName.endsWith('.dbf')) {
+          formData.append('dbfFile', file)
+        } else if (fileName.endsWith('.prj')) {
+          formData.append('prjFile', file)
+        }
+      })
+      formData.append('layerName', serviceFormData.value.name)
+      
+      try {
+        const uploadResponse = await request({
+          url: '/cesium/geojson/upload',
+          method: 'post',
+          data: formData,
+          headers: { 'Content-Type': 'multipart/form-data' }
+        })
+        
+        console.log('上传响应完整:', uploadResponse)
+        console.log('上传响应code:', uploadResponse.code)
+        console.log('上传响应data:', uploadResponse.data)
+        console.log('上传响应msg:', uploadResponse.msg)
+        
+        if (uploadResponse.code === 200 || uploadResponse.code === undefined) {
+          ElMessage.success('添加服务成功')
+          addServiceDialogVisible.value = false
+          shpUploadRef.value?.clearFiles()
+          shpFiles.value = []
+          fetchServices()
+        } else {
+          ElMessage.error(uploadResponse.msg || '上传失败')
+        }
+      } catch (error) {
+            console.error('上传SHP文件失败:', error)
+            console.error('错误响应状态:', error.response?.status)
+            console.error('错误响应状态文本:', error.response?.statusText)
+            console.error('错误响应数据:', error.response?.data)
+            const errorMsg = error.response?.data?.msg || error.response?.data?.message || error.message || error.response?.statusText || '未知错误'
+            ElMessage.error('上传失败: ' + errorMsg)
+      }
+      return
+    }
+    
     if (!serviceFormData.value.url) {
       ElMessage.error('请输入服务地址')
       return
@@ -944,7 +1120,10 @@ const submitAddService = async () => {
     fetchServices()
   } catch (error) {
     console.error('添加服务失败:', error)
-    ElMessage.error('添加服务失败: ' + (error.message || '未知错误'))
+    console.error('错误响应:', error.response)
+    console.error('错误详情:', error.response?.data || error.message)
+    const errorMsg = error.response?.data?.msg || error.response?.data?.message || error.message || '未知错误'
+    ElMessage.error('添加服务失败: ' + errorMsg)
   }
 }
 
@@ -1539,4 +1718,31 @@ watch(() => props.selectedCategory, (newVal) => {
 .el-pagination .el-select .el-select__selected-item {
   color: #606266 !important;
 }
+
+/* SHP上传组件样式 */
+.shp-upload {
+  width: 100%;
+}
+
+.shp-upload .el-upload-dragger {
+  width: 100%;
+  padding: 20px;
+}
+
+.shp-upload .el-icon--upload {
+  font-size: 48px;
+  color: #409eff;
+  margin-bottom: 10px;
+}
+
+.shp-upload .el-upload__text {
+  color: #606266;
+  font-size: 14px;
+}
+
+.shp-upload .el-upload__tip {
+  color: #909399;
+  font-size: 12px;
+  margin-top: 5px;
+}
 </style>

+ 6 - 1
RuoYi-Vue3/vite.config.js

@@ -59,7 +59,7 @@ export default defineConfig(({ mode, command }) => {
     },
     // vite 相关配置
     server: {
-      port: 80,
+      port: 5173,
       host: true,
       open: true,
       // 优化开发服务器性能
@@ -86,6 +86,11 @@ export default defineConfig(({ mode, command }) => {
         '/uploads': {
           target: baseUrl,
           changeOrigin: true
+        },
+        // profile 静态文件代理
+        '/profile': {
+          target: baseUrl,
+          changeOrigin: true
         },
          // springdoc proxy
          '^\/v3/api-docs/(.*)': {

+ 709 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/cesium/CesiumGeojsonController.java

@@ -0,0 +1,709 @@
+package com.ruoyi.web.controller.cesium;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.WatershedService;
+import com.ruoyi.system.mapper.WatershedServiceMapper;
+
+@RestController
+@RequestMapping("/cesium/geojson")
+public class CesiumGeojsonController extends BaseController
+{
+    private static final Logger logger = LoggerFactory.getLogger(CesiumGeojsonController.class);
+    
+    private int sourceSrid = 0;
+    private String sourceProjection = null;
+    
+    @Autowired
+    private WatershedServiceMapper watershedServiceMapper;
+
+    @PostMapping("/upload")
+    @Log(title = "上传SHP文件", businessType = BusinessType.IMPORT)
+    public AjaxResult upload(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam(value = "shxFile", required = false) MultipartFile shxFile,
+            @RequestParam(value = "dbfFile", required = false) MultipartFile dbfFile,
+            @RequestParam(value = "prjFile", required = false) MultipartFile prjFile,
+            @RequestParam(value = "layerName", required = false) String layerName)
+    {
+        try {
+            if (file == null || file.isEmpty()) {
+                return AjaxResult.error("上传文件不能为空");
+            }
+            
+            String originalFilename = file.getOriginalFilename();
+            if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".shp")) {
+                return AjaxResult.error("必须上传 .shp 格式的主文件");
+            }
+            
+            String uploadPath = RuoYiConfig.getProfile();
+            
+            String uuid = UUID.randomUUID().toString().replace("-", "");
+            String baseName = originalFilename.substring(0, originalFilename.lastIndexOf("."));
+            
+            String targetDir = uploadPath + "/geojson/" + uuid;
+            new File(targetDir).mkdirs();
+            
+            String shpFileName = baseName + ".shp";
+            String shpFullPath = targetDir + "/" + shpFileName;
+            file.transferTo(new File(shpFullPath));
+            
+            if (shxFile != null && !shxFile.isEmpty()) {
+                shxFile.transferTo(new File(targetDir + "/" + baseName + ".shx"));
+            }
+            if (dbfFile != null && !dbfFile.isEmpty()) {
+                dbfFile.transferTo(new File(targetDir + "/" + baseName + ".dbf"));
+            }
+            if (prjFile != null && !prjFile.isEmpty()) {
+                prjFile.transferTo(new File(targetDir + "/" + baseName + ".prj"));
+            }
+            
+            String shpRelativePath = "geojson/" + uuid + "/" + shpFileName;
+            String geojsonFileName = uuid + ".geojson";
+            String geojsonRelativePath = "geojson/" + geojsonFileName;
+            String geojsonFullPath = uploadPath + "/" + geojsonRelativePath;
+            
+            File uploadDir = new File(uploadPath + "/geojson");
+            if (!uploadDir.exists()) {
+                uploadDir.mkdirs();
+            }
+            
+            String geojsonUrl = "/profile/" + geojsonRelativePath;
+            
+            String geojsonData = parseShpToGeoJson(shpFullPath);
+            
+            try (FileWriter writer = new FileWriter(geojsonFullPath)) {
+                writer.write(geojsonData);
+            }
+            
+            String displayName = StringUtils.isNotEmpty(layerName) ? layerName : originalFilename.replace(".shp", "").replace(".SHP", "");
+            
+            WatershedService service = new WatershedService();
+            service.setServiceName(displayName);
+            service.setServiceType("SHP");
+            service.setServiceUrl(geojsonUrl);
+            service.setTokenRequired(0);
+            service.setServiceToken("");
+            service.setStatus("NORMAL");
+            service.setCreatedAt(new java.util.Date());
+            service.setUpdatedAt(new java.util.Date());
+            
+            int result = watershedServiceMapper.insertWatershedService(service);
+            
+            if (result > 0) {
+                return AjaxResult.success("上传成功", geojsonUrl);
+            } else {
+                return AjaxResult.error("保存数据失败");
+            }
+        } catch (Exception e) {
+            logger.error("上传SHP文件失败", e);
+            return AjaxResult.error("上传失败: " + e.getMessage());
+        }
+    }
+    
+    private String parseShpToGeoJson(String shpPath) {
+        logger.info("开始解析SHP文件: {}", shpPath);
+        
+        try {
+            File shpFile = new File(shpPath);
+            if (!shpFile.exists()) {
+                logger.error("SHP文件不存在: {}", shpPath);
+                return createSampleGeoJson();
+            }
+            
+            String basePath = shpPath.substring(0, shpPath.lastIndexOf("."));
+            String outputGeoJson = basePath + ".geojson";
+            
+            String pythonScript = getClass().getClassLoader().getResource("shp2geojson.py").getPath();
+            if (pythonScript.startsWith("/")) {
+                pythonScript = pythonScript.substring(1);
+            }
+            
+            String pythonExe = findPython();
+            if (pythonExe == null) {
+                logger.warn("未找到Python,使用Java原生解析");
+                return parseShpToGeoJsonJava(shpPath);
+            }
+            
+            logger.info("调用Python脚本转换SHP: {} -> {}", shpPath, outputGeoJson);
+            
+            ProcessBuilder pb = new ProcessBuilder(pythonExe, pythonScript, shpPath, outputGeoJson);
+            pb.redirectErrorStream(true);
+            Process process = pb.start();
+            
+            StringBuilder output = new StringBuilder();
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    output.append(line).append("\n");
+                }
+            }
+            
+            int exitCode = process.waitFor();
+            logger.info("Python脚本输出: {}", output.toString());
+            logger.info("Python脚本退出码: {}", exitCode);
+            
+            if (exitCode == 0 && new File(outputGeoJson).exists()) {
+                String geojsonContent = new String(java.nio.file.Files.readAllBytes(new File(outputGeoJson).toPath()), StandardCharsets.UTF_8);
+                logger.info("成功读取GeoJSON文件,内容长度: {}", geojsonContent.length());
+                return geojsonContent;
+            } else {
+                logger.error("Python转换失败,使用Java原生解析作为备选");
+                return parseShpToGeoJsonJava(shpPath);
+            }
+            
+        } catch (Exception e) {
+            logger.error("调用Python转换失败: " + e.getMessage() + ",使用Java原生解析");
+            try {
+                return parseShpToGeoJsonJava(shpPath);
+            } catch (Exception ex) {
+                logger.error("Java原生解析也失败: " + ex.getMessage(), ex);
+                return createSampleGeoJson();
+            }
+        }
+    }
+    
+    private String parseShpToGeoJsonJava(String shpPath) {
+        logger.info("使用Java原生解析SHP文件: {}", shpPath);
+        try {
+            File shpFile = new File(shpPath);
+            if (!shpFile.exists()) {
+                logger.error("SHP文件不存在: {}", shpPath);
+                return createSampleGeoJson();
+            }
+            
+            String basePath = shpPath.substring(0, shpPath.lastIndexOf("."));
+            String prjPath = basePath + ".prj";
+            File prjFile = new File(prjPath);
+            String sourceCrsDef = null;
+            
+            logger.info("检查PRJ文件: {}", prjPath);
+            if (prjFile.exists()) {
+                try {
+                    String content = new String(java.nio.file.Files.readAllBytes(prjFile.toPath()));
+                    sourceCrsDef = content.trim();
+                    logger.info("读取到PRJ文件内容: {}", sourceCrsDef);
+                } catch (Exception e) {
+                    logger.warn("读取PRJ文件失败: {}", e.getMessage());
+                    sourceCrsDef = null;
+                }
+            } else {
+                logger.warn("PRJ文件不存在,将假设为WGS84坐标系");
+            }
+            
+            initCoordinateTransform(sourceCrsDef);
+            
+            String dbfPath = basePath + ".dbf";
+            logger.info("检查DBF文件: {}", dbfPath);
+            List<String> dbfFieldNames = new ArrayList<>();
+            List<Map<Integer, String>> dbfRecords = new ArrayList<>();
+            readDbfFile(dbfPath, dbfFieldNames, dbfRecords);
+            logger.info("DBF字段: {}", dbfFieldNames);
+            logger.info("DBF记录数: {}", dbfRecords.size());
+            
+            FileInputStream fis = new FileInputStream(shpFile);
+            byte[] header = new byte[100];
+            fis.read(header);
+            
+            int fileCode = readIntBE(header, 0);
+            logger.info("SHP文件头 - 文件代码: {}, 期望值: 9994", fileCode);
+            logger.info("SHP文件头 - 版本: {}", readIntLE(header, 28));
+            logger.info("SHP文件头 - 形状类型: {}", header[32] & 0xFF);
+            
+            if (fileCode != 9994) {
+                logger.error("无效的SHP文件代码: {}", fileCode);
+                fis.close();
+                return createSampleGeoJson();
+            }
+            
+            List<String> features = new ArrayList<>();
+            byte[] recordHeader = new byte[8];
+            
+            int recordCount = 0;
+            try {
+                logger.info("开始读取记录,文件可用字节: {}", fis.available());
+                while (fis.available() > 8 && recordCount < 10000) {
+                    if (fis.read(recordHeader) < 8) break;
+                    
+                    int recordNumber = readIntBE(recordHeader, 0);
+                    int contentLengthWords = readIntBE(recordHeader, 4);
+                    int contentLength = contentLengthWords * 2;
+                    
+                    if (contentLength <= 0 || contentLength > 100000) {
+                        logger.warn("无效的记录长度: {} (words: {})", contentLength, contentLengthWords);
+                        break;
+                    }
+                    
+                    byte[] recordContent = new byte[contentLength];
+                    int readBytes = fis.read(recordContent);
+                    if (readBytes < contentLength) {
+                        logger.warn("读取的字节数不完整: expected {}, got {}", contentLength, readBytes);
+                        break;
+                    }
+                    
+                    int recShapeType = recordContent[0] & 0xFF;
+                    logger.info("记录 #{} - 形状类型: {}, 内容长度: {}", recordCount + 1, recShapeType, contentLength);
+                    
+                    Map<String, Object> properties = new HashMap<>();
+                    properties.put("id", recordCount + 1);
+                    
+                    if (!dbfFieldNames.isEmpty() && recordCount < dbfRecords.size()) {
+                        Map<Integer, String> record = dbfRecords.get(recordCount);
+                        for (int i = 0; i < dbfFieldNames.size() && i < record.size(); i++) {
+                            String value = record.get(i);
+                            if (value != null && !value.trim().isEmpty()) {
+                                properties.put(dbfFieldNames.get(i), value.trim());
+                            }
+                        }
+                    }
+                    
+                    String propsJson = mapToJson(properties);
+                    
+                    if (recShapeType == 1) {
+                        double x = readDoubleLE(recordContent, 4);
+                        double y = readDoubleLE(recordContent, 12);
+                        logger.info("Point坐标读取: x={}, y={}", x, y);
+                        double[] transformed = transformCoordinate(x, y);
+                        logger.info("Point转换后: tx={}, ty={}", transformed[0], transformed[1]);
+                        features.add("{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[" + transformed[0] + "," + transformed[1] + "]}," + propsJson + "}");
+                        recordCount++;
+                    } else if (recShapeType == 3) {
+                        int numParts = readIntLE(recordContent, 36);
+                        int numPoints = readIntLE(recordContent, 40);
+                        
+                        if (numParts > 0 && numPoints > 0 && numParts < 1000 && numPoints < 10000) {
+                            StringBuilder coords = new StringBuilder();
+                            int offset = 44 + numParts * 4 + 4;
+                            for (int i = 0; i < numPoints && i < 5000; i++) {
+                                if (offset + i * 16 + 16 <= recordContent.length) {
+                                    double x = readDoubleLE(recordContent, offset + i * 16);
+                                    double y = readDoubleLE(recordContent, offset + i * 16 + 8);
+                                    double[] transformed = transformCoordinate(x, y);
+                                    if (i > 0) coords.append(",");
+                                    coords.append("[").append(transformed[0]).append(",").append(transformed[1]).append("]");
+                                }
+                            }
+                            features.add("{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[" + coords + "]}," + propsJson + "}");
+                            recordCount++;
+                        }
+                    } else if (recShapeType == 5) {
+                        int numParts = readIntLE(recordContent, 36);
+                        int numPoints = readIntLE(recordContent, 40);
+                        
+                        if (numParts > 0 && numPoints > 0 && numParts < 1000 && numPoints < 10000) {
+                            StringBuilder polygonCoords = new StringBuilder();
+                            for (int p = 0; p < numParts; p++) {
+                                if (p > 0) polygonCoords.append(",");
+                                polygonCoords.append("[");
+                                int start = p == 0 ? 0 : readIntLE(recordContent, 44 + (p - 1) * 4);
+                                int end = p < numParts - 1 ? readIntLE(recordContent, 44 + p * 4) : numPoints;
+                                for (int j = start; j < end && j < numPoints; j++) {
+                                    if (j > start) polygonCoords.append(",");
+                                    int coordOffset = 44 + numParts * 4 + 4 + j * 16;
+                                    if (coordOffset + 16 <= recordContent.length) {
+                                        double x = readDoubleLE(recordContent, coordOffset);
+                                        double y = readDoubleLE(recordContent, coordOffset + 8);
+                                        double[] transformed = transformCoordinate(x, y);
+                                        polygonCoords.append("[").append(transformed[0]).append(",").append(transformed[1]).append("]");
+                                    }
+                                }
+                                polygonCoords.append("]");
+                            }
+                            features.add("{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[" + polygonCoords + "]}," + propsJson + "}");
+                            recordCount++;
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                logger.error("解析SHP记录时出错: " + e.getMessage());
+            }
+            
+            fis.close();
+            
+            logger.info("解析完成,记录数: {}", recordCount);
+            
+            if (features.isEmpty()) {
+                logger.warn("未解析到任何要素,返回示例数据");
+                return createSampleGeoJson();
+            }
+            
+            StringBuilder sb = new StringBuilder();
+            sb.append("{\"type\":\"FeatureCollection\",\"features\":[");
+            for (int i = 0; i < features.size(); i++) {
+                if (i > 0) sb.append(",");
+                sb.append(features.get(i));
+            }
+            sb.append("]}");
+            logger.info("成功生成GeoJSON,要素数: {}", features.size());
+            return sb.toString();
+        } catch (Exception e) {
+            logger.error("解析SHP文件失败: " + e.getMessage(), e);
+            return createSampleGeoJson();
+        }
+    }
+    
+    private void readDbfFile(String dbfPath, List<String> fieldNames, List<Map<Integer, String>> records) {
+        File dbfFile = new File(dbfPath);
+        if (!dbfFile.exists()) {
+            logger.info("DBF文件不存在: {}", dbfPath);
+            return;
+        }
+        
+        try (FileInputStream fis = new FileInputStream(dbfFile)) {
+            byte[] header = new byte[32];
+            if (fis.read(header) < 32) return;
+            
+            int numRecords = readIntLE(header, 4);
+            int headerSize = readIntLE(header, 8);
+            int recordSize = readIntLE(header, 10);
+            
+            int numFields = (headerSize - 32) / 32;
+            
+            byte[][] fieldHeaders = new byte[numFields][32];
+            for (int i = 0; i < numFields; i++) {
+                fis.read(fieldHeaders[i]);
+            }
+            
+            for (int i = 0; i < numFields; i++) {
+                String fieldName = new String(fieldHeaders[i], 0, 11).trim();
+                if (!fieldName.isEmpty()) {
+                    fieldNames.add(fieldName);
+                }
+            }
+            
+            fis.read(header, 0, 1);
+            
+            byte[] recordBuffer = new byte[recordSize];
+            for (int r = 0; r < numRecords && r < 10000; r++) {
+                int read = fis.read(recordBuffer);
+                if (read < recordSize) break;
+                
+                if (recordBuffer[0] == '*') continue;
+                
+                Map<Integer, String> record = new HashMap<>();
+                int pos = 1;
+                for (int f = 0; f < numFields && f < fieldNames.size(); f++) {
+                    int fieldLen = fieldHeaders[f][16] & 0xFF;
+                    if (pos + fieldLen <= recordBuffer.length) {
+                        String value = new String(recordBuffer, pos, fieldLen, Charset.forName("GBK")).trim();
+                        if (value.isEmpty()) {
+                            value = new String(recordBuffer, pos, fieldLen, StandardCharsets.UTF_8).trim();
+                        }
+                        record.put(f, value);
+                    }
+                    pos += fieldLen;
+                }
+                records.add(record);
+            }
+            
+            logger.info("成功读取DBF文件,记录数: {}, 字段数: {}", records.size(), fieldNames.size());
+            
+        } catch (Exception e) {
+            logger.error("读取DBF文件失败: {}", e.getMessage());
+        }
+    }
+    
+    private String mapToJson(Map<String, Object> map) {
+        if (map == null || map.isEmpty()) {
+            return "\"properties\":{}";
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append("\"properties\":{");
+        boolean first = true;
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            if (!first) sb.append(",");
+            sb.append("\"").append(escapeJson(entry.getKey())).append("\":");
+            Object value = entry.getValue();
+            if (value instanceof Number) {
+                sb.append(value.toString());
+            } else {
+                sb.append("\"").append(escapeJson(value.toString())).append("\"");
+            }
+            first = false;
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+    
+    private String escapeJson(String s) {
+        if (s == null) return "";
+        return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
+    }
+    
+    private int readIntBE(byte[] data, int offset) {
+        return ((data[offset] & 0xFF) << 24) |
+               ((data[offset + 1] & 0xFF) << 16) |
+               ((data[offset + 2] & 0xFF) << 8) |
+               (data[offset + 3] & 0xFF);
+    }
+    
+    private int readIntLE(byte[] data, int offset) {
+        if (offset + 4 > data.length) return 0;
+        return (data[offset] & 0xFF) |
+               ((data[offset + 1] & 0xFF) << 8) |
+               ((data[offset + 2] & 0xFF) << 16) |
+               ((data[offset + 3] & 0xFF) << 24);
+    }
+    
+    private double readDoubleLE(byte[] data, int offset) {
+        if (offset + 8 > data.length) return 0;
+        long bits = (data[offset] & 0xFF) |
+                    ((long)(data[offset + 1] & 0xFF) << 8) |
+                    ((long)(data[offset + 2] & 0xFF) << 16) |
+                    ((long)(data[offset + 3] & 0xFF) << 24) |
+                    ((long)(data[offset + 4] & 0xFF) << 32) |
+                    ((long)(data[offset + 5] & 0xFF) << 40) |
+                    ((long)(data[offset + 6] & 0xFF) << 48) |
+                    ((long)(data[offset + 7] & 0xFF) << 56);
+        return Double.longBitsToDouble(bits);
+    }
+    
+    private double readDoubleBE(byte[] data, int offset) {
+        if (offset + 8 > data.length) return 0;
+        long bits = ((long)(data[offset] & 0xFF) << 56) |
+                    ((long)(data[offset + 1] & 0xFF) << 48) |
+                    ((long)(data[offset + 2] & 0xFF) << 40) |
+                    ((long)(data[offset + 3] & 0xFF) << 32) |
+                    ((long)(data[offset + 4] & 0xFF) << 24) |
+                    ((long)(data[offset + 5] & 0xFF) << 16) |
+                    ((long)(data[offset + 6] & 0xFF) << 8) |
+                    (data[offset + 7] & 0xFF);
+        return Double.longBitsToDouble(bits);
+    }
+    
+    private void initCoordinateTransform(String sourceCrsDef) {
+        if (sourceCrsDef == null || sourceCrsDef.isEmpty()) {
+            logger.warn("未找到PRJ文件,假设数据已经是WGS84坐标");
+            sourceProjection = null;
+            sourceSrid = 4326;
+            return;
+        }
+        
+        try {
+            String epsgCode = extractEpsgCode(sourceCrsDef);
+            if (epsgCode != null) {
+                sourceSrid = Integer.parseInt(epsgCode);
+                sourceProjection = epsgCode;
+                logger.info("检测到EPSG代码: {}", sourceSrid);
+                return;
+            }
+            
+            if (sourceCrsDef.contains("UTM")) {
+                if (sourceCrsDef.contains("south") || sourceCrsDef.contains("Southern")) {
+                    sourceProjection = "UTM_SOUTH";
+                } else {
+                    sourceProjection = "UTM_NORTH";
+                }
+                logger.info("检测到UTM投影");
+                return;
+            }
+            
+            if (sourceCrsDef.contains("Mercator") || sourceCrsDef.contains("mercator")) {
+                sourceProjection = "MERCATOR";
+                sourceSrid = 3857;
+                return;
+            }
+            
+            if (sourceCrsDef.contains("CGCS2000") || sourceCrsDef.contains("China_Geodetic_Coordinate_System_2000") 
+                || sourceCrsDef.contains("GCS_China_Geodetic") || sourceCrsDef.contains("CGCS_2000")) {
+                logger.info("检测到CGCS2000坐标系,与WGS84等价,无需转换");
+                sourceProjection = "CGCS2000";
+                sourceSrid = 4490;
+                return;
+            }
+            
+            if (sourceCrsDef.contains("WGS_1984") || sourceCrsDef.contains("WGS84") || sourceCrsDef.contains("GCS_WGS_1984")) {
+                logger.info("检测到WGS84坐标系,无需转换");
+                sourceProjection = "WGS84";
+                sourceSrid = 4326;
+                return;
+            }
+            
+            logger.warn("无法解析CRS定义,假设数据已经是WGS84坐标: {}", sourceCrsDef.substring(0, Math.min(100, sourceCrsDef.length())));
+            sourceProjection = null;
+            sourceSrid = 4326;
+            
+        } catch (Exception e) {
+            logger.error("初始化坐标转换失败: " + e.getMessage(), e);
+            sourceProjection = null;
+            sourceSrid = 4326;
+        }
+    }
+    
+    private String extractEpsgCode(String prjContent) {
+        if (prjContent == null) return null;
+        if (prjContent.contains("EPSG_")) {
+            int idx = prjContent.indexOf("EPSG_");
+            String num = prjContent.substring(idx + 5).trim();
+            int endIdx = num.indexOf(',');
+            if (endIdx > 0) num = num.substring(0, endIdx);
+            num = num.replaceAll("[^0-9]", "");
+            if (!num.isEmpty()) return num;
+        }
+        if (prjContent.contains("\"EPSG")) {
+            int idx = prjContent.indexOf("\"EPSG");
+            String num = prjContent.substring(idx + 5).replace("\"", "").trim();
+            int endIdx = num.indexOf(',');
+            if (endIdx > 0) num = num.substring(0, endIdx);
+            num = num.replaceAll("[^0-9]", "");
+            if (!num.isEmpty()) return num;
+        }
+        if (prjContent.contains("326") || prjContent.contains("327")) {
+            java.util.regex.Pattern p = java.util.regex.Pattern.compile("(32[567])(\\d{2})");
+            java.util.regex.Matcher m = p.matcher(prjContent);
+            if (m.find()) {
+                return m.group(1) + m.group(2);
+            }
+        }
+        return null;
+    }
+    
+    private double[] transformCoordinate(double x, double y) {
+        if (sourceProjection == null || "WGS84".equals(sourceProjection)) {
+            if (isValidWgs84Coordinate(x, y)) {
+                return new double[]{x, y};
+            }
+            logger.warn("坐标不在WGS84范围内: x={}, y={}", x, y);
+            return new double[]{x, y};
+        }
+        
+        try {
+            double lon, lat;
+            
+            if (sourceProjection.startsWith("326") || sourceProjection.startsWith("327")) {
+                int zone = Integer.parseInt(sourceProjection.substring(3));
+                boolean isNorth = sourceProjection.startsWith("326");
+                double[] result = utmToWgs84(x, y, zone, isNorth);
+                lon = result[0];
+                lat = result[1];
+            } else if ("MERCATOR".equals(sourceProjection)) {
+                double[] result = mercatorToWgs84(x, y);
+                lon = result[0];
+                lat = result[1];
+            } else if (sourceSrid == 3857 || sourceSrid == 900913) {
+                double[] result = mercatorToWgs84(x, y);
+                lon = result[0];
+                lat = result[1];
+            } else {
+                return new double[]{x, y};
+            }
+            
+            if (Double.isNaN(lon) || Double.isNaN(lat) || 
+                Double.isInfinite(lon) || Double.isInfinite(lat)) {
+                logger.warn("转换结果无效: x={}, y={}", x, y);
+                return new double[]{x, y};
+            }
+            
+            return new double[]{lon, lat};
+        } catch (Exception e) {
+            logger.error("坐标转换失败: x={}, y={}, error={}", x, y, e.getMessage());
+            return new double[]{x, y};
+        }
+    }
+    
+    private double[] utmToWgs84(double easting, double northing, int zone, boolean isNorth) {
+        double lat, lon;
+        
+        double fe = 500000.0;
+        double fn = isNorth ? 0.0 : 10000000.0;
+        double k0 = 0.9996;
+        double a = 6378137.0;
+        double e = 0.081819191;
+        double e1sq = 0.006739497;
+        
+        double M = (northing - fn) / k0;
+        double mu = M / (a * (1 - Math.pow(e, 2) / 4 - 3 * Math.pow(e, 4) / 64 - 5 * Math.pow(e, 6) / 256));
+        
+        double e1 = (1 - Math.sqrt(1 - e * e)) / (1 + Math.sqrt(1 - e * e));
+        double phi1 = mu + (3 * e1 / 2 - 27 * Math.pow(e1, 3) / 32) * Math.sin(2 * mu)
+                    + (21 * e1 * e1 / 16 - 55 * Math.pow(e1, 4) / 32) * Math.sin(4 * mu)
+                    + (151 * Math.pow(e1, 3) / 96) * Math.sin(6 * mu);
+        
+        double N1 = a / Math.sqrt(1 - Math.pow(e * Math.sin(phi1), 2));
+        double T1 = Math.tan(phi1) * Math.tan(phi1);
+        double C1 = e1sq * Math.cos(phi1) * Math.cos(phi1);
+        double R1 = a * (1 - e * e) / Math.pow(1 - Math.pow(e * Math.sin(phi1), 2), 1.5);
+        double D = (easting - fe) / (N1 * k0);
+        
+        lat = phi1 - (N1 * Math.tan(phi1) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * e1sq) * Math.pow(D, 4) / 24
+                + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * e1sq - 3 * C1 * C1) * Math.pow(D, 6) / 720);
+        
+        lon = (D - (1 + 2 * T1 + C1) * Math.pow(D, 3) / 6
+                + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * e1sq + 24 * T1 * T1) * Math.pow(D, 5) / 120) / Math.cos(phi1);
+        
+        lat = Math.toDegrees(lat);
+        lon = Math.toDegrees(lon);
+        lon = zone * 6 - 180 + lon;
+        
+        return new double[]{lon, lat};
+    }
+    
+    private double[] mercatorToWgs84(double x, double y) {
+        double lon = (x / 20037508.34) * 180;
+        double lat = (y / 20037508.34) * 180;
+        lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
+        return new double[]{lon, lat};
+    }
+    
+    private boolean isValidWgs84Coordinate(double x, double y) {
+        return x >= -180 && x <= 180 && y >= -90 && y <= 90;
+    }
+    
+    private String findPython() {
+        String[] possiblePaths = {
+            "python",
+            "python3",
+            "C:\\Python312\\python.exe",
+            "C:\\Python311\\python.exe",
+            "C:\\Python310\\python.exe",
+            "C:\\Python39\\python.exe",
+            "C:\\Program Files\\Python312\\python.exe",
+            "C:\\Program Files\\Python311\\python.exe",
+            "C:\\Program Files\\Python310\\python.exe",
+            "C:\\Users\\Public\\Documents\\MarvelousDesigner\\Configuration\\python311\\Lib\\venv\\scripts\\nt\\python.exe"
+        };
+        
+        for (String path : possiblePaths) {
+            try {
+                ProcessBuilder pb = new ProcessBuilder(path, "--version");
+                pb.redirectErrorStream(true);
+                Process p = pb.start();
+                int exitCode = p.waitFor();
+                if (exitCode == 0) {
+                    logger.info("找到Python: {}", path);
+                    return path;
+                }
+            } catch (Exception e) {
+            }
+        }
+        return null;
+    }
+    
+    private String createSampleGeoJson() {
+        return "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[116.4,39.9]},\"properties\":{\"name\":\"Sample Data\"}}]}";
+    }
+}

+ 45 - 0
ruoyi-admin/src/main/resources/shp2geojson.py

@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+import sys
+import os
+import json
+
+try:
+    import geopandas as gpd
+except ImportError:
+    print("ERROR: geopandas not installed")
+    sys.exit(1)
+
+def shp_to_geojson(shp_path, output_path):
+    """
+    SHP 转 GeoJSON(自动转换坐标系为 WGS84)
+    """
+    try:
+        gdf = gpd.read_file(shp_path)
+        
+        if gdf.crs is None:
+            print("WARNING: No CRS found in SHP file, assuming WGS84")
+            gdf = gdf.set_crs("EPSG:4326", allow_override=True)
+        elif gdf.crs != "EPSG:4326":
+            gdf = gdf.to_crs(epsg=4326)
+        
+        gdf.to_file(output_path, driver="GeoJSON", encoding="utf-8")
+        print(f"SUCCESS: Converted {shp_path} to {output_path}")
+        return True
+    except Exception as e:
+        print(f"ERROR: {str(e)}")
+        return False
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        print("Usage: python shp2geojson.py <input.shp> <output.geojson>")
+        sys.exit(1)
+    
+    shp_path = sys.argv[1]
+    output_path = sys.argv[2]
+    
+    if not os.path.exists(shp_path):
+        print(f"ERROR: SHP file not found: {shp_path}")
+        sys.exit(1)
+    
+    success = shp_to_geojson(shp_path, output_path)
+    sys.exit(0 if success else 1)

+ 16 - 0
ruoyi-admin/src/main/resources/sql/cesium_geojson.sql

@@ -0,0 +1,16 @@
+-- ----------------------------
+-- Table structure for cesium_geojson
+-- ----------------------------
+CREATE TABLE WATERSHED.CESIUM_GEOJSON (
+  id BIGINT IDENTITY(1,1) PRIMARY KEY,
+  user_id BIGINT NOT NULL,
+  layer_name VARCHAR(100),
+  file_name VARCHAR(200),
+  geojson_url VARCHAR(500),
+  original_data CLOB,
+  create_by VARCHAR(64) DEFAULT '',
+  create_time TIMESTAMP,
+  update_by VARCHAR(64) DEFAULT '',
+  update_time TIMESTAMP,
+  remark VARCHAR(500)
+);

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


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


+ 45 - 0
ruoyi-admin/target/classes/shp2geojson.py

@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+import sys
+import os
+import json
+
+try:
+    import geopandas as gpd
+except ImportError:
+    print("ERROR: geopandas not installed")
+    sys.exit(1)
+
+def shp_to_geojson(shp_path, output_path):
+    """
+    SHP 转 GeoJSON(自动转换坐标系为 WGS84)
+    """
+    try:
+        gdf = gpd.read_file(shp_path)
+        
+        if gdf.crs is None:
+            print("WARNING: No CRS found in SHP file, assuming WGS84")
+            gdf = gdf.set_crs("EPSG:4326", allow_override=True)
+        elif gdf.crs != "EPSG:4326":
+            gdf = gdf.to_crs(epsg=4326)
+        
+        gdf.to_file(output_path, driver="GeoJSON", encoding="utf-8")
+        print(f"SUCCESS: Converted {shp_path} to {output_path}")
+        return True
+    except Exception as e:
+        print(f"ERROR: {str(e)}")
+        return False
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        print("Usage: python shp2geojson.py <input.shp> <output.geojson>")
+        sys.exit(1)
+    
+    shp_path = sys.argv[1]
+    output_path = sys.argv[2]
+    
+    if not os.path.exists(shp_path):
+        print(f"ERROR: SHP file not found: {shp_path}")
+        sys.exit(1)
+    
+    success = shp_to_geojson(shp_path, output_path)
+    sys.exit(0 if success else 1)

+ 16 - 0
ruoyi-admin/target/classes/sql/cesium_geojson.sql

@@ -0,0 +1,16 @@
+-- ----------------------------
+-- Table structure for cesium_geojson
+-- ----------------------------
+CREATE TABLE WATERSHED.CESIUM_GEOJSON (
+  id BIGINT IDENTITY(1,1) PRIMARY KEY,
+  user_id BIGINT NOT NULL,
+  layer_name VARCHAR(100),
+  file_name VARCHAR(200),
+  geojson_url VARCHAR(500),
+  original_data CLOB,
+  create_by VARCHAR(64) DEFAULT '',
+  create_time TIMESTAMP,
+  update_by VARCHAR(64) DEFAULT '',
+  update_time TIMESTAMP,
+  remark VARCHAR(500)
+);

+ 0 - 3
ruoyi-admin/target/maven-archiver/pom.properties

@@ -1,3 +0,0 @@
-artifactId=ruoyi-admin
-groupId=com.ruoyi
-version=3.9.1

+ 1 - 0
ruoyi-admin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -15,6 +15,7 @@ com\ruoyi\web\controller\monitor\SysLogininforController.class
 com\ruoyi\web\controller\monitor\SysUserOnlineController.class
 com\ruoyi\web\controller\system\SysLoginController.class
 com\ruoyi\web\controller\monitor\CacheController.class
+com\ruoyi\web\controller\cesium\CesiumGeojsonController.class
 com\ruoyi\web\controller\system\SysNoticeController.class
 com\ruoyi\web\controller\system\SysDictTypeController.class
 com\ruoyi\web\controller\cesium\CesiumMapConfigController.class

+ 1 - 0
ruoyi-admin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -10,6 +10,7 @@ D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\web\controller\monitor\
 D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysIndexController.java
 D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysDictDataController.java
 D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysRegisterController.java
+D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\web\controller\cesium\CesiumGeojsonController.java
 D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\RuoYiApplication.java
 D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysUserController.java
 D:\Web\PlatformModel\ruoyi-admin\src\main\java\com\ruoyi\web\core\config\SwaggerConfig.java

BIN
ruoyi-admin/target/ruoyi-admin.jar


BIN
ruoyi-admin/target/ruoyi-admin.jar.original


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
ruoyi-admin/uploads/models/geojson/28bde72dd24946448f8607da98e9b137.geojson


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


+ 1 - 0
ruoyi-admin/uploads/models/geojson/28bde72dd24946448f8607da98e9b137/河道管理范围.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/28bde72dd24946448f8607da98e9b137/河道管理范围.shp


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


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11.geojson


BIN
ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11/水闸.dbf


+ 1 - 0
ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11/水闸.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/2996a06fc50c453aa337961cb3e33e11/水闸.shp


BIN
ruoyi-admin/uploads/models/geojson/2996a06fc50c453aa337961cb3e33e11/水闸.shx


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c.geojson


BIN
ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c/水库.dbf


+ 1 - 0
ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c/水库.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/5b7185e6c901473baf66926e37f2528c/水库.shp


BIN
ruoyi-admin/uploads/models/geojson/5b7185e6c901473baf66926e37f2528c/水库.shx


+ 1 - 0
ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd.geojson

@@ -0,0 +1 @@
+{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[0.0,0.0]},"properties":{"id":1}}]}

BIN
ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd/水闸.dbf


+ 1 - 0
ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd/水闸.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/9371a64443544a46affdade359485bdd/水闸.shp


BIN
ruoyi-admin/uploads/models/geojson/9371a64443544a46affdade359485bdd/水闸.shx


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
ruoyi-admin/uploads/models/geojson/fd35f1516d9144a3b97a78f55f06f219.geojson


BIN
ruoyi-admin/uploads/models/geojson/fd35f1516d9144a3b97a78f55f06f219.shp


BIN
ruoyi-common/target/ruoyi-common-3.9.1.jar


BIN
ruoyi-framework/target/ruoyi-framework-3.9.1.jar


BIN
ruoyi-generator/target/ruoyi-generator-3.9.1.jar


BIN
ruoyi-quartz/target/ruoyi-quartz-3.9.1.jar


+ 108 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/CesiumGeojson.java

@@ -0,0 +1,108 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+public class CesiumGeojson extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    @JsonProperty("id")
+    private Long id;
+
+    private Long userId;
+
+    @Excel(name = "图层名称")
+    private String layerName;
+
+    @Excel(name = "原始文件名")
+    private String fileName;
+
+    @Excel(name = "GeoJSON地址")
+    private String geojsonUrl;
+
+    @JsonProperty("originalData")
+    private String originalData;
+
+    public Long getId() 
+    {
+        return id;
+    }
+
+    public void setId(Long id) 
+    {
+        this.id = id;
+    }
+
+    public Long getUserId() 
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId) 
+    {
+        this.userId = userId;
+    }
+
+    public String getLayerName() 
+    {
+        return layerName;
+    }
+
+    public void setLayerName(String layerName) 
+    {
+        this.layerName = layerName;
+    }
+
+    public String getFileName() 
+    {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) 
+    {
+        this.fileName = fileName;
+    }
+
+    public String getGeojsonUrl() 
+    {
+        return geojsonUrl;
+    }
+
+    public void setGeojsonUrl(String geojsonUrl) 
+    {
+        this.geojsonUrl = geojsonUrl;
+    }
+
+    public String getOriginalData() 
+    {
+        return originalData;
+    }
+
+    public void setOriginalData(String originalData) 
+    {
+        this.originalData = originalData;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("userId", getUserId())
+            .append("layerName", getLayerName())
+            .append("fileName", getFileName())
+            .append("geojsonUrl", getGeojsonUrl())
+            .append("originalData", getOriginalData())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}

+ 20 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/CesiumGeojsonMapper.java

@@ -0,0 +1,20 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.CesiumGeojson;
+import org.apache.ibatis.annotations.Param;
+
+public interface CesiumGeojsonMapper
+{
+    public CesiumGeojson selectCesiumGeojsonById(Long id);
+
+    public List<CesiumGeojson> selectCesiumGeojsonList(CesiumGeojson cesiumGeojson);
+
+    public int insertCesiumGeojson(CesiumGeojson cesiumGeojson);
+
+    public int updateCesiumGeojson(CesiumGeojson cesiumGeojson);
+
+    public int deleteCesiumGeojsonById(Long id);
+
+    public int deleteCesiumGeojsonByIds(Long[] ids);
+}

+ 19 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/ICesiumGeojsonService.java

@@ -0,0 +1,19 @@
+package com.ruoyi.system.service;
+
+import java.util.List;
+import com.ruoyi.system.domain.CesiumGeojson;
+
+public interface ICesiumGeojsonService 
+{
+    public CesiumGeojson selectCesiumGeojsonById(Long id);
+
+    public List<CesiumGeojson> selectCesiumGeojsonList(CesiumGeojson cesiumGeojson);
+
+    public int insertCesiumGeojson(CesiumGeojson cesiumGeojson);
+
+    public int updateCesiumGeojson(CesiumGeojson cesiumGeojson);
+
+    public int deleteCesiumGeojsonByIds(Long[] ids);
+
+    public int deleteCesiumGeojsonById(Long id);
+}

+ 56 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CesiumGeojsonServiceImpl.java

@@ -0,0 +1,56 @@
+package com.ruoyi.system.service.impl;
+
+
+import java.util.List;
+import com.ruoyi.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.system.mapper.CesiumGeojsonMapper;
+import com.ruoyi.system.domain.CesiumGeojson;
+import com.ruoyi.system.service.ICesiumGeojsonService;
+
+@Service
+public class CesiumGeojsonServiceImpl implements ICesiumGeojsonService 
+{
+    @Autowired
+    private CesiumGeojsonMapper cesiumGeojsonMapper;
+
+    @Override
+    public CesiumGeojson selectCesiumGeojsonById(Long id)
+    {
+        return cesiumGeojsonMapper.selectCesiumGeojsonById(id);
+    }
+
+    @Override
+    public List<CesiumGeojson> selectCesiumGeojsonList(CesiumGeojson cesiumGeojson)
+    {
+        return cesiumGeojsonMapper.selectCesiumGeojsonList(cesiumGeojson);
+    }
+
+    @Override
+    public int insertCesiumGeojson(CesiumGeojson cesiumGeojson)
+    {
+        cesiumGeojson.setCreateTime(DateUtils.getNowDate());
+        cesiumGeojson.setUpdateTime(DateUtils.getNowDate());
+        return cesiumGeojsonMapper.insertCesiumGeojson(cesiumGeojson);
+    }
+
+    @Override
+    public int updateCesiumGeojson(CesiumGeojson cesiumGeojson)
+    {
+        cesiumGeojson.setUpdateTime(DateUtils.getNowDate());
+        return cesiumGeojsonMapper.updateCesiumGeojson(cesiumGeojson);
+    }
+
+    @Override
+    public int deleteCesiumGeojsonByIds(Long[] ids)
+    {
+        return cesiumGeojsonMapper.deleteCesiumGeojsonByIds(ids);
+    }
+
+    @Override
+    public int deleteCesiumGeojsonById(Long id)
+    {
+        return cesiumGeojsonMapper.deleteCesiumGeojsonById(id);
+    }
+}

+ 95 - 0
ruoyi-system/src/main/resources/mapper/system/CesiumGeojsonMapper.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.CesiumGeojsonMapper">
+
+    <resultMap type="com.ruoyi.system.domain.CesiumGeojson" id="CesiumGeojsonResult">
+        <result property="id"            column="id"              />
+        <result property="userId"        column="user_id"        />
+        <result property="layerName"      column="layer_name"     />
+        <result property="fileName"       column="file_name"      />
+        <result property="geojsonUrl"     column="geojson_url"    />
+        <result property="originalData"   column="original_data"  />
+        <result property="createBy"       column="create_by"      />
+        <result property="createTime"     column="create_time"    />
+        <result property="updateBy"       column="update_by"      />
+        <result property="updateTime"     column="update_time"    />
+        <result property="remark"         column="remark"         />
+    </resultMap>
+
+    <sql id="selectCesiumGeojsonVo">
+        select id, user_id, layer_name, file_name, geojson_url, original_data,
+               create_by, create_time, update_by, update_time, remark 
+        from cesium_geojson
+    </sql>
+
+    <select id="selectCesiumGeojsonById" parameterType="Long" resultMap="CesiumGeojsonResult">
+        <include refid="selectCesiumGeojsonVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectCesiumGeojsonList" parameterType="com.ruoyi.system.domain.CesiumGeojson" resultMap="CesiumGeojsonResult">
+        <include refid="selectCesiumGeojsonVo"/>
+        <where>  
+            <if test="userId != null"> and user_id = #{userId}</if>
+            <if test="layerName != null and layerName != ''"> and layer_name like concat('%', #{layerName}, '%')</if>
+            <if test="fileName != null and fileName != ''"> and file_name like concat('%', #{fileName}, '%')</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <insert id="insertCesiumGeojson" parameterType="com.ruoyi.system.domain.CesiumGeojson" useGeneratedKeys="true" keyProperty="id">
+        insert into cesium_geojson
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="layerName != null and layerName != ''">layer_name,</if>
+            <if test="fileName != null and fileName != ''">file_name,</if>
+            <if test="geojsonUrl != null and geojsonUrl != ''">geojson_url,</if>
+            <if test="originalData != null">original_data,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null and updateBy != ''">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null and remark != ''">remark,</if>
+        </trim>
+        <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="layerName != null and layerName != ''">#{layerName},</if>
+            <if test="fileName != null and fileName != ''">#{fileName},</if>
+            <if test="geojsonUrl != null and geojsonUrl != ''">#{geojsonUrl},</if>
+            <if test="originalData != null">#{originalData},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null and remark != ''">#{remark},</if>
+        </trim>
+    </insert>
+
+    <update id="updateCesiumGeojson" parameterType="com.ruoyi.system.domain.CesiumGeojson">
+        update cesium_geojson
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="layerName != null and layerName != ''">layer_name = #{layerName},</if>
+            <if test="fileName != null and fileName != ''">file_name = #{fileName},</if>
+            <if test="geojsonUrl != null and geojsonUrl != ''">geojson_url = #{geojsonUrl},</if>
+            <if test="originalData != null">original_data = #{originalData},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null and remark != ''">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCesiumGeojsonById" parameterType="Long">
+        delete from cesium_geojson where id = #{id}
+    </delete>
+
+    <delete id="deleteCesiumGeojsonByIds" parameterType="Long">
+        delete from cesium_geojson where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

BIN
ruoyi-system/target/classes/com/ruoyi/system/domain/CesiumGeojson.class


BIN
ruoyi-system/target/classes/com/ruoyi/system/mapper/CesiumGeojsonMapper.class


BIN
ruoyi-system/target/classes/com/ruoyi/system/service/ICesiumGeojsonService.class


BIN
ruoyi-system/target/classes/com/ruoyi/system/service/impl/CesiumGeojsonServiceImpl.class


+ 95 - 0
ruoyi-system/target/classes/mapper/system/CesiumGeojsonMapper.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.CesiumGeojsonMapper">
+
+    <resultMap type="com.ruoyi.system.domain.CesiumGeojson" id="CesiumGeojsonResult">
+        <result property="id"            column="id"              />
+        <result property="userId"        column="user_id"        />
+        <result property="layerName"      column="layer_name"     />
+        <result property="fileName"       column="file_name"      />
+        <result property="geojsonUrl"     column="geojson_url"    />
+        <result property="originalData"   column="original_data"  />
+        <result property="createBy"       column="create_by"      />
+        <result property="createTime"     column="create_time"    />
+        <result property="updateBy"       column="update_by"      />
+        <result property="updateTime"     column="update_time"    />
+        <result property="remark"         column="remark"         />
+    </resultMap>
+
+    <sql id="selectCesiumGeojsonVo">
+        select id, user_id, layer_name, file_name, geojson_url, original_data,
+               create_by, create_time, update_by, update_time, remark 
+        from cesium_geojson
+    </sql>
+
+    <select id="selectCesiumGeojsonById" parameterType="Long" resultMap="CesiumGeojsonResult">
+        <include refid="selectCesiumGeojsonVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectCesiumGeojsonList" parameterType="com.ruoyi.system.domain.CesiumGeojson" resultMap="CesiumGeojsonResult">
+        <include refid="selectCesiumGeojsonVo"/>
+        <where>  
+            <if test="userId != null"> and user_id = #{userId}</if>
+            <if test="layerName != null and layerName != ''"> and layer_name like concat('%', #{layerName}, '%')</if>
+            <if test="fileName != null and fileName != ''"> and file_name like concat('%', #{fileName}, '%')</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <insert id="insertCesiumGeojson" parameterType="com.ruoyi.system.domain.CesiumGeojson" useGeneratedKeys="true" keyProperty="id">
+        insert into cesium_geojson
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="layerName != null and layerName != ''">layer_name,</if>
+            <if test="fileName != null and fileName != ''">file_name,</if>
+            <if test="geojsonUrl != null and geojsonUrl != ''">geojson_url,</if>
+            <if test="originalData != null">original_data,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null and updateBy != ''">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null and remark != ''">remark,</if>
+        </trim>
+        <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="layerName != null and layerName != ''">#{layerName},</if>
+            <if test="fileName != null and fileName != ''">#{fileName},</if>
+            <if test="geojsonUrl != null and geojsonUrl != ''">#{geojsonUrl},</if>
+            <if test="originalData != null">#{originalData},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null and remark != ''">#{remark},</if>
+        </trim>
+    </insert>
+
+    <update id="updateCesiumGeojson" parameterType="com.ruoyi.system.domain.CesiumGeojson">
+        update cesium_geojson
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="layerName != null and layerName != ''">layer_name = #{layerName},</if>
+            <if test="fileName != null and fileName != ''">file_name = #{fileName},</if>
+            <if test="geojsonUrl != null and geojsonUrl != ''">geojson_url = #{geojsonUrl},</if>
+            <if test="originalData != null">original_data = #{originalData},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null and remark != ''">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCesiumGeojsonById" parameterType="Long">
+        delete from cesium_geojson where id = #{id}
+    </delete>
+
+    <delete id="deleteCesiumGeojsonByIds" parameterType="Long">
+        delete from cesium_geojson where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

+ 4 - 0
ruoyi-system/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -23,12 +23,14 @@ com\ruoyi\system\service\ISysUserOnlineService.class
 com\ruoyi\system\service\ISysPostService.class
 com\ruoyi\system\domain\SysCache.class
 com\ruoyi\system\mapper\SysPostMapper.class
+com\ruoyi\system\mapper\CesiumGeojsonMapper.class
 com\ruoyi\system\service\impl\SysMenuServiceImpl.class
 com\ruoyi\system\service\ISysMenuService.class
 com\ruoyi\system\domain\WatershedEquipment.class
 com\ruoyi\system\mapper\WatershedModelMapper.class
 com\ruoyi\system\service\impl\WatershedEquipmentServiceImpl.class
 com\ruoyi\system\domain\SysPost.class
+com\ruoyi\system\service\ICesiumGeojsonService.class
 com\ruoyi\system\service\impl\SysUserServiceImpl.class
 com\ruoyi\system\mapper\SysDictTypeMapper.class
 com\ruoyi\system\service\ISysLogininforService.class
@@ -44,6 +46,7 @@ com\ruoyi\system\domain\SysUserOnline.class
 com\ruoyi\system\service\impl\WatershedServiceServiceImpl.class
 com\ruoyi\system\mapper\SysUserRoleMapper.class
 com\ruoyi\system\service\impl\SysRoleServiceImpl.class
+com\ruoyi\system\service\impl\CesiumGeojsonServiceImpl.class
 com\ruoyi\system\service\impl\SysDictTypeServiceImpl.class
 com\ruoyi\system\service\IWatershedModelService.class
 com\ruoyi\system\domain\vo\RouterVo.class
@@ -66,4 +69,5 @@ com\ruoyi\system\service\impl\SysPostServiceImpl.class
 com\ruoyi\system\mapper\SysRoleMenuMapper.class
 com\ruoyi\system\domain\SysNotice.class
 com\ruoyi\system\mapper\CesiumMapConfigMapper.class
+com\ruoyi\system\domain\CesiumGeojson.class
 com\ruoyi\system\mapper\SysRoleDeptMapper.class

+ 4 - 0
ruoyi-system/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -24,6 +24,7 @@ D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\domain\SysRoleD
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysDeptServiceImpl.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\SysUserRoleMapper.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysUserOnlineServiceImpl.java
+D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\domain\CesiumGeojson.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\domain\WatershedEquipment.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\SysOperLogMapper.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\SysRoleDeptMapper.java
@@ -34,6 +35,7 @@ D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\CesiumMa
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysUserServiceImpl.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\IWatershedService.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\domain\SysPost.java
+D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\CesiumGeojsonMapper.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\SysDeptMapper.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\impl\WatershedModelServiceImpl.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysConfigServiceImpl.java
@@ -47,12 +49,14 @@ D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\SysUserM
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\ISysUserOnlineService.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\ISysDictDataService.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\domain\WatershedFacility.java
+D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\impl\CesiumGeojsonServiceImpl.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\impl\WatershedEquipmentServiceImpl.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\ISysUserService.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\SysRoleMapper.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\ISysNoticeService.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\ISysPostService.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\WatershedServiceMapper.java
+D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\service\ICesiumGeojsonService.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\mapper\WatershedModelMapper.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\domain\SysRoleMenu.java
 D:\Web\PlatformModel\ruoyi-system\src\main\java\com\ruoyi\system\domain\SysCache.java

BIN
ruoyi-system/target/ruoyi-system-3.9.1.jar


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است