Pārlūkot izejas kodu

添加水下地形,增加角度测量工具

WQQ 5 dienas atpakaļ
vecāks
revīzija
f4e0b06e5c

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 10 - 103
README.md


+ 197 - 4
RuoYi-Vue3/src/supermap-cesium-module/components/analysis_3d/measure/measure.js

@@ -14,12 +14,21 @@ function measure(props) {
         contourColor: '#ff7d00', //等高线颜色
         isShowDVH: false, //显示等高线界面
         isShowLine: true, //显示等高线
-        pickPointEnabled:true //开启顶点捕捉
+        pickPointEnabled:true, //开启顶点捕捉
+        isShowAnglePanel: false, //显示角度测量面板
+        angleMeasureMode: 'twoPoint', //角度测量模式:twoPoint-两点角度,threePoint-三点夹角
+        horizontalAngle: 0, //水平角(方位角)
+        verticalAngle: 0, //竖直角(俯仰角)
+        includedAngle: 0 //三点夹角
     });
 
 
     // 初始化数据
-    let layers, handlerDis, handlerArea, handlerHeight, isoline, lineHeight, setHypFlag,height_0= 0;
+    let layers, handlerDis, handlerArea, handlerHeight, handlerAngle, isoline, lineHeight, setHypFlag,height_0= 0;
+    // 角度测量相关变量
+    let anglePoints = []; //存储角度测量点
+    let angleLabels = []; //存储角度标签
+    let angleLines = []; //存储角度线
     // 等高线
     isoline = new Cesium.HypsometricSetting();
     isoline.DisplayMode = Cesium.HypsometricSettingEnum.DisplayMode.LINE;
@@ -144,9 +153,167 @@ function measure(props) {
             }
         });
 
-
+        //初始化角度测量
+        initAngleMeasure();
     };
 
+    // 初始化角度测量
+    function initAngleMeasure() {
+        viewer.eventManager.addEventListener("CLICK", handleAngleClick, false);
+    }
+
+    // 处理角度测量点击
+    function handleAngleClick(e) {
+        if (!state.isShowAnglePanel) return;
+        
+        let position = viewer.scene.pickPosition(e.message.position);
+        if (!position) return;
+
+        // 检查是否已经达到最大点数
+        if (state.angleMeasureMode === 'twoPoint' && anglePoints.length >= 2) {
+            return; // 两点角度测量已完成
+        }
+        if (state.angleMeasureMode === 'threePoint' && anglePoints.length >= 3) {
+            return; // 三点夹角测量已完成
+        }
+
+        anglePoints.push(position.clone());
+        
+        // 添加点标记
+        addAnglePoint(position);
+        
+        if (state.angleMeasureMode === 'twoPoint') {
+            // 两点角度测量
+            if (anglePoints.length >= 2) {
+                calculateTwoPointAngles();
+            }
+        } else {
+            // 三点夹角测量
+            if (anglePoints.length >= 3) {
+                calculateThreePointAngle();
+            }
+        }
+    }
+
+    // 添加角度测量点
+    function addAnglePoint(position) {
+        let pointEntity = viewer.entities.add({
+            position: position,
+            point: {
+                pixelSize: 8,
+                color: Cesium.Color.RED,
+                outlineColor: Cesium.Color.WHITE,
+                outlineWidth: 2
+            }
+        });
+        angleLabels.push(pointEntity);
+    }
+
+    // 计算两点角度(方位角和俯仰角)
+    function calculateTwoPointAngles() {
+        if (anglePoints.length < 2) return;
+        
+        let pointA = anglePoints[anglePoints.length - 2];
+        let pointB = anglePoints[anglePoints.length - 1];
+        
+        // 计算俯仰角(竖直角)
+        let pitchResult = tool.getPitch(pointA, pointB);
+        state.verticalAngle = pitchResult.angle.toFixed(2);
+        
+        // 计算方位角(水平角)
+        let azimuthResult = tool.getAngleAndRadian(pointA, pointB);
+        state.horizontalAngle = azimuthResult.angle.toFixed(2);
+        
+        // 绘制连接线
+        drawAngleLine(pointA, pointB);
+        
+        // 添加角度标签
+        addAngleLabel(pointB, `方位角: ${state.horizontalAngle}°\n俯仰角: ${state.verticalAngle}°`);
+    }
+
+    // 计算三点夹角
+    function calculateThreePointAngle() {
+        if (anglePoints.length < 3) return;
+        
+        let pointA = anglePoints[anglePoints.length - 3];
+        let pointB = anglePoints[anglePoints.length - 2];
+        let pointC = anglePoints[anglePoints.length - 1];
+        
+        // 计算向量BA和BC
+        let vectorBA = Cesium.Cartesian3.subtract(pointA, pointB, new Cesium.Cartesian3());
+        let vectorBC = Cesium.Cartesian3.subtract(pointC, pointB, new Cesium.Cartesian3());
+        
+        // 归一化向量
+        Cesium.Cartesian3.normalize(vectorBA, vectorBA);
+        Cesium.Cartesian3.normalize(vectorBC, vectorBC);
+        
+        // 计算点积
+        let dotProduct = Cesium.Cartesian3.dot(vectorBA, vectorBC);
+        
+        // 计算夹角(弧度转角度)
+        let angleRad = Math.acos(Math.max(-1, Math.min(1, dotProduct)));
+        let angleDeg = Cesium.Math.toDegrees(angleRad);
+        
+        state.includedAngle = angleDeg.toFixed(2);
+        
+        // 绘制夹角线
+        drawAngleLine(pointA, pointB);
+        drawAngleLine(pointB, pointC);
+        
+        // 添加夹角标签
+        addAngleLabel(pointB, `夹角: ${state.includedAngle}°`);
+    }
+
+    // 绘制角度线
+    function drawAngleLine(start, end) {
+        let lineEntity = viewer.entities.add({
+            polyline: {
+                positions: [start, end],
+                width: 2,
+                color: Cesium.Color.YELLOW,
+                clampToGround: false
+            }
+        });
+        angleLines.push(lineEntity);
+    }
+
+    // 添加角度标签
+    function addAngleLabel(position, text) {
+        let labelEntity = viewer.entities.add({
+            position: position,
+            label: {
+                text: text,
+                font: '14px sans-serif',
+                fillColor: Cesium.Color.WHITE,
+                outlineColor: Cesium.Color.BLACK,
+                outlineWidth: 2,
+                style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+                pixelOffset: new Cesium.Cartesian2(15, 0),
+                eyeOffset: new Cesium.Cartesian3(0, 0, 50)
+            }
+        });
+        angleLabels.push(labelEntity);
+    }
+
+    // 清除角度测量
+    function clearAngleMeasure() {
+        anglePoints = [];
+        
+        angleLabels.forEach(entity => {
+            viewer.entities.remove(entity);
+        });
+        angleLabels = [];
+        
+        angleLines.forEach(entity => {
+            viewer.entities.remove(entity);
+        });
+        angleLines = [];
+        
+        state.horizontalAngle = 0;
+        state.verticalAngle = 0;
+        state.includedAngle = 0;
+    }
+
     if (storeState.isViewer) {
         init();
     }
