| | |
| | | package lujing; |
| | | |
| | | 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; |
| | | import java.util.List; |
| | | |
| | | 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; |
| | | |
| | |
| | | boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue); |
| | | boolean saveMowingWidth(Dikuai dikuai, String value); |
| | | boolean savePlannedPath(Dikuai dikuai, String value); |
| | | boolean saveMowingSafetyDistance(Dikuai dikuai, String value); |
| | | } |
| | | |
| | | private final Dikuai dikuai; |
| | |
| | | * 预览路径 |
| | | */ |
| | | private void previewPath() { |
| | | // 先保存当前路径到地块(临时保存,用于预览) |
| | | String pathNormalized = normalizeCoordinateInput(pathArea.getText()); |
| | | // 直接从文本域获取路径数据 |
| | | String rawPath = pathArea.getText(); |
| | | String pathNormalized = normalizeCoordinateInput(rawPath); |
| | | |
| | | if (!"-1".equals(pathNormalized)) { |
| | | // 规范化路径数据:支持换行、空格等分隔符 |
| | | pathNormalized = pathNormalized |
| | | .replace("\r\n", ";") |
| | | .replace('\r', ';') |
| | | .replace('\n', ';') |
| | | .replaceAll(";+", ";") |
| | | .replaceAll("\\s*;\\s*", ";") |
| | | .trim(); |
| | | .replaceAll("\\s+", ";") // 将所有空白字符替换为分号 |
| | | .replaceAll(";+", ";"); // 合并连续分号 |
| | | |
| | | // 去除首尾分号 |
| | | if (pathNormalized.startsWith(";")) pathNormalized = pathNormalized.substring(1); |
| | | if (pathNormalized.endsWith(";")) pathNormalized = pathNormalized.substring(0, pathNormalized.length() - 1); |
| | | |
| | | if (pathNormalized.isEmpty()) { |
| | | pathNormalized = "-1"; |
| | | } |
| | | } |
| | | |
| | | if ("-1".equals(pathNormalized)) { |
| | | JOptionPane.showMessageDialog(this, "请先生成割草路径", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | JOptionPane.showMessageDialog(this, "请先生成割草路径或在文本框中输入有效坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 临时保存路径到地块对象(不持久化) |
| | | if (saveCallback != null) { |
| | | saveCallback.savePlannedPath(dikuai, pathNormalized); |
| | | } |
| | | // 注意:预览时不自动保存路径到地块,仅使用文本域中的数据进行预览 |
| | | // 只有点击"保存路径"按钮时才持久化数据 |
| | | |
| | | // 保存当前页面状态,用于返回时恢复 |
| | | String currentBaseStation = baseStationField.getText(); |
| | |
| | | String boundaryInput = normalizeCoordinateInput(boundaryArea.getText()); |
| | | final String boundary; |
| | | if (!"-1".equals(boundaryInput)) { |
| | | String processed = boundaryInput.replace("\r\n", ";") |
| | | String processed = boundaryInput |
| | | .replace("\r\n", ";") |
| | | .replace('\r', ';') |
| | | .replace('\n', ';') |
| | | .replaceAll(";+", ";") |
| | | .replaceAll("\\s*;\\s*", ";") |
| | | .trim(); |
| | | .replaceAll("\\s+", ";") |
| | | .replaceAll(";+", ";"); |
| | | |
| | | if (processed.startsWith(";")) processed = processed.substring(1); |
| | | if (processed.endsWith(";")) processed = processed.substring(0, processed.length() - 1); |
| | | |
| | | if (processed.isEmpty()) { |
| | | boundary = dikuai.getBoundaryCoordinates(); |
| | | } else { |
| | |
| | | JOptionPane.showMessageDialog(this, "请先生成割草路径", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 更新当前地块对象的属性 |
| | | if (dikuai != null) { |
| | | dikuai.setBaseStationCoordinates(baseStationNormalized); |
| | | dikuai.setBoundaryCoordinates(boundaryNormalized); |
| | | dikuai.setMowingWidth(widthNormalized); |
| | | dikuai.setPlannedPath(pathNormalized); |
| | | dikuai.setObstacleCoordinates(obstacleNormalized); |
| | | |
| | | // 获取并更新安全距离 |
| | | String safetyDistance = getSafetyDistanceString(); |
| | | if (safetyDistance != null) { |
| | | dikuai.setMowingSafetyDistance(safetyDistance); |
| | | } |
| | | } |
| | | |
| | | // 调用回调保存数据 |
| | | if (saveCallback != null) { |
| | |
| | | JOptionPane.showMessageDialog(this, "无法保存割草路径", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 保存安全距离 |
| | | String safetyDistance = getSafetyDistanceString(); |
| | | if (safetyDistance != null) { |
| | | if (!saveCallback.saveMowingSafetyDistance(dikuai, safetyDistance)) { |
| | | JOptionPane.showMessageDialog(this, "无法保存割草安全距离", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | JOptionPane.showMessageDialog(this, "割草路径已保存", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | dispose(); |
| | | // dispose(); // 用户要求保存后不关闭页面 |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | String obstacles = sanitizeValueOrNull(obstacleInput); |
| | | if (obstacles != null) { |
| | | obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' '); |
| | | // 按照用户要求,多个障碍物之间用 $ 符号分隔 |
| | | // 如果输入中包含 $,则保留 $,否则将换行符替换为 $ |
| | | if (obstacles.contains("$")) { |
| | | // 已经是 $ 分隔的格式,只需清理换行符 |
| | | obstacles = obstacles.replace("\r\n", "").replace('\r', ' ').replace('\n', ' '); |
| | | } else { |
| | | // 尝试将换行符转换为 $,或者如果是一行则保持原样 |
| | | // 这里假设用户可能用换行分隔多个障碍物 |
| | | // 但根据需求描述,似乎输入本身就应该是 $ 分隔的,或者我们需要处理成 $ 分隔 |
| | | // 为了兼容性,如果用户输入的是换行分隔的多个障碍物,我们将其转换为 $ 分隔 |
| | | // 但通常障碍物坐标是一串坐标点,如果用户没有显式用 $ 分隔,我们很难区分是同一个障碍物的点还是多个障碍物 |
| | | // 因此,这里主要处理清理工作,具体的解析逻辑在各实现类中处理 |
| | | obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' '); |
| | | } |
| | | } |
| | | |
| | | // 获取安全距离 |
| | |
| | | int grassType = shapeJudger.judgeGrassType(boundary); |
| | | // grassType: 0=无法判断, 1=凸形, 2=异形 |
| | | |
| | | // 解析障碍物列表 |
| | | List<List<Coordinate>> obstacleList = Lunjingguihua.parseObstacles(obstacles); |
| | | if (obstacleList == null) { |
| | | obstacleList = new ArrayList<>(); |
| | | } |
| | | |
| | | // 判断是否有有效的障碍物:只有当解析成功且列表不为空时,才认为有障碍物 |
| | | boolean hasValidObstacles = !obstacleList.isEmpty(); |
| | | |
| | | String generated = null; |
| | | |
| | | // 2. 根据地块类型和是否有障碍物,调用不同的路径生成类 |
| | | if (!hasValidObstacles) { |
| | | if (!hasObstacleInput) { |
| | | // 无障碍物的情况 |
| | | if (grassType == 1) { |
| | | // 凸形地块,无障碍物 -> 调用 AoxinglujingNoObstacle |
| | |
| | | generated = formatAoxingPathSegments(segments); |
| | | } else if (grassType == 2) { |
| | | // 异形地块,无障碍物 -> 调用 YixinglujingNoObstacle |
| | | // 注意:如果该类还没有实现,这里会抛出异常或返回null |
| | | try { |
| | | // 调用 YixinglujingNoObstacle.planPath 获取路径段列表 |
| | | List<YixinglujingNoObstacle.PathSegment> segments = |
| | | YixinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr); |
| | | // 格式化路径段列表为字符串 |
| | | generated = formatYixingPathSegments(segments); |
| | | } catch (Exception e) { |
| | | // 如果类还没有实现,使用原来的方法作为后备 |
| | | if (showMessages) { |
| | | System.err.println("YixinglujingNoObstacle 尚未实现,使用默认方法: " + e.getMessage()); |
| | | } |
| | | generated = Lunjingguihua.generatePathFromStrings( |
| | | boundary, obstacles != null ? obstacles : "", plannerWidth, safetyMarginStr, mode); |
| | | } |
| | | // 调用 YixinglujingNoObstacle.planPath 获取路径段列表 |
| | | List<YixinglujingNoObstacle.PathSegment> segments = |
| | | YixinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr); |
| | | // 格式化路径段列表为字符串 |
| | | generated = formatYixingPathSegments(segments); |
| | | } else { |
| | | // 无法判断地块类型,使用原来的方法作为后备 |
| | | // 无法判断地块类型,默认按凸形处理或提示 |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "无法判断地块类型,使用默认路径生成方法", |
| | | JOptionPane.showMessageDialog(parentComponent, "无法判断地块类型,尝试按凸形地块处理", |
| | | "提示", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | generated = Lunjingguihua.generatePathFromStrings( |
| | | boundary, obstacles != null ? obstacles : "", plannerWidth, safetyMarginStr, mode); |
| | | List<AoxinglujingNoObstacle.PathSegment> segments = |
| | | AoxinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr); |
| | | generated = formatAoxingPathSegments(segments); |
| | | } |
| | | } else { |
| | | // 有障碍物的情况 |
| | | if (grassType == 1) { |
| | | // 凸形地块,有障碍物 -> 调用 AoxinglujingHaveObstacel |
| | | try { |
| | | // 假设 AoxinglujingHaveObstacel 有类似的方法签名 |
| | | List<AoxinglujingHaveObstacel.PathSegment> segments = AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | generated = formatAoxingHaveObstaclePathSegments(segments); |
| | | } 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); |
| | | } |
| | | // 传入参数:boundary(A), obstacles(B), plannerWidth(C), safetyMarginStr(D) |
| | | List<AoxinglujingHaveObstacel.PathSegment> segments = |
| | | AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | generated = formatAoxingHaveObstaclePathSegments(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); |
| | | } |
| | | // 传入参数:boundary(A), obstacles(B), plannerWidth(C), safetyMarginStr(D) |
| | | // 注意:YixinglujingHaveObstacel.planPath 返回 String |
| | | generated = YixinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | } else { |
| | | // 无法判断地块类型,使用原来的方法作为后备 |
| | | // 无法判断地块类型,默认按凸形处理或提示 |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "无法判断地块类型,使用默认路径生成方法", |
| | | 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); |
| | | List<AoxinglujingHaveObstacel.PathSegment> segments = |
| | | AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | generated = formatAoxingHaveObstaclePathSegments(segments); |
| | | } |
| | | } |
| | | |