package dikuai;
|
|
import java.awt.BorderLayout;
|
import java.awt.CardLayout;
|
import java.awt.Color;
|
import java.awt.Component;
|
import java.awt.Cursor;
|
import java.awt.Dimension;
|
import java.awt.FlowLayout;
|
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 java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
|
import javax.swing.BorderFactory;
|
import javax.swing.Box;
|
import javax.swing.BoxLayout;
|
import javax.swing.ImageIcon;
|
import javax.swing.JButton;
|
import javax.swing.JDialog;
|
import javax.swing.JLabel;
|
import javax.swing.JOptionPane;
|
import javax.swing.JPanel;
|
import javax.swing.SwingConstants;
|
import javax.swing.SwingUtilities;
|
import javax.swing.JTextField;
|
import javax.swing.border.Border;
|
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentListener;
|
|
import baseStation.BaseStation;
|
import gecaoji.Device;
|
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},支持通过实地绘制采集障碍物坐标。
|
*/
|
public class addzhangaiwu extends JDialog {
|
private static final long serialVersionUID = 1L;
|
private static final double METERS_PER_DEGREE_LAT = 111320.0d;
|
|
private final Color PRIMARY_COLOR = new Color(46, 139, 87);
|
private final Color PRIMARY_DARK = new Color(30, 107, 69);
|
private final Color PRIMARY_LIGHT = new Color(232, 245, 233);
|
private final Color WHITE = Color.WHITE;
|
private final Color BORDER_COLOR = new Color(222, 226, 230);
|
private final Color MEDIUM_GRAY = new Color(233, 236, 239);
|
private final Color TEXT_COLOR = new Color(33, 37, 41);
|
private final Color LIGHT_TEXT = new Color(108, 117, 125);
|
private final Color DANGER_COLOR = new Color(220, 53, 69);
|
private final Color DANGER_LIGHT = new Color(255, 235, 238);
|
|
private final Dikuai targetDikuai;
|
private final List<ExistingObstacle> existingObstacles;
|
private final Map<String, String> formData = new HashMap<>();
|
|
private CardLayout cardLayout;
|
private JPanel stepsPanel;
|
private JButton prevButton;
|
private JButton nextButton;
|
private JButton saveButton;
|
|
private JPanel methodOptionsPanel;
|
private JPanel shapeOptionsPanel;
|
private final Map<String, JPanel> methodOptionPanels = new HashMap<>();
|
private final Map<String, JPanel> shapeOptionPanels = new HashMap<>();
|
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 int currentStep = 1;
|
private boolean drawingInProgress;
|
private String activeDrawingShape;
|
|
private static ObstacleDrawingSession activeSession;
|
private static boolean resumeRequested;
|
|
private static final class ObstacleDrawingSession {
|
Dikuai target;
|
Map<String, String> data = new HashMap<>();
|
String baseStation;
|
boolean drawingCompleted;
|
boolean captureSuccessful;
|
String captureMessage;
|
}
|
|
private addzhangaiwu(Window owner, Dikuai target, List<String> obstacleNames) {
|
super(owner, "新增障碍物", ModalityType.APPLICATION_MODAL);
|
if (target == null) {
|
throw new IllegalArgumentException("targetDikuai 不能为空");
|
}
|
Coordinate.coordinates.clear();
|
Coordinate.setStartSaveGngga(false);
|
Coordinate.clearActiveDeviceIdFilter();
|
this.targetDikuai = target;
|
this.existingObstacles = new ArrayList<>(resolveExistingObstacles(target, obstacleNames));
|
initializeUI();
|
setupEventHandlers();
|
preloadData();
|
}
|
|
private void initializeUI() {
|
Dimension dialogSize = new Dimension(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
|
setSize(dialogSize);
|
setPreferredSize(dialogSize);
|
setMinimumSize(dialogSize);
|
setMaximumSize(dialogSize);
|
setResizable(false);
|
setLocationRelativeTo(getOwner());
|
getContentPane().setLayout(new BorderLayout());
|
getContentPane().setBackground(WHITE);
|
|
JPanel container = new JPanel(new BorderLayout());
|
container.setBackground(WHITE);
|
container.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
container.setPreferredSize(new Dimension(UIConfig.DIALOG_WIDTH - 40, UIConfig.DIALOG_HEIGHT - 40));
|
|
cardLayout = new CardLayout();
|
stepsPanel = new JPanel(cardLayout);
|
stepsPanel.setBackground(WHITE);
|
stepsPanel.setPreferredSize(new Dimension(UIConfig.DIALOG_WIDTH - 40, UIConfig.DIALOG_HEIGHT - 140));
|
stepsPanel.add(createStep1Panel(), "step1");
|
stepsPanel.add(createStep2Panel(), "step2");
|
|
container.add(stepsPanel, BorderLayout.CENTER);
|
container.add(createButtonPanel(), BorderLayout.SOUTH);
|
|
add(container, BorderLayout.CENTER);
|
showStep(1);
|
}
|
|
private JPanel createStep1Panel() {
|
JPanel stepPanel = new JPanel();
|
stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
|
stepPanel.setBackground(WHITE);
|
|
JLabel title = new JLabel("步骤1:确认地块信息");
|
title.setFont(new Font("微软雅黑", Font.BOLD, 20));
|
title.setForeground(TEXT_COLOR);
|
title.setAlignmentX(Component.LEFT_ALIGNMENT);
|
title.setHorizontalAlignment(SwingConstants.LEFT);
|
stepPanel.add(title);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
|
JPanel infoContainer = new JPanel();
|
infoContainer.setLayout(new BoxLayout(infoContainer, BoxLayout.Y_AXIS));
|
infoContainer.setOpaque(false);
|
infoContainer.setAlignmentX(Component.LEFT_ALIGNMENT);
|
infoContainer.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
|
|
infoContainer.add(createInfoRow("地块编号:", safeValue(targetDikuai.getLandNumber(), "未设置")));
|
infoContainer.add(Box.createRigidArea(new Dimension(0, 16)));
|
infoContainer.add(createInfoRow("地块名称:", safeValue(targetDikuai.getLandName(), "未命名")));
|
infoContainer.add(Box.createRigidArea(new Dimension(0, 16)));
|
infoContainer.add(createObstacleNameRow());
|
infoContainer.add(Box.createRigidArea(new Dimension(0, 12)));
|
infoContainer.add(createStep1NextButtonRow());
|
infoContainer.add(Box.createRigidArea(new Dimension(0, 12)));
|
infoContainer.add(buildExistingObstacleSection());
|
|
stepPanel.add(infoContainer);
|
stepPanel.add(Box.createVerticalGlue());
|
return stepPanel;
|
}
|
|
private JPanel buildExistingObstacleSection() {
|
JPanel section = new JPanel();
|
section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS));
|
section.setOpaque(false);
|
section.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel titleLabel = new JLabel("已有障碍物");
|
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
titleLabel.setForeground(TEXT_COLOR);
|
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
section.add(titleLabel);
|
section.add(Box.createRigidArea(new Dimension(0, 8)));
|
|
existingObstacleListPanel = new JPanel();
|
existingObstacleListPanel.setLayout(new BoxLayout(existingObstacleListPanel, BoxLayout.Y_AXIS));
|
existingObstacleListPanel.setOpaque(false);
|
existingObstacleListPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
section.add(existingObstacleListPanel);
|
refreshExistingObstacleList();
|
return section;
|
}
|
|
private JPanel createStep1NextButtonRow() {
|
if (step1NextButtonRow == null) {
|
step1NextButtonRow = new JPanel();
|
step1NextButtonRow.setLayout(new BoxLayout(step1NextButtonRow, BoxLayout.X_AXIS));
|
step1NextButtonRow.setOpaque(false);
|
step1NextButtonRow.setAlignmentX(Component.LEFT_ALIGNMENT);
|
step1NextButtonRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
|
step1NextButtonRow.add(Box.createHorizontalGlue());
|
}
|
return step1NextButtonRow;
|
}
|
|
private void attachNextButtonToStep1Row() {
|
if (step1NextButtonRow == null || nextButton == null) {
|
return;
|
}
|
step1NextButtonRow.removeAll();
|
step1NextButtonRow.add(Box.createHorizontalGlue());
|
nextButton.setAlignmentX(Component.RIGHT_ALIGNMENT);
|
step1NextButtonRow.add(nextButton);
|
step1NextButtonRow.revalidate();
|
step1NextButtonRow.repaint();
|
}
|
|
private void refreshExistingObstacleList() {
|
if (existingObstacleListPanel == null) {
|
return;
|
}
|
existingObstacleListPanel.removeAll();
|
if (existingObstacles.isEmpty()) {
|
JLabel emptyLabel = new JLabel("暂无");
|
emptyLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
emptyLabel.setForeground(LIGHT_TEXT);
|
emptyLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
existingObstacleListPanel.add(emptyLabel);
|
} else {
|
for (int i = 0; i < existingObstacles.size(); i++) {
|
ExistingObstacle obstacle = existingObstacles.get(i);
|
JPanel row = createObstacleSummaryRow(obstacle);
|
row.setAlignmentX(Component.LEFT_ALIGNMENT);
|
existingObstacleListPanel.add(row);
|
if (i < existingObstacles.size() - 1) {
|
existingObstacleListPanel.add(Box.createRigidArea(new Dimension(0, 6)));
|
}
|
}
|
}
|
existingObstacleListPanel.revalidate();
|
existingObstacleListPanel.repaint();
|
}
|
|
private JPanel createObstacleSummaryRow(ExistingObstacle obstacle) {
|
JPanel row = new JPanel();
|
row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
|
row.setOpaque(false);
|
row.setAlignmentX(Component.LEFT_ALIGNMENT);
|
row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
|
|
JLabel infoLabel = new JLabel(buildObstacleSummaryText(obstacle));
|
infoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
infoLabel.setForeground(TEXT_COLOR);
|
infoLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JButton editButton = createInlineButton("修改");
|
editButton.addActionListener(e -> populateObstacleForEditing(obstacle));
|
|
JButton deleteButton = createInlineIconButton("image/delete.png", "删除障碍物");
|
deleteButton.addActionListener(e -> handleDeleteExistingObstacle(obstacle));
|
|
row.add(infoLabel);
|
row.add(Box.createHorizontalGlue());
|
row.add(editButton);
|
row.add(Box.createRigidArea(new Dimension(6, 0)));
|
row.add(deleteButton);
|
return row;
|
}
|
|
private JButton createInlineButton(String text) {
|
JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
button.setBorder(BorderFactory.createEmptyBorder(6, 16, 6, 16));
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
Dimension size = new Dimension(72, 28);
|
button.setPreferredSize(size);
|
button.setMinimumSize(size);
|
button.setMaximumSize(size);
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(PRIMARY_DARK);
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(PRIMARY_COLOR);
|
}
|
});
|
return button;
|
}
|
|
private JButton createInlineIconButton(String iconPath, String tooltip) {
|
JButton button = new JButton();
|
button.setPreferredSize(new Dimension(32, 28));
|
button.setMinimumSize(new Dimension(32, 28));
|
button.setMaximumSize(new Dimension(32, 28));
|
button.setBackground(WHITE);
|
button.setBorder(BorderFactory.createLineBorder(DANGER_COLOR));
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.setFocusPainted(false);
|
button.setFocusable(false);
|
button.setOpaque(true);
|
button.setContentAreaFilled(true);
|
if (tooltip != null && !tooltip.trim().isEmpty()) {
|
button.setToolTipText(tooltip);
|
}
|
|
ImageIcon icon = null;
|
if (iconPath != null && !iconPath.trim().isEmpty()) {
|
ImageIcon rawIcon = new ImageIcon(iconPath);
|
if (rawIcon.getIconWidth() > 0 && rawIcon.getIconHeight() > 0) {
|
Image scaled = rawIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH);
|
icon = new ImageIcon(scaled);
|
}
|
}
|
if (icon != null) {
|
button.setIcon(icon);
|
} else {
|
button.setText("删");
|
button.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
button.setForeground(DANGER_COLOR);
|
}
|
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(DANGER_LIGHT);
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(WHITE);
|
}
|
});
|
return button;
|
}
|
|
private String buildObstacleSummaryText(ExistingObstacle obstacle) {
|
String name = obstacle.getName();
|
String shape = obstacle.getShapeDisplay();
|
String coordPreview = buildCoordinatePreview(obstacle.getDisplayCoordinates(), 5);
|
return String.format(Locale.CHINA, "%s,%s,坐标:%s", name, shape, coordPreview);
|
}
|
|
private String buildCoordinatePreview(String coords, int keepLength) {
|
if (!isMeaningfulValue(coords)) {
|
return "无坐标";
|
}
|
String sanitized = coords.replaceAll("\\s+", "");
|
if (sanitized.length() <= keepLength) {
|
return sanitized;
|
}
|
if (keepLength <= 0) {
|
return "...";
|
}
|
return sanitized.substring(0, keepLength) + "...";
|
}
|
|
private boolean deleteObstacleFromConfig(String obstacleName) {
|
String landNumber = targetDikuai != null ? targetDikuai.getLandNumber() : null;
|
if (!isMeaningfulValue(landNumber) || !isMeaningfulValue(obstacleName)) {
|
return false;
|
}
|
try {
|
File configFile = new File("Obstacledge.properties");
|
if (!configFile.exists()) {
|
return false;
|
}
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return false;
|
}
|
Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
|
if (plot == null) {
|
return false;
|
}
|
if (!plot.removeObstacleByName(obstacleName.trim())) {
|
return false;
|
}
|
if (!manager.saveToFile(configFile.getAbsolutePath())) {
|
return false;
|
}
|
Dikuai.updateField(landNumber.trim(), "updateTime", currentTime());
|
Dikuai.saveToProperties();
|
Dikuaiguanli.notifyExternalCreation(landNumber.trim());
|
return true;
|
} catch (Exception ex) {
|
System.err.println("删除障碍物失败: " + ex.getMessage());
|
return false;
|
}
|
}
|
|
private void populateObstacleForEditing(ExistingObstacle obstacle) {
|
if (obstacle == null) {
|
return;
|
}
|
String name = obstacle.getName();
|
if (isMeaningfulValue(name)) {
|
formData.put("obstacleName", name.trim());
|
formData.put("editingObstacleName", name.trim());
|
} else {
|
formData.remove("obstacleName");
|
formData.remove("editingObstacleName");
|
}
|
if (obstacleNameField != null) {
|
obstacleNameField.setText(isMeaningfulValue(name) ? name.trim() : "");
|
}
|
String xyCoords = obstacle.getXyCoordinates();
|
String displayCoords = obstacle.getDisplayCoordinates();
|
if (isMeaningfulValue(xyCoords)) {
|
formData.put("obstacleCoordinates", xyCoords.trim());
|
} else if (isMeaningfulValue(displayCoords)) {
|
formData.put("obstacleCoordinates", displayCoords.trim());
|
} else {
|
formData.remove("obstacleCoordinates");
|
}
|
String originalCoords = obstacle.getOriginalCoordinates();
|
if (isMeaningfulValue(originalCoords)) {
|
formData.put("obstacleOriginalCoordinates", originalCoords.trim());
|
} else {
|
formData.remove("obstacleOriginalCoordinates");
|
}
|
String shapeKey = obstacle.getShapeKey();
|
if (shapeKey != null) {
|
JPanel shapePanel = shapeOptionPanels.get(shapeKey);
|
selectShapeOption(shapePanel, shapeKey, false);
|
}
|
updateDrawingStatus();
|
showStep(2);
|
}
|
|
private void handleDeleteExistingObstacle(ExistingObstacle obstacle) {
|
if (obstacle == null) {
|
return;
|
}
|
String obstacleName = obstacle.getName();
|
if (!isMeaningfulValue(obstacleName)) {
|
JOptionPane.showMessageDialog(this, "无法删除:障碍物名称无效", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
int choice = JOptionPane.showConfirmDialog(this,
|
"确定要删除障碍物 \"" + obstacleName + "\" 吗?此操作无法撤销。",
|
"删除确认",
|
JOptionPane.YES_NO_OPTION,
|
JOptionPane.WARNING_MESSAGE);
|
if (choice != JOptionPane.YES_OPTION) {
|
return;
|
}
|
boolean removedFromConfig = deleteObstacleFromConfig(obstacleName);
|
boolean hasPersistedData = obstacle.hasPersistedData() || obstacle.getShapeKey() != null;
|
if (!removedFromConfig && hasPersistedData) {
|
JOptionPane.showMessageDialog(this, "删除失败,未能在配置中移除该障碍物。", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
if (!existingObstacles.remove(obstacle)) {
|
existingObstacles.removeIf(item -> obstacleName.equals(item != null ? item.getName() : null));
|
}
|
refreshExistingObstacleList();
|
clearEditingStateIfDeleted(obstacleName);
|
JOptionPane.showMessageDialog(this, "障碍物已删除", "成功", JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
private void clearEditingStateIfDeleted(String obstacleName) {
|
if (!isMeaningfulValue(obstacleName)) {
|
return;
|
}
|
String editingName = formData.get("editingObstacleName");
|
if (isMeaningfulValue(editingName) && obstacleName.equals(editingName)) {
|
formData.remove("editingObstacleName");
|
formData.remove("obstacleCoordinates");
|
formData.remove("obstacleOriginalCoordinates");
|
formData.remove("generatedBoundaryCoordinates"); // 清除生成的边界坐标
|
if (obstacleNameField != null) {
|
obstacleNameField.setText("");
|
} else {
|
formData.remove("obstacleName");
|
}
|
updateDrawingStatus();
|
updateSaveButtonState();
|
updatePreviewButtonState();
|
}
|
}
|
|
private JPanel createStep2Panel() {
|
JPanel stepPanel = new JPanel();
|
stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
|
stepPanel.setBackground(WHITE);
|
|
JLabel title = new JLabel("步骤2:绘制障碍物坐标");
|
title.setFont(new Font("微软雅黑", Font.BOLD, 20));
|
title.setForeground(TEXT_COLOR);
|
title.setAlignmentX(Component.LEFT_ALIGNMENT);
|
stepPanel.add(title);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 16)));
|
|
stepPanel.add(createSectionHeader("选择绘制方式", "支持割草机或手持设备采集障碍物边界"));
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
|
methodOptionsPanel = new JPanel();
|
methodOptionsPanel.setLayout(new BoxLayout(methodOptionsPanel, BoxLayout.Y_AXIS));
|
methodOptionsPanel.setOpaque(false);
|
methodOptionsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
methodOptionsPanel.add(createMethodOption("mower", "割草机绘制", "驾驶割草机沿障碍物边界行驶采集坐标", "image/mow.png"));
|
methodOptionsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
methodOptionsPanel.add(createMethodOption("handheld", "手持设备绘制", "使用手持设备沿障碍物边界行走记录坐标", "image/URT.png"));
|
stepPanel.add(methodOptionsPanel);
|
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
stepPanel.add(createSectionHeader("障碍物形状", "多边形需采集多个点,圆形需在圆周上采集至少三个点"));
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
|
shapeOptionsPanel = new JPanel();
|
shapeOptionsPanel.setLayout(new BoxLayout(shapeOptionsPanel, BoxLayout.Y_AXIS));
|
shapeOptionsPanel.setOpaque(false);
|
shapeOptionsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
shapeOptionsPanel.add(createShapeOption("polygon", "多边形", "依次采集轮廓上的多个点或者沿障碍物边缘走一圈"));
|
shapeOptionsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
shapeOptionsPanel.add(createShapeOption("circle", "圆形", "沿圆周任意采集三个点自动生成圆"));
|
stepPanel.add(shapeOptionsPanel);
|
|
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.addActionListener(e -> startDrawingWorkflow());
|
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 = new JLabel("尚未采集障碍物坐标");
|
drawingStatusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
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;
|
}
|
|
private JPanel createButtonPanel() {
|
JPanel buttonPanel = new JPanel();
|
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
|
buttonPanel.setBackground(WHITE);
|
buttonPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
|
|
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();
|
|
return buttonPanel;
|
}
|
|
private JPanel createInstructionPanel(String text) {
|
JPanel instruction = new JPanel(new BorderLayout());
|
instruction.setBackground(PRIMARY_LIGHT);
|
instruction.setAlignmentX(Component.LEFT_ALIGNMENT);
|
instruction.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createMatteBorder(0, 5, 0, 0, PRIMARY_COLOR),
|
BorderFactory.createEmptyBorder(12, 14, 12, 14)));
|
|
JLabel icon = new JLabel("💡", SwingConstants.CENTER);
|
icon.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 18));
|
icon.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
|
instruction.add(icon, BorderLayout.WEST);
|
|
JLabel content = new JLabel(text);
|
content.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
content.setForeground(TEXT_COLOR);
|
instruction.add(content, BorderLayout.CENTER);
|
|
return instruction;
|
}
|
|
private JPanel createSectionHeader(String title, String hint) {
|
JPanel panel = new JPanel();
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setOpaque(false);
|
panel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel titleLabel = new JLabel(title);
|
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
titleLabel.setForeground(TEXT_COLOR);
|
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
panel.add(titleLabel);
|
|
if (hint != null && !hint.trim().isEmpty()) {
|
panel.add(Box.createRigidArea(new Dimension(0, 4)));
|
JLabel hintLabel = new JLabel(hint);
|
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
hintLabel.setForeground(LIGHT_TEXT);
|
hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
panel.add(hintLabel);
|
}
|
|
return panel;
|
}
|
|
private JPanel createMethodOption(String type, String title, String description, String iconPath) {
|
JPanel option = new JPanel(new BorderLayout(15, 0));
|
option.setBackground(WHITE);
|
option.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(18, 20, 18, 20)));
|
option.putClientProperty("paddingInsets", new int[]{18, 20, 18, 20});
|
option.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
option.setAlignmentX(Component.LEFT_ALIGNMENT);
|
option.setMaximumSize(new Dimension(520, 120));
|
|
JLabel iconLabel = new JLabel();
|
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
iconLabel.setPreferredSize(new Dimension(80, 80));
|
if (iconPath != null && !iconPath.trim().isEmpty()) {
|
ImageIcon rawIcon = new ImageIcon(iconPath);
|
if (rawIcon.getIconWidth() > 0 && rawIcon.getIconHeight() > 0) {
|
Image scaled = rawIcon.getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH);
|
iconLabel.setIcon(new ImageIcon(scaled));
|
}
|
}
|
if (iconLabel.getIcon() == null) {
|
iconLabel.setText("mower".equals(type) ? "🚜" : "📱");
|
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 44));
|
}
|
option.add(iconLabel, BorderLayout.WEST);
|
|
JPanel textPanel = new JPanel();
|
textPanel.setOpaque(false);
|
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS));
|
|
JLabel titleLabel = new JLabel(title);
|
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
titleLabel.setForeground(TEXT_COLOR);
|
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
option.putClientProperty("titleLabel", titleLabel);
|
textPanel.add(titleLabel);
|
|
if (description != null && !description.trim().isEmpty()) {
|
textPanel.add(Box.createRigidArea(new Dimension(0, 8)));
|
JLabel descLabel = new JLabel(description);
|
descLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
descLabel.setForeground(LIGHT_TEXT);
|
descLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
textPanel.add(descLabel);
|
}
|
|
option.add(textPanel, BorderLayout.CENTER);
|
option.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
selectMethodOption(option, type, true);
|
}
|
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (option != selectedMethodPanel) {
|
option.setBackground(new Color(245, 245, 245));
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (option != selectedMethodPanel) {
|
option.setBackground(WHITE);
|
}
|
}
|
});
|
|
methodOptionPanels.put(type, option);
|
return option;
|
}
|
|
private JPanel createShapeOption(String type, String title, String description) {
|
JPanel option = new JPanel();
|
option.setLayout(new BoxLayout(option, BoxLayout.Y_AXIS));
|
option.setBackground(WHITE);
|
option.setAlignmentX(Component.LEFT_ALIGNMENT);
|
option.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(12, 18, 12, 18)));
|
option.putClientProperty("paddingInsets", new int[]{12, 18, 12, 18});
|
option.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
|
JLabel titleLabel = new JLabel(title);
|
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 15));
|
titleLabel.setForeground(TEXT_COLOR);
|
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
option.putClientProperty("titleLabel", titleLabel);
|
option.add(titleLabel);
|
|
if (description != null && !description.trim().isEmpty()) {
|
option.add(Box.createRigidArea(new Dimension(0, 4)));
|
JLabel descLabel = new JLabel(description);
|
descLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
descLabel.setForeground(LIGHT_TEXT);
|
descLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
option.add(descLabel);
|
}
|
|
option.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
selectShapeOption(option, type, true);
|
}
|
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (option != selectedShapePanel) {
|
option.setBackground(new Color(245, 245, 245));
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (option != selectedShapePanel) {
|
option.setBackground(WHITE);
|
}
|
}
|
});
|
|
shapeOptionPanels.put(type, option);
|
return option;
|
}
|
|
private void selectMethodOption(JPanel option, String type, boolean userTriggered) {
|
if (option == null) {
|
return;
|
}
|
if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) {
|
JOptionPane.showMessageDialog(this, "请先添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
if (selectedMethodPanel != null && selectedMethodPanel != option) {
|
resetOptionAppearance(selectedMethodPanel);
|
}
|
selectedMethodPanel = option;
|
highlightOption(option);
|
formData.put("drawingMethod", type);
|
if (userTriggered) {
|
updateSaveButtonState();
|
}
|
}
|
|
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;
|
}
|
if (selectedShapePanel != null && selectedShapePanel != option) {
|
resetOptionAppearance(selectedShapePanel);
|
}
|
selectedShapePanel = option;
|
highlightOption(option);
|
formData.put("obstacleShape", type);
|
if (userTriggered) {
|
updateSaveButtonState();
|
}
|
}
|
|
private void highlightOption(JPanel option) {
|
option.setBackground(PRIMARY_LIGHT);
|
option.setBorder(buildOptionBorder(PRIMARY_COLOR, 3, option));
|
Object titleObj = option.getClientProperty("titleLabel");
|
if (titleObj instanceof JLabel) {
|
((JLabel) titleObj).setForeground(PRIMARY_COLOR);
|
}
|
}
|
|
private void resetOptionAppearance(JPanel option) {
|
if (option == null) {
|
return;
|
}
|
option.setBackground(WHITE);
|
option.setBorder(buildOptionBorder(BORDER_COLOR, 2, option));
|
Object titleObj = option.getClientProperty("titleLabel");
|
if (titleObj instanceof JLabel) {
|
((JLabel) titleObj).setForeground(TEXT_COLOR);
|
}
|
}
|
|
private Border buildOptionBorder(Color color, int thickness, JPanel option) {
|
Object paddingObj = option != null ? option.getClientProperty("paddingInsets") : null;
|
if (paddingObj instanceof int[]) {
|
int[] padding = (int[]) paddingObj;
|
if (padding.length == 4) {
|
Border line = BorderFactory.createLineBorder(color, thickness);
|
Border paddingBorder = BorderFactory.createEmptyBorder(padding[0], padding[1], padding[2], padding[3]);
|
return BorderFactory.createCompoundBorder(line, paddingBorder);
|
}
|
}
|
return BorderFactory.createLineBorder(color, thickness);
|
}
|
|
private void setupEventHandlers() {
|
prevButton.addActionListener(e -> {
|
if (currentStep > 1) {
|
showStep(currentStep - 1);
|
}
|
});
|
|
nextButton.addActionListener(e -> {
|
if (currentStep == 1) {
|
String name = obstacleNameField != null ? obstacleNameField.getText() : null;
|
if (!isMeaningfulValue(name)) {
|
JOptionPane.showMessageDialog(this, "请先填写障碍物名称", "提示", JOptionPane.WARNING_MESSAGE);
|
if (obstacleNameField != null) {
|
obstacleNameField.requestFocusInWindow();
|
}
|
return;
|
}
|
if (!isObstacleNameUnique(name)) {
|
JOptionPane.showMessageDialog(this, "障碍物名称已存在,请输入唯一名称", "提示", JOptionPane.WARNING_MESSAGE);
|
if (obstacleNameField != null) {
|
obstacleNameField.requestFocusInWindow();
|
}
|
return;
|
}
|
formData.put("obstacleName", name.trim());
|
Coordinate.coordinates.clear();
|
showStep(2);
|
}
|
});
|
|
saveButton.addActionListener(e -> saveObstacleAndClose());
|
|
addWindowListener(new WindowAdapter() {
|
@Override
|
public void windowClosing(WindowEvent e) {
|
dispose();
|
}
|
});
|
}
|
|
private void startDrawingWorkflow() {
|
if (drawingInProgress) {
|
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)) {
|
JOptionPane.showMessageDialog(this, "请选择绘制方式", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
String shape = formData.get("obstacleShape");
|
if (!isMeaningfulValue(shape)) {
|
JOptionPane.showMessageDialog(this, "请选择障碍物形状", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
String baseStation = resolveBaseStationCoordinates();
|
if (!isMeaningfulValue(baseStation)) {
|
JOptionPane.showMessageDialog(this, "未获取到有效的基准站坐标,请先在基准站管理中完成设置", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
String deviceId = resolveDrawingDeviceId(method);
|
if (!isMeaningfulValue(deviceId)) {
|
String idLabel = "handheld".equalsIgnoreCase(method) ? "手持设备编号" : "割草机编号";
|
JOptionPane.showMessageDialog(this,
|
"未获取到有效的" + idLabel + ",请先在系统设置中完成配置", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
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);
|
yulanzhangaiwu.startPreview(activeDrawingShape, baseStation);
|
drawingInProgress = true;
|
drawButton.setText("正在绘制...");
|
drawButton.setEnabled(false);
|
if ("circle".equals(activeDrawingShape)) {
|
drawingStatusLabel.setText("正在采集圆形障碍物,请沿圆周采集至少三个点后在主界面结束绘制。");
|
} else {
|
drawingStatusLabel.setText("正在采集障碍物坐标,请在主界面完成绘制。");
|
}
|
|
if (activeSession == null) {
|
activeSession = new ObstacleDrawingSession();
|
}
|
activeSession.target = targetDikuai;
|
activeSession.data.clear();
|
activeSession.data.putAll(formData);
|
activeSession.baseStation = baseStation;
|
activeSession.drawingCompleted = false;
|
activeSession.captureSuccessful = false;
|
activeSession.captureMessage = null;
|
|
resumeRequested = false;
|
closeForDrawingSession();
|
}
|
|
private void closeForDrawingSession() {
|
Shouye shouye = Shouye.getInstance();
|
if (shouye != null) {
|
shouye.showEndDrawingButton(addzhangaiwu::finishDrawingSession, activeDrawingShape);
|
}
|
activeDrawingShape = null;
|
setVisible(false);
|
dispose();
|
}
|
|
public static void finishDrawingSession() {
|
Coordinate.setStartSaveGngga(false);
|
Coordinate.clearActiveDeviceIdFilter();
|
yulanzhangaiwu.stopPreview();
|
|
Shouye shouye = Shouye.getInstance();
|
if (shouye != null) {
|
shouye.hideEndDrawingButton();
|
}
|
|
if (activeSession == null) {
|
return;
|
}
|
|
processCapturedCoordinates(activeSession);
|
activeSession.drawingCompleted = true;
|
resumeRequested = true;
|
|
Component parent = shouye != null ? shouye : null;
|
showDialog(parent, activeSession.target);
|
}
|
|
public static String getActiveSessionBaseStation() {
|
if (activeSession != null && isMeaningfulValue(activeSession.baseStation)) {
|
return activeSession.baseStation.trim();
|
}
|
return null;
|
}
|
|
/**
|
* Stops an in-progress circle capture and reopens the wizard on step 2.
|
*/
|
public static void abortCircleDrawingAndReturn(String message) {
|
Coordinate.setStartSaveGngga(false);
|
Coordinate.clearActiveDeviceIdFilter();
|
yulanzhangaiwu.stopPreview();
|
Coordinate.coordinates.clear();
|
|
if (activeSession == null || activeSession.target == null) {
|
return;
|
}
|
|
activeSession.captureSuccessful = false;
|
activeSession.drawingCompleted = false;
|
activeSession.captureMessage = (message != null && !message.trim().isEmpty())
|
? message.trim()
|
: null;
|
|
resumeRequested = true;
|
|
Component parent = Shouye.getInstance();
|
Dikuai target = activeSession.target;
|
SwingUtilities.invokeLater(() -> showDialog(parent, target));
|
}
|
|
private static void processCapturedCoordinates(ObstacleDrawingSession session) {
|
List<Coordinate> captured = new ArrayList<>(Coordinate.coordinates);
|
Coordinate.coordinates.clear();
|
|
if (captured.isEmpty()) {
|
session.captureSuccessful = false;
|
session.captureMessage = "未采集到任何障碍物坐标,请重新绘制";
|
return;
|
}
|
|
if (!isMeaningfulValue(session.baseStation)) {
|
session.captureSuccessful = false;
|
session.captureMessage = "基准站坐标无效,无法转换障碍物坐标";
|
return;
|
}
|
|
String originalCoordStr = buildOriginalCoordinateString(captured);
|
if (isMeaningfulValue(originalCoordStr)) {
|
session.data.put("obstacleOriginalCoordinates", originalCoordStr);
|
} else {
|
session.data.remove("obstacleOriginalCoordinates");
|
}
|
|
List<double[]> xyPoints = convertToLocalXY(captured, session.baseStation);
|
if (xyPoints.isEmpty()) {
|
session.captureSuccessful = false;
|
session.captureMessage = "坐标转换失败,请检查基准站设置";
|
return;
|
}
|
|
String shape = session.data.get("obstacleShape");
|
if ("circle".equals(shape)) {
|
if (xyPoints.size() < 3) {
|
session.captureSuccessful = false;
|
session.captureMessage = "圆形障碍物至少需要三个采集点(圆周上的点)";
|
return;
|
}
|
CircleFitResult circle = fitCircleFromPoints(xyPoints);
|
if (circle == 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, radiusX, radiusY);
|
session.data.put("obstacleCoordinates", result);
|
session.captureSuccessful = true;
|
session.captureMessage = "已采集圆形障碍物,共 " + xyPoints.size() + " 个点";
|
} else {
|
if (xyPoints.size() < 3) {
|
session.captureSuccessful = false;
|
session.captureMessage = "多边形障碍物至少需要三个采集点";
|
return;
|
}
|
StringBuilder sb = new StringBuilder();
|
for (double[] pt : xyPoints) {
|
if (sb.length() > 0) {
|
sb.append(";");
|
}
|
sb.append(String.format(Locale.US, "%.2f,%.2f", pt[0], pt[1]));
|
}
|
session.data.put("obstacleCoordinates", sb.toString());
|
session.captureSuccessful = true;
|
session.captureMessage = "已采集多边形障碍物,共 " + xyPoints.size() + " 个点";
|
}
|
}
|
|
private static List<double[]> convertToLocalXY(List<Coordinate> coords, String baseStation) {
|
List<double[]> result = new ArrayList<>();
|
if (!isMeaningfulValue(baseStation)) {
|
return result;
|
}
|
String[] parts = baseStation.split(",");
|
if (parts.length < 4) {
|
return result;
|
}
|
double baseLat = parseDMToDecimal(parts[0], parts[1]);
|
double baseLon = parseDMToDecimal(parts[2], parts[3]);
|
if (!Double.isFinite(baseLat) || !Double.isFinite(baseLon)) {
|
return result;
|
}
|
|
for (Coordinate coord : coords) {
|
double lat = parseDMToDecimal(coord.getLatitude(), coord.getLatDirection());
|
double lon = parseDMToDecimal(coord.getLongitude(), coord.getLonDirection());
|
if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
|
continue;
|
}
|
result.add(convertLatLonToLocal(lat, lon, baseLat, baseLon));
|
}
|
return result;
|
}
|
|
private static String buildOriginalCoordinateString(List<Coordinate> coords) {
|
if (coords == null || coords.isEmpty()) {
|
return null;
|
}
|
StringBuilder sb = new StringBuilder();
|
for (Coordinate coord : coords) {
|
if (coord == null) {
|
continue;
|
}
|
String latToken = sanitizeCoordinateToken(coord.getLatitude());
|
String lonToken = sanitizeCoordinateToken(coord.getLongitude());
|
if (latToken == null || lonToken == null) {
|
continue;
|
}
|
char latDir = sanitizeDirection(coord.getLatDirection(), 'N');
|
char lonDir = sanitizeDirection(coord.getLonDirection(), 'E');
|
if (sb.length() > 0) {
|
sb.append(";");
|
}
|
sb.append(latToken)
|
.append(",")
|
.append(latDir)
|
.append(",")
|
.append(lonToken)
|
.append(",")
|
.append(lonDir);
|
}
|
return sb.length() > 0 ? sb.toString() : null;
|
}
|
|
private static String sanitizeCoordinateToken(String token) {
|
if (token == null) {
|
return null;
|
}
|
String trimmed = token.trim();
|
if (trimmed.isEmpty()) {
|
return null;
|
}
|
try {
|
Double.parseDouble(trimmed);
|
return trimmed;
|
} catch (NumberFormatException ex) {
|
return null;
|
}
|
}
|
|
private static char sanitizeDirection(String direction, char fallback) {
|
if (direction == null) {
|
return fallback;
|
}
|
String trimmed = direction.trim();
|
if (trimmed.isEmpty()) {
|
return fallback;
|
}
|
char ch = Character.toUpperCase(trimmed.charAt(0));
|
if (ch != 'N' && ch != 'S' && ch != 'E' && ch != 'W') {
|
return fallback;
|
}
|
return ch;
|
}
|
|
private static double parseDMToDecimal(String dmm, String direction) {
|
if (dmm == null || dmm.trim().isEmpty()) {
|
return Double.NaN;
|
}
|
try {
|
String trimmed = dmm.trim();
|
int dotIndex = trimmed.indexOf('.');
|
if (dotIndex < 2) {
|
return Double.NaN;
|
}
|
int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
|
double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
|
double decimal = degrees + minutes / 60.0;
|
if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
|
decimal = -decimal;
|
}
|
return decimal;
|
} catch (NumberFormatException ex) {
|
return Double.NaN;
|
}
|
}
|
|
private static double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
|
double deltaLat = lat - baseLat;
|
double deltaLon = lon - baseLon;
|
double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
|
double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
|
double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
|
return new double[]{eastMeters, northMeters};
|
}
|
|
private static CircleFitResult fitCircleFromPoints(List<double[]> points) {
|
if (points == null || points.size() < 3) {
|
return null;
|
}
|
CircleFitResult best = null;
|
double bestScore = 0.0;
|
int n = points.size();
|
for (int i = 0; i < n - 2; i++) {
|
double[] p1 = points.get(i);
|
for (int j = i + 1; j < n - 1; j++) {
|
double[] p2 = points.get(j);
|
for (int k = j + 1; k < n; k++) {
|
double[] p3 = points.get(k);
|
CircleFitResult candidate = circleFromThreePoints(p1, p2, p3);
|
if (candidate == null || candidate.radius <= 0) {
|
continue;
|
}
|
double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3)));
|
if (minEdge > bestScore) {
|
bestScore = minEdge;
|
best = candidate;
|
}
|
}
|
}
|
}
|
return best;
|
}
|
|
private static CircleFitResult circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
|
double x1 = p1[0];
|
double y1 = p1[1];
|
double x2 = p2[0];
|
double y2 = p2[1];
|
double x3 = p3[0];
|
double y3 = p3[1];
|
|
double determinant = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
|
if (Math.abs(determinant) < 1e-6) {
|
return null;
|
}
|
|
double x1Sq = x1 * x1 + y1 * y1;
|
double x2Sq = x2 * x2 + y2 * y2;
|
double x3Sq = x3 * x3 + y3 * y3;
|
|
double centerX = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / determinant;
|
double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / determinant;
|
double radius = Math.hypot(centerX - x1, centerY - y1);
|
|
if (!Double.isFinite(centerX) || !Double.isFinite(centerY) || !Double.isFinite(radius)) {
|
return null;
|
}
|
if (radius < 0.05) {
|
return null;
|
}
|
|
return new CircleFitResult(centerX, centerY, radius, new double[]{x1, y1});
|
}
|
|
private static double[] pickRadiusPoint(List<double[]> points, CircleFitResult circle) {
|
if (circle == null || points == null || points.isEmpty()) {
|
return null;
|
}
|
double[] best = null;
|
double bestDistance = 0.0;
|
for (double[] pt : points) {
|
double dist = Math.hypot(pt[0] - circle.centerX, pt[1] - circle.centerY);
|
if (dist > bestDistance) {
|
bestDistance = dist;
|
best = pt;
|
}
|
}
|
return best;
|
}
|
|
private static double distance(double[] a, double[] b) {
|
double dx = a[0] - b[0];
|
double dy = a[1] - b[1];
|
return Math.hypot(dx, dy);
|
}
|
|
private static final class CircleFitResult {
|
final double centerX;
|
final double centerY;
|
final double radius;
|
final double[] referencePoint;
|
|
CircleFitResult(double centerX, double centerY, double radius, double[] referencePoint) {
|
this.centerX = centerX;
|
this.centerY = centerY;
|
this.radius = radius;
|
this.referencePoint = referencePoint;
|
}
|
}
|
|
private List<ExistingObstacle> resolveExistingObstacles(Dikuai target, List<String> providedNames) {
|
String landNumber = target != null ? target.getLandNumber() : null;
|
Map<String, ExistingObstacle> configMap = loadObstacleDetailsFromConfig(landNumber);
|
List<ExistingObstacle> result = new ArrayList<>();
|
List<String> sanitized = sanitizeObstacleNames(providedNames);
|
if (!sanitized.isEmpty()) {
|
for (String name : sanitized) {
|
ExistingObstacle info = configMap.remove(name);
|
if (info != null) {
|
result.add(info);
|
} else {
|
result.add(ExistingObstacle.placeholder(name));
|
}
|
}
|
}
|
if (!configMap.isEmpty()) {
|
List<ExistingObstacle> remaining = new ArrayList<>(configMap.values());
|
remaining.sort(Comparator.comparing(ExistingObstacle::getName, String.CASE_INSENSITIVE_ORDER));
|
result.addAll(remaining);
|
}
|
if (result.isEmpty()) {
|
return Collections.emptyList();
|
}
|
return result;
|
}
|
|
private List<String> sanitizeObstacleNames(List<String> names) {
|
List<String> sanitized = new ArrayList<>();
|
if (names == null) {
|
return sanitized;
|
}
|
for (String name : names) {
|
if (isMeaningfulValue(name)) {
|
sanitized.add(name.trim());
|
}
|
}
|
return sanitized;
|
}
|
|
private Map<String, ExistingObstacle> loadObstacleDetailsFromConfig(String landNumber) {
|
Map<String, ExistingObstacle> details = new HashMap<>();
|
if (!isMeaningfulValue(landNumber)) {
|
return details;
|
}
|
try {
|
File configFile = new File("Obstacledge.properties");
|
if (!configFile.exists()) {
|
return details;
|
}
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return details;
|
}
|
Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
|
if (plot == null) {
|
return details;
|
}
|
for (Obstacledge.Obstacle obstacle : plot.getObstacles()) {
|
if (obstacle == null) {
|
continue;
|
}
|
String name = obstacle.getObstacleName();
|
if (!isMeaningfulValue(name)) {
|
continue;
|
}
|
String trimmedName = name.trim();
|
Obstacledge.ObstacleShape shape = obstacle.getShape();
|
String xyCoords = obstacle.getXyCoordsString();
|
String original = obstacle.getOriginalCoordsString();
|
String xy = isMeaningfulValue(xyCoords) ? xyCoords.trim() : "";
|
String originalTrimmed = isMeaningfulValue(original) ? original.trim() : "";
|
details.put(trimmedName, new ExistingObstacle(trimmedName, shape, xy, originalTrimmed));
|
}
|
} catch (Exception ex) {
|
System.err.println("加载障碍物详情失败: " + ex.getMessage());
|
}
|
return details;
|
}
|
|
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() {
|
if (drawingStatusLabel == null) {
|
return;
|
}
|
String coords = formData.get("obstacleCoordinates");
|
String shapeKey = formData.get("obstacleShape");
|
boolean isCircle = "circle".equals(shapeKey);
|
|
if (isMeaningfulValue(coords)) {
|
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) {
|
if (!isMeaningfulValue(coords)) {
|
return 0;
|
}
|
String[] parts = coords.split(";+");
|
int count = 0;
|
for (String part : parts) {
|
if (part == null) {
|
continue;
|
}
|
String trimmed = part.trim();
|
if (trimmed.isEmpty()) {
|
continue;
|
}
|
if (trimmed.contains(",")) {
|
count++;
|
}
|
}
|
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"));
|
|
// 检查是否生成了障碍物边界坐标
|
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);
|
}
|
}
|
}
|
|
private boolean validateStep2() {
|
String name = formData.get("obstacleName");
|
if (!isMeaningfulValue(name)) {
|
JOptionPane.showMessageDialog(this, "请填写障碍物名称", "提示", JOptionPane.WARNING_MESSAGE);
|
showStep(1);
|
if (obstacleNameField != null) {
|
obstacleNameField.requestFocusInWindow();
|
}
|
return false;
|
}
|
if (!isObstacleNameUnique(name)) {
|
JOptionPane.showMessageDialog(this, "障碍物名称已存在,请输入唯一名称", "提示", JOptionPane.WARNING_MESSAGE);
|
showStep(1);
|
if (obstacleNameField != null) {
|
obstacleNameField.requestFocusInWindow();
|
}
|
return false;
|
}
|
if (!isMeaningfulValue(formData.get("obstacleCoordinates"))) {
|
JOptionPane.showMessageDialog(this, "请先完成障碍物绘制", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
return true;
|
}
|
|
private void saveObstacleAndClose() {
|
if (!validateStep2()) {
|
return;
|
}
|
String landNumber = targetDikuai.getLandNumber();
|
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");
|
|
if (!isMeaningfulValue(coordsValue)) {
|
JOptionPane.showMessageDialog(this, "障碍物坐标无效", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
|
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);
|
return;
|
}
|
|
formData.put("obstacleName", trimmedName);
|
if (isMeaningfulValue(previousName)) {
|
formData.put("editingObstacleName", trimmedName);
|
}
|
|
if (!persistObstacleToConfig(landNumber, previousName, trimmedName, shapeKey, finalCoords, originalCoords)) {
|
JOptionPane.showMessageDialog(this, "写入障碍物配置失败,请重试", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
Dikuai.updateField(landNumber, "updateTime", currentTime());
|
Dikuai.saveToProperties();
|
Dikuaiguanli.notifyExternalCreation(landNumber);
|
|
JOptionPane.showMessageDialog(this, "障碍物数据已保存", "成功", JOptionPane.INFORMATION_MESSAGE);
|
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 (!isMeaningfulValue(landNumber) || !isMeaningfulValue(obstacleName) || !isMeaningfulValue(xyCoords)) {
|
return false;
|
}
|
|
String normalizedLandNumber = landNumber.trim();
|
String normalizedNewName = obstacleName.trim();
|
String normalizedPrevious = isMeaningfulValue(previousName) ? previousName.trim() : null;
|
|
String normalizedXy = xyCoords.trim();
|
|
try {
|
File configFile = new File("Obstacledge.properties");
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (configFile.exists()) {
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return false;
|
}
|
}
|
|
Obstacledge.Plot plot = manager.getPlotById(normalizedLandNumber);
|
if (plot == null) {
|
plot = new Obstacledge.Plot(normalizedLandNumber);
|
manager.addPlot(plot);
|
}
|
|
ensurePlotBaseStation(plot);
|
|
if (normalizedPrevious != null && !normalizedPrevious.equals(normalizedNewName)) {
|
plot.removeObstacleByName(normalizedPrevious);
|
}
|
|
Obstacledge.Obstacle obstacle = plot.getObstacleByName(normalizedNewName);
|
if (obstacle == null) {
|
obstacle = new Obstacledge.Obstacle(normalizedLandNumber, normalizedNewName,
|
determineObstacleShape(shapeKey, normalizedXy));
|
plot.addObstacle(obstacle);
|
}
|
|
obstacle.setPlotId(normalizedLandNumber);
|
obstacle.setObstacleName(normalizedNewName);
|
obstacle.setShape(determineObstacleShape(shapeKey, normalizedXy));
|
|
obstacle.setXyCoordinates(new ArrayList<Obstacledge.XYCoordinate>());
|
obstacle.setOriginalCoordinates(new ArrayList<Obstacledge.DMCoordinate>());
|
|
obstacle.setXyCoordsString(normalizedXy);
|
|
if (isMeaningfulValue(originalCoords)) {
|
try {
|
obstacle.setOriginalCoordsString(originalCoords);
|
} catch (Exception parseEx) {
|
System.err.println("解析障碍物原始坐标失败,将使用占位值: " + parseEx.getMessage());
|
obstacle.setOriginalCoordinates(new ArrayList<Obstacledge.DMCoordinate>());
|
}
|
}
|
|
ensurePlaceholderOriginalCoords(obstacle);
|
|
return manager.saveToFile(configFile.getAbsolutePath());
|
} catch (Exception ex) {
|
System.err.println("保存障碍物配置失败: " + ex.getMessage());
|
return false;
|
}
|
}
|
|
private Obstacledge.ObstacleShape determineObstacleShape(String shapeKey, String xyCoords) {
|
if (isMeaningfulValue(shapeKey)) {
|
String normalized = shapeKey.trim().toLowerCase(Locale.ROOT);
|
if ("circle".equals(normalized) || "圆形".equals(normalized) || "0".equals(normalized)) {
|
return Obstacledge.ObstacleShape.CIRCLE;
|
}
|
if ("polygon".equals(normalized) || "多边形".equals(normalized) || "1".equals(normalized)) {
|
return Obstacledge.ObstacleShape.POLYGON;
|
}
|
}
|
|
int pairCount = countCoordinatePairs(xyCoords);
|
if (pairCount <= 0) {
|
return Obstacledge.ObstacleShape.POLYGON;
|
}
|
if (pairCount <= 2) {
|
return Obstacledge.ObstacleShape.CIRCLE;
|
}
|
return Obstacledge.ObstacleShape.POLYGON;
|
}
|
|
private void ensurePlaceholderOriginalCoords(Obstacledge.Obstacle obstacle) {
|
if (obstacle == null) {
|
return;
|
}
|
List<Obstacledge.DMCoordinate> originals = obstacle.getOriginalCoordinates();
|
if (originals != null && !originals.isEmpty()) {
|
return;
|
}
|
List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
|
int pointCount = (xyCoords != null && !xyCoords.isEmpty()) ? xyCoords.size() : 1;
|
List<Obstacledge.DMCoordinate> placeholders = new ArrayList<>(pointCount * 2);
|
for (int i = 0; i < pointCount; i++) {
|
placeholders.add(new Obstacledge.DMCoordinate(0.0, 'N'));
|
placeholders.add(new Obstacledge.DMCoordinate(0.0, 'E'));
|
}
|
obstacle.setOriginalCoordinates(placeholders);
|
}
|
|
private void ensurePlotBaseStation(Obstacledge.Plot plot) {
|
if (plot == null) {
|
return;
|
}
|
String existing = plot.getBaseStationString();
|
if (isMeaningfulValue(existing)) {
|
return;
|
}
|
String baseStation = resolveBaseStationCoordinates();
|
if (!isMeaningfulValue(baseStation)) {
|
return;
|
}
|
try {
|
plot.setBaseStationString(baseStation.trim());
|
} catch (Exception ex) {
|
System.err.println("设置基准站坐标失败: " + ex.getMessage());
|
}
|
}
|
|
private static final class ExistingObstacle {
|
private final String name;
|
private final Obstacledge.ObstacleShape shape;
|
private final String xyCoordinates;
|
private final String originalCoordinates;
|
|
ExistingObstacle(String name, Obstacledge.ObstacleShape shape, String xyCoordinates, String originalCoordinates) {
|
this.name = name != null ? name : "";
|
this.shape = shape;
|
this.xyCoordinates = safeCoordString(xyCoordinates);
|
this.originalCoordinates = safeCoordString(originalCoordinates);
|
}
|
|
static ExistingObstacle placeholder(String name) {
|
return new ExistingObstacle(name, null, "", "");
|
}
|
|
String getName() {
|
return name;
|
}
|
|
String getShapeDisplay() {
|
if (shape == null) {
|
return "未知形状";
|
}
|
return shape.getDescription();
|
}
|
|
String getShapeKey() {
|
if (shape == Obstacledge.ObstacleShape.CIRCLE) {
|
return "circle";
|
}
|
if (shape == Obstacledge.ObstacleShape.POLYGON) {
|
return "polygon";
|
}
|
return null;
|
}
|
|
String getCoordinates() {
|
return getDisplayCoordinates();
|
}
|
|
String getDisplayCoordinates() {
|
return hasText(xyCoordinates) ? xyCoordinates : originalCoordinates;
|
}
|
|
String getXyCoordinates() {
|
return xyCoordinates;
|
}
|
|
String getOriginalCoordinates() {
|
return originalCoordinates;
|
}
|
|
boolean hasPersistedData() {
|
return hasText(xyCoordinates) || hasText(originalCoordinates);
|
}
|
|
private static String safeCoordString(String value) {
|
if (value == null) {
|
return "";
|
}
|
return value.trim();
|
}
|
|
private static boolean hasText(String value) {
|
if (value == null) {
|
return false;
|
}
|
String trimmed = value.trim();
|
return !trimmed.isEmpty() && !"-1".equals(trimmed);
|
}
|
}
|
|
private void showStep(int step) {
|
currentStep = step;
|
cardLayout.show(stepsPanel, "step" + step);
|
prevButton.setVisible(step > 1);
|
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();
|
repaint();
|
}
|
|
private void applySessionData(ObstacleDrawingSession session) {
|
drawingInProgress = false;
|
if (drawButton != null) {
|
drawButton.setEnabled(true);
|
}
|
formData.clear();
|
formData.putAll(session.data);
|
|
String sessionName = session.data.get("obstacleName");
|
if (obstacleNameField != null) {
|
obstacleNameField.setText(sessionName != null ? sessionName : "");
|
}
|
|
String method = session.data.get("drawingMethod");
|
if (method != null) {
|
JPanel panel = methodOptionPanels.get(method);
|
if (panel != null) {
|
selectMethodOption(panel, method, false);
|
}
|
}
|
|
String shape = session.data.get("obstacleShape");
|
if (shape != null) {
|
JPanel panel = shapeOptionPanels.get(shape);
|
if (panel != null) {
|
selectShapeOption(panel, shape, false);
|
}
|
}
|
|
if (session.captureMessage != null && !session.captureSuccessful) {
|
JOptionPane.showMessageDialog(this,
|
session.captureMessage,
|
session.captureSuccessful ? "成功" : "提示",
|
session.captureSuccessful ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.WARNING_MESSAGE);
|
}
|
|
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 JPanel createInfoRow(String label, String value) {
|
return createInfoRow(label, value, null);
|
}
|
|
private JPanel createObstacleNameRow() {
|
JPanel row = new JPanel();
|
row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
|
row.setOpaque(false);
|
row.setAlignmentX(Component.LEFT_ALIGNMENT);
|
row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
|
|
JLabel label = new JLabel("障碍物名称:");
|
label.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
label.setForeground(TEXT_COLOR);
|
label.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
if (obstacleNameField == null) {
|
obstacleNameField = new JTextField();
|
obstacleNameField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
obstacleNameField.setColumns(16);
|
Dimension fieldSize = new Dimension(240, 30);
|
obstacleNameField.setPreferredSize(fieldSize);
|
obstacleNameField.setMinimumSize(fieldSize);
|
obstacleNameField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30));
|
obstacleNameField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 1),
|
BorderFactory.createEmptyBorder(4, 8, 4, 8)));
|
obstacleNameField.getDocument().addDocumentListener(new DocumentListener() {
|
@Override
|
public void insertUpdate(DocumentEvent e) {
|
handleObstacleNameChanged();
|
}
|
|
@Override
|
public void removeUpdate(DocumentEvent e) {
|
handleObstacleNameChanged();
|
}
|
|
@Override
|
public void changedUpdate(DocumentEvent e) {
|
handleObstacleNameChanged();
|
}
|
});
|
}
|
|
String existing = formData.get("obstacleName");
|
if (existing != null && !existing.equals(obstacleNameField.getText())) {
|
obstacleNameField.setText(existing);
|
}
|
|
row.add(label);
|
row.add(Box.createRigidArea(new Dimension(10, 0)));
|
row.add(obstacleNameField);
|
row.add(Box.createHorizontalGlue());
|
return row;
|
}
|
|
private void handleObstacleNameChanged() {
|
if (obstacleNameField == null) {
|
return;
|
}
|
String text = obstacleNameField.getText();
|
if (isMeaningfulValue(text)) {
|
formData.put("obstacleName", text.trim());
|
} else {
|
formData.remove("obstacleName");
|
}
|
updateSaveButtonState();
|
}
|
|
private boolean isObstacleNameUnique(String candidate) {
|
if (!isMeaningfulValue(candidate)) {
|
return false;
|
}
|
String trimmed = candidate.trim().toLowerCase(Locale.ROOT);
|
String original = formData.get("editingObstacleName");
|
if (isMeaningfulValue(original) && trimmed.equals(original.trim().toLowerCase(Locale.ROOT))) {
|
return true;
|
}
|
for (ExistingObstacle obstacle : existingObstacles) {
|
if (obstacle == null) {
|
continue;
|
}
|
String existingName = obstacle.getName();
|
if (isMeaningfulValue(existingName) && trimmed.equals(existingName.trim().toLowerCase(Locale.ROOT))) {
|
return false;
|
}
|
}
|
return true;
|
}
|
|
private JPanel createInfoRow(String label, String value, String tooltip) {
|
JPanel row = new JPanel();
|
row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
|
row.setOpaque(false);
|
row.setAlignmentX(Component.LEFT_ALIGNMENT);
|
row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
|
|
JLabel labelComp = new JLabel(label);
|
labelComp.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
labelComp.setForeground(TEXT_COLOR);
|
labelComp.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel valueComp = new JLabel(value != null ? value : "");
|
valueComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
valueComp.setForeground(TEXT_COLOR);
|
valueComp.setVerticalAlignment(SwingConstants.TOP);
|
valueComp.setAlignmentX(Component.LEFT_ALIGNMENT);
|
if (tooltip != null && !tooltip.trim().isEmpty()) {
|
valueComp.setToolTipText(tooltip);
|
}
|
|
row.add(labelComp);
|
row.add(Box.createRigidArea(new Dimension(6, 0)));
|
row.add(valueComp);
|
return row;
|
}
|
|
private JButton createPrimaryButton(String text, int fontSize) {
|
JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR);
|
button.setFont(new Font("微软雅黑", Font.BOLD, fontSize));
|
button.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_DARK, 2),
|
BorderFactory.createEmptyBorder(10, 22, 10, 22)));
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.setOpaque(true);
|
button.setContentAreaFilled(true);
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (button.isEnabled()) {
|
button.setBackground(PRIMARY_DARK);
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (button.isEnabled()) {
|
button.setBackground(PRIMARY_COLOR);
|
} else {
|
// 禁用时保持灰色背景
|
button.setBackground(MEDIUM_GRAY);
|
}
|
}
|
});
|
return button;
|
}
|
|
private JButton createSecondaryButton(String text) {
|
JButton button = buttonset.createStyledButton(text, MEDIUM_GRAY);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
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.setOpaque(true);
|
button.setContentAreaFilled(true);
|
return button;
|
}
|
|
private static boolean isMeaningfulValue(String value) {
|
if (value == null) {
|
return false;
|
}
|
String trimmed = value.trim();
|
return !trimmed.isEmpty() && !"-1".equals(trimmed);
|
}
|
|
private String resolveBaseStationCoordinates() {
|
String coords = targetDikuai.getBaseStationCoordinates();
|
if (isMeaningfulValue(coords)) {
|
return coords.trim();
|
}
|
|
Device device = new Device();
|
device.initFromProperties();
|
coords = device.getBaseStationCoordinates();
|
if (isMeaningfulValue(coords)) {
|
return coords.trim();
|
}
|
|
BaseStation baseStation = new BaseStation();
|
baseStation.load();
|
coords = baseStation.getInstallationCoordinates();
|
if (isMeaningfulValue(coords)) {
|
return coords.trim();
|
}
|
return null;
|
}
|
|
private String resolveDrawingDeviceId(String method) {
|
if (!isMeaningfulValue(method)) {
|
return null;
|
}
|
String key = "handheld".equalsIgnoreCase(method) ? "handheldMarkerId" : "mowerId";
|
String value = Setsys.getPropertyValue(key);
|
if (!isMeaningfulValue(value)) {
|
return null;
|
}
|
return value.trim();
|
}
|
|
private String safeValue(String value, String fallback) {
|
if (!isMeaningfulValue(value)) {
|
return fallback;
|
}
|
return value.trim();
|
}
|
|
private String currentTime() {
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
return sdf.format(new Date());
|
}
|
|
/**
|
* 显示新增障碍物对话框。
|
*/
|
public static void showDialog(Component parent, Dikuai target) {
|
showDialog(parent, target, null);
|
}
|
|
public static void showDialog(Component parent, Dikuai target, List<String> obstacleNames) {
|
Window owner = null;
|
if (parent instanceof Window) {
|
owner = (Window) parent;
|
} else if (parent != null) {
|
owner = SwingUtilities.getWindowAncestor(parent);
|
}
|
|
addzhangaiwu dialog = new addzhangaiwu(owner, target, obstacleNames);
|
if (resumeRequested && activeSession != null && activeSession.target == target) {
|
dialog.applySessionData(activeSession);
|
resumeRequested = false;
|
}
|
dialog.setVisible(true);
|
}
|
}
|