@@ -346,6 +513,26 @@ function measure(props) {
         height_0 = p[2];
     }
 
+    // 角度测量相关函数
+    function twoPointAngleClk() {
+        deactiveAll();
+        state.isShowAnglePanel = true;
+        state.angleMeasureMode = 'twoPoint';
+        clearAngleMeasure();
+    };
+
+    function threePointAngleClk() {
+        deactiveAll();
+        state.isShowAnglePanel = true;
+        state.angleMeasureMode = 'threePoint';
+        clearAngleMeasure();
+    };
+
+    function clearAngleClk() {
+        clearAngleMeasure();
+        state.isShowAnglePanel = false;
+    };
+
     // 清除
     function clearAll() {
         deactiveAll();
@@ -353,6 +540,8 @@ function measure(props) {
         handlerArea && handlerArea.clear();
         handlerHeight && handlerHeight.clear();
         clearLine();
+        clearAngleMeasure();
+        state.isShowAnglePanel = false;
     };
     //   清除等值线
     function clearLine() {
@@ -366,6 +555,7 @@ function measure(props) {
         state.isShowDVH = false;
         state.Ellipsoid = null;
         lineHeight = -10000;
+        state.isShowAnglePanel = false;
     };
     function clear() {
         clearAll();
@@ -417,7 +607,10 @@ function measure(props) {
         distanceClk,
         areaClk,
         heightClk,
-        clear
+        clear,
+        twoPointAngleClk,
+        threePointAngleClk,
+        clearAngleClk
     };
 };
 

+ 55 - 2
RuoYi-Vue3/src/supermap-cesium-module/components/analysis_3d/measure/measure.vue

@@ -46,6 +46,31 @@
       >
         <span class="iconfont iconkongjianmianji"></span>
       </button>
+
+      <!-- 角度测量按钮 -->
+      <button
+        @click="twoPointAngleClk"
+        class="sm-btn"
+        id="twoPointAngle"
+        :title="Resource.twoPointAngle"
+        type="button"
+        :disabled="measureMode === 'null'"
+        :class="measureMode === 'null'?'pointer-events':''"
+      >
+        <span style="font-size: 12px;">两点角</span>
+      </button>
+      <button
+        @click="threePointAngleClk"
+        class="sm-btn"
+        id="threePointAngle"
+        :title="Resource.threePointAngle"
+        type="button"
+        :disabled="measureMode === 'null'"
+        :class="measureMode === 'null'?'pointer-events':''"
+      >
+        <span style="font-size: 12px;">三点角</span>
+      </button>
+
       <button @click="clear" class="sm-btn" id="clear" :title="Resource.clear" type="button">
         <span class="iconfont iconqingchu"></span>
       </button>
@@ -54,6 +79,18 @@
         <input checked id="showLine" style="margin-left: 6px" type="checkbox" v-model="isShowLine" />
         <span style="margin-left: 10%">{{ Resource.showContour }}</span>
       </div>
+
+      <!-- 角度测量结果面板 -->
+      <div class="sm-half-L flex-start" style="margin-top: 6px; padding: 6px; border: 1px solid #ccc; border-radius: 4px;" v-show="isShowAnglePanel">
+        <div v-if="angleMeasureMode === 'twoPoint'" style="font-size: 12px; color: #333;">
+          <div style="margin-bottom: 4px;">{{ Resource.horizontalAngle }}({{ Resource.azimuth }}): <span style="color: #1890ff;">{{ horizontalAngle }}°</span></div>
+          <div>{{ Resource.verticalAngle }}({{ Resource.pitchAngle }}): <span style="color: #1890ff;">{{ verticalAngle }}°</span></div>
+        </div>
+        <div v-else style="font-size: 12px; color: #333;">
+          <div>{{ Resource.includedAngle }}: <span style="color: #1890ff;">{{ includedAngle }}°</span></div>
+        </div>
+        <button @click="clearAngleClk" style="margin-top: 8px; padding: 2px 8px; font-size: 11px;" class="sm-btn">{{ Resource.clearAngle }}</button>
+      </div>
     </div>
   </div>
 </template>
@@ -69,10 +106,18 @@ export default {
       isShowDVH, //显示勾选界面
       interval, //等值线距
       isShowLine, //显示等高线
+      isShowAnglePanel, //显示角度测量面板
+      angleMeasureMode, //角度测量模式
+      horizontalAngle, //水平角(方位角)
+      verticalAngle, //竖直角(俯仰角)
+      includedAngle, //三点夹角
       distanceClk, //点击测距函数
       areaClk, //点击测面
       heightClk, //点击测高
-      clear //清除
+      clear, //清除
+      twoPointAngleClk, //两点角度测量
+      threePointAngleClk, //三点夹角测量
+      clearAngleClk //清除角度测量
     } = measure(props);
 
     return {
@@ -80,10 +125,18 @@ export default {
       isShowDVH, //显示勾选界面
       interval, //等值线距
       isShowLine, //显示等高线
+      isShowAnglePanel,
+      angleMeasureMode,
+      horizontalAngle,
+      verticalAngle,
+      includedAngle,
       distanceClk,
       areaClk,
       heightClk,
-      clear
+      clear,
+      twoPointAngleClk,
+      threePointAngleClk,
+      clearAngleClk
     };
   }
 };

+ 44 - 5
RuoYi-Vue3/src/supermap-cesium-module/components/typhoon-visualization/TyphoonVisualization.vue

@@ -654,21 +654,46 @@ const addTyphoonCircle = () => {
   });
 };
 
