| | |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.Comparator; |
| | | import java.awt.geom.Point2D; |
| | | |
| | | import baseStation.BaseStation; |
| | | import bianjie.jisuanmianjie; |
| | | import dikuai.Dikuai; |
| | | import dikuai.Dikuaiguanli; |
| | | import gecaoji.Device; |
| | | import bianjie.bianjieguihua2; |
| | | import lujing.Lunjingguihua; |
| | | import publicway.buttonset; |
| | | import set.Setsys; |
| | | import ui.UIConfig; |
| | | import zhuye.MowerLocationData; |
| | | import zhuye.Shouye; |
| | | import zhuye.Coordinate; |
| | | import gecaoji.Device; |
| | | |
| | | /** |
| | | * 新增地块对话框 - 多步骤表单设计 |
| | |
| | | private final Color LIGHT_TEXT = new Color(108, 117, 125); |
| | | private final Color BORDER_COLOR = new Color(222, 226, 230); |
| | | private final Color SUCCESS_COLOR = new Color(40, 167, 69); |
| | | private final Color ERROR_COLOR = new Color(220, 53, 69); |
| | | private static final String KEY_PATH_MESSAGE_TEXT = "__pathMessageText"; |
| | | private static final String KEY_PATH_MESSAGE_SUCCESS = "__pathMessageSuccess"; |
| | | |
| | | // 步骤面板 |
| | | private JPanel mainPanel; |
| | |
| | | private JTextField landNumberField; |
| | | private JTextField areaNameField; |
| | | private JComboBox<String> mowingPatternCombo; |
| | | private JSpinner mowingWidthSpinner; |
| | | private JTextField mowingWidthField; // 割草机割刀宽度 |
| | | private JTextField overlapDistanceField; // 相邻行重叠距离 |
| | | private JTextField mowingSafetyDistanceField; // 割草安全距离 |
| | | private JLabel calculatedMowingWidthLabel; // 计算后的割草宽度显示 |
| | | private JPanel previewPanel; |
| | | private Map<String, JPanel> drawingOptionPanels = new HashMap<>(); |
| | | |
| | |
| | | private JButton prevButton; |
| | | private JButton nextButton; |
| | | private JButton createButton; |
| | | private JButton previewButton; |
| | | private Component previewButtonSpacer; |
| | | private JLabel boundaryCountLabel; |
| | | private JPanel obstacleListContainer; |
| | | private JTextArea pathGenerationMessageArea; |
| | | private JPanel pathMessageWrapper; |
| | | |
| | | // 地块数据 |
| | | private Map<String, String> dikuaiData = new HashMap<>(); |
| | |
| | | final boolean success; |
| | | final String errorMessage; |
| | | final String originalBoundary; |
| | | final String originalBoundaryXY; |
| | | final String optimizedBoundary; |
| | | final String areaSqMeters; |
| | | final String baseStationCoordinates; |
| | |
| | | private BoundarySnapshotResult(boolean success, |
| | | String errorMessage, |
| | | String originalBoundary, |
| | | String originalBoundaryXY, |
| | | String optimizedBoundary, |
| | | String areaSqMeters, |
| | | String baseStationCoordinates, |
| | |
| | | this.success = success; |
| | | this.errorMessage = errorMessage; |
| | | this.originalBoundary = originalBoundary; |
| | | this.originalBoundaryXY = originalBoundaryXY; |
| | | this.optimizedBoundary = optimizedBoundary; |
| | | this.areaSqMeters = areaSqMeters; |
| | | this.baseStationCoordinates = baseStationCoordinates; |
| | | this.messageType = messageType; |
| | | } |
| | | |
| | | static BoundarySnapshotResult success(String original, String optimized, String area, String baseStation) { |
| | | return new BoundarySnapshotResult(true, null, original, optimized, area, baseStation, JOptionPane.INFORMATION_MESSAGE); |
| | | static BoundarySnapshotResult success(String original, String originalXY, String optimized, String area, String baseStation) { |
| | | return new BoundarySnapshotResult(true, null, original, originalXY, optimized, area, baseStation, JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | |
| | | static BoundarySnapshotResult failure(String message, int messageType) { |
| | | return new BoundarySnapshotResult(false, message, null, null, null, null, messageType); |
| | | return new BoundarySnapshotResult(false, message, null, null, null, null, null, messageType); |
| | | } |
| | | } |
| | | |
| | |
| | | formGroup.add(areaNameField); |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | formGroup.add(hintLabel); |
| | | |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | JPanel obstacleSection = createObstacleSummarySection(); |
| | | formGroup.add(obstacleSection); |
| | | |
| | | stepPanel.add(formGroup); |
| | | stepPanel.add(Box.createVerticalGlue()); |
| | |
| | | |
| | | private List<ObstacleSummary> loadExistingObstacles() { |
| | | List<ObstacleSummary> summaries = new ArrayList<>(); |
| | | List<ExistingObstacle> obstacles = fetchExistingObstacleDetails(); |
| | | |
| | | String landNumber = getPendingLandNumber(); |
| | | String raw = null; |
| | | if (landNumber != null) { |
| | | Dikuai dikuai = Dikuai.getDikuai(landNumber); |
| | | if (dikuai != null) { |
| | | raw = normalizeCoordinateValue(dikuai.getObstacleCoordinates()); |
| | | } |
| | | } |
| | | if (raw == null) { |
| | | raw = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates")); |
| | | } |
| | | if (!isMeaningfulValue(raw)) { |
| | | if (obstacles.isEmpty()) { |
| | | return summaries; |
| | | } |
| | | |
| | | String normalized = stripInlineComment(raw); |
| | | if (normalized.isEmpty()) { |
| | | return summaries; |
| | | } |
| | | |
| | | List<String> entries = splitObstacleEntries(normalized); |
| | | int defaultIndex = 1; |
| | | |
| | | for (String entry : entries) { |
| | | String trimmedEntry = stripInlineComment(entry); |
| | | if (trimmedEntry.isEmpty()) { |
| | | for (ExistingObstacle obstacle : obstacles) { |
| | | if (obstacle == null) { |
| | | continue; |
| | | } |
| | | |
| | | String nameToken = null; |
| | | String shapeToken = null; |
| | | String coordsSection = trimmedEntry; |
| | | |
| | | if (trimmedEntry.contains("::")) { |
| | | String[] parts = trimmedEntry.split("::", 3); |
| | | if (parts.length == 3) { |
| | | nameToken = parts[0].trim(); |
| | | shapeToken = parts[1].trim(); |
| | | coordsSection = parts[2].trim(); |
| | | } |
| | | } else if (trimmedEntry.contains("@")) { |
| | | String[] parts = trimmedEntry.split("@", 3); |
| | | if (parts.length == 3) { |
| | | nameToken = parts[0].trim(); |
| | | shapeToken = parts[1].trim(); |
| | | coordsSection = parts[2].trim(); |
| | | } else if (parts.length == 2) { |
| | | shapeToken = parts[0].trim(); |
| | | coordsSection = parts[1].trim(); |
| | | } |
| | | } else if (trimmedEntry.contains(":")) { |
| | | String[] parts = trimmedEntry.split(":", 3); |
| | | if (parts.length == 3) { |
| | | nameToken = parts[0].trim(); |
| | | shapeToken = parts[1].trim(); |
| | | coordsSection = parts[2].trim(); |
| | | } else if (parts.length == 2) { |
| | | if (looksLikeShapeToken(parts[0])) { |
| | | shapeToken = parts[0].trim(); |
| | | coordsSection = parts[1].trim(); |
| | | } else { |
| | | nameToken = parts[0].trim(); |
| | | coordsSection = parts[1].trim(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | String sanitizedCoords = sanitizeCoordinateString(coordsSection); |
| | | if (!isMeaningfulValue(sanitizedCoords)) { |
| | | String name = obstacle.getName(); |
| | | if (!isMeaningfulValue(name)) { |
| | | continue; |
| | | } |
| | | |
| | | String resolvedName; |
| | | if (nameToken != null && !nameToken.isEmpty()) { |
| | | resolvedName = nameToken; |
| | | } else { |
| | | resolvedName = "障碍物" + defaultIndex++; |
| | | } |
| | | |
| | | String displayCoords = truncateCoordinateForDisplay(sanitizedCoords, 12); |
| | | summaries.add(new ObstacleSummary(resolvedName, sanitizedCoords, displayCoords)); |
| | | String coords = obstacle.getDisplayCoordinates(); |
| | | String fullCoords = isMeaningfulValue(coords) ? coords.trim() : ""; |
| | | String preview = buildCoordinatePreview(fullCoords, 20); |
| | | summaries.add(new ObstacleSummary(name.trim(), fullCoords, preview)); |
| | | } |
| | | |
| | | return summaries; |
| | | } |
| | | |
| | | private List<ExistingObstacle> fetchExistingObstacleDetails() { |
| | | File configFile = new File("Obstacledge.properties"); |
| | | if (!configFile.exists()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | List<ExistingObstacle> result = new ArrayList<>(); |
| | | try { |
| | | Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); |
| | | if (!manager.loadFromFile(configFile.getAbsolutePath())) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | for (Obstacledge.Plot plot : manager.getPlots()) { |
| | | if (plot == null) { |
| | | continue; |
| | | } |
| | | String landNumber = isMeaningfulValue(plot.getPlotId()) ? plot.getPlotId().trim() : ""; |
| | | List<Obstacledge.Obstacle> plotObstacles = plot.getObstacles(); |
| | | if (plotObstacles == null || plotObstacles.isEmpty()) { |
| | | continue; |
| | | } |
| | | for (Obstacledge.Obstacle obstacle : plotObstacles) { |
| | | if (obstacle == null) { |
| | | continue; |
| | | } |
| | | String name = obstacle.getObstacleName(); |
| | | if (!isMeaningfulValue(name)) { |
| | | continue; |
| | | } |
| | | String coords = extractObstacleCoordinates(obstacle); |
| | | result.add(new ExistingObstacle(landNumber, name.trim(), coords)); |
| | | } |
| | | } |
| | | } catch (Exception ex) { |
| | | System.err.println("加载已有障碍物失败: " + ex.getMessage()); |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | if (result.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | result.sort(Comparator.comparing(ExistingObstacle::getName, String.CASE_INSENSITIVE_ORDER)); |
| | | return result; |
| | | } |
| | | |
| | | private String extractObstacleCoordinates(Obstacledge.Obstacle obstacle) { |
| | | if (obstacle == null) { |
| | | return ""; |
| | | } |
| | | String xy = obstacle.getXyCoordsString(); |
| | | if (isMeaningfulValue(xy)) { |
| | | return xy.trim(); |
| | | } |
| | | String original = obstacle.getOriginalCoordsString(); |
| | | if (isMeaningfulValue(original)) { |
| | | return original.trim(); |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | private String buildCoordinatePreview(String coords, int keepLength) { |
| | | if (!isMeaningfulValue(coords)) { |
| | | return "无坐标"; |
| | | } |
| | | String sanitized = sanitizeCoordinateString(coords); |
| | | if (sanitized.length() <= keepLength) { |
| | | return sanitized; |
| | | } |
| | | if (keepLength <= 0) { |
| | | return "..."; |
| | | } |
| | | return sanitized.substring(0, keepLength) + "..."; |
| | | } |
| | | |
| | | private List<String> splitObstacleEntries(String data) { |
| | | List<String> entries = new ArrayList<>(); |
| | | if (data.indexOf('|') >= 0) { |
| | |
| | | return displayCoords; |
| | | } |
| | | } |
| | | |
| | | private static final class ExistingObstacle { |
| | | private final String landNumber; |
| | | private final String name; |
| | | private final String coordinates; |
| | | |
| | | ExistingObstacle(String landNumber, String name, String coordinates) { |
| | | this.landNumber = landNumber != null ? landNumber : ""; |
| | | this.name = name != null ? name : ""; |
| | | this.coordinates = coordinates != null ? coordinates : ""; |
| | | } |
| | | |
| | | String getLandNumber() { |
| | | return landNumber; |
| | | } |
| | | |
| | | String getName() { |
| | | return name; |
| | | } |
| | | |
| | | String getDisplayCoordinates() { |
| | | return coordinates; |
| | | } |
| | | } |
| | | |
| | | private JPanel createStep2Panel() { |
| | | JPanel stepPanel = new JPanel(); |
| | |
| | | if (!optionPanel.isEnabled()) { |
| | | return; |
| | | } |
| | | selectDrawingOption(optionPanel, type); |
| | | startEndDrawingBtn.setEnabled(true); // 选择后启用按钮 |
| | | if (selectDrawingOption(optionPanel, type, true)) { |
| | | startEndDrawingBtn.setEnabled(true); // 选择后启用按钮 |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | return optionPanel; |
| | | } |
| | | |
| | | private void selectDrawingOption(JPanel optionPanel, String type) { |
| | | private boolean selectDrawingOption(JPanel optionPanel, String type, boolean userTriggered) { |
| | | if (optionPanel == null) { |
| | | return false; |
| | | } |
| | | if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) { |
| | | JOptionPane.showMessageDialog(this, "请先去系统设置添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return false; |
| | | } |
| | | |
| | | // 重置之前选中的选项 |
| | | if (selectedOptionPanel != null) { |
| | | selectedOptionPanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR, 2)); |
| | |
| | | ((JLabel) oldTitle).setForeground(TEXT_COLOR); |
| | | } |
| | | } |
| | | |
| | | |
| | | // 设置新的选中状态 |
| | | optionPanel.setBorder(BorderFactory.createLineBorder(PRIMARY_COLOR, 3)); |
| | | optionPanel.setBackground(PRIMARY_LIGHT); |
| | |
| | | ((JLabel) titleObj).setForeground(PRIMARY_COLOR); |
| | | } |
| | | selectedOptionPanel = optionPanel; |
| | | |
| | | |
| | | // 保存选择 |
| | | dikuaiData.put("drawingMethod", type); |
| | | return true; |
| | | } |
| | | |
| | | private boolean hasConfiguredHandheldMarker() { |
| | | String handheldId = Setsys.getPropertyValue("handheldMarkerId"); |
| | | return handheldId != null && !handheldId.trim().isEmpty(); |
| | | } |
| | | |
| | | private void toggleDrawing() { |
| | |
| | | settingsPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | // 割草模式选择 |
| | | JPanel patternPanel = createFormGroup("割草模式", "选择割草路径的生成模式"); |
| | | JPanel patternPanel = createFormGroupWithoutHint("割草模式"); |
| | | mowingPatternCombo = new JComboBox<>(new String[]{"平行线", "螺旋式"}); |
| | | mowingPatternCombo.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | mowingPatternCombo.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | |
| | | |
| | | patternPanel.add(mowingPatternCombo); |
| | | settingsPanel.add(patternPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 割草宽度设置 |
| | | JPanel widthPanel = createFormGroup("割草宽度", "设置割草机单次割草的宽度"); |
| | | // 割草机割刀宽度设置 |
| | | JPanel widthPanel = createFormGroupWithoutHint("割草机割刀宽度"); |
| | | JPanel widthInputPanel = new JPanel(new BorderLayout()); |
| | | widthInputPanel.setBackground(WHITE); |
| | | widthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | widthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | SpinnerNumberModel widthModel = new SpinnerNumberModel(40, 20, 60, 1); |
| | | mowingWidthSpinner = new JSpinner(widthModel); |
| | | mowingWidthSpinner.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) mowingWidthSpinner.getEditor(); |
| | | editor.getTextField().setBorder(BorderFactory.createCompoundBorder( |
| | | mowingWidthField = new JTextField("0.40"); |
| | | mowingWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | mowingWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | |
| | | // 添加微调器焦点效果 |
| | | mowingWidthSpinner.addFocusListener(new FocusAdapter() { |
| | | // 添加文本框焦点效果和变化监听 |
| | | mowingWidthField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | | editor.getTextField().setBorder(BorderFactory.createCompoundBorder( |
| | | mowingWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | |
| | | |
| | | @Override |
| | | public void focusLost(FocusEvent e) { |
| | | editor.getTextField().setBorder(BorderFactory.createCompoundBorder( |
| | | mowingWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | updateCalculatedMowingWidth(); |
| | | } |
| | | }); |
| | | |
| | | JLabel unitLabel = new JLabel("米"); |
| | | unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | unitLabel.setForeground(LIGHT_TEXT); |
| | | unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | |
| | | widthInputPanel.add(mowingWidthField, BorderLayout.CENTER); |
| | | widthInputPanel.add(unitLabel, BorderLayout.EAST); |
| | | |
| | | widthPanel.add(widthInputPanel); |
| | | settingsPanel.add(widthPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 相邻行重叠距离设置 |
| | | JPanel overlapPanel = createFormGroupWithoutHint("相邻行重叠距离"); |
| | | JPanel overlapInputPanel = new JPanel(new BorderLayout()); |
| | | overlapInputPanel.setBackground(WHITE); |
| | | overlapInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | overlapInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | overlapDistanceField = new JTextField("0.06"); |
| | | overlapDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | overlapDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | |
| | | // 添加文本框焦点效果和变化监听 |
| | | overlapDistanceField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | | overlapDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | |
| | | @Override |
| | | public void focusLost(FocusEvent e) { |
| | | overlapDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | updateCalculatedMowingWidth(); |
| | | } |
| | | }); |
| | | |
| | | JLabel overlapUnitLabel = new JLabel("米"); |
| | | overlapUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | overlapUnitLabel.setForeground(LIGHT_TEXT); |
| | | overlapUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | |
| | | overlapInputPanel.add(overlapDistanceField, BorderLayout.CENTER); |
| | | overlapInputPanel.add(overlapUnitLabel, BorderLayout.EAST); |
| | | |
| | | overlapPanel.add(overlapInputPanel); |
| | | settingsPanel.add(overlapPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 割草宽度显示(自动计算,不可修改) |
| | | JPanel calculatedWidthPanel = createFormGroup("割草宽度", "割草宽度 = 割草机割刀宽度 - 重叠距离(自动计算)"); |
| | | JPanel calculatedWidthDisplayPanel = new JPanel(new BorderLayout()); |
| | | calculatedWidthDisplayPanel.setBackground(LIGHT_GRAY); |
| | | calculatedWidthDisplayPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | calculatedWidthDisplayPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | calculatedWidthDisplayPanel.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | |
| | | calculatedMowingWidthLabel = new JLabel("0.00 米"); |
| | | calculatedMowingWidthLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | calculatedMowingWidthLabel.setForeground(TEXT_COLOR); |
| | | calculatedWidthDisplayPanel.add(calculatedMowingWidthLabel, BorderLayout.CENTER); |
| | | |
| | | calculatedWidthPanel.add(calculatedWidthDisplayPanel); |
| | | settingsPanel.add(calculatedWidthPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 割草安全距离 |
| | | JPanel safetyDistancePanel = createFormGroupWithoutHint("割草安全距离"); |
| | | JPanel safetyDistanceInputPanel = new JPanel(new BorderLayout()); |
| | | safetyDistanceInputPanel.setBackground(WHITE); |
| | | safetyDistanceInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | safetyDistanceInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | // 获取默认值 |
| | | String defaultSafetyDistance = "0.5"; // 默认值 |
| | | try { |
| | | Device device = Device.getGecaoji(); |
| | | if (device != null) { |
| | | String safetyDistance = device.getMowingSafetyDistance(); |
| | | if (safetyDistance != null && !safetyDistance.trim().isEmpty() && !"-1".equals(safetyDistance.trim())) { |
| | | defaultSafetyDistance = safetyDistance.trim(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | // 如果获取失败,使用默认值 |
| | | } |
| | | |
| | | mowingSafetyDistanceField = new JTextField(defaultSafetyDistance); |
| | | mowingSafetyDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | |
| | | // 添加文本框焦点效果 |
| | | mowingSafetyDistanceField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | | mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | |
| | | @Override |
| | | public void focusLost(FocusEvent e) { |
| | | mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | }); |
| | | |
| | | JLabel unitLabel = new JLabel("厘米"); |
| | | unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | unitLabel.setForeground(LIGHT_TEXT); |
| | | unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | JLabel safetyDistanceUnitLabel = new JLabel("米"); |
| | | safetyDistanceUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | safetyDistanceUnitLabel.setForeground(LIGHT_TEXT); |
| | | safetyDistanceUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | |
| | | widthInputPanel.add(mowingWidthSpinner, BorderLayout.CENTER); |
| | | widthInputPanel.add(unitLabel, BorderLayout.EAST); |
| | | safetyDistanceInputPanel.add(mowingSafetyDistanceField, BorderLayout.CENTER); |
| | | safetyDistanceInputPanel.add(safetyDistanceUnitLabel, BorderLayout.EAST); |
| | | |
| | | widthPanel.add(widthInputPanel); |
| | | settingsPanel.add(widthPanel); |
| | | safetyDistancePanel.add(safetyDistanceInputPanel); |
| | | settingsPanel.add(safetyDistancePanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 25))); |
| | | |
| | | stepPanel.add(settingsPanel); |
| | | |
| | | // 初始化计算后的割草宽度 |
| | | updateCalculatedMowingWidth(); |
| | | |
| | | JButton generatePathButton = createPrimaryButton("生成割草路径", 16); |
| | | generatePathButton.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | generatePathButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, 55)); |
| | |
| | | |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | stepPanel.add(generatePathButton); |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 12))); |
| | | |
| | | pathMessageWrapper = new JPanel(new BorderLayout()); |
| | | pathMessageWrapper.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | pathMessageWrapper.setBackground(PRIMARY_LIGHT); |
| | | pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_COLOR, 1), |
| | | BorderFactory.createEmptyBorder(12, 12, 12, 12) |
| | | )); |
| | | pathMessageWrapper.setVisible(false); |
| | | |
| | | pathGenerationMessageArea = new JTextArea(); |
| | | pathGenerationMessageArea.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | pathGenerationMessageArea.setForeground(TEXT_COLOR); |
| | | pathGenerationMessageArea.setOpaque(false); |
| | | pathGenerationMessageArea.setEditable(false); |
| | | pathGenerationMessageArea.setLineWrap(true); |
| | | pathGenerationMessageArea.setWrapStyleWord(true); |
| | | pathGenerationMessageArea.setFocusable(false); |
| | | pathGenerationMessageArea.setBorder(null); |
| | | |
| | | pathMessageWrapper.add(pathGenerationMessageArea, BorderLayout.CENTER); |
| | | stepPanel.add(pathMessageWrapper); |
| | | stepPanel.add(Box.createVerticalGlue()); |
| | | |
| | | return stepPanel; |
| | | } |
| | | |
| | | /** |
| | | * 更新计算后的割草宽度显示 |
| | | */ |
| | | private void updateCalculatedMowingWidth() { |
| | | if (calculatedMowingWidthLabel == null || mowingWidthField == null || overlapDistanceField == null) { |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | String widthText = mowingWidthField.getText().trim(); |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | |
| | | if (widthText.isEmpty() || overlapText.isEmpty()) { |
| | | calculatedMowingWidthLabel.setText("0.00 米"); |
| | | return; |
| | | } |
| | | |
| | | double bladeWidthMeters = Double.parseDouble(widthText); |
| | | double overlapMeters = Double.parseDouble(overlapText); |
| | | double calculatedWidthMeters = bladeWidthMeters - overlapMeters; |
| | | |
| | | if (calculatedWidthMeters <= 0) { |
| | | calculatedMowingWidthLabel.setText("无效(割刀宽度需大于重叠距离)"); |
| | | calculatedMowingWidthLabel.setForeground(ERROR_COLOR); |
| | | } else { |
| | | calculatedMowingWidthLabel.setText(String.format(Locale.US, "%.2f 米", calculatedWidthMeters)); |
| | | calculatedMowingWidthLabel.setForeground(TEXT_COLOR); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | calculatedMowingWidthLabel.setText("0.00 米"); |
| | | } catch (Exception e) { |
| | | calculatedMowingWidthLabel.setText("0.00 米"); |
| | | } |
| | | } |
| | | |
| | | private JPanel createInstructionPanel(String text) { |
| | | JPanel instructionPanel = new JPanel(new BorderLayout()); |
| | | instructionPanel.setBackground(PRIMARY_LIGHT); |
| | |
| | | |
| | | return formGroup; |
| | | } |
| | | |
| | | private JPanel createFormGroupWithoutHint(String label) { |
| | | JPanel formGroup = new JPanel(); |
| | | formGroup.setLayout(new BoxLayout(formGroup, BoxLayout.Y_AXIS)); |
| | | formGroup.setBackground(WHITE); |
| | | formGroup.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | JLabel nameLabel = new JLabel(label); |
| | | nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); |
| | | nameLabel.setForeground(TEXT_COLOR); |
| | | nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | formGroup.add(nameLabel); |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | |
| | | return formGroup; |
| | | } |
| | | |
| | | private void generateMowingPath() { |
| | | if (!dikuaiData.containsKey("boundaryDrawn")) { |
| | | JOptionPane.showMessageDialog(this, "请先完成边界绘制后再生成路径", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("请先完成边界绘制后再生成路径。", false); |
| | | setPathAvailability(false); |
| | | showStep(2); |
| | | return; |
| | | } |
| | |
| | | } |
| | | if (boundaryCoords == null) { |
| | | JOptionPane.showMessageDialog(this, "未找到有效的地块边界坐标,无法生成路径", "提示", JOptionPane.WARNING_MESSAGE); |
| | | if (createButton != null) { |
| | | createButton.setEnabled(false); |
| | | } |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("未找到有效的地块边界坐标,无法生成路径。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | |
| | | String obstacleCoords = null; |
| | | if (dikuai != null) { |
| | | obstacleCoords = normalizeCoordinateValue(dikuai.getObstacleCoordinates()); |
| | | String landNumber = getPendingLandNumber(); |
| | | if (isMeaningfulValue(landNumber)) { |
| | | obstacleCoords = normalizeCoordinateValue(resolveObstaclePayloadFromConfig(landNumber)); |
| | | } |
| | | if (obstacleCoords == null) { |
| | | obstacleCoords = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates")); |
| | |
| | | String patternDisplay = (String) mowingPatternCombo.getSelectedItem(); |
| | | dikuaiData.put("mowingPattern", patternDisplay); |
| | | |
| | | Object widthObj = mowingWidthSpinner.getValue(); |
| | | if (!(widthObj instanceof Number)) { |
| | | JOptionPane.showMessageDialog(this, "割草宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE); |
| | | if (createButton != null) { |
| | | createButton.setEnabled(false); |
| | | } |
| | | String widthText = mowingWidthField.getText().trim(); |
| | | if (widthText.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "割草机割刀宽度不能为空", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("割草机割刀宽度不能为空,请重新输入。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | double widthCm = ((Number) widthObj).doubleValue(); |
| | | if (widthCm <= 0) { |
| | | JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE); |
| | | if (createButton != null) { |
| | | createButton.setEnabled(false); |
| | | } |
| | | double bladeWidthMeters; |
| | | try { |
| | | bladeWidthMeters = Double.parseDouble(widthText); |
| | | } catch (NumberFormatException e) { |
| | | JOptionPane.showMessageDialog(this, "割草机割刀宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("割草机割刀宽度输入无效,请重新输入。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | dikuaiData.put("mowingWidth", widthObj.toString()); |
| | | if (bladeWidthMeters <= 0) { |
| | | JOptionPane.showMessageDialog(this, "割草机割刀宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("割草机割刀宽度必须大于0,请重新设置。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | // 保存割草机割刀宽度(米) |
| | | dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters)); |
| | | |
| | | // 获取重叠距离 |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | if (overlapText.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "相邻行重叠距离不能为空", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("相邻行重叠距离不能为空,请重新输入。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | double overlapMeters; |
| | | try { |
| | | overlapMeters = Double.parseDouble(overlapText); |
| | | } catch (NumberFormatException e) { |
| | | JOptionPane.showMessageDialog(this, "相邻行重叠距离输入无效", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("相邻行重叠距离输入无效,请重新输入。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | if (overlapMeters < 0) { |
| | | JOptionPane.showMessageDialog(this, "相邻行重叠距离不能为负数", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("相邻行重叠距离不能为负数,请重新设置。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | |
| | | // 保存重叠距离到地块数据 |
| | | dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters)); |
| | | |
| | | // 计算实际使用的割草宽度(割刀宽度 - 重叠距离) |
| | | double calculatedWidthMeters = bladeWidthMeters - overlapMeters; |
| | | if (calculatedWidthMeters <= 0) { |
| | | JOptionPane.showMessageDialog(this, "计算后的割草宽度必须大于0(割刀宽度必须大于重叠距离)", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("计算后的割草宽度必须大于0,请调整割刀宽度或重叠距离。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | |
| | | String widthMeters = String.format(Locale.US, "%.2f", widthCm / 100.0); |
| | | // 保存割草宽度(计算后的值,转换为厘米存储,保持与原有数据格式兼容) |
| | | double calculatedWidthCm = calculatedWidthMeters * 100.0; |
| | | dikuaiData.put("mowingWidth", String.format(Locale.US, "%.0f", calculatedWidthCm)); |
| | | |
| | | String widthMeters = String.format(Locale.US, "%.2f", calculatedWidthMeters); |
| | | String plannerMode = resolvePlannerMode(patternDisplay); |
| | | |
| | | try { |
| | |
| | | String plannedPath = Lunjingguihua.formatPathSegments(segments); |
| | | if (!isMeaningfulValue(plannedPath)) { |
| | | JOptionPane.showMessageDialog(this, "生成割草路径失败: 生成结果为空", "错误", JOptionPane.ERROR_MESSAGE); |
| | | if (createButton != null) { |
| | | createButton.setEnabled(false); |
| | | } |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("生成割草路径失败:生成结果为空。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | dikuaiData.put("plannedPath", plannedPath); |
| | | if (createButton != null) { |
| | | createButton.setEnabled(true); |
| | | if (isMeaningfulValue(boundaryCoords)) { |
| | | dikuaiData.put("boundaryCoordinates", boundaryCoords); |
| | | } |
| | | JOptionPane.showMessageDialog(this, |
| | | "已根据当前设置生成割草路径,共生成 " + segments.size() + " 段。", |
| | | "成功", |
| | | JOptionPane.INFORMATION_MESSAGE); |
| | | if (isMeaningfulValue(obstacleCoords)) { |
| | | dikuaiData.put("obstacleCoordinates", obstacleCoords); |
| | | } |
| | | dikuaiData.put("plannedPath", plannedPath); |
| | | setPathAvailability(true); |
| | | showPathGenerationMessage( |
| | | "已根据当前设置生成割草路径,共生成 " + segments.size() + " 段。\n点击“预览”按钮可在主页面查看效果。", |
| | | true); |
| | | } catch (IllegalArgumentException ex) { |
| | | JOptionPane.showMessageDialog(this, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); |
| | | if (createButton != null) { |
| | | createButton.setEnabled(false); |
| | | } |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("生成割草路径失败:" + ex.getMessage(), false); |
| | | setPathAvailability(false); |
| | | } catch (Exception ex) { |
| | | ex.printStackTrace(); |
| | | JOptionPane.showMessageDialog(this, "生成割草路径时发生异常: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); |
| | | if (createButton != null) { |
| | | createButton.setEnabled(false); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("生成割草路径时发生异常:" + ex.getMessage(), false); |
| | | setPathAvailability(false); |
| | | } |
| | | } |
| | | |
| | | private void previewMowingPath() { |
| | | if (!hasGeneratedPath()) { |
| | | showPathGenerationMessage("请先生成割草路径后再预览。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | |
| | | persistStep3Inputs(); |
| | | |
| | | String landNumber = getPendingLandNumber(); |
| | | String trimmedAreaName = areaNameField.getText() != null ? areaNameField.getText().trim() : ""; |
| | | String displayAreaName = isMeaningfulValue(trimmedAreaName) ? trimmedAreaName : landNumber; |
| | | |
| | | String plannedPath = dikuaiData.get("plannedPath"); |
| | | if (!isMeaningfulValue(plannedPath)) { |
| | | showPathGenerationMessage("请先生成割草路径后再预览。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | |
| | | String boundary = null; |
| | | Dikuai pending = getOrCreatePendingDikuai(); |
| | | if (pending != null) { |
| | | boundary = normalizeCoordinateValue(pending.getBoundaryCoordinates()); |
| | | } |
| | | if (boundary == null) { |
| | | boundary = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates")); |
| | | } |
| | | |
| | | String obstacles = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates")); |
| | | if (!isMeaningfulValue(obstacles)) { |
| | | obstacles = resolveObstaclePayloadFromConfig(landNumber); |
| | | if (isMeaningfulValue(obstacles)) { |
| | | dikuaiData.put("obstacleCoordinates", obstacles); |
| | | } |
| | | } |
| | | |
| | | Shouye shouye = Shouye.getInstance(); |
| | | if (shouye == null) { |
| | | JOptionPane.showMessageDialog(this, "无法打开主页面,请稍后重试", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | dikuaiData.put("areaName", trimmedAreaName); |
| | | if (isMeaningfulValue(boundary)) { |
| | | dikuaiData.put("boundaryCoordinates", boundary); |
| | | } |
| | | |
| | | pendingLandNumber = landNumber; |
| | | captureSessionSnapshot(); |
| | | |
| | | resumeRequested = true; |
| | | boolean started = shouye.startMowingPathPreview( |
| | | landNumber, |
| | | displayAreaName, |
| | | boundary, |
| | | obstacles, |
| | | plannedPath, |
| | | AddDikuai::resumeFromPreview |
| | | ); |
| | | if (!started) { |
| | | resumeRequested = false; |
| | | JOptionPane.showMessageDialog(this, "无法启动预览,请稍后再试", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | closePreviewAndDispose(); |
| | | } |
| | | |
| | | private void persistStep3Inputs() { |
| | | String trimmedName = areaNameField.getText() != null ? areaNameField.getText().trim() : ""; |
| | | dikuaiData.put("areaName", trimmedName); |
| | | |
| | | if (mowingPatternCombo != null) { |
| | | Object selection = mowingPatternCombo.getSelectedItem(); |
| | | if (selection != null) { |
| | | dikuaiData.put("mowingPattern", selection.toString()); |
| | | } |
| | | } |
| | | |
| | | // 保存割草机割刀宽度 |
| | | if (mowingWidthField != null) { |
| | | String bladeWidthText = mowingWidthField.getText().trim(); |
| | | if (!bladeWidthText.isEmpty()) { |
| | | try { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthText); |
| | | dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters)); |
| | | } catch (NumberFormatException e) { |
| | | // 如果解析失败,直接保存文本 |
| | | dikuaiData.put("mowingBladeWidth", bladeWidthText); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存相邻行重叠距离 |
| | | if (overlapDistanceField != null) { |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | if (!overlapText.isEmpty()) { |
| | | try { |
| | | double overlapMeters = Double.parseDouble(overlapText); |
| | | dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters)); |
| | | } catch (NumberFormatException e) { |
| | | // 如果解析失败,直接保存文本 |
| | | dikuaiData.put("mowingOverlapDistance", overlapText); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 计算并保存割草宽度(割刀宽度 - 重叠距离) |
| | | if (mowingWidthField != null && overlapDistanceField != null) { |
| | | try { |
| | | String bladeWidthText = mowingWidthField.getText().trim(); |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthText); |
| | | double overlapMeters = Double.parseDouble(overlapText); |
| | | double calculatedWidthMeters = bladeWidthMeters - overlapMeters; |
| | | if (calculatedWidthMeters > 0) { |
| | | // 转换为厘米存储,保持与原有数据格式兼容 |
| | | int widthCm = (int) Math.round(calculatedWidthMeters * 100.0); |
| | | dikuaiData.put("mowingWidth", Integer.toString(widthCm)); |
| | | } |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 如果计算失败,不保存割草宽度 |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void captureSessionSnapshot() { |
| | | if (activeSession == null) { |
| | | activeSession = new DrawingSession(); |
| | | } |
| | | String landNumber = getPendingLandNumber(); |
| | | activeSession.landNumber = landNumber; |
| | | activeSession.areaName = areaNameField.getText() != null ? areaNameField.getText().trim() : ""; |
| | | activeSession.drawingCompleted = true; |
| | | activeSession.data = new HashMap<>(dikuaiData); |
| | | } |
| | | |
| | | private void closePreviewAndDispose() { |
| | | setVisible(false); |
| | | dispose(); |
| | | } |
| | | |
| | | private JButton createPrimaryButton(String text, int fontSize) { |
| | | JButton button = new JButton(text); |
| | | JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR); |
| | | button.setFont(new Font("微软雅黑", Font.BOLD, fontSize)); |
| | | button.setBackground(PRIMARY_COLOR); |
| | | button.setForeground(WHITE); |
| | | button.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_DARK, 2), |
| | | BorderFactory.createEmptyBorder(12, 25, 12, 25) |
| | | )); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | |
| | | // 按钮悬停效果 |
| | | |
| | | button.addMouseListener(new MouseAdapter() { |
| | | @Override |
| | | public void mouseEntered(MouseEvent e) { |
| | |
| | | button.setBackground(PRIMARY_DARK); |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void mouseExited(MouseEvent e) { |
| | | if (button.isEnabled()) { |
| | |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | return button; |
| | | } |
| | | |
| | | private void showPathGenerationMessage(String message, boolean success) { |
| | | if (pathGenerationMessageArea == null || pathMessageWrapper == null) { |
| | | return; |
| | | } |
| | | String display = message == null ? "" : message.trim(); |
| | | if (display.isEmpty()) { |
| | | dikuaiData.remove(KEY_PATH_MESSAGE_TEXT); |
| | | dikuaiData.remove(KEY_PATH_MESSAGE_SUCCESS); |
| | | } else { |
| | | dikuaiData.put(KEY_PATH_MESSAGE_TEXT, display); |
| | | dikuaiData.put(KEY_PATH_MESSAGE_SUCCESS, success ? "true" : "false"); |
| | | } |
| | | pathGenerationMessageArea.setText(display); |
| | | Color borderColor = success ? PRIMARY_COLOR : ERROR_COLOR; |
| | | Color textColor = success ? PRIMARY_DARK : ERROR_COLOR; |
| | | Color backgroundColor = success ? PRIMARY_LIGHT : new Color(255, 235, 238); |
| | | pathGenerationMessageArea.setForeground(textColor); |
| | | pathMessageWrapper.setBackground(backgroundColor); |
| | | pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(borderColor, 1), |
| | | BorderFactory.createEmptyBorder(12, 12, 12, 12) |
| | | )); |
| | | pathMessageWrapper.setVisible(!display.isEmpty()); |
| | | pathMessageWrapper.revalidate(); |
| | | pathMessageWrapper.repaint(); |
| | | } |
| | | |
| | | private void setPathAvailability(boolean available) { |
| | | boolean effective = available && currentStep == 3; |
| | | if (createButton != null) { |
| | | createButton.setEnabled(effective); |
| | | } |
| | | if (previewButton != null) { |
| | | boolean visible = previewButton.isVisible(); |
| | | previewButton.setEnabled(effective && visible); |
| | | } |
| | | } |
| | | |
| | | private JPanel createButtonPanel() { |
| | | JPanel buttonPanel = new JPanel(); |
| | |
| | | buttonPanel.setBackground(WHITE); |
| | | buttonPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0)); |
| | | |
| | | prevButton = new JButton("上一步"); |
| | | prevButton = buttonset.createStyledButton("上一步", MEDIUM_GRAY); |
| | | prevButton.setFont(new Font("微软雅黑", Font.BOLD, 16)); |
| | | prevButton.setBackground(MEDIUM_GRAY); |
| | | prevButton.setForeground(TEXT_COLOR); |
| | | prevButton.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 25, 10, 25) |
| | | )); |
| | | prevButton.setFocusPainted(false); |
| | | prevButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | |
| | | nextButton = createPrimaryButton("下一步", 16); |
| | | createButton = createPrimaryButton("保存", 16); |
| | | createButton.setVisible(false); |
| | | createButton.setEnabled(false); |
| | | createButton.setEnabled(false); |
| | | |
| | | previewButton = createPrimaryButton("预览", 16); |
| | | previewButton.setVisible(false); |
| | | previewButton.setEnabled(false); |
| | | |
| | | previewButtonSpacer = Box.createHorizontalStrut(15); |
| | | previewButtonSpacer.setVisible(false); |
| | | |
| | | buttonPanel.add(prevButton); |
| | | buttonPanel.add(Box.createHorizontalGlue()); |
| | | buttonPanel.add(nextButton); |
| | | buttonPanel.add(previewButtonSpacer); |
| | | buttonPanel.add(previewButton); |
| | | buttonPanel.add(Box.createHorizontalStrut(15)); |
| | | buttonPanel.add(createButton); |
| | | |
| | |
| | | Dikuai dikuai = getOrCreatePendingDikuai(); |
| | | if (dikuai != null) { |
| | | dikuai.setBoundaryOriginalCoordinates(snapshot.originalBoundary); |
| | | dikuai.setBoundaryOriginalXY(snapshot.originalBoundaryXY); |
| | | dikuai.setBoundaryCoordinates(snapshot.optimizedBoundary); |
| | | dikuai.setLandArea(snapshot.areaSqMeters); |
| | | dikuai.setBaseStationCoordinates(snapshot.baseStationCoordinates); |
| | |
| | | } |
| | | |
| | | dikuaiData.put("boundaryOriginalCoordinates", snapshot.originalBoundary); |
| | | dikuaiData.put("boundaryOriginalXY", snapshot.originalBoundaryXY); |
| | | dikuaiData.put("boundaryCoordinates", snapshot.optimizedBoundary); |
| | | dikuaiData.put("landArea", snapshot.areaSqMeters); |
| | | dikuaiData.put("baseStationCoordinates", snapshot.baseStationCoordinates); |
| | | if (activeSession != null) { |
| | | activeSession.data.put("boundaryOriginalCoordinates", snapshot.originalBoundary); |
| | | activeSession.data.put("boundaryOriginalXY", snapshot.originalBoundaryXY); |
| | | activeSession.data.put("boundaryCoordinates", snapshot.optimizedBoundary); |
| | | activeSession.data.put("landArea", snapshot.areaSqMeters); |
| | | activeSession.data.put("baseStationCoordinates", snapshot.baseStationCoordinates); |
| | |
| | | return true; |
| | | } |
| | | |
| | | private static String buildOriginalBoundaryString() { |
| | | if (Coordinate.coordinates == null || Coordinate.coordinates.isEmpty()) { |
| | | private static List<Coordinate> sanitizeCoordinateList(List<Coordinate> source) { |
| | | if (source == null || source.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | List<Coordinate> snapshot = new ArrayList<>(); |
| | | for (Coordinate coordinate : source) { |
| | | if (coordinate != null) { |
| | | snapshot.add(coordinate); |
| | | } |
| | | } |
| | | if (snapshot.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | DecimalFormat latLonFormat = new DecimalFormat("0.000000"); |
| | | LinkedHashMap<String, Coordinate> unique = new LinkedHashMap<>(); |
| | | for (Coordinate coord : snapshot) { |
| | | double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection()); |
| | | double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection()); |
| | | String key = latLonFormat.format(lat) + "," + latLonFormat.format(lon); |
| | | unique.putIfAbsent(key, coord); |
| | | } |
| | | return new ArrayList<>(unique.values()); |
| | | } |
| | | |
| | | private static String buildOriginalBoundaryString(List<Coordinate> coordinates) { |
| | | if (coordinates == null || coordinates.isEmpty()) { |
| | | return "-1"; |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | DecimalFormat latLonFormat = new DecimalFormat("0.000000"); |
| | | DecimalFormat elevationFormat = new DecimalFormat("0.00"); |
| | | for (Coordinate coord : Coordinate.coordinates) { |
| | | for (Coordinate coord : coordinates) { |
| | | double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection()); |
| | | double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection()); |
| | | double elevation = coord.getElevation(); |
| | |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 构建原始边界XY坐标字符串(相对于基准站的XY坐标) |
| | | */ |
| | | private static String buildOriginalBoundaryXYString(List<Coordinate> coordinates, String baseStationCoordinates) { |
| | | if (coordinates == null || coordinates.isEmpty()) { |
| | | return "-1"; |
| | | } |
| | | if (baseStationCoordinates == null || baseStationCoordinates.trim().isEmpty() || "-1".equals(baseStationCoordinates.trim())) { |
| | | return "-1"; |
| | | } |
| | | |
| | | try { |
| | | // 解析基准站坐标 |
| | | String[] baseParts = baseStationCoordinates.split(","); |
| | | if (baseParts.length < 4) { |
| | | return "-1"; |
| | | } |
| | | double baseLat = publicway.Gpstoxuzuobiao.parseDMToDecimal(baseParts[0], baseParts[1]); |
| | | double baseLon = publicway.Gpstoxuzuobiao.parseDMToDecimal(baseParts[2], baseParts[3]); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | DecimalFormat xyFormat = new DecimalFormat("0.00"); |
| | | for (Coordinate coord : coordinates) { |
| | | double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection()); |
| | | double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection()); |
| | | |
| | | // 转换为XY坐标 |
| | | double[] xy = publicway.Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon); |
| | | |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(xyFormat.format(xy[0])).append(",").append(xyFormat.format(xy[1])); |
| | | } |
| | | return sb.toString(); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | return "-1"; |
| | | } |
| | | } |
| | | |
| | | private static double convertToDecimalDegree(String dmm, String direction) { |
| | | if (dmm == null || dmm.isEmpty()) { |
| | |
| | | return isMeaningfulValue(dikuaiData.get("plannedPath")); |
| | | } |
| | | |
| | | private String resolveObstaclePayloadFromConfig(String landNumber) { |
| | | if (!isMeaningfulValue(landNumber)) { |
| | | return null; |
| | | } |
| | | try { |
| | | File configFile = new File("Obstacledge.properties"); |
| | | if (!configFile.exists()) { |
| | | return null; |
| | | } |
| | | Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); |
| | | if (!manager.loadFromFile(configFile.getAbsolutePath())) { |
| | | return null; |
| | | } |
| | | Obstacledge.Plot plot = manager.getPlotById(landNumber.trim()); |
| | | if (plot == null) { |
| | | return null; |
| | | } |
| | | return Obstacledge.buildPlannerPayload(plot.getObstacles()); |
| | | } catch (Exception ex) { |
| | | System.err.println("加载障碍物配置失败: " + ex.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private static String normalizeCoordinateValue(String value) { |
| | | return isMeaningfulValue(value) ? value.trim() : null; |
| | | } |
| | |
| | | } |
| | | |
| | | private static BoundarySnapshotResult computeBoundarySnapshot() { |
| | | int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0; |
| | | if (count < 3) { |
| | | List<Coordinate> uniqueCoordinates; |
| | | synchronized (Coordinate.coordinates) { |
| | | uniqueCoordinates = sanitizeCoordinateList(Coordinate.coordinates); |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.coordinates.addAll(uniqueCoordinates); |
| | | } |
| | | |
| | | if (uniqueCoordinates.size() < 3) { |
| | | return BoundarySnapshotResult.failure("采集的边界点不足,无法生成地块边界", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | |
| | |
| | | return BoundarySnapshotResult.failure("当前地块面积为0,无法继续", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | |
| | | Device device = new Device(); |
| | | device.initFromProperties(); |
| | | String baseStationCoordinates = normalizeCoordinateValue(device.getBaseStationCoordinates()); |
| | | BaseStation baseStation = new BaseStation(); |
| | | baseStation.load(); |
| | | String baseStationCoordinates = normalizeCoordinateValue(baseStation.getInstallationCoordinates()); |
| | | if (!isMeaningfulValue(baseStationCoordinates)) { |
| | | return BoundarySnapshotResult.failure("未获取到有效的基准站坐标,请先在基准站管理中设置", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | |
| | | return BoundarySnapshotResult.failure("生成地块边界失败: " + ex.getMessage(), JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | |
| | | String originalBoundary = buildOriginalBoundaryString(); |
| | | String originalBoundary = buildOriginalBoundaryString(uniqueCoordinates); |
| | | String originalBoundaryXY = buildOriginalBoundaryXYString(uniqueCoordinates, baseStationCoordinates); |
| | | DecimalFormat areaFormat = new DecimalFormat("0.00"); |
| | | String areaString = areaFormat.format(area); |
| | | |
| | | return BoundarySnapshotResult.success(originalBoundary, optimizedBoundary, areaString, baseStationCoordinates); |
| | | return BoundarySnapshotResult.success(originalBoundary, originalBoundaryXY, optimizedBoundary, areaString, baseStationCoordinates); |
| | | } |
| | | |
| | | private String resolvePlannerMode(String patternDisplay) { |
| | |
| | | |
| | | // 创建地块按钮 |
| | | createButton.addActionListener(e -> createDikuai()); |
| | | |
| | | if (previewButton != null) { |
| | | previewButton.addActionListener(e -> previewMowingPath()); |
| | | } |
| | | |
| | | // 关闭对话框 |
| | | addWindowListener(new WindowAdapter() { |
| | |
| | | private void showStep(int step) { |
| | | currentStep = step; |
| | | cardLayout.show(stepsPanel, "step" + step); |
| | | |
| | | if (step == 1) { |
| | | updateObstacleSummary(); |
| | | } |
| | | |
| | | // 更新按钮状态 |
| | | updateButtonState(step); |
| | |
| | | if (step < 3) { |
| | | nextButton.setVisible(true); |
| | | createButton.setVisible(false); |
| | | createButton.setEnabled(false); |
| | | setPathAvailability(false); |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | previewButton.setEnabled(false); |
| | | } |
| | | if (previewButtonSpacer != null) { |
| | | previewButtonSpacer.setVisible(false); |
| | | } |
| | | } else { |
| | | nextButton.setVisible(false); |
| | | createButton.setVisible(true); |
| | | createButton.setEnabled(hasGeneratedPath()); |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(true); |
| | | } |
| | | if (previewButtonSpacer != null) { |
| | | previewButtonSpacer.setVisible(true); |
| | | } |
| | | setPathAvailability(hasGeneratedPath()); |
| | | } |
| | | |
| | | Container parent = prevButton.getParent(); |
| | |
| | | |
| | | case 3: |
| | | dikuaiData.put("mowingPattern", (String) mowingPatternCombo.getSelectedItem()); |
| | | dikuaiData.put("mowingWidth", mowingWidthSpinner.getValue().toString()); |
| | | |
| | | // 保存割草机割刀宽度 |
| | | if (mowingWidthField != null) { |
| | | String bladeWidthText = mowingWidthField.getText().trim(); |
| | | if (!bladeWidthText.isEmpty()) { |
| | | try { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthText); |
| | | dikuaiData.put("mowingBladeWidth", String.format(Locale.US, "%.2f", bladeWidthMeters)); |
| | | } catch (NumberFormatException e) { |
| | | dikuaiData.put("mowingBladeWidth", bladeWidthText); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存相邻行重叠距离 |
| | | if (overlapDistanceField != null) { |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | if (!overlapText.isEmpty()) { |
| | | try { |
| | | double overlapMeters = Double.parseDouble(overlapText); |
| | | dikuaiData.put("mowingOverlapDistance", String.format(Locale.US, "%.2f", overlapMeters)); |
| | | } catch (NumberFormatException e) { |
| | | dikuaiData.put("mowingOverlapDistance", overlapText); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 计算并保存割草宽度(割刀宽度 - 重叠距离) |
| | | if (mowingWidthField != null && overlapDistanceField != null) { |
| | | try { |
| | | String bladeWidthText = mowingWidthField.getText().trim(); |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthText); |
| | | double overlapMeters = Double.parseDouble(overlapText); |
| | | double calculatedWidthMeters = bladeWidthMeters - overlapMeters; |
| | | if (calculatedWidthMeters > 0) { |
| | | // 转换为厘米存储,保持与原有数据格式兼容 |
| | | int widthCm = (int) Math.round(calculatedWidthMeters * 100.0); |
| | | dikuaiData.put("mowingWidth", Integer.toString(widthCm)); |
| | | } |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 如果计算失败,不保存割草宽度 |
| | | } |
| | | } |
| | | |
| | | if (!hasGeneratedPath()) { |
| | | JOptionPane.showMessageDialog(this, "请先生成割草路径", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return false; |
| | |
| | | if (method != null) { |
| | | JPanel panel = drawingOptionPanels.get(method); |
| | | if (panel != null) { |
| | | selectDrawingOption(panel, method); |
| | | selectDrawingOption(panel, method, false); |
| | | } |
| | | } |
| | | |
| | |
| | | showStep(1); |
| | | hideBoundaryPointSummary(); |
| | | } |
| | | |
| | | restoreGeneratedPathState(session); |
| | | } |
| | | |
| | | private void restoreGeneratedPathState(DrawingSession session) { |
| | | if (session == null || session.data == null) { |
| | | showPathGenerationMessage("", true); |
| | | return; |
| | | } |
| | | |
| | | Map<String, String> data = session.data; |
| | | |
| | | if (mowingPatternCombo != null) { |
| | | String pattern = data.get("mowingPattern"); |
| | | if (pattern != null) { |
| | | ComboBoxModel<String> model = mowingPatternCombo.getModel(); |
| | | for (int i = 0; i < model.getSize(); i++) { |
| | | String candidate = model.getElementAt(i); |
| | | if (pattern.equals(candidate)) { |
| | | mowingPatternCombo.setSelectedIndex(i); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 恢复割草机割刀宽度(优先从mowingBladeWidth获取) |
| | | if (mowingWidthField != null) { |
| | | String bladeWidth = data.get("mowingBladeWidth"); |
| | | if (isMeaningfulValue(bladeWidth)) { |
| | | try { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidth.trim()); |
| | | mowingWidthField.setText(String.format(Locale.US, "%.2f", bladeWidthMeters)); |
| | | } catch (NumberFormatException ignored) { |
| | | // 如果mowingBladeWidth不存在或解析失败,尝试从mowingWidth恢复 |
| | | String width = data.get("mowingWidth"); |
| | | if (isMeaningfulValue(width)) { |
| | | try { |
| | | // 如果存储的是厘米,转换为米显示 |
| | | double parsed = Double.parseDouble(width.trim()); |
| | | // 假设如果值大于10,则是厘米,需要转换为米;否则已经是米 |
| | | double widthMeters = parsed > 10 ? parsed / 100.0 : parsed; |
| | | mowingWidthField.setText(String.format(Locale.US, "%.2f", widthMeters)); |
| | | } catch (NumberFormatException ignored2) { |
| | | // 保持当前值 |
| | | } |
| | | } |
| | | } |
| | | } else { |
| | | // 如果mowingBladeWidth不存在,尝试从mowingWidth恢复 |
| | | String width = data.get("mowingWidth"); |
| | | if (isMeaningfulValue(width)) { |
| | | try { |
| | | // 如果存储的是厘米,转换为米显示 |
| | | double parsed = Double.parseDouble(width.trim()); |
| | | // 假设如果值大于10,则是厘米,需要转换为米;否则已经是米 |
| | | double widthMeters = parsed > 10 ? parsed / 100.0 : parsed; |
| | | mowingWidthField.setText(String.format(Locale.US, "%.2f", widthMeters)); |
| | | } catch (NumberFormatException ignored) { |
| | | // 保持当前值 |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (overlapDistanceField != null) { |
| | | String overlap = data.get("mowingOverlapDistance"); |
| | | if (isMeaningfulValue(overlap)) { |
| | | try { |
| | | double overlapMeters = Double.parseDouble(overlap.trim()); |
| | | overlapDistanceField.setText(String.format(Locale.US, "%.2f", overlapMeters)); |
| | | } catch (NumberFormatException ignored) { |
| | | // 保持当前值 |
| | | } |
| | | } |
| | | } |
| | | |
| | | boolean hasPath = isMeaningfulValue(data.get("plannedPath")); |
| | | if (!hasPath) { |
| | | showPathGenerationMessage("", true); |
| | | if (currentStep == 3) { |
| | | setPathAvailability(false); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | String message = data.get(KEY_PATH_MESSAGE_TEXT); |
| | | boolean success = !"false".equalsIgnoreCase(data.get(KEY_PATH_MESSAGE_SUCCESS)); |
| | | showStep(3); |
| | | if (isMeaningfulValue(message)) { |
| | | showPathGenerationMessage(message, success); |
| | | } else { |
| | | showPathGenerationMessage("已生成割草路径,可点击“预览”按钮查看效果。", true); |
| | | } |
| | | setPathAvailability(true); |
| | | } |
| | | |
| | | public static void finishDrawingSession() { |
| | |
| | | BoundarySnapshotResult snapshot = computeBoundarySnapshot(); |
| | | if (snapshot.success && activeSession != null) { |
| | | activeSession.data.put("boundaryOriginalCoordinates", snapshot.originalBoundary); |
| | | activeSession.data.put("boundaryOriginalXY", snapshot.originalBoundaryXY); |
| | | activeSession.data.put("boundaryCoordinates", snapshot.optimizedBoundary); |
| | | activeSession.data.put("landArea", snapshot.areaSqMeters); |
| | | activeSession.data.put("baseStationCoordinates", snapshot.baseStationCoordinates); |
| | |
| | | Component parent = shouye != null ? shouye : null; |
| | | showAddDikuaiDialog(parent); |
| | | } |
| | | |
| | | public static void resumeFromPreview() { |
| | | Shouye shouye = Shouye.getInstance(); |
| | | if (shouye != null) { |
| | | shouye.exitMowingPathPreview(); |
| | | } |
| | | if (activeSession == null) { |
| | | return; |
| | | } |
| | | resumeRequested = true; |
| | | Component parent = shouye != null ? shouye : null; |
| | | SwingUtilities.invokeLater(() -> showAddDikuaiDialog(parent)); |
| | | } |
| | | |
| | | private void createDikuai() { |
| | | if (!validateCurrentStep()) { |
| | |
| | | if (dikuaiData.containsKey("boundaryOriginalCoordinates")) { |
| | | dikuai.setBoundaryOriginalCoordinates(dikuaiData.get("boundaryOriginalCoordinates")); |
| | | } |
| | | if (dikuaiData.containsKey("boundaryOriginalXY")) { |
| | | dikuai.setBoundaryOriginalXY(dikuaiData.get("boundaryOriginalXY")); |
| | | } |
| | | if (dikuaiData.containsKey("boundaryCoordinates")) { |
| | | dikuai.setBoundaryCoordinates(dikuaiData.get("boundaryCoordinates")); |
| | | } |
| | |
| | | if (dikuaiData.containsKey("mowingPattern")) { |
| | | dikuai.setMowingPattern(dikuaiData.get("mowingPattern")); |
| | | } |
| | | |
| | | // 保存割草机割刀宽度(优先从dikuaiData获取,否则从TextField获取) |
| | | if (dikuaiData.containsKey("mowingBladeWidth")) { |
| | | dikuai.setMowingBladeWidth(dikuaiData.get("mowingBladeWidth")); |
| | | } else if (mowingWidthField != null) { |
| | | String bladeWidthText = mowingWidthField.getText().trim(); |
| | | if (!bladeWidthText.isEmpty()) { |
| | | try { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthText); |
| | | dikuai.setMowingBladeWidth(String.format(Locale.US, "%.2f", bladeWidthMeters)); |
| | | } catch (NumberFormatException e) { |
| | | dikuai.setMowingBladeWidth(bladeWidthText); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存相邻行重叠距离(优先从dikuaiData获取,否则从TextField获取) |
| | | if (dikuaiData.containsKey("mowingOverlapDistance")) { |
| | | dikuai.setMowingOverlapDistance(dikuaiData.get("mowingOverlapDistance")); |
| | | } else if (overlapDistanceField != null) { |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | if (!overlapText.isEmpty()) { |
| | | try { |
| | | double overlapMeters = Double.parseDouble(overlapText); |
| | | dikuai.setMowingOverlapDistance(String.format(Locale.US, "%.2f", overlapMeters)); |
| | | } catch (NumberFormatException e) { |
| | | dikuai.setMowingOverlapDistance(overlapText); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存割草宽度(计算后的值,优先从dikuaiData获取) |
| | | if (dikuaiData.containsKey("mowingWidth")) { |
| | | dikuai.setMowingWidth(dikuaiData.get("mowingWidth")); |
| | | } else { |
| | | // 如果没有在dikuaiData中,则从TextField计算 |
| | | if (mowingWidthField != null && overlapDistanceField != null) { |
| | | try { |
| | | String bladeWidthText = mowingWidthField.getText().trim(); |
| | | String overlapText = overlapDistanceField.getText().trim(); |
| | | if (!bladeWidthText.isEmpty() && !overlapText.isEmpty()) { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthText); |
| | | double overlapMeters = Double.parseDouble(overlapText); |
| | | double calculatedWidthMeters = bladeWidthMeters - overlapMeters; |
| | | if (calculatedWidthMeters > 0) { |
| | | // 转换为厘米存储,保持与原有数据格式兼容 |
| | | int widthCm = (int) Math.round(calculatedWidthMeters * 100.0); |
| | | dikuai.setMowingWidth(Integer.toString(widthCm)); |
| | | } |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 如果计算失败,保持原有值或使用默认值 |
| | | } |
| | | } |
| | | } |
| | | |
| | | String plannedPath = dikuaiData.get("plannedPath"); |
| | | if (isMeaningfulValue(plannedPath)) { |
| | | dikuai.setPlannedPath(plannedPath); |
| | | } |
| | | |
| | | // 保存割草安全距离(优先从dikuaiData获取,否则从TextField获取) |
| | | if (dikuaiData.containsKey("mowingSafetyDistance")) { |
| | | dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance")); |
| | | } else if (mowingSafetyDistanceField != null) { |
| | | String safetyDistanceText = mowingSafetyDistanceField.getText().trim(); |
| | | if (!safetyDistanceText.isEmpty()) { |
| | | try { |
| | | double safetyDistanceMeters = Double.parseDouble(safetyDistanceText); |
| | | dikuai.setMowingSafetyDistance(String.format(Locale.US, "%.2f", safetyDistanceMeters)); |
| | | } catch (NumberFormatException e) { |
| | | dikuai.setMowingSafetyDistance(safetyDistanceText); |
| | | } |
| | | } |
| | | } |
| | | |
| | | Dikuai.putDikuai(landNumber, dikuai); |
| | | Dikuai.saveToProperties(); |