| | |
| | | # 割草机地块障碍物配置文件 |
| | | # 生成时间:2025-12-17T13:28:00.386604500 |
| | | # 生成时间:2025-12-17T16:39:03.715543100 |
| | | # 坐标系:WGS84(度分格式) |
| | | |
| | | # ============ 地块基准站配置 ============ |
| | |
| | | # 格式:plot.[地块编号].obstacle.[障碍物名称].xyCoords=[坐标串] |
| | | |
| | | # --- 地块LAND1的障碍物 --- |
| | | plot.LAND1.obstacle.障碍物1.shape=1 |
| | | plot.LAND1.obstacle.障碍物1.originalCoords=0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E |
| | | plot.LAND1.obstacle.障碍物1.xyCoords=81.22,-22.17;81.21,-22.17;81.19,-22.18;81.20,-22.19;81.18,-22.21;81.07,-22.54;80.94,-23.05;80.44,-23.54;79.84,-23.83;79.27,-24.19;78.63,-24.52;78.11,-24.79;77.69,-25.22;77.43,-25.75;77.15,-26.45;77.08,-27.23;77.10,-27.92;77.08,-28.67;77.08,-29.32;77.15,-29.97;77.27,-30.57;77.53,-31.19;77.92,-31.45;78.22,-31.73;78.92,-31.87;79.65,-31.77;80.35,-31.60;80.90,-31.17;81.27,-30.63;81.70,-30.11;81.90,-29.48;81.95,-28.92;81.90,-28.38;81.73,-27.83;81.73,-27.30;81.52,-26.81;81.42,-26.24;81.26,-25.76;81.27,-25.25;81.04,-25.07;81.11,-24.98;81.11,-25.00;81.12,-25.00 |
| | | plot.LAND1.obstacle.水电费GV.shape=1 |
| | | plot.LAND1.obstacle.水电费GV.originalCoords=3949.899067,N;11616.760758,E;3949.899164,N;11616.760704,E;3949.899295,N;11616.760673,E;3949.899424,N;11616.760648,E;3949.899535,N;11616.760607,E;3949.899673,N;11616.760584,E;3949.899800,N;11616.760543,E;3949.899910,N;11616.760515,E;3949.900044,N;11616.760481,E;3949.900174,N;11616.760435,E;3949.900305,N;11616.760412,E;3949.900433,N;11616.760361,E;3949.900555,N;11616.760306,E;3949.900651,N;11616.760245,E;3949.900732,N;11616.760161,E;3949.900806,N;11616.760057,E;3949.900889,N;11616.759945,E;3949.900967,N;11616.759856,E;3949.901066,N;11616.759724,E;3949.901142,N;11616.759616,E;3949.901243,N;11616.759498,E;3949.901321,N;11616.759402,E;3949.901443,N;11616.759329,E;3949.901554,N;11616.759243,E;3949.901652,N;11616.759203,E;3949.901783,N;11616.759170,E;3949.901921,N;11616.759142,E;3949.902031,N;11616.759109,E;3949.902162,N;11616.759081,E;3949.902289,N;11616.759050,E;3949.902394,N;11616.759018,E;3949.902532,N;11616.758989,E;3949.902654,N;11616.758975,E;3949.902768,N;11616.758947,E;3949.902759,N;11616.758921,E;3949.902765,N;11616.758830,E;3949.902775,N;11616.758725,E;3949.902781,N;11616.758571,E;3949.902764,N;11616.758404,E;3949.902711,N;11616.758280,E;3949.902679,N;11616.758163,E;3949.902639,N;11616.758002,E;3949.902610,N;11616.757869,E;3949.902580,N;11616.757678,E;3949.902542,N;11616.757505,E;3949.902533,N;11616.757411,E;3949.902502,N;11616.757243,E;3949.902473,N;11616.757063,E;3949.902460,N;11616.756947,E;3949.902438,N;11616.756821,E;3949.902430,N;11616.756804,E;3949.902408,N;11616.756828,E;3949.902392,N;11616.756884,E;3949.902388,N;11616.756920,E;3949.902389,N;11616.756920,E;3949.902387,N;11616.756920,E;3949.902388,N;11616.756919,E;3949.902388,N;11616.756922,E;3949.902388,N;11616.756922,E;3949.902388,N;11616.756922,E;3949.902389,N;11616.756923,E;3949.902389,N;11616.756920,E;3949.901649,N;11616.757013,E;3949.901650,N;11616.757016,E;3949.901650,N;11616.757014,E;3949.901650,N;11616.757013,E;3949.901650,N;11616.757014,E;3949.901650,N;11616.757013,E;3949.901651,N;11616.757015,E;3949.901649,N;11616.757012,E;3949.901650,N;11616.757012,E;3949.901650,N;11616.757012,E;3949.901651,N;11616.757011,E;3949.901652,N;11616.757010,E |
| | | plot.LAND1.obstacle.水电费GV.xyCoords=5.46,-6.16;2.88,0.70;-0.16,0.08;0.13,-1.37;1.46,-2.56;2.79,-3.76;4.13,-4.96;5.46,-6.16 |
| | | |
| | |
| | | #Dikuai Properties |
| | | #Wed Dec 17 13:50:43 CST 2025 |
| | | #Wed Dec 17 16:39:03 CST 2025 |
| | | LAND1.angleThreshold=-1 |
| | | LAND1.baseStationCoordinates=3949.90238860,N,11616.75692000,E |
| | | LAND1.boundaryCoordinates=77.19,-32.68;80.71,-54.97;80.99,-55.90;83.54,-56.46;85.04,-55.55;85.94,-53.74;83.24,-35.82;84.55,-34.54;94.02,-31.92;94.10,-31.11;90.88,-20.39;90.35,-19.53;88.33,-19.00;84.12,-19.47;78.92,-22.36;76.63,-25.55;76.93,-29.84;77.06,-31.26;77.19,-32.68 |
| | |
| | | LAND1.mowingWidth=40 |
| | | LAND1.plannedPath=77.45,-31.44;81.28,-55.71;81.70,-55.80;77.91,-31.78;78.05,-31.91;78.17,-31.98;78.35,-32.01;82.12,-55.89;82.54,-55.98;78.77,-32.09;78.95,-32.12;79.17,-32.09;82.96,-56.08;83.38,-56.17;79.57,-32.03;79.96,-31.95;83.76,-56.03;84.13,-55.81;80.35,-31.86;80.50,-31.80;80.72,-31.63;84.50,-55.58;84.87,-55.33;81.08,-31.34;81.41,-30.87;85.18,-54.72;85.48,-54.10;81.75,-30.45;81.89,-30.27;81.94,-30.19;82.05,-29.82;83.00,-35.83;83.34,-35.38;81.43,-23.31;81.47,-22.18;81.43,-22.04;81.32,-21.94;81.21,-21.92;81.13,-21.42;81.50,-21.21;83.69,-35.03;84.04,-34.69;81.88,-21.00;82.25,-20.80;84.39,-34.35;84.77,-34.22;82.62,-20.59;82.99,-20.38;85.16,-34.11;85.55,-34.00;83.37,-20.18;83.74,-19.97;85.94,-33.90;86.32,-33.79;84.11,-19.76;84.50,-19.68;86.71,-33.68;87.10,-33.57;84.90,-19.63;85.30,-19.59;87.49,-33.47;87.88,-33.36;85.70,-19.55;86.09,-19.50;88.26,-33.25;88.65,-33.15;86.49,-19.46;86.89,-19.41;89.04,-33.04;89.43,-32.93;87.29,-19.37;87.69,-19.32;89.82,-32.82;90.20,-32.72;88.08,-19.28;88.49,-19.30;90.59,-32.61;90.98,-32.50;88.91,-19.41;89.34,-19.52;91.37,-32.39;91.76,-32.29;89.76,-19.63;90.18,-19.74;92.14,-32.18;92.53,-32.07;90.76,-20.88;91.62,-23.72;92.92,-31.96;93.31,-31.86;92.47,-26.56;93.33,-29.40;93.70,-31.75;81.40,-22.00;81.27,-21.93;81.21,-21.92;81.08,-21.96;80.98,-22.05;79.85,-23.55;79.64,-22.24;80.01,-22.04;80.22,-23.37;80.58,-23.05;80.39,-21.83;80.76,-21.62;80.87,-22.34;77.96,-24.59;77.63,-24.92;77.59,-24.64;77.92,-24.18;77.99,-24.57;78.36,-24.38;78.25,-23.72;78.59,-23.25;78.73,-24.19;79.11,-23.99;78.92,-22.79;79.27,-22.45;79.48,-23.76;77.99,-24.57;77.93,-24.62;77.51,-25.05;77.47,-25.11;77.20,-25.66;77.01,-26.12;76.93,-25.57;77.26,-25.10;77.31,-25.43 |
| | | LAND1.returnPointCoordinates=-1 |
| | | LAND1.updateTime=2025-12-17 13\:50\:43 |
| | | LAND1.updateTime=2025-12-17 16\:39\:03 |
| | | LAND1.userId=-1 |
| | |
| | | #Mower Configuration Properties - Updated |
| | | #Wed Dec 17 15:26:06 CST 2025 |
| | | #Wed Dec 17 16:39:38 CST 2025 |
| | | appVersion=-1 |
| | | currentWorkLandNumber=LAND1 |
| | | cuttingWidth=200 |
| | | firmwareVersion=-1 |
| | | handheldMarkerId= |
| | | idleTrailDurationSeconds=60 |
| | | mapScale=28.94 |
| | | mowerId=1234 |
| | | mapScale=4.96 |
| | | mowerId=1872 |
| | | serialAutoConnect=true |
| | | serialBaudRate=115200 |
| | | serialPortName=COM15 |
| | | simCardNumber=-1 |
| | | viewCenterX=-81.30 |
| | | viewCenterY=30.28 |
| | | viewCenterX=-30.79 |
| | | viewCenterY=22.28 |
| | |
| | | |
| | | import zhangaiwu.Obstacledge; |
| | | import zhuye.Shouye; |
| | | import zhuye.Coordinate; |
| | | import bianjie.bianjieguihua2; |
| | | |
| | | /** |
| | | * 障碍物管理页面 - UI优化版 |
| | |
| | | return; |
| | | } |
| | | |
| | | double baseLat = parseDMToDecimal(baseParts[0].trim(), baseParts[1].trim()); |
| | | double baseLon = parseDMToDecimal(baseParts[2].trim(), baseParts[3].trim()); |
| | | Obstacledge.ObstacleShape shape = obstacle.getShape(); |
| | | List<Obstacledge.XYCoordinate> xyCoords; |
| | | |
| | | List<Obstacledge.XYCoordinate> xyCoords = new ArrayList<>(); |
| | | for (int i = 0; i < originalCoordsList.size(); i += 2) { |
| | | if (i + 1 >= originalCoordsList.size()) break; |
| | | double lat = originalCoordsList.get(i).toDecimalDegree(); |
| | | double lon = originalCoordsList.get(i + 1).toDecimalDegree(); |
| | | |
| | | if (Double.isFinite(lat) && Double.isFinite(lon)) { |
| | | double[] localXY = convertLatLonToLocal(lat, lon, baseLat, baseLon); |
| | | xyCoords.add(new Obstacledge.XYCoordinate(localXY[0], localXY[1])); |
| | | } |
| | | // 根据障碍物形状调用不同的算法 |
| | | if (shape == Obstacledge.ObstacleShape.POLYGON) { |
| | | // 多边形:使用 bianjieguihua2 算法 |
| | | xyCoords = generatePolygonCoordinates(originalCoordsList, baseStation); |
| | | } else if (shape == Obstacledge.ObstacleShape.CIRCLE) { |
| | | // 圆形:使用简单的坐标转换(保持原有逻辑) |
| | | xyCoords = generateCircleCoordinates(originalCoordsList, baseStation); |
| | | } else { |
| | | JOptionPane.showMessageDialog(this, "未知的障碍物形状", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | if (xyCoords.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "坐标转换失败", "错误", JOptionPane.ERROR_MESSAGE); |
| | | if (xyCoords == null || xyCoords.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "坐标生成失败", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用 bianjieguihua2 算法生成多边形坐标 |
| | | */ |
| | | private List<Obstacledge.XYCoordinate> generatePolygonCoordinates( |
| | | List<Obstacledge.DMCoordinate> originalCoordsList, String baseStation) { |
| | | |
| | | // 保存当前的 Coordinate.coordinates |
| | | List<Coordinate> savedCoordinates = new ArrayList<>(Coordinate.coordinates); |
| | | |
| | | try { |
| | | // 将障碍物的原始坐标转换为 Coordinate 对象列表 |
| | | List<Coordinate> coordinateList = new ArrayList<>(); |
| | | for (int i = 0; i < originalCoordsList.size(); i += 2) { |
| | | if (i + 1 >= originalCoordsList.size()) break; |
| | | |
| | | Obstacledge.DMCoordinate latCoord = originalCoordsList.get(i); |
| | | Obstacledge.DMCoordinate lonCoord = originalCoordsList.get(i + 1); |
| | | |
| | | // 转换为 Coordinate 对象 |
| | | // DMCoordinate 的 degreeMinute 是度分格式(如 2324.200273 表示 23度24.200273分) |
| | | // 需要格式化为字符串,保持度分格式 |
| | | double latDM = latCoord.getDegreeMinute(); |
| | | double lonDM = lonCoord.getDegreeMinute(); |
| | | |
| | | // 格式化度分格式:确保整数部分至少2位(度),小数部分是分 |
| | | String latStr = formatDegreeMinute(latDM); |
| | | String lonStr = formatDegreeMinute(lonDM); |
| | | char latDir = latCoord.getDirection(); |
| | | char lonDir = lonCoord.getDirection(); |
| | | |
| | | Coordinate coord = new Coordinate( |
| | | latStr, |
| | | String.valueOf(latDir), |
| | | lonStr, |
| | | String.valueOf(lonDir), |
| | | 0.0 // 高程数据,障碍物可能没有,设为0 |
| | | ); |
| | | coordinateList.add(coord); |
| | | } |
| | | |
| | | if (coordinateList.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // 设置到全局坐标列表 |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.coordinates.addAll(coordinateList); |
| | | |
| | | // 调用 bianjieguihua2 算法生成优化后的多边形坐标 |
| | | String optimizedCoordsStr = bianjieguihua2.processCoordinateListAuto(baseStation); |
| | | |
| | | if (optimizedCoordsStr == null || optimizedCoordsStr.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // 解析返回的坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." |
| | | List<Obstacledge.XYCoordinate> xyCoords = new ArrayList<>(); |
| | | String[] pointStrings = optimizedCoordsStr.split(";"); |
| | | for (String pointStr : pointStrings) { |
| | | pointStr = pointStr.trim(); |
| | | if (pointStr.isEmpty()) continue; |
| | | |
| | | String[] parts = pointStr.split(","); |
| | | if (parts.length >= 2) { |
| | | try { |
| | | double x = Double.parseDouble(parts[0].trim()); |
| | | double y = Double.parseDouble(parts[1].trim()); |
| | | if (Double.isFinite(x) && Double.isFinite(y)) { |
| | | xyCoords.add(new Obstacledge.XYCoordinate(x, y)); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 跳过无效的坐标点 |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return xyCoords; |
| | | |
| | | } finally { |
| | | // 恢复原来的坐标列表 |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.coordinates.addAll(savedCoordinates); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 格式化度分格式坐标 |
| | | * @param degreeMinute 度分值,如 2324.200273 表示 23度24.200273分 |
| | | * @return 格式化的字符串 |
| | | */ |
| | | private String formatDegreeMinute(double degreeMinute) { |
| | | // 度分格式:整数部分是度,小数部分是分 |
| | | // 例如 2324.200273 -> "2324.200273" |
| | | return String.format("%.6f", degreeMinute); |
| | | } |
| | | |
| | | /** |
| | | * 生成圆形坐标(保持原有简单转换逻辑) |
| | | */ |
| | | private List<Obstacledge.XYCoordinate> generateCircleCoordinates( |
| | | List<Obstacledge.DMCoordinate> originalCoordsList, String baseStation) { |
| | | |
| | | String[] baseParts = baseStation.split(","); |
| | | if (baseParts.length < 4) { |
| | | return null; |
| | | } |
| | | |
| | | double baseLat = parseDMToDecimal(baseParts[0].trim(), baseParts[1].trim()); |
| | | double baseLon = parseDMToDecimal(baseParts[2].trim(), baseParts[3].trim()); |
| | | |
| | | List<Obstacledge.XYCoordinate> xyCoords = new ArrayList<>(); |
| | | for (int i = 0; i < originalCoordsList.size(); i += 2) { |
| | | if (i + 1 >= originalCoordsList.size()) break; |
| | | double lat = originalCoordsList.get(i).toDecimalDegree(); |
| | | double lon = originalCoordsList.get(i + 1).toDecimalDegree(); |
| | | |
| | | if (Double.isFinite(lat) && Double.isFinite(lon)) { |
| | | double[] localXY = convertLatLonToLocal(lat, lon, baseLat, baseLon); |
| | | xyCoords.add(new Obstacledge.XYCoordinate(localXY[0], localXY[1])); |
| | | } |
| | | } |
| | | |
| | | return xyCoords; |
| | | } |
| | | |
| | | private void saveObstacleUpdate(Obstacledge.Obstacle obstacle, JTextArea coordArea) { |
| | | String landNumber = dikuai.getLandNumber(); |
| | | try { |
| | |
| | | import java.awt.Font; |
| | | import java.awt.Image; |
| | | import java.awt.Window; |
| | | import java.awt.Container; |
| | | import java.awt.event.ActionListener; |
| | | import java.awt.event.MouseAdapter; |
| | | import java.awt.event.MouseEvent; |
| | | import java.awt.event.MouseListener; |
| | | import java.awt.event.WindowAdapter; |
| | | import java.awt.event.WindowEvent; |
| | | import java.io.File; |
| | |
| | | import zhangaiwu.Obstacledge; |
| | | import zhangaiwu.yulanzhangaiwu; |
| | | import zhuye.buttonset; |
| | | import bianjie.bianjieguihua2; |
| | | |
| | | /** |
| | | * 障碍物新增/编辑对话框。设计语言参考 {@link AddDikuai},支持通过实地绘制采集障碍物坐标。 |
| | |
| | | private JPanel selectedMethodPanel; |
| | | private JPanel selectedShapePanel; |
| | | private JButton drawButton; |
| | | private JButton generateBoundaryButton; // 生成障碍物边界按钮 |
| | | private JButton previewButton; // 预览按钮 |
| | | private JLabel drawingStatusLabel; |
| | | private JLabel boundaryStatusLabel; // 生成障碍物边界状态标签 |
| | | private JTextField obstacleNameField; |
| | | private JPanel existingObstacleListPanel; |
| | | private JPanel step1NextButtonRow; |
| | |
| | | formData.remove("editingObstacleName"); |
| | | formData.remove("obstacleCoordinates"); |
| | | formData.remove("obstacleOriginalCoordinates"); |
| | | formData.remove("generatedBoundaryCoordinates"); // 清除生成的边界坐标 |
| | | if (obstacleNameField != null) { |
| | | obstacleNameField.setText(""); |
| | | } else { |
| | |
| | | } |
| | | updateDrawingStatus(); |
| | | updateSaveButtonState(); |
| | | updatePreviewButtonState(); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 24))); |
| | | |
| | | // 按钮容器:重新绘制 + 生成障碍物边界 |
| | | JPanel buttonRow = new JPanel(); |
| | | buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.X_AXIS)); |
| | | buttonRow.setOpaque(false); |
| | | buttonRow.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | drawButton = createPrimaryButton("开始绘制", 16); |
| | | drawButton.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | drawButton.addActionListener(e -> startDrawingWorkflow()); |
| | | stepPanel.add(drawButton); |
| | | buttonRow.add(drawButton); |
| | | |
| | | buttonRow.add(Box.createRigidArea(new Dimension(10, 0))); |
| | | |
| | | generateBoundaryButton = createPrimaryButton("生成障碍物边界", 16); |
| | | generateBoundaryButton.setVisible(false); // 初始隐藏,绘制完成后显示 |
| | | generateBoundaryButton.addActionListener(e -> generateObstacleBoundary()); |
| | | buttonRow.add(generateBoundaryButton); |
| | | |
| | | stepPanel.add(buttonRow); |
| | | |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 12))); |
| | | |
| | |
| | | drawingStatusLabel.setForeground(LIGHT_TEXT); |
| | | drawingStatusLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | stepPanel.add(drawingStatusLabel); |
| | | |
| | | // 添加生成障碍物边界状态标签 |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | boundaryStatusLabel = new JLabel(""); |
| | | boundaryStatusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); |
| | | boundaryStatusLabel.setForeground(LIGHT_TEXT); |
| | | boundaryStatusLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | boundaryStatusLabel.setVisible(false); // 初始隐藏 |
| | | stepPanel.add(boundaryStatusLabel); |
| | | |
| | | stepPanel.add(Box.createVerticalGlue()); |
| | | return stepPanel; |
| | |
| | | // 设置下一步按钮宽度为300像素 |
| | | nextButton.setPreferredSize(new Dimension(300, nextButton.getPreferredSize().height)); |
| | | nextButton.setMaximumSize(new Dimension(300, nextButton.getPreferredSize().height)); |
| | | |
| | | previewButton = createSecondaryButton("预览"); |
| | | previewButton.setVisible(false); // 初始隐藏,生成边界后显示 |
| | | previewButton.setOpaque(true); |
| | | previewButton.setContentAreaFilled(true); |
| | | previewButton.addActionListener(e -> previewObstacleBoundary()); |
| | | |
| | | saveButton = createPrimaryButton("保存", 16); |
| | | saveButton.setVisible(false); |
| | | |
| | | buttonPanel.add(prevButton); |
| | | buttonPanel.add(Box.createHorizontalGlue()); |
| | | buttonPanel.add(Box.createRigidArea(new Dimension(12, 0))); |
| | | buttonPanel.add(previewButton); |
| | | buttonPanel.add(Box.createRigidArea(new Dimension(10, 0))); |
| | | buttonPanel.add(saveButton); |
| | | |
| | | attachNextButtonToStep1Row(); |
| | |
| | | } |
| | | |
| | | activeDrawingShape = null; |
| | | |
| | | // 重新绘制时清除之前生成的边界坐标 |
| | | formData.remove("generatedBoundaryCoordinates"); |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | } |
| | | // 清除边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | boundaryStatusLabel.setText(""); |
| | | } |
| | | |
| | | String method = formData.get("drawingMethod"); |
| | | if (!isMeaningfulValue(method)) { |
| | |
| | | private void preloadData() { |
| | | formData.remove("obstacleCoordinates"); |
| | | formData.remove("obstacleOriginalCoordinates"); |
| | | formData.remove("generatedBoundaryCoordinates"); // 清除生成的边界坐标 |
| | | formData.remove("editingObstacleName"); |
| | | updateDrawingStatus(); |
| | | updatePreviewButtonState(); |
| | | // 清除边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | boundaryStatusLabel.setText(""); |
| | | } |
| | | } |
| | | |
| | | private void updateDrawingStatus() { |
| | |
| | | drawButton.setText("重新绘制"); |
| | | drawButton.setEnabled(true); |
| | | } |
| | | // 绘制完成后显示"生成障碍物边界"按钮 |
| | | if (generateBoundaryButton != null) { |
| | | generateBoundaryButton.setVisible(true); |
| | | } |
| | | } else { |
| | | drawingStatusLabel.setText("尚未采集障碍物坐标"); |
| | | if (!drawingInProgress && drawButton != null) { |
| | | drawButton.setText("开始绘制"); |
| | | drawButton.setEnabled(true); |
| | | } |
| | | // 未绘制时隐藏"生成障碍物边界"按钮 |
| | | if (generateBoundaryButton != null) { |
| | | generateBoundaryButton.setVisible(false); |
| | | } |
| | | // 未生成边界时隐藏预览按钮 |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | } |
| | | // 隐藏边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | } |
| | | } |
| | | updateSaveButtonState(); |
| | | updatePreviewButtonState(); |
| | | } |
| | | |
| | | /** |
| | | * 更新边界状态标签 |
| | | */ |
| | | private void updateBoundaryStatusLabel(int pointCount) { |
| | | if (boundaryStatusLabel == null) { |
| | | return; |
| | | } |
| | | if (pointCount > 0) { |
| | | boundaryStatusLabel.setText("生成障碍物边界点数:" + pointCount); |
| | | boundaryStatusLabel.setVisible(true); |
| | | } else { |
| | | boundaryStatusLabel.setText(""); |
| | | boundaryStatusLabel.setVisible(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新预览按钮的显示状态 |
| | | */ |
| | | private void updatePreviewButtonState() { |
| | | if (previewButton == null) { |
| | | return; |
| | | } |
| | | // 只有在生成边界后才显示预览按钮 |
| | | String generatedBoundary = formData.get("generatedBoundaryCoordinates"); |
| | | boolean hasGeneratedBoundary = isMeaningfulValue(generatedBoundary); |
| | | |
| | | if (hasGeneratedBoundary) { |
| | | // 生成边界后,重新创建绿色的预览按钮 |
| | | // 获取按钮的父容器和位置 |
| | | Container parent = previewButton.getParent(); |
| | | if (parent != null) { |
| | | // 保存原有的ActionListener |
| | | ActionListener[] listeners = previewButton.getActionListeners(); |
| | | |
| | | // 移除旧按钮 |
| | | parent.remove(previewButton); |
| | | |
| | | // 创建新的绿色预览按钮(使用主按钮样式) |
| | | previewButton = createPrimaryButton("预览", 16); |
| | | previewButton.setVisible(true); |
| | | previewButton.setEnabled(true); |
| | | |
| | | // 恢复ActionListener |
| | | for (ActionListener listener : listeners) { |
| | | previewButton.addActionListener(listener); |
| | | } |
| | | |
| | | // 添加到父容器(在保存按钮之前) |
| | | int previewIndex = -1; |
| | | Component[] components = parent.getComponents(); |
| | | for (int i = 0; i < components.length; i++) { |
| | | if (components[i] instanceof JButton) { |
| | | JButton btn = (JButton) components[i]; |
| | | if ("保存".equals(btn.getText())) { |
| | | previewIndex = i; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (previewIndex >= 0) { |
| | | parent.add(previewButton, previewIndex); |
| | | parent.add(Box.createRigidArea(new Dimension(10, 0)), previewIndex + 1); |
| | | } else { |
| | | // 如果找不到保存按钮,添加到末尾 |
| | | parent.add(Box.createRigidArea(new Dimension(12, 0))); |
| | | parent.add(previewButton); |
| | | parent.add(Box.createRigidArea(new Dimension(10, 0))); |
| | | } |
| | | |
| | | // 刷新布局 |
| | | parent.revalidate(); |
| | | parent.repaint(); |
| | | } |
| | | } else { |
| | | // 未生成边界时,隐藏按钮 |
| | | previewButton.setVisible(false); |
| | | previewButton.setEnabled(false); |
| | | } |
| | | } |
| | | |
| | | private int countCoordinatePairs(String coords) { |
| | |
| | | if (saveButton != null) { |
| | | boolean hasCoords = isMeaningfulValue(formData.get("obstacleCoordinates")); |
| | | boolean hasName = isMeaningfulValue(formData.get("obstacleName")); |
| | | boolean enabled = hasCoords && hasName; |
| | | |
| | | // 检查是否生成了障碍物边界坐标 |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | boolean hasGeneratedBoundary = isMeaningfulValue(formData.get("generatedBoundaryCoordinates")); |
| | | |
| | | // 如果是多边形障碍物,必须生成边界坐标才能保存 |
| | | // 如果是圆形障碍物或其他形状,不需要生成边界就能保存 |
| | | boolean boundaryRequirementMet = true; |
| | | if ("polygon".equals(shapeKey)) { |
| | | boundaryRequirementMet = hasGeneratedBoundary; |
| | | } |
| | | |
| | | boolean enabled = hasCoords && hasName && boundaryRequirementMet; |
| | | saveButton.setEnabled(enabled); |
| | | if (enabled) { |
| | | saveButton.setBackground(PRIMARY_COLOR); |
| | | saveButton.setForeground(WHITE); |
| | | saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | saveButton.setOpaque(true); |
| | | saveButton.setContentAreaFilled(true); |
| | | } else { |
| | | saveButton.setBackground(MEDIUM_GRAY); |
| | | saveButton.setForeground(TEXT_COLOR); |
| | | saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); |
| | | saveButton.setOpaque(true); |
| | | saveButton.setContentAreaFilled(true); |
| | | } |
| | | } |
| | | } |
| | |
| | | String obstacleName = formData.get("obstacleName"); |
| | | String coordsValue = formData.get("obstacleCoordinates"); |
| | | String originalValue = formData.get("obstacleOriginalCoordinates"); |
| | | String generatedBoundaryValue = formData.get("generatedBoundaryCoordinates"); // 生成的边界坐标 |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | String previousName = formData.get("editingObstacleName"); |
| | | |
| | |
| | | |
| | | String coords = coordsValue.trim(); |
| | | String originalCoords = isMeaningfulValue(originalValue) ? originalValue.trim() : null; |
| | | // 如果有生成的边界坐标,使用生成的边界坐标;否则使用原始坐标 |
| | | String finalCoords = isMeaningfulValue(generatedBoundaryValue) ? generatedBoundaryValue.trim() : coords; |
| | | String trimmedName = isMeaningfulValue(obstacleName) ? obstacleName.trim() : null; |
| | | if (!isMeaningfulValue(trimmedName)) { |
| | | JOptionPane.showMessageDialog(this, "障碍物名称无效", "错误", JOptionPane.ERROR_MESSAGE); |
| | |
| | | formData.put("editingObstacleName", trimmedName); |
| | | } |
| | | |
| | | if (!persistObstacleToConfig(landNumber, previousName, trimmedName, shapeKey, coords, originalCoords)) { |
| | | if (!persistObstacleToConfig(landNumber, previousName, trimmedName, shapeKey, finalCoords, originalCoords)) { |
| | | JOptionPane.showMessageDialog(this, "写入障碍物配置失败,请重试", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | |
| | | activeSession = null; |
| | | dispose(); |
| | | } |
| | | |
| | | /** |
| | | * 生成障碍物边界坐标 |
| | | */ |
| | | private void generateObstacleBoundary() { |
| | | String originalCoords = formData.get("obstacleOriginalCoordinates"); |
| | | if (!isMeaningfulValue(originalCoords)) { |
| | | JOptionPane.showMessageDialog(this, "无原始坐标数据,无法生成边界", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | String baseStation = targetDikuai.getBaseStationCoordinates(); |
| | | if (!isMeaningfulValue(baseStation)) { |
| | | JOptionPane.showMessageDialog(this, "地块未设置基站坐标", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | if (!"polygon".equals(shapeKey)) { |
| | | JOptionPane.showMessageDialog(this, "只有多边形障碍物才需要生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 检查绘制方式,只有割草机绘制的多边形才调用bianjieguihua2 |
| | | String method = formData.get("drawingMethod"); |
| | | if (!"mower".equals(method)) { |
| | | JOptionPane.showMessageDialog(this, "只有割草机绘制的多边形才支持生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 将原始坐标转换为Coordinate对象列表 |
| | | List<Coordinate> coordinateList = parseOriginalCoordinatesToCoordinateList(originalCoords); |
| | | if (coordinateList.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "原始坐标数据无效", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 保存当前的Coordinate.coordinates |
| | | List<Coordinate> savedCoordinates = new ArrayList<>(Coordinate.coordinates); |
| | | |
| | | try { |
| | | // 设置到全局坐标列表 |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.coordinates.addAll(coordinateList); |
| | | |
| | | // 调用bianjieguihua2算法生成优化后的多边形边界坐标 |
| | | String optimizedCoordsStr = bianjieguihua2.processCoordinateListAuto(baseStation); |
| | | |
| | | if (optimizedCoordsStr == null || optimizedCoordsStr.trim().isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "生成边界坐标失败", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 保存生成的边界坐标 |
| | | formData.put("generatedBoundaryCoordinates", optimizedCoordsStr.trim()); |
| | | |
| | | // 计算生成的边界点数 |
| | | int boundaryPointCount = countCoordinatePairs(optimizedCoordsStr.trim()); |
| | | |
| | | // 更新边界状态标签显示 |
| | | updateBoundaryStatusLabel(boundaryPointCount); |
| | | |
| | | // 更新预览按钮显示(变成绿色可点击) |
| | | updatePreviewButtonState(); |
| | | |
| | | // 更新保存按钮状态(变成可点击) |
| | | updateSaveButtonState(); |
| | | |
| | | // 强制刷新UI |
| | | SwingUtilities.invokeLater(() -> { |
| | | if (previewButton != null) { |
| | | previewButton.revalidate(); |
| | | previewButton.repaint(); |
| | | } |
| | | }); |
| | | |
| | | JOptionPane.showMessageDialog(this, "障碍物边界坐标已生成", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | |
| | | } finally { |
| | | // 恢复原来的坐标列表 |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.coordinates.addAll(savedCoordinates); |
| | | } |
| | | |
| | | } catch (Exception ex) { |
| | | ex.printStackTrace(); |
| | | JOptionPane.showMessageDialog(this, "生成边界坐标时发生错误: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将原始坐标字符串解析为Coordinate对象列表 |
| | | */ |
| | | private List<Coordinate> parseOriginalCoordinatesToCoordinateList(String originalCoords) { |
| | | List<Coordinate> coordinateList = new ArrayList<>(); |
| | | if (!isMeaningfulValue(originalCoords)) { |
| | | return coordinateList; |
| | | } |
| | | |
| | | // 原始坐标格式:纬度1,方向1,经度1,方向1;纬度2,方向2,经度2,方向2;... |
| | | String[] pointStrings = originalCoords.split(";"); |
| | | for (String pointStr : pointStrings) { |
| | | pointStr = pointStr.trim(); |
| | | if (pointStr.isEmpty()) continue; |
| | | |
| | | String[] parts = pointStr.split(","); |
| | | if (parts.length >= 4) { |
| | | try { |
| | | String lat = parts[0].trim(); |
| | | String latDir = parts[1].trim(); |
| | | String lon = parts[2].trim(); |
| | | String lonDir = parts[3].trim(); |
| | | |
| | | Coordinate coord = new Coordinate(lat, latDir, lon, lonDir, 0.0); |
| | | coordinateList.add(coord); |
| | | } catch (Exception e) { |
| | | // 跳过无效的坐标点 |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return coordinateList; |
| | | } |
| | | |
| | | /** |
| | | * 预览障碍物边界 |
| | | */ |
| | | private void previewObstacleBoundary() { |
| | | String generatedBoundary = formData.get("generatedBoundaryCoordinates"); |
| | | if (!isMeaningfulValue(generatedBoundary)) { |
| | | JOptionPane.showMessageDialog(this, "请先生成障碍物边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | String landNumber = targetDikuai.getLandNumber(); |
| | | String landName = targetDikuai.getLandName(); |
| | | String boundary = targetDikuai.getBoundaryCoordinates(); |
| | | |
| | | // 关闭当前对话框 |
| | | setVisible(false); |
| | | |
| | | SwingUtilities.invokeLater(() -> { |
| | | Shouye shouye = Shouye.getInstance(); |
| | | if (shouye != null) { |
| | | // 传递回调以重新打开新增障碍物步骤2页面 |
| | | shouye.startMowingPathPreview( |
| | | landNumber, |
| | | landName, |
| | | boundary, |
| | | generatedBoundary, // 使用生成的边界坐标作为障碍物坐标 |
| | | null, |
| | | () -> SwingUtilities.invokeLater(() -> { |
| | | // 重新打开新增障碍物步骤2页面 |
| | | Window owner = SwingUtilities.getWindowAncestor(shouye); |
| | | setVisible(true); |
| | | }) |
| | | ); |
| | | } else { |
| | | JOptionPane.showMessageDialog(null, "无法打开主页面进行预览", "提示", JOptionPane.WARNING_MESSAGE); |
| | | setVisible(true); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private boolean persistObstacleToConfig(String landNumber, String previousName, String obstacleName, |
| | | String shapeKey, String xyCoords, String originalCoords) { |
| | |
| | | if (step < 2) { |
| | | nextButton.setVisible(true); |
| | | saveButton.setVisible(false); |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | } |
| | | } else { |
| | | nextButton.setVisible(false); |
| | | saveButton.setVisible(true); |
| | | updateDrawingStatus(); |
| | | updatePreviewButtonState(); |
| | | } |
| | | updateSaveButtonState(); |
| | | revalidate(); |
| | |
| | | } |
| | | |
| | | updateDrawingStatus(); |
| | | |
| | | // 如果已有生成的边界坐标,更新边界状态标签 |
| | | String generatedBoundary = session.data.get("generatedBoundaryCoordinates"); |
| | | if (isMeaningfulValue(generatedBoundary)) { |
| | | int boundaryPointCount = countCoordinatePairs(generatedBoundary); |
| | | updateBoundaryStatusLabel(boundaryPointCount); |
| | | } else { |
| | | // 如果没有生成的边界坐标,隐藏边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | boundaryStatusLabel.setText(""); |
| | | } |
| | | } |
| | | |
| | | // 更新按钮状态 |
| | | updatePreviewButtonState(); |
| | | updateSaveButtonState(); |
| | | |
| | | currentStep = 2; |
| | | showStep(2); |
| | | } |
| | |
| | | BorderFactory.createLineBorder(PRIMARY_DARK, 2), |
| | | BorderFactory.createEmptyBorder(10, 22, 10, 22))); |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | button.setOpaque(true); |
| | | button.setContentAreaFilled(true); |
| | | button.addMouseListener(new MouseAdapter() { |
| | | @Override |
| | | public void mouseEntered(MouseEvent e) { |
| | |
| | | public void mouseExited(MouseEvent e) { |
| | | if (button.isEnabled()) { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | } else { |
| | | // 禁用时保持灰色背景 |
| | | button.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | }); |
| | |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 22, 10, 22))); |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | button.setOpaque(true); |
| | | button.setContentAreaFilled(true); |
| | | return button; |
| | | } |
| | | |
| | |
| | | formGroup.add(areaNameField); |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | formGroup.add(hintLabel); |
| | | |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | JPanel obstacleSection = createObstacleSummarySection(); |
| | | formGroup.add(obstacleSection); |
| | | |
| | | stepPanel.add(formGroup); |
| | | stepPanel.add(Box.createVerticalGlue()); |
| | |
| | | private void showStep(int step) { |
| | | currentStep = step; |
| | | cardLayout.show(stepsPanel, "step" + step); |
| | | |
| | | if (step == 1) { |
| | | updateObstacleSummary(); |
| | | } |
| | | |
| | | // 更新按钮状态 |
| | | updateButtonState(step); |