| | |
| | | import java.awt.Font; |
| | | import java.awt.Image; |
| | | import java.awt.Window; |
| | | import java.awt.Container; |
| | | import java.awt.event.ActionListener; |
| | | import java.awt.event.MouseAdapter; |
| | | import java.awt.event.MouseEvent; |
| | | import java.awt.event.MouseListener; |
| | | import java.awt.event.WindowAdapter; |
| | | import java.awt.event.WindowEvent; |
| | | import java.io.File; |
| | |
| | | import set.Setsys; |
| | | import ui.UIConfig; |
| | | import zhuye.Coordinate; |
| | | import zhuye.MapRenderer; |
| | | import zhuye.Shouye; |
| | | import zhangaiwu.AddDikuai; |
| | | import zhangaiwu.Obstacledge; |
| | | import zhangaiwu.yulanzhangaiwu; |
| | | import zhuye.buttonset; |
| | | import bianjie.bianjieguihua2; |
| | | import bianjie.ThreePointCircle; |
| | | |
| | | /** |
| | | * 障碍物新增/编辑对话框。设计语言参考 {@link AddDikuai},支持通过实地绘制采集障碍物坐标。 |
| | |
| | | private JPanel selectedMethodPanel; |
| | | private JPanel selectedShapePanel; |
| | | private JButton drawButton; |
| | | private JButton generateBoundaryButton; // 生成障碍物边界按钮 |
| | | private JButton previewButton; // 预览按钮 |
| | | private JLabel drawingStatusLabel; |
| | | private JLabel boundaryStatusLabel; // 生成障碍物边界状态标签 |
| | | private JTextField obstacleNameField; |
| | | private JPanel existingObstacleListPanel; |
| | | private JPanel step1NextButtonRow; |
| | |
| | | } |
| | | |
| | | private JButton createInlineButton(String text) { |
| | | JButton button = new JButton(text); |
| | | JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR); |
| | | button.setFont(new Font("微软雅黑", Font.BOLD, 12)); |
| | | button.setForeground(WHITE); |
| | | button.setBackground(PRIMARY_COLOR); |
| | | button.setBorder(BorderFactory.createEmptyBorder(6, 16, 6, 16)); |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | button.setFocusPainted(false); |
| | | Dimension size = new Dimension(72, 28); |
| | | button.setPreferredSize(size); |
| | | button.setMinimumSize(size); |
| | |
| | | formData.remove("editingObstacleName"); |
| | | formData.remove("obstacleCoordinates"); |
| | | formData.remove("obstacleOriginalCoordinates"); |
| | | formData.remove("generatedBoundaryCoordinates"); // 清除生成的边界坐标 |
| | | if (obstacleNameField != null) { |
| | | obstacleNameField.setText(""); |
| | | } else { |
| | |
| | | } |
| | | updateDrawingStatus(); |
| | | updateSaveButtonState(); |
| | | updatePreviewButtonState(); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 24))); |
| | | |
| | | // 按钮容器:重新绘制 + 生成障碍物边界 |
| | | JPanel buttonRow = new JPanel(); |
| | | buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.X_AXIS)); |
| | | buttonRow.setOpaque(false); |
| | | buttonRow.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | drawButton = createPrimaryButton("开始绘制", 16); |
| | | drawButton.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | drawButton.addActionListener(e -> startDrawingWorkflow()); |
| | | stepPanel.add(drawButton); |
| | | buttonRow.add(drawButton); |
| | | |
| | | buttonRow.add(Box.createRigidArea(new Dimension(10, 0))); |
| | | |
| | | generateBoundaryButton = createPrimaryButton("生成障碍物边界", 16); |
| | | generateBoundaryButton.setVisible(false); // 初始隐藏,绘制完成后显示 |
| | | generateBoundaryButton.addActionListener(e -> generateObstacleBoundary()); |
| | | buttonRow.add(generateBoundaryButton); |
| | | |
| | | stepPanel.add(buttonRow); |
| | | |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 12))); |
| | | |
| | |
| | | drawingStatusLabel.setForeground(LIGHT_TEXT); |
| | | drawingStatusLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | stepPanel.add(drawingStatusLabel); |
| | | |
| | | // 添加生成障碍物边界状态标签 |
| | | stepPanel.add(Box.createRigidArea(new Dimension(0, 8))); |
| | | boundaryStatusLabel = new JLabel(""); |
| | | boundaryStatusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); |
| | | boundaryStatusLabel.setForeground(LIGHT_TEXT); |
| | | boundaryStatusLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | boundaryStatusLabel.setVisible(false); // 初始隐藏 |
| | | stepPanel.add(boundaryStatusLabel); |
| | | |
| | | stepPanel.add(Box.createVerticalGlue()); |
| | | return stepPanel; |
| | |
| | | |
| | | prevButton = createSecondaryButton("上一步"); |
| | | nextButton = createPrimaryButton("下一步", 16); |
| | | // 设置下一步按钮宽度为300像素 |
| | | nextButton.setPreferredSize(new Dimension(300, nextButton.getPreferredSize().height)); |
| | | nextButton.setMaximumSize(new Dimension(300, nextButton.getPreferredSize().height)); |
| | | |
| | | previewButton = createSecondaryButton("预览"); |
| | | previewButton.setVisible(false); // 初始隐藏,生成边界后显示 |
| | | previewButton.setOpaque(true); |
| | | previewButton.setContentAreaFilled(true); |
| | | previewButton.addActionListener(e -> previewObstacleBoundary()); |
| | | |
| | | saveButton = createPrimaryButton("保存", 16); |
| | | saveButton.setVisible(false); |
| | | |
| | | buttonPanel.add(prevButton); |
| | | buttonPanel.add(Box.createHorizontalGlue()); |
| | | buttonPanel.add(Box.createRigidArea(new Dimension(12, 0))); |
| | | buttonPanel.add(previewButton); |
| | | buttonPanel.add(Box.createRigidArea(new Dimension(10, 0))); |
| | | buttonPanel.add(saveButton); |
| | | |
| | | attachNextButtonToStep1Row(); |
| | |
| | | if (option == null) { |
| | | return; |
| | | } |
| | | if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) { |
| | | JOptionPane.showMessageDialog(this, "请先添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | if (selectedMethodPanel != null && selectedMethodPanel != option) { |
| | | resetOptionAppearance(selectedMethodPanel); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private boolean hasConfiguredHandheldMarker() { |
| | | String handheldId = Setsys.getPropertyValue("handheldMarkerId"); |
| | | return handheldId != null && !handheldId.trim().isEmpty(); |
| | | } |
| | | |
| | | private void selectShapeOption(JPanel option, String type, boolean userTriggered) { |
| | | if (option == null) { |
| | | return; |
| | |
| | | } |
| | | |
| | | activeDrawingShape = null; |
| | | |
| | | // 重新绘制时清除之前生成的边界坐标 |
| | | formData.remove("generatedBoundaryCoordinates"); |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | } |
| | | // 清除边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | boundaryStatusLabel.setText(""); |
| | | } |
| | | // 对于圆形障碍物,隐藏生成边界按钮 |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | boolean isCircle = "circle".equals(shapeKey); |
| | | if (isCircle && generateBoundaryButton != null) { |
| | | generateBoundaryButton.setVisible(false); |
| | | } |
| | | |
| | | String method = formData.get("drawingMethod"); |
| | | if (!isMeaningfulValue(method)) { |
| | |
| | | |
| | | activeDrawingShape = shape.toLowerCase(Locale.ROOT); |
| | | |
| | | Shouye shouyeInstance = Shouye.getInstance(); |
| | | if (shouyeInstance != null) { |
| | | shouyeInstance.setHandheldMowerIconActive("handheld".equalsIgnoreCase(method)); |
| | | MapRenderer renderer = shouyeInstance.getMapRenderer(); |
| | | if (renderer != null) { |
| | | renderer.clearIdleTrail(); |
| | | } |
| | | } |
| | | |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.setActiveDeviceIdFilter(deviceId); |
| | | Coordinate.setStartSaveGngga(true); |
| | |
| | | session.captureMessage = "无法根据采集的点生成圆,请确保选择了三个非共线的圆周点"; |
| | | return; |
| | | } |
| | | double[] radiusPoint = pickRadiusPoint(xyPoints, circle); |
| | | if (radiusPoint == null) { |
| | | session.captureSuccessful = false; |
| | | session.captureMessage = "采集的圆周点异常,无法生成圆"; |
| | | return; |
| | | } |
| | | // 使用计算出的半径生成一个真正的圆上点(圆心右侧的点),确保预览时计算的半径和结束绘制时的半径一致 |
| | | double radiusX = circle.centerX + circle.radius; |
| | | double radiusY = circle.centerY; |
| | | String result = String.format(Locale.US, "%.2f,%.2f;%.2f,%.2f", |
| | | circle.centerX, circle.centerY, radiusPoint[0], radiusPoint[1]); |
| | | circle.centerX, circle.centerY, radiusX, radiusY); |
| | | session.data.put("obstacleCoordinates", result); |
| | | session.captureSuccessful = true; |
| | | session.captureMessage = "已采集圆形障碍物,共 " + xyPoints.size() + " 个点"; |
| | |
| | | private void preloadData() { |
| | | formData.remove("obstacleCoordinates"); |
| | | formData.remove("obstacleOriginalCoordinates"); |
| | | formData.remove("generatedBoundaryCoordinates"); // 清除生成的边界坐标 |
| | | formData.remove("editingObstacleName"); |
| | | updateDrawingStatus(); |
| | | updatePreviewButtonState(); |
| | | // 清除边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | boundaryStatusLabel.setText(""); |
| | | } |
| | | } |
| | | |
| | | private void updateDrawingStatus() { |
| | |
| | | return; |
| | | } |
| | | String coords = formData.get("obstacleCoordinates"); |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | boolean isCircle = "circle".equals(shapeKey); |
| | | |
| | | if (isMeaningfulValue(coords)) { |
| | | int count = countCoordinatePairs(coords); |
| | | drawingStatusLabel.setText("已采集障碍物数据,点数:" + count); |
| | | if (isCircle) { |
| | | // 对于圆形障碍物,显示圆心坐标和半径 |
| | | String statusText = parseCircleStatusText(coords); |
| | | drawingStatusLabel.setText(statusText); |
| | | } else { |
| | | // 对于多边形障碍物,显示点数 |
| | | int count = countCoordinatePairs(coords); |
| | | drawingStatusLabel.setText("已采集障碍物数据,点数:" + count); |
| | | } |
| | | if (!drawingInProgress && drawButton != null) { |
| | | drawButton.setText("重新绘制"); |
| | | drawButton.setEnabled(true); |
| | | } |
| | | // 对于圆形障碍物,不显示"生成障碍物边界"按钮 |
| | | if (generateBoundaryButton != null) { |
| | | generateBoundaryButton.setVisible(!isCircle); |
| | | } |
| | | } else { |
| | | drawingStatusLabel.setText("尚未采集障碍物坐标"); |
| | | if (!drawingInProgress && drawButton != null) { |
| | | drawButton.setText("开始绘制"); |
| | | drawButton.setEnabled(true); |
| | | } |
| | | // 未绘制时隐藏"生成障碍物边界"按钮 |
| | | if (generateBoundaryButton != null) { |
| | | generateBoundaryButton.setVisible(false); |
| | | } |
| | | // 未生成边界时隐藏预览按钮 |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | } |
| | | // 隐藏边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | } |
| | | } |
| | | updateSaveButtonState(); |
| | | updatePreviewButtonState(); |
| | | } |
| | | |
| | | /** |
| | | * 更新边界状态标签 |
| | | */ |
| | | private void updateBoundaryStatusLabel(int pointCount) { |
| | | if (boundaryStatusLabel == null) { |
| | | return; |
| | | } |
| | | if (pointCount > 0) { |
| | | boundaryStatusLabel.setText("生成障碍物边界点数:" + pointCount); |
| | | boundaryStatusLabel.setVisible(true); |
| | | } else { |
| | | boundaryStatusLabel.setText(""); |
| | | boundaryStatusLabel.setVisible(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新预览按钮的显示状态 |
| | | */ |
| | | private void updatePreviewButtonState() { |
| | | if (previewButton == null) { |
| | | return; |
| | | } |
| | | |
| | | // 对于圆形障碍物,不显示预览按钮 |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | boolean isCircle = "circle".equals(shapeKey); |
| | | if (isCircle) { |
| | | previewButton.setVisible(false); |
| | | previewButton.setEnabled(false); |
| | | return; |
| | | } |
| | | |
| | | // 只有在生成边界后才显示预览按钮 |
| | | String generatedBoundary = formData.get("generatedBoundaryCoordinates"); |
| | | boolean hasGeneratedBoundary = isMeaningfulValue(generatedBoundary); |
| | | |
| | | if (hasGeneratedBoundary) { |
| | | // 生成边界后,重新创建绿色的预览按钮 |
| | | // 获取按钮的父容器和位置 |
| | | Container parent = previewButton.getParent(); |
| | | if (parent != null) { |
| | | // 保存原有的ActionListener |
| | | ActionListener[] listeners = previewButton.getActionListeners(); |
| | | |
| | | // 移除旧按钮 |
| | | parent.remove(previewButton); |
| | | |
| | | // 创建新的绿色预览按钮(使用主按钮样式) |
| | | previewButton = createPrimaryButton("预览", 16); |
| | | previewButton.setVisible(true); |
| | | previewButton.setEnabled(true); |
| | | |
| | | // 恢复ActionListener |
| | | for (ActionListener listener : listeners) { |
| | | previewButton.addActionListener(listener); |
| | | } |
| | | |
| | | // 添加到父容器(在保存按钮之前) |
| | | int previewIndex = -1; |
| | | Component[] components = parent.getComponents(); |
| | | for (int i = 0; i < components.length; i++) { |
| | | if (components[i] instanceof JButton) { |
| | | JButton btn = (JButton) components[i]; |
| | | if ("保存".equals(btn.getText())) { |
| | | previewIndex = i; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (previewIndex >= 0) { |
| | | parent.add(previewButton, previewIndex); |
| | | parent.add(Box.createRigidArea(new Dimension(10, 0)), previewIndex + 1); |
| | | } else { |
| | | // 如果找不到保存按钮,添加到末尾 |
| | | parent.add(Box.createRigidArea(new Dimension(12, 0))); |
| | | parent.add(previewButton); |
| | | parent.add(Box.createRigidArea(new Dimension(10, 0))); |
| | | } |
| | | |
| | | // 刷新布局 |
| | | parent.revalidate(); |
| | | parent.repaint(); |
| | | } |
| | | } else { |
| | | // 未生成边界时,隐藏按钮 |
| | | previewButton.setVisible(false); |
| | | previewButton.setEnabled(false); |
| | | } |
| | | } |
| | | |
| | | private int countCoordinatePairs(String coords) { |
| | |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | /** |
| | | * 解析圆形障碍物坐标,生成状态文本 |
| | | * 格式:centerX,centerY;radiusX,radiusY |
| | | * 返回:当前采集圆形圆心坐标x,y,半径n米 |
| | | */ |
| | | private String parseCircleStatusText(String coords) { |
| | | if (!isMeaningfulValue(coords)) { |
| | | return "尚未采集圆形障碍物坐标"; |
| | | } |
| | | |
| | | try { |
| | | String[] pairs = coords.split(";"); |
| | | if (pairs.length < 2) { |
| | | return "圆形障碍物坐标格式错误"; |
| | | } |
| | | |
| | | // 解析圆心坐标 |
| | | String[] centerParts = pairs[0].trim().split(","); |
| | | if (centerParts.length < 2) { |
| | | return "圆形障碍物坐标格式错误"; |
| | | } |
| | | double centerX = Double.parseDouble(centerParts[0].trim()); |
| | | double centerY = Double.parseDouble(centerParts[1].trim()); |
| | | |
| | | // 解析圆上一点坐标 |
| | | String[] radiusParts = pairs[1].trim().split(","); |
| | | if (radiusParts.length < 2) { |
| | | return "圆形障碍物坐标格式错误"; |
| | | } |
| | | double radiusX = Double.parseDouble(radiusParts[0].trim()); |
| | | double radiusY = Double.parseDouble(radiusParts[1].trim()); |
| | | |
| | | // 计算半径(米) |
| | | double radius = Math.sqrt(Math.pow(radiusX - centerX, 2) + Math.pow(radiusY - centerY, 2)); |
| | | |
| | | // 格式化显示:当前采集圆形圆心坐标x,y,半径n米 |
| | | return String.format(Locale.US, "当前采集圆形圆心坐标%.2f,%.2f,半径%.2f米", |
| | | centerX, centerY, radius); |
| | | } catch (Exception e) { |
| | | return "圆形障碍物坐标解析失败"; |
| | | } |
| | | } |
| | | |
| | | private void updateSaveButtonState() { |
| | | if (saveButton != null) { |
| | | boolean hasCoords = isMeaningfulValue(formData.get("obstacleCoordinates")); |
| | | boolean hasName = isMeaningfulValue(formData.get("obstacleName")); |
| | | boolean enabled = hasCoords && hasName; |
| | | |
| | | // 检查是否生成了障碍物边界坐标 |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | boolean hasGeneratedBoundary = isMeaningfulValue(formData.get("generatedBoundaryCoordinates")); |
| | | |
| | | // 如果是多边形障碍物,必须生成边界坐标才能保存 |
| | | // 如果是圆形障碍物或其他形状,不需要生成边界就能保存 |
| | | boolean boundaryRequirementMet = true; |
| | | if ("polygon".equals(shapeKey)) { |
| | | boundaryRequirementMet = hasGeneratedBoundary; |
| | | } |
| | | |
| | | boolean enabled = hasCoords && hasName && boundaryRequirementMet; |
| | | saveButton.setEnabled(enabled); |
| | | if (enabled) { |
| | | saveButton.setBackground(PRIMARY_COLOR); |
| | | saveButton.setForeground(WHITE); |
| | | saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | saveButton.setOpaque(true); |
| | | saveButton.setContentAreaFilled(true); |
| | | } else { |
| | | saveButton.setBackground(MEDIUM_GRAY); |
| | | saveButton.setForeground(TEXT_COLOR); |
| | | saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); |
| | | saveButton.setOpaque(true); |
| | | saveButton.setContentAreaFilled(true); |
| | | } |
| | | } |
| | | } |
| | |
| | | String obstacleName = formData.get("obstacleName"); |
| | | String coordsValue = formData.get("obstacleCoordinates"); |
| | | String originalValue = formData.get("obstacleOriginalCoordinates"); |
| | | String generatedBoundaryValue = formData.get("generatedBoundaryCoordinates"); // 生成的边界坐标 |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | String previousName = formData.get("editingObstacleName"); |
| | | |
| | |
| | | |
| | | String coords = coordsValue.trim(); |
| | | String originalCoords = isMeaningfulValue(originalValue) ? originalValue.trim() : null; |
| | | // 对于圆形障碍物,直接使用obstacleCoordinates(在第3个点确认时已生成) |
| | | // 对于多边形障碍物,如果有生成的边界坐标,使用生成的边界坐标;否则使用原始坐标 |
| | | String finalCoords; |
| | | if ("circle".equals(shapeKey)) { |
| | | // 圆形障碍物直接使用已生成的坐标 |
| | | finalCoords = coords; |
| | | } else { |
| | | // 多边形障碍物优先使用生成的边界坐标 |
| | | finalCoords = isMeaningfulValue(generatedBoundaryValue) ? generatedBoundaryValue.trim() : coords; |
| | | } |
| | | String trimmedName = isMeaningfulValue(obstacleName) ? obstacleName.trim() : null; |
| | | if (!isMeaningfulValue(trimmedName)) { |
| | | JOptionPane.showMessageDialog(this, "障碍物名称无效", "错误", JOptionPane.ERROR_MESSAGE); |
| | |
| | | formData.put("editingObstacleName", trimmedName); |
| | | } |
| | | |
| | | if (!persistObstacleToConfig(landNumber, previousName, trimmedName, shapeKey, coords, originalCoords)) { |
| | | if (!persistObstacleToConfig(landNumber, previousName, trimmedName, shapeKey, finalCoords, originalCoords)) { |
| | | JOptionPane.showMessageDialog(this, "写入障碍物配置失败,请重试", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | |
| | | activeSession = null; |
| | | dispose(); |
| | | } |
| | | |
| | | /** |
| | | * 生成障碍物边界坐标 |
| | | */ |
| | | private void generateObstacleBoundary() { |
| | | String originalCoords = formData.get("obstacleOriginalCoordinates"); |
| | | if (!isMeaningfulValue(originalCoords)) { |
| | | JOptionPane.showMessageDialog(this, "无原始坐标数据,无法生成边界", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | String baseStation = targetDikuai.getBaseStationCoordinates(); |
| | | if (!isMeaningfulValue(baseStation)) { |
| | | JOptionPane.showMessageDialog(this, "地块未设置基站坐标", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | |
| | | try { |
| | | String method = formData.get("drawingMethod"); |
| | | |
| | | // 处理圆形障碍物 |
| | | if ("circle".equals(shapeKey)) { |
| | | if (!"mower".equals(method)) { |
| | | JOptionPane.showMessageDialog(this, "只有割草机绘制的圆形障碍物才支持生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 将原始坐标转换为Coordinate对象列表 |
| | | List<Coordinate> coordinateList = parseOriginalCoordinatesToCoordinateList(originalCoords); |
| | | if (coordinateList.size() < 3) { |
| | | JOptionPane.showMessageDialog(this, "圆形障碍物至少需要三个采集点", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 解析基站坐标 |
| | | String[] baseParts = baseStation.split(","); |
| | | if (baseParts.length < 4) { |
| | | JOptionPane.showMessageDialog(this, "基站坐标格式无效", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | double baseLat = parseDMToDecimal(baseParts[0].trim(), baseParts[1].trim()); |
| | | double baseLon = parseDMToDecimal(baseParts[2].trim(), baseParts[3].trim()); |
| | | |
| | | // 将原始坐标转换为XY坐标(本地坐标系) |
| | | List<double[]> xyPoints = new ArrayList<>(); |
| | | for (Coordinate coord : coordinateList) { |
| | | double lat = parseDMToDecimal(coord.getLatitude(), coord.getLatDirection()); |
| | | double lon = parseDMToDecimal(coord.getLongitude(), coord.getLonDirection()); |
| | | xyPoints.add(convertLatLonToLocal(lat, lon, baseLat, baseLon)); |
| | | } |
| | | |
| | | // 使用ThreePointCircle算法计算圆心和半径 |
| | | if (xyPoints.size() < 3) { |
| | | JOptionPane.showMessageDialog(this, "至少需要三个点才能生成圆形边界", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 取前三个点计算圆 |
| | | double[] p1 = xyPoints.get(0); |
| | | double[] p2 = xyPoints.get(1); |
| | | double[] p3 = xyPoints.get(2); |
| | | |
| | | String point1 = String.format(Locale.US, "%.2f,%.2f", p1[0], p1[1]); |
| | | String point2 = String.format(Locale.US, "%.2f,%.2f", p2[0], p2[1]); |
| | | String point3 = String.format(Locale.US, "%.2f,%.2f", p3[0], p3[1]); |
| | | |
| | | String circleResult = bianjie.ThreePointCircle.getCircleFromPoints(point1, point2, point3); |
| | | |
| | | // 解析结果:格式为 "圆心: x,y; 半径: r" |
| | | if (circleResult == null || circleResult.startsWith("错误")) { |
| | | JOptionPane.showMessageDialog(this, "生成圆形边界失败: " + circleResult, "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 解析圆心和半径 |
| | | String[] parts = circleResult.split(";"); |
| | | if (parts.length < 2) { |
| | | JOptionPane.showMessageDialog(this, "解析圆形边界结果失败", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 提取圆心坐标 |
| | | String centerPart = parts[0].trim(); // "圆心: x,y" |
| | | String radiusPart = parts[1].trim(); // "半径: r" |
| | | |
| | | String centerCoords = centerPart.substring(centerPart.indexOf(":") + 1).trim(); |
| | | String radiusStr = radiusPart.substring(radiusPart.indexOf(":") + 1).trim(); |
| | | |
| | | String[] centerXY = centerCoords.split(","); |
| | | if (centerXY.length < 2) { |
| | | JOptionPane.showMessageDialog(this, "解析圆心坐标失败", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | double centerX = Double.parseDouble(centerXY[0].trim()); |
| | | double centerY = Double.parseDouble(centerXY[1].trim()); |
| | | double radius = Double.parseDouble(radiusStr.trim()); |
| | | |
| | | // 计算圆上一点(圆心右侧的点) |
| | | double radiusX = centerX + radius; |
| | | double radiusY = centerY; |
| | | |
| | | // 生成边界坐标格式:圆心X,圆心Y;圆上点X,圆上点Y |
| | | String boundaryCoords = String.format(Locale.US, "%.2f,%.2f;%.2f,%.2f", |
| | | centerX, centerY, radiusX, radiusY); |
| | | |
| | | // 保存生成的边界坐标 |
| | | formData.put("generatedBoundaryCoordinates", boundaryCoords); |
| | | |
| | | // 更新边界状态标签显示(圆形只有2个点:圆心和圆上一点) |
| | | updateBoundaryStatusLabel(2); |
| | | |
| | | // 更新预览按钮显示(变成绿色可点击) |
| | | updatePreviewButtonState(); |
| | | |
| | | // 更新保存按钮状态(变成可点击) |
| | | updateSaveButtonState(); |
| | | |
| | | // 强制刷新UI |
| | | SwingUtilities.invokeLater(() -> { |
| | | if (previewButton != null) { |
| | | previewButton.revalidate(); |
| | | previewButton.repaint(); |
| | | } |
| | | }); |
| | | |
| | | JOptionPane.showMessageDialog(this, "圆形障碍物边界坐标已生成", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 处理多边形障碍物 |
| | | if (!"polygon".equals(shapeKey)) { |
| | | JOptionPane.showMessageDialog(this, "只有多边形或圆形障碍物才需要生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 检查绘制方式,只有割草机绘制的多边形才调用bianjieguihua2 |
| | | if (!"mower".equals(method)) { |
| | | JOptionPane.showMessageDialog(this, "只有割草机绘制的多边形才支持生成边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 将原始坐标转换为Coordinate对象列表 |
| | | List<Coordinate> coordinateList = parseOriginalCoordinatesToCoordinateList(originalCoords); |
| | | if (coordinateList.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "原始坐标数据无效", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 保存当前的Coordinate.coordinates |
| | | List<Coordinate> savedCoordinates = new ArrayList<>(Coordinate.coordinates); |
| | | |
| | | try { |
| | | // 设置到全局坐标列表 |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.coordinates.addAll(coordinateList); |
| | | |
| | | // 调用bianjieguihua2算法生成优化后的多边形边界坐标 |
| | | String optimizedCoordsStr = bianjieguihua2.processCoordinateListAuto(baseStation); |
| | | |
| | | if (optimizedCoordsStr == null || optimizedCoordsStr.trim().isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "生成边界坐标失败", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 保存生成的边界坐标 |
| | | formData.put("generatedBoundaryCoordinates", optimizedCoordsStr.trim()); |
| | | |
| | | // 计算生成的边界点数 |
| | | int boundaryPointCount = countCoordinatePairs(optimizedCoordsStr.trim()); |
| | | |
| | | // 更新边界状态标签显示 |
| | | updateBoundaryStatusLabel(boundaryPointCount); |
| | | |
| | | // 更新预览按钮显示(变成绿色可点击) |
| | | updatePreviewButtonState(); |
| | | |
| | | // 更新保存按钮状态(变成可点击) |
| | | updateSaveButtonState(); |
| | | |
| | | // 强制刷新UI |
| | | SwingUtilities.invokeLater(() -> { |
| | | if (previewButton != null) { |
| | | previewButton.revalidate(); |
| | | previewButton.repaint(); |
| | | } |
| | | }); |
| | | |
| | | JOptionPane.showMessageDialog(this, "障碍物边界坐标已生成", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | |
| | | } finally { |
| | | // 恢复原来的坐标列表 |
| | | Coordinate.coordinates.clear(); |
| | | Coordinate.coordinates.addAll(savedCoordinates); |
| | | } |
| | | |
| | | } catch (Exception ex) { |
| | | ex.printStackTrace(); |
| | | JOptionPane.showMessageDialog(this, "生成边界坐标时发生错误: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将原始坐标字符串解析为Coordinate对象列表 |
| | | */ |
| | | private List<Coordinate> parseOriginalCoordinatesToCoordinateList(String originalCoords) { |
| | | List<Coordinate> coordinateList = new ArrayList<>(); |
| | | if (!isMeaningfulValue(originalCoords)) { |
| | | return coordinateList; |
| | | } |
| | | |
| | | // 原始坐标格式:纬度1,方向1,经度1,方向1;纬度2,方向2,经度2,方向2;... |
| | | String[] pointStrings = originalCoords.split(";"); |
| | | for (String pointStr : pointStrings) { |
| | | pointStr = pointStr.trim(); |
| | | if (pointStr.isEmpty()) continue; |
| | | |
| | | String[] parts = pointStr.split(","); |
| | | if (parts.length >= 4) { |
| | | try { |
| | | String lat = parts[0].trim(); |
| | | String latDir = parts[1].trim(); |
| | | String lon = parts[2].trim(); |
| | | String lonDir = parts[3].trim(); |
| | | |
| | | Coordinate coord = new Coordinate(lat, latDir, lon, lonDir, 0.0); |
| | | coordinateList.add(coord); |
| | | } catch (Exception e) { |
| | | // 跳过无效的坐标点 |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return coordinateList; |
| | | } |
| | | |
| | | /** |
| | | * 预览障碍物边界 |
| | | */ |
| | | private void previewObstacleBoundary() { |
| | | String generatedBoundary = formData.get("generatedBoundaryCoordinates"); |
| | | if (!isMeaningfulValue(generatedBoundary)) { |
| | | JOptionPane.showMessageDialog(this, "请先生成障碍物边界坐标", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | String landNumber = targetDikuai.getLandNumber(); |
| | | String landName = targetDikuai.getLandName(); |
| | | String boundary = targetDikuai.getBoundaryCoordinates(); |
| | | |
| | | // 获取障碍物坐标(优先使用生成的边界坐标,如果没有则使用原始坐标) |
| | | String obstacleCoords = generatedBoundary; |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | String obstacleName = formData.get("obstacleName"); |
| | | |
| | | // 对于圆形障碍物,生成的边界坐标格式就是障碍物坐标格式,可以直接使用 |
| | | // 对于多边形障碍物,也需要使用生成的边界坐标 |
| | | // 如果生成的边界坐标不可用,尝试使用原始障碍物坐标 |
| | | if (!isMeaningfulValue(obstacleCoords)) { |
| | | obstacleCoords = formData.get("obstacleCoordinates"); |
| | | if (!isMeaningfulValue(obstacleCoords)) { |
| | | JOptionPane.showMessageDialog(this, "无法获取障碍物坐标进行预览", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 构建障碍物数据字符串,包含名称、形状和坐标 |
| | | // 格式:障碍物名称::形状::坐标 或 障碍物名称:形状:坐标 |
| | | final String obstacleData; |
| | | if (isMeaningfulValue(obstacleName) && isMeaningfulValue(shapeKey)) { |
| | | // 使用 :: 分隔符格式:名称::形状::坐标 |
| | | obstacleData = obstacleName.trim() + "::" + shapeKey.trim() + "::" + obstacleCoords; |
| | | } else if (isMeaningfulValue(shapeKey)) { |
| | | // 只有形状:形状::坐标 |
| | | obstacleData = shapeKey.trim() + "::" + obstacleCoords; |
| | | } else { |
| | | // 只有坐标 |
| | | obstacleData = obstacleCoords; |
| | | } |
| | | |
| | | // 关闭当前对话框 |
| | | setVisible(false); |
| | | |
| | | SwingUtilities.invokeLater(() -> { |
| | | Shouye shouye = Shouye.getInstance(); |
| | | if (shouye != null) { |
| | | // 传递回调以重新打开新增障碍物步骤2页面 |
| | | shouye.startMowingPathPreview( |
| | | landNumber, |
| | | landName, |
| | | boundary, |
| | | obstacleData, // 使用包含名称和形状的障碍物数据 |
| | | null, |
| | | () -> SwingUtilities.invokeLater(() -> { |
| | | // 重新打开新增障碍物步骤2页面 |
| | | Window owner = SwingUtilities.getWindowAncestor(shouye); |
| | | setVisible(true); |
| | | }) |
| | | ); |
| | | } else { |
| | | JOptionPane.showMessageDialog(null, "无法打开主页面进行预览", "提示", JOptionPane.WARNING_MESSAGE); |
| | | setVisible(true); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private boolean persistObstacleToConfig(String landNumber, String previousName, String obstacleName, |
| | | String shapeKey, String xyCoords, String originalCoords) { |
| | |
| | | if (step < 2) { |
| | | nextButton.setVisible(true); |
| | | saveButton.setVisible(false); |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | } |
| | | } else { |
| | | nextButton.setVisible(false); |
| | | saveButton.setVisible(true); |
| | | updateDrawingStatus(); |
| | | updatePreviewButtonState(); |
| | | // 对于圆形障碍物,确保预览按钮和生成边界按钮隐藏 |
| | | String shapeKey = formData.get("obstacleShape"); |
| | | boolean isCircle = "circle".equals(shapeKey); |
| | | if (isCircle) { |
| | | if (previewButton != null) { |
| | | previewButton.setVisible(false); |
| | | } |
| | | if (generateBoundaryButton != null) { |
| | | generateBoundaryButton.setVisible(false); |
| | | } |
| | | } |
| | | } |
| | | updateSaveButtonState(); |
| | | revalidate(); |
| | |
| | | } |
| | | |
| | | updateDrawingStatus(); |
| | | |
| | | // 如果已有生成的边界坐标,更新边界状态标签 |
| | | String generatedBoundary = session.data.get("generatedBoundaryCoordinates"); |
| | | if (isMeaningfulValue(generatedBoundary)) { |
| | | int boundaryPointCount = countCoordinatePairs(generatedBoundary); |
| | | updateBoundaryStatusLabel(boundaryPointCount); |
| | | } else { |
| | | // 如果没有生成的边界坐标,隐藏边界状态标签 |
| | | if (boundaryStatusLabel != null) { |
| | | boundaryStatusLabel.setVisible(false); |
| | | boundaryStatusLabel.setText(""); |
| | | } |
| | | } |
| | | |
| | | // 更新按钮状态 |
| | | updatePreviewButtonState(); |
| | | updateSaveButtonState(); |
| | | |
| | | currentStep = 2; |
| | | showStep(2); |
| | | } |
| | |
| | | } |
| | | |
| | | 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(10, 22, 10, 22))); |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | button.setFocusPainted(false); |
| | | button.setOpaque(true); |
| | | button.setContentAreaFilled(true); |
| | | button.addMouseListener(new MouseAdapter() { |
| | | @Override |
| | | public void mouseEntered(MouseEvent e) { |
| | |
| | | public void mouseExited(MouseEvent e) { |
| | | if (button.isEnabled()) { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | } else { |
| | | // 禁用时保持灰色背景 |
| | | button.setBackground(MEDIUM_GRAY); |
| | | } |
| | | } |
| | | }); |
| | |
| | | } |
| | | |
| | | private JButton createSecondaryButton(String text) { |
| | | JButton button = new JButton(text); |
| | | JButton button = buttonset.createStyledButton(text, MEDIUM_GRAY); |
| | | button.setFont(new Font("微软雅黑", Font.BOLD, 16)); |
| | | button.setBackground(MEDIUM_GRAY); |
| | | button.setForeground(TEXT_COLOR); |
| | | button.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 22, 10, 22))); |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | button.setFocusPainted(false); |
| | | button.setOpaque(true); |
| | | button.setContentAreaFilled(true); |
| | | return button; |
| | | } |
| | | |