| | |
| | | package zhangaiwu; |
| | | |
| | | import javax.swing.*; |
| | | |
| | | import java.awt.*; |
| | | import java.awt.event.*; |
| | | import java.io.File; |
| | |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.text.DecimalFormat; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | |
| | | 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 bianjie.bianjieguihua2; |
| | | import dikuai.Gecaokuanjisuan; |
| | | import dikuai.Gecaoanquanjuli; |
| | | import bianjie.Bianjieyouhuatoxy; |
| | | import lujing.Lunjingguihua; |
| | | import set.Setsys; |
| | | import ui.UIConfig; |
| | | import zhuye.MowerLocationData; |
| | | import zhuye.Shouye; |
| | | import zhuye.Coordinate; |
| | | import publicway.buttonset; |
| | | |
| | | /** |
| | | * 新增地块对话框 - 多步骤表单设计 |
| | |
| | | private JTextField landNumberField; |
| | | private JTextField areaNameField; |
| | | private JComboBox<String> mowingPatternCombo; |
| | | private JSpinner mowingWidthSpinner; |
| | | private JTextField bladeWidthField; // 割草机割刀宽度(m) |
| | | private JTextField mowingWidthField; // 割草宽度(m,可编辑) |
| | | private JTextField safetyDistanceField; // 割草安全距离(m,可编辑) |
| | | private JPanel previewPanel; |
| | | private Map<String, JPanel> drawingOptionPanels = new HashMap<>(); |
| | | |
| | |
| | | private JButton prevButton; |
| | | private JButton nextButton; |
| | | private JButton createButton; |
| | | private JButton previewButton; |
| | | private JButton previewButton; // 步骤3的预览按钮(预览割草路径) |
| | | private JButton boundaryPreviewButton; // 步骤2的预览按钮(预览边界) |
| | | private Component previewButtonSpacer; |
| | | private JLabel boundaryCountLabel; |
| | | private JTextArea boundaryXYTextArea; // 显示边界XY坐标的文本域 |
| | | private JScrollPane boundaryXYScrollPane; // 边界XY坐标文本域的滚动面板 |
| | | private JPanel obstacleListContainer; |
| | | private JTextArea pathGenerationMessageArea; |
| | | private JPanel pathMessageWrapper; |
| | |
| | | )); |
| | | areaNameField.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | // 添加输入框焦点效果 |
| | | // 添加输入框焦点效果和文本变化监听 |
| | | areaNameField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(12, 15, 12, 15) |
| | | )); |
| | | // 更新下一步按钮状态 |
| | | updateStep1ButtonState(); |
| | | } |
| | | }); |
| | | |
| | | // 添加文本变化监听,实时更新按钮状态 |
| | | areaNameField.addKeyListener(new KeyAdapter() { |
| | | @Override |
| | | public void keyReleased(KeyEvent e) { |
| | | updateStep1ButtonState(); |
| | | } |
| | | }); |
| | | |
| | |
| | | startEndDrawingBtn.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | startEndDrawingBtn.setMaximumSize(new Dimension(400, 55)); |
| | | startEndDrawingBtn.setEnabled(false); // 初始不可用 |
| | | startEndDrawingBtn.setBackground(MEDIUM_GRAY); // 初始灰色背景 |
| | | |
| | | startEndDrawingBtn.addActionListener(e -> toggleDrawing()); |
| | | |
| | | stepPanel.add(startEndDrawingBtn); |
| | | |
| | | // 边界XY坐标显示文本域(3行,带滚动条,在已完成按钮下方) |
| | | boundaryXYTextArea = new JTextArea(3, 0); |
| | | boundaryXYTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 12)); |
| | | boundaryXYTextArea.setForeground(TEXT_COLOR); |
| | | boundaryXYTextArea.setEditable(false); |
| | | boundaryXYTextArea.setBackground(LIGHT_GRAY); |
| | | boundaryXYTextArea.setLineWrap(true); |
| | | boundaryXYTextArea.setWrapStyleWord(true); |
| | | boundaryXYTextArea.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | boundaryXYTextArea.setVisible(false); |
| | | |
| | | // 创建滚动面板,默认显示3行 |
| | | boundaryXYScrollPane = new JScrollPane(boundaryXYTextArea); |
| | | boundaryXYScrollPane.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 1), |
| | | BorderFactory.createEmptyBorder(0, 0, 0, 0) |
| | | )); |
| | | boundaryXYScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
| | | boundaryXYScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); |
| | | boundaryXYScrollPane.getVerticalScrollBar().setUnitIncrement(16); |
| | | boundaryXYScrollPane.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | boundaryXYScrollPane.setVisible(false); |
| | | boundaryXYScrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, 80)); |
| | | |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 15))); |
| | | stepPanel.add(boundaryXYScrollPane); |
| | | |
| | | boundaryCountLabel = new JLabel("已采集到边界点0个"); |
| | | boundaryCountLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | boundaryCountLabel.setForeground(LIGHT_TEXT); |
| | |
| | | if (!optionPanel.isEnabled()) { |
| | | return; |
| | | } |
| | | selectDrawingOption(optionPanel, type); |
| | | startEndDrawingBtn.setEnabled(true); // 选择后启用按钮 |
| | | if (selectDrawingOption(optionPanel, type, true)) { |
| | | updateStartDrawingButtonState(); // 选择后更新按钮状态 |
| | | } |
| | | } |
| | | |
| | | @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() { |
| | |
| | | // 用户在对话框内主动结束(未触发外部流程) |
| | | isDrawing = false; |
| | | Coordinate.setStartSaveGngga(false); |
| | | startEndDrawingBtn.setText("开始绘制"); |
| | | startEndDrawingBtn.setBackground(PRIMARY_COLOR); |
| | | updateOtherOptionsState(false); |
| | | startEndDrawingBtn.setText("已完成"); |
| | | startEndDrawingBtn.setBackground(MEDIUM_GRAY); |
| | | startEndDrawingBtn.setEnabled(false); |
| | | updateOtherOptionsState(true); |
| | | |
| | | // 优化边界 |
| | | optimizeBoundaryAndSave(); |
| | | |
| | | dikuaiData.put("boundaryDrawn", "true"); |
| | | JOptionPane.showMessageDialog(this, "边界绘制已完成", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | showBoundaryPointSummary(); |
| | | updateBoundaryXYDisplay(); |
| | | // 更新预览和下一步按钮状态(背景颜色变绿色,可点击) |
| | | updateStep2ButtonsAfterDrawing(); |
| | | } |
| | | } |
| | | |
| | |
| | | hideBoundaryPointSummary(); |
| | | } |
| | | |
| | | /** |
| | | * 优化边界并保存优化后的数据 |
| | | */ |
| | | private void optimizeBoundaryAndSave() { |
| | | try { |
| | | // 获取基准站坐标 |
| | | BaseStation baseStation = new BaseStation(); |
| | | baseStation.load(); |
| | | String baseStationCoordinates = normalizeCoordinateValue(baseStation.getInstallationCoordinates()); |
| | | if (!isMeaningfulValue(baseStationCoordinates)) { |
| | | System.err.println("未获取到有效的基准站坐标,无法优化边界"); |
| | | return; |
| | | } |
| | | |
| | | // 获取原始坐标列表 |
| | | List<Coordinate> uniqueCoordinates; |
| | | synchronized (Coordinate.coordinates) { |
| | | uniqueCoordinates = sanitizeCoordinateList(Coordinate.coordinates); |
| | | } |
| | | |
| | | if (uniqueCoordinates.size() < 3) { |
| | | System.err.println("采集的边界点不足,无法优化边界"); |
| | | return; |
| | | } |
| | | |
| | | // 构建边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)" |
| | | String boundaryStr = buildBoundaryStringForOptimization(uniqueCoordinates); |
| | | |
| | | // 调用优化方法 |
| | | String optimizedXYStr = Bianjieyouhuatoxy.optimizeBoundary(baseStationCoordinates, boundaryStr); |
| | | |
| | | // 保存优化后的XY坐标字符串 |
| | | if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) { |
| | | dikuaiData.put("optimizedBoundaryXY", optimizedXYStr); |
| | | if (activeSession != null) { |
| | | activeSession.data.put("optimizedBoundaryXY", optimizedXYStr); |
| | | } |
| | | } else { |
| | | System.err.println("边界优化失败: " + optimizedXYStr); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | System.err.println("优化边界时发生错误: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 构建用于优化的边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)" |
| | | * 其中lat和lon是度分格式(DMM格式),例如 "3949.89151752" |
| | | */ |
| | | private static String buildBoundaryStringForOptimization(List<Coordinate> coordinates) { |
| | | if (coordinates == null || coordinates.isEmpty()) { |
| | | return "()"; |
| | | } |
| | | StringBuilder sb = new StringBuilder("("); |
| | | DecimalFormat elevationFormat = new DecimalFormat("0.00"); |
| | | for (int i = 0; i < coordinates.size(); i++) { |
| | | Coordinate coord = coordinates.get(i); |
| | | // Coordinate类中的getLatitude()和getLongitude()已经返回度分格式(DMM格式) |
| | | String latDMM = coord.getLatitude(); |
| | | String lonDMM = coord.getLongitude(); |
| | | double elevation = coord.getElevation(); |
| | | |
| | | if (i > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(latDMM).append(",") |
| | | .append(lonDMM).append(",") |
| | | .append(elevationFormat.format(elevation)); |
| | | } |
| | | sb.append(")"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | private JPanel createStep3Panel() { |
| | | JPanel stepPanel = new JPanel(); |
| | | stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS)); |
| | |
| | | settingsPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | // 割草模式选择 |
| | | JPanel patternPanel = createFormGroup("割草模式", "选择割草路径的生成模式"); |
| | | JPanel patternPanel = createFormGroup("割草模式", ""); |
| | | mowingPatternCombo = new JComboBox<>(new String[]{"平行线", "螺旋式"}); |
| | | mowingPatternCombo.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | mowingPatternCombo.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | |
| | | settingsPanel.add(patternPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | // 割草宽度设置 |
| | | JPanel widthPanel = createFormGroup("割草宽度", "设置割草机单次割草的宽度"); |
| | | JPanel widthInputPanel = new JPanel(new BorderLayout()); |
| | | widthInputPanel.setBackground(WHITE); |
| | | widthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | widthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | // 割草机割刀宽度设置 |
| | | JPanel bladeWidthPanel = createFormGroup("割草机割刀宽度", ""); |
| | | JPanel bladeWidthInputPanel = new JPanel(new BorderLayout()); |
| | | bladeWidthInputPanel.setBackground(WHITE); |
| | | bladeWidthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | bladeWidthInputPanel.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( |
| | | bladeWidthField = new JTextField("0.50"); |
| | | bladeWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | bladeWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | |
| | | // 添加微调器焦点效果 |
| | | mowingWidthSpinner.addFocusListener(new FocusAdapter() { |
| | | bladeWidthField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | | editor.getTextField().setBorder(BorderFactory.createCompoundBorder( |
| | | bladeWidthField.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( |
| | | bladeWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | updateMowingWidthAndSafetyDistance(); |
| | | } |
| | | }); |
| | | |
| | | JLabel bladeUnitLabel = new JLabel("m"); |
| | | bladeUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | bladeUnitLabel.setForeground(LIGHT_TEXT); |
| | | bladeUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | |
| | | bladeWidthInputPanel.add(bladeWidthField, BorderLayout.CENTER); |
| | | bladeWidthInputPanel.add(bladeUnitLabel, BorderLayout.EAST); |
| | | |
| | | bladeWidthPanel.add(bladeWidthInputPanel); |
| | | settingsPanel.add(bladeWidthPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | // 割草宽度设置(可编辑) |
| | | JPanel widthPanel = createFormGroup("割草宽度", "计算方法:割草宽度 = 割刀宽 ×85%"); |
| | | JPanel widthInputPanel = new JPanel(new BorderLayout()); |
| | | widthInputPanel.setBackground(WHITE); |
| | | widthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | widthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | mowingWidthField = new JTextField(); |
| | | mowingWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | mowingWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | mowingWidthField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | | mowingWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | |
| | | @Override |
| | | public void focusLost(FocusEvent e) { |
| | | mowingWidthField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | }); |
| | | |
| | | JLabel unitLabel = new JLabel("厘米"); |
| | | JLabel unitLabel = new JLabel("m"); |
| | | unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | unitLabel.setForeground(LIGHT_TEXT); |
| | | unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | |
| | | widthInputPanel.add(mowingWidthSpinner, BorderLayout.CENTER); |
| | | widthInputPanel.add(mowingWidthField, BorderLayout.CENTER); |
| | | widthInputPanel.add(unitLabel, BorderLayout.EAST); |
| | | |
| | | widthPanel.add(widthInputPanel); |
| | | settingsPanel.add(widthPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | // 割草安全距离设置(可编辑) |
| | | JPanel safetyDistancePanel = createFormGroup("割草安全距离", "根据割草机尺寸自动计算的安全距离"); |
| | | JPanel safetyDistanceInputPanel = new JPanel(new BorderLayout()); |
| | | safetyDistanceInputPanel.setBackground(WHITE); |
| | | safetyDistanceInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | safetyDistanceInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | safetyDistanceField = new JTextField(); |
| | | safetyDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); |
| | | safetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | safetyDistanceField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | | safetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | |
| | | @Override |
| | | public void focusLost(FocusEvent e) { |
| | | safetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | }); |
| | | |
| | | JLabel safetyUnitLabel = new JLabel("m"); |
| | | safetyUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | safetyUnitLabel.setForeground(LIGHT_TEXT); |
| | | safetyUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | |
| | | safetyDistanceInputPanel.add(safetyDistanceField, BorderLayout.CENTER); |
| | | safetyDistanceInputPanel.add(safetyUnitLabel, BorderLayout.EAST); |
| | | |
| | | safetyDistancePanel.add(safetyDistanceInputPanel); |
| | | settingsPanel.add(safetyDistancePanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 25))); |
| | | |
| | | stepPanel.add(settingsPanel); |
| | | // 初始化计算值 |
| | | updateMowingWidthAndSafetyDistance(); |
| | | |
| | | // 将设置面板放入滚动面板 |
| | | JScrollPane scrollPane = new JScrollPane(settingsPanel); |
| | | scrollPane.setBorder(null); |
| | | scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
| | | scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); |
| | | scrollPane.getVerticalScrollBar().setUnitIncrement(16); |
| | | scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | stepPanel.add(scrollPane); |
| | | |
| | | JButton generatePathButton = createPrimaryButton("生成割草路径", 16); |
| | | generatePathButton.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | |
| | | nameLabel.setForeground(TEXT_COLOR); |
| | | nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | JLabel hintLabel = new JLabel(hint); |
| | | hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); |
| | | hintLabel.setForeground(LIGHT_TEXT); |
| | | hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | formGroup.add(nameLabel); |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 6))); |
| | | formGroup.add(hintLabel); |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | |
| | | // 只有当提示文字不为空时才显示 |
| | | if (hint != null && !hint.trim().isEmpty()) { |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 6))); |
| | | JLabel hintLabel = new JLabel(hint); |
| | | hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); |
| | | hintLabel.setForeground(LIGHT_TEXT); |
| | | hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | formGroup.add(hintLabel); |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | } else { |
| | | formGroup.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | } |
| | | |
| | | return formGroup; |
| | | } |
| | | |
| | | /** |
| | | * 根据割刀宽度更新割草宽度和安全距离 |
| | | */ |
| | | private void updateMowingWidthAndSafetyDistance() { |
| | | try { |
| | | String bladeWidthStr = bladeWidthField.getText().trim(); |
| | | if (bladeWidthStr == null || bladeWidthStr.isEmpty()) { |
| | | mowingWidthField.setText(""); |
| | | safetyDistanceField.setText(""); |
| | | return; |
| | | } |
| | | |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthStr); |
| | | if (bladeWidthMeters <= 0) { |
| | | mowingWidthField.setText(""); |
| | | safetyDistanceField.setText(""); |
| | | return; |
| | | } |
| | | |
| | | // 调用 Gecaokuanjisuan 类计算割草宽度(返回厘米,保留2位小数,需要转换为米) |
| | | double mowingWidthCm = Gecaokuanjisuan.calculateMowingWidth(bladeWidthMeters); |
| | | if (mowingWidthCm > 0) { |
| | | // 将厘米转换为米,并保留2位小数 |
| | | double mowingWidthMeters = mowingWidthCm / 100.0; |
| | | mowingWidthField.setText(String.format(Locale.US, "%.2f", mowingWidthMeters)); |
| | | } else { |
| | | mowingWidthField.setText(""); |
| | | } |
| | | |
| | | // 调用 Gecaoanquanjuli 类计算安全距离 |
| | | String safetyDistanceStr = Gecaoanquanjuli.calculateSafetyDistanceFromString(bladeWidthStr); |
| | | safetyDistanceField.setText(safetyDistanceStr); |
| | | |
| | | } catch (NumberFormatException e) { |
| | | mowingWidthField.setText(""); |
| | | safetyDistanceField.setText(""); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | mowingWidthField.setText(""); |
| | | safetyDistanceField.setText(""); |
| | | } |
| | | } |
| | | |
| | | private void generateMowingPath() { |
| | | if (!dikuaiData.containsKey("boundaryDrawn")) { |
| | | JOptionPane.showMessageDialog(this, "请先完成边界绘制后再生成路径", "提示", JOptionPane.WARNING_MESSAGE); |
| | |
| | | String patternDisplay = (String) mowingPatternCombo.getSelectedItem(); |
| | | dikuaiData.put("mowingPattern", patternDisplay); |
| | | |
| | | Object widthObj = mowingWidthSpinner.getValue(); |
| | | if (!(widthObj instanceof Number)) { |
| | | JOptionPane.showMessageDialog(this, "割草宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE); |
| | | // 获取割草宽度(从文本框,单位:米) |
| | | String mowingWidthStr = mowingWidthField.getText().trim(); |
| | | if (mowingWidthStr == null || mowingWidthStr.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "请先输入割草宽度", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("割草宽度输入无效,请重新输入。", false); |
| | | showPathGenerationMessage("请先输入割草宽度。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | double widthCm = ((Number) widthObj).doubleValue(); |
| | | if (widthCm <= 0) { |
| | | |
| | | double widthMeters; |
| | | try { |
| | | widthMeters = Double.parseDouble(mowingWidthStr); |
| | | } catch (NumberFormatException e) { |
| | | JOptionPane.showMessageDialog(this, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("割草宽度格式不正确,请重新输入。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | |
| | | if (widthMeters <= 0) { |
| | | JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | | showPathGenerationMessage("割草宽度必须大于0,请重新设置。", false); |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | dikuaiData.put("mowingWidth", widthObj.toString()); |
| | | |
| | | // 保存割草宽度(转换为厘米保存,保持与原有数据格式一致) |
| | | double widthCm = widthMeters * 100.0; |
| | | dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm)); |
| | | |
| | | String widthMeters = String.format(Locale.US, "%.2f", widthCm / 100.0); |
| | | // 获取安全距离 |
| | | String safetyDistanceStr = safetyDistanceField.getText().trim(); |
| | | double safetyDistanceMeters = Double.NaN; |
| | | if (safetyDistanceStr != null && !safetyDistanceStr.isEmpty()) { |
| | | try { |
| | | safetyDistanceMeters = Double.parseDouble(safetyDistanceStr); |
| | | } catch (NumberFormatException e) { |
| | | // 使用NaN,让系统自动计算 |
| | | } |
| | | } |
| | | |
| | | String widthMetersStr = String.format(Locale.US, "%.3f", widthMeters); |
| | | String safetyDistanceMetersStr = Double.isNaN(safetyDistanceMeters) ? null : String.format(Locale.US, "%.3f", safetyDistanceMeters); |
| | | String plannerMode = resolvePlannerMode(patternDisplay); |
| | | |
| | | try { |
| | | List<Lunjingguihua.PathSegment> segments = Lunjingguihua.generatePathSegments( |
| | | // 使用与路径规划页面相同的方法:Lunjingguihua.generatePathFromStrings |
| | | String plannedPath = Lunjingguihua.generatePathFromStrings( |
| | | boundaryCoords, |
| | | obstacleCoords, |
| | | widthMeters, |
| | | obstacleCoords != null ? obstacleCoords : "", |
| | | widthMetersStr, |
| | | safetyDistanceMetersStr, |
| | | plannerMode |
| | | ); |
| | | String plannedPath = Lunjingguihua.formatPathSegments(segments); |
| | | |
| | | if (!isMeaningfulValue(plannedPath)) { |
| | | JOptionPane.showMessageDialog(this, "生成割草路径失败: 生成结果为空", "错误", JOptionPane.ERROR_MESSAGE); |
| | | dikuaiData.remove("plannedPath"); |
| | |
| | | setPathAvailability(false); |
| | | return; |
| | | } |
| | | |
| | | if (isMeaningfulValue(boundaryCoords)) { |
| | | dikuaiData.put("boundaryCoordinates", boundaryCoords); |
| | | } |
| | |
| | | dikuaiData.put("plannedPath", plannedPath); |
| | | setPathAvailability(true); |
| | | showPathGenerationMessage( |
| | | "已根据当前设置生成割草路径,共生成 " + segments.size() + " 段。\n点击“预览”按钮可在主页面查看效果。", |
| | | "已根据当前设置生成割草路径。\n点击预览按钮可在主页面查看效果。", |
| | | true); |
| | | } catch (IllegalArgumentException ex) { |
| | | JOptionPane.showMessageDialog(this, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); |
| | |
| | | JOptionPane.showMessageDialog(this, "无法启动预览,请稍后再试", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 在步骤3预览时,不显示边界点圆圈 |
| | | if (shouye.getMapRenderer() != null) { |
| | | shouye.getMapRenderer().setBoundaryPointsVisible(false); |
| | | } |
| | | |
| | | closePreviewAndDispose(); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | if (mowingWidthSpinner != null) { |
| | | Object widthValue = mowingWidthSpinner.getValue(); |
| | | if (widthValue instanceof Number) { |
| | | int widthInt = ((Number) widthValue).intValue(); |
| | | dikuaiData.put("mowingWidth", Integer.toString(widthInt)); |
| | | } else if (widthValue != null) { |
| | | dikuaiData.put("mowingWidth", widthValue.toString()); |
| | | // 保存割草宽度(从文本框获取,单位:米,转换为厘米保存) |
| | | if (mowingWidthField != null) { |
| | | String widthStr = mowingWidthField.getText().trim(); |
| | | if (widthStr != null && !widthStr.isEmpty()) { |
| | | try { |
| | | double widthMeters = Double.parseDouble(widthStr); |
| | | double widthCm = widthMeters * 100.0; |
| | | dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm)); |
| | | } catch (NumberFormatException e) { |
| | | // 保持原值 |
| | | dikuaiData.put("mowingWidth", widthStr); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | 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) { |
| | | if (button.isEnabled()) { |
| | | button.setBackground(PRIMARY_DARK); |
| | | // 如果按钮可用,鼠标悬停时显示深绿色 |
| | | if (button.getBackground().equals(PRIMARY_COLOR)) { |
| | | button.setBackground(PRIMARY_DARK); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void mouseExited(MouseEvent e) { |
| | | if (button.isEnabled()) { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | // 如果按钮可用,鼠标离开时恢复绿色 |
| | | if (!button.getBackground().equals(MEDIUM_GRAY)) { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | } |
| | | } else { |
| | | // 如果按钮不可用,保持灰色 |
| | | button.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | return button; |
| | | } |
| | | |
| | |
| | | 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)); |
| | | |
| | | boundaryPreviewButton = createPrimaryButton("预览", 16); |
| | | boundaryPreviewButton.setVisible(false); |
| | | boundaryPreviewButton.setEnabled(false); |
| | | boundaryPreviewButton.addActionListener(e -> previewBoundary()); |
| | | |
| | | nextButton = createPrimaryButton("下一步", 16); |
| | | nextButton.setBackground(MEDIUM_GRAY); // 初始灰色背景 |
| | | nextButton.setEnabled(false); // 初始不可用 |
| | | |
| | | createButton = createPrimaryButton("保存", 16); |
| | | createButton.setVisible(false); |
| | | createButton.setEnabled(false); |
| | |
| | | |
| | | buttonPanel.add(prevButton); |
| | | buttonPanel.add(Box.createHorizontalGlue()); |
| | | buttonPanel.add(boundaryPreviewButton); |
| | | buttonPanel.add(Box.createHorizontalStrut(15)); |
| | | buttonPanel.add(nextButton); |
| | | buttonPanel.add(previewButtonSpacer); |
| | | buttonPanel.add(previewButton); |
| | |
| | | if (boundaryCountLabel == null) { |
| | | return; |
| | | } |
| | | int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0; |
| | | double area = jisuanmianjie.calculatePolygonArea(); |
| | | DecimalFormat areaFormat = new DecimalFormat("0.00"); |
| | | boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaFormat.format(area) + "㎡"); |
| | | |
| | | // 使用优化后的边界数据计算点数和面积 |
| | | String optimizedXYStr = dikuaiData.get("optimizedBoundaryXY"); |
| | | if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) { |
| | | int count = Bianjieyouhuatoxy.calculatePointCount(optimizedXYStr); |
| | | String areaStr = Bianjieyouhuatoxy.calculateArea(optimizedXYStr); |
| | | boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaStr + "㎡"); |
| | | } else { |
| | | // 如果没有优化后的数据,使用原始数据 |
| | | int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0; |
| | | double area = jisuanmianjie.calculatePolygonArea(); |
| | | DecimalFormat areaFormat = new DecimalFormat("0.00"); |
| | | boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaFormat.format(area) + "㎡"); |
| | | } |
| | | boundaryCountLabel.setVisible(true); |
| | | } |
| | | |
| | |
| | | dikuai.setLandArea(snapshot.areaSqMeters); |
| | | dikuai.setBaseStationCoordinates(snapshot.baseStationCoordinates); |
| | | dikuai.setUpdateTime(getCurrentTime()); |
| | | // 计算并设置原始边界XY坐标 |
| | | String originalBoundaryXY = convertOriginalBoundaryToXY(snapshot.originalBoundary, snapshot.baseStationCoordinates); |
| | | if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) { |
| | | dikuai.setBoundaryOriginalXY(originalBoundaryXY); |
| | | } |
| | | Dikuai.putDikuai(landNumber, dikuai); |
| | | } |
| | | |
| | |
| | | } |
| | | return dikuai; |
| | | } |
| | | |
| | | /** |
| | | * 将原始边界坐标(经纬度格式)转换为XY坐标 |
| | | * @param originalBoundary 原始边界坐标字符串,格式:"lat1,lon1,alt1;lat2,lon2,alt2;..." |
| | | * @param baseStationCoordinates 基准站坐标,格式:"lat,N/S,lon,E/W" |
| | | * @return XY坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." 如果转换失败返回null |
| | | */ |
| | | private static String convertOriginalBoundaryToXY(String originalBoundary, String baseStationCoordinates) { |
| | | if (originalBoundary == null || originalBoundary.trim().isEmpty() || "-1".equals(originalBoundary.trim())) { |
| | | return null; |
| | | } |
| | | if (baseStationCoordinates == null || baseStationCoordinates.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | // 解析基准站坐标 |
| | | String[] baseParts = baseStationCoordinates.trim().split(","); |
| | | if (baseParts.length != 4) { |
| | | return null; |
| | | } |
| | | double baseLat = convertToDecimalDegree(baseParts[0], baseParts[1]); |
| | | double baseLon = convertToDecimalDegree(baseParts[2], baseParts[3]); |
| | | |
| | | // 解析原始边界坐标 |
| | | String[] points = originalBoundary.split(";"); |
| | | StringBuilder xyStr = new StringBuilder(); |
| | | |
| | | for (int i = 0; i < points.length; i++) { |
| | | String point = points[i].trim(); |
| | | if (point.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | String[] coords = point.split(","); |
| | | if (coords.length >= 2) { |
| | | try { |
| | | double lat = Double.parseDouble(coords[0].trim()); |
| | | double lon = Double.parseDouble(coords[1].trim()); |
| | | |
| | | // 转换为XY坐标 |
| | | double[] xy = publicway.Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon); |
| | | if (xy != null && xy.length >= 2) { |
| | | if (xyStr.length() > 0) { |
| | | xyStr.append(";"); |
| | | } |
| | | xyStr.append(String.format(Locale.US, "%.3f,%.3f", xy[0], xy[1])); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 跳过无效的坐标点 |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return xyStr.length() > 0 ? xyStr.toString() : null; |
| | | } catch (Exception e) { |
| | | System.err.println("转换原始边界坐标到XY失败: " + e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 预览边界 |
| | | */ |
| | | private void previewBoundary() { |
| | | if (!dikuaiData.containsKey("boundaryDrawn")) { |
| | | JOptionPane.showMessageDialog(this, "请先完成边界绘制后再预览", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 获取或创建地块对象 |
| | | String landNumber = getPendingLandNumber(); |
| | | Dikuai dikuai = getOrCreatePendingDikuai(); |
| | | if (dikuai == null) { |
| | | JOptionPane.showMessageDialog(this, "无法获取地块信息", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 确保地块数据是最新的 |
| | | String optimizedBoundaryXY = dikuaiData.get("optimizedBoundaryXY"); |
| | | if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || optimizedBoundaryXY.startsWith("ERROR")) { |
| | | // 如果没有优化后的边界,尝试从boundaryCoordinates获取 |
| | | String boundaryCoords = dikuaiData.get("boundaryCoordinates"); |
| | | if (boundaryCoords != null && !boundaryCoords.isEmpty() && !"-1".equals(boundaryCoords)) { |
| | | optimizedBoundaryXY = boundaryCoords; |
| | | } else { |
| | | // 尝试从地块对象获取 |
| | | optimizedBoundaryXY = dikuai.getBoundaryCoordinates(); |
| | | } |
| | | } |
| | | |
| | | if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || "-1".equals(optimizedBoundaryXY)) { |
| | | JOptionPane.showMessageDialog(this, "未找到有效的边界坐标,无法预览", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 确保原始边界XY坐标已计算 |
| | | String originalBoundaryXY = dikuai.getBoundaryOriginalXY(); |
| | | if (originalBoundaryXY == null || originalBoundaryXY.isEmpty() || "-1".equals(originalBoundaryXY)) { |
| | | // 计算原始边界XY坐标 |
| | | String originalBoundary = dikuaiData.get("boundaryOriginalCoordinates"); |
| | | String baseStationCoordinates = dikuaiData.get("baseStationCoordinates"); |
| | | if (originalBoundary != null && baseStationCoordinates != null) { |
| | | originalBoundaryXY = convertOriginalBoundaryToXY(originalBoundary, baseStationCoordinates); |
| | | if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) { |
| | | dikuai.setBoundaryOriginalXY(originalBoundaryXY); |
| | | Dikuai.putDikuai(landNumber, dikuai); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存会话快照 |
| | | captureSessionSnapshot(); |
| | | |
| | | // 创建final变量供lambda使用 |
| | | final String finalOptimizedBoundaryXY = optimizedBoundaryXY; |
| | | final Dikuai finalDikuai = dikuai; |
| | | |
| | | // 关闭对话框 |
| | | setVisible(false); |
| | | dispose(); |
| | | |
| | | // 调用首页显示预览(与边界管理页面逻辑一致) |
| | | SwingUtilities.invokeLater(() -> { |
| | | Shouye.showBoundaryPreview(finalDikuai, finalOptimizedBoundaryXY, () -> { |
| | | // 返回回调:重新打开新增地块对话框,并显示步骤2 |
| | | Component parent = Shouye.getInstance(); |
| | | if (parent != null) { |
| | | // 确保会话状态正确,以便返回时显示步骤2 |
| | | if (activeSession != null && activeSession.drawingCompleted) { |
| | | resumeRequested = true; |
| | | } |
| | | showAddDikuaiDialog(parent); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | private void hideBoundaryPointSummary() { |
| | | if (boundaryCountLabel != null) { |
| | | boundaryCountLabel.setVisible(false); |
| | | } |
| | | if (boundaryXYTextArea != null) { |
| | | boundaryXYTextArea.setVisible(false); |
| | | boundaryXYTextArea.setText(""); |
| | | } |
| | | if (boundaryXYScrollPane != null) { |
| | | boundaryXYScrollPane.setVisible(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新边界XY坐标显示 |
| | | */ |
| | | private void updateBoundaryXYDisplay() { |
| | | if (boundaryXYTextArea == null || boundaryXYScrollPane == null) { |
| | | return; |
| | | } |
| | | |
| | | // 获取优化后的边界XY坐标 |
| | | String optimizedXYStr = dikuaiData.get("optimizedBoundaryXY"); |
| | | if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) { |
| | | boundaryXYTextArea.setText(optimizedXYStr); |
| | | boundaryXYTextArea.setVisible(true); |
| | | boundaryXYScrollPane.setVisible(true); |
| | | } else { |
| | | // 如果没有优化后的数据,尝试从原始坐标优化并显示 |
| | | // 如果已经有原始坐标但还没有优化,则进行优化 |
| | | if (dikuaiData.containsKey("boundaryDrawn") && |
| | | (Coordinate.coordinates != null && !Coordinate.coordinates.isEmpty())) { |
| | | // 尝试优化边界 |
| | | optimizeBoundaryAndSave(); |
| | | // 再次获取优化后的坐标 |
| | | optimizedXYStr = dikuaiData.get("optimizedBoundaryXY"); |
| | | if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) { |
| | | boundaryXYTextArea.setText(optimizedXYStr); |
| | | boundaryXYTextArea.setVisible(true); |
| | | boundaryXYScrollPane.setVisible(true); |
| | | } else { |
| | | boundaryXYTextArea.setText("边界坐标数据未就绪"); |
| | | boundaryXYTextArea.setVisible(true); |
| | | boundaryXYScrollPane.setVisible(true); |
| | | } |
| | | } else { |
| | | boundaryXYTextArea.setText("边界坐标数据未就绪"); |
| | | boundaryXYTextArea.setVisible(true); |
| | | boundaryXYScrollPane.setVisible(true); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private boolean hasGeneratedPath() { |
| | |
| | | |
| | | String optimizedBoundary; |
| | | try { |
| | | optimizedBoundary = bianjieguihua2.processCoordinateListAuto(baseStationCoordinates); |
| | | } catch (RuntimeException ex) { |
| | | // 构建边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)" |
| | | String boundaryStr = buildBoundaryStringForOptimization(uniqueCoordinates); |
| | | // 调用 Bianjieyouhuatoxy.optimizeBoundary 方法 |
| | | optimizedBoundary = Bianjieyouhuatoxy.optimizeBoundary(baseStationCoordinates, boundaryStr); |
| | | if (optimizedBoundary == null || optimizedBoundary.isEmpty() || optimizedBoundary.startsWith("ERROR")) { |
| | | return BoundarySnapshotResult.failure("生成地块边界失败: 优化后的边界坐标无效", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | } catch (Exception ex) { |
| | | ex.printStackTrace(); |
| | | return BoundarySnapshotResult.failure("生成地块边界失败: " + ex.getMessage(), JOptionPane.ERROR_MESSAGE); |
| | | } |
| | |
| | | |
| | | if (step == 1) { |
| | | updateObstacleSummary(); |
| | | // 步骤1显示时,立即更新按钮状态 |
| | | SwingUtilities.invokeLater(() -> updateStep1ButtonState()); |
| | | } |
| | | |
| | | // 更新按钮状态 |
| | |
| | | if (previewButtonSpacer != null) { |
| | | previewButtonSpacer.setVisible(false); |
| | | } |
| | | // 步骤1:根据验证结果更新下一步按钮状态 |
| | | if (step == 1) { |
| | | updateStep1ButtonState(); |
| | | } |
| | | // 步骤2显示边界预览按钮 |
| | | if (step == 2) { |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setVisible(true); |
| | | // 根据是否完成边界绘制来设置按钮状态和背景颜色 |
| | | boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn"); |
| | | boundaryPreviewButton.setEnabled(boundaryDrawn); |
| | | if (boundaryDrawn) { |
| | | boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } else { |
| | | boundaryPreviewButton.setBackground(MEDIUM_GRAY); // 灰色背景 |
| | | } |
| | | } |
| | | // 更新下一步按钮状态(根据是否完成边界绘制) |
| | | boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn"); |
| | | nextButton.setEnabled(boundaryDrawn); |
| | | if (boundaryDrawn) { |
| | | nextButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } else { |
| | | nextButton.setBackground(MEDIUM_GRAY); // 灰色背景 |
| | | } |
| | | // 更新开始绘制按钮状态 |
| | | updateStartDrawingButtonState(); |
| | | } else { |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setVisible(false); |
| | | boundaryPreviewButton.setEnabled(false); |
| | | } |
| | | } |
| | | } else { |
| | | nextButton.setVisible(false); |
| | | createButton.setVisible(true); |
| | |
| | | if (previewButtonSpacer != null) { |
| | | previewButtonSpacer.setVisible(true); |
| | | } |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setVisible(false); |
| | | boundaryPreviewButton.setEnabled(false); |
| | | } |
| | | setPathAvailability(hasGeneratedPath()); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新步骤1的下一步按钮状态 |
| | | * 根据地块名称是否填写来设置按钮的启用状态和背景颜色 |
| | | */ |
| | | private void updateStep1ButtonState() { |
| | | if (nextButton == null || currentStep != 1) { |
| | | return; |
| | | } |
| | | |
| | | String name = areaNameField.getText().trim(); |
| | | boolean canProceed = !name.isEmpty(); |
| | | |
| | | nextButton.setEnabled(canProceed); |
| | | if (canProceed) { |
| | | // 可点击时:绿色背景 |
| | | nextButton.setBackground(PRIMARY_COLOR); |
| | | } else { |
| | | // 不可点击时:灰色背景 |
| | | nextButton.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新步骤2的开始绘制按钮状态 |
| | | * 根据是否选择了绘制方式来设置按钮的启用状态和背景颜色 |
| | | */ |
| | | private void updateStartDrawingButtonState() { |
| | | if (startEndDrawingBtn == null || currentStep != 2) { |
| | | return; |
| | | } |
| | | |
| | | boolean hasSelectedMethod = dikuaiData.containsKey("drawingMethod"); |
| | | boolean isDrawingActive = isDrawing; |
| | | |
| | | // 如果正在绘制,按钮状态由toggleDrawing方法控制 |
| | | if (isDrawingActive) { |
| | | return; |
| | | } |
| | | |
| | | // 如果已经完成绘制,按钮显示"已完成"且不可用 |
| | | boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn"); |
| | | if (boundaryDrawn) { |
| | | startEndDrawingBtn.setEnabled(false); |
| | | startEndDrawingBtn.setBackground(MEDIUM_GRAY); |
| | | return; |
| | | } |
| | | |
| | | startEndDrawingBtn.setEnabled(hasSelectedMethod); |
| | | if (hasSelectedMethod) { |
| | | // 已选择绘制方式:绿色背景,可点击 |
| | | startEndDrawingBtn.setBackground(PRIMARY_COLOR); |
| | | } else { |
| | | // 未选择绘制方式:灰色背景,不可点击 |
| | | startEndDrawingBtn.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新步骤2的预览和下一步按钮状态(在完成边界绘制后调用) |
| | | * 将按钮背景颜色设置为绿色,表示可以点击操作 |
| | | */ |
| | | private void updateStep2ButtonsAfterDrawing() { |
| | | if (currentStep != 2) { |
| | | return; |
| | | } |
| | | |
| | | // 更新预览按钮 |
| | | if (boundaryPreviewButton != null) { |
| | | boundaryPreviewButton.setEnabled(true); |
| | | boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } |
| | | |
| | | // 更新下一步按钮 |
| | | if (nextButton != null) { |
| | | nextButton.setEnabled(true); |
| | | nextButton.setBackground(PRIMARY_COLOR); // 绿色背景 |
| | | } |
| | | } |
| | | |
| | | private boolean validateCurrentStep() { |
| | | switch (currentStep) { |
| | | case 1: |
| | |
| | | |
| | | case 3: |
| | | dikuaiData.put("mowingPattern", (String) mowingPatternCombo.getSelectedItem()); |
| | | dikuaiData.put("mowingWidth", mowingWidthSpinner.getValue().toString()); |
| | | // 保存割草宽度(从文本框获取,单位:米,转换为厘米保存) |
| | | if (mowingWidthField != null) { |
| | | String widthStr = mowingWidthField.getText().trim(); |
| | | if (widthStr != null && !widthStr.isEmpty()) { |
| | | try { |
| | | double widthMeters = Double.parseDouble(widthStr); |
| | | double widthCm = widthMeters * 100.0; |
| | | dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm)); |
| | | } catch (NumberFormatException e) { |
| | | // 保持原值 |
| | | dikuaiData.put("mowingWidth", widthStr); |
| | | } |
| | | } |
| | | } |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | isDrawing = false; |
| | | showStep(2); |
| | | showBoundaryPointSummary(); |
| | | updateBoundaryXYDisplay(); |
| | | // 更新预览和下一步按钮状态(背景颜色变绿色,可点击) |
| | | updateStep2ButtonsAfterDrawing(); |
| | | } else { |
| | | if (startEndDrawingBtn != null) { |
| | | startEndDrawingBtn.setText("开始绘制"); |
| | |
| | | } |
| | | } |
| | | |
| | | if (mowingWidthSpinner != null) { |
| | | String width = data.get("mowingWidth"); |
| | | if (isMeaningfulValue(width)) { |
| | | try { |
| | | double parsed = Double.parseDouble(width.trim()); |
| | | SpinnerNumberModel model = (SpinnerNumberModel) mowingWidthSpinner.getModel(); |
| | | int min = ((Number) model.getMinimum()).intValue(); |
| | | int max = ((Number) model.getMaximum()).intValue(); |
| | | int rounded = (int) Math.round(parsed); |
| | | if (rounded < min) { |
| | | rounded = min; |
| | | } else if (rounded > max) { |
| | | rounded = max; |
| | | } |
| | | mowingWidthSpinner.setValue(rounded); |
| | | } catch (NumberFormatException ignored) { |
| | | // 保持当前值 |
| | | // 恢复割草宽度:从保存的割草宽度(厘米)转换为米并设置 |
| | | String width = data.get("mowingWidth"); |
| | | if (isMeaningfulValue(width) && mowingWidthField != null) { |
| | | try { |
| | | double mowingWidthCm = Double.parseDouble(width.trim()); |
| | | // 将厘米转换为米 |
| | | double mowingWidthMeters = mowingWidthCm / 100.0; |
| | | mowingWidthField.setText(String.format(Locale.US, "%.3f", mowingWidthMeters)); |
| | | } catch (NumberFormatException ignored) { |
| | | // 保持当前值 |
| | | } |
| | | } |
| | | |
| | | // 恢复割刀宽度:从保存的割草宽度反推割刀宽度并设置 |
| | | // 由于割草宽度 = 割刀宽度 * 0.85,所以割刀宽度 = 割草宽度 / 0.85 |
| | | if (isMeaningfulValue(width) && bladeWidthField != null) { |
| | | try { |
| | | double mowingWidthCm = Double.parseDouble(width.trim()); |
| | | // 将厘米转换为米,然后反推割刀宽度 |
| | | double mowingWidthMeters = mowingWidthCm / 100.0; |
| | | double bladeWidthMeters = mowingWidthMeters / 0.85; |
| | | bladeWidthField.setText(String.format(Locale.US, "%.3f", bladeWidthMeters)); |
| | | // 触发自动计算安全距离 |
| | | if (safetyDistanceField != null) { |
| | | String safetyDistanceStr = Gecaoanquanjuli.calculateSafetyDistanceFromString( |
| | | String.format(Locale.US, "%.3f", bladeWidthMeters)); |
| | | safetyDistanceField.setText(safetyDistanceStr); |
| | | } |
| | | } catch (NumberFormatException ignored) { |
| | | // 保持当前值 |
| | | } |
| | | } |
| | | |
| | |
| | | if (dikuaiData.containsKey("mowingPattern")) { |
| | | dikuai.setMowingPattern(dikuaiData.get("mowingPattern")); |
| | | } |
| | | if (dikuaiData.containsKey("mowingWidth")) { |
| | | |
| | | // 保存割草宽度(从文本框获取,单位:米,转换为厘米保存) |
| | | if (mowingWidthField != null) { |
| | | String mowingWidthStr = mowingWidthField.getText().trim(); |
| | | if (mowingWidthStr != null && !mowingWidthStr.isEmpty()) { |
| | | try { |
| | | double mowingWidthMeters = Double.parseDouble(mowingWidthStr); |
| | | // 转换为厘米保存 |
| | | double mowingWidthCm = mowingWidthMeters * 100.0; |
| | | dikuai.setMowingWidth(String.format(Locale.US, "%.2f", mowingWidthCm)); |
| | | } catch (NumberFormatException e) { |
| | | // 如果解析失败,尝试使用dikuaiData中的值 |
| | | if (dikuaiData.containsKey("mowingWidth")) { |
| | | dikuai.setMowingWidth(dikuaiData.get("mowingWidth")); |
| | | } |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingWidth")) { |
| | | dikuai.setMowingWidth(dikuaiData.get("mowingWidth")); |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingWidth")) { |
| | | dikuai.setMowingWidth(dikuaiData.get("mowingWidth")); |
| | | } |
| | | |
| | | // 保存割草机割刀宽度(从文本框获取,单位:米) |
| | | if (bladeWidthField != null) { |
| | | String bladeWidthStr = bladeWidthField.getText().trim(); |
| | | if (bladeWidthStr != null && !bladeWidthStr.isEmpty()) { |
| | | try { |
| | | double bladeWidthMeters = Double.parseDouble(bladeWidthStr); |
| | | // 保存为米,保留2位小数 |
| | | dikuai.setMowingBladeWidth(String.format(Locale.US, "%.2f", bladeWidthMeters)); |
| | | } catch (NumberFormatException e) { |
| | | // 解析失败时,保存原始字符串 |
| | | dikuai.setMowingBladeWidth(bladeWidthStr); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保存割草安全距离(从文本框获取,单位:米) |
| | | if (safetyDistanceField != null) { |
| | | String safetyDistanceStr = safetyDistanceField.getText().trim(); |
| | | if (safetyDistanceStr != null && !safetyDistanceStr.isEmpty()) { |
| | | try { |
| | | double safetyDistanceMeters = Double.parseDouble(safetyDistanceStr); |
| | | // 保存为米,保留2位小数 |
| | | String formattedValue = String.format(Locale.US, "%.2f", safetyDistanceMeters); |
| | | dikuai.setMowingSafetyDistance(formattedValue); |
| | | // 同时保存到dikuaiData中,以便后续使用 |
| | | dikuaiData.put("mowingSafetyDistance", formattedValue); |
| | | } catch (NumberFormatException e) { |
| | | // 解析失败时,保存原始字符串 |
| | | dikuai.setMowingSafetyDistance(safetyDistanceStr); |
| | | dikuaiData.put("mowingSafetyDistance", safetyDistanceStr); |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingSafetyDistance")) { |
| | | // 如果文本框为空,尝试从dikuaiData获取 |
| | | dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance")); |
| | | } |
| | | } else if (dikuaiData.containsKey("mowingSafetyDistance")) { |
| | | // 如果safetyDistanceField为null,从dikuaiData获取 |
| | | dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance")); |
| | | } |
| | | |
| | | // 保存割草路径坐标 |
| | | String plannedPath = dikuaiData.get("plannedPath"); |
| | | if (isMeaningfulValue(plannedPath)) { |
| | | dikuai.setPlannedPath(plannedPath); |
| | |
| | | if (resumeRequested && activeSession != null) { |
| | | dialog.applySessionData(activeSession); |
| | | resumeRequested = false; |
| | | // 如果会话已生成路径,优先显示步骤3 |
| | | if (activeSession.data != null && isMeaningfulValue(activeSession.data.get("plannedPath"))) { |
| | | dialog.showStep(3); |
| | | } else if (activeSession.drawingCompleted) { |
| | | // 如果会话已完成绘制,显示步骤2 |
| | | dialog.showStep(2); |
| | | } |
| | | } |
| | | |
| | | dialog.setVisible(true); |