| | |
| | | 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; |
| | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | |
| | | // 格式化割草宽度和安全距离(单位:米,保留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); |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 格式化 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); |