826220679@qq.com
2 天以前 48ee74129bb09a817a0bbbabe860c4007b74c66b
src/lujing/MowingPathGenerationPage.java
@@ -3,6 +3,8 @@
import javax.swing.*;
import javax.swing.SwingUtilities;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
@@ -11,7 +13,15 @@
import dikuai.Dikuai;
import lujing.Lunjingguihua;
import lujing.ObstaclePathPlanner;
import lujing.Qufenxingzhuang;
import lujing.AoxinglujingNoObstacle;
import lujing.YixinglujingNoObstacle;
import publicway.Fuzhibutton;
import lujing.AoxinglujingHaveObstacel;
import lujing.YixinglujingHaveObstacel;
import org.locationtech.jts.geom.Coordinate;
import gecaoji.Device;
import java.util.Locale;
/**
 * 生成割草路径页面
@@ -125,6 +135,26 @@
        widthField = createInfoTextField(widthValue != null ? widthValue : "", true);
        contentPanel.add(createTextFieldSection("割草宽度 (厘米)", widthField));
        
        // 割草安全距离(只读显示)
        String displaySafetyDistance = "未设置";
        Device device = Device.getActiveDevice();
        if (device != null) {
            String safetyDistanceValue = device.getMowingSafetyDistance();
            if (safetyDistanceValue != null && !"-1".equals(safetyDistanceValue) && !safetyDistanceValue.trim().isEmpty()) {
                try {
                    double distanceMeters = Double.parseDouble(safetyDistanceValue.trim());
                    // 如果值大于100,认为是厘米,需要转换为米
                    if (distanceMeters > 100) {
                        distanceMeters = distanceMeters / 100.0;
                    }
                    displaySafetyDistance = String.format("%.2f米", distanceMeters);
                } catch (NumberFormatException e) {
                    displaySafetyDistance = "未设置";
                }
            }
        }
        contentPanel.add(createInfoValueSection("割草安全距离", displaySafetyDistance));
        // 割草模式(只读显示)
        contentPanel.add(createInfoValueSection("割草模式", formatMowingPatternForDialog(modeValue)));
        
@@ -469,8 +499,24 @@
            obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' ');
        }
        // 获取安全距离
        String safetyMarginStr = getSafetyDistanceString();
        if (safetyMarginStr == null) {
            // 如果没有设置安全距离,使用默认值:割草宽度的一半 + 0.2米
            double defaultSafetyDistance = widthMeters / 2.0 + 0.2;
            safetyMarginStr = BigDecimal.valueOf(defaultSafetyDistance)
                .setScale(3, RoundingMode.HALF_UP)
                .stripTrailingZeros()
                .toPlainString();
        }
        String mode = normalizeExistingMowingPattern(modeInput);
        try {
            // 1. 首先判断地块类型(凸形还是异形)
            Qufenxingzhuang shapeJudger = new Qufenxingzhuang();
            int grassType = shapeJudger.judgeGrassType(boundary);
            // grassType: 0=无法判断, 1=凸形, 2=异形
            // 解析障碍物列表
            List<List<Coordinate>> obstacleList = Lunjingguihua.parseObstacles(obstacles);
            if (obstacleList == null) {
@@ -480,35 +526,110 @@
            // 判断是否有有效的障碍物:只有当解析成功且列表不为空时,才认为有障碍物
            boolean hasValidObstacles = !obstacleList.isEmpty();
            
            String generated;
            String generated = null;
            
            // 2. 根据地块类型和是否有障碍物,调用不同的路径生成类
            if (!hasValidObstacles) {
                // 障碍物坐标不存在或为空时,使用Lunjingguihua类的方法生成路径
                generated = Lunjingguihua.generatePathFromStrings(
                    boundary,
                    obstacles != null ? obstacles : "",
                    plannerWidth,
                    null,  // safetyDistStr,使用默认值
                    mode
                );
            } else {
                // 有有效障碍物时,使用ObstaclePathPlanner处理路径生成
                List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary);
                if (polygon.size() < 4) {
                    if (showMessages) {
                        JOptionPane.showMessageDialog(parentComponent, "多边形坐标数量不足,至少需要三个点",
                            "错误", JOptionPane.ERROR_MESSAGE);
                // 无障碍物的情况
                if (grassType == 1) {
                    // 凸形地块,无障碍物 -> 调用 AoxinglujingNoObstacle
                    List<AoxinglujingNoObstacle.PathSegment> segments =
                        AoxinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr);
                    generated = formatAoxingPathSegments(segments);
                } else if (grassType == 2) {
                    // 异形地块,无障碍物 -> 调用 YixinglujingNoObstacle
                    // 注意:如果该类还没有实现,这里会抛出异常或返回null
                    try {
                        // 假设 YixinglujingNoObstacle 有类似的方法签名
                        // 如果类还没有实现,可能需要使用原来的方法作为后备
                        generated = YixinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr);
                    } catch (Exception e) {
                        // 如果类还没有实现,使用原来的方法作为后备
                        if (showMessages) {
                            System.err.println("YixinglujingNoObstacle 尚未实现,使用默认方法: " + e.getMessage());
                        }
                        generated = Lunjingguihua.generatePathFromStrings(
                            boundary, obstacles != null ? obstacles : "", plannerWidth, safetyMarginStr, mode);
                    }
                    return null;
                } else {
                    // 无法判断地块类型,使用原来的方法作为后备
                    if (showMessages) {
                        JOptionPane.showMessageDialog(parentComponent, "无法判断地块类型,使用默认路径生成方法",
                            "提示", JOptionPane.WARNING_MESSAGE);
                    }
                    generated = Lunjingguihua.generatePathFromStrings(
                        boundary, obstacles != null ? obstacles : "", plannerWidth, safetyMarginStr, mode);
                }
                // 有障碍物时使用割草宽度的一半 + 0.05米额外安全距离
                double safetyDistance = widthMeters / 2.0 + 0.05;
                ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner(
                    polygon, widthMeters, mode, obstacleList, safetyDistance);
                List<Lunjingguihua.PathSegment> segments = pathPlanner.generate();
                generated = Lunjingguihua.formatPathSegments(segments);
            } else {
                // 有障碍物的情况
                if (grassType == 1) {
                    // 凸形地块,有障碍物 -> 调用 AoxinglujingHaveObstacel
                    try {
                        // 假设 AoxinglujingHaveObstacel 有类似的方法签名
                        generated = AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr);
                    } catch (Exception e) {
                        // 如果类还没有实现,使用原来的方法作为后备
                        if (showMessages) {
                            System.err.println("AoxinglujingHaveObstacel 尚未实现,使用默认方法: " + e.getMessage());
                        }
                        List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary);
                        if (polygon.size() < 4) {
                            if (showMessages) {
                                JOptionPane.showMessageDialog(parentComponent, "多边形坐标数量不足,至少需要三个点",
                                    "错误", JOptionPane.ERROR_MESSAGE);
                            }
                            return null;
                        }
                        double safetyDistance = Double.parseDouble(safetyMarginStr);
                        ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner(
                            polygon, widthMeters, mode, obstacleList, safetyDistance);
                        List<Lunjingguihua.PathSegment> segments = pathPlanner.generate();
                        generated = Lunjingguihua.formatPathSegments(segments);
                    }
                } else if (grassType == 2) {
                    // 异形地块,有障碍物 -> 调用 YixinglujingHaveObstacel
                    try {
                        // 假设 YixinglujingHaveObstacel 有类似的方法签名
                        generated = YixinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr);
                    } catch (Exception e) {
                        // 如果类还没有实现,使用原来的方法作为后备
                        if (showMessages) {
                            System.err.println("YixinglujingHaveObstacel 尚未实现,使用默认方法: " + e.getMessage());
                        }
                        List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary);
                        if (polygon.size() < 4) {
                            if (showMessages) {
                                JOptionPane.showMessageDialog(parentComponent, "多边形坐标数量不足,至少需要三个点",
                                    "错误", JOptionPane.ERROR_MESSAGE);
                            }
                            return null;
                        }
                        double safetyDistance = Double.parseDouble(safetyMarginStr);
                        ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner(
                            polygon, widthMeters, mode, obstacleList, safetyDistance);
                        List<Lunjingguihua.PathSegment> segments = pathPlanner.generate();
                        generated = Lunjingguihua.formatPathSegments(segments);
                    }
                } else {
                    // 无法判断地块类型,使用原来的方法作为后备
                    if (showMessages) {
                        JOptionPane.showMessageDialog(parentComponent, "无法判断地块类型,使用默认路径生成方法",
                            "提示", JOptionPane.WARNING_MESSAGE);
                    }
                    List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary);
                    if (polygon.size() < 4) {
                        if (showMessages) {
                            JOptionPane.showMessageDialog(parentComponent, "多边形坐标数量不足,至少需要三个点",
                                "错误", JOptionPane.ERROR_MESSAGE);
                        }
                        return null;
                    }
                    double safetyDistance = Double.parseDouble(safetyMarginStr);
                    ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner(
                        polygon, widthMeters, mode, obstacleList, safetyDistance);
                    List<Lunjingguihua.PathSegment> segments = pathPlanner.generate();
                    generated = Lunjingguihua.formatPathSegments(segments);
                }
            }
            
            String trimmed = generated != null ? generated.trim() : "";
@@ -539,6 +660,77 @@
        return null;
    }
    
    /**
     * 获取安全距离字符串(米)
     */
    private String getSafetyDistanceString() {
        Device device = Device.getActiveDevice();
        if (device != null) {
            String safetyDistanceValue = device.getMowingSafetyDistance();
            if (safetyDistanceValue != null && !"-1".equals(safetyDistanceValue) && !safetyDistanceValue.trim().isEmpty()) {
                try {
                    double distanceMeters = Double.parseDouble(safetyDistanceValue.trim());
                    // 如果值大于100,认为是厘米,需要转换为米
                    if (distanceMeters > 100) {
                        distanceMeters = distanceMeters / 100.0;
                    }
                    return BigDecimal.valueOf(distanceMeters)
                        .setScale(3, RoundingMode.HALF_UP)
                        .stripTrailingZeros()
                        .toPlainString();
                } catch (NumberFormatException e) {
                    // 解析失败,返回null,使用默认值
                }
            }
        }
        return null;
    }
    /**
     * 格式化 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 || !equals2D(last, segment.start)) {
                    appendPoint(sb, segment.start);
                }
                // 添加终点
                appendPoint(sb, segment.end);
                last = segment.end;
            }
        }
        return sb.toString();
    }
    /**
     * 比较两个点是否相同(使用小的容差)
     */
    private boolean equals2D(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;
    }
    /**
     * 添加点到字符串构建器
     */
    private void appendPoint(StringBuilder sb, AoxinglujingNoObstacle.Point point) {
        if (sb.length() > 0) {
            sb.append(";");
        }
        sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y));
    }
    // ========== UI辅助方法 ==========
    
    private JTextArea createInfoTextArea(String text, boolean editable, int rows) {
@@ -559,10 +751,31 @@
        section.setBackground(BACKGROUND_COLOR);
        section.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        // 创建标题面板,包含标题和复制图标
        JPanel titlePanel = new JPanel(new BorderLayout());
        titlePanel.setBackground(BACKGROUND_COLOR);
        titlePanel.setOpaque(false);
        JLabel titleLabel = new JLabel(title);
        titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
        titleLabel.setForeground(TEXT_COLOR);
        section.add(titleLabel, BorderLayout.NORTH);
        titlePanel.add(titleLabel, BorderLayout.WEST);
        // 创建复制按钮(使用 Fuzhibutton)
        JButton copyButton = Fuzhibutton.createCopyButton(
            () -> {
                String text = textArea.getText();
                if (text == null || text.trim().isEmpty() || "-1".equals(text.trim())) {
                    return null; // 返回null会触发"未设置"提示
                }
                return text;
            },
            "复制" + title,
            new Color(230, 250, 240)
        );
        titlePanel.add(copyButton, BorderLayout.EAST);
        section.add(titlePanel, BorderLayout.NORTH);
        
        JScrollPane scrollPane = new JScrollPane(textArea);
        scrollPane.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
@@ -591,10 +804,31 @@
        section.setBackground(BACKGROUND_COLOR);
        section.setAlignmentX(Component.LEFT_ALIGNMENT);
        
        // 创建标题面板,包含标题和复制图标
        JPanel titlePanel = new JPanel(new BorderLayout());
        titlePanel.setBackground(BACKGROUND_COLOR);
        titlePanel.setOpaque(false);
        JLabel titleLabel = new JLabel(title);
        titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
        titleLabel.setForeground(TEXT_COLOR);
        section.add(titleLabel, BorderLayout.NORTH);
        titlePanel.add(titleLabel, BorderLayout.WEST);
        // 创建复制按钮(使用 Fuzhibutton)
        JButton copyButton = Fuzhibutton.createCopyButton(
            () -> {
                String text = textField.getText();
                if (text == null || text.trim().isEmpty() || "-1".equals(text.trim())) {
                    return null; // 返回null会触发"未设置"提示
                }
                return text;
            },
            "复制" + title,
            new Color(230, 250, 240)
        );
        titlePanel.add(copyButton, BorderLayout.EAST);
        section.add(titlePanel, BorderLayout.NORTH);
        
        JPanel fieldWrapper = new JPanel(new BorderLayout());
        fieldWrapper.setBackground(textField.isEditable() ? WHITE : new Color(245, 245, 245));
@@ -732,6 +966,7 @@
        }
        return "parallel";
    }
}