张世豪
2 天以前 d0ae1a5baf919cf470d2ab2102587948623cc725
src/zhangaiwu/AddDikuai.java
@@ -24,6 +24,9 @@
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;
@@ -1345,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);
@@ -1417,19 +1439,48 @@
            }
        }
        
        // 格式化割草宽度和安全距离(单位:米,保留3位小数)
        String widthMetersStr = String.format(Locale.US, "%.3f", widthMeters);
        String safetyDistanceMetersStr = Double.isNaN(safetyDistanceMeters) ? null : String.format(Locale.US, "%.3f", safetyDistanceMeters);
        String plannerMode = resolvePlannerMode(patternDisplay);
        // 如果没有安全距离,使用默认值
        if (safetyDistanceMetersStr == null) {
            double defaultSafetyDistance = widthMeters / 2.0 + 0.2;
            safetyDistanceMetersStr = String.format(Locale.US, "%.3f", defaultSafetyDistance);
        }
        try {
            // 使用与路径规划页面相同的方法:Lunjingguihua.generatePathFromStrings
            String plannedPath = Lunjingguihua.generatePathFromStrings(
                boundaryCoords,
                obstacleCoords != null ? obstacleCoords : "",
                widthMetersStr,
                safetyDistanceMetersStr,
                plannerMode
            );
            // 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);
@@ -1464,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);
@@ -2543,6 +2684,25 @@
    private String generateNewLandNumber() {
        Map<String, Dikuai> existing = Dikuai.getAllDikuai();
        // 获取割草机编号
        String mowerId = Setsys.getPropertyValue("mowerId");
        // 如果有割草机编号,使用 编号+两位自增数字 格式
        if (mowerId != null && !mowerId.trim().isEmpty() && !"-1".equals(mowerId)) {
            int attempt = 1;
            while (true) {
                // 格式化为两位数字,如 01, 02, ...
                String suffix = String.format("%02d", attempt);
                String candidate = mowerId + suffix;
                if (!existing.containsKey(candidate)) {
                    return candidate;
                }
                attempt++;
            }
        }
        // 如果没有割草机编号,回退到默认逻辑
        int attempt = 1;
        while (true) {
            String candidate = "LAND" + attempt;