| | |
| | | |
| | | // 地块边界 |
| | | boundaryArea = createInfoTextArea(boundaryValue != null ? boundaryValue : "", true, 6); |
| | | contentPanel.add(createTextAreaSection("地块边界", boundaryArea)); |
| | | String boundaryTitle = "地块边界"; |
| | | if (boundaryValue != null && !boundaryValue.trim().isEmpty() && !"-1".equals(boundaryValue.trim())) { |
| | | int boundaryCount = boundaryValue.split(";").length; |
| | | boundaryTitle = "地块边界 (" + boundaryCount + "点)"; |
| | | } |
| | | contentPanel.add(createTextAreaSection(boundaryTitle, boundaryArea)); |
| | | |
| | | // 障碍物坐标 |
| | | obstacleArea = createInfoTextArea(obstacleValue != null ? obstacleValue : "", true, 6); |
| | | contentPanel.add(createTextAreaSection("障碍物坐标", obstacleArea)); |
| | | String obstacleTitle = "障碍物坐标"; |
| | | if (obstacleValue != null && !obstacleValue.trim().isEmpty() && !"-1".equals(obstacleValue.trim())) { |
| | | // 障碍物坐标格式可能是空格分隔的多个障碍物,每个障碍物用分号分隔坐标点 |
| | | // 计算所有障碍物的总坐标点数 |
| | | String[] obstacles = obstacleValue.trim().split("\\s+"); |
| | | int totalObstaclePoints = 0; |
| | | for (String obstacle : obstacles) { |
| | | if (obstacle != null && !obstacle.trim().isEmpty()) { |
| | | totalObstaclePoints += obstacle.split(";").length; |
| | | } |
| | | } |
| | | if (totalObstaclePoints > 0) { |
| | | obstacleTitle = "障碍物坐标 (" + totalObstaclePoints + "点)"; |
| | | } |
| | | } |
| | | contentPanel.add(createTextAreaSection(obstacleTitle, obstacleArea)); |
| | | |
| | | // 割草宽度 |
| | | widthField = createInfoTextField(widthValue != null ? widthValue : "", true); |
| | | contentPanel.add(createTextFieldSection("割草宽度 (厘米)", widthField)); |
| | | contentPanel.add(createTextFieldSection("割草宽度 (cm)", widthField)); |
| | | |
| | | // 割草安全距离(只读显示) |
| | | // 优先从Dikuai对象获取,如果Dikuai中没有,再从Device获取 |
| | | 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 = "未设置"; |
| | | String safetyDistanceValue = null; |
| | | |
| | | // 首先尝试从Dikuai对象获取 |
| | | if (dikuai != null) { |
| | | safetyDistanceValue = dikuai.getMowingSafetyDistance(); |
| | | } |
| | | |
| | | // 如果Dikuai中没有,从Device获取 |
| | | if ((safetyDistanceValue == null || "-1".equals(safetyDistanceValue) || safetyDistanceValue.trim().isEmpty())) { |
| | | Device device = Device.getActiveDevice(); |
| | | if (device != null) { |
| | | 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(Locale.US, "%.2fm", distanceMeters); |
| | | } catch (NumberFormatException e) { |
| | | displaySafetyDistance = "未设置"; |
| | | } |
| | | } |
| | | contentPanel.add(createInfoValueSection("割草安全距离", displaySafetyDistance)); |
| | |
| | | String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath()); |
| | | String pathSeed = initialGeneratedPath != null ? initialGeneratedPath : existingPath; |
| | | pathArea = createInfoTextArea(pathSeed != null ? pathSeed : "", true, 10); |
| | | contentPanel.add(createTextAreaSection("割草路径坐标", pathArea)); |
| | | String pathTitle = "割草路径坐标"; |
| | | if (pathSeed != null && !pathSeed.trim().isEmpty() && !"-1".equals(pathSeed.trim())) { |
| | | int pathCount = pathSeed.split(";").length; |
| | | pathTitle = "割草路径坐标 (" + pathCount + "点)"; |
| | | } |
| | | contentPanel.add(createTextAreaSection(pathTitle, pathArea)); |
| | | |
| | | JScrollPane dialogScrollPane = new JScrollPane(contentPanel); |
| | | dialogScrollPane.setBorder(BorderFactory.createEmptyBorder()); |
| | |
| | | String rawWidthInput = widthField.getText() != null ? widthField.getText().trim() : ""; |
| | | String widthSanitized = sanitizeWidthString(widthField.getText()); |
| | | if (widthSanitized == null) { |
| | | String message = rawWidthInput.isEmpty() ? "请先设置割草宽度(厘米)" : "割草宽度格式不正确"; |
| | | String message = rawWidthInput.isEmpty() ? "请先设置割草宽度(cm)" : "割草宽度格式不正确"; |
| | | JOptionPane.showMessageDialog(this, message, "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | |
| | | } |
| | | |
| | | JOptionPane.showMessageDialog(this, "割草路径已保存", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | dispose(); |
| | | // dispose(); // 用户要求保存后不关闭页面 |
| | | } |
| | | |
| | | /** |
| | |
| | | String widthStr = sanitizeWidthString(widthCmInput); |
| | | if (widthStr == null) { |
| | | if (showMessages) { |
| | | String message = rawWidth.isEmpty() ? "请先设置割草宽度(厘米)" : "割草宽度格式不正确"; |
| | | String message = rawWidth.isEmpty() ? "请先设置割草宽度(cm)" : "割草宽度格式不正确"; |
| | | JOptionPane.showMessageDialog(parentComponent, message, "提示", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | return null; |
| | |
| | | // 异形地块,无障碍物 -> 调用 YixinglujingNoObstacle |
| | | // 注意:如果该类还没有实现,这里会抛出异常或返回null |
| | | try { |
| | | // 假设 YixinglujingNoObstacle 有类似的方法签名 |
| | | // 如果类还没有实现,可能需要使用原来的方法作为后备 |
| | | generated = YixinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr); |
| | | // 调用 YixinglujingNoObstacle.planPath 获取路径段列表 |
| | | List<YixinglujingNoObstacle.PathSegment> segments = |
| | | YixinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr); |
| | | // 格式化路径段列表为字符串 |
| | | generated = formatYixingPathSegments(segments); |
| | | } catch (Exception e) { |
| | | // 如果类还没有实现,使用原来的方法作为后备 |
| | | if (showMessages) { |
| | |
| | | // 凸形地块,有障碍物 -> 调用 AoxinglujingHaveObstacel |
| | | try { |
| | | // 假设 AoxinglujingHaveObstacel 有类似的方法签名 |
| | | generated = AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | List<AoxinglujingHaveObstacel.PathSegment> segments = AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | generated = formatAoxingHaveObstaclePathSegments(segments); |
| | | } catch (Exception e) { |
| | | // 如果类还没有实现,使用原来的方法作为后备 |
| | | if (showMessages) { |
| | |
| | | |
| | | /** |
| | | * 获取安全距离字符串(米) |
| | | * 优先从Dikuai对象获取,如果Dikuai中没有,再从Device获取 |
| | | */ |
| | | 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,使用默认值 |
| | | String safetyDistanceValue = null; |
| | | |
| | | // 首先尝试从Dikuai对象获取 |
| | | if (dikuai != null) { |
| | | safetyDistanceValue = dikuai.getMowingSafetyDistance(); |
| | | } |
| | | |
| | | // 如果Dikuai中没有,从Device获取 |
| | | if ((safetyDistanceValue == null || "-1".equals(safetyDistanceValue) || safetyDistanceValue.trim().isEmpty())) { |
| | | Device device = Device.getActiveDevice(); |
| | | if (device != null) { |
| | | 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; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 格式化 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(); |
| | | } |
| | | |
| | | /** |
| | | * 格式化 AoxinglujingHaveObstacel.PathSegment 列表为坐标字符串 |
| | | */ |
| | | private String formatAoxingHaveObstaclePathSegments(List<AoxinglujingHaveObstacel.PathSegment> segments) { |
| | | if (segments == null || segments.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | AoxinglujingHaveObstacel.Point last = null; |
| | | for (AoxinglujingHaveObstacel.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) { |
| | |
| | | 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)); |
| | | } |
| | | |
| | | /** |
| | | * 比较两个 AoxinglujingHaveObstacel.Point 是否相同(使用小的容差) |
| | | */ |
| | | private boolean equals2D(AoxinglujingHaveObstacel.Point p1, AoxinglujingHaveObstacel.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; |
| | | } |
| | | |
| | | /** |
| | | * 添加 AoxinglujingHaveObstacel.Point 到字符串构建器 |
| | | */ |
| | | private void appendPoint(StringBuilder sb, AoxinglujingHaveObstacel.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) { |