张世豪
3 天以前 0930bed760105b81e2e5055801bec6d6e8d57358
src/zhangaiwu/AddDikuai.java
@@ -1,7 +1,5 @@
package zhangaiwu;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
@@ -9,10 +7,8 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -20,20 +16,23 @@
import java.util.Map;
import java.util.Comparator;
import java.awt.geom.Point2D;
import baseStation.BaseStation;
import bianjie.jisuanmianjie;
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
import bianjie.bianjieguihua2;
import dikuai.Gecaokuanjisuan;
import dikuai.Gecaoanquanjuli;
import bianjie.Bianjieyouhuatoxy;
import lujing.Lunjingguihua;
import lujing.Qufenxingzhuang;
import lujing.AoxinglujingNoObstacle;
import lujing.YixinglujingNoObstacle;
import set.Setsys;
import ui.UIConfig;
import zhuye.MowerLocationData;
import zhuye.Shouye;
import zhuye.Coordinate;
import zhuye.buttonset;
import gecaoji.Device;
import publicway.buttonset;
/**
 * 新增地块对话框 - 多步骤表单设计
@@ -66,10 +65,9 @@
    private JTextField landNumberField;
    private JTextField areaNameField;
    private JComboBox<String> mowingPatternCombo;
    private JTextField mowingWidthField; // 割草机割刀宽度
    private JTextField overlapDistanceField; // 相邻行重叠距离
    private JTextField mowingSafetyDistanceField; // 割草安全距离
    private JLabel calculatedMowingWidthLabel; // 计算后的割草宽度显示
    private JTextField bladeWidthField;  // 割草机割刀宽度(m)
    private JTextField mowingWidthField;  // 割草宽度(m,可编辑)
    private JTextField safetyDistanceField;  // 割草安全距离(m,可编辑)
    private JPanel previewPanel;
    private Map<String, JPanel> drawingOptionPanels = new HashMap<>();
    
@@ -78,9 +76,12 @@
    private JButton prevButton;
    private JButton nextButton;
    private JButton createButton;
    private JButton previewButton;
    private JButton previewButton;  // 步骤3的预览按钮(预览割草路径)
    private JButton boundaryPreviewButton;  // 步骤2的预览按钮(预览边界)
    private Component previewButtonSpacer;
    private JLabel boundaryCountLabel;
    private JTextArea boundaryXYTextArea;  // 显示边界XY坐标的文本域
    private JScrollPane boundaryXYScrollPane;  // 边界XY坐标文本域的滚动面板
    private JPanel obstacleListContainer;
    private JTextArea pathGenerationMessageArea;
    private JPanel pathMessageWrapper;
@@ -263,7 +264,7 @@
        ));
        areaNameField.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        // 添加输入框焦点效果
        // 添加输入框焦点效果和文本变化监听
        areaNameField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
@@ -279,6 +280,16 @@
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(12, 15, 12, 15)
                ));
                // 更新下一步按钮状态
                updateStep1ButtonState();
            }
        });
        // 添加文本变化监听,实时更新按钮状态
        areaNameField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                updateStep1ButtonState();
            }
        });
        
@@ -296,6 +307,10 @@
        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());
@@ -653,10 +668,39 @@
        startEndDrawingBtn.setAlignmentX(Component.LEFT_ALIGNMENT);
        startEndDrawingBtn.setMaximumSize(new Dimension(400, 55));
        startEndDrawingBtn.setEnabled(false); // 初始不可用
        startEndDrawingBtn.setBackground(MEDIUM_GRAY); // 初始灰色背景
        
        startEndDrawingBtn.addActionListener(e -> toggleDrawing());
        
        stepPanel.add(startEndDrawingBtn);
        // 边界XY坐标显示文本域(3行,带滚动条,在已完成按钮下方)
        boundaryXYTextArea = new JTextArea(3, 0);
        boundaryXYTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        boundaryXYTextArea.setForeground(TEXT_COLOR);
        boundaryXYTextArea.setEditable(false);
        boundaryXYTextArea.setBackground(LIGHT_GRAY);
        boundaryXYTextArea.setLineWrap(true);
        boundaryXYTextArea.setWrapStyleWord(true);
        boundaryXYTextArea.setAlignmentX(Component.LEFT_ALIGNMENT);
        boundaryXYTextArea.setVisible(false);
        // 创建滚动面板,默认显示3行
        boundaryXYScrollPane = new JScrollPane(boundaryXYTextArea);
        boundaryXYScrollPane.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 1),
            BorderFactory.createEmptyBorder(0, 0, 0, 0)
        ));
        boundaryXYScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        boundaryXYScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        boundaryXYScrollPane.getVerticalScrollBar().setUnitIncrement(16);
        boundaryXYScrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
        boundaryXYScrollPane.setVisible(false);
        boundaryXYScrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, 80));
        stepPanel.add(Box.createRigidArea(new Dimension(0, 15)));
        stepPanel.add(boundaryXYScrollPane);
        boundaryCountLabel = new JLabel("已采集到边界点0个");
        boundaryCountLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        boundaryCountLabel.setForeground(LIGHT_TEXT);
@@ -744,7 +788,7 @@
                    return;
                }
                if (selectDrawingOption(optionPanel, type, true)) {
                    startEndDrawingBtn.setEnabled(true); // 选择后启用按钮
                    updateStartDrawingButtonState(); // 选择后更新按钮状态
                }
            }
            
@@ -772,7 +816,7 @@
            return false;
        }
        if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) {
            JOptionPane.showMessageDialog(this, "请先去系统设置添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE);
            JOptionPane.showMessageDialog(this, "请先添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE);
            return false;
        }
@@ -825,12 +869,20 @@
            // 用户在对话框内主动结束(未触发外部流程)
            isDrawing = false;
            Coordinate.setStartSaveGngga(false);
            startEndDrawingBtn.setText("开始绘制");
            startEndDrawingBtn.setBackground(PRIMARY_COLOR);
            updateOtherOptionsState(false);
            startEndDrawingBtn.setText("已完成");
            startEndDrawingBtn.setBackground(MEDIUM_GRAY);
            startEndDrawingBtn.setEnabled(false);
            updateOtherOptionsState(true);
            // 优化边界
            optimizeBoundaryAndSave();
            dikuaiData.put("boundaryDrawn", "true");
            JOptionPane.showMessageDialog(this, "边界绘制已完成", "提示", JOptionPane.INFORMATION_MESSAGE);
            showBoundaryPointSummary();
            updateBoundaryXYDisplay();
            // 更新预览和下一步按钮状态(背景颜色变绿色,可点击)
            updateStep2ButtonsAfterDrawing();
        }
    }
@@ -881,6 +933,80 @@
        hideBoundaryPointSummary();
    }
    
    /**
     * 优化边界并保存优化后的数据
     */
    private void optimizeBoundaryAndSave() {
        try {
            // 获取基准站坐标
            BaseStation baseStation = new BaseStation();
            baseStation.load();
            String baseStationCoordinates = normalizeCoordinateValue(baseStation.getInstallationCoordinates());
            if (!isMeaningfulValue(baseStationCoordinates)) {
                System.err.println("未获取到有效的基准站坐标,无法优化边界");
                return;
            }
            // 获取原始坐标列表
            List<Coordinate> uniqueCoordinates;
            synchronized (Coordinate.coordinates) {
                uniqueCoordinates = sanitizeCoordinateList(Coordinate.coordinates);
            }
            if (uniqueCoordinates.size() < 3) {
                System.err.println("采集的边界点不足,无法优化边界");
                return;
            }
            // 构建边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
            String boundaryStr = buildBoundaryStringForOptimization(uniqueCoordinates);
            // 调用优化方法
            String optimizedXYStr = Bianjieyouhuatoxy.optimizeBoundary(baseStationCoordinates, boundaryStr);
            // 保存优化后的XY坐标字符串
            if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
                dikuaiData.put("optimizedBoundaryXY", optimizedXYStr);
                if (activeSession != null) {
                    activeSession.data.put("optimizedBoundaryXY", optimizedXYStr);
                }
            } else {
                System.err.println("边界优化失败: " + optimizedXYStr);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("优化边界时发生错误: " + e.getMessage());
        }
    }
    /**
     * 构建用于优化的边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
     * 其中lat和lon是度分格式(DMM格式),例如 "3949.89151752"
     */
    private static String buildBoundaryStringForOptimization(List<Coordinate> coordinates) {
        if (coordinates == null || coordinates.isEmpty()) {
            return "()";
        }
        StringBuilder sb = new StringBuilder("(");
        DecimalFormat elevationFormat = new DecimalFormat("0.00");
        for (int i = 0; i < coordinates.size(); i++) {
            Coordinate coord = coordinates.get(i);
            // Coordinate类中的getLatitude()和getLongitude()已经返回度分格式(DMM格式)
            String latDMM = coord.getLatitude();
            String lonDMM = coord.getLongitude();
            double elevation = coord.getElevation();
            if (i > 0) {
                sb.append(";");
            }
            sb.append(latDMM).append(",")
              .append(lonDMM).append(",")
              .append(elevationFormat.format(elevation));
        }
        sb.append(")");
        return sb.toString();
    }
    private JPanel createStep3Panel() {
        JPanel stepPanel = new JPanel();
        stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
@@ -902,7 +1028,7 @@
        settingsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        // 割草模式选择
        JPanel patternPanel = createFormGroupWithoutHint("割草模式");
        JPanel patternPanel = createFormGroup("割草模式", "");
        mowingPatternCombo = new JComboBox<>(new String[]{"平行线", "螺旋式"});
        mowingPatternCombo.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        mowingPatternCombo.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
@@ -934,23 +1060,65 @@
        
        patternPanel.add(mowingPatternCombo);
        settingsPanel.add(patternPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 20)));
        
        // 割草机割刀宽度设置
        JPanel widthPanel = createFormGroupWithoutHint("割草机割刀宽度");
        JPanel bladeWidthPanel = createFormGroup("割草机割刀宽度", "");
        JPanel bladeWidthInputPanel = new JPanel(new BorderLayout());
        bladeWidthInputPanel.setBackground(WHITE);
        bladeWidthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        bladeWidthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        bladeWidthField = new JTextField("0.50");
        bladeWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        bladeWidthField.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        bladeWidthField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                bladeWidthField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
            }
            @Override
            public void focusLost(FocusEvent e) {
                bladeWidthField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
                updateMowingWidthAndSafetyDistance();
            }
        });
        JLabel bladeUnitLabel = new JLabel("m");
        bladeUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        bladeUnitLabel.setForeground(LIGHT_TEXT);
        bladeUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        bladeWidthInputPanel.add(bladeWidthField, BorderLayout.CENTER);
        bladeWidthInputPanel.add(bladeUnitLabel, BorderLayout.EAST);
        bladeWidthPanel.add(bladeWidthInputPanel);
        settingsPanel.add(bladeWidthPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 20)));
        // 割草宽度设置(可编辑)
        JPanel widthPanel = createFormGroup("割草宽度", "计算方法:割草宽度 = 割刀宽 ×85%");
        JPanel widthInputPanel = new JPanel(new BorderLayout());
        widthInputPanel.setBackground(WHITE);
        widthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        widthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        mowingWidthField = new JTextField("0.40");
        mowingWidthField = new JTextField();
        mowingWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        mowingWidthField.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        // 添加文本框焦点效果和变化监听
        mowingWidthField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
@@ -966,11 +1134,10 @@
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
                updateCalculatedMowingWidth();
            }
        });
        
        JLabel unitLabel = new JLabel("米");
        JLabel unitLabel = new JLabel("m");
        unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        unitLabel.setForeground(LIGHT_TEXT);
        unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
@@ -980,107 +1147,25 @@
        
        widthPanel.add(widthInputPanel);
        settingsPanel.add(widthPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 20)));
        
        // 相邻行重叠距离设置
        JPanel overlapPanel = createFormGroupWithoutHint("相邻行重叠距离");
        JPanel overlapInputPanel = new JPanel(new BorderLayout());
        overlapInputPanel.setBackground(WHITE);
        overlapInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        overlapInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        overlapDistanceField = new JTextField("0.06");
        overlapDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        overlapDistanceField.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        // 添加文本框焦点效果和变化监听
        overlapDistanceField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                overlapDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
            }
            @Override
            public void focusLost(FocusEvent e) {
                overlapDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
                updateCalculatedMowingWidth();
            }
        });
        JLabel overlapUnitLabel = new JLabel("米");
        overlapUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        overlapUnitLabel.setForeground(LIGHT_TEXT);
        overlapUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        overlapInputPanel.add(overlapDistanceField, BorderLayout.CENTER);
        overlapInputPanel.add(overlapUnitLabel, BorderLayout.EAST);
        overlapPanel.add(overlapInputPanel);
        settingsPanel.add(overlapPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        // 割草宽度显示(自动计算,不可修改)
        JPanel calculatedWidthPanel = createFormGroup("割草宽度", "割草宽度 = 割草机割刀宽度 - 重叠距离(自动计算)");
        JPanel calculatedWidthDisplayPanel = new JPanel(new BorderLayout());
        calculatedWidthDisplayPanel.setBackground(LIGHT_GRAY);
        calculatedWidthDisplayPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        calculatedWidthDisplayPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        calculatedWidthDisplayPanel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        calculatedMowingWidthLabel = new JLabel("0.00 米");
        calculatedMowingWidthLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        calculatedMowingWidthLabel.setForeground(TEXT_COLOR);
        calculatedWidthDisplayPanel.add(calculatedMowingWidthLabel, BorderLayout.CENTER);
        calculatedWidthPanel.add(calculatedWidthDisplayPanel);
        settingsPanel.add(calculatedWidthPanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        // 割草安全距离
        JPanel safetyDistancePanel = createFormGroupWithoutHint("割草安全距离");
        // 割草安全距离设置(可编辑)
        JPanel safetyDistancePanel = createFormGroup("割草安全距离", "根据割草机尺寸自动计算的安全距离");
        JPanel safetyDistanceInputPanel = new JPanel(new BorderLayout());
        safetyDistanceInputPanel.setBackground(WHITE);
        safetyDistanceInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
        safetyDistanceInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        // 获取默认值
        String defaultSafetyDistance = "0.5"; // 默认值
        try {
            Device device = Device.getGecaoji();
            if (device != null) {
                String safetyDistance = device.getMowingSafetyDistance();
                if (safetyDistance != null && !safetyDistance.trim().isEmpty() && !"-1".equals(safetyDistance.trim())) {
                    defaultSafetyDistance = safetyDistance.trim();
                }
            }
        } catch (Exception e) {
            // 如果获取失败,使用默认值
        }
        mowingSafetyDistanceField = new JTextField(defaultSafetyDistance);
        mowingSafetyDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
        safetyDistanceField = new JTextField();
        safetyDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
        safetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(BORDER_COLOR, 2),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)
        ));
        // 添加文本框焦点效果
        mowingSafetyDistanceField.addFocusListener(new FocusAdapter() {
        safetyDistanceField.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
                safetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
@@ -1088,29 +1173,37 @@
            
            @Override
            public void focusLost(FocusEvent e) {
                mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
                safetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(BORDER_COLOR, 2),
                    BorderFactory.createEmptyBorder(10, 12, 10, 12)
                ));
            }
        });
        
        JLabel safetyDistanceUnitLabel = new JLabel("米");
        safetyDistanceUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        safetyDistanceUnitLabel.setForeground(LIGHT_TEXT);
        safetyDistanceUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        JLabel safetyUnitLabel = new JLabel("m");
        safetyUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        safetyUnitLabel.setForeground(LIGHT_TEXT);
        safetyUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
        
        safetyDistanceInputPanel.add(mowingSafetyDistanceField, BorderLayout.CENTER);
        safetyDistanceInputPanel.add(safetyDistanceUnitLabel, BorderLayout.EAST);
        safetyDistanceInputPanel.add(safetyDistanceField, BorderLayout.CENTER);
        safetyDistanceInputPanel.add(safetyUnitLabel, BorderLayout.EAST);
        
        safetyDistancePanel.add(safetyDistanceInputPanel);
        settingsPanel.add(safetyDistancePanel);
        settingsPanel.add(Box.createRigidArea(new Dimension(0, 25)));
        
        stepPanel.add(settingsPanel);
        // 初始化计算值
        updateMowingWidthAndSafetyDistance();
        
        // 初始化计算后的割草宽度
        updateCalculatedMowingWidth();
        // 将设置面板放入滚动面板
        JScrollPane scrollPane = new JScrollPane(settingsPanel);
        scrollPane.setBorder(null);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollPane.getVerticalScrollBar().setUnitIncrement(16);
        scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
        stepPanel.add(scrollPane);
        
        JButton generatePathButton = createPrimaryButton("生成割草路径", 16);
        generatePathButton.setAlignmentX(Component.LEFT_ALIGNMENT);
@@ -1147,41 +1240,6 @@
        return stepPanel;
    }
    
    /**
     * 更新计算后的割草宽度显示
     */
    private void updateCalculatedMowingWidth() {
        if (calculatedMowingWidthLabel == null || mowingWidthField == null || overlapDistanceField == null) {
            return;
        }
        try {
            String widthText = mowingWidthField.getText().trim();
            String overlapText = overlapDistanceField.getText().trim();
            if (widthText.isEmpty() || overlapText.isEmpty()) {
                calculatedMowingWidthLabel.setText("0.00 米");
                return;
            }
            double bladeWidthMeters = Double.parseDouble(widthText);
            double overlapMeters = Double.parseDouble(overlapText);
            double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
            if (calculatedWidthMeters <= 0) {
                calculatedMowingWidthLabel.setText("无效(割刀宽度需大于重叠距离)");
                calculatedMowingWidthLabel.setForeground(ERROR_COLOR);
            } else {
                calculatedMowingWidthLabel.setText(String.format(Locale.US, "%.2f 米", calculatedWidthMeters));
                calculatedMowingWidthLabel.setForeground(TEXT_COLOR);
            }
        } catch (NumberFormatException e) {
            calculatedMowingWidthLabel.setText("0.00 米");
        } catch (Exception e) {
            calculatedMowingWidthLabel.setText("0.00 米");
        }
    }
    private JPanel createInstructionPanel(String text) {
        JPanel instructionPanel = new JPanel(new BorderLayout());
        instructionPanel.setBackground(PRIMARY_LIGHT);
@@ -1220,36 +1278,67 @@
        nameLabel.setForeground(TEXT_COLOR);
        nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        JLabel hintLabel = new JLabel(hint);
        hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
        hintLabel.setForeground(LIGHT_TEXT);
        hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        formGroup.add(nameLabel);
        formGroup.add(Box.createRigidArea(new Dimension(0, 6)));
        formGroup.add(hintLabel);
        formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
        
        return formGroup;
    }
    private JPanel createFormGroupWithoutHint(String label) {
        JPanel formGroup = new JPanel();
        formGroup.setLayout(new BoxLayout(formGroup, BoxLayout.Y_AXIS));
        formGroup.setBackground(WHITE);
        formGroup.setAlignmentX(Component.LEFT_ALIGNMENT);
        JLabel nameLabel = new JLabel(label);
        nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
        nameLabel.setForeground(TEXT_COLOR);
        nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        formGroup.add(nameLabel);
        formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
        // 只有当提示文字不为空时才显示
        if (hint != null && !hint.trim().isEmpty()) {
            formGroup.add(Box.createRigidArea(new Dimension(0, 6)));
            JLabel hintLabel = new JLabel(hint);
            hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
            hintLabel.setForeground(LIGHT_TEXT);
            hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
            formGroup.add(hintLabel);
            formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
        } else {
            formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
        }
        
        return formGroup;
    }
    /**
     * 根据割刀宽度更新割草宽度和安全距离
     */
    private void updateMowingWidthAndSafetyDistance() {
        try {
            String bladeWidthStr = bladeWidthField.getText().trim();
            if (bladeWidthStr == null || bladeWidthStr.isEmpty()) {
                mowingWidthField.setText("");
                safetyDistanceField.setText("");
                return;
            }
            double bladeWidthMeters = Double.parseDouble(bladeWidthStr);
            if (bladeWidthMeters <= 0) {
                mowingWidthField.setText("");
                safetyDistanceField.setText("");
                return;
            }
            // 调用 Gecaokuanjisuan 类计算割草宽度(返回厘米,保留2位小数,需要转换为米)
            double mowingWidthCm = Gecaokuanjisuan.calculateMowingWidth(bladeWidthMeters);
            if (mowingWidthCm > 0) {
                // 将厘米转换为米,并保留2位小数
                double mowingWidthMeters = mowingWidthCm / 100.0;
                mowingWidthField.setText(String.format(Locale.US, "%.2f", mowingWidthMeters));
            } else {
                mowingWidthField.setText("");
            }
            // 调用 Gecaoanquanjuli 类计算安全距离
            String safetyDistanceStr = Gecaoanquanjuli.calculateSafetyDistanceFromString(bladeWidthStr);
            safetyDistanceField.setText(safetyDistanceStr);
        } catch (NumberFormatException e) {
            mowingWidthField.setText("");
            safetyDistanceField.setText("");
        } catch (Exception e) {
            e.printStackTrace();
            mowingWidthField.setText("");
            safetyDistanceField.setText("");
        }
    }
    private void generateMowingPath() {
        if (!dikuaiData.containsKey("boundaryDrawn")) {
            JOptionPane.showMessageDialog(this, "请先完成边界绘制后再生成路径", "提示", JOptionPane.WARNING_MESSAGE);
@@ -1259,15 +1348,34 @@
            showStep(2);
            return;
        }
        Dikuai dikuai = getOrCreatePendingDikuai();
        // 从步骤2的边界坐标文本域获取边界坐标
        String boundaryCoords = null;
        if (dikuai != null) {
            boundaryCoords = normalizeCoordinateValue(dikuai.getBoundaryCoordinates());
        if (boundaryXYTextArea != null) {
            String boundaryText = boundaryXYTextArea.getText();
            if (boundaryText != null && !boundaryText.trim().isEmpty() && !boundaryText.startsWith("ERROR")) {
                boundaryCoords = boundaryText.trim();
            }
        }
        if (boundaryCoords == null) {
        // 如果文本域中没有,尝试从dikuaiData获取
        if (boundaryCoords == null || boundaryCoords.isEmpty()) {
            boundaryCoords = normalizeCoordinateValue(dikuaiData.get("optimizedBoundaryXY"));
        }
        // 如果还是没有,尝试从Dikuai对象获取
        if (boundaryCoords == null || boundaryCoords.isEmpty()) {
            Dikuai dikuai = getOrCreatePendingDikuai();
            if (dikuai != null) {
                boundaryCoords = normalizeCoordinateValue(dikuai.getBoundaryCoordinates());
            }
        }
        // 如果还是没有,从dikuaiData获取boundaryCoordinates
        if (boundaryCoords == null || boundaryCoords.isEmpty()) {
            boundaryCoords = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates"));
        }
        if (boundaryCoords == null) {
        if (boundaryCoords == null || boundaryCoords.isEmpty()) {
            JOptionPane.showMessageDialog(this, "未找到有效的地块边界坐标,无法生成路径", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("未找到有效的地块边界坐标,无法生成路径。", false);
@@ -1287,89 +1395,93 @@
        String patternDisplay = (String) mowingPatternCombo.getSelectedItem();
        dikuaiData.put("mowingPattern", patternDisplay);
        String widthText = mowingWidthField.getText().trim();
        if (widthText.isEmpty()) {
            JOptionPane.showMessageDialog(this, "割草机割刀宽度不能为空", "提示", JOptionPane.WARNING_MESSAGE);
        // 获取割草宽度(从文本框,单位:米)
        String mowingWidthStr = mowingWidthField.getText().trim();
        if (mowingWidthStr == null || mowingWidthStr.isEmpty()) {
            JOptionPane.showMessageDialog(this, "请先输入割草宽度", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草机割刀宽度不能为空,请重新输入。", false);
            showPathGenerationMessage("请先输入割草宽度。", false);
            setPathAvailability(false);
            return;
        }
        double bladeWidthMeters;
        double widthMeters;
        try {
            bladeWidthMeters = Double.parseDouble(widthText);
            widthMeters = Double.parseDouble(mowingWidthStr);
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(this, "割草机割刀宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE);
            JOptionPane.showMessageDialog(this, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草机割刀宽度输入无效,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        if (bladeWidthMeters <= 0) {
            JOptionPane.showMessageDialog(this, "割草机割刀宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草机割刀宽度必须大于0,请重新设置。", false);
            setPathAvailability(false);
            return;
        }
        // 保存割草机割刀宽度(米)
        dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters));
        // 获取重叠距离
        String overlapText = overlapDistanceField.getText().trim();
        if (overlapText.isEmpty()) {
            JOptionPane.showMessageDialog(this, "相邻行重叠距离不能为空", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("相邻行重叠距离不能为空,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        double overlapMeters;
        try {
            overlapMeters = Double.parseDouble(overlapText);
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(this, "相邻行重叠距离输入无效", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("相邻行重叠距离输入无效,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        if (overlapMeters < 0) {
            JOptionPane.showMessageDialog(this, "相邻行重叠距离不能为负数", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("相邻行重叠距离不能为负数,请重新设置。", false);
            showPathGenerationMessage("割草宽度格式不正确,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        
        // 保存重叠距离到地块数据
        dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters));
        // 计算实际使用的割草宽度(割刀宽度 - 重叠距离)
        double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
        if (calculatedWidthMeters <= 0) {
            JOptionPane.showMessageDialog(this, "计算后的割草宽度必须大于0(割刀宽度必须大于重叠距离)", "提示", JOptionPane.WARNING_MESSAGE);
        if (widthMeters <= 0) {
            JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("计算后的割草宽度必须大于0,请调整割刀宽度或重叠距离。", false);
            showPathGenerationMessage("割草宽度必须大于0,请重新设置。", false);
            setPathAvailability(false);
            return;
        }
        // 保存割草宽度(转换为厘米保存,保持与原有数据格式一致)
        double widthCm = widthMeters * 100.0;
        dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm));
        // 保存割草宽度(计算后的值,转换为厘米存储,保持与原有数据格式兼容)
        double calculatedWidthCm = calculatedWidthMeters * 100.0;
        dikuaiData.put("mowingWidth", String.format(Locale.US, "%.0f", calculatedWidthCm));
        String widthMeters = String.format(Locale.US, "%.2f", calculatedWidthMeters);
        String plannerMode = resolvePlannerMode(patternDisplay);
        // 获取安全距离
        String safetyDistanceStr = safetyDistanceField.getText().trim();
        double safetyDistanceMeters = Double.NaN;
        if (safetyDistanceStr != null && !safetyDistanceStr.isEmpty()) {
            try {
                safetyDistanceMeters = Double.parseDouble(safetyDistanceStr);
            } catch (NumberFormatException e) {
                // 使用NaN,让系统自动计算
            }
        }
        // 格式化割草宽度和安全距离(单位:米,保留3位小数)
        String widthMetersStr = String.format(Locale.US, "%.3f", widthMeters);
        String safetyDistanceMetersStr = Double.isNaN(safetyDistanceMeters) ? null : String.format(Locale.US, "%.3f", safetyDistanceMeters);
        // 如果没有安全距离,使用默认值
        if (safetyDistanceMetersStr == null) {
            double defaultSafetyDistance = widthMeters / 2.0 + 0.2;
            safetyDistanceMetersStr = String.format(Locale.US, "%.3f", defaultSafetyDistance);
        }
        try {
            List<Lunjingguihua.PathSegment> segments = Lunjingguihua.generatePathSegments(
                boundaryCoords,
                obstacleCoords,
                widthMeters,
                plannerMode
            );
            String plannedPath = Lunjingguihua.formatPathSegments(segments);
            // 1. 调用Qufenxingzhuang中的judgeGrassType方法计算地块边界的形状
            Qufenxingzhuang shapeJudger = new Qufenxingzhuang();
            int grassType = shapeJudger.judgeGrassType(boundaryCoords);
            // grassType: 0=无法判断, 1=凸形, 2=异形
            String plannedPath = null;
            // 2. 根据计算后的结果决定调用哪个方法生成割草路径
            if (grassType == 1) {
                // 凸形地块 -> 调用AoxinglujingNoObstacle类中的方法
                List<AoxinglujingNoObstacle.PathSegment> segments =
                    AoxinglujingNoObstacle.planPath(boundaryCoords, widthMetersStr, safetyDistanceMetersStr);
                plannedPath = formatAoxingPathSegments(segments);
            } else if (grassType == 2) {
                // 异形地块 -> 调用YixinglujingNoObstacle中的方法
                List<YixinglujingNoObstacle.PathSegment> segments =
                    YixinglujingNoObstacle.planPath(boundaryCoords, widthMetersStr, safetyDistanceMetersStr);
                plannedPath = formatYixingPathSegments(segments);
            } else {
                // 无法判断地块类型,使用默认方法作为后备
                JOptionPane.showMessageDialog(this, "无法判断地块类型,使用默认路径生成方法",
                    "提示", JOptionPane.WARNING_MESSAGE);
                String plannerMode = resolvePlannerMode(patternDisplay);
                plannedPath = Lunjingguihua.generatePathFromStrings(
                    boundaryCoords,
                    obstacleCoords != null ? obstacleCoords : "",
                    widthMetersStr,
                    safetyDistanceMetersStr,
                    plannerMode
                );
            }
            if (!isMeaningfulValue(plannedPath)) {
                JOptionPane.showMessageDialog(this, "生成割草路径失败: 生成结果为空", "错误", JOptionPane.ERROR_MESSAGE);
                dikuaiData.remove("plannedPath");
@@ -1377,6 +1489,7 @@
                setPathAvailability(false);
                return;
            }
            if (isMeaningfulValue(boundaryCoords)) {
                dikuaiData.put("boundaryCoordinates", boundaryCoords);
            }
@@ -1386,7 +1499,7 @@
            dikuaiData.put("plannedPath", plannedPath);
            setPathAvailability(true);
            showPathGenerationMessage(
                "已根据当前设置生成割草路径,共生成 " + segments.size() + " 段。\n点击“预览”按钮可在主页面查看效果。",
                "已根据当前设置生成割草路径。\n点击预览按钮可在主页面查看效果。",
                true);
        } catch (IllegalArgumentException ex) {
            JOptionPane.showMessageDialog(this, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
@@ -1402,6 +1515,96 @@
        }
    }
    /**
     * 格式化 AoxinglujingNoObstacle.PathSegment 列表为坐标字符串
     */
    private String formatAoxingPathSegments(List<AoxinglujingNoObstacle.PathSegment> segments) {
        if (segments == null || segments.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        AoxinglujingNoObstacle.Point last = null;
        for (AoxinglujingNoObstacle.PathSegment segment : segments) {
            // 只添加割草工作段,跳过过渡段
            if (segment.isMowing) {
                // 如果起点与上一个终点不同,添加起点
                if (last == null || !equalsAoxingPoint(last, segment.start)) {
                    appendAoxingPoint(sb, segment.start);
                }
                // 添加终点
                appendAoxingPoint(sb, segment.end);
                last = segment.end;
            }
        }
        return sb.toString();
    }
    /**
     * 格式化 YixinglujingNoObstacle.PathSegment 列表为坐标字符串
     */
    private String formatYixingPathSegments(List<YixinglujingNoObstacle.PathSegment> segments) {
        if (segments == null || segments.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        YixinglujingNoObstacle.Point last = null;
        for (YixinglujingNoObstacle.PathSegment segment : segments) {
            // 只添加割草工作段,跳过过渡段
            if (segment.isMowing) {
                // 如果起点与上一个终点不同,添加起点
                if (last == null || !equalsYixingPoint(last, segment.start)) {
                    appendYixingPoint(sb, segment.start);
                }
                // 添加终点
                appendYixingPoint(sb, segment.end);
                last = segment.end;
            }
        }
        return sb.toString();
    }
    /**
     * 比较两个 AoxinglujingNoObstacle.Point 是否相同(使用小的容差)
     */
    private boolean equalsAoxingPoint(AoxinglujingNoObstacle.Point p1, AoxinglujingNoObstacle.Point p2) {
        if (p1 == null || p2 == null) {
            return p1 == p2;
        }
        double tolerance = 1e-6;
        return Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.y - p2.y) < tolerance;
    }
    /**
     * 添加 AoxinglujingNoObstacle.Point 到字符串构建器
     */
    private void appendAoxingPoint(StringBuilder sb, AoxinglujingNoObstacle.Point point) {
        if (sb.length() > 0) {
            sb.append(";");
        }
        sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y));
    }
    /**
     * 比较两个 YixinglujingNoObstacle.Point 是否相同(使用小的容差)
     */
    private boolean equalsYixingPoint(YixinglujingNoObstacle.Point p1, YixinglujingNoObstacle.Point p2) {
        if (p1 == null || p2 == null) {
            return p1 == p2;
        }
        double tolerance = 1e-6;
        return Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.y - p2.y) < tolerance;
    }
    /**
     * 添加 YixinglujingNoObstacle.Point 到字符串构建器
     */
    private void appendYixingPoint(StringBuilder sb, YixinglujingNoObstacle.Point point) {
        if (sb.length() > 0) {
            sb.append(";");
        }
        sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y));
    }
    private void previewMowingPath() {
        if (!hasGeneratedPath()) {
            showPathGenerationMessage("请先生成割草路径后再预览。", false);
@@ -1467,6 +1670,11 @@
            JOptionPane.showMessageDialog(this, "无法启动预览,请稍后再试", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        // 在步骤3预览时,不显示边界点圆圈
        if (shouye.getMapRenderer() != null) {
            shouye.getMapRenderer().setBoundaryPointsVisible(false);
        }
        closePreviewAndDispose();
    }
@@ -1482,53 +1690,20 @@
            }
        }
        // 保存割草机割刀宽度
        // 保存割草宽度(从文本框获取,单位:米,转换为厘米保存)
        if (mowingWidthField != null) {
            String bladeWidthText = mowingWidthField.getText().trim();
            if (!bladeWidthText.isEmpty()) {
            String widthStr = mowingWidthField.getText().trim();
            if (widthStr != null && !widthStr.isEmpty()) {
                try {
                    double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                    dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters));
                    double widthMeters = Double.parseDouble(widthStr);
                    double widthCm = widthMeters * 100.0;
                    dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm));
                } catch (NumberFormatException e) {
                    // 如果解析失败,直接保存文本
                    dikuaiData.put("mowingBladeWidth", bladeWidthText);
                    // 保持原值
                    dikuaiData.put("mowingWidth", widthStr);
                }
            }
        }
        // 保存相邻行重叠距离
        if (overlapDistanceField != null) {
            String overlapText = overlapDistanceField.getText().trim();
            if (!overlapText.isEmpty()) {
                try {
                    double overlapMeters = Double.parseDouble(overlapText);
                    dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters));
                } catch (NumberFormatException e) {
                    // 如果解析失败,直接保存文本
                    dikuaiData.put("mowingOverlapDistance", overlapText);
                }
            }
        }
        // 计算并保存割草宽度(割刀宽度 - 重叠距离)
        if (mowingWidthField != null && overlapDistanceField != null) {
            try {
                String bladeWidthText = mowingWidthField.getText().trim();
                String overlapText = overlapDistanceField.getText().trim();
                if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) {
                    double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                    double overlapMeters = Double.parseDouble(overlapText);
                    double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
                    if (calculatedWidthMeters > 0) {
                        // 转换为厘米存储,保持与原有数据格式兼容
                        int widthCm = (int) Math.round(calculatedWidthMeters * 100.0);
                        dikuaiData.put("mowingWidth", Integer.toString(widthCm));
                    }
                }
            } catch (NumberFormatException e) {
                // 如果计算失败,不保存割草宽度
            }
        }
    }
    private void captureSessionSnapshot() {
@@ -1560,14 +1735,23 @@
            @Override
            public void mouseEntered(MouseEvent e) {
                if (button.isEnabled()) {
                    button.setBackground(PRIMARY_DARK);
                    // 如果按钮可用,鼠标悬停时显示深绿色
                    if (button.getBackground().equals(PRIMARY_COLOR)) {
                        button.setBackground(PRIMARY_DARK);
                    }
                }
            }
            @Override
            public void mouseExited(MouseEvent e) {
                if (button.isEnabled()) {
                    button.setBackground(PRIMARY_COLOR);
                    // 如果按钮可用,鼠标离开时恢复绿色
                    if (!button.getBackground().equals(MEDIUM_GRAY)) {
                        button.setBackground(PRIMARY_COLOR);
                    }
                } else {
                    // 如果按钮不可用,保持灰色
                    button.setBackground(MEDIUM_GRAY);
                }
            }
        });
@@ -1628,7 +1812,15 @@
        ));
        prevButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
        boundaryPreviewButton = createPrimaryButton("预览", 16);
        boundaryPreviewButton.setVisible(false);
        boundaryPreviewButton.setEnabled(false);
        boundaryPreviewButton.addActionListener(e -> previewBoundary());
        nextButton = createPrimaryButton("下一步", 16);
        nextButton.setBackground(MEDIUM_GRAY); // 初始灰色背景
        nextButton.setEnabled(false); // 初始不可用
        createButton = createPrimaryButton("保存", 16);
        createButton.setVisible(false);
        createButton.setEnabled(false);