-// 获取台风风圈多边形点
+// 获取台风风圈多边形点(增加验证)
 const getTyphoonPolygonPoints = (pointObj, cNum) => {
+  // 验证输入参数
+  if (!pointObj || !pointObj[cNum]) {
+    console.warn('台风风圈数据无效');
+    return [];
+  }
+  
   const points = [];
-  const center = [Number(pointObj.lon), Number(pointObj.lat)];
+  const centerLon = Number(pointObj.lon);
+  const centerLat = Number(pointObj.lat);
+  
+  // 验证中心点坐标
+  if (isNaN(centerLon) || isNaN(centerLat) || !isFinite(centerLon) || !isFinite(centerLat)) {
+    console.warn('台风中心点坐标无效');
+    return [];
+  }
+  
+  const center = [centerLon, centerLat];
   const radiusList = [
     pointObj[cNum].radius1,
     pointObj[cNum].radius2,
     pointObj[cNum].radius3,
     pointObj[cNum].radius4
   ];
+  
+  // 验证半径数据
+  for (let i = 0; i < radiusList.length; i++) {
+    const radius = Number(radiusList[i]);
+    if (isNaN(radius) || !isFinite(radius) || radius < 0) {
+      console.warn(`台风风圈半径数据无效: ${radiusList[i]}`);
+      return [];
+    }
+  }
+  
   const startAngleList = [0, 90, 180, 270];
   let fx, fy;
 
   startAngleList.forEach((startAngle, index) => {
-    const radius = radiusList[index] / 100;
+    const radius = Number(radiusList[index]) / 100;
     const pointNum = 90;
     const endAngle = startAngle + 90;
 
@@ -678,7 +703,11 @@ const getTyphoonPolygonPoints = (pointObj, cNum) => {
       const cos = Math.cos((angle * Math.PI) / 180);
       const x = center[0] + radius * sin;
       const y = center[1] + radius * cos;
-      points.push(x, y);
+      
+      // 验证生成的点坐标
+      if (!isNaN(x) && !isNaN(y) && isFinite(x) && isFinite(y)) {
+        points.push(x, y);
+      }
 
       if (startAngle === 0 && i === 0) {
         fx = x;
@@ -687,7 +716,17 @@ const getTyphoonPolygonPoints = (pointObj, cNum) => {
     }
   });
 
-  points.push(fx, fy);
+  // 确保闭合多边形
+  if (!isNaN(fx) && !isNaN(fy)) {
+    points.push(fx, fy);
+  }
+  
+  // 确保至少有6个值(3个点)
+  if (points.length < 6) {
+    console.warn('台风风圈多边形点数量不足');
+    return [];
+  }
+  
   return points;
 };
 

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

@@ -120,6 +120,24 @@ function initViewer(props, callback) {
   window.scene = viewer.scene;
   scene.moon.show = false;
   viewer.eventManager = new EventManager(viewer);  //添加事件管理派发
+  
+  // ========== 关键修复:全局渲染错误捕获,防止渲染停止 ==========
+  // 捕获渲染错误,返回true表示已处理,Cesium会继续渲染
+  viewer.scene.renderError.addEventListener((scene, error) => {
+    console.warn('Cesium渲染错误(全局捕获):', error.message, error);
+    // 返回true表示错误已处理,Cesium会继续渲染循环
+    return true;
+  });
+  
+  // 捕获WebGL上下文丢失错误
+  viewer.scene.context.canvas.addEventListener('webglcontextlost', (event) => {
+    console.error('WebGL上下文丢失:', event);
+    event.preventDefault();
+  }, false);
+  
+  viewer.scene.context.canvas.addEventListener('webglcontextrestored', () => {
+    console.log('WebGL上下文已恢复');
+  }, false);
   let widget = viewer.cesiumWidget;
   actions.setIsViewer(true);  //初始化viewer标志 
   if (viewer.geocoder) {

+ 17 - 0
RuoYi-Vue3/src/supermap-cesium-module/config/server_config.js

@@ -54,6 +54,23 @@ export default [
                 layers: [{ type: 'IMG', layerName: 'image' }, { type: 'TERRAIN', layerName: 'srtm_54_07%40zhufeng' }],
                 state: 0
             },
+            // 水下地形(超图iServer本地发布的3D-SXDX_DEM服务),
+            // 沿用珠峰"在已有地形上叠加另一层"的做法:作为 REALSPACE 场景加载,
+            // 内部会调用 addScene -> viewer.scene.open(),以 S3M 图层形式叠加到现有地形之上
+            // useServerView: true 表示使用超图服务端默认视口
+            // transparentColor: '#000000' 表示将黑色设为透明色
+            // transparentColorTolerance: 0.2 增大容差值以处理接近黑色的区域
+            {
+                type: "REALSPACE",
+                thumbnail: "/img/webServeImg/珠峰.png",
+                proxiedUrl: "http://localhost:8090/iserver/services/SXDX-yx/rest/realspace",
+                name: "水下地形",
+                layers: [{ type: 'S3M', layerName: 'SXDX@fujian' }],
+                state: 0,
+                useServerView: true,
+                transparentColor: '#000000',
+                transparentColorTolerance: 0.05
+            },
             {
                 type: "REALSPACE",
                 thumbnail: "/img/webServeImg/点云.png",

+ 9 - 0
RuoYi-Vue3/src/supermap-cesium-module/config/server_position_config.js

@@ -30,6 +30,15 @@ export default {
         pitch: -0.4335396082759968,
         roll: 9.618661422905461e-10
     },
+    // 水下地形飞向位置(指向福建沿海区域,经纬度 119.3379, 25.7021)
+    '水下地形': {
+        longitude: 119.3379,
+        latitude: 25.7021,
+        height: 50000,
+        heading: 0.55,
+        pitch: -0.45,
+        roll: 0
+    },
     '四姑娘山': {
         Cartesian3: { x: -1225751.214237253, y: 5341398.693545163, z: 3283081.9250365244 },
         heading: 0.5992931591902016,

+ 183 - 6
RuoYi-Vue3/src/supermap-cesium-module/js/common/layerManagement.js

@@ -23,6 +23,10 @@ function addS3mLayers(scps, callback) {
 
 // 添加场景
 function addScene(url, options, callback) {
+    console.log('===== addScene 开始 =====');
+    console.log('URL:', url);
+    console.log('选项:', options);
+    
     if (options && options.SceneToken) {
         Cesium.Credential.CREDENTIAL = new Cesium.Credential(options.SceneToken);
     }
@@ -30,17 +34,130 @@ function addScene(url, options, callback) {
     if (options && options.autoSetView !== undefined) {
         flag = options.autoSetView
     }
+    // 提取透明色配置
+    const transparentColor = options && options.transparentColor;
+    const transparentColorTolerance = options && options.transparentColorTolerance || 0.01;
+    
+    console.log('transparentColor:', transparentColor);
+    console.log('transparentColorTolerance:', transparentColorTolerance);
+    
     if (checkURL(url)) {
         try {
+            console.log('开始调用 viewer.scene.open');
             let s = [viewer.scene.open(url, undefined, { 'autoSetView': flag })];
-            promiseWhen(s, callback, 'SCENE');
+            promiseWhen(s, function(layers, type) {
+                console.log('promiseWhen 回调触发');
+                console.log('回调参数layers:', layers);
+                console.log('viewer.scene.layers:', viewer.scene.layers);
+                
+                // 如果配置了透明色,在图层加载完成后设置
+                if (transparentColor) {
+                    console.log('准备设置透明色');
+                    
+                    // 尝试从多个来源获取图层
+                    let targetLayers = [];
+                    
+                    // 1. 尝试从回调参数获取(处理嵌套数组的情况)
+                    if (layers && layers.length > 0) {
+                        console.log('从回调参数获取图层');
+                        // 处理嵌套数组:layers 可能是 [Array(1)],真正的图层在 layers[0] 中
+                        // 影像图层可能有 transparentBackColor 属性
+                        if (layers[0] && layers[0].length > 0) {
+                            targetLayers = layers[0];
+                            console.log('图层在嵌套数组中,已提取');
+                        } else if (layers[0]) {
+                            targetLayers = layers;
+                        }
+                    }
+                    // 2. 尝试从图层队列获取
+                    else if (viewer.scene.layers && viewer.scene.layers.layerQueue && viewer.scene.layers.layerQueue.length > 0) {
+                        console.log('从layerQueue获取图层');
+                        targetLayers = viewer.scene.layers.layerQueue;
+                    }
+                    // 3. 尝试从_layers属性获取(可能是Map或Set)
+                    else if (viewer.scene.layers && viewer.scene.layers._layers) {
+                        console.log('从_layers属性获取图层');
+                        const layersObj = viewer.scene.layers._layers;
+                        if (layersObj && layersObj.length > 0) {
+                            targetLayers = Array.from(layersObj);
+                        } else if (layersObj && layersObj.values) {
+                            targetLayers = Array.from(layersObj.values());
+                        } else if (layersObj && typeof layersObj === 'object') {
+                            targetLayers = Object.values(layersObj);
+                        }
+                    }
+                    // 4. 尝试从viewer.imageryLayers获取(影像图层可能在这里)
+                    else if (viewer.imageryLayers && viewer.imageryLayers.length > 0) {
+                        console.log('从viewer.imageryLayers获取图层');
+                        targetLayers = [];
+                        for (let i = 0; i < viewer.imageryLayers.length; i++) {
+                            targetLayers.push(viewer.imageryLayers.get(i));
+                        }
+                    }
+                    
+                    console.log('最终目标图层数组:', targetLayers);
+                    
+                    if (targetLayers.length > 0) {
+                        console.log('找到图层,开始设置透明色');
+                        targetLayers.forEach(layer => {
+                            console.log('图层名称:', layer._name || layer.name);
+                            console.log('图层类型:', layer.constructor.name);
+                            
+                            const colorObj = Cesium.Color.fromCssColorString(transparentColor);
+                            
+                            if (layer && layer.style3D) {
+                                // S3M图层
+                                layer.style3D.transparentColor = colorObj;
+                                layer.style3D.transparentColorTolerance = transparentColorTolerance;
+                                layer.style3D.enableTransparent = true;
+                                layer.refresh();
+                                console.log('S3M图层透明色设置成功:', transparentColor);
+                            } else if (layer && layer.imageryLayer) {
+                                // 包装的影像图层
+                                layer.imageryLayer.transparentBackColor = colorObj;
+                                layer.imageryLayer.transparentBackColorTolerance = transparentColorTolerance;
+                                console.log('包装影像图层透明色设置成功:', transparentColor);
+                            } else if (layer && 'transparentBackColor' in layer) {
+                                // 直接是影像图层(使用transparentBackColor属性)
+                                layer.transparentBackColor = colorObj;
+                                layer.transparentBackColorTolerance = transparentColorTolerance;
+                                console.log('影像图层透明色设置成功:', transparentColor);
+                                console.log('设置的transparentBackColor:', layer.transparentBackColor);
+                                console.log('设置的transparentBackColorTolerance:', layer.transparentBackColorTolerance);
+                            } else if (layer && layer._imageryProvider) {
+                                // 尝试在imageryProvider上设置
+                                if (layer._imageryProvider && 'transparentColor' in layer._imageryProvider) {
+                                    layer._imageryProvider.transparentColor = colorObj;
+                                    layer._imageryProvider.transparentColorTolerance = transparentColorTolerance;
+                                    console.log('ImageryProvider透明色设置成功:', transparentColor);
+                                } else {
+                                    console.log('图层有_imageryProvider但不支持透明色设置');
+                                }
+                            } else {
+                                console.log('图层不支持透明色设置,尝试输出图层详情:', layer);
+                                if (layer) {
+                                    console.log('图层所有属性:', Object.keys(layer));
+                                }
+                            }
+                        });
+                    } else {
+                        console.log('未找到任何图层');
+                    }
+                } else {
+                    console.log('没有配置透明色,跳过设置');
+                }
+                callback(layers, type);
+            }, 'SCENE');
         } catch (e) {
+            console.error('addScene 错误:', e);
             let widget = viewer.cesiumWidget;
             if (widget._showRenderLoopErrors) {
                 let title = "渲染时发生错误,已停止渲染。";
                 widget.showErrorPanel(title, undefined, e);
             }
         }
+    } else {
+        console.log('URL检查失败');
     }
 };
 
@@ -198,6 +315,59 @@ function addMvtLayer(LayerURL, name, callback) {
     }
 };
 
+// 验证GeoJSON坐标数据
+function validateGeoJsonCoordinates(coordinates, geometryType) {
+    try {
+        if (!coordinates || !Array.isArray(coordinates)) {
+            return false;
+        }
+
+        let coordArrays = [];
+
+        if (geometryType === 'Polygon') {
+            // Polygon: [[[lon, lat], ...]]
+            if (Array.isArray(coordinates[0]) && Array.isArray(coordinates[0][0])) {
+                coordArrays = coordinates[0];
+            }
+        } else if (geometryType === 'MultiPolygon') {
+            // MultiPolygon: [[[[lon, lat], ...]]]
+            if (Array.isArray(coordinates[0]) && Array.isArray(coordinates[0][0]) && Array.isArray(coordinates[0][0][0])) {
+                coordArrays = coordinates[0][0];
+            }
+        } else if (geometryType === 'LineString') {
+            // LineString: [[lon, lat], ...]
+            if (Array.isArray(coordinates[0])) {
+                coordArrays = coordinates;
+            }
+        } else if (geometryType === 'Point') {
+            // Point: [lon, lat]
+            if (typeof coordinates[0] === 'number') {
+                return typeof coordinates[0] === 'number' && typeof coordinates[1] === 'number' &&
+                    !isNaN(coordinates[0]) && !isNaN(coordinates[1]) &&
+                    Math.abs(coordinates[0]) <= 180 && Math.abs(coordinates[1]) <= 90;
+            }
+        }
+
+        for (const coord of coordArrays) {
+            if (!coord || !Array.isArray(coord) || coord.length < 2) {
+                return false;
+            }
+            const lon = coord[0];
+            const lat = coord[1];
+            if (typeof lon !== 'number' || typeof lat !== 'number' ||
+                isNaN(lon) || isNaN(lat) || !isFinite(lon) || !isFinite(lat) ||
+                Math.abs(lon) > 180 || Math.abs(lat) > 90) {
+                return false;
+            }
+        }
+
+        return coordArrays.length >= 2; // 至少需要2个点(线)或3个点(面)
+    } catch (e) {
+        console.error('验证GeoJSON坐标失败:', e);
+        return false;
+    }
+}
+
 // 加载GeoJson图层
 function addGeoJsonLayer(url, name, callback) {
     try {
@@ -215,16 +385,23 @@ function addGeoJsonLayer(url, name, callback) {
         });
 
         Cesium.when(loadPromise, function (dataSource) {
-            viewer.dataSources.add(dataSource);
-            processGeoJsonEntities(dataSource, name);
-            viewer.flyTo(dataSource, { duration: 2 });
-            actions.setChangeLayers();
-            if (callback) callback(dataSource);
+            try {
+                viewer.dataSources.add(dataSource);
+                processGeoJsonEntities(dataSource, name);
+                viewer.flyTo(dataSource, { duration: 2 });
+                actions.setChangeLayers();
+                if (callback) callback(dataSource);
+            } catch (e) {
+                console.error("添加GeoJSON数据源失败:", e);
+                if (callback) callback(null, e);
+            }
         }, function (error) {
             console.error("加载GeoJSON失败:", error);
+            if (callback) callback(null, error);
         });
     } catch (e) {
         console.error("加载GeoJSON异常:", e);
+        if (callback) callback(null, e);
     }
 };
 

+ 119 - 13
RuoYi-Vue3/src/supermap-cesium-module/js/common/waterLayer.js

@@ -1,5 +1,69 @@
 import { actions } from '../store/store.js';
 
+// 验证并清理坐标数据,移除无效坐标和重复点
+function validateAndCleanCoordinates(coords) {
+    if (!coords || !Array.isArray(coords)) {
+        return [];
+    }
+    
+    const validCoords = [];
+    
+    for (let i = 0; i < coords.length; i++) {
+        const coord = coords[i];
+        
+        // 验证坐标是否有效
+        if (!coord || !Array.isArray(coord) || coord.length < 2) {
+            console.warn('跳过无效坐标:', coord);
+            continue;
+        }
+        
+        const lon = coord[0];
+        const lat = coord[1];
+        
+        // 检查坐标值是否为有效数字
+        if (typeof lon !== 'number' || typeof lat !== 'number') {
+            console.warn('跳过错类型坐标:', coord);
+            continue;
+        }
+        
+        // 检查是否为 NaN 或 Infinity
+        if (isNaN(lon) || isNaN(lat) || !isFinite(lon) || !isFinite(lat)) {
+            console.warn('跳过NaN或Infinity坐标:', coord);
+            continue;
+        }
+        
+        // 检查经纬度范围是否合理
+        if (Math.abs(lon) > 180 || Math.abs(lat) > 90) {
+            console.warn('坐标超出有效范围:', coord);
+            continue;
+        }
+        
+        // 检查是否与前一个点重复
+        const lastCoord = validCoords[validCoords.length - 1];
+        if (lastCoord) {
+            const lastLon = lastCoord[0];
+            const lastLat = lastCoord[1];
+            if (Math.abs(lon - lastLon) < 1e-9 && Math.abs(lat - lastLat) < 1e-9) {
+                console.warn('跳过重复坐标:', coord);
+                continue;
+            }
+        }
+        
+        validCoords.push([lon, lat]);
+    }
+    
+    // 确保多边形闭合(如果最后一个点与第一个点不同,添加第一个点)
+    if (validCoords.length >= 3) {
+        const first = validCoords[0];
+        const last = validCoords[validCoords.length - 1];
+        if (Math.abs(first[0] - last[0]) >= 1e-9 || Math.abs(first[1] - last[1]) >= 1e-9) {
+            validCoords.push([first[0], first[1]]);
+        }
+    }
+    
+    return validCoords;
+}
+
 function loadWaterLayer(url, name, callback) {
     try {
         fetch(url)
@@ -28,19 +92,24 @@ function loadWaterLayer(url, name, callback) {
                     const outerRing = polygonCoords[0];
                     if (!outerRing || outerRing.length === 0) return;
                     
+                    // 验证坐标数据
+                    const validCoords = validateAndCleanCoordinates(outerRing);
+                    if (validCoords.length < 3) {
+                        console.warn('多边形顶点数不足,跳过此多边形');
+                        return;
+                    }
+                    
                     const positions = [];
                     const cartographics = [];
                     
-                    for (let i = 0; i < outerRing.length; i++) {
-                        const coord = outerRing[i];
+                    for (let i = 0; i < validCoords.length; i++) {
+                        const coord = validCoords[i];
                         const longitude = coord[0];
                         const latitude = coord[1];
                         const cartographic = Cesium.Cartographic.fromDegrees(longitude, latitude, 0);
                         cartographics.push(cartographic);
                     }
                     
-                    if (cartographics.length < 3) return;
-                    
                     let terrainHeight = 0;
                     try {
                         const sampledPositions = await Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, cartographics);
@@ -50,21 +119,58 @@ function loadWaterLayer(url, name, callback) {
                         console.warn('地形采样失败,使用默认高度:', e);
                     }
                     
+                    // 验证地形高度是否有效
+                    if (!isFinite(terrainHeight) || isNaN(terrainHeight)) {
+                        console.warn('地形高度无效,使用默认值 0');
+                        terrainHeight = 0;
+                    }
+                    
                     const waterHeight = terrainHeight - 22;
                     
-                    for (let i = 0; i < outerRing.length; i++) {
-                        const coord = outerRing[i];
+                    // 验证水面高度是否有效
+                    if (!isFinite(waterHeight) || isNaN(waterHeight)) {
+                        console.warn('水面高度无效,使用默认值 -22');
+                        waterHeight = -22;
+                    }
+                    
+                    // 限制水面高度范围,避免极端值
+                    const minWaterHeight = -1000;
+                    const maxWaterHeight = 10000;
+                    const clampedWaterHeight = Math.max(minWaterHeight, Math.min(waterHeight, maxWaterHeight));
+                    
+                    for (let i = 0; i < validCoords.length; i++) {
+                        const coord = validCoords[i];
                         const longitude = coord[0];
                         const latitude = coord[1];
-                        positions.push(Cesium.Cartesian3.fromDegrees(longitude, latitude, waterHeight));
+                        const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, clampedWaterHeight);
+                        
+                        // 验证每个位置是否有效
+                        if (!Cesium.Cartesian3.validate(position)) {
+                            console.warn('创建无效位置,跳过此点:', coord);
+                            continue;
+                        }
+                        
+                        positions.push(position);
                     }
                     
-                    const polygonGeometry = new Cesium.PolygonGeometry({
-                        polygonHierarchy: new Cesium.PolygonHierarchy(positions),
-                        height: waterHeight,
-                        extrudedHeight: waterHeight,
-                        vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
-                    });
+                    // 验证最终位置数组
+                    if (positions.length < 3) {
+                        console.warn('有效位置不足3个,跳过此多边形');
+                        return;
+                    }
+                    
+                    let polygonGeometry;
+                    try {
+                        polygonGeometry = new Cesium.PolygonGeometry({
+                            polygonHierarchy: new Cesium.PolygonHierarchy(positions),
+                            height: waterHeight,
+                            extrudedHeight: waterHeight,
+                            vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
+                        });
+                    } catch (e) {
+                        console.error('创建多边形几何体失败:', e);
+                        return;
+                    }
                     
                     const geometryInstance = new Cesium.GeometryInstance({
                         geometry: polygonGeometry,

+ 12 - 0
RuoYi-Vue3/src/supermap-cesium-module/resource/resourceCN.js

@@ -304,4 +304,16 @@ export default {
     startAngle: '开始角度',
     endAngle: '终止角度',
     isClosed: '是否封口',
+    
+    // 角度测量相关
+    measureAngle: '角度测量',
+    twoPointAngle: '两点角度',
+    threePointAngle: '三点夹角',
+    horizontalAngle: '水平角',
+    verticalAngle: '竖直角',
+    azimuth: '方位角',
+    pitchAngle: '俯仰角',
+    includedAngle: '夹角',
+    angleUnit: '度',
+    clearAngle: '清除角度',
 }

+ 1 - 1
RuoYi-Vue3/src/supermap-cesium-module/style/components.scss

@@ -24,7 +24,7 @@
     width: 200px;
   }
   #measure-panel {
-    width: 168px ;
+    width: 200px ;
     .iconfont {
       font-size: 24px;
     }

+ 8 - 0
RuoYi-Vue3/src/supermap-cesium-module/style/iconfont.css

@@ -221,3 +221,11 @@
   content: "\e6e9";
 }
 
+.iconjiaodu:before {
+  content: "\e6ea";
+}
+
+.iconjiaojiao:before {
+  content: "\e6eb";
+}
+

+ 44 - 6
RuoYi-Vue3/src/supermap-cesium-module/views/layout/aside.vue

@@ -920,9 +920,16 @@ export default {
 
     //添加公共服务
     addWebServe(obj, shouldFlyTo = true) {
+      console.log('===== addWebServe 开始 =====');
+      console.log('服务名称:', obj.name);
+      console.log('服务类型:', obj.type);
+      console.log('服务状态:', obj.state);
+      console.log('是否有transparentColor:', obj.transparentColor);
+      
       if (obj.state === 0) {
         // add mvt
         if (obj.type === "MVT") {
+          console.log('MVT类型,直接返回');
           layerManagement.addMvtLayer(obj.proxiedUrl, obj.name, () => {
             obj.state = 1;
             window.store.actions.setChangeLayers();
@@ -931,10 +938,26 @@ export default {
           return;
         }
         // add scene
+        console.log('REALSPACE类型,准备加载场景');
+        // useServerView 为 true 时使用超图服务端默认视口,否则使用本地配置的视口
+        const useServerView = obj.useServerView || false;
+        // 构建场景选项,包含透明色配置
+        const sceneOptions = { 
+          autoSetView: useServerView 
+        };
+        if (obj.transparentColor) {
+          sceneOptions.transparentColor = obj.transparentColor;
+          sceneOptions.transparentColorTolerance = obj.transparentColorTolerance || 0.01;
+          console.log('透明色配置:', sceneOptions);
+        } else {
+          console.log('没有配置透明色');
+        }
+        
         layerManagement.addScene(
           obj.proxiedUrl,
-          { autoSetView: false },
+          sceneOptions,
           layers => {
+            console.log('场景加载完成回调');
             obj.state = 1;
             window.store.actions.setChangeLayers();
             // 白膜添加线框
@@ -945,13 +968,16 @@ export default {
               layers[0].style3D.fillStyle =
                 Cesium.FillStyle[obj.style.fillStyle];
             }
+            
             this.saveLoadedServices();
-            if (shouldFlyTo) {
+            // 如果使用服务端默认视口,则不需要调用本地flyTo
+            if (shouldFlyTo && !useServerView) {
               this.flyTo(obj.name, server_flyTo_config);
             }
           }
         );
       } else {
+        console.log('服务已加载,仅执行飞行');
         if (shouldFlyTo) {
           this.flyTo(obj.name, server_flyTo_config);
         }
@@ -1649,18 +1675,30 @@ export default {
     flyTo(webName, obj) {
       let cameraParam = obj[webName];
       if (cameraParam) {
-        viewer.scene.camera.flyTo({
-          destination: new Cesium.Cartesian3(
+        let destination;
+        // 支持两种格式:经纬度格式和笛卡尔坐标格式
+        if (cameraParam.longitude !== undefined && cameraParam.latitude !== undefined) {
+          destination = Cesium.Cartesian3.fromDegrees(
+            cameraParam.longitude,
+            cameraParam.latitude,
+            cameraParam.height || 50000
+          );
+        } else if (cameraParam.Cartesian3) {
+          destination = new Cesium.Cartesian3(
             cameraParam.Cartesian3.x,
             cameraParam.Cartesian3.y,
             cameraParam.Cartesian3.z
-          ),
+          );
+        }
+        
+        viewer.scene.camera.flyTo({
+          destination: destination,
           orientation: {
             heading: cameraParam.heading,
             pitch: cameraParam.pitch,
             roll: cameraParam.roll
           },
-          duration: 0
+          duration: 2
         });
         return;
       } else {

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels