package zhangaiwu;
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
import java.awt.event.*;
|
import java.io.File;
|
import java.io.IOException;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.Files;
|
import java.text.DecimalFormat;
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
import java.util.Comparator;
|
import java.awt.geom.Point2D;
|
|
import bianjie.jisuanmianjie;
|
import dikuai.Dikuai;
|
import dikuai.Dikuaiguanli;
|
import gecaoji.Device;
|
import bianjie.bianjieguihua2;
|
import lujing.Lunjingguihua;
|
import ui.UIConfig;
|
import zhuye.MowerLocationData;
|
import zhuye.Shouye;
|
import zhuye.Coordinate;
|
|
/**
|
* 新增地块对话框 - 多步骤表单设计
|
* 适配6.5寸竖屏优化版本 - 左对齐版
|
*/
|
public class AddDikuai extends JDialog {
|
private static final long serialVersionUID = 1L;
|
|
// 主题颜色 - 优化配色方案
|
private final Color PRIMARY_COLOR = new Color(46, 139, 87);
|
private final Color PRIMARY_LIGHT = new Color(232, 245, 233);
|
private final Color PRIMARY_DARK = new Color(30, 107, 69);
|
private final Color WHITE = Color.WHITE;
|
private final Color LIGHT_GRAY = new Color(248, 249, 250);
|
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 BORDER_COLOR = new Color(222, 226, 230);
|
private final Color SUCCESS_COLOR = new Color(40, 167, 69);
|
|
// 步骤面板
|
private JPanel mainPanel;
|
private CardLayout cardLayout;
|
private JPanel stepsPanel;
|
|
// 步骤组件
|
private JTextField landNumberField;
|
private JTextField areaNameField;
|
private JComboBox<String> mowingPatternCombo;
|
private JSpinner mowingWidthSpinner;
|
private JPanel previewPanel;
|
private Map<String, JPanel> drawingOptionPanels = new HashMap<>();
|
|
// 步骤控制
|
private int currentStep = 1;
|
private JButton prevButton;
|
private JButton nextButton;
|
private JButton createButton;
|
private JLabel boundaryCountLabel;
|
private JPanel obstacleListContainer;
|
|
// 地块数据
|
private Map<String, String> dikuaiData = new HashMap<>();
|
private String createdLandNumber;
|
private String pendingLandNumber;
|
|
// 步骤2选项状态
|
private JPanel selectedOptionPanel = null;
|
private JButton startEndDrawingBtn;
|
private boolean isDrawing = false;
|
|
private static DrawingSession activeSession;
|
private static boolean resumeRequested;
|
private static final List<Point2D.Double> TEMP_HANDHELD_POINTS = new ArrayList<>();
|
|
private static final class BoundarySnapshotResult {
|
final boolean success;
|
final String errorMessage;
|
final String originalBoundary;
|
final String optimizedBoundary;
|
final String areaSqMeters;
|
final String baseStationCoordinates;
|
final int messageType;
|
|
private BoundarySnapshotResult(boolean success,
|
String errorMessage,
|
String originalBoundary,
|
String optimizedBoundary,
|
String areaSqMeters,
|
String baseStationCoordinates,
|
int messageType) {
|
this.success = success;
|
this.errorMessage = errorMessage;
|
this.originalBoundary = originalBoundary;
|
this.optimizedBoundary = optimizedBoundary;
|
this.areaSqMeters = areaSqMeters;
|
this.baseStationCoordinates = baseStationCoordinates;
|
this.messageType = messageType;
|
}
|
|
static BoundarySnapshotResult success(String original, String optimized, String area, String baseStation) {
|
return new BoundarySnapshotResult(true, null, original, optimized, area, baseStation, JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
static BoundarySnapshotResult failure(String message, int messageType) {
|
return new BoundarySnapshotResult(false, message, null, null, null, null, messageType);
|
}
|
}
|
|
private static class DrawingSession {
|
String landNumber;
|
String areaName;
|
Map<String, String> data = new HashMap<>();
|
boolean drawingCompleted;
|
}
|
|
public static void recordTemporaryBoundaryPoints(List<Point2D.Double> points) {
|
synchronized (TEMP_HANDHELD_POINTS) {
|
TEMP_HANDHELD_POINTS.clear();
|
if (points == null) {
|
return;
|
}
|
for (Point2D.Double point : points) {
|
if (point == null) {
|
continue;
|
}
|
TEMP_HANDHELD_POINTS.add(new Point2D.Double(point.x, point.y));
|
}
|
}
|
}
|
|
public static List<Point2D.Double> getTemporaryBoundaryPointsSnapshot() {
|
synchronized (TEMP_HANDHELD_POINTS) {
|
return new ArrayList<>(TEMP_HANDHELD_POINTS);
|
}
|
}
|
|
public AddDikuai(JFrame parent) {
|
super(parent, "新增地块", true);
|
Dikuai.initFromProperties();
|
initializeUI();
|
setupEventHandlers();
|
}
|
|
public AddDikuai(JDialog parent) {
|
super(parent, "新增地块", true);
|
Dikuai.initFromProperties();
|
initializeUI();
|
setupEventHandlers();
|
}
|
|
private void initializeUI() {
|
setLayout(new BorderLayout());
|
setBackground(WHITE);
|
|
// 统一使用 6.5 寸竖屏适配尺寸
|
setSize(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
|
setLocationRelativeTo(getParent());
|
setResizable(false);
|
|
createMainPanel();
|
add(mainPanel, BorderLayout.CENTER);
|
}
|
|
private void createMainPanel() {
|
mainPanel = new JPanel(new BorderLayout());
|
mainPanel.setBackground(WHITE);
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
|
// 步骤内容面板
|
cardLayout = new CardLayout();
|
stepsPanel = new JPanel(cardLayout);
|
stepsPanel.setBackground(WHITE);
|
stepsPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
|
|
stepsPanel.add(createStep1Panel(), "step1");
|
stepsPanel.add(createStep2Panel(), "step2");
|
stepsPanel.add(createStep3Panel(), "step3");
|
|
mainPanel.add(stepsPanel, BorderLayout.CENTER);
|
|
// 按钮面板
|
mainPanel.add(createButtonPanel(), BorderLayout.SOUTH);
|
|
// 显示第一步
|
showStep(1);
|
}
|
|
private JPanel createStep1Panel() {
|
JPanel stepPanel = new JPanel();
|
stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
|
stepPanel.setBackground(WHITE);
|
stepPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
|
// 步骤标题 - 左对齐
|
JLabel stepTitle = new JLabel("步骤1:基本信息");
|
stepTitle.setFont(new Font("微软雅黑", Font.BOLD, 20));
|
stepTitle.setForeground(TEXT_COLOR);
|
stepTitle.setAlignmentX(Component.LEFT_ALIGNMENT);
|
stepPanel.add(stepTitle);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
|
// 表单组
|
JPanel formGroup = new JPanel();
|
formGroup.setLayout(new BoxLayout(formGroup, BoxLayout.Y_AXIS));
|
formGroup.setBackground(WHITE);
|
formGroup.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel numberLabel = new JLabel("地块编号");
|
numberLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
numberLabel.setForeground(TEXT_COLOR);
|
numberLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
landNumberField = new JTextField();
|
landNumberField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
landNumberField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 50));
|
landNumberField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(12, 15, 12, 15)
|
));
|
landNumberField.setEditable(false);
|
landNumberField.setFocusable(false);
|
landNumberField.setBackground(LIGHT_GRAY);
|
landNumberField.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel nameLabel = new JLabel("地块名称");
|
nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
nameLabel.setForeground(TEXT_COLOR);
|
nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
areaNameField = new JTextField();
|
areaNameField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
areaNameField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 50));
|
areaNameField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(12, 15, 12, 15)
|
));
|
areaNameField.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
// 添加输入框焦点效果
|
areaNameField.addFocusListener(new FocusAdapter() {
|
@Override
|
public void focusGained(FocusEvent e) {
|
areaNameField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
|
BorderFactory.createEmptyBorder(12, 15, 12, 15)
|
));
|
}
|
|
@Override
|
public void focusLost(FocusEvent e) {
|
areaNameField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(12, 15, 12, 15)
|
));
|
}
|
});
|
|
JLabel hintLabel = new JLabel("例如: 前院草坪、后院草坪等");
|
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
hintLabel.setForeground(LIGHT_TEXT);
|
hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
formGroup.add(numberLabel);
|
formGroup.add(Box.createRigidArea(new Dimension(0, 10)));
|
formGroup.add(landNumberField);
|
formGroup.add(Box.createRigidArea(new Dimension(0, 18)));
|
formGroup.add(nameLabel);
|
formGroup.add(Box.createRigidArea(new Dimension(0, 10)));
|
formGroup.add(areaNameField);
|
formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
|
formGroup.add(hintLabel);
|
|
formGroup.add(Box.createRigidArea(new Dimension(0, 20)));
|
JPanel obstacleSection = createObstacleSummarySection();
|
formGroup.add(obstacleSection);
|
|
stepPanel.add(formGroup);
|
stepPanel.add(Box.createVerticalGlue());
|
|
landNumberField.setText(getPendingLandNumber());
|
|
return stepPanel;
|
}
|
|
private JPanel createObstacleSummarySection() {
|
JPanel section = new JPanel();
|
section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS));
|
section.setOpaque(false);
|
section.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel title = new JLabel("已有障碍物");
|
title.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
title.setForeground(TEXT_COLOR);
|
title.setAlignmentX(Component.LEFT_ALIGNMENT);
|
section.add(title);
|
section.add(Box.createRigidArea(new Dimension(0, 8)));
|
|
obstacleListContainer = new JPanel();
|
obstacleListContainer.setLayout(new BoxLayout(obstacleListContainer, BoxLayout.Y_AXIS));
|
obstacleListContainer.setOpaque(false);
|
obstacleListContainer.setAlignmentX(Component.LEFT_ALIGNMENT);
|
section.add(obstacleListContainer);
|
|
updateObstacleSummary();
|
|
return section;
|
}
|
|
private void updateObstacleSummary() {
|
if (obstacleListContainer == null) {
|
return;
|
}
|
|
obstacleListContainer.removeAll();
|
List<ObstacleSummary> summaries = loadExistingObstacles();
|
|
if (summaries.isEmpty()) {
|
JLabel emptyLabel = new JLabel("暂无障碍物");
|
emptyLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
emptyLabel.setForeground(LIGHT_TEXT);
|
emptyLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
obstacleListContainer.add(emptyLabel);
|
} else {
|
int index = 1;
|
for (ObstacleSummary summary : summaries) {
|
JPanel row = new JPanel(new BorderLayout());
|
row.setOpaque(false);
|
row.setAlignmentX(Component.LEFT_ALIGNMENT);
|
row.setBorder(BorderFactory.createEmptyBorder(6, 0, 6, 0));
|
|
String labelText = String.format(Locale.CHINA, "%02d. %s", index++, summary.getName());
|
JLabel nameLabel = new JLabel(labelText);
|
nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
nameLabel.setForeground(TEXT_COLOR);
|
|
JLabel coordLabel = new JLabel(summary.getDisplayCoords());
|
coordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
coordLabel.setForeground(LIGHT_TEXT);
|
coordLabel.setToolTipText(summary.getFullCoords());
|
|
row.add(nameLabel, BorderLayout.WEST);
|
row.add(coordLabel, BorderLayout.CENTER);
|
obstacleListContainer.add(row);
|
}
|
}
|
|
obstacleListContainer.revalidate();
|
obstacleListContainer.repaint();
|
}
|
|
private List<ObstacleSummary> loadExistingObstacles() {
|
List<ObstacleSummary> summaries = new ArrayList<>();
|
List<ExistingObstacle> obstacles = fetchExistingObstacleDetails();
|
|
if (obstacles.isEmpty()) {
|
return summaries;
|
}
|
|
for (ExistingObstacle obstacle : obstacles) {
|
if (obstacle == null) {
|
continue;
|
}
|
String name = obstacle.getName();
|
if (!isMeaningfulValue(name)) {
|
continue;
|
}
|
String coords = obstacle.getDisplayCoordinates();
|
String fullCoords = isMeaningfulValue(coords) ? coords.trim() : "";
|
String preview = buildCoordinatePreview(fullCoords, 20);
|
summaries.add(new ObstacleSummary(name.trim(), fullCoords, preview));
|
}
|
|
return summaries;
|
}
|
|
private List<ExistingObstacle> fetchExistingObstacleDetails() {
|
File configFile = new File("Obstacledge.properties");
|
if (!configFile.exists()) {
|
return Collections.emptyList();
|
}
|
|
List<ExistingObstacle> result = new ArrayList<>();
|
try {
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return Collections.emptyList();
|
}
|
|
for (Obstacledge.Plot plot : manager.getPlots()) {
|
if (plot == null) {
|
continue;
|
}
|
String landNumber = isMeaningfulValue(plot.getPlotId()) ? plot.getPlotId().trim() : "";
|
List<Obstacledge.Obstacle> plotObstacles = plot.getObstacles();
|
if (plotObstacles == null || plotObstacles.isEmpty()) {
|
continue;
|
}
|
for (Obstacledge.Obstacle obstacle : plotObstacles) {
|
if (obstacle == null) {
|
continue;
|
}
|
String name = obstacle.getObstacleName();
|
if (!isMeaningfulValue(name)) {
|
continue;
|
}
|
String coords = extractObstacleCoordinates(obstacle);
|
result.add(new ExistingObstacle(landNumber, name.trim(), coords));
|
}
|
}
|
} catch (Exception ex) {
|
System.err.println("加载已有障碍物失败: " + ex.getMessage());
|
return Collections.emptyList();
|
}
|
|
if (result.isEmpty()) {
|
return Collections.emptyList();
|
}
|
|
result.sort(Comparator.comparing(ExistingObstacle::getName, String.CASE_INSENSITIVE_ORDER));
|
return result;
|
}
|
|
private String extractObstacleCoordinates(Obstacledge.Obstacle obstacle) {
|
if (obstacle == null) {
|
return "";
|
}
|
String xy = obstacle.getXyCoordsString();
|
if (isMeaningfulValue(xy)) {
|
return xy.trim();
|
}
|
String original = obstacle.getOriginalCoordsString();
|
if (isMeaningfulValue(original)) {
|
return original.trim();
|
}
|
return "";
|
}
|
|
private String buildCoordinatePreview(String coords, int keepLength) {
|
if (!isMeaningfulValue(coords)) {
|
return "无坐标";
|
}
|
String sanitized = sanitizeCoordinateString(coords);
|
if (sanitized.length() <= keepLength) {
|
return sanitized;
|
}
|
if (keepLength <= 0) {
|
return "...";
|
}
|
return sanitized.substring(0, keepLength) + "...";
|
}
|
|
private List<String> splitObstacleEntries(String data) {
|
List<String> entries = new ArrayList<>();
|
if (data.indexOf('|') >= 0) {
|
String[] parts = data.split("\\|");
|
for (String part : parts) {
|
if (part != null && !part.trim().isEmpty()) {
|
entries.add(part.trim());
|
}
|
}
|
} else if (data.contains("\n")) {
|
String[] lines = data.split("\r?\n");
|
for (String line : lines) {
|
if (line != null && !line.trim().isEmpty()) {
|
entries.add(line.trim());
|
}
|
}
|
} else {
|
entries.add(data);
|
}
|
return entries;
|
}
|
|
private String stripInlineComment(String text) {
|
if (text == null) {
|
return "";
|
}
|
int hashIndex = text.indexOf('#');
|
if (hashIndex >= 0) {
|
return text.substring(0, hashIndex).trim();
|
}
|
return text.trim();
|
}
|
|
private boolean looksLikeShapeToken(String token) {
|
if (token == null) {
|
return false;
|
}
|
String normalized = token.trim().toLowerCase(Locale.ROOT);
|
return "circle".equals(normalized)
|
|| "polygon".equals(normalized)
|
|| "圆形".equals(normalized)
|
|| "多边形".equals(normalized)
|
|| "0".equals(normalized)
|
|| "1".equals(normalized);
|
}
|
|
private String sanitizeCoordinateString(String value) {
|
if (value == null) {
|
return "";
|
}
|
String stripped = stripInlineComment(value);
|
if (stripped.isEmpty()) {
|
return "";
|
}
|
return stripped.replace("\r", "").replace("\n", "").replace("\t", "").replace(" ", "");
|
}
|
|
private String truncateCoordinateForDisplay(String coords, int maxLength) {
|
if (coords == null) {
|
return "";
|
}
|
String trimmed = coords.trim();
|
if (trimmed.length() <= maxLength) {
|
return trimmed;
|
}
|
if (maxLength <= 3) {
|
return trimmed.substring(0, maxLength);
|
}
|
return trimmed.substring(0, maxLength - 3) + "...";
|
}
|
|
private static final class ObstacleSummary {
|
private final String name;
|
private final String fullCoords;
|
private final String displayCoords;
|
|
ObstacleSummary(String name, String fullCoords, String displayCoords) {
|
this.name = name;
|
this.fullCoords = fullCoords;
|
this.displayCoords = displayCoords;
|
}
|
|
public String getName() {
|
return name;
|
}
|
|
public String getFullCoords() {
|
return fullCoords;
|
}
|
|
public String getDisplayCoords() {
|
return displayCoords;
|
}
|
}
|
|
private static final class ExistingObstacle {
|
private final String landNumber;
|
private final String name;
|
private final String coordinates;
|
|
ExistingObstacle(String landNumber, String name, String coordinates) {
|
this.landNumber = landNumber != null ? landNumber : "";
|
this.name = name != null ? name : "";
|
this.coordinates = coordinates != null ? coordinates : "";
|
}
|
|
String getLandNumber() {
|
return landNumber;
|
}
|
|
String getName() {
|
return name;
|
}
|
|
String getDisplayCoordinates() {
|
return coordinates;
|
}
|
}
|
|
private JPanel createStep2Panel() {
|
JPanel stepPanel = new JPanel();
|
stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
|
stepPanel.setBackground(WHITE);
|
stepPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
|
// 步骤标题 - 左对齐
|
JLabel stepTitle = new JLabel("步骤2:绘制边界");
|
stepTitle.setFont(new Font("微软雅黑", Font.BOLD, 20));
|
stepTitle.setForeground(TEXT_COLOR);
|
stepTitle.setAlignmentX(Component.LEFT_ALIGNMENT);
|
stepPanel.add(stepTitle);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
|
// 步骤说明
|
JPanel instructionPanel = createInstructionPanel(
|
"请选择绘制边界的方式。割草机绘制需要驾驶割草机沿边界行驶," +
|
"手持设备绘制则使用便携设备标记边界点。"
|
);
|
instructionPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
stepPanel.add(instructionPanel);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 25)));
|
|
// 绘制方式选择
|
JLabel methodLabel = new JLabel("选择绘制方式");
|
methodLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
methodLabel.setForeground(TEXT_COLOR);
|
methodLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
stepPanel.add(methodLabel);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 绘制选项面板 - 垂直布局以完整显示图标
|
JPanel optionsPanel = new JPanel();
|
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.Y_AXIS));
|
optionsPanel.setBackground(WHITE);
|
optionsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
// 割草机绘制选项
|
JPanel mowerOption = createDrawingOption("🚜", "割草机绘制",
|
"", "mower");
|
mowerOption.setAlignmentX(Component.LEFT_ALIGNMENT);
|
mowerOption.setBorder(BorderFactory.createLineBorder(BORDER_COLOR, 2));
|
|
// 手持设备绘制选项
|
JPanel handheldOption = createDrawingOption("📱", "手持设备绘制",
|
"", "handheld");
|
handheldOption.setAlignmentX(Component.LEFT_ALIGNMENT);
|
handheldOption.setBorder(BorderFactory.createLineBorder(BORDER_COLOR, 2));
|
|
optionsPanel.add(mowerOption);
|
optionsPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
optionsPanel.add(handheldOption);
|
|
stepPanel.add(optionsPanel);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 30)));
|
|
// 开始/结束绘制按钮
|
startEndDrawingBtn = createPrimaryButton("开始绘制", 16);
|
startEndDrawingBtn.setAlignmentX(Component.LEFT_ALIGNMENT);
|
startEndDrawingBtn.setMaximumSize(new Dimension(400, 55));
|
startEndDrawingBtn.setEnabled(false); // 初始不可用
|
|
startEndDrawingBtn.addActionListener(e -> toggleDrawing());
|
|
stepPanel.add(startEndDrawingBtn);
|
boundaryCountLabel = new JLabel("已采集到边界点0个");
|
boundaryCountLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
boundaryCountLabel.setForeground(LIGHT_TEXT);
|
boundaryCountLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
boundaryCountLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
|
boundaryCountLabel.setVisible(false);
|
stepPanel.add(boundaryCountLabel);
|
stepPanel.add(Box.createVerticalGlue());
|
|
return stepPanel;
|
}
|
|
private JPanel createDrawingOption(String icon, String text, String description, String type) {
|
JPanel optionPanel = new JPanel(new BorderLayout(15, 0));
|
optionPanel.setBackground(WHITE);
|
optionPanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
optionPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
optionPanel.setMaximumSize(new Dimension(500, 120));
|
|
// 图标区域
|
JLabel iconLabel;
|
if ("handheld".equals(type) || "mower".equals(type)) {
|
iconLabel = new JLabel();
|
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
iconLabel.setPreferredSize(new Dimension(80, 80));
|
String iconPath = "handheld".equals(type) ? "image/URT.png" : "image/mow.png";
|
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));
|
} else {
|
iconLabel.setText(icon);
|
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 48));
|
}
|
} else {
|
iconLabel = new JLabel(icon, JLabel.CENTER);
|
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 48));
|
iconLabel.setPreferredSize(new Dimension(80, 80));
|
}
|
|
// 文本区域
|
JPanel textPanel = new JPanel(new GridBagLayout());
|
textPanel.setBackground(WHITE);
|
|
JLabel textLabel = new JLabel(text);
|
textLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
textLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
textLabel.setVerticalAlignment(SwingConstants.CENTER);
|
|
GridBagConstraints gbc = new GridBagConstraints();
|
gbc.gridx = 0;
|
gbc.gridy = 0;
|
gbc.weightx = 1;
|
gbc.weighty = description == null || description.trim().isEmpty() ? 1 : 0;
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
gbc.anchor = GridBagConstraints.CENTER;
|
textPanel.add(textLabel, gbc);
|
|
if (description != null && !description.trim().isEmpty()) {
|
JLabel descLabel = new JLabel(description);
|
descLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
descLabel.setForeground(LIGHT_TEXT);
|
descLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
|
GridBagConstraints descGbc = new GridBagConstraints();
|
descGbc.gridx = 0;
|
descGbc.gridy = 1;
|
descGbc.weightx = 1;
|
descGbc.weighty = 1;
|
descGbc.fill = GridBagConstraints.HORIZONTAL;
|
descGbc.anchor = GridBagConstraints.CENTER;
|
textPanel.add(descLabel, descGbc);
|
}
|
|
optionPanel.putClientProperty("titleLabel", textLabel);
|
|
optionPanel.add(iconLabel, BorderLayout.WEST);
|
optionPanel.add(textPanel, BorderLayout.CENTER);
|
|
// 添加点击事件
|
optionPanel.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
if (!optionPanel.isEnabled()) {
|
return;
|
}
|
selectDrawingOption(optionPanel, type);
|
startEndDrawingBtn.setEnabled(true); // 选择后启用按钮
|
}
|
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (optionPanel != selectedOptionPanel) {
|
optionPanel.setBackground(new Color(245, 245, 245));
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (optionPanel != selectedOptionPanel) {
|
optionPanel.setBackground(WHITE);
|
}
|
}
|
});
|
|
drawingOptionPanels.put(type, optionPanel);
|
return optionPanel;
|
}
|
|
private void selectDrawingOption(JPanel optionPanel, String type) {
|
// 重置之前选中的选项
|
if (selectedOptionPanel != null) {
|
selectedOptionPanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR, 2));
|
selectedOptionPanel.setBackground(WHITE);
|
Object oldTitle = selectedOptionPanel.getClientProperty("titleLabel");
|
if (oldTitle instanceof JLabel) {
|
((JLabel) oldTitle).setForeground(TEXT_COLOR);
|
}
|
}
|
|
// 设置新的选中状态
|
optionPanel.setBorder(BorderFactory.createLineBorder(PRIMARY_COLOR, 3));
|
optionPanel.setBackground(PRIMARY_LIGHT);
|
Object titleObj = optionPanel.getClientProperty("titleLabel");
|
if (titleObj instanceof JLabel) {
|
((JLabel) titleObj).setForeground(PRIMARY_COLOR);
|
}
|
selectedOptionPanel = optionPanel;
|
|
// 保存选择
|
dikuaiData.put("drawingMethod", type);
|
}
|
|
private void toggleDrawing() {
|
if (!isDrawing) {
|
if (!prepareDrawingSession()) {
|
return;
|
}
|
isDrawing = true;
|
hideBoundaryPointSummary();
|
Coordinate.setStartSaveGngga(true);
|
startEndDrawingBtn.setText("结束绘制");
|
startEndDrawingBtn.setBackground(new Color(220, 53, 69));
|
updateOtherOptionsState(true);
|
|
if (!startDrawingBoundary()) {
|
Coordinate.setStartSaveGngga(false);
|
resetDrawingState();
|
}
|
} else {
|
// 用户在对话框内主动结束(未触发外部流程)
|
isDrawing = false;
|
Coordinate.setStartSaveGngga(false);
|
startEndDrawingBtn.setText("开始绘制");
|
startEndDrawingBtn.setBackground(PRIMARY_COLOR);
|
updateOtherOptionsState(false);
|
dikuaiData.put("boundaryDrawn", "true");
|
JOptionPane.showMessageDialog(this, "边界绘制已完成", "提示", JOptionPane.INFORMATION_MESSAGE);
|
showBoundaryPointSummary();
|
}
|
}
|
|
private boolean prepareDrawingSession() {
|
String areaName = areaNameField.getText().trim();
|
if (areaName.isEmpty()) {
|
JOptionPane.showMessageDialog(this, "请先填写地块名称", "提示", JOptionPane.WARNING_MESSAGE);
|
areaNameField.requestFocus();
|
return false;
|
}
|
if (!dikuaiData.containsKey("drawingMethod")) {
|
JOptionPane.showMessageDialog(this, "请选择绘制方式", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
dikuaiData.put("areaName", areaName);
|
|
String landNumber = getPendingLandNumber();
|
if (activeSession == null) {
|
activeSession = new DrawingSession();
|
}
|
activeSession.landNumber = landNumber;
|
activeSession.areaName = areaName;
|
activeSession.drawingCompleted = false;
|
activeSession.data = new HashMap<>(dikuaiData);
|
|
return true;
|
}
|
|
private void updateOtherOptionsState(boolean disable) {
|
if (selectedOptionPanel == null) {
|
return;
|
}
|
Component[] components = selectedOptionPanel.getParent().getComponents();
|
for (Component comp : components) {
|
if (comp instanceof JPanel && comp != selectedOptionPanel) {
|
comp.setEnabled(!disable);
|
((JPanel) comp).setBackground(disable ? LIGHT_GRAY : WHITE);
|
}
|
}
|
}
|
|
private void resetDrawingState() {
|
isDrawing = false;
|
Coordinate.setStartSaveGngga(false);
|
startEndDrawingBtn.setText("开始绘制");
|
startEndDrawingBtn.setBackground(PRIMARY_COLOR);
|
updateOtherOptionsState(false);
|
hideBoundaryPointSummary();
|
}
|
|
private JPanel createStep3Panel() {
|
JPanel stepPanel = new JPanel();
|
stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
|
stepPanel.setBackground(WHITE);
|
stepPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
|
|
// 步骤标题 - 左对齐
|
JLabel stepTitle = new JLabel("步骤3:生成割草路径");
|
stepTitle.setFont(new Font("微软雅黑", Font.BOLD, 20));
|
stepTitle.setForeground(TEXT_COLOR);
|
stepTitle.setAlignmentX(Component.LEFT_ALIGNMENT);
|
stepPanel.add(stepTitle);
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
|
// 设置面板容器
|
JPanel settingsPanel = new JPanel();
|
settingsPanel.setLayout(new BoxLayout(settingsPanel, BoxLayout.Y_AXIS));
|
settingsPanel.setBackground(WHITE);
|
settingsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
// 割草模式选择
|
JPanel patternPanel = createFormGroup("割草模式", "选择割草路径的生成模式");
|
mowingPatternCombo = new JComboBox<>(new String[]{"平行线", "螺旋式"});
|
mowingPatternCombo.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
mowingPatternCombo.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
|
mowingPatternCombo.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
mowingPatternCombo.setSelectedIndex(0);
|
mowingPatternCombo.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
// 添加下拉框焦点效果
|
mowingPatternCombo.addFocusListener(new FocusAdapter() {
|
@Override
|
public void focusGained(FocusEvent e) {
|
mowingPatternCombo.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
|
@Override
|
public void focusLost(FocusEvent e) {
|
mowingPatternCombo.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
});
|
|
patternPanel.add(mowingPatternCombo);
|
settingsPanel.add(patternPanel);
|
settingsPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
|
// 割草宽度设置
|
JPanel widthPanel = createFormGroup("割草宽度", "设置割草机单次割草的宽度");
|
JPanel widthInputPanel = new JPanel(new BorderLayout());
|
widthInputPanel.setBackground(WHITE);
|
widthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
|
widthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
SpinnerNumberModel widthModel = new SpinnerNumberModel(40, 20, 60, 1);
|
mowingWidthSpinner = new JSpinner(widthModel);
|
mowingWidthSpinner.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) mowingWidthSpinner.getEditor();
|
editor.getTextField().setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
|
// 添加微调器焦点效果
|
mowingWidthSpinner.addFocusListener(new FocusAdapter() {
|
@Override
|
public void focusGained(FocusEvent e) {
|
editor.getTextField().setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
|
@Override
|
public void focusLost(FocusEvent e) {
|
editor.getTextField().setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
});
|
|
JLabel unitLabel = new JLabel("厘米");
|
unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
unitLabel.setForeground(LIGHT_TEXT);
|
unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
|
|
widthInputPanel.add(mowingWidthSpinner, BorderLayout.CENTER);
|
widthInputPanel.add(unitLabel, BorderLayout.EAST);
|
|
widthPanel.add(widthInputPanel);
|
settingsPanel.add(widthPanel);
|
settingsPanel.add(Box.createRigidArea(new Dimension(0, 25)));
|
|
stepPanel.add(settingsPanel);
|
|
JButton generatePathButton = createPrimaryButton("生成割草路径", 16);
|
generatePathButton.setAlignmentX(Component.LEFT_ALIGNMENT);
|
generatePathButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, 55));
|
generatePathButton.addActionListener(e -> generateMowingPath());
|
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
stepPanel.add(generatePathButton);
|
stepPanel.add(Box.createVerticalGlue());
|
|
return stepPanel;
|
}
|
|
private JPanel createInstructionPanel(String text) {
|
JPanel instructionPanel = new JPanel(new BorderLayout());
|
instructionPanel.setBackground(PRIMARY_LIGHT);
|
instructionPanel.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createMatteBorder(0, 5, 0, 0, PRIMARY_COLOR),
|
BorderFactory.createEmptyBorder(12, 12, 12, 12)
|
));
|
instructionPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 70));
|
|
JLabel iconLabel = new JLabel("💡");
|
iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 16));
|
iconLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
|
|
JTextArea instructionText = new JTextArea(text);
|
instructionText.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
instructionText.setForeground(TEXT_COLOR);
|
instructionText.setBackground(PRIMARY_LIGHT);
|
instructionText.setLineWrap(true);
|
instructionText.setWrapStyleWord(true);
|
instructionText.setEditable(false);
|
|
instructionPanel.add(iconLabel, BorderLayout.WEST);
|
instructionPanel.add(instructionText, BorderLayout.CENTER);
|
|
return instructionPanel;
|
}
|
|
private JPanel createFormGroup(String label, String hint) {
|
JPanel formGroup = new JPanel();
|
formGroup.setLayout(new BoxLayout(formGroup, BoxLayout.Y_AXIS));
|
formGroup.setBackground(WHITE);
|
formGroup.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel nameLabel = new JLabel(label);
|
nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
nameLabel.setForeground(TEXT_COLOR);
|
nameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JLabel hintLabel = new JLabel(hint);
|
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
hintLabel.setForeground(LIGHT_TEXT);
|
hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
formGroup.add(nameLabel);
|
formGroup.add(Box.createRigidArea(new Dimension(0, 6)));
|
formGroup.add(hintLabel);
|
formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
|
|
return formGroup;
|
}
|
|
private void generateMowingPath() {
|
if (!dikuaiData.containsKey("boundaryDrawn")) {
|
JOptionPane.showMessageDialog(this, "请先完成边界绘制后再生成路径", "提示", JOptionPane.WARNING_MESSAGE);
|
showStep(2);
|
return;
|
}
|
Dikuai dikuai = getOrCreatePendingDikuai();
|
String boundaryCoords = null;
|
if (dikuai != null) {
|
boundaryCoords = normalizeCoordinateValue(dikuai.getBoundaryCoordinates());
|
}
|
if (boundaryCoords == null) {
|
boundaryCoords = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates"));
|
}
|
if (boundaryCoords == null) {
|
JOptionPane.showMessageDialog(this, "未找到有效的地块边界坐标,无法生成路径", "提示", JOptionPane.WARNING_MESSAGE);
|
if (createButton != null) {
|
createButton.setEnabled(false);
|
}
|
return;
|
}
|
|
String obstacleCoords = null;
|
String landNumber = getPendingLandNumber();
|
if (isMeaningfulValue(landNumber)) {
|
obstacleCoords = normalizeCoordinateValue(resolveObstaclePayloadFromConfig(landNumber));
|
}
|
if (obstacleCoords == null) {
|
obstacleCoords = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates"));
|
}
|
|
String patternDisplay = (String) mowingPatternCombo.getSelectedItem();
|
dikuaiData.put("mowingPattern", patternDisplay);
|
|
Object widthObj = mowingWidthSpinner.getValue();
|
if (!(widthObj instanceof Number)) {
|
JOptionPane.showMessageDialog(this, "割草宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE);
|
if (createButton != null) {
|
createButton.setEnabled(false);
|
}
|
return;
|
}
|
double widthCm = ((Number) widthObj).doubleValue();
|
if (widthCm <= 0) {
|
JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
|
if (createButton != null) {
|
createButton.setEnabled(false);
|
}
|
return;
|
}
|
dikuaiData.put("mowingWidth", widthObj.toString());
|
|
String widthMeters = String.format(Locale.US, "%.2f", widthCm / 100.0);
|
String plannerMode = resolvePlannerMode(patternDisplay);
|
|
try {
|
List<Lunjingguihua.PathSegment> segments = Lunjingguihua.generatePathSegments(
|
boundaryCoords,
|
obstacleCoords,
|
widthMeters,
|
plannerMode
|
);
|
String plannedPath = Lunjingguihua.formatPathSegments(segments);
|
if (!isMeaningfulValue(plannedPath)) {
|
JOptionPane.showMessageDialog(this, "生成割草路径失败: 生成结果为空", "错误", JOptionPane.ERROR_MESSAGE);
|
if (createButton != null) {
|
createButton.setEnabled(false);
|
}
|
return;
|
}
|
dikuaiData.put("plannedPath", plannedPath);
|
if (createButton != null) {
|
createButton.setEnabled(true);
|
}
|
JOptionPane.showMessageDialog(this,
|
"已根据当前设置生成割草路径,共生成 " + segments.size() + " 段。",
|
"成功",
|
JOptionPane.INFORMATION_MESSAGE);
|
} catch (IllegalArgumentException ex) {
|
JOptionPane.showMessageDialog(this, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
|
if (createButton != null) {
|
createButton.setEnabled(false);
|
}
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
JOptionPane.showMessageDialog(this, "生成割草路径时发生异常: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
|
if (createButton != null) {
|
createButton.setEnabled(false);
|
}
|
}
|
}
|
|
private JButton createPrimaryButton(String text, int fontSize) {
|
JButton button = new JButton(text);
|
button.setFont(new Font("微软雅黑", Font.BOLD, fontSize));
|
button.setBackground(PRIMARY_COLOR);
|
button.setForeground(WHITE);
|
button.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_DARK, 2),
|
BorderFactory.createEmptyBorder(12, 25, 12, 25)
|
));
|
button.setFocusPainted(false);
|
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
|
// 按钮悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (button.isEnabled()) {
|
button.setBackground(PRIMARY_DARK);
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (button.isEnabled()) {
|
button.setBackground(PRIMARY_COLOR);
|
}
|
}
|
});
|
|
return button;
|
}
|
|
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 = new JButton("上一步");
|
prevButton.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
prevButton.setBackground(MEDIUM_GRAY);
|
prevButton.setForeground(TEXT_COLOR);
|
prevButton.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 25, 10, 25)
|
));
|
prevButton.setFocusPainted(false);
|
prevButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
|
nextButton = createPrimaryButton("下一步", 16);
|
createButton = createPrimaryButton("保存", 16);
|
createButton.setVisible(false);
|
createButton.setEnabled(false);
|
|
buttonPanel.add(prevButton);
|
buttonPanel.add(Box.createHorizontalGlue());
|
buttonPanel.add(nextButton);
|
buttonPanel.add(Box.createHorizontalStrut(15));
|
buttonPanel.add(createButton);
|
|
return buttonPanel;
|
}
|
|
private void showBoundaryPointSummary() {
|
if (boundaryCountLabel == null) {
|
return;
|
}
|
int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0;
|
double area = jisuanmianjie.calculatePolygonArea();
|
DecimalFormat areaFormat = new DecimalFormat("0.00");
|
boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaFormat.format(area) + "㎡");
|
boundaryCountLabel.setVisible(true);
|
}
|
|
private boolean prepareBoundaryTransition() {
|
BoundarySnapshotResult snapshot = computeBoundarySnapshot();
|
if (!snapshot.success) {
|
if (snapshot.errorMessage != null) {
|
JOptionPane.showMessageDialog(this, snapshot.errorMessage, "提示", snapshot.messageType);
|
}
|
return false;
|
}
|
|
String landNumber = getPendingLandNumber();
|
Dikuai dikuai = getOrCreatePendingDikuai();
|
if (dikuai != null) {
|
dikuai.setBoundaryOriginalCoordinates(snapshot.originalBoundary);
|
dikuai.setBoundaryCoordinates(snapshot.optimizedBoundary);
|
dikuai.setLandArea(snapshot.areaSqMeters);
|
dikuai.setBaseStationCoordinates(snapshot.baseStationCoordinates);
|
dikuai.setUpdateTime(getCurrentTime());
|
Dikuai.putDikuai(landNumber, dikuai);
|
}
|
|
dikuaiData.put("boundaryOriginalCoordinates", snapshot.originalBoundary);
|
dikuaiData.put("boundaryCoordinates", snapshot.optimizedBoundary);
|
dikuaiData.put("landArea", snapshot.areaSqMeters);
|
dikuaiData.put("baseStationCoordinates", snapshot.baseStationCoordinates);
|
if (activeSession != null) {
|
activeSession.data.put("boundaryOriginalCoordinates", snapshot.originalBoundary);
|
activeSession.data.put("boundaryCoordinates", snapshot.optimizedBoundary);
|
activeSession.data.put("landArea", snapshot.areaSqMeters);
|
activeSession.data.put("baseStationCoordinates", snapshot.baseStationCoordinates);
|
}
|
|
return true;
|
}
|
|
private static String buildOriginalBoundaryString() {
|
if (Coordinate.coordinates == null || Coordinate.coordinates.isEmpty()) {
|
return "-1";
|
}
|
StringBuilder sb = new StringBuilder();
|
DecimalFormat latLonFormat = new DecimalFormat("0.000000");
|
DecimalFormat elevationFormat = new DecimalFormat("0.00");
|
for (Coordinate coord : Coordinate.coordinates) {
|
double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection());
|
double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection());
|
double elevation = coord.getElevation();
|
|
if (sb.length() > 0) {
|
sb.append(";");
|
}
|
sb.append(latLonFormat.format(lat)).append(",")
|
.append(latLonFormat.format(lon)).append(",")
|
.append(elevationFormat.format(elevation));
|
}
|
return sb.toString();
|
}
|
|
private static double convertToDecimalDegree(String dmm, String direction) {
|
if (dmm == null || dmm.isEmpty()) {
|
return 0.0;
|
}
|
try {
|
int dotIndex = dmm.indexOf('.');
|
if (dotIndex == -1) {
|
dotIndex = dmm.length();
|
}
|
if (dotIndex < 2) {
|
return 0.0;
|
}
|
|
int degrees = Integer.parseInt(dmm.substring(0, dotIndex - 2));
|
double minutes = Double.parseDouble(dmm.substring(dotIndex - 2));
|
double decimalDegrees = degrees + minutes / 60.0;
|
|
if (direction != null && ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction))) {
|
decimalDegrees = -decimalDegrees;
|
}
|
|
return decimalDegrees;
|
} catch (NumberFormatException ex) {
|
System.err.println("坐标格式转换错误: " + dmm);
|
return 0.0;
|
}
|
}
|
|
private Dikuai getOrCreatePendingDikuai() {
|
String landNumber = getPendingLandNumber();
|
if (landNumber == null) {
|
return null;
|
}
|
Dikuai dikuai = Dikuai.getDikuai(landNumber);
|
if (dikuai == null) {
|
dikuai = new Dikuai();
|
dikuai.setLandNumber(landNumber);
|
Dikuai.putDikuai(landNumber, dikuai);
|
}
|
return dikuai;
|
}
|
|
private void hideBoundaryPointSummary() {
|
if (boundaryCountLabel != null) {
|
boundaryCountLabel.setVisible(false);
|
}
|
}
|
|
private boolean hasGeneratedPath() {
|
return isMeaningfulValue(dikuaiData.get("plannedPath"));
|
}
|
|
private String resolveObstaclePayloadFromConfig(String landNumber) {
|
if (!isMeaningfulValue(landNumber)) {
|
return null;
|
}
|
try {
|
File configFile = new File("Obstacledge.properties");
|
if (!configFile.exists()) {
|
return null;
|
}
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return null;
|
}
|
Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
|
if (plot == null) {
|
return null;
|
}
|
return Obstacledge.buildPlannerPayload(plot.getObstacles());
|
} catch (Exception ex) {
|
System.err.println("加载障碍物配置失败: " + ex.getMessage());
|
return null;
|
}
|
}
|
|
private static String normalizeCoordinateValue(String value) {
|
return isMeaningfulValue(value) ? value.trim() : null;
|
}
|
|
private static boolean isMeaningfulValue(String value) {
|
if (value == null) {
|
return false;
|
}
|
String trimmed = value.trim();
|
return !trimmed.isEmpty() && !"-1".equals(trimmed);
|
}
|
|
private static BoundarySnapshotResult computeBoundarySnapshot() {
|
int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0;
|
if (count < 3) {
|
return BoundarySnapshotResult.failure("采集的边界点不足,无法生成地块边界", JOptionPane.WARNING_MESSAGE);
|
}
|
|
double area = jisuanmianjie.calculatePolygonArea();
|
if (area <= 0) {
|
return BoundarySnapshotResult.failure("当前地块面积为0,无法继续", JOptionPane.WARNING_MESSAGE);
|
}
|
|
Device device = new Device();
|
device.initFromProperties();
|
String baseStationCoordinates = normalizeCoordinateValue(device.getBaseStationCoordinates());
|
if (!isMeaningfulValue(baseStationCoordinates)) {
|
return BoundarySnapshotResult.failure("未获取到有效的基准站坐标,请先在基准站管理中设置", JOptionPane.WARNING_MESSAGE);
|
}
|
|
String optimizedBoundary;
|
try {
|
optimizedBoundary = bianjieguihua2.processCoordinateListAuto(baseStationCoordinates);
|
} catch (RuntimeException ex) {
|
ex.printStackTrace();
|
return BoundarySnapshotResult.failure("生成地块边界失败: " + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
|
}
|
|
String originalBoundary = buildOriginalBoundaryString();
|
DecimalFormat areaFormat = new DecimalFormat("0.00");
|
String areaString = areaFormat.format(area);
|
|
return BoundarySnapshotResult.success(originalBoundary, optimizedBoundary, areaString, baseStationCoordinates);
|
}
|
|
private String resolvePlannerMode(String patternDisplay) {
|
if (patternDisplay == null) {
|
return "parallel";
|
}
|
String trimmed = patternDisplay.trim();
|
if (trimmed.isEmpty()) {
|
return "parallel";
|
}
|
if ("螺旋式".equals(trimmed) || "spiral".equalsIgnoreCase(trimmed) || "1".equals(trimmed)) {
|
return "spiral";
|
}
|
return "parallel";
|
}
|
|
private void setupEventHandlers() {
|
// 上一步按钮
|
prevButton.addActionListener(e -> {
|
if (currentStep > 1) {
|
showStep(currentStep - 1);
|
}
|
});
|
|
// 下一步按钮
|
nextButton.addActionListener(e -> {
|
if (validateCurrentStep()) {
|
if (currentStep < 3) {
|
// 步骤2特殊验证:必须完成边界绘制才能进入下一步
|
if (currentStep == 2 && !dikuaiData.containsKey("boundaryDrawn")) {
|
JOptionPane.showMessageDialog(this, "请先完成边界绘制", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
if (currentStep == 2 && !prepareBoundaryTransition()) {
|
return;
|
}
|
showStep(currentStep + 1);
|
}
|
}
|
});
|
|
// 创建地块按钮
|
createButton.addActionListener(e -> createDikuai());
|
|
// 关闭对话框
|
addWindowListener(new WindowAdapter() {
|
@Override
|
public void windowClosing(WindowEvent e) {
|
dispose();
|
}
|
});
|
}
|
|
private void showStep(int step) {
|
currentStep = step;
|
cardLayout.show(stepsPanel, "step" + step);
|
|
if (step == 1) {
|
updateObstacleSummary();
|
}
|
|
// 更新按钮状态
|
updateButtonState(step);
|
}
|
|
private void updateButtonState(int step) {
|
prevButton.setVisible(step > 1);
|
|
if (step < 3) {
|
nextButton.setVisible(true);
|
createButton.setVisible(false);
|
createButton.setEnabled(false);
|
} else {
|
nextButton.setVisible(false);
|
createButton.setVisible(true);
|
createButton.setEnabled(hasGeneratedPath());
|
}
|
|
Container parent = prevButton.getParent();
|
if (parent != null) {
|
parent.revalidate();
|
parent.repaint();
|
}
|
}
|
|
private boolean validateCurrentStep() {
|
switch (currentStep) {
|
case 1:
|
String name = areaNameField.getText().trim();
|
if (name.isEmpty()) {
|
JOptionPane.showMessageDialog(this, "请输入地块名称", "提示", JOptionPane.WARNING_MESSAGE);
|
areaNameField.requestFocus();
|
return false;
|
}
|
String currentLandNumber = getPendingLandNumber();
|
if (isLandNameDuplicate(name, currentLandNumber)) {
|
JOptionPane.showMessageDialog(this, "地块名称已存在,请输入唯一名称", "提示", JOptionPane.WARNING_MESSAGE);
|
areaNameField.requestFocus();
|
return false;
|
}
|
dikuaiData.put("areaName", name);
|
break;
|
|
case 2:
|
if (!dikuaiData.containsKey("drawingMethod")) {
|
JOptionPane.showMessageDialog(this, "请选择绘制方式", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
// 步骤2的特殊验证:必须完成边界绘制
|
if (!dikuaiData.containsKey("boundaryDrawn")) {
|
JOptionPane.showMessageDialog(this, "请先完成边界绘制", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
break;
|
|
case 3:
|
dikuaiData.put("mowingPattern", (String) mowingPatternCombo.getSelectedItem());
|
dikuaiData.put("mowingWidth", mowingWidthSpinner.getValue().toString());
|
if (!hasGeneratedPath()) {
|
JOptionPane.showMessageDialog(this, "请先生成割草路径", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
break;
|
}
|
return true;
|
}
|
|
private boolean startDrawingBoundary() {
|
String method = dikuaiData.get("drawingMethod");
|
if ("mower".equals(method)) {
|
Shouye shouye = Shouye.getInstance();
|
if (shouye == null) {
|
JOptionPane.showMessageDialog(this, "无法进入主页面,请稍后重试", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
if (!shouye.startMowerBoundaryCapture()) {
|
JOptionPane.showMessageDialog(this, "未能开始割草机绘制,请确认设备状态和基准站设置后重试", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
closeForDrawingSession();
|
return true;
|
} else if ("handheld".equals(method)) {
|
if (closeForHandheldDrawingSession()) {
|
return true;
|
}
|
resetDrawingState();
|
return false;
|
}
|
return false;
|
}
|
|
private boolean captureGnssSnapshot() {
|
if (activeSession != null && activeSession.drawingCompleted) {
|
return false;
|
}
|
Object qualityObj = MowerLocationData.getProperty("positioningQuality");
|
if (!"4".equals(String.valueOf(qualityObj))) {
|
return false;
|
}
|
|
Object latObj = MowerLocationData.getProperty("latitude");
|
Object lonObj = MowerLocationData.getProperty("longitude");
|
if (!(latObj instanceof Number) || !(lonObj instanceof Number)) {
|
return false;
|
}
|
|
double latitude = ((Number) latObj).doubleValue();
|
double longitude = ((Number) lonObj).doubleValue();
|
|
if (!Double.isFinite(latitude) || !Double.isFinite(longitude)) {
|
return false;
|
}
|
|
File file = new File("huizhiibanjieGNSS.propertie");
|
try {
|
if (!file.exists()) {
|
File parent = file.getAbsoluteFile().getParentFile();
|
if (parent != null && !parent.exists()) {
|
parent.mkdirs();
|
}
|
file.createNewFile();
|
}
|
|
List<String> lines = Files.exists(file.toPath()) ? Files.readAllLines(file.toPath(), StandardCharsets.UTF_8) : new ArrayList<>();
|
String formatted = formatCoordinate(latitude, longitude);
|
if (lines.isEmpty()) {
|
lines.add(formatted);
|
} else {
|
String existing = lines.get(0).trim();
|
if (!existing.isEmpty()) {
|
existing = existing + ";" + formatted;
|
} else {
|
existing = formatted;
|
}
|
lines.set(0, existing);
|
}
|
|
Files.write(file.toPath(), lines, StandardCharsets.UTF_8);
|
appendCoordinateToSession(formatted);
|
return true;
|
} catch (IOException ex) {
|
ex.printStackTrace();
|
return false;
|
}
|
}
|
|
private void appendCoordinateToSession(String coordinate) {
|
if (coordinate == null || coordinate.isEmpty()) {
|
return;
|
}
|
if (activeSession != null && activeSession.drawingCompleted) {
|
return;
|
}
|
if (getPendingLandNumber() == null) {
|
return;
|
}
|
String existing = dikuaiData.get("boundaryOriginalCoordinates");
|
if ((existing == null || existing.isEmpty()) && activeSession != null) {
|
existing = activeSession.data.get("boundaryOriginalCoordinates");
|
}
|
String updated = mergeCoordinates(existing, coordinate);
|
|
dikuaiData.put("boundaryOriginalCoordinates", updated);
|
if (activeSession != null) {
|
activeSession.data.put("boundaryOriginalCoordinates", updated);
|
}
|
}
|
|
private String formatCoordinate(double latitude, double longitude) {
|
DecimalFormat df = new DecimalFormat("0.000000");
|
return df.format(latitude) + "," + df.format(longitude);
|
}
|
|
private String mergeCoordinates(String existing, String coordinate) {
|
if (existing == null || existing.trim().isEmpty() || "-1".equals(existing.trim())) {
|
return coordinate;
|
}
|
String trimmed = existing.trim();
|
if (trimmed.endsWith(";")) {
|
trimmed = trimmed.substring(0, trimmed.length() - 1);
|
}
|
if (trimmed.contains(coordinate)) {
|
return trimmed;
|
}
|
return trimmed + ";" + coordinate;
|
}
|
|
private String getPendingLandNumber() {
|
if (pendingLandNumber != null) {
|
updateLandNumberField(pendingLandNumber);
|
return pendingLandNumber;
|
}
|
if (activeSession != null && activeSession.landNumber != null) {
|
pendingLandNumber = activeSession.landNumber;
|
updateLandNumberField(pendingLandNumber);
|
return pendingLandNumber;
|
}
|
pendingLandNumber = generateNewLandNumber();
|
if (activeSession != null) {
|
activeSession.landNumber = pendingLandNumber;
|
}
|
updateLandNumberField(pendingLandNumber);
|
return pendingLandNumber;
|
}
|
|
private String generateNewLandNumber() {
|
Map<String, Dikuai> existing = Dikuai.getAllDikuai();
|
int attempt = 1;
|
while (true) {
|
String candidate = "LAND" + attempt;
|
if (!existing.containsKey(candidate)) {
|
return candidate;
|
}
|
attempt++;
|
}
|
}
|
|
private void updateLandNumberField(String value) {
|
if (landNumberField != null && value != null) {
|
landNumberField.setText(value);
|
}
|
}
|
|
private boolean isLandNameDuplicate(String name, String currentLandNumber) {
|
if (name == null || name.isEmpty()) {
|
return false;
|
}
|
Map<String, Dikuai> existing = Dikuai.getAllDikuai();
|
for (Map.Entry<String, Dikuai> entry : existing.entrySet()) {
|
String key = entry.getKey();
|
Dikuai dikuai = entry.getValue();
|
if (key == null || dikuai == null) {
|
continue;
|
}
|
if (key.equals(currentLandNumber)) {
|
continue;
|
}
|
String existingName = dikuai.getLandName();
|
if (existingName != null && !"-1".equals(existingName) && existingName.trim().equalsIgnoreCase(name.trim())) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private void closeForDrawingSession() {
|
isDrawing = false;
|
Shouye shouye = Shouye.getInstance();
|
if (shouye != null) {
|
shouye.showEndDrawingButton(AddDikuai::finishDrawingSession);
|
}
|
setVisible(false);
|
dispose();
|
}
|
|
private boolean closeForHandheldDrawingSession() {
|
isDrawing = false;
|
Coordinate.setStartSaveGngga(false);
|
Shouye shouye = Shouye.getInstance();
|
if (shouye == null) {
|
JOptionPane.showMessageDialog(this, "无法进入主页面,请稍后重试", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
boolean started = shouye.startHandheldBoundaryCapture();
|
if (!started) {
|
JOptionPane.showMessageDialog(this, "无法开始手持采集,请确认设备状态", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
setVisible(false);
|
dispose();
|
return true;
|
}
|
|
private void applySessionData(DrawingSession session) {
|
if (session == null) {
|
return;
|
}
|
pendingLandNumber = session.landNumber;
|
dikuaiData.clear();
|
dikuaiData.putAll(session.data);
|
updateLandNumberField(pendingLandNumber);
|
areaNameField.setText(session.areaName != null ? session.areaName : "");
|
|
String method = session.data.get("drawingMethod");
|
if (method != null) {
|
JPanel panel = drawingOptionPanels.get(method);
|
if (panel != null) {
|
selectDrawingOption(panel, method);
|
}
|
}
|
|
if (session.drawingCompleted) {
|
dikuaiData.put("boundaryDrawn", "true");
|
if (startEndDrawingBtn != null) {
|
startEndDrawingBtn.setText("已完成");
|
startEndDrawingBtn.setBackground(MEDIUM_GRAY);
|
startEndDrawingBtn.setEnabled(false);
|
}
|
updateOtherOptionsState(true);
|
isDrawing = false;
|
showStep(2);
|
showBoundaryPointSummary();
|
} else {
|
if (startEndDrawingBtn != null) {
|
startEndDrawingBtn.setText("开始绘制");
|
startEndDrawingBtn.setBackground(PRIMARY_COLOR);
|
startEndDrawingBtn.setEnabled(true);
|
}
|
updateOtherOptionsState(false);
|
showStep(1);
|
hideBoundaryPointSummary();
|
}
|
}
|
|
public static void finishDrawingSession() {
|
Shouye shouye = Shouye.getInstance();
|
List<Point2D.Double> previewPoints = shouye != null
|
? shouye.getHandheldTemporaryPointsSnapshot()
|
: Collections.emptyList();
|
recordTemporaryBoundaryPoints(previewPoints);
|
|
BoundarySnapshotResult snapshot = computeBoundarySnapshot();
|
if (snapshot.success && activeSession != null) {
|
activeSession.data.put("boundaryOriginalCoordinates", snapshot.originalBoundary);
|
activeSession.data.put("boundaryCoordinates", snapshot.optimizedBoundary);
|
activeSession.data.put("landArea", snapshot.areaSqMeters);
|
activeSession.data.put("baseStationCoordinates", snapshot.baseStationCoordinates);
|
}
|
|
if (shouye != null) {
|
shouye.hideEndDrawingButton();
|
}
|
|
if (activeSession == null) {
|
return;
|
}
|
|
activeSession.drawingCompleted = true;
|
activeSession.data.put("boundaryDrawn", "true");
|
Coordinate.setStartSaveGngga(false);
|
|
resumeRequested = true;
|
Component parent = shouye != null ? shouye : null;
|
showAddDikuaiDialog(parent);
|
}
|
|
private void createDikuai() {
|
if (!validateCurrentStep()) {
|
return;
|
}
|
|
String areaName = areaNameField.getText().trim();
|
dikuaiData.put("areaName", areaName);
|
|
String landNumber = getPendingLandNumber();
|
Dikuai dikuai = Dikuai.getDikuai(landNumber);
|
if (dikuai == null) {
|
dikuai = new Dikuai();
|
dikuai.setLandNumber(landNumber);
|
dikuai.setCreateTime(getCurrentTime());
|
} else if (dikuai.getCreateTime() == null || "-1".equals(dikuai.getCreateTime())) {
|
dikuai.setCreateTime(getCurrentTime());
|
}
|
|
dikuai.setLandName(areaName);
|
dikuai.setUpdateTime(getCurrentTime());
|
|
if (dikuaiData.containsKey("boundaryOriginalCoordinates")) {
|
dikuai.setBoundaryOriginalCoordinates(dikuaiData.get("boundaryOriginalCoordinates"));
|
}
|
if (dikuaiData.containsKey("boundaryCoordinates")) {
|
dikuai.setBoundaryCoordinates(dikuaiData.get("boundaryCoordinates"));
|
}
|
if (dikuaiData.containsKey("landArea")) {
|
dikuai.setLandArea(dikuaiData.get("landArea"));
|
}
|
if (dikuaiData.containsKey("baseStationCoordinates")) {
|
dikuai.setBaseStationCoordinates(dikuaiData.get("baseStationCoordinates"));
|
}
|
|
if (dikuaiData.containsKey("mowingPattern")) {
|
dikuai.setMowingPattern(dikuaiData.get("mowingPattern"));
|
}
|
if (dikuaiData.containsKey("mowingWidth")) {
|
dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
|
}
|
|
String plannedPath = dikuaiData.get("plannedPath");
|
if (isMeaningfulValue(plannedPath)) {
|
dikuai.setPlannedPath(plannedPath);
|
}
|
|
Dikuai.putDikuai(landNumber, dikuai);
|
Dikuai.saveToProperties();
|
|
JOptionPane.showMessageDialog(this,
|
"地块创建成功!\n地块编号: " + landNumber + "\n地块名称: " + areaName,
|
"成功",
|
JOptionPane.INFORMATION_MESSAGE);
|
|
createdLandNumber = landNumber;
|
pendingLandNumber = null;
|
if (activeSession != null && landNumber.equals(activeSession.landNumber)) {
|
activeSession = null;
|
}
|
resumeRequested = false;
|
Shouye shouye = Shouye.getInstance();
|
if (shouye != null) {
|
shouye.hideEndDrawingButton();
|
}
|
Dikuaiguanli.notifyExternalCreation(landNumber);
|
dispose();
|
}
|
|
private static String getCurrentTime() {
|
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
return sdf.format(new java.util.Date());
|
}
|
|
/**
|
* 显示新增地块对话框
|
* @param parent 父组件
|
*/
|
public static String showAddDikuaiDialog(Component parent) {
|
AddDikuai dialog;
|
|
Window parentWindow = null;
|
if (parent != null) {
|
parentWindow = SwingUtilities.getWindowAncestor(parent);
|
}
|
|
if (parentWindow instanceof JFrame) {
|
dialog = new AddDikuai((JFrame) parentWindow);
|
} else if (parentWindow instanceof JDialog) {
|
dialog = new AddDikuai((JDialog) parentWindow);
|
} else {
|
dialog = new AddDikuai((JFrame) null);
|
}
|
if (resumeRequested && activeSession != null) {
|
dialog.applySessionData(activeSession);
|
resumeRequested = false;
|
}
|
|
dialog.setVisible(true);
|
return dialog.createdLandNumber;
|
}
|
}
|