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.util.ArrayList;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
import java.util.Comparator;
|
import java.awt.geom.Point2D;
|
import baseStation.BaseStation;
|
import bianjie.jisuanmianjie;
|
import dikuai.Dikuai;
|
import dikuai.Dikuaiguanli;
|
import dikuai.Gecaokuanjisuan;
|
import dikuai.Gecaoanquanjuli;
|
import bianjie.Bianjieyouhuatoxy;
|
import lujing.Lunjingguihua;
|
import lujing.Qufenxingzhuang;
|
import lujing.AoxinglujingNoObstacle;
|
import lujing.YixinglujingNoObstacle;
|
import set.Setsys;
|
import ui.UIConfig;
|
import zhuye.MowerLocationData;
|
import zhuye.Shouye;
|
import zhuye.Coordinate;
|
import publicway.buttonset;
|
|
/**
|
* 新增地块对话框 - 多步骤表单设计
|
* 适配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 final Color ERROR_COLOR = new Color(220, 53, 69);
|
private static final String KEY_PATH_MESSAGE_TEXT = "__pathMessageText";
|
private static final String KEY_PATH_MESSAGE_SUCCESS = "__pathMessageSuccess";
|
|
// 步骤面板
|
private JPanel mainPanel;
|
private CardLayout cardLayout;
|
private JPanel stepsPanel;
|
|
// 步骤组件
|
private JTextField landNumberField;
|
private JTextField areaNameField;
|
private JComboBox<String> mowingPatternCombo;
|
private JTextField bladeWidthField; // 割草机割刀宽度(m)
|
private JTextField mowingWidthField; // 割草宽度(m,可编辑)
|
private JTextField safetyDistanceField; // 割草安全距离(m,可编辑)
|
private JPanel previewPanel;
|
private Map<String, JPanel> drawingOptionPanels = new HashMap<>();
|
|
// 步骤控制
|
private int currentStep = 1;
|
private JButton prevButton;
|
private JButton nextButton;
|
private JButton createButton;
|
private JButton previewButton; // 步骤3的预览按钮(预览割草路径)
|
private JButton boundaryPreviewButton; // 步骤2的预览按钮(预览边界)
|
private Component previewButtonSpacer;
|
private JLabel boundaryCountLabel;
|
private JTextArea boundaryXYTextArea; // 显示边界XY坐标的文本域
|
private JScrollPane boundaryXYScrollPane; // 边界XY坐标文本域的滚动面板
|
private JPanel obstacleListContainer;
|
private JTextArea pathGenerationMessageArea;
|
private JPanel pathMessageWrapper;
|
|
// 地块数据
|
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)
|
));
|
// 更新下一步按钮状态
|
updateStep1ButtonState();
|
}
|
});
|
|
// 添加文本变化监听,实时更新按钮状态
|
areaNameField.addKeyListener(new KeyAdapter() {
|
@Override
|
public void keyReleased(KeyEvent e) {
|
updateStep1ButtonState();
|
}
|
});
|
|
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.setBackground(MEDIUM_GRAY); // 初始灰色背景
|
|
startEndDrawingBtn.addActionListener(e -> toggleDrawing());
|
|
stepPanel.add(startEndDrawingBtn);
|
|
// 边界XY坐标显示文本域(3行,带滚动条,在已完成按钮下方)
|
boundaryXYTextArea = new JTextArea(3, 0);
|
boundaryXYTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
boundaryXYTextArea.setForeground(TEXT_COLOR);
|
boundaryXYTextArea.setEditable(false);
|
boundaryXYTextArea.setBackground(LIGHT_GRAY);
|
boundaryXYTextArea.setLineWrap(true);
|
boundaryXYTextArea.setWrapStyleWord(true);
|
boundaryXYTextArea.setAlignmentX(Component.LEFT_ALIGNMENT);
|
boundaryXYTextArea.setVisible(false);
|
|
// 创建滚动面板,默认显示3行
|
boundaryXYScrollPane = new JScrollPane(boundaryXYTextArea);
|
boundaryXYScrollPane.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 1),
|
BorderFactory.createEmptyBorder(0, 0, 0, 0)
|
));
|
boundaryXYScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
boundaryXYScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
|
boundaryXYScrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
boundaryXYScrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
|
boundaryXYScrollPane.setVisible(false);
|
boundaryXYScrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, 80));
|
|
stepPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
stepPanel.add(boundaryXYScrollPane);
|
|
boundaryCountLabel = new JLabel("已采集到边界点0个");
|
boundaryCountLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
boundaryCountLabel.setForeground(LIGHT_TEXT);
|
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;
|
}
|
if (selectDrawingOption(optionPanel, type, true)) {
|
updateStartDrawingButtonState(); // 选择后更新按钮状态
|
}
|
}
|
|
@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 boolean selectDrawingOption(JPanel optionPanel, String type, boolean userTriggered) {
|
if (optionPanel == null) {
|
return false;
|
}
|
if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) {
|
JOptionPane.showMessageDialog(this, "请先添加便携打点器编号", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
// 重置之前选中的选项
|
if (selectedOptionPanel != null) {
|
selectedOptionPanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR, 2));
|
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);
|
return true;
|
}
|
|
private boolean hasConfiguredHandheldMarker() {
|
String handheldId = Setsys.getPropertyValue("handheldMarkerId");
|
return handheldId != null && !handheldId.trim().isEmpty();
|
}
|
|
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(MEDIUM_GRAY);
|
startEndDrawingBtn.setEnabled(false);
|
updateOtherOptionsState(true);
|
|
// 优化边界
|
optimizeBoundaryAndSave();
|
|
dikuaiData.put("boundaryDrawn", "true");
|
JOptionPane.showMessageDialog(this, "边界绘制已完成", "提示", JOptionPane.INFORMATION_MESSAGE);
|
showBoundaryPointSummary();
|
updateBoundaryXYDisplay();
|
// 更新预览和下一步按钮状态(背景颜色变绿色,可点击)
|
updateStep2ButtonsAfterDrawing();
|
}
|
}
|
|
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 void optimizeBoundaryAndSave() {
|
try {
|
// 获取基准站坐标
|
BaseStation baseStation = new BaseStation();
|
baseStation.load();
|
String baseStationCoordinates = normalizeCoordinateValue(baseStation.getInstallationCoordinates());
|
if (!isMeaningfulValue(baseStationCoordinates)) {
|
System.err.println("未获取到有效的基准站坐标,无法优化边界");
|
return;
|
}
|
|
// 获取原始坐标列表
|
List<Coordinate> uniqueCoordinates;
|
synchronized (Coordinate.coordinates) {
|
uniqueCoordinates = sanitizeCoordinateList(Coordinate.coordinates);
|
}
|
|
if (uniqueCoordinates.size() < 3) {
|
System.err.println("采集的边界点不足,无法优化边界");
|
return;
|
}
|
|
// 构建边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
|
String boundaryStr = buildBoundaryStringForOptimization(uniqueCoordinates);
|
|
// 调用优化方法
|
String optimizedXYStr = Bianjieyouhuatoxy.optimizeBoundary(baseStationCoordinates, boundaryStr);
|
|
// 保存优化后的XY坐标字符串
|
if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
|
dikuaiData.put("optimizedBoundaryXY", optimizedXYStr);
|
if (activeSession != null) {
|
activeSession.data.put("optimizedBoundaryXY", optimizedXYStr);
|
}
|
} else {
|
System.err.println("边界优化失败: " + optimizedXYStr);
|
}
|
} catch (Exception e) {
|
e.printStackTrace();
|
System.err.println("优化边界时发生错误: " + e.getMessage());
|
}
|
}
|
|
/**
|
* 构建用于优化的边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
|
* 其中lat和lon是度分格式(DMM格式),例如 "3949.89151752"
|
*/
|
private static String buildBoundaryStringForOptimization(List<Coordinate> coordinates) {
|
if (coordinates == null || coordinates.isEmpty()) {
|
return "()";
|
}
|
StringBuilder sb = new StringBuilder("(");
|
DecimalFormat elevationFormat = new DecimalFormat("0.00");
|
for (int i = 0; i < coordinates.size(); i++) {
|
Coordinate coord = coordinates.get(i);
|
// Coordinate类中的getLatitude()和getLongitude()已经返回度分格式(DMM格式)
|
String latDMM = coord.getLatitude();
|
String lonDMM = coord.getLongitude();
|
double elevation = coord.getElevation();
|
|
if (i > 0) {
|
sb.append(";");
|
}
|
sb.append(latDMM).append(",")
|
.append(lonDMM).append(",")
|
.append(elevationFormat.format(elevation));
|
}
|
sb.append(")");
|
return sb.toString();
|
}
|
|
private JPanel createStep3Panel() {
|
JPanel stepPanel = new JPanel();
|
stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
|
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 bladeWidthPanel = createFormGroup("割草机割刀宽度", "");
|
JPanel bladeWidthInputPanel = new JPanel(new BorderLayout());
|
bladeWidthInputPanel.setBackground(WHITE);
|
bladeWidthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
|
bladeWidthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
bladeWidthField = new JTextField("0.50");
|
bladeWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
bladeWidthField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
bladeWidthField.addFocusListener(new FocusAdapter() {
|
@Override
|
public void focusGained(FocusEvent e) {
|
bladeWidthField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
|
@Override
|
public void focusLost(FocusEvent e) {
|
bladeWidthField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
updateMowingWidthAndSafetyDistance();
|
}
|
});
|
|
JLabel bladeUnitLabel = new JLabel("m");
|
bladeUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
bladeUnitLabel.setForeground(LIGHT_TEXT);
|
bladeUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
|
|
bladeWidthInputPanel.add(bladeWidthField, BorderLayout.CENTER);
|
bladeWidthInputPanel.add(bladeUnitLabel, BorderLayout.EAST);
|
|
bladeWidthPanel.add(bladeWidthInputPanel);
|
settingsPanel.add(bladeWidthPanel);
|
settingsPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
|
// 割草宽度设置(可编辑)
|
JPanel widthPanel = createFormGroup("割草宽度", "计算方法:割草宽度 = 割刀宽 ×85%");
|
JPanel widthInputPanel = new JPanel(new BorderLayout());
|
widthInputPanel.setBackground(WHITE);
|
widthInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
|
widthInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
mowingWidthField = new JTextField();
|
mowingWidthField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
mowingWidthField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
mowingWidthField.addFocusListener(new FocusAdapter() {
|
@Override
|
public void focusGained(FocusEvent e) {
|
mowingWidthField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
|
@Override
|
public void focusLost(FocusEvent e) {
|
mowingWidthField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
});
|
|
JLabel unitLabel = new JLabel("m");
|
unitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
unitLabel.setForeground(LIGHT_TEXT);
|
unitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
|
|
widthInputPanel.add(mowingWidthField, BorderLayout.CENTER);
|
widthInputPanel.add(unitLabel, BorderLayout.EAST);
|
|
widthPanel.add(widthInputPanel);
|
settingsPanel.add(widthPanel);
|
settingsPanel.add(Box.createRigidArea(new Dimension(0, 20)));
|
|
// 割草安全距离设置(可编辑)
|
JPanel safetyDistancePanel = createFormGroup("割草安全距离", "根据割草机尺寸自动计算的安全距离");
|
JPanel safetyDistanceInputPanel = new JPanel(new BorderLayout());
|
safetyDistanceInputPanel.setBackground(WHITE);
|
safetyDistanceInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48));
|
safetyDistanceInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
safetyDistanceField = new JTextField();
|
safetyDistanceField.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
safetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
safetyDistanceField.addFocusListener(new FocusAdapter() {
|
@Override
|
public void focusGained(FocusEvent e) {
|
safetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
|
@Override
|
public void focusLost(FocusEvent e) {
|
safetyDistanceField.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)
|
));
|
}
|
});
|
|
JLabel safetyUnitLabel = new JLabel("m");
|
safetyUnitLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
safetyUnitLabel.setForeground(LIGHT_TEXT);
|
safetyUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0));
|
|
safetyDistanceInputPanel.add(safetyDistanceField, BorderLayout.CENTER);
|
safetyDistanceInputPanel.add(safetyUnitLabel, BorderLayout.EAST);
|
|
safetyDistancePanel.add(safetyDistanceInputPanel);
|
settingsPanel.add(safetyDistancePanel);
|
settingsPanel.add(Box.createRigidArea(new Dimension(0, 25)));
|
|
// 初始化计算值
|
updateMowingWidthAndSafetyDistance();
|
|
// 将设置面板放入滚动面板
|
JScrollPane scrollPane = new JScrollPane(settingsPanel);
|
scrollPane.setBorder(null);
|
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
|
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
stepPanel.add(scrollPane);
|
|
JButton generatePathButton = createPrimaryButton("生成割草路径", 16);
|
generatePathButton.setAlignmentX(Component.LEFT_ALIGNMENT);
|
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.createRigidArea(new Dimension(0, 12)));
|
|
pathMessageWrapper = new JPanel(new BorderLayout());
|
pathMessageWrapper.setAlignmentX(Component.LEFT_ALIGNMENT);
|
pathMessageWrapper.setBackground(PRIMARY_LIGHT);
|
pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 1),
|
BorderFactory.createEmptyBorder(12, 12, 12, 12)
|
));
|
pathMessageWrapper.setVisible(false);
|
|
pathGenerationMessageArea = new JTextArea();
|
pathGenerationMessageArea.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
pathGenerationMessageArea.setForeground(TEXT_COLOR);
|
pathGenerationMessageArea.setOpaque(false);
|
pathGenerationMessageArea.setEditable(false);
|
pathGenerationMessageArea.setLineWrap(true);
|
pathGenerationMessageArea.setWrapStyleWord(true);
|
pathGenerationMessageArea.setFocusable(false);
|
pathGenerationMessageArea.setBorder(null);
|
|
pathMessageWrapper.add(pathGenerationMessageArea, BorderLayout.CENTER);
|
stepPanel.add(pathMessageWrapper);
|
stepPanel.add(Box.createVerticalGlue());
|
|
return stepPanel;
|
}
|
|
private 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);
|
|
formGroup.add(nameLabel);
|
|
// 只有当提示文字不为空时才显示
|
if (hint != null && !hint.trim().isEmpty()) {
|
formGroup.add(Box.createRigidArea(new Dimension(0, 6)));
|
JLabel hintLabel = new JLabel(hint);
|
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
hintLabel.setForeground(LIGHT_TEXT);
|
hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
formGroup.add(hintLabel);
|
formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
|
} else {
|
formGroup.add(Box.createRigidArea(new Dimension(0, 8)));
|
}
|
|
return formGroup;
|
}
|
|
/**
|
* 根据割刀宽度更新割草宽度和安全距离
|
*/
|
private void updateMowingWidthAndSafetyDistance() {
|
try {
|
String bladeWidthStr = bladeWidthField.getText().trim();
|
if (bladeWidthStr == null || bladeWidthStr.isEmpty()) {
|
mowingWidthField.setText("");
|
safetyDistanceField.setText("");
|
return;
|
}
|
|
double bladeWidthMeters = Double.parseDouble(bladeWidthStr);
|
if (bladeWidthMeters <= 0) {
|
mowingWidthField.setText("");
|
safetyDistanceField.setText("");
|
return;
|
}
|
|
// 调用 Gecaokuanjisuan 类计算割草宽度(返回厘米,保留2位小数,需要转换为米)
|
double mowingWidthCm = Gecaokuanjisuan.calculateMowingWidth(bladeWidthMeters);
|
if (mowingWidthCm > 0) {
|
// 将厘米转换为米,并保留2位小数
|
double mowingWidthMeters = mowingWidthCm / 100.0;
|
mowingWidthField.setText(String.format(Locale.US, "%.2f", mowingWidthMeters));
|
} else {
|
mowingWidthField.setText("");
|
}
|
|
// 调用 Gecaoanquanjuli 类计算安全距离
|
String safetyDistanceStr = Gecaoanquanjuli.calculateSafetyDistanceFromString(bladeWidthStr);
|
safetyDistanceField.setText(safetyDistanceStr);
|
|
} catch (NumberFormatException e) {
|
mowingWidthField.setText("");
|
safetyDistanceField.setText("");
|
} catch (Exception e) {
|
e.printStackTrace();
|
mowingWidthField.setText("");
|
safetyDistanceField.setText("");
|
}
|
}
|
|
private void generateMowingPath() {
|
if (!dikuaiData.containsKey("boundaryDrawn")) {
|
JOptionPane.showMessageDialog(this, "请先完成边界绘制后再生成路径", "提示", JOptionPane.WARNING_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("请先完成边界绘制后再生成路径。", false);
|
setPathAvailability(false);
|
showStep(2);
|
return;
|
}
|
// 从步骤2的边界坐标文本域获取边界坐标
|
String boundaryCoords = null;
|
if (boundaryXYTextArea != null) {
|
String boundaryText = boundaryXYTextArea.getText();
|
if (boundaryText != null && !boundaryText.trim().isEmpty() && !boundaryText.startsWith("ERROR")) {
|
boundaryCoords = boundaryText.trim();
|
}
|
}
|
|
// 如果文本域中没有,尝试从dikuaiData获取
|
if (boundaryCoords == null || boundaryCoords.isEmpty()) {
|
boundaryCoords = normalizeCoordinateValue(dikuaiData.get("optimizedBoundaryXY"));
|
}
|
|
// 如果还是没有,尝试从Dikuai对象获取
|
if (boundaryCoords == null || boundaryCoords.isEmpty()) {
|
Dikuai dikuai = getOrCreatePendingDikuai();
|
if (dikuai != null) {
|
boundaryCoords = normalizeCoordinateValue(dikuai.getBoundaryCoordinates());
|
}
|
}
|
|
// 如果还是没有,从dikuaiData获取boundaryCoordinates
|
if (boundaryCoords == null || boundaryCoords.isEmpty()) {
|
boundaryCoords = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates"));
|
}
|
|
if (boundaryCoords == null || boundaryCoords.isEmpty()) {
|
JOptionPane.showMessageDialog(this, "未找到有效的地块边界坐标,无法生成路径", "提示", JOptionPane.WARNING_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("未找到有效的地块边界坐标,无法生成路径。", false);
|
setPathAvailability(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);
|
|
// 获取割草宽度(从文本框,单位:米)
|
String mowingWidthStr = mowingWidthField.getText().trim();
|
if (mowingWidthStr == null || mowingWidthStr.isEmpty()) {
|
JOptionPane.showMessageDialog(this, "请先输入割草宽度", "提示", JOptionPane.WARNING_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("请先输入割草宽度。", false);
|
setPathAvailability(false);
|
return;
|
}
|
|
double widthMeters;
|
try {
|
widthMeters = Double.parseDouble(mowingWidthStr);
|
} catch (NumberFormatException e) {
|
JOptionPane.showMessageDialog(this, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("割草宽度格式不正确,请重新输入。", false);
|
setPathAvailability(false);
|
return;
|
}
|
|
if (widthMeters <= 0) {
|
JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("割草宽度必须大于0,请重新设置。", false);
|
setPathAvailability(false);
|
return;
|
}
|
|
// 保存割草宽度(转换为厘米保存,保持与原有数据格式一致)
|
double widthCm = widthMeters * 100.0;
|
dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm));
|
|
// 获取安全距离
|
String safetyDistanceStr = safetyDistanceField.getText().trim();
|
double safetyDistanceMeters = Double.NaN;
|
if (safetyDistanceStr != null && !safetyDistanceStr.isEmpty()) {
|
try {
|
safetyDistanceMeters = Double.parseDouble(safetyDistanceStr);
|
} catch (NumberFormatException e) {
|
// 使用NaN,让系统自动计算
|
}
|
}
|
|
// 格式化割草宽度和安全距离(单位:米,保留3位小数)
|
String widthMetersStr = String.format(Locale.US, "%.3f", widthMeters);
|
String safetyDistanceMetersStr = Double.isNaN(safetyDistanceMeters) ? null : String.format(Locale.US, "%.3f", safetyDistanceMeters);
|
|
// 如果没有安全距离,使用默认值
|
if (safetyDistanceMetersStr == null) {
|
double defaultSafetyDistance = widthMeters / 2.0 + 0.2;
|
safetyDistanceMetersStr = String.format(Locale.US, "%.3f", defaultSafetyDistance);
|
}
|
|
try {
|
// 1. 调用Qufenxingzhuang中的judgeGrassType方法计算地块边界的形状
|
Qufenxingzhuang shapeJudger = new Qufenxingzhuang();
|
int grassType = shapeJudger.judgeGrassType(boundaryCoords);
|
// grassType: 0=无法判断, 1=凸形, 2=异形
|
|
String plannedPath = null;
|
|
// 2. 根据计算后的结果决定调用哪个方法生成割草路径
|
if (grassType == 1) {
|
// 凸形地块 -> 调用AoxinglujingNoObstacle类中的方法
|
List<AoxinglujingNoObstacle.PathSegment> segments =
|
AoxinglujingNoObstacle.planPath(boundaryCoords, widthMetersStr, safetyDistanceMetersStr);
|
plannedPath = formatAoxingPathSegments(segments);
|
} else if (grassType == 2) {
|
// 异形地块 -> 调用YixinglujingNoObstacle中的方法
|
List<YixinglujingNoObstacle.PathSegment> segments =
|
YixinglujingNoObstacle.planPath(boundaryCoords, widthMetersStr, safetyDistanceMetersStr);
|
plannedPath = formatYixingPathSegments(segments);
|
} else {
|
// 无法判断地块类型,使用默认方法作为后备
|
JOptionPane.showMessageDialog(this, "无法判断地块类型,使用默认路径生成方法",
|
"提示", JOptionPane.WARNING_MESSAGE);
|
String plannerMode = resolvePlannerMode(patternDisplay);
|
plannedPath = Lunjingguihua.generatePathFromStrings(
|
boundaryCoords,
|
obstacleCoords != null ? obstacleCoords : "",
|
widthMetersStr,
|
safetyDistanceMetersStr,
|
plannerMode
|
);
|
}
|
|
if (!isMeaningfulValue(plannedPath)) {
|
JOptionPane.showMessageDialog(this, "生成割草路径失败: 生成结果为空", "错误", JOptionPane.ERROR_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("生成割草路径失败:生成结果为空。", false);
|
setPathAvailability(false);
|
return;
|
}
|
|
if (isMeaningfulValue(boundaryCoords)) {
|
dikuaiData.put("boundaryCoordinates", boundaryCoords);
|
}
|
if (isMeaningfulValue(obstacleCoords)) {
|
dikuaiData.put("obstacleCoordinates", obstacleCoords);
|
}
|
dikuaiData.put("plannedPath", plannedPath);
|
setPathAvailability(true);
|
showPathGenerationMessage(
|
"已根据当前设置生成割草路径。\n点击预览按钮可在主页面查看效果。",
|
true);
|
} catch (IllegalArgumentException ex) {
|
JOptionPane.showMessageDialog(this, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("生成割草路径失败:" + ex.getMessage(), false);
|
setPathAvailability(false);
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
JOptionPane.showMessageDialog(this, "生成割草路径时发生异常: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
|
dikuaiData.remove("plannedPath");
|
showPathGenerationMessage("生成割草路径时发生异常:" + ex.getMessage(), false);
|
setPathAvailability(false);
|
}
|
}
|
|
/**
|
* 格式化 AoxinglujingNoObstacle.PathSegment 列表为坐标字符串
|
*/
|
private String formatAoxingPathSegments(List<AoxinglujingNoObstacle.PathSegment> segments) {
|
if (segments == null || segments.isEmpty()) {
|
return "";
|
}
|
StringBuilder sb = new StringBuilder();
|
AoxinglujingNoObstacle.Point last = null;
|
for (AoxinglujingNoObstacle.PathSegment segment : segments) {
|
// 只添加割草工作段,跳过过渡段
|
if (segment.isMowing) {
|
// 如果起点与上一个终点不同,添加起点
|
if (last == null || !equalsAoxingPoint(last, segment.start)) {
|
appendAoxingPoint(sb, segment.start);
|
}
|
// 添加终点
|
appendAoxingPoint(sb, segment.end);
|
last = segment.end;
|
}
|
}
|
return sb.toString();
|
}
|
|
/**
|
* 格式化 YixinglujingNoObstacle.PathSegment 列表为坐标字符串
|
*/
|
private String formatYixingPathSegments(List<YixinglujingNoObstacle.PathSegment> segments) {
|
if (segments == null || segments.isEmpty()) {
|
return "";
|
}
|
StringBuilder sb = new StringBuilder();
|
YixinglujingNoObstacle.Point last = null;
|
for (YixinglujingNoObstacle.PathSegment segment : segments) {
|
// 只添加割草工作段,跳过过渡段
|
if (segment.isMowing) {
|
// 如果起点与上一个终点不同,添加起点
|
if (last == null || !equalsYixingPoint(last, segment.start)) {
|
appendYixingPoint(sb, segment.start);
|
}
|
// 添加终点
|
appendYixingPoint(sb, segment.end);
|
last = segment.end;
|
}
|
}
|
return sb.toString();
|
}
|
|
/**
|
* 比较两个 AoxinglujingNoObstacle.Point 是否相同(使用小的容差)
|
*/
|
private boolean equalsAoxingPoint(AoxinglujingNoObstacle.Point p1, AoxinglujingNoObstacle.Point p2) {
|
if (p1 == null || p2 == null) {
|
return p1 == p2;
|
}
|
double tolerance = 1e-6;
|
return Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.y - p2.y) < tolerance;
|
}
|
|
/**
|
* 添加 AoxinglujingNoObstacle.Point 到字符串构建器
|
*/
|
private void appendAoxingPoint(StringBuilder sb, AoxinglujingNoObstacle.Point point) {
|
if (sb.length() > 0) {
|
sb.append(";");
|
}
|
sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y));
|
}
|
|
/**
|
* 比较两个 YixinglujingNoObstacle.Point 是否相同(使用小的容差)
|
*/
|
private boolean equalsYixingPoint(YixinglujingNoObstacle.Point p1, YixinglujingNoObstacle.Point p2) {
|
if (p1 == null || p2 == null) {
|
return p1 == p2;
|
}
|
double tolerance = 1e-6;
|
return Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.y - p2.y) < tolerance;
|
}
|
|
/**
|
* 添加 YixinglujingNoObstacle.Point 到字符串构建器
|
*/
|
private void appendYixingPoint(StringBuilder sb, YixinglujingNoObstacle.Point point) {
|
if (sb.length() > 0) {
|
sb.append(";");
|
}
|
sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y));
|
}
|
|
private void previewMowingPath() {
|
if (!hasGeneratedPath()) {
|
showPathGenerationMessage("请先生成割草路径后再预览。", false);
|
setPathAvailability(false);
|
return;
|
}
|
|
persistStep3Inputs();
|
|
String landNumber = getPendingLandNumber();
|
String trimmedAreaName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
|
String displayAreaName = isMeaningfulValue(trimmedAreaName) ? trimmedAreaName : landNumber;
|
|
String plannedPath = dikuaiData.get("plannedPath");
|
if (!isMeaningfulValue(plannedPath)) {
|
showPathGenerationMessage("请先生成割草路径后再预览。", false);
|
setPathAvailability(false);
|
return;
|
}
|
|
String boundary = null;
|
Dikuai pending = getOrCreatePendingDikuai();
|
if (pending != null) {
|
boundary = normalizeCoordinateValue(pending.getBoundaryCoordinates());
|
}
|
if (boundary == null) {
|
boundary = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates"));
|
}
|
|
String obstacles = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates"));
|
if (!isMeaningfulValue(obstacles)) {
|
obstacles = resolveObstaclePayloadFromConfig(landNumber);
|
if (isMeaningfulValue(obstacles)) {
|
dikuaiData.put("obstacleCoordinates", obstacles);
|
}
|
}
|
|
Shouye shouye = Shouye.getInstance();
|
if (shouye == null) {
|
JOptionPane.showMessageDialog(this, "无法打开主页面,请稍后重试", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
dikuaiData.put("areaName", trimmedAreaName);
|
if (isMeaningfulValue(boundary)) {
|
dikuaiData.put("boundaryCoordinates", boundary);
|
}
|
|
pendingLandNumber = landNumber;
|
captureSessionSnapshot();
|
|
resumeRequested = true;
|
boolean started = shouye.startMowingPathPreview(
|
landNumber,
|
displayAreaName,
|
boundary,
|
obstacles,
|
plannedPath,
|
AddDikuai::resumeFromPreview
|
);
|
if (!started) {
|
resumeRequested = false;
|
JOptionPane.showMessageDialog(this, "无法启动预览,请稍后再试", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
// 在步骤3预览时,不显示边界点圆圈
|
if (shouye.getMapRenderer() != null) {
|
shouye.getMapRenderer().setBoundaryPointsVisible(false);
|
}
|
|
closePreviewAndDispose();
|
}
|
|
private void persistStep3Inputs() {
|
String trimmedName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
|
dikuaiData.put("areaName", trimmedName);
|
|
if (mowingPatternCombo != null) {
|
Object selection = mowingPatternCombo.getSelectedItem();
|
if (selection != null) {
|
dikuaiData.put("mowingPattern", selection.toString());
|
}
|
}
|
|
// 保存割草宽度(从文本框获取,单位:米,转换为厘米保存)
|
if (mowingWidthField != null) {
|
String widthStr = mowingWidthField.getText().trim();
|
if (widthStr != null && !widthStr.isEmpty()) {
|
try {
|
double widthMeters = Double.parseDouble(widthStr);
|
double widthCm = widthMeters * 100.0;
|
dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm));
|
} catch (NumberFormatException e) {
|
// 保持原值
|
dikuaiData.put("mowingWidth", widthStr);
|
}
|
}
|
}
|
}
|
|
private void captureSessionSnapshot() {
|
if (activeSession == null) {
|
activeSession = new DrawingSession();
|
}
|
String landNumber = getPendingLandNumber();
|
activeSession.landNumber = landNumber;
|
activeSession.areaName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
|
activeSession.drawingCompleted = true;
|
activeSession.data = new HashMap<>(dikuaiData);
|
}
|
|
private void closePreviewAndDispose() {
|
setVisible(false);
|
dispose();
|
}
|
|
private JButton createPrimaryButton(String text, int fontSize) {
|
JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR);
|
button.setFont(new Font("微软雅黑", Font.BOLD, fontSize));
|
button.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_DARK, 2),
|
BorderFactory.createEmptyBorder(12, 25, 12, 25)
|
));
|
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (button.isEnabled()) {
|
// 如果按钮可用,鼠标悬停时显示深绿色
|
if (button.getBackground().equals(PRIMARY_COLOR)) {
|
button.setBackground(PRIMARY_DARK);
|
}
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (button.isEnabled()) {
|
// 如果按钮可用,鼠标离开时恢复绿色
|
if (!button.getBackground().equals(MEDIUM_GRAY)) {
|
button.setBackground(PRIMARY_COLOR);
|
}
|
} else {
|
// 如果按钮不可用,保持灰色
|
button.setBackground(MEDIUM_GRAY);
|
}
|
}
|
});
|
|
return button;
|
}
|
|
private void showPathGenerationMessage(String message, boolean success) {
|
if (pathGenerationMessageArea == null || pathMessageWrapper == null) {
|
return;
|
}
|
String display = message == null ? "" : message.trim();
|
if (display.isEmpty()) {
|
dikuaiData.remove(KEY_PATH_MESSAGE_TEXT);
|
dikuaiData.remove(KEY_PATH_MESSAGE_SUCCESS);
|
} else {
|
dikuaiData.put(KEY_PATH_MESSAGE_TEXT, display);
|
dikuaiData.put(KEY_PATH_MESSAGE_SUCCESS, success ? "true" : "false");
|
}
|
pathGenerationMessageArea.setText(display);
|
Color borderColor = success ? PRIMARY_COLOR : ERROR_COLOR;
|
Color textColor = success ? PRIMARY_DARK : ERROR_COLOR;
|
Color backgroundColor = success ? PRIMARY_LIGHT : new Color(255, 235, 238);
|
pathGenerationMessageArea.setForeground(textColor);
|
pathMessageWrapper.setBackground(backgroundColor);
|
pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(borderColor, 1),
|
BorderFactory.createEmptyBorder(12, 12, 12, 12)
|
));
|
pathMessageWrapper.setVisible(!display.isEmpty());
|
pathMessageWrapper.revalidate();
|
pathMessageWrapper.repaint();
|
}
|
|
private void setPathAvailability(boolean available) {
|
boolean effective = available && currentStep == 3;
|
if (createButton != null) {
|
createButton.setEnabled(effective);
|
}
|
if (previewButton != null) {
|
boolean visible = previewButton.isVisible();
|
previewButton.setEnabled(effective && visible);
|
}
|
}
|
|
private JPanel createButtonPanel() {
|
JPanel buttonPanel = new JPanel();
|
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
|
buttonPanel.setBackground(WHITE);
|
buttonPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
|
|
prevButton = buttonset.createStyledButton("上一步", MEDIUM_GRAY);
|
prevButton.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
prevButton.setForeground(TEXT_COLOR);
|
prevButton.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(BORDER_COLOR, 2),
|
BorderFactory.createEmptyBorder(10, 25, 10, 25)
|
));
|
prevButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
|
boundaryPreviewButton = createPrimaryButton("预览", 16);
|
boundaryPreviewButton.setVisible(false);
|
boundaryPreviewButton.setEnabled(false);
|
boundaryPreviewButton.addActionListener(e -> previewBoundary());
|
|
nextButton = createPrimaryButton("下一步", 16);
|
nextButton.setBackground(MEDIUM_GRAY); // 初始灰色背景
|
nextButton.setEnabled(false); // 初始不可用
|
|
createButton = createPrimaryButton("保存", 16);
|
createButton.setVisible(false);
|
createButton.setEnabled(false);
|
|
previewButton = createPrimaryButton("预览", 16);
|
previewButton.setVisible(false);
|
previewButton.setEnabled(false);
|
|
previewButtonSpacer = Box.createHorizontalStrut(15);
|
previewButtonSpacer.setVisible(false);
|
|
buttonPanel.add(prevButton);
|
buttonPanel.add(Box.createHorizontalGlue());
|
buttonPanel.add(boundaryPreviewButton);
|
buttonPanel.add(Box.createHorizontalStrut(15));
|
buttonPanel.add(nextButton);
|
buttonPanel.add(previewButtonSpacer);
|
buttonPanel.add(previewButton);
|
buttonPanel.add(Box.createHorizontalStrut(15));
|
buttonPanel.add(createButton);
|
|
return buttonPanel;
|
}
|
|
private void showBoundaryPointSummary() {
|
if (boundaryCountLabel == null) {
|
return;
|
}
|
|
// 使用优化后的边界数据计算点数和面积
|
String optimizedXYStr = dikuaiData.get("optimizedBoundaryXY");
|
if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
|
int count = Bianjieyouhuatoxy.calculatePointCount(optimizedXYStr);
|
String areaStr = Bianjieyouhuatoxy.calculateArea(optimizedXYStr);
|
boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaStr + "㎡");
|
} else {
|
// 如果没有优化后的数据,使用原始数据
|
int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0;
|
double area = jisuanmianjie.calculatePolygonArea();
|
DecimalFormat areaFormat = new DecimalFormat("0.00");
|
boundaryCountLabel.setText("已采集到边界点" + count + "个,当前地块面积为" + areaFormat.format(area) + "㎡");
|
}
|
boundaryCountLabel.setVisible(true);
|
}
|
|
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());
|
// 计算并设置原始边界XY坐标
|
String originalBoundaryXY = convertOriginalBoundaryToXY(snapshot.originalBoundary, snapshot.baseStationCoordinates);
|
if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) {
|
dikuai.setBoundaryOriginalXY(originalBoundaryXY);
|
}
|
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 List<Coordinate> sanitizeCoordinateList(List<Coordinate> source) {
|
if (source == null || source.isEmpty()) {
|
return Collections.emptyList();
|
}
|
|
List<Coordinate> snapshot = new ArrayList<>();
|
for (Coordinate coordinate : source) {
|
if (coordinate != null) {
|
snapshot.add(coordinate);
|
}
|
}
|
if (snapshot.isEmpty()) {
|
return Collections.emptyList();
|
}
|
|
DecimalFormat latLonFormat = new DecimalFormat("0.000000");
|
LinkedHashMap<String, Coordinate> unique = new LinkedHashMap<>();
|
for (Coordinate coord : snapshot) {
|
double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection());
|
double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection());
|
String key = latLonFormat.format(lat) + "," + latLonFormat.format(lon);
|
unique.putIfAbsent(key, coord);
|
}
|
return new ArrayList<>(unique.values());
|
}
|
|
private static String buildOriginalBoundaryString(List<Coordinate> coordinates) {
|
if (coordinates == null || coordinates.isEmpty()) {
|
return "-1";
|
}
|
StringBuilder sb = new StringBuilder();
|
DecimalFormat latLonFormat = new DecimalFormat("0.000000");
|
DecimalFormat elevationFormat = new DecimalFormat("0.00");
|
for (Coordinate coord : 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;
|
}
|
|
/**
|
* 将原始边界坐标(经纬度格式)转换为XY坐标
|
* @param originalBoundary 原始边界坐标字符串,格式:"lat1,lon1,alt1;lat2,lon2,alt2;..."
|
* @param baseStationCoordinates 基准站坐标,格式:"lat,N/S,lon,E/W"
|
* @return XY坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..." 如果转换失败返回null
|
*/
|
private static String convertOriginalBoundaryToXY(String originalBoundary, String baseStationCoordinates) {
|
if (originalBoundary == null || originalBoundary.trim().isEmpty() || "-1".equals(originalBoundary.trim())) {
|
return null;
|
}
|
if (baseStationCoordinates == null || baseStationCoordinates.trim().isEmpty()) {
|
return null;
|
}
|
|
try {
|
// 解析基准站坐标
|
String[] baseParts = baseStationCoordinates.trim().split(",");
|
if (baseParts.length != 4) {
|
return null;
|
}
|
double baseLat = convertToDecimalDegree(baseParts[0], baseParts[1]);
|
double baseLon = convertToDecimalDegree(baseParts[2], baseParts[3]);
|
|
// 解析原始边界坐标
|
String[] points = originalBoundary.split(";");
|
StringBuilder xyStr = new StringBuilder();
|
|
for (int i = 0; i < points.length; i++) {
|
String point = points[i].trim();
|
if (point.isEmpty()) {
|
continue;
|
}
|
|
String[] coords = point.split(",");
|
if (coords.length >= 2) {
|
try {
|
double lat = Double.parseDouble(coords[0].trim());
|
double lon = Double.parseDouble(coords[1].trim());
|
|
// 转换为XY坐标
|
double[] xy = publicway.Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon);
|
if (xy != null && xy.length >= 2) {
|
if (xyStr.length() > 0) {
|
xyStr.append(";");
|
}
|
xyStr.append(String.format(Locale.US, "%.3f,%.3f", xy[0], xy[1]));
|
}
|
} catch (NumberFormatException e) {
|
// 跳过无效的坐标点
|
continue;
|
}
|
}
|
}
|
|
return xyStr.length() > 0 ? xyStr.toString() : null;
|
} catch (Exception e) {
|
System.err.println("转换原始边界坐标到XY失败: " + e.getMessage());
|
return null;
|
}
|
}
|
|
/**
|
* 预览边界
|
*/
|
private void previewBoundary() {
|
if (!dikuaiData.containsKey("boundaryDrawn")) {
|
JOptionPane.showMessageDialog(this, "请先完成边界绘制后再预览", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
// 获取或创建地块对象
|
String landNumber = getPendingLandNumber();
|
Dikuai dikuai = getOrCreatePendingDikuai();
|
if (dikuai == null) {
|
JOptionPane.showMessageDialog(this, "无法获取地块信息", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
|
// 确保地块数据是最新的
|
String optimizedBoundaryXY = dikuaiData.get("optimizedBoundaryXY");
|
if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || optimizedBoundaryXY.startsWith("ERROR")) {
|
// 如果没有优化后的边界,尝试从boundaryCoordinates获取
|
String boundaryCoords = dikuaiData.get("boundaryCoordinates");
|
if (boundaryCoords != null && !boundaryCoords.isEmpty() && !"-1".equals(boundaryCoords)) {
|
optimizedBoundaryXY = boundaryCoords;
|
} else {
|
// 尝试从地块对象获取
|
optimizedBoundaryXY = dikuai.getBoundaryCoordinates();
|
}
|
}
|
|
if (optimizedBoundaryXY == null || optimizedBoundaryXY.isEmpty() || "-1".equals(optimizedBoundaryXY)) {
|
JOptionPane.showMessageDialog(this, "未找到有效的边界坐标,无法预览", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
// 确保原始边界XY坐标已计算
|
String originalBoundaryXY = dikuai.getBoundaryOriginalXY();
|
if (originalBoundaryXY == null || originalBoundaryXY.isEmpty() || "-1".equals(originalBoundaryXY)) {
|
// 计算原始边界XY坐标
|
String originalBoundary = dikuaiData.get("boundaryOriginalCoordinates");
|
String baseStationCoordinates = dikuaiData.get("baseStationCoordinates");
|
if (originalBoundary != null && baseStationCoordinates != null) {
|
originalBoundaryXY = convertOriginalBoundaryToXY(originalBoundary, baseStationCoordinates);
|
if (originalBoundaryXY != null && !originalBoundaryXY.isEmpty()) {
|
dikuai.setBoundaryOriginalXY(originalBoundaryXY);
|
Dikuai.putDikuai(landNumber, dikuai);
|
}
|
}
|
}
|
|
// 保存会话快照
|
captureSessionSnapshot();
|
|
// 创建final变量供lambda使用
|
final String finalOptimizedBoundaryXY = optimizedBoundaryXY;
|
final Dikuai finalDikuai = dikuai;
|
|
// 关闭对话框
|
setVisible(false);
|
dispose();
|
|
// 调用首页显示预览(与边界管理页面逻辑一致)
|
SwingUtilities.invokeLater(() -> {
|
Shouye.showBoundaryPreview(finalDikuai, finalOptimizedBoundaryXY, () -> {
|
// 返回回调:重新打开新增地块对话框,并显示步骤2
|
Component parent = Shouye.getInstance();
|
if (parent != null) {
|
// 确保会话状态正确,以便返回时显示步骤2
|
if (activeSession != null && activeSession.drawingCompleted) {
|
resumeRequested = true;
|
}
|
showAddDikuaiDialog(parent);
|
}
|
});
|
});
|
}
|
|
private void hideBoundaryPointSummary() {
|
if (boundaryCountLabel != null) {
|
boundaryCountLabel.setVisible(false);
|
}
|
if (boundaryXYTextArea != null) {
|
boundaryXYTextArea.setVisible(false);
|
boundaryXYTextArea.setText("");
|
}
|
if (boundaryXYScrollPane != null) {
|
boundaryXYScrollPane.setVisible(false);
|
}
|
}
|
|
/**
|
* 更新边界XY坐标显示
|
*/
|
private void updateBoundaryXYDisplay() {
|
if (boundaryXYTextArea == null || boundaryXYScrollPane == null) {
|
return;
|
}
|
|
// 获取优化后的边界XY坐标
|
String optimizedXYStr = dikuaiData.get("optimizedBoundaryXY");
|
if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
|
boundaryXYTextArea.setText(optimizedXYStr);
|
boundaryXYTextArea.setVisible(true);
|
boundaryXYScrollPane.setVisible(true);
|
} else {
|
// 如果没有优化后的数据,尝试从原始坐标优化并显示
|
// 如果已经有原始坐标但还没有优化,则进行优化
|
if (dikuaiData.containsKey("boundaryDrawn") &&
|
(Coordinate.coordinates != null && !Coordinate.coordinates.isEmpty())) {
|
// 尝试优化边界
|
optimizeBoundaryAndSave();
|
// 再次获取优化后的坐标
|
optimizedXYStr = dikuaiData.get("optimizedBoundaryXY");
|
if (optimizedXYStr != null && !optimizedXYStr.isEmpty() && !optimizedXYStr.startsWith("ERROR")) {
|
boundaryXYTextArea.setText(optimizedXYStr);
|
boundaryXYTextArea.setVisible(true);
|
boundaryXYScrollPane.setVisible(true);
|
} else {
|
boundaryXYTextArea.setText("边界坐标数据未就绪");
|
boundaryXYTextArea.setVisible(true);
|
boundaryXYScrollPane.setVisible(true);
|
}
|
} else {
|
boundaryXYTextArea.setText("边界坐标数据未就绪");
|
boundaryXYTextArea.setVisible(true);
|
boundaryXYScrollPane.setVisible(true);
|
}
|
}
|
}
|
|
private boolean hasGeneratedPath() {
|
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() {
|
List<Coordinate> uniqueCoordinates;
|
synchronized (Coordinate.coordinates) {
|
uniqueCoordinates = sanitizeCoordinateList(Coordinate.coordinates);
|
Coordinate.coordinates.clear();
|
Coordinate.coordinates.addAll(uniqueCoordinates);
|
}
|
|
if (uniqueCoordinates.size() < 3) {
|
return BoundarySnapshotResult.failure("采集的边界点不足,无法生成地块边界", JOptionPane.WARNING_MESSAGE);
|
}
|
|
double area = jisuanmianjie.calculatePolygonArea();
|
if (area <= 0) {
|
return BoundarySnapshotResult.failure("当前地块面积为0,无法继续", JOptionPane.WARNING_MESSAGE);
|
}
|
|
BaseStation baseStation = new BaseStation();
|
baseStation.load();
|
String baseStationCoordinates = normalizeCoordinateValue(baseStation.getInstallationCoordinates());
|
if (!isMeaningfulValue(baseStationCoordinates)) {
|
return BoundarySnapshotResult.failure("未获取到有效的基准站坐标,请先在基准站管理中设置", JOptionPane.WARNING_MESSAGE);
|
}
|
|
String optimizedBoundary;
|
try {
|
// 构建边界字符串,格式为 "(lat1,lon1,alt1;lat2,lon2,alt2;...)"
|
String boundaryStr = buildBoundaryStringForOptimization(uniqueCoordinates);
|
// 调用 Bianjieyouhuatoxy.optimizeBoundary 方法
|
optimizedBoundary = Bianjieyouhuatoxy.optimizeBoundary(baseStationCoordinates, boundaryStr);
|
if (optimizedBoundary == null || optimizedBoundary.isEmpty() || optimizedBoundary.startsWith("ERROR")) {
|
return BoundarySnapshotResult.failure("生成地块边界失败: 优化后的边界坐标无效", JOptionPane.ERROR_MESSAGE);
|
}
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
return BoundarySnapshotResult.failure("生成地块边界失败: " + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
|
}
|
|
String originalBoundary = buildOriginalBoundaryString(uniqueCoordinates);
|
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());
|
|
if (previewButton != null) {
|
previewButton.addActionListener(e -> previewMowingPath());
|
}
|
|
// 关闭对话框
|
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();
|
// 步骤1显示时,立即更新按钮状态
|
SwingUtilities.invokeLater(() -> updateStep1ButtonState());
|
}
|
|
// 更新按钮状态
|
updateButtonState(step);
|
}
|
|
private void updateButtonState(int step) {
|
prevButton.setVisible(step > 1);
|
|
if (step < 3) {
|
nextButton.setVisible(true);
|
createButton.setVisible(false);
|
setPathAvailability(false);
|
if (previewButton != null) {
|
previewButton.setVisible(false);
|
previewButton.setEnabled(false);
|
}
|
if (previewButtonSpacer != null) {
|
previewButtonSpacer.setVisible(false);
|
}
|
// 步骤1:根据验证结果更新下一步按钮状态
|
if (step == 1) {
|
updateStep1ButtonState();
|
}
|
// 步骤2显示边界预览按钮
|
if (step == 2) {
|
if (boundaryPreviewButton != null) {
|
boundaryPreviewButton.setVisible(true);
|
// 根据是否完成边界绘制来设置按钮状态和背景颜色
|
boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn");
|
boundaryPreviewButton.setEnabled(boundaryDrawn);
|
if (boundaryDrawn) {
|
boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景
|
} else {
|
boundaryPreviewButton.setBackground(MEDIUM_GRAY); // 灰色背景
|
}
|
}
|
// 更新下一步按钮状态(根据是否完成边界绘制)
|
boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn");
|
nextButton.setEnabled(boundaryDrawn);
|
if (boundaryDrawn) {
|
nextButton.setBackground(PRIMARY_COLOR); // 绿色背景
|
} else {
|
nextButton.setBackground(MEDIUM_GRAY); // 灰色背景
|
}
|
// 更新开始绘制按钮状态
|
updateStartDrawingButtonState();
|
} else {
|
if (boundaryPreviewButton != null) {
|
boundaryPreviewButton.setVisible(false);
|
boundaryPreviewButton.setEnabled(false);
|
}
|
}
|
} else {
|
nextButton.setVisible(false);
|
createButton.setVisible(true);
|
if (previewButton != null) {
|
previewButton.setVisible(true);
|
}
|
if (previewButtonSpacer != null) {
|
previewButtonSpacer.setVisible(true);
|
}
|
if (boundaryPreviewButton != null) {
|
boundaryPreviewButton.setVisible(false);
|
boundaryPreviewButton.setEnabled(false);
|
}
|
setPathAvailability(hasGeneratedPath());
|
}
|
|
Container parent = prevButton.getParent();
|
if (parent != null) {
|
parent.revalidate();
|
parent.repaint();
|
}
|
}
|
|
/**
|
* 更新步骤1的下一步按钮状态
|
* 根据地块名称是否填写来设置按钮的启用状态和背景颜色
|
*/
|
private void updateStep1ButtonState() {
|
if (nextButton == null || currentStep != 1) {
|
return;
|
}
|
|
String name = areaNameField.getText().trim();
|
boolean canProceed = !name.isEmpty();
|
|
nextButton.setEnabled(canProceed);
|
if (canProceed) {
|
// 可点击时:绿色背景
|
nextButton.setBackground(PRIMARY_COLOR);
|
} else {
|
// 不可点击时:灰色背景
|
nextButton.setBackground(MEDIUM_GRAY);
|
}
|
}
|
|
/**
|
* 更新步骤2的开始绘制按钮状态
|
* 根据是否选择了绘制方式来设置按钮的启用状态和背景颜色
|
*/
|
private void updateStartDrawingButtonState() {
|
if (startEndDrawingBtn == null || currentStep != 2) {
|
return;
|
}
|
|
boolean hasSelectedMethod = dikuaiData.containsKey("drawingMethod");
|
boolean isDrawingActive = isDrawing;
|
|
// 如果正在绘制,按钮状态由toggleDrawing方法控制
|
if (isDrawingActive) {
|
return;
|
}
|
|
// 如果已经完成绘制,按钮显示"已完成"且不可用
|
boolean boundaryDrawn = dikuaiData.containsKey("boundaryDrawn");
|
if (boundaryDrawn) {
|
startEndDrawingBtn.setEnabled(false);
|
startEndDrawingBtn.setBackground(MEDIUM_GRAY);
|
return;
|
}
|
|
startEndDrawingBtn.setEnabled(hasSelectedMethod);
|
if (hasSelectedMethod) {
|
// 已选择绘制方式:绿色背景,可点击
|
startEndDrawingBtn.setBackground(PRIMARY_COLOR);
|
} else {
|
// 未选择绘制方式:灰色背景,不可点击
|
startEndDrawingBtn.setBackground(MEDIUM_GRAY);
|
}
|
}
|
|
/**
|
* 更新步骤2的预览和下一步按钮状态(在完成边界绘制后调用)
|
* 将按钮背景颜色设置为绿色,表示可以点击操作
|
*/
|
private void updateStep2ButtonsAfterDrawing() {
|
if (currentStep != 2) {
|
return;
|
}
|
|
// 更新预览按钮
|
if (boundaryPreviewButton != null) {
|
boundaryPreviewButton.setEnabled(true);
|
boundaryPreviewButton.setBackground(PRIMARY_COLOR); // 绿色背景
|
}
|
|
// 更新下一步按钮
|
if (nextButton != null) {
|
nextButton.setEnabled(true);
|
nextButton.setBackground(PRIMARY_COLOR); // 绿色背景
|
}
|
}
|
|
private boolean validateCurrentStep() {
|
switch (currentStep) {
|
case 1:
|
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());
|
// 保存割草宽度(从文本框获取,单位:米,转换为厘米保存)
|
if (mowingWidthField != null) {
|
String widthStr = mowingWidthField.getText().trim();
|
if (widthStr != null && !widthStr.isEmpty()) {
|
try {
|
double widthMeters = Double.parseDouble(widthStr);
|
double widthCm = widthMeters * 100.0;
|
dikuaiData.put("mowingWidth", String.format(Locale.US, "%.2f", widthCm));
|
} catch (NumberFormatException e) {
|
// 保持原值
|
dikuaiData.put("mowingWidth", widthStr);
|
}
|
}
|
}
|
if (!hasGeneratedPath()) {
|
JOptionPane.showMessageDialog(this, "请先生成割草路径", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
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();
|
|
// 获取割草机编号
|
String mowerId = Setsys.getPropertyValue("mowerId");
|
|
// 如果有割草机编号,使用 编号+两位自增数字 格式
|
if (mowerId != null && !mowerId.trim().isEmpty() && !"-1".equals(mowerId)) {
|
int attempt = 1;
|
while (true) {
|
// 格式化为两位数字,如 01, 02, ...
|
String suffix = String.format("%02d", attempt);
|
String candidate = mowerId + suffix;
|
if (!existing.containsKey(candidate)) {
|
return candidate;
|
}
|
attempt++;
|
}
|
}
|
|
// 如果没有割草机编号,回退到默认逻辑
|
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, false);
|
}
|
}
|
|
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();
|
updateBoundaryXYDisplay();
|
// 更新预览和下一步按钮状态(背景颜色变绿色,可点击)
|
updateStep2ButtonsAfterDrawing();
|
} else {
|
if (startEndDrawingBtn != null) {
|
startEndDrawingBtn.setText("开始绘制");
|
startEndDrawingBtn.setBackground(PRIMARY_COLOR);
|
startEndDrawingBtn.setEnabled(true);
|
}
|
updateOtherOptionsState(false);
|
showStep(1);
|
hideBoundaryPointSummary();
|
}
|
|
restoreGeneratedPathState(session);
|
}
|
|
private void restoreGeneratedPathState(DrawingSession session) {
|
if (session == null || session.data == null) {
|
showPathGenerationMessage("", true);
|
return;
|
}
|
|
Map<String, String> data = session.data;
|
|
if (mowingPatternCombo != null) {
|
String pattern = data.get("mowingPattern");
|
if (pattern != null) {
|
ComboBoxModel<String> model = mowingPatternCombo.getModel();
|
for (int i = 0; i < model.getSize(); i++) {
|
String candidate = model.getElementAt(i);
|
if (pattern.equals(candidate)) {
|
mowingPatternCombo.setSelectedIndex(i);
|
break;
|
}
|
}
|
}
|
}
|
|
// 恢复割草宽度:从保存的割草宽度(厘米)转换为米并设置
|
String width = data.get("mowingWidth");
|
if (isMeaningfulValue(width) && mowingWidthField != null) {
|
try {
|
double mowingWidthCm = Double.parseDouble(width.trim());
|
// 将厘米转换为米
|
double mowingWidthMeters = mowingWidthCm / 100.0;
|
mowingWidthField.setText(String.format(Locale.US, "%.3f", mowingWidthMeters));
|
} catch (NumberFormatException ignored) {
|
// 保持当前值
|
}
|
}
|
|
// 恢复割刀宽度:从保存的割草宽度反推割刀宽度并设置
|
// 由于割草宽度 = 割刀宽度 * 0.85,所以割刀宽度 = 割草宽度 / 0.85
|
if (isMeaningfulValue(width) && bladeWidthField != null) {
|
try {
|
double mowingWidthCm = Double.parseDouble(width.trim());
|
// 将厘米转换为米,然后反推割刀宽度
|
double mowingWidthMeters = mowingWidthCm / 100.0;
|
double bladeWidthMeters = mowingWidthMeters / 0.85;
|
bladeWidthField.setText(String.format(Locale.US, "%.3f", bladeWidthMeters));
|
// 触发自动计算安全距离
|
if (safetyDistanceField != null) {
|
String safetyDistanceStr = Gecaoanquanjuli.calculateSafetyDistanceFromString(
|
String.format(Locale.US, "%.3f", bladeWidthMeters));
|
safetyDistanceField.setText(safetyDistanceStr);
|
}
|
} catch (NumberFormatException ignored) {
|
// 保持当前值
|
}
|
}
|
|
boolean hasPath = isMeaningfulValue(data.get("plannedPath"));
|
if (!hasPath) {
|
showPathGenerationMessage("", true);
|
if (currentStep == 3) {
|
setPathAvailability(false);
|
}
|
return;
|
}
|
|
String message = data.get(KEY_PATH_MESSAGE_TEXT);
|
boolean success = !"false".equalsIgnoreCase(data.get(KEY_PATH_MESSAGE_SUCCESS));
|
showStep(3);
|
if (isMeaningfulValue(message)) {
|
showPathGenerationMessage(message, success);
|
} else {
|
showPathGenerationMessage("已生成割草路径,可点击“预览”按钮查看效果。", true);
|
}
|
setPathAvailability(true);
|
}
|
|
public static void finishDrawingSession() {
|
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);
|
}
|
|
public static void resumeFromPreview() {
|
Shouye shouye = Shouye.getInstance();
|
if (shouye != null) {
|
shouye.exitMowingPathPreview();
|
}
|
if (activeSession == null) {
|
return;
|
}
|
resumeRequested = true;
|
Component parent = shouye != null ? shouye : null;
|
SwingUtilities.invokeLater(() -> showAddDikuaiDialog(parent));
|
}
|
|
private void createDikuai() {
|
if (!validateCurrentStep()) {
|
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 (mowingWidthField != null) {
|
String mowingWidthStr = mowingWidthField.getText().trim();
|
if (mowingWidthStr != null && !mowingWidthStr.isEmpty()) {
|
try {
|
double mowingWidthMeters = Double.parseDouble(mowingWidthStr);
|
// 转换为厘米保存
|
double mowingWidthCm = mowingWidthMeters * 100.0;
|
dikuai.setMowingWidth(String.format(Locale.US, "%.2f", mowingWidthCm));
|
} catch (NumberFormatException e) {
|
// 如果解析失败,尝试使用dikuaiData中的值
|
if (dikuaiData.containsKey("mowingWidth")) {
|
dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
|
}
|
}
|
} else if (dikuaiData.containsKey("mowingWidth")) {
|
dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
|
}
|
} else if (dikuaiData.containsKey("mowingWidth")) {
|
dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
|
}
|
|
// 保存割草机割刀宽度(从文本框获取,单位:米)
|
if (bladeWidthField != null) {
|
String bladeWidthStr = bladeWidthField.getText().trim();
|
if (bladeWidthStr != null && !bladeWidthStr.isEmpty()) {
|
try {
|
double bladeWidthMeters = Double.parseDouble(bladeWidthStr);
|
// 保存为米,保留2位小数
|
dikuai.setMowingBladeWidth(String.format(Locale.US, "%.2f", bladeWidthMeters));
|
} catch (NumberFormatException e) {
|
// 解析失败时,保存原始字符串
|
dikuai.setMowingBladeWidth(bladeWidthStr);
|
}
|
}
|
}
|
|
// 保存割草安全距离(从文本框获取,单位:米)
|
if (safetyDistanceField != null) {
|
String safetyDistanceStr = safetyDistanceField.getText().trim();
|
if (safetyDistanceStr != null && !safetyDistanceStr.isEmpty()) {
|
try {
|
double safetyDistanceMeters = Double.parseDouble(safetyDistanceStr);
|
// 保存为米,保留2位小数
|
String formattedValue = String.format(Locale.US, "%.2f", safetyDistanceMeters);
|
dikuai.setMowingSafetyDistance(formattedValue);
|
// 同时保存到dikuaiData中,以便后续使用
|
dikuaiData.put("mowingSafetyDistance", formattedValue);
|
} catch (NumberFormatException e) {
|
// 解析失败时,保存原始字符串
|
dikuai.setMowingSafetyDistance(safetyDistanceStr);
|
dikuaiData.put("mowingSafetyDistance", safetyDistanceStr);
|
}
|
} else if (dikuaiData.containsKey("mowingSafetyDistance")) {
|
// 如果文本框为空,尝试从dikuaiData获取
|
dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance"));
|
}
|
} else if (dikuaiData.containsKey("mowingSafetyDistance")) {
|
// 如果safetyDistanceField为null,从dikuaiData获取
|
dikuai.setMowingSafetyDistance(dikuaiData.get("mowingSafetyDistance"));
|
}
|
|
// 保存割草路径坐标
|
String plannedPath = dikuaiData.get("plannedPath");
|
if (isMeaningfulValue(plannedPath)) {
|
dikuai.setPlannedPath(plannedPath);
|
}
|
|
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;
|
// 如果会话已生成路径,优先显示步骤3
|
if (activeSession.data != null && isMeaningfulValue(activeSession.data.get("plannedPath"))) {
|
dialog.showStep(3);
|
} else if (activeSession.drawingCompleted) {
|
// 如果会话已完成绘制,显示步骤2
|
dialog.showStep(2);
|
}
|
}
|
|
dialog.setVisible(true);
|
return dialog.createdLandNumber;
|
}
|
}
|