| | |
| | | package dikuai; |
| | | |
| | | import javax.swing.*; |
| | | import java.awt.*; |
| | | import java.awt.event.*; |
| | |
| | | import java.util.Collections; |
| | | import java.util.Objects; |
| | | import java.util.Properties; |
| | | |
| | | import lujing.Lunjingguihua; |
| | | import java.util.Locale; |
| | | import lujing.MowingPathGenerationPage; |
| | | import publicway.Fuzhibutton; |
| | | import publicway.Lookbutton; |
| | |
| | | contentPanel.add(mowingPatternPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 割草机割刀宽度 |
| | | // 割草机割刀宽度(单位:米,保留2位小数) |
| | | String mowingBladeWidthValue = dikuai.getMowingBladeWidth(); |
| | | String displayBladeWidth = "未设置"; |
| | | if (mowingBladeWidthValue != null && !"-1".equals(mowingBladeWidthValue) && !mowingBladeWidthValue.trim().isEmpty()) { |
| | | try { |
| | | double bladeWidthMeters = Double.parseDouble(mowingBladeWidthValue.trim()); |
| | | double bladeWidthCm = bladeWidthMeters * 100.0; |
| | | displayBladeWidth = String.format("%.2f厘米", bladeWidthCm); |
| | | // 如果值大于100,认为是厘米,需要转换为米 |
| | | if (bladeWidthMeters > 100) { |
| | | bladeWidthMeters = bladeWidthMeters / 100.0; |
| | | } |
| | | displayBladeWidth = String.format(Locale.US, "%.2fm", bladeWidthMeters); |
| | | } catch (NumberFormatException e) { |
| | | displayBladeWidth = "未设置"; |
| | | } |
| | |
| | | contentPanel.add(mowingBladeWidthPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 割草宽度(单位:米,保留2位小数) |
| | | String mowingWidthValue = dikuai.getMowingWidth(); |
| | | String displayWidth = "未设置"; |
| | | if (mowingWidthValue != null && !"-1".equals(mowingWidthValue) && !mowingWidthValue.trim().isEmpty()) { |
| | | displayWidth = mowingWidthValue + "厘米"; |
| | | try { |
| | | double widthCm = Double.parseDouble(mowingWidthValue.trim()); |
| | | // 将厘米转换为米,保留2位小数 |
| | | double widthMeters = widthCm / 100.0; |
| | | displayWidth = String.format(Locale.US, "%.2fm", widthMeters); |
| | | } catch (NumberFormatException e) { |
| | | displayWidth = "未设置"; |
| | | } |
| | | } |
| | | JPanel mowingWidthPanel = createCardInfoItem("割草宽度:", displayWidth); |
| | | contentPanel.add(mowingWidthPanel); |
| | |
| | | if (distanceMeters > 100) { |
| | | distanceMeters = distanceMeters / 100.0; |
| | | } |
| | | displaySafetyDistance = String.format("%.2f米", distanceMeters); |
| | | displaySafetyDistance = String.format(Locale.US, "%.2fm", distanceMeters); |
| | | } catch (NumberFormatException e) { |
| | | displaySafetyDistance = "未设置"; |
| | | } |
| | |
| | | contentPanel.add(obstaclePanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 地块边界坐标(带显示顶点按钮) |
| | | JPanel boundaryPanel = createBoundaryInfoItem(dikuai); |
| | | // 地块边界坐标(带查看按钮) |
| | | JPanel boundaryPanel = createCardInfoItemWithIconButton("地块边界:", |
| | | createViewButton(e -> editBoundaryCoordinates(dikuai))); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel), |
| | | () -> editBoundaryCoordinates(dikuai), |
| | | "点击查看/编辑地块边界坐标"); |
| | |
| | | contentPanel.add(baseStationPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | JPanel boundaryOriginalPanel = createCardInfoItemWithIconButton("边界原始坐标:", |
| | | createViewButton(e -> editBoundaryOriginalCoordinates(dikuai))); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel), |
| | | () -> editBoundaryOriginalCoordinates(dikuai), |
| | | "点击查看/编辑边界原始坐标"); |
| | | contentPanel.add(boundaryOriginalPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | JPanel completedTrackPanel = createCardInfoItemWithButton("已完成割草路径:", |
| | | getTruncatedValue(dikuai.getMowingTrack(), 12, "未记录"), |
| | | createViewButton(e -> showCompletedMowingTrackDialog(dikuai))); |
| | |
| | | if (dikuai == null) { |
| | | return; |
| | | } |
| | | String edited = promptCoordinateEditing("查看 / 编辑地块边界坐标", dikuai.getBoundaryCoordinates()); |
| | | Window owner = SwingUtilities.getWindowAncestor(this); |
| | | |
| | | // 获取地块管理对话框,准备在打开边界编辑页面时关闭 |
| | | Window managementWindow = null; |
| | | if (owner instanceof JDialog) { |
| | | managementWindow = owner; |
| | | } |
| | | |
| | | // 打开边界编辑页面 |
| | | Dikuanbianjipage page = new Dikuanbianjipage(owner, "边界管理页面", dikuai.getBoundaryCoordinates(), dikuai); |
| | | page.setVisible(true); |
| | | |
| | | // 关闭地块管理页面 |
| | | if (managementWindow != null) { |
| | | managementWindow.dispose(); |
| | | } |
| | | |
| | | // 获取编辑结果并保存 |
| | | String edited = page.getResult(); |
| | | if (edited == null) { |
| | | return; |
| | | } |
| | | String normalized = normalizeCoordinateInput(edited); |
| | | if (!saveFieldAndRefresh(dikuai, "boundaryCoordinates", normalized)) { |
| | | JOptionPane.showMessageDialog(this, "无法更新地块边界坐标", "错误", JOptionPane.ERROR_MESSAGE); |
| | | JOptionPane.showMessageDialog(null, "无法更新地块边界坐标", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | String message = "-1".equals(normalized) ? "地块边界坐标已清空" : "地块边界坐标已更新"; |
| | | JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | JOptionPane.showMessageDialog(null, message, "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | |
| | | private void editPlannedPath(Dikuai dikuai) { |
| | |
| | | JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | |
| | | private void editBoundaryOriginalCoordinates(Dikuai dikuai) { |
| | | if (dikuai == null) { |
| | | return; |
| | | } |
| | | String edited = promptCoordinateEditing("查看 / 编辑边界原始坐标", dikuai.getBoundaryOriginalCoordinates()); |
| | | if (edited == null) { |
| | | return; |
| | | } |
| | | String normalized = normalizeCoordinateInput(edited); |
| | | if (!saveFieldAndRefresh(dikuai, "boundaryOriginalCoordinates", normalized)) { |
| | | JOptionPane.showMessageDialog(this, "无法更新边界原始坐标", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | String message = "-1".equals(normalized) ? "边界原始坐标已清空" : "边界原始坐标已更新"; |
| | | JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | |
| | | private void editMowingPattern(Dikuai dikuai) { |
| | | if (dikuai == null) { |
| | |
| | | public boolean savePlannedPath(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "plannedPath", value); |
| | | } |
| | | |
| | | @Override |
| | | public boolean saveMowingSafetyDistance(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "mowingSafetyDistance", value); |
| | | } |
| | | }; |
| | | |
| | | // 显示路径规划页面 |
| | |
| | | } |
| | | } |
| | | String modeValue = sanitizeValueOrNull(dikuai.getMowingPattern()); |
| | | String initialGenerated = attemptMowingPathPreview( |
| | | boundaryValue, |
| | | obstacleValue, |
| | | widthValue, |
| | | modeValue, |
| | | this, |
| | | false |
| | | ); |
| | | // 不再预先生成路径,由路径规划页面处理 |
| | | String initialGenerated = null; |
| | | showMowingPathDialog(dikuai, baseStationValue, boundaryValue, obstacleValue, widthValue, modeValue, initialGenerated); |
| | | } |
| | | |
| | |
| | | public boolean savePlannedPath(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "plannedPath", value); |
| | | } |
| | | |
| | | @Override |
| | | public boolean saveMowingSafetyDistance(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "mowingSafetyDistance", value); |
| | | } |
| | | }; |
| | | |
| | | // 使用新的独立页面类 |
| | |
| | | dialog.setVisible(true); |
| | | } |
| | | |
| | | private String attemptMowingPathPreview( |
| | | String boundaryInput, |
| | | String obstacleInput, |
| | | String widthCmInput, |
| | | String modeInput, |
| | | Component parentComponent, |
| | | boolean showMessages) { |
| | | String boundary = sanitizeValueOrNull(boundaryInput); |
| | | if (boundary == null) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "当前地块未设置边界坐标,无法生成路径", "提示", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | String rawWidth = widthCmInput != null ? widthCmInput.trim() : ""; |
| | | String widthStr = sanitizeWidthString(widthCmInput); |
| | | if (widthStr == null) { |
| | | if (showMessages) { |
| | | String message = rawWidth.isEmpty() ? "请先设置割草宽度(厘米)" : "割草宽度格式不正确"; |
| | | JOptionPane.showMessageDialog(parentComponent, message, "提示", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | double widthCm; |
| | | try { |
| | | widthCm = Double.parseDouble(widthStr); |
| | | } catch (NumberFormatException ex) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | if (widthCm <= 0) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | double widthMeters = widthCm / 100.0d; |
| | | String plannerWidth = BigDecimal.valueOf(widthMeters) |
| | | .setScale(3, RoundingMode.HALF_UP) |
| | | .stripTrailingZeros() |
| | | .toPlainString(); |
| | | String obstacles = sanitizeValueOrNull(obstacleInput); |
| | | if (obstacles != null) { |
| | | obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' '); |
| | | } |
| | | String mode = normalizeExistingMowingPattern(modeInput); |
| | | try { |
| | | String generated = Lunjingguihua.generatePathFromStrings(boundary, obstacles, plannerWidth, mode); |
| | | String trimmed = generated != null ? generated.trim() : ""; |
| | | if (trimmed.isEmpty()) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "未生成有效的割草路径,请检查地块数据", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "割草路径已生成", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | return trimmed; |
| | | } catch (IllegalArgumentException ex) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | } catch (Exception ex) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "生成割草路径时发生异常: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | /** |
| | | * 尝试生成割草路径预览 |
| | | * 用于在对话框中实时预览或生成路径,包含参数校验和错误提示逻辑 |
| | | * |
| | | * @param boundaryInput 地块边界坐标字符串 |
| | | * @param obstacleInput 障碍物坐标字符串 |
| | | * @param widthCmInput 割草宽度(厘米) |
| | | * @param modeInput 割草模式(平行/螺旋) |
| | | * @param parentComponent 用于显示提示框的父组件 |
| | | * @param showMessages 是否显示成功/失败的提示框 |
| | | * @return 生成的路径字符串,如果生成失败或校验未通过则返回 null |
| | | */ |
| | | |
| | | |
| | | private JTextArea createInfoTextArea(String text, boolean editable, int rows) { |
| | | JTextArea area = new JTextArea(text); |
| | |
| | | return false; |
| | | } |
| | | |
| | | // 同时保存到 dikuai.properties |
| | | saveFieldAndRefresh(dikuai, "obstacleCoordinates", obstaclePayload); |
| | | |
| | | obstacleSummaryCache = loadObstacleSummaries(); |
| | | boolean isCurrent = landNumber.equals(currentWorkLandNumber); |
| | | loadDikuaiData(); |