张世豪
6 天以前 8ce07ce9a4034fdc959d280dd38ecb3e05cbe6e1
重新设计了障碍物的绘制逻辑
已修改6个文件
565 ■■■■■ 文件已修改
Obstacledge.properties 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dikuai.properties 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
set.properties 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/dikuai/ObstacleManagementPage.java 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/dikuai/addzhangaiwu.java 379 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhangaiwu/AddDikuai.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Obstacledge.properties
@@ -1,5 +1,5 @@
# 割草机地块障碍物配置文件
# 生成时间:2025-12-17T13:28:00.386604500
# 生成时间:2025-12-17T16:39:03.715543100
# 坐标系:WGS84(度分格式)
# ============ 地块基准站配置 ============
@@ -13,7 +13,7 @@
# 格式: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
@@ -1,5 +1,5 @@
#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
@@ -15,5 +15,5 @@
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
set.properties
@@ -1,16 +1,16 @@
#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
src/dikuai/ObstacleManagementPage.java
@@ -14,6 +14,8 @@
import zhangaiwu.Obstacledge;
import zhuye.Shouye;
import zhuye.Coordinate;
import bianjie.bianjieguihua2;
/**
 * 障碍物管理页面 - UI优化版
@@ -618,23 +620,23 @@
                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;
            }
            
@@ -647,6 +649,132 @@
        }
    }
    
    /**
     * 使用 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 {
src/dikuai/addzhangaiwu.java
@@ -10,8 +10,11 @@
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;
@@ -52,6 +55,7 @@
import zhangaiwu.Obstacledge;
import zhangaiwu.yulanzhangaiwu;
import zhuye.buttonset;
import bianjie.bianjieguihua2;
/**
 * 障碍物新增/编辑对话框。设计语言参考 {@link AddDikuai},支持通过实地绘制采集障碍物坐标。
@@ -88,7 +92,10 @@
    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;
@@ -490,6 +497,7 @@
            formData.remove("editingObstacleName");
            formData.remove("obstacleCoordinates");
            formData.remove("obstacleOriginalCoordinates");
            formData.remove("generatedBoundaryCoordinates");  // 清除生成的边界坐标
            if (obstacleNameField != null) {
                obstacleNameField.setText("");
            } else {
@@ -497,6 +505,7 @@
            }
            updateDrawingStatus();
            updateSaveButtonState();
            updatePreviewButtonState();
        }
    }
@@ -539,10 +548,24 @@
        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)));
@@ -551,6 +574,15 @@
        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;
@@ -567,12 +599,21 @@
        // 设置下一步按钮宽度为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();
@@ -869,6 +910,17 @@
        }
        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)) {
@@ -1368,8 +1420,15 @@
    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() {
@@ -1384,14 +1443,113 @@
                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) {
@@ -1419,16 +1577,32 @@
        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);
            }
        }
    }
@@ -1466,6 +1640,7 @@
        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");
@@ -1476,6 +1651,8 @@
        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);
@@ -1487,7 +1664,7 @@
            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;
        }
@@ -1499,6 +1676,171 @@
        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) {
@@ -1706,10 +2048,14 @@
        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();
@@ -1753,6 +2099,24 @@
        }
        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);
    }
@@ -1882,6 +2246,8 @@
                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) {
@@ -1894,6 +2260,9 @@
            public void mouseExited(MouseEvent e) {
                if (button.isEnabled()) {
                    button.setBackground(PRIMARY_COLOR);
                } else {
                    // 禁用时保持灰色背景
                    button.setBackground(MEDIUM_GRAY);
                }
            }
        });
@@ -1908,6 +2277,8 @@
                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;
    }
src/zhangaiwu/AddDikuai.java
@@ -292,10 +292,6 @@
        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());
@@ -1658,10 +1654,6 @@
    private void showStep(int step) {
        currentStep = step;
        cardLayout.show(stepsPanel, "step" + step);
        if (step == 1) {
            updateObstacleSummary();
        }
        // 更新按钮状态
        updateButtonState(step);