@@ -1642,6 +1834,8 @@
        buttonPanel.add(prevButton);
        buttonPanel.add(Box.createHorizontalGlue());
        buttonPanel.add(boundaryPreviewButton);
        buttonPanel.add(Box.createHorizontalStrut(15));
        buttonPanel.add(nextButton);
        buttonPanel.add(previewButtonSpacer);
        buttonPanel.add(previewButton);
@@ -1655,10 +1849,20 @@
        if (boundaryCountLabel == null) {
            return;
        }
        int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0;
        double area = jisuanmianjie.calculatePolygonArea();
        DecimalFormat areaFormat = new DecimalFormat("0.00");
        boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaFormat.format(area) + "㎡");
        // 使用优化后的边界数据计算点数和面积
        String optimizedXYStr = dikuaiData.get("optimizedBoundaryXY");
        if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
            int count = Bianjieyouhuatoxy.calculatePointCount(optimizedXYStr);
            String areaStr = Bianjieyouhuatoxy.calculateArea(optimizedXYStr);
            boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaStr + "㎡");
        } else {
            // 如果没有优化后的数据,使用原始数据
            int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0;
            double area = jisuanmianjie.calculatePolygonArea();
            DecimalFormat areaFormat = new DecimalFormat("0.00");
            boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaFormat.format(area) + "㎡");
        }
        boundaryCountLabel.setVisible(true);
    }
