| | |
| | | #Dikuai Properties |
| | | #Mon Dec 22 18:52:36 CST 2025 |
| | | #Mon Dec 22 19:37:01 CST 2025 |
| | | LAND1.angleThreshold=-1 |
| | | LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E |
| | | LAND1.boundaryCoordinates=2.87,-0.19;6.73,-1.23;22.76,1.06;27.88,3.65;35.84,9.96;38.40,10.35;40.83,9.40;44.09,4.86;43.49,2.92;32.39,0.16;30.22,-1.26;30.00,-2.37;33.85,-25.32;32.30,-24.06;30.75,-22.81;29.20,-21.55;27.65,-20.29;26.11,-19.04;24.56,-17.78;23.01,-16.52;21.46,-15.27;19.91,-14.01;18.36,-12.75;16.81,-11.50;15.26,-10.24;13.72,-8.98;12.17,-7.73;10.62,-6.47;9.07,-5.21;7.52,-3.96;5.97,-2.70;4.42,-1.45;2.87,-0.19 |
| | | LAND1.boundaryCoordinates=2.88,-0.19;2.85,-0.19;2.84,-0.28;3.13,-0.43;4.02,-0.92;5.27,-1.24;6.74,-1.23;8.20,-1.02;9.67,-0.79;11.10,-0.46;12.55,-0.12;14.05,0.13;15.51,0.36;16.95,0.41;18.40,0.49;19.82,0.62;21.31,0.81;22.79,1.06;24.17,1.50;26.64,2.85;27.91,3.66;29.17,4.56;30.42,5.45;31.60,6.39;32.65,7.37;33.72,8.31;34.76,9.22;35.88,9.97;37.11,10.36;38.44,10.36;39.71,10.04;40.88,9.41;41.96,8.50;42.85,7.41;43.60,6.15;44.14,4.87;44.17,3.71;43.54,2.92;42.33,2.47;40.93,2.10;39.53,1.77;38.12,1.53;36.68,1.24;35.24,0.94;33.80,0.59;32.43,0.16;31.15,-0.38;30.26,-1.26;30.03,-2.37;30.12,-3.81;30.34,-5.22;30.87,-7.98;31.10,-9.36;31.33,-10.77;31.57,-12.20;31.82,-13.68;32.07,-15.16;32.28,-16.60;32.52,-17.92;32.78,-19.37;33.07,-20.80;33.35,-22.37;33.62,-23.91;33.89,-25.35 |
| | | LAND1.boundaryOriginalCoordinates=39.831524,116.279912,49.30;39.831524,116.279911,49.29;39.831523,116.279911,49.23;39.831521,116.279915,49.31;39.831517,116.279925,49.34;39.831514,116.279940,49.30;39.831514,116.279957,49.28;39.831516,116.279974,49.28;39.831518,116.279991,49.29;39.831521,116.280008,49.24;39.831524,116.280025,49.30;39.831526,116.280042,49.24;39.831529,116.280059,49.29;39.831529,116.280076,49.26;39.831530,116.280093,49.32;39.831531,116.280110,49.28;39.831533,116.280127,49.28;39.831535,116.280144,49.26;39.831539,116.280161,49.27;39.831544,116.280175,49.25;39.831551,116.280190,49.24;39.831558,116.280204,49.26;39.831566,116.280219,49.26;39.831574,116.280234,49.22;39.831583,116.280248,49.24;39.831591,116.280260,49.24;39.831600,116.280272,49.23;39.831608,116.280285,49.18;39.831615,116.280298,49.12;39.831618,116.280312,49.11;39.831618,116.280328,49.12;39.831615,116.280342,49.15;39.831610,116.280356,49.21;39.831602,116.280369,49.23;39.831592,116.280379,49.25;39.831581,116.280388,49.25;39.831569,116.280394,49.19;39.831559,116.280395,49.23;39.831552,116.280387,49.28;39.831547,116.280373,49.32;39.831544,116.280357,49.33;39.831541,116.280340,49.29;39.831539,116.280324,49.27;39.831536,116.280307,49.24;39.831534,116.280290,49.25;39.831531,116.280273,49.26;39.831527,116.280257,49.28;39.831522,116.280242,49.21;39.831514,116.280232,49.28;39.831504,116.280229,49.24;39.831491,116.280230,49.33;39.831478,116.280233,49.34;39.831466,116.280236,49.31;39.831454,116.280239,49.31;39.831441,116.280242,49.26;39.831429,116.280244,49.23;39.831416,116.280247,49.25;39.831402,116.280250,49.22;39.831389,116.280253,49.25;39.831376,116.280256,49.26;39.831364,116.280258,49.24;39.831351,116.280261,49.25;39.831338,116.280265,49.26;39.831324,116.280268,49.20;39.831311,116.280271,49.16;39.831298,116.280274,49.17 |
| | | LAND1.boundaryOriginalXY=2.88,-0.19;2.85,-0.19;2.84,-0.28;3.13,-0.43;4.02,-0.92;5.27,-1.24;6.74,-1.23;8.20,-1.02;9.67,-0.79;11.10,-0.46;12.55,-0.12;14.05,0.13;15.51,0.36;16.95,0.41;18.40,0.49;19.82,0.62;21.31,0.81;22.79,1.06;24.17,1.50;25.41,2.13;26.64,2.85;27.91,3.66;29.17,4.56;30.42,5.45;31.60,6.39;32.65,7.37;33.72,8.31;34.76,9.22;35.88,9.97;37.11,10.36;38.44,10.36;39.71,10.04;40.88,9.41;41.96,8.50;42.85,7.41;43.60,6.15;44.14,4.87;44.17,3.71;43.54,2.92;42.33,2.47;40.93,2.10;39.53,1.77;38.12,1.53;36.68,1.24;35.24,0.94;33.80,0.59;32.43,0.16;31.15,-0.38;30.26,-1.26;30.03,-2.37;30.12,-3.81;30.34,-5.22;30.59,-6.60;30.87,-7.98;31.10,-9.36;31.33,-10.77;31.57,-12.20;31.82,-13.68;32.07,-15.16;32.28,-16.60;32.52,-17.92;32.78,-19.37;33.07,-20.80;33.35,-22.37;33.62,-23.91;33.89,-25.35 |
| | | LAND1.boundaryPointInterval=-1 |
| | | LAND1.createTime=2025-12-22 18\:52\:36 |
| | | LAND1.intelligentSceneAnalysis=-1 |
| | | LAND1.landArea=459.07 |
| | | LAND1.landName=12344 |
| | | LAND1.landNumber=LAND1 |
| | |
| | | LAND1.mowingOverlapDistance=0.06 |
| | | LAND1.mowingPattern=平行线 |
| | | LAND1.mowingSafetyDistance=0.50 |
| | | LAND1.mowingTrack=-1 |
| | | LAND1.mowingWidth=34 |
| | | LAND1.plannedPath=4.652,-1.162;4.633,-1.048;4.994,-1.145;5.051,-1.483;5.449,-1.805;5.355,-1.243;5.716,-1.340;5.848,-2.126;6.247,-2.448;6.077,-1.437;6.438,-1.535;6.646,-2.773;7.045,-3.097;6.793,-1.595;7.130,-1.547;7.444,-3.422;7.843,-3.746;7.467,-1.499;7.803,-1.450;8.242,-4.067;8.641,-4.389;8.140,-1.402;8.477,-1.354;9.040,-4.710;9.438,-5.033;8.813,-1.306;9.150,-1.258;9.838,-5.357;10.237,-5.682;9.487,-1.210;9.823,-1.162;10.636,-6.006;11.035,-6.331;10.160,-1.114;10.497,-1.066;11.434,-6.655;11.834,-6.980;10.833,-1.018;11.170,-0.969;12.233,-7.304;12.632,-7.627;11.507,-0.921;11.843,-0.873;13.030,-7.948;13.429,-8.270;12.180,-0.825;12.517,-0.777;13.828,-8.592;14.227,-8.917;12.853,-0.729;13.190,-0.681;14.627,-9.244;15.026,-9.571;13.527,-0.633;13.864,-0.585;15.426,-9.898;15.825,-10.223;14.200,-0.537;14.537,-0.488;16.224,-10.547;16.623,-10.872;14.874,-0.440;15.210,-0.392;17.023,-11.196;17.421,-11.518;15.547,-0.344;15.884,-0.296;17.820,-11.839;18.219,-12.161;16.220,-0.248;16.557,-0.200;18.617,-12.482;19.017,-12.807;16.894,-0.152;17.230,-0.104;19.416,-13.131;19.815,-13.456;17.567,-0.056;17.904,-0.008;20.214,-13.780;20.613,-14.105;18.240,0.041;18.577,0.089;21.013,-14.429;21.412,-14.754;18.914,0.137;19.250,0.185;21.811,-15.078;22.209,-15.399;19.587,0.233;19.924,0.281;22.608,-15.721;23.007,-16.042;20.260,0.329;20.597,0.377;23.406,-16.365;23.805,-16.689;20.934,0.425;21.271,0.473;24.204,-17.014;24.603,-17.338;21.607,0.522;21.944,0.570;25.003,-17.663;25.402,-17.987;22.281,0.618;22.617,0.666;25.801,-18.312;26.200,-18.636;22.949,0.741;23.267,0.902;26.599,-18.961;26.998,-19.284;23.585,1.063;23.903,1.223;27.397,-19.608;27.796,-19.932;24.221,1.384;24.538,1.545;28.196,-20.257;28.595,-20.581;24.856,1.706;25.174,1.866;28.994,-20.906;29.393,-21.230;25.492,2.027;25.809,2.188;29.792,-21.555;30.192,-21.879;26.127,2.349;26.445,2.509;30.591,-22.204;30.990,-22.528;26.763,2.670;27.081,2.831;31.389,-22.850;31.787,-23.171;27.398,2.992;27.716,3.152;32.186,-23.493;32.585,-23.815;28.034,3.313;28.340,3.543;32.984,-24.139;29.652,-2.222;28.644,3.784;28.949,4.025;29.839,-1.280;30.117,-0.885;29.253,4.266;29.557,4.507;30.428,-0.682;30.738,-0.479;29.862,4.749;30.166,4.990;31.049,-0.275;31.360,-0.072;30.470,5.231;30.774,5.472;31.670,0.131;31.981,0.335;31.079,5.714;31.383,5.955;32.295,0.517;32.626,0.600;31.687,6.196;31.992,6.437;32.957,0.682;33.288,0.765;32.296,6.678;32.600,6.920;33.619,0.847;33.950,0.929;32.904,7.161;33.209,7.402;34.281,1.011;34.612,1.094;33.513,7.643;33.817,7.884;34.943,1.176;35.274,1.258;34.122,8.126;34.426,8.367;35.605,1.341;35.936,1.423;34.730,8.608;35.034,8.849;36.266,1.505;36.597,1.587;35.339,9.091;35.643,9.332;36.928,1.670;37.259,1.752;35.947,9.573;36.279,9.653;37.590,1.834;37.921,1.917;36.615,9.704;36.951,9.755;38.252,1.999;38.583,2.081;37.287,9.806;37.623,9.857;38.914,2.163;39.245,2.246;37.960,9.909;38.296,9.960;39.576,2.328;39.907,2.410;38.659,9.852;39.028,9.707;40.238,2.493;40.569,2.575;39.396,9.563;39.765,9.419;40.900,2.657;41.231,2.739;40.134,9.275;40.503,9.130;41.562,2.822;41.893,2.904;40.933,8.622;41.383,7.995;42.224,2.986;42.554,3.069;41.833,7.369;42.283,6.742;42.885,3.151;43.210,3.268;42.733,6.116;43.183,5.489;43.434,3.991;43.657,4.714;43.633,4.863 |
| | | LAND1.updateTime=2025-12-22 18\:52\:36 |
| | | LAND1.returnPathCoordinates=-1 |
| | | LAND1.returnPathRawCoordinates=-1 |
| | | LAND1.returnPointCoordinates=-1 |
| | | LAND1.updateTime=2025-12-22 19\:37\:01 |
| | | LAND1.userId=-1 |
| | |
| | | #Mower Configuration Properties - Updated |
| | | #Mon Dec 22 19:03:58 CST 2025 |
| | | #Mon Dec 22 19:37:04 CST 2025 |
| | | appVersion=-1 |
| | | boundaryLengthVisible=false |
| | | currentWorkLandNumber=LAND1 |
| | |
| | | handheldMarkerId=1872 |
| | | idleTrailDurationSeconds=60 |
| | | manualBoundaryDrawingMode=false |
| | | mapScale=11.18 |
| | | mapScale=6.12 |
| | | measurementModeEnabled=false |
| | | mowerId=860 |
| | | serialAutoConnect=true |
| | | serialBaudRate=115200 |
| | | serialPortName=COM15 |
| | | simCardNumber=-1 |
| | | viewCenterX=-21.01 |
| | | viewCenterY=3.68 |
| | | viewCenterX=-18.02 |
| | | viewCenterY=7.50 |
| | |
| | | |
| | | public class bianjieguihua2 { |
| | | /** |
| | | * 优化边界XY坐标字符串 |
| | | * |
| | | * @param boundaryXYString 边界XY坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." |
| | | * @return 优化后的边界坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." |
| | | */ |
| | | public static String optimizeBoundaryXYString(String boundaryXYString) { |
| | | try { |
| | | // 检查输入数据 |
| | | if (boundaryXYString == null || boundaryXYString.trim().isEmpty()) { |
| | | throw new IllegalArgumentException("边界坐标字符串不能为空"); |
| | | } |
| | | |
| | | // 解析XY坐标字符串 |
| | | List<BoundaryAlgorithm.Coordinate> localCoordinates = parseXYString(boundaryXYString); |
| | | |
| | | if (localCoordinates == null || localCoordinates.isEmpty()) { |
| | | throw new IllegalArgumentException("无法解析边界坐标字符串"); |
| | | } |
| | | |
| | | // 三角形小区域特殊处理,避免过度插值导致点数扩增 |
| | | if (localCoordinates.size() == 3) { |
| | | double triangleArea = calculatePolygonArea(localCoordinates); |
| | | double trianglePerimeter = calculatePerimeter(localCoordinates); |
| | | |
| | | System.out.println("检测到三角形边界,面积=" + String.format("%.2f", triangleArea) + |
| | | "m², 周长=" + String.format("%.2f", trianglePerimeter) + "m"); |
| | | |
| | | if (triangleArea < 100.0 || trianglePerimeter < 30.0) { |
| | | System.out.println("小三角形,跳过插值优化"); |
| | | BoundaryAlgorithm.Coordinate firstPoint = localCoordinates.get(0); |
| | | List<BoundaryAlgorithm.Coordinate> trianglePoints = new ArrayList<>(localCoordinates); |
| | | trianglePoints.add(new BoundaryAlgorithm.Coordinate( |
| | | firstPoint.x, |
| | | firstPoint.y, |
| | | firstPoint.lat, |
| | | firstPoint.lon)); |
| | | return convertBoundaryPointsToString(trianglePoints); |
| | | } |
| | | } |
| | | |
| | | // 创建算法实例 |
| | | BoundaryAlgorithm algorithm = new BoundaryAlgorithm(); |
| | | |
| | | // 自动场景分析(基于XY坐标,无高程数据) |
| | | BoundaryAlgorithm.SceneAnalysis sceneAnalysis = analyzeSceneFromXYCoordinates(localCoordinates); |
| | | System.out.println("自动场景分析结果:"); |
| | | System.out.println(sceneAnalysis.toString()); |
| | | |
| | | // 根据场景分析结果获取参数 |
| | | BoundaryAlgorithm.BoundaryParameters params = |
| | | algorithm.getParametersForPreset(sceneAnalysis.suggestedPreset); |
| | | |
| | | System.out.println("自动选择的参数: 间隔=" + params.interval + "米, 角度阈值=" + |
| | | params.angleThreshold + "度"); |
| | | |
| | | // 使用优化算法处理边界 |
| | | List<BoundaryAlgorithm.Coordinate> optimizedPoints = |
| | | algorithm.optimizeBoundaryPointsAdvanced(localCoordinates, params); |
| | | |
| | | // 质量评估 |
| | | BoundaryAlgorithm.BoundaryQuality boundaryQuality = |
| | | algorithm.evaluateBoundaryQuality(optimizedPoints); |
| | | |
| | | System.out.println("边界质量评估结果:"); |
| | | System.out.println(boundaryQuality.toString()); |
| | | |
| | | // 转换为输出字符串格式 |
| | | return convertBoundaryPointsToString(optimizedPoints); |
| | | |
| | | } catch (Exception e) { |
| | | throw new RuntimeException("优化边界坐标字符串时发生错误: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析XY坐标字符串为Coordinate列表 |
| | | * |
| | | * @param xyString XY坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." |
| | | * @return Coordinate列表 |
| | | */ |
| | | private static List<BoundaryAlgorithm.Coordinate> parseXYString(String xyString) { |
| | | List<BoundaryAlgorithm.Coordinate> coordinates = new ArrayList<>(); |
| | | |
| | | if (xyString == null || xyString.trim().isEmpty()) { |
| | | return coordinates; |
| | | } |
| | | |
| | | String[] points = xyString.split(";"); |
| | | for (String point : points) { |
| | | point = point.trim(); |
| | | if (point.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | String[] parts = point.split(","); |
| | | if (parts.length >= 2) { |
| | | try { |
| | | double x = Double.parseDouble(parts[0].trim()); |
| | | double y = Double.parseDouble(parts[1].trim()); |
| | | // lat和lon设为0,因为我们只需要XY坐标 |
| | | coordinates.add(new BoundaryAlgorithm.Coordinate(x, y, 0.0, 0.0)); |
| | | } catch (NumberFormatException e) { |
| | | System.err.println("解析坐标失败: " + point + ", 错误: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return coordinates; |
| | | } |
| | | |
| | | /** |
| | | * 基于XY坐标进行场景分析(无高程数据) |
| | | */ |
| | | private static BoundaryAlgorithm.SceneAnalysis analyzeSceneFromXYCoordinates( |
| | | List<BoundaryAlgorithm.Coordinate> localCoords) { |
| | | |
| | | BoundaryAlgorithm.SceneAnalysis analysis = new BoundaryAlgorithm.SceneAnalysis(); |
| | | |
| | | if (localCoords.size() < 3) { |
| | | analysis.suggestedPreset = "复杂小区域"; |
| | | return analysis; |
| | | } |
| | | |
| | | // 计算基本统计信息 |
| | | calculateBasicStatisticsFromCoordinates(localCoords, analysis); |
| | | |
| | | // 计算边界复杂度 |
| | | calculateBoundaryComplexityFromCoordinates(localCoords, analysis); |
| | | |
| | | // 无高程数据,设置为0 |
| | | analysis.elevationRange = 0; |
| | | |
| | | // 自动选择预设场景(不考虑高程因素) |
| | | selectPresetAutomaticallyFromXYCoordinates(analysis); |
| | | |
| | | return analysis; |
| | | } |
| | | |
| | | /** |
| | | * 从XY坐标自动选择预设场景(不考虑高程因素) |
| | | */ |
| | | private static void selectPresetAutomaticallyFromXYCoordinates(BoundaryAlgorithm.SceneAnalysis analysis) { |
| | | // 决策逻辑基于面积和复杂度 |
| | | double areaWeight = 0.6; |
| | | double complexityWeight = 0.4; |
| | | |
| | | // 计算综合得分 |
| | | double score = 0; |
| | | |
| | | // 面积因素:面积越大,越适合大间隔 |
| | | double areaScore = Math.min(1.0, analysis.area / 1000.0); // 1000平方米为基准 |
| | | score += areaScore * areaWeight; |
| | | |
| | | // 复杂度因素:复杂度越高,越需要小间隔 |
| | | double complexityScore = analysis.complexity; |
| | | score += complexityScore * complexityWeight; |
| | | |
| | | // 根据得分选择预设 |
| | | if (score < 0.3) { |
| | | analysis.suggestedPreset = "平坦大区域"; |
| | | } else if (score < 0.6) { |
| | | analysis.suggestedPreset = "常规区域"; |
| | | } else { |
| | | analysis.suggestedPreset = "复杂小区域"; |
| | | } |
| | | |
| | | System.out.println("自动场景选择得分: " + String.format("%.2f", score) + " -> " + analysis.suggestedPreset); |
| | | } |
| | | |
| | | /** |
| | | * 自动处理Coordinate列表并生成优化后的边界坐标(无需传入间隔和角度阈值) |
| | | * |
| | | * @param coordinates Coordinate对象列表 |
| | |
| | | JOptionPane.showMessageDialog(this, "未找到地块信息,无法优化", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | String baseStation = dikuai.getBaseStationCoordinates(); |
| | | if (baseStation == null || baseStation.trim().isEmpty() || "-1".equals(baseStation)) { |
| | | JOptionPane.showMessageDialog(this, "基站坐标未设置,无法优化", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 准备 Coordinate 列表 |
| | | String originalCoords = dikuai.getBoundaryOriginalCoordinates(); |
| | | if (originalCoords == null || originalCoords.trim().isEmpty() || "-1".equals(originalCoords)) { |
| | | JOptionPane.showMessageDialog(this, "原始边界坐标为空,无法优化", "错误", JOptionPane.ERROR_MESSAGE); |
| | | // 从原始边界XY坐标文本域获取输入 |
| | | String inputXY = rawXYArea.getText(); |
| | | if (inputXY == null || inputXY.trim().isEmpty() || "-1".equals(inputXY.trim())) { |
| | | JOptionPane.showMessageDialog(this, "原始边界XY坐标为空,无法优化", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 解析原始坐标到 Coordinate.coordinates |
| | | List<Coordinate> coords = new ArrayList<>(); |
| | | String[] points = originalCoords.split(";"); |
| | | for (String point : points) { |
| | | String[] parts = point.split(","); |
| | | if (parts.length >= 2) { |
| | | double lonDecimal = Double.parseDouble(parts[0].trim()); |
| | | double latDecimal = Double.parseDouble(parts[1].trim()); |
| | | double alt = parts.length > 2 ? Double.parseDouble(parts[2].trim()) : 0.0; |
| | | |
| | | // 将十进制度转换为度分格式字符串 |
| | | String latDM = decimalToDegreeMinute(latDecimal); |
| | | String lonDM = decimalToDegreeMinute(lonDecimal); |
| | | String latDir = latDecimal >= 0 ? "N" : "S"; |
| | | String lonDir = lonDecimal >= 0 ? "E" : "W"; |
| | | |
| | | coords.add(new Coordinate(latDM, latDir, lonDM, lonDir, alt)); |
| | | } |
| | | } |
| | | Coordinate.coordinates = coords; |
| | | |
| | | // 调用优化算法 |
| | | String optimized = bianjieguihua2.processCoordinateListAuto(baseStation); |
| | | // 调用优化算法(直接使用XY坐标字符串) |
| | | String optimized = bianjieguihua2.optimizeBoundaryXYString(inputXY.trim()); |
| | | optTextArea.setText(optimized); |
| | | JOptionPane.showMessageDialog(this, "边界优化完成", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | } catch (Exception ex) { |
| | |
| | | // 获取当前优化后的边界 |
| | | String currentOptimized = optTextArea.getText(); |
| | | |
| | | // 保存地块编号,用于返回回调 |
| | | final String targetLandNumber = dikuai.getLandNumber(); |
| | | |
| | | // 调用首页显示预览 |
| | | SwingUtilities.invokeLater(() -> { |
| | | Shouye.showBoundaryPreview(dikuai, currentOptimized, () -> { |
| | | // 返回回调:重新打开此页面 |
| | | // 返回回调:重新打开此页面,从地块对象重新读取最新的边界坐标 |
| | | // 从 dikuaiMap 重新获取地块对象(确保获取到最新的值) |
| | | Dikuai updatedDikuai = Dikuai.getDikuai(targetLandNumber); |
| | | if (updatedDikuai != null) { |
| | | String latestBoundary = updatedDikuai.getBoundaryCoordinates(); |
| | | new Dikuanbianjipage(getOwner(), getTitle(), latestBoundary, updatedDikuai).setVisible(true); |
| | | } else { |
| | | // 如果获取失败,使用当前值 |
| | | new Dikuanbianjipage(getOwner(), getTitle(), currentOptimized, dikuai).setVisible(true); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | |
| | | Dikuai.saveToProperties(); |
| | | this.result = trimmed; |
| | | JOptionPane.showMessageDialog(this, "地块边界坐标已更新", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | dispose(); |
| | | // 不退出页面,只更新显示 |
| | | // dispose(); // 移除退出逻辑 |
| | | }); |
| | | |
| | | closeBtn.addActionListener(e -> dispose()); |
| | |
| | | if (handleMowerClick(e.getPoint())) { |
| | | return; |
| | | } |
| | | // 优先处理优化后边界坐标点点击(边界预览模式下) |
| | | if (boundaryPreviewActive && handleOptimizedBoundaryPointClick(e.getPoint())) { |
| | | return; |
| | | } |
| | | // 优先处理障碍物边界点点击(如果可见) |
| | | if (obstaclePointsVisible && handleObstaclePointClick(e.getPoint())) { |
| | | return; |
| | |
| | | // 绘制鼠标实时位置(手动绘制边界模式时) |
| | | manualBoundaryDrawer.drawMousePosition(g2d, scale); |
| | | |
| | | // 绘制导航路径(中层) |
| | | if (hasPlannedPath) { |
| | | // 绘制导航路径(中层)- 边界预览模式下不显示导航路径 |
| | | if (hasPlannedPath && !boundaryPreviewActive) { |
| | | drawCurrentPlannedPath(g2d); |
| | | } |
| | | |
| | |
| | | // 绘制优化后边界(绿色,与正常边界颜色一致) |
| | | if (previewOptimizedBoundary != null && previewOptimizedBoundary.size() >= 2) { |
| | | bianjiedrwa.drawBoundary(g2d, previewOptimizedBoundary, scale, GRASS_FILL_COLOR, GRASS_BORDER_COLOR); |
| | | |
| | | // 绘制优化后边界坐标点(紫色实心圆圈,显示序号) |
| | | drawOptimizedBoundaryPointsWithNumbers(g2d, previewOptimizedBoundary, scale); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 绘制优化后边界坐标点(紫色实心圆圈,显示序号) |
| | | * 序号显示在点中心,字体大小11号,不随缩放变化 |
| | | */ |
| | | private void drawOptimizedBoundaryPointsWithNumbers(Graphics2D g2d, List<Point2D.Double> boundary, double scale) { |
| | | if (boundary == null || boundary.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | // 保存原始变换 |
| | | AffineTransform originalTransform = g2d.getTransform(); |
| | | |
| | | // 设置点的大小(实心圆圈,直径约0.3米) |
| | | double scaleFactor = Math.max(0.5, scale); |
| | | double markerDiameter = 0.3; // 圆圈直径(米) |
| | | double markerRadius = markerDiameter / 2.0; |
| | | |
| | | // 设置字体(11号,不随缩放变化) |
| | | Font labelFont = new Font("微软雅黑", Font.PLAIN, 11); |
| | | g2d.setFont(labelFont); |
| | | FontMetrics fontMetrics = g2d.getFontMetrics(labelFont); |
| | | |
| | | // 紫色实心圆圈颜色 |
| | | Color purpleColor = new Color(128, 0, 128, 255); // 紫色 |
| | | |
| | | // 绘制每个点及其序号 |
| | | for (int i = 0; i < boundary.size(); i++) { |
| | | Point2D.Double point = boundary.get(i); |
| | | double x = point.x; |
| | | double y = point.y; |
| | | |
| | | // 绘制紫色实心圆圈(在世界坐标系中,随缩放变化) |
| | | g2d.setColor(purpleColor); |
| | | Ellipse2D.Double marker = new Ellipse2D.Double( |
| | | x - markerRadius, |
| | | y - markerRadius, |
| | | markerDiameter, |
| | | markerDiameter |
| | | ); |
| | | g2d.fill(marker); |
| | | |
| | | // 将世界坐标转换为屏幕坐标以绘制序号(不随缩放变化) |
| | | Point2D.Double worldPoint = new Point2D.Double(x, y); |
| | | Point2D.Double screenPoint = new Point2D.Double(); |
| | | originalTransform.transform(worldPoint, screenPoint); |
| | | |
| | | // 保存当前变换 |
| | | AffineTransform savedTransform = g2d.getTransform(); |
| | | |
| | | // 重置变换为屏幕坐标系统 |
| | | g2d.setTransform(new AffineTransform()); |
| | | |
| | | // 绘制序号(在屏幕坐标系中,不随缩放变化) |
| | | String numberText = String.valueOf(i + 1); |
| | | int textWidth = fontMetrics.stringWidth(numberText); |
| | | int textHeight = fontMetrics.getHeight(); |
| | | |
| | | // 在点中心绘制序号 |
| | | int textX = (int)(screenPoint.x - textWidth / 2.0); |
| | | int textY = (int)(screenPoint.y + textHeight / 4.0); |
| | | |
| | | // 绘制序号文字(黑色) |
| | | g2d.setColor(Color.BLACK); |
| | | g2d.drawString(numberText, textX, textY); |
| | | |
| | | // 恢复变换 |
| | | g2d.setTransform(savedTransform); |
| | | } |
| | | |
| | | // 恢复原始变换 |
| | | g2d.setTransform(originalTransform); |
| | | } |
| | | |
| | | /** |
| | | * 处理优化后边界坐标点点击 |
| | | * @param screenPoint 屏幕坐标点 |
| | | * @return 是否处理了点击 |
| | | */ |
| | | private boolean handleOptimizedBoundaryPointClick(Point screenPoint) { |
| | | if (previewOptimizedBoundary == null || previewOptimizedBoundary.isEmpty()) { |
| | | return false; |
| | | } |
| | | |
| | | // 计算选择阈值(像素) |
| | | double threshold = computeOptimizedBoundaryPointSelectionThreshold(); |
| | | |
| | | // 查找被点击的点 |
| | | int hitIndex = -1; |
| | | for (int i = 0; i < previewOptimizedBoundary.size(); i++) { |
| | | Point2D.Double worldPoint = previewOptimizedBoundary.get(i); |
| | | Point2D.Double screenPosition = worldToScreen(worldPoint); |
| | | double dx = screenPosition.x - screenPoint.x; |
| | | double dy = screenPosition.y - screenPoint.y; |
| | | if (Math.hypot(dx, dy) <= threshold) { |
| | | hitIndex = i; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (hitIndex < 0) { |
| | | return false; |
| | | } |
| | | |
| | | // 弹出确认对话框 |
| | | String pointLabel = String.valueOf(hitIndex + 1); |
| | | int choice = JOptionPane.showConfirmDialog( |
| | | visualizationPanel, |
| | | "确定要删除第" + pointLabel + "号优化后边界坐标点吗?", |
| | | "删除边界坐标点", |
| | | JOptionPane.OK_CANCEL_OPTION, |
| | | JOptionPane.WARNING_MESSAGE |
| | | ); |
| | | |
| | | if (choice == JOptionPane.OK_OPTION) { |
| | | // 删除坐标点 |
| | | List<Point2D.Double> updated = new ArrayList<>(previewOptimizedBoundary); |
| | | updated.remove(hitIndex); |
| | | |
| | | // 更新预览边界 |
| | | previewOptimizedBoundary = updated; |
| | | |
| | | // 转换为字符串格式并保存 |
| | | String updatedBoundaryString = convertBoundaryToString(updated); |
| | | |
| | | // 通知 Shouye 保存更新后的边界坐标 |
| | | if (boundaryPreviewUpdateCallback != null) { |
| | | boundaryPreviewUpdateCallback.accept(updatedBoundaryString); |
| | | } |
| | | |
| | | // 刷新显示 |
| | | visualizationPanel.repaint(); |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 计算优化后边界坐标点的选择阈值(像素) |
| | | */ |
| | | private double computeOptimizedBoundaryPointSelectionThreshold() { |
| | | double scaleFactor = Math.max(0.5, scale); |
| | | double markerDiameterWorld = 0.3; // 圆圈直径(米) |
| | | double markerDiameterPixels = markerDiameterWorld * scale; |
| | | return Math.max(8.0, markerDiameterPixels * 1.5); |
| | | } |
| | | |
| | | /** |
| | | * 将边界点列表转换为字符串格式 |
| | | */ |
| | | private String convertBoundaryToString(List<Point2D.Double> boundary) { |
| | | if (boundary == null || boundary.isEmpty()) { |
| | | return ""; |
| | | } |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < boundary.size(); i++) { |
| | | Point2D.Double point = boundary.get(i); |
| | | sb.append(String.format(Locale.US, "%.2f,%.2f", point.x, point.y)); |
| | | if (i < boundary.size() - 1) { |
| | | sb.append(";"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 设置边界预览更新回调 |
| | | */ |
| | | private java.util.function.Consumer<String> boundaryPreviewUpdateCallback; |
| | | |
| | | public void setBoundaryPreviewUpdateCallback(java.util.function.Consumer<String> callback) { |
| | | this.boundaryPreviewUpdateCallback = callback; |
| | | } |
| | | |
| | | } |
| | |
| | | private JButton saveManualBoundaryButton; // 保存手动绘制边界的按钮 |
| | | private String previewRestoreLandNumber; |
| | | private String previewRestoreLandName; |
| | | private Dikuai currentBoundaryPreviewDikuai; // 当前边界预览的地块引用 |
| | | private boolean drawingPaused; |
| | | private ImageIcon pauseIcon; |
| | | private ImageIcon pauseActiveIcon; |
| | |
| | | return; |
| | | } |
| | | |
| | | // 保存当前地块引用 |
| | | shouye.currentBoundaryPreviewDikuai = dikuai; |
| | | |
| | | // 获取原始边界XY坐标 |
| | | String originalBoundaryXY = dikuai.getBoundaryOriginalXY(); |
| | | |
| | | // 设置边界预览 |
| | | shouye.mapRenderer.setBoundaryPreview(originalBoundaryXY, optimizedBoundary); |
| | | |
| | | // 设置边界预览更新回调,用于保存删除坐标点后的边界 |
| | | shouye.mapRenderer.setBoundaryPreviewUpdateCallback(updatedBoundary -> { |
| | | if (shouye.currentBoundaryPreviewDikuai != null && updatedBoundary != null) { |
| | | // 保存更新后的边界坐标 |
| | | Dikuai.updateField(shouye.currentBoundaryPreviewDikuai.getLandNumber(), "boundaryCoordinates", updatedBoundary); |
| | | java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| | | Dikuai.updateField(shouye.currentBoundaryPreviewDikuai.getLandNumber(), "updateTime", sdf.format(new java.util.Date())); |
| | | Dikuai.saveToProperties(); |
| | | |
| | | // 同步更新当前地块对象的内存值(确保返回时能获取到最新值) |
| | | shouye.currentBoundaryPreviewDikuai.setBoundaryCoordinates(updatedBoundary); |
| | | |
| | | // 更新预览边界(重新设置以刷新显示) |
| | | shouye.mapRenderer.setBoundaryPreview(originalBoundaryXY, updatedBoundary); |
| | | } |
| | | }); |
| | | |
| | | // 停止绘制割草机实时拖尾 |
| | | if (shouye.mapRenderer != null) { |
| | | shouye.mapRenderer.setIdleTrailSuppressed(true); |
| | |
| | | private void exitBoundaryPreview() { |
| | | pathPreviewActive = false; |
| | | |
| | | // 清除当前地块引用 |
| | | currentBoundaryPreviewDikuai = null; |
| | | |
| | | // 恢复绘制割草机实时拖尾 |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setIdleTrailSuppressed(false); |
| | |
| | | // 清除边界预览 |
| | | if (mapRenderer != null) { |
| | | mapRenderer.clearBoundaryPreview(); |
| | | mapRenderer.setBoundaryPreviewUpdateCallback(null); |
| | | } |
| | | |
| | | // 隐藏返回按钮 |