@@ -1679,6 +1883,11 @@
            dikuai.setLandArea(snapshot.areaSqMeters);
            dikuai.setBaseStationCoordinates(snapshot.baseStationCoordinates);
            dikuai.setUpdateTime(getCurrentTime());
            // 计算并设置原始边界XY坐标
            String originalBoundaryXY = convertOriginalBoundaryToXY(snapshot.originalBoundary, snapshot.baseStationCoordinates);
            if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) {
                dikuai.setBoundaryOriginalXY(originalBoundaryXY);
            }
            Dikuai.putDikuai(landNumber, dikuai);
        }
@@ -1785,11 +1994,196 @@
        }
        return dikuai;
    }
    /**
     * 将原始边界坐标(经纬度格式)转换为XY坐标
     * @param originalBoundary 原始边界坐标字符串,格式:"lat1,lon1,alt1;lat2,lon2,alt2;..."
     * @param baseStationCoordinates 基准站坐标,格式:"lat,N/S,lon,E/W"
     * @return XY坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." 如果转换失败返回null
     */
    private static String convertOriginalBoundaryToXY(String originalBoundary, String baseStationCoordinates) {
        if (originalBoundary == null || originalBoundary.trim().isEmpty() || "-1".equals(originalBoundary.trim())) {
            return null;
        }
        if (baseStationCoordinates == null || baseStationCoordinates.trim().isEmpty()) {
            return null;
        }
        try {
            // 解析基准站坐标
            String[] baseParts = baseStationCoordinates.trim().split(",");
            if (baseParts.length != 4) {
                return null;
            }
            double baseLat = convertToDecimalDegree(baseParts[0], baseParts[1]);
            double baseLon = convertToDecimalDegree(baseParts[2], baseParts[3]);
            // 解析原始边界坐标
            String[] points = originalBoundary.split(";");
            StringBuilder xyStr = new StringBuilder();
            for (int i = 0; i < points.length; i++) {
                String point = points[i].trim();
                if (point.isEmpty()) {
                    continue;
                }
                String[] coords = point.split(",");
                if (coords.length >= 2) {
                    try {
                        double lat = Double.parseDouble(coords[0].trim());
                        double lon = Double.parseDouble(coords[1].trim());
                        // 转换为XY坐标
                        double[] xy = publicway.Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon);
                        if (xy != null && xy.length >= 2) {
                            if (xyStr.length() > 0) {
                                xyStr.append(";");
                            }
                            xyStr.append(String.format(Locale.US, "%.3f,%.3f", xy[0], xy[1]));
                        }
                    } catch (NumberFormatException e) {
                        // 跳过无效的坐标点
                        continue;
                    }
                }
            }
            return xyStr.length() > 0 ? xyStr.toString() : null;
        } catch (Exception e) {
            System.err.println("转换原始边界坐标到XY失败: " + e.getMessage());
            return null;
        }
    }
    /**
     * 预览边界
     */
    private void previewBoundary() {
        if (!dikuaiData.containsKey("boundaryDrawn")) {
            JOptionPane.showMessageDialog(this, "请先完成边界绘制后再预览", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        // 获取或创建地块对象
        String landNumber = getPendingLandNumber();
        Dikuai dikuai = getOrCreatePendingDikuai();
        if (dikuai == null) {
            JOptionPane.showMessageDialog(this, "无法获取地块信息", "错误", JOptionPane.ERROR_MESSAGE);
            return;
        }
        // 确保地块数据是最新的
        String optimizedBoundaryXY = dikuaiData.get("optimizedBoundaryXY");
        if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || optimizedBoundaryXY.startsWith("ERROR")) {
            // 如果没有优化后的边界,尝试从boundaryCoordinates获取
            String boundaryCoords = dikuaiData.get("boundaryCoordinates");
            if (boundaryCoords != null && !boundaryCoords.isEmpty() && !"-1".equals(boundaryCoords)) {
                optimizedBoundaryXY = boundaryCoords;
            } else {
                // 尝试从地块对象获取
                optimizedBoundaryXY = dikuai.getBoundaryCoordinates();
            }
        }
        if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || "-1".equals(optimizedBoundaryXY)) {
            JOptionPane.showMessageDialog(this, "未找到有效的边界坐标,无法预览", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        // 确保原始边界XY坐标已计算
        String originalBoundaryXY = dikuai.getBoundaryOriginalXY();
        if (originalBoundaryXY == null || originalBoundaryXY.isEmpty() || "-1".equals(originalBoundaryXY)) {
            // 计算原始边界XY坐标
            String originalBoundary = dikuaiData.get("boundaryOriginalCoordinates");
            String baseStationCoordinates = dikuaiData.get("baseStationCoordinates");
            if (originalBoundary != null && baseStationCoordinates != null) {
                originalBoundaryXY = convertOriginalBoundaryToXY(originalBoundary, baseStationCoordinates);
                if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) {
                    dikuai.setBoundaryOriginalXY(originalBoundaryXY);
                    Dikuai.putDikuai(landNumber, dikuai);
                }
            }
        }
        // 保存会话快照
        captureSessionSnapshot();
        // 创建final变量供lambda使用
        final String finalOptimizedBoundaryXY = optimizedBoundaryXY;
        final Dikuai finalDikuai = dikuai;
        // 关闭对话框
        setVisible(false);
        dispose();
        // 调用首页显示预览(与边界管理页面逻辑一致)
        SwingUtilities.invokeLater(() -> {
            Shouye.showBoundaryPreview(finalDikuai, finalOptimizedBoundaryXY, () -> {
                // 返回回调:重新打开新增地块对话框,并显示步骤2
                Component parent = Shouye.getInstance();
                if (parent != null) {
                    // 确保会话状态正确,以便返回时显示步骤2
                    if (activeSession != null && activeSession.drawingCompleted) {
                        resumeRequested = true;
                    }
                    showAddDikuaiDialog(parent);
                }
            });
        });
    }
    private void hideBoundaryPointSummary() {
        if (boundaryCountLabel != null) {
            boundaryCountLabel.setVisible(false);
        }
        if (boundaryXYTextArea != null) {
            boundaryXYTextArea.setVisible(false);
            boundaryXYTextArea.setText("");
        }
        if (boundaryXYScrollPane != null) {
            boundaryXYScrollPane.setVisible(false);
        }
    }
    /**
     * 更新边界XY坐标显示
     */
    private void updateBoundaryXYDisplay() {
        if (boundaryXYTextArea == null || boundaryXYScrollPane == null) {
            return;
        }
        // 获取优化后的边界XY坐标
        String optimizedXYStr = dikuaiData.get("optimizedBoundaryXY");
        if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
            boundaryXYTextArea.setText(optimizedXYStr);
            boundaryXYTextArea.setVisible(true);
            boundaryXYScrollPane.setVisible(true);
        } else {
            // 如果没有优化后的数据,尝试从原始坐标优化并显示
            // 如果已经有原始坐标但还没有优化,则进行优化
            if (dikuaiData.containsKey("boundaryDrawn") &&
                (Coordinate.coordinates != null && !Coordinate.coordinates.isEmpty())) {
                // 尝试优化边界
                optimizeBoundaryAndSave();
                // 再次获取优化后的坐标
                optimizedXYStr = dikuaiData.get("optimizedBoundaryXY");
                if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
                    boundaryXYTextArea.setText(optimizedXYStr);
                    boundaryXYTextArea.setVisible(true);
                    boundaryXYScrollPane.setVisible(true);
                } else {
                    boundaryXYTextArea.setText("边界坐标数据未就绪");
                    boundaryXYTextArea.setVisible(true);
                    boundaryXYScrollPane.setVisible(true);
                }
            } else {
                boundaryXYTextArea.setText("边界坐标数据未就绪");
                boundaryXYTextArea.setVisible(true);
                boundaryXYScrollPane.setVisible(true);
            }
        }
    }
    private boolean hasGeneratedPath() {
@@ -1858,8 +2252,14 @@
        String optimizedBoundary;
        try {
            optimizedBoundary = bianjieguihua2.processCoordinateListAuto(baseStationCoordinates);
        } catch (RuntimeException ex) {
            // 构建边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
            String boundaryStr = buildBoundaryStringForOptimization(uniqueCoordinates);
            // 调用 Bianjieyouhuatoxy.optimizeBoundary 方法
            optimizedBoundary = Bianjieyouhuatoxy.optimizeBoundary(baseStationCoordinates, boundaryStr);
            if (optimizedBoundary == null || optimizedBoundary.isEmpty() || optimizedBoundary.startsWith("ERROR")) {
                return BoundarySnapshotResult.failure("生成地块边界失败: 优化后的边界坐标无效", JOptionPane.ERROR_MESSAGE);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            return BoundarySnapshotResult.failure("生成地块边界失败: " + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
        }
@@ -1929,6 +2329,12 @@
    private void showStep(int step) {
        currentStep = step;
        cardLayout.show(stepsPanel, "step" + step);
        if (step == 1) {
            updateObstacleSummary();
            // 步骤1显示时,立即更新按钮状态
            SwingUtilities.invokeLater(() -> updateStep1ButtonState());
        }
        // 更新按钮状态
        updateButtonState(step);
@@ -1948,6 +2354,39 @@
            if (previewButtonSpacer != null) {
                previewButtonSpacer.setVisible(false);
            }
            // 步骤1:根据验证结果更新下一步按钮状态
            if (step == 1) {
                updateStep1ButtonState();
            }
            // 步骤2显示边界预览按钮
            if (step == 2) {
                if (boundaryPreviewButton != null) {
                    boundaryPreviewButton.setVisible(true);
                    // 根据是否完成边界绘制来设置按钮状态和背景颜色
                    boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn");
                    boundaryPreviewButton.setEnabled(boundaryDrawn);
                    if (boundaryDrawn) {
                        boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景
                    } else {
                        boundaryPreviewButton.setBackground(MEDIUM_GRAY); // 灰色背景
                    }
                }
                // 更新下一步按钮状态(根据是否完成边界绘制)
                boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn");
                nextButton.setEnabled(boundaryDrawn);
                if (boundaryDrawn) {
                    nextButton.setBackground(PRIMARY_COLOR); // 绿色背景
                } else {
                    nextButton.setBackground(MEDIUM_GRAY); // 灰色背景
                }
                // 更新开始绘制按钮状态
                updateStartDrawingButtonState();
            } else {
                if (boundaryPreviewButton != null) {
                    boundaryPreviewButton.setVisible(false);
                    boundaryPreviewButton.setEnabled(false);
                }
            }
        } else {
            nextButton.setVisible(false);
            createButton.setVisible(true);
@@ -1957,6 +2396,10 @@
            if (previewButtonSpacer != null) {
                previewButtonSpacer.setVisible(true);
            }
            if (boundaryPreviewButton != null) {
                boundaryPreviewButton.setVisible(false);
                boundaryPreviewButton.setEnabled(false);
            }
            setPathAvailability(hasGeneratedPath());
        }
@@ -1967,6 +2410,85 @@
        }
    }
    
    /**
     * 更新步骤1的下一步按钮状态
     * 根据地块名称是否填写来设置按钮的启用状态和背景颜色
     */
    private void updateStep1ButtonState() {
        if (nextButton == null || currentStep != 1) {
            return;
        }
        String name = areaNameField.getText().trim();
        boolean canProceed = !name.isEmpty();
        nextButton.setEnabled(canProceed);
        if (canProceed) {
            // 可点击时:绿色背景
            nextButton.setBackground(PRIMARY_COLOR);
        } else {
            // 不可点击时:灰色背景
            nextButton.setBackground(MEDIUM_GRAY);
        }
    }
    /**
     * 更新步骤2的开始绘制按钮状态
     * 根据是否选择了绘制方式来设置按钮的启用状态和背景颜色
     */
    private void updateStartDrawingButtonState() {
        if (startEndDrawingBtn == null || currentStep != 2) {
            return;
        }
        boolean hasSelectedMethod = dikuaiData.containsKey("drawingMethod");
        boolean isDrawingActive = isDrawing;
        // 如果正在绘制,按钮状态由toggleDrawing方法控制
        if (isDrawingActive) {
            return;
        }
        // 如果已经完成绘制,按钮显示"已完成"且不可用
        boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn");
        if (boundaryDrawn) {
            startEndDrawingBtn.setEnabled(false);
            startEndDrawingBtn.setBackground(MEDIUM_GRAY);
            return;
        }
        startEndDrawingBtn.setEnabled(hasSelectedMethod);
        if (hasSelectedMethod) {
            // 已选择绘制方式:绿色背景,可点击
            startEndDrawingBtn.setBackground(PRIMARY_COLOR);
        } else {
            // 未选择绘制方式:灰色背景,不可点击
            startEndDrawingBtn.setBackground(MEDIUM_GRAY);
        }
    }
    /**
     * 更新步骤2的预览和下一步按钮状态(在完成边界绘制后调用)
     * 将按钮背景颜色设置为绿色,表示可以点击操作
     */
    private void updateStep2ButtonsAfterDrawing() {
        if (currentStep != 2) {
            return;
        }
        // 更新预览按钮
        if (boundaryPreviewButton != null) {
            boundaryPreviewButton.setEnabled(true);
            boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景
        }
        // 更新下一步按钮
        if (nextButton != null) {
            nextButton.setEnabled(true);
            nextButton.setBackground(PRIMARY_COLOR); // 绿色背景
        }
    }
    private boolean validateCurrentStep() {
        switch (currentStep) {
            case 1:
@@ -1999,53 +2521,20 @@
            case 3:
                dikuaiData.put("mowingPattern", (String) mowingPatternCombo.getSelectedItem());
                // 保存割草机割刀宽度
                // 保存割草宽度(从文本框获取,单位:米,转换为厘米保存)
                if (mowingWidthField != null) {
                    String bladeWidthText = mowingWidthField.getText().trim();
                    if (!bladeWidthText.isEmpty()) {
                    String widthStr = mowingWidthField.getText().trim();
                    if (widthStr != null && !widthStr.isEmpty()) {
                        try {
                            double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                            dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters));
                            double widthMeters = Double.parseDouble(widthStr);
                            double widthCm = widthMeters * 100.0;
                            dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm));
                        } catch (NumberFormatException e) {
                            dikuaiData.put("mowingBladeWidth", bladeWidthText);
                            // 保持原值
                            dikuaiData.put("mowingWidth", widthStr);
                        }
                    }
                }
                // 保存相邻行重叠距离
                if (overlapDistanceField != null) {
                    String overlapText = overlapDistanceField.getText().trim();
                    if (!overlapText.isEmpty()) {
                        try {
                            double overlapMeters = Double.parseDouble(overlapText);
                            dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters));
                        } catch (NumberFormatException e) {
                            dikuaiData.put("mowingOverlapDistance", overlapText);
                        }
                    }
                }
                // 计算并保存割草宽度(割刀宽度 - 重叠距离)
                if (mowingWidthField != null && overlapDistanceField != null) {
                    try {
                        String bladeWidthText = mowingWidthField.getText().trim();
                        String overlapText = overlapDistanceField.getText().trim();
                        if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) {
                            double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                            double overlapMeters = Double.parseDouble(overlapText);
                            double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
                            if (calculatedWidthMeters > 0) {
                                // 转换为厘米存储,保持与原有数据格式兼容
                                int widthCm = (int) Math.round(calculatedWidthMeters * 100.0);
                                dikuaiData.put("mowingWidth", Integer.toString(widthCm));
                            }
                        }
                    } catch (NumberFormatException e) {
                        // 如果计算失败,不保存割草宽度
                    }
                }
                if (!hasGeneratedPath()) {
                    JOptionPane.showMessageDialog(this, "请先生成割草路径", "提示", JOptionPane.WARNING_MESSAGE);
                    return false;
@@ -2290,6 +2779,9 @@
            isDrawing = false;
            showStep(2);
            showBoundaryPointSummary();
            updateBoundaryXYDisplay();
            // 更新预览和下一步按钮状态(背景颜色变绿色,可点击)
            updateStep2ButtonsAfterDrawing();
        } else {
            if (startEndDrawingBtn != null) {
                startEndDrawingBtn.setText("开始绘制");
@@ -2326,54 +2818,36 @@
            }
        }
        // 恢复割草机割刀宽度(优先从mowingBladeWidth获取)
        if (mowingWidthField != null) {
            String bladeWidth = data.get("mowingBladeWidth");
            if (isMeaningfulValue(bladeWidth)) {
                try {
                    double bladeWidthMeters = Double.parseDouble(bladeWidth.trim());
                    mowingWidthField.setText(String.format(Locale.US, "%.2f", bladeWidthMeters));
                } catch (NumberFormatException ignored) {
                    // 如果mowingBladeWidth不存在或解析失败,尝试从mowingWidth恢复
                    String width = data.get("mowingWidth");
                    if (isMeaningfulValue(width)) {
                        try {
                            // 如果存储的是厘米,转换为米显示
                            double parsed = Double.parseDouble(width.trim());
                            // 假设如果值大于10,则是厘米,需要转换为米;否则已经是米
                            double widthMeters = parsed > 10 ? parsed / 100.0 : parsed;
                            mowingWidthField.setText(String.format(Locale.US, "%.2f", widthMeters));
                        } catch (NumberFormatException ignored2) {
                            // 保持当前值
                        }
                    }
                }
            } else {
                // 如果mowingBladeWidth不存在,尝试从mowingWidth恢复
                String width = data.get("mowingWidth");
                if (isMeaningfulValue(width)) {
                    try {
                        // 如果存储的是厘米,转换为米显示
                        double parsed = Double.parseDouble(width.trim());
                        // 假设如果值大于10,则是厘米,需要转换为米;否则已经是米
                        double widthMeters = parsed > 10 ? parsed / 100.0 : parsed;
                        mowingWidthField.setText(String.format(Locale.US, "%.2f", widthMeters));
                    } catch (NumberFormatException ignored) {
                        // 保持当前值
                    }
                }
        // 恢复割草宽度:从保存的割草宽度(厘米)转换为米并设置
        String width = data.get("mowingWidth");
        if (isMeaningfulValue(width) && mowingWidthField != null) {
            try {
                double mowingWidthCm = Double.parseDouble(width.trim());
                // 将厘米转换为米
                double mowingWidthMeters = mowingWidthCm / 100.0;
                mowingWidthField.setText(String.format(Locale.US, "%.3f", mowingWidthMeters));
            } catch (NumberFormatException ignored) {
                // 保持当前值
            }
        }
        
        if (overlapDistanceField != null) {
            String overlap = data.get("mowingOverlapDistance");
            if (isMeaningfulValue(overlap)) {
                try {
                    double overlapMeters = Double.parseDouble(overlap.trim());
                    overlapDistanceField.setText(String.format(Locale.US, "%.2f", overlapMeters));
                } catch (NumberFormatException ignored) {
                    // 保持当前值
        // 恢复割刀宽度:从保存的割草宽度反推割刀宽度并设置
        // 由于割草宽度 = 割刀宽度 * 0.85,所以割刀宽度 = 割草宽度 / 0.85
        if (isMeaningfulValue(width) && bladeWidthField != null) {
            try {
                double mowingWidthCm = Double.parseDouble(width.trim());
                // 将厘米转换为米,然后反推割刀宽度
                double mowingWidthMeters = mowingWidthCm / 100.0;
                double bladeWidthMeters = mowingWidthMeters / 0.85;
                bladeWidthField.setText(String.format(Locale.US, "%.3f", bladeWidthMeters));
                // 触发自动计算安全距离
                if (safetyDistanceField != null) {
                    String safetyDistanceStr = Gecaoanquanjuli.calculateSafetyDistanceFromString(
                        String.format(Locale.US, "%.3f", bladeWidthMeters));
                    safetyDistanceField.setText(safetyDistanceStr);
                }
            } catch (NumberFormatException ignored) {
                // 保持当前值
            }
        }
@@ -2480,80 +2954,73 @@
            dikuai.setMowingPattern(dikuaiData.get("mowingPattern"));
        }
        
        // 保存割草机割刀宽度(优先从dikuaiData获取,否则从TextField获取)
        if (dikuaiData.containsKey("mowingBladeWidth")) {
            dikuai.setMowingBladeWidth(dikuaiData.get("mowingBladeWidth"));
        } else if (mowingWidthField != null) {
            String bladeWidthText = mowingWidthField.getText().trim();
            if (!bladeWidthText.isEmpty()) {
        // 保存割草宽度(从文本框获取,单位:米,转换为厘米保存)
        if (mowingWidthField != null) {
            String mowingWidthStr = mowingWidthField.getText().trim();
            if (mowingWidthStr != null && !mowingWidthStr.isEmpty()) {
                try {
                    double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                    double mowingWidthMeters = Double.parseDouble(mowingWidthStr);
                    // 转换为厘米保存
                    double mowingWidthCm = mowingWidthMeters * 100.0;
                    dikuai.setMowingWidth(String.format(Locale.US, "%.2f", mowingWidthCm));
                } catch (NumberFormatException e) {
                    // 如果解析失败,尝试使用dikuaiData中的值
                    if (dikuaiData.containsKey("mowingWidth")) {
                        dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
                    }
                }
            } else if (dikuaiData.containsKey("mowingWidth")) {
                dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
            }
        } else if (dikuaiData.containsKey("mowingWidth")) {
            dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
        }
        // 保存割草机割刀宽度(从文本框获取,单位:米)
        if (bladeWidthField != null) {
            String bladeWidthStr = bladeWidthField.getText().trim();
            if (bladeWidthStr != null && !bladeWidthStr.isEmpty()) {
                try {
                    double bladeWidthMeters = Double.parseDouble(bladeWidthStr);
                    // 保存为米,保留2位小数
                    dikuai.setMowingBladeWidth(String.format(Locale.US, "%.2f", bladeWidthMeters));
                } catch (NumberFormatException e) {
                    dikuai.setMowingBladeWidth(bladeWidthText);
                    // 解析失败时,保存原始字符串
                    dikuai.setMowingBladeWidth(bladeWidthStr);
                }
            }
        }
        
        // 保存相邻行重叠距离(优先从dikuaiData获取,否则从TextField获取)
        if (dikuaiData.containsKey("mowingOverlapDistance")) {
            dikuai.setMowingOverlapDistance(dikuaiData.get("mowingOverlapDistance"));
        } else if (overlapDistanceField != null) {
            String overlapText = overlapDistanceField.getText().trim();
            if (!overlapText.isEmpty()) {
        // 保存割草安全距离(从文本框获取,单位:米)
        if (safetyDistanceField != null) {
            String safetyDistanceStr = safetyDistanceField.getText().trim();
            if (safetyDistanceStr != null && !safetyDistanceStr.isEmpty()) {
                try {
                    double overlapMeters = Double.parseDouble(overlapText);
                    dikuai.setMowingOverlapDistance(String.format(Locale.US, "%.2f", overlapMeters));
                    double safetyDistanceMeters = Double.parseDouble(safetyDistanceStr);
                    // 保存为米,保留2位小数
                    String formattedValue = String.format(Locale.US, "%.2f", safetyDistanceMeters);
                    dikuai.setMowingSafetyDistance(formattedValue);
                    // 同时保存到dikuaiData中,以便后续使用
                    dikuaiData.put("mowingSafetyDistance", formattedValue);
                } catch (NumberFormatException e) {
                    dikuai.setMowingOverlapDistance(overlapText);
                    // 解析失败时,保存原始字符串
                    dikuai.setMowingSafetyDistance(safetyDistanceStr);
                    dikuaiData.put("mowingSafetyDistance", safetyDistanceStr);
                }
            } else if (dikuaiData.containsKey("mowingSafetyDistance")) {
                // 如果文本框为空,尝试从dikuaiData获取
                dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance"));
            }
        }
        // 保存割草宽度(计算后的值,优先从dikuaiData获取)
        if (dikuaiData.containsKey("mowingWidth")) {
            dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
        } else {
            // 如果没有在dikuaiData中,则从TextField计算
            if (mowingWidthField != null && overlapDistanceField != null) {
                try {
                    String bladeWidthText = mowingWidthField.getText().trim();
                    String overlapText = overlapDistanceField.getText().trim();
                    if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) {
                        double bladeWidthMeters = Double.parseDouble(bladeWidthText);
                        double overlapMeters = Double.parseDouble(overlapText);
                        double calculatedWidthMeters = bladeWidthMeters - overlapMeters;
                        if (calculatedWidthMeters > 0) {
                            // 转换为厘米存储,保持与原有数据格式兼容
                            int widthCm = (int) Math.round(calculatedWidthMeters * 100.0);
                            dikuai.setMowingWidth(Integer.toString(widthCm));
                        }
                    }
                } catch (NumberFormatException e) {
                    // 如果计算失败,保持原有值或使用默认值
                }
            }
        } else if (dikuaiData.containsKey("mowingSafetyDistance")) {
            // 如果safetyDistanceField为null,从dikuaiData获取
            dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance"));
        }
        // 保存割草路径坐标
        String plannedPath = dikuaiData.get("plannedPath");
        if (isMeaningfulValue(plannedPath)) {
            dikuai.setPlannedPath(plannedPath);
        }
        // 保存割草安全距离(优先从dikuaiData获取,否则从TextField获取)
        if (dikuaiData.containsKey("mowingSafetyDistance")) {
            dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance"));
        } else if (mowingSafetyDistanceField != null) {
            String safetyDistanceText = mowingSafetyDistanceField.getText().trim();
            if (!safetyDistanceText.isEmpty()) {
                try {
                    double safetyDistanceMeters = Double.parseDouble(safetyDistanceText);
                    dikuai.setMowingSafetyDistance(String.format(Locale.US, "%.2f", safetyDistanceMeters));
                } catch (NumberFormatException e) {
                    dikuai.setMowingSafetyDistance(safetyDistanceText);
                }
            }
        }
        Dikuai.putDikuai(landNumber, dikuai);
        Dikuai.saveToProperties();
@@ -2604,6 +3071,13 @@
        if (resumeRequested && activeSession != null) {
            dialog.applySessionData(activeSession);
            resumeRequested = false;
            // 如果会话已生成路径,优先显示步骤3
            if (activeSession.data != null && isMeaningfulValue(activeSession.data.get("plannedPath"))) {
                dialog.showStep(3);
            } else if (activeSession.drawingCompleted) {
                // 如果会话已完成绘制,显示步骤2
                dialog.showStep(2);
            }
        }
        
        dialog.setVisible(true);