From d709e6dad60398fd599900cf781d0dd1e8c37c1c Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期一, 01 十二月 2025 19:55:14 +0800
Subject: [PATCH] 20251201
---
src/gecaoji/Device.java | 5
src/dikuai/addzhangaiwu.java | 1243 ++++++++++++++++
image/startzuoye.png | 0
.classpath | 8
src/zhangaiwu/AddDikuai.java | 393 +++++
src/zhuye/MapRenderer.java | 289 +++
image/zantingzuoye.png | 0
src/udpdell/UDPServer.java | 44
src/zhuye/Shouye.java | 419 +++++
Obstacledge.properties | 21
src/dikuai/Dikuaiguanli.java | 75
src/zhangaiwu/Obstacledraw.java | 635 ++++++++
src/zhangaiwu/Obstacledge.java | 827 ++++++++++
src/lujing/Lunjingguihua.java | 568 +++++++
src/ui/UIConfig.java | 4
15 files changed, 4,450 insertions(+), 81 deletions(-)
diff --git a/.classpath b/.classpath
index fb50116..5b68229 100644
--- a/.classpath
+++ b/.classpath
@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
+ <attributes>
+ <attribute name="module" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="lib" path="lib/jSerialComm-2.10.4.jar"/>
+ <classpathentry kind="lib" path="lib/jts-core-1.19.0.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/Obstacledge.properties b/Obstacledge.properties
new file mode 100644
index 0000000..b45db39
--- /dev/null
+++ b/Obstacledge.properties
@@ -0,0 +1,21 @@
+# 鍓茶崏鏈哄湴鍧楅殰纰嶇墿閰嶇疆鏂囦欢
+# 鐢熸垚鏃堕棿锛�2025-12-01T18:24:39.295276600
+# 鍧愭爣绯伙細WGS84锛堝害鍒嗘牸寮忥級
+
+# ============ 鍦板潡鍩哄噯绔欓厤缃� ============
+# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].baseStation=[缁忓害],[N/S],[绾害],[E/W]
+plot.DK-001.baseStation=2324.200273,N,11330.666730,E
+
+# ============ 闅滅鐗╅厤缃� ============
+# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].obstacle.[闅滅鐗╁悕绉癩.shape=[0|1]
+# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].obstacle.[闅滅鐗╁悕绉癩.originalCoords=[鍧愭爣涓瞉
+# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].obstacle.[闅滅鐗╁悕绉癩.xyCoords=[鍧愭爣涓瞉
+
+# --- 鍦板潡001鐨勯殰纰嶇墿 ---
+plot.DK-001.obstacle.tree1.shape=0
+plot.DK-001.obstacle.tree1.originalCoords=2324.200373,N;11330.666830,E;2324.200400,N;11330.666850,E
+plot.DK-001.obstacle.tree1.xyCoords=5.20,3.80;7.50,6.20
+plot.DK-001.obstacle.pond.shape=1
+plot.DK-001.obstacle.pond.originalCoords=2324.200500,N;11330.666900,E;2324.200550,N;11330.666950,E;2324.200600,N;11330.666900,E;2324.200550,N;11330.666850,E
+plot.DK-001.obstacle.pond.xyCoords=15.30,20.10;18.70,22.50;22.10,20.10;18.70,17.80
+
diff --git a/image/startzuoye.png b/image/startzuoye.png
new file mode 100644
index 0000000..b70648d
--- /dev/null
+++ b/image/startzuoye.png
Binary files differ
diff --git a/image/zantingzuoye.png b/image/zantingzuoye.png
new file mode 100644
index 0000000..e454060
--- /dev/null
+++ b/image/zantingzuoye.png
Binary files differ
diff --git a/src/dikuai/Dikuaiguanli.java b/src/dikuai/Dikuaiguanli.java
index 269d7a3..94a3d1c 100644
--- a/src/dikuai/Dikuaiguanli.java
+++ b/src/dikuai/Dikuaiguanli.java
@@ -5,6 +5,7 @@
import java.awt.event.*;
import java.awt.datatransfer.*;
import java.awt.datatransfer.StringSelection;
+import java.io.File;
import java.math.BigDecimal;
import ui.UIConfig;
import ui.UIUtils;
@@ -14,8 +15,12 @@
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
+
+import zhangaiwu.AddDikuai;
+import zhangaiwu.Obstacledge;
import zhuye.MapRenderer;
import zhuye.Shouye;
+import zhuye.Coordinate;
/**
* 鍦板潡绠$悊闈㈡澘 - 鍗$墖寮忓竷灞�璁捐
@@ -662,14 +667,20 @@
if (renderer != null) {
String boundary = (dikuai != null) ? dikuai.getBoundaryCoordinates() : null;
String plannedPath = (dikuai != null) ? dikuai.getPlannedPath() : null;
+ String obstacles = (dikuai != null) ? dikuai.getObstacleCoordinates() : null;
renderer.setCurrentBoundary(boundary, landNumber, landNumber == null ? null : landName);
renderer.setCurrentPlannedPath(plannedPath);
+ renderer.setCurrentObstacles(obstacles, landNumber);
boolean showBoundaryPoints = landNumber != null && boundaryPointVisibility.getOrDefault(landNumber, false);
renderer.setBoundaryPointsVisible(showBoundaryPoints);
}
}
}
+ public static String getCurrentWorkLandNumber() {
+ return currentWorkLandNumber;
+ }
+
private ImageIcon loadIcon(String path, int width, int height) {
try {
ImageIcon rawIcon = new ImageIcon(path);
@@ -693,6 +704,7 @@
if (ownerWindow instanceof JDialog) {
ownerWindow.dispose();
}
+ Coordinate.coordinates.clear();
latestInstance = null;
SwingUtilities.invokeLater(() -> AddDikuai.showAddDikuaiDialog(parentComponent));
});
@@ -708,22 +720,55 @@
}
private void addNewObstacle(Dikuai dikuai) {
- String obstacleCoords = JOptionPane.showInputDialog(this,
- "璇疯緭鍏ラ殰纰嶇墿鍧愭爣(鏍煎紡: 绾害1,缁忓害1;绾害2,缁忓害2;...):");
-
- if (obstacleCoords != null && !obstacleCoords.trim().isEmpty()) {
- // 鏇存柊闅滅鐗╁潗鏍�
- Dikuai.updateField(dikuai.getLandNumber(), "obstacleCoordinates", obstacleCoords.trim());
- Dikuai.updateField(dikuai.getLandNumber(), "updateTime", getCurrentTime());
-
- // 淇濆瓨鍒版枃浠�
- Dikuai.saveToProperties();
-
- // 鍒锋柊鏄剧ず
- loadDikuaiData();
-
- JOptionPane.showMessageDialog(this, "闅滅鐗╁潗鏍囧凡娣诲姞锛�", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ if (dikuai == null) {
+ JOptionPane.showMessageDialog(this, "鏈壘鍒板綋鍓嶅湴鍧楋紝鏃犳硶鏂板闅滅鐗�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
}
+ Window windowAncestor = SwingUtilities.getWindowAncestor(this);
+ if (windowAncestor instanceof JDialog) {
+ windowAncestor.dispose();
+ }
+ Component parent = windowAncestor != null ? windowAncestor.getOwner() instanceof Component ? (Component) windowAncestor.getOwner() : windowAncestor : null;
+ List<String> existingObstacleNames = loadObstacleNamesForLand(dikuai.getLandNumber());
+ addzhangaiwu.showDialog(parent, dikuai, existingObstacleNames);
+ loadDikuaiData();
+ }
+
+ private List<String> loadObstacleNamesForLand(String landNumber) {
+ List<String> names = new ArrayList<>();
+ if (landNumber == null || landNumber.trim().isEmpty()) {
+ return names;
+ }
+ try {
+ File configFile = new File("Obstacledge.properties");
+ if (!configFile.exists()) {
+ return names;
+ }
+ Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+ if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+ return names;
+ }
+ Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
+ if (plot == null) {
+ return names;
+ }
+ for (Obstacledge.Obstacle obstacle : plot.getObstacles()) {
+ if (obstacle == null) {
+ continue;
+ }
+ String name = obstacle.getObstacleName();
+ if (name == null) {
+ continue;
+ }
+ String trimmed = name.trim();
+ if (!trimmed.isEmpty() && !names.contains(trimmed)) {
+ names.add(trimmed);
+ }
+ }
+ } catch (Exception ex) {
+ System.err.println("璇诲彇闅滅鐗╅厤缃け璐�: " + ex.getMessage());
+ }
+ return names;
}
private void editMowingWidth(Dikuai dikuai) {
diff --git a/src/dikuai/addzhangaiwu.java b/src/dikuai/addzhangaiwu.java
new file mode 100644
index 0000000..c936a6b
--- /dev/null
+++ b/src/dikuai/addzhangaiwu.java
@@ -0,0 +1,1243 @@
+package dikuai;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Image;
+import java.awt.Window;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+
+import baseStation.BaseStation;
+import gecaoji.Device;
+import ui.UIConfig;
+import zhuye.Coordinate;
+import zhuye.Shouye;
+import zhangaiwu.AddDikuai;
+import zhangaiwu.Obstacledge;
+
+/**
+ * 闅滅鐗╂柊澧�/缂栬緫瀵硅瘽妗嗐�傝璁¤瑷�鍙傝�� {@link AddDikuai}锛屾敮鎸侀�氳繃瀹炲湴缁樺埗閲囬泦闅滅鐗╁潗鏍囥��
+ */
+public class addzhangaiwu extends JDialog {
+ private static final long serialVersionUID = 1L;
+ private static final double METERS_PER_DEGREE_LAT = 111320.0d;
+
+ private final Color PRIMARY_COLOR = new Color(46, 139, 87);
+ private final Color PRIMARY_DARK = new Color(30, 107, 69);
+ private final Color PRIMARY_LIGHT = new Color(232, 245, 233);
+ private final Color WHITE = Color.WHITE;
+ private final Color BORDER_COLOR = new Color(222, 226, 230);
+ private final Color MEDIUM_GRAY = new Color(233, 236, 239);
+ private final Color TEXT_COLOR = new Color(33, 37, 41);
+ private final Color LIGHT_TEXT = new Color(108, 117, 125);
+
+ private final Dikuai targetDikuai;
+ private final List<ExistingObstacle> existingObstacles;
+ private final Map<String, String> formData = new HashMap<>();
+
+ private CardLayout cardLayout;
+ private JPanel stepsPanel;
+ private JButton prevButton;
+ private JButton nextButton;
+ private JButton saveButton;
+
+ private JPanel methodOptionsPanel;
+ private JPanel shapeOptionsPanel;
+ private final Map<String, JPanel> methodOptionPanels = new HashMap<>();
+ private final Map<String, JPanel> shapeOptionPanels = new HashMap<>();
+ private JPanel selectedMethodPanel;
+ private JPanel selectedShapePanel;
+ private JButton drawButton;
+ private JLabel drawingStatusLabel;
+
+ private int currentStep = 1;
+ private boolean drawingInProgress;
+ private String activeDrawingShape;
+
+ private static ObstacleDrawingSession activeSession;
+ private static boolean resumeRequested;
+
+ private static final class ObstacleDrawingSession {
+ Dikuai target;
+ Map<String, String> data = new HashMap<>();
+ String baseStation;
+ boolean drawingCompleted;
+ boolean captureSuccessful;
+ String captureMessage;
+ }
+
+ private addzhangaiwu(Window owner, Dikuai target, List<String> obstacleNames) {
+ super(owner, "鏂板闅滅鐗�", ModalityType.APPLICATION_MODAL);
+ if (target == null) {
+ throw new IllegalArgumentException("targetDikuai 涓嶈兘涓虹┖");
+ }
+ this.targetDikuai = target;
+ this.existingObstacles = Collections.unmodifiableList(resolveExistingObstacles(target, obstacleNames));
+ initializeUI();
+ setupEventHandlers();
+ preloadData();
+ }
+
+ private void initializeUI() {
+ Dimension dialogSize = new Dimension(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
+ setSize(dialogSize);
+ setPreferredSize(dialogSize);
+ setMinimumSize(dialogSize);
+ setMaximumSize(dialogSize);
+ setResizable(false);
+ setLocationRelativeTo(getOwner());
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().setBackground(WHITE);
+
+ JPanel container = new JPanel(new BorderLayout());
+ container.setBackground(WHITE);
+ container.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
+ container.setPreferredSize(new Dimension(UIConfig.DIALOG_WIDTH - 40, UIConfig.DIALOG_HEIGHT - 40));
+
+ cardLayout = new CardLayout();
+ stepsPanel = new JPanel(cardLayout);
+ stepsPanel.setBackground(WHITE);
+ stepsPanel.setPreferredSize(new Dimension(UIConfig.DIALOG_WIDTH - 40, UIConfig.DIALOG_HEIGHT - 140));
+ stepsPanel.add(createStep1Panel(), "step1");
+ stepsPanel.add(createStep2Panel(), "step2");
+
+ container.add(stepsPanel, BorderLayout.CENTER);
+ container.add(createButtonPanel(), BorderLayout.SOUTH);
+
+ add(container, BorderLayout.CENTER);
+ showStep(1);
+ }
+
+ private JPanel createStep1Panel() {
+ JPanel stepPanel = new JPanel();
+ stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
+ stepPanel.setBackground(WHITE);
+
+ JLabel title = new JLabel("姝ラ1锛氱‘璁ゅ湴鍧椾俊鎭�");
+ title.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 20));
+ title.setForeground(TEXT_COLOR);
+ title.setAlignmentX(Component.LEFT_ALIGNMENT);
+ title.setHorizontalAlignment(SwingConstants.LEFT);
+ stepPanel.add(title);
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
+
+ JPanel infoContainer = new JPanel();
+ infoContainer.setLayout(new BoxLayout(infoContainer, BoxLayout.Y_AXIS));
+ infoContainer.setOpaque(false);
+ infoContainer.setAlignmentX(Component.LEFT_ALIGNMENT);
+ infoContainer.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
+
+ infoContainer.add(createInfoRow("鍦板潡缂栧彿锛�", safeValue(targetDikuai.getLandNumber(), "鏈缃�")));
+ infoContainer.add(Box.createRigidArea(new Dimension(0, 16)));
+ infoContainer.add(createInfoRow("鍦板潡鍚嶇О锛�", safeValue(targetDikuai.getLandName(), "鏈懡鍚�")));
+ infoContainer.add(Box.createRigidArea(new Dimension(0, 20)));
+ infoContainer.add(buildExistingObstacleSection());
+
+ stepPanel.add(infoContainer);
+ stepPanel.add(Box.createVerticalGlue());
+ return stepPanel;
+ }
+
+ private JPanel buildExistingObstacleSection() {
+ JPanel section = new JPanel();
+ section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS));
+ section.setOpaque(false);
+ section.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ JLabel titleLabel = new JLabel("宸叉湁闅滅鐗�");
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 16));
+ titleLabel.setForeground(TEXT_COLOR);
+ titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ section.add(titleLabel);
+ section.add(Box.createRigidArea(new Dimension(0, 8)));
+
+ if (existingObstacles.isEmpty()) {
+ JLabel emptyLabel = new JLabel("鏆傛棤");
+ emptyLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ emptyLabel.setForeground(LIGHT_TEXT);
+ emptyLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ section.add(emptyLabel);
+ return section;
+ }
+
+ JPanel listPanel = new JPanel();
+ listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS));
+ listPanel.setOpaque(false);
+ listPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ for (int i = 0; i < existingObstacles.size(); i++) {
+ ExistingObstacle obstacle = existingObstacles.get(i);
+ JPanel row = createObstacleSummaryRow(obstacle);
+ row.setAlignmentX(Component.LEFT_ALIGNMENT);
+ listPanel.add(row);
+ if (i < existingObstacles.size() - 1) {
+ listPanel.add(Box.createRigidArea(new Dimension(0, 6)));
+ }
+ }
+
+ section.add(listPanel);
+ return section;
+ }
+
+ private JPanel createObstacleSummaryRow(ExistingObstacle obstacle) {
+ JPanel row = new JPanel();
+ row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
+ row.setOpaque(false);
+ row.setAlignmentX(Component.LEFT_ALIGNMENT);
+ row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
+
+ JLabel infoLabel = new JLabel(buildObstacleSummaryText(obstacle));
+ infoLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ infoLabel.setForeground(TEXT_COLOR);
+ infoLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ JButton editButton = createInlineButton("淇敼");
+ editButton.addActionListener(e -> populateObstacleForEditing(obstacle));
+
+ row.add(infoLabel);
+ row.add(Box.createHorizontalGlue());
+ row.add(editButton);
+ return row;
+ }
+
+ private JButton createInlineButton(String text) {
+ JButton button = new JButton(text);
+ button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 12));
+ button.setForeground(WHITE);
+ button.setBackground(PRIMARY_COLOR);
+ button.setBorder(BorderFactory.createEmptyBorder(6, 16, 6, 16));
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ button.setFocusPainted(false);
+ Dimension size = new Dimension(72, 28);
+ button.setPreferredSize(size);
+ button.setMinimumSize(size);
+ button.setMaximumSize(size);
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ button.setBackground(PRIMARY_DARK);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ button.setBackground(PRIMARY_COLOR);
+ }
+ });
+ return button;
+ }
+
+ private String buildObstacleSummaryText(ExistingObstacle obstacle) {
+ String name = obstacle.getName();
+ String shape = obstacle.getShapeDisplay();
+ String coordPreview = buildCoordinatePreview(obstacle.getCoordinates(), 5);
+ return String.format(Locale.CHINA, "%s锛�%s锛屽潗鏍�:%s", name, shape, coordPreview);
+ }
+
+ private String buildCoordinatePreview(String coords, int keepLength) {
+ if (!isMeaningfulValue(coords)) {
+ return "鏃犲潗鏍�";
+ }
+ String sanitized = coords.replaceAll("\\s+", "");
+ if (sanitized.length() <= keepLength) {
+ return sanitized;
+ }
+ if (keepLength <= 0) {
+ return "...";
+ }
+ return sanitized.substring(0, keepLength) + "...";
+ }
+
+ private void populateObstacleForEditing(ExistingObstacle obstacle) {
+ if (obstacle == null) {
+ return;
+ }
+ String coords = obstacle.getCoordinates();
+ if (isMeaningfulValue(coords)) {
+ formData.put("obstacleCoordinates", coords.trim());
+ } else {
+ formData.remove("obstacleCoordinates");
+ }
+ String shapeKey = obstacle.getShapeKey();
+ if (shapeKey != null) {
+ JPanel shapePanel = shapeOptionPanels.get(shapeKey);
+ selectShapeOption(shapePanel, shapeKey, false);
+ }
+ updateDrawingStatus();
+ showStep(2);
+ }
+
+ private JPanel createStep2Panel() {
+ JPanel stepPanel = new JPanel();
+ stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
+ stepPanel.setBackground(WHITE);
+
+ JLabel title = new JLabel("姝ラ2锛氱粯鍒堕殰纰嶇墿鍧愭爣");
+ title.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 20));
+ title.setForeground(TEXT_COLOR);
+ title.setAlignmentX(Component.LEFT_ALIGNMENT);
+ stepPanel.add(title);
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 16)));
+
+ stepPanel.add(createSectionHeader("閫夋嫨缁樺埗鏂瑰紡", "鏀寔鍓茶崏鏈烘垨鎵嬫寔璁惧閲囬泦闅滅鐗╄竟鐣�"));
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+ methodOptionsPanel = new JPanel();
+ methodOptionsPanel.setLayout(new BoxLayout(methodOptionsPanel, BoxLayout.Y_AXIS));
+ methodOptionsPanel.setOpaque(false);
+ methodOptionsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ methodOptionsPanel.add(createMethodOption("mower", "鍓茶崏鏈虹粯鍒�", "椹鹃┒鍓茶崏鏈烘部闅滅鐗╄竟鐣岃椹堕噰闆嗗潗鏍�", "image/mow.png"));
+ methodOptionsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+ methodOptionsPanel.add(createMethodOption("handheld", "鎵嬫寔璁惧缁樺埗", "浣跨敤鎵嬫寔璁惧娌块殰纰嶇墿杈圭晫琛岃蛋璁板綍鍧愭爣", "image/URT.png"));
+ stepPanel.add(methodOptionsPanel);
+
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
+ stepPanel.add(createSectionHeader("闅滅鐗╁舰鐘�", "澶氳竟褰㈤渶閲囬泦澶氫釜鐐癸紝鍦嗗舰鍙渶鍦嗗績涓庡渾鍛ㄤ笂涓�鐐�"));
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+ shapeOptionsPanel = new JPanel();
+ shapeOptionsPanel.setLayout(new BoxLayout(shapeOptionsPanel, BoxLayout.Y_AXIS));
+ shapeOptionsPanel.setOpaque(false);
+ shapeOptionsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ shapeOptionsPanel.add(createShapeOption("polygon", "澶氳竟褰�", "渚濇閲囬泦杞粨涓婄殑澶氫釜鐐规垨鑰呮部闅滅鐗╄竟缂樿蛋涓�鍦�"));
+ shapeOptionsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+ shapeOptionsPanel.add(createShapeOption("circle", "鍦嗗舰", "鍏堥噰闆嗗渾蹇冨潗鏍囷紝鍐嶉噰闆嗗渾鍛ㄤ笂涓�鐐�"));
+ stepPanel.add(shapeOptionsPanel);
+
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 24)));
+
+ drawButton = createPrimaryButton("寮�濮嬬粯鍒�", 16);
+ drawButton.setAlignmentX(Component.LEFT_ALIGNMENT);
+ drawButton.addActionListener(e -> startDrawingWorkflow());
+ stepPanel.add(drawButton);
+
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 12)));
+
+ drawingStatusLabel = new JLabel("灏氭湭閲囬泦闅滅鐗╁潗鏍�");
+ drawingStatusLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ drawingStatusLabel.setForeground(LIGHT_TEXT);
+ drawingStatusLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ stepPanel.add(drawingStatusLabel);
+
+ stepPanel.add(Box.createVerticalGlue());
+ return stepPanel;
+ }
+
+ private JPanel createButtonPanel() {
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
+ buttonPanel.setBackground(WHITE);
+ buttonPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0));
+
+ prevButton = createSecondaryButton("涓婁竴姝�");
+ nextButton = createPrimaryButton("涓嬩竴姝�", 16);
+ saveButton = createPrimaryButton("淇濆瓨", 16);
+ saveButton.setVisible(false);
+
+ buttonPanel.add(prevButton);
+ buttonPanel.add(Box.createHorizontalGlue());
+ buttonPanel.add(nextButton);
+ buttonPanel.add(Box.createRigidArea(new Dimension(12, 0)));
+ buttonPanel.add(saveButton);
+
+ return buttonPanel;
+ }
+
+ private JPanel createInstructionPanel(String text) {
+ JPanel instruction = new JPanel(new BorderLayout());
+ instruction.setBackground(PRIMARY_LIGHT);
+ instruction.setAlignmentX(Component.LEFT_ALIGNMENT);
+ instruction.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createMatteBorder(0, 5, 0, 0, PRIMARY_COLOR),
+ BorderFactory.createEmptyBorder(12, 14, 12, 14)));
+
+ JLabel icon = new JLabel("馃挕", SwingConstants.CENTER);
+ icon.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 18));
+ icon.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
+ instruction.add(icon, BorderLayout.WEST);
+
+ JLabel content = new JLabel(text);
+ content.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ content.setForeground(TEXT_COLOR);
+ instruction.add(content, BorderLayout.CENTER);
+
+ return instruction;
+ }
+
+ private JPanel createSectionHeader(String title, String hint) {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setOpaque(false);
+ panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ JLabel titleLabel = new JLabel(title);
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 16));
+ titleLabel.setForeground(TEXT_COLOR);
+ titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ panel.add(titleLabel);
+
+ if (hint != null && !hint.trim().isEmpty()) {
+ panel.add(Box.createRigidArea(new Dimension(0, 4)));
+ JLabel hintLabel = new JLabel(hint);
+ hintLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ hintLabel.setForeground(LIGHT_TEXT);
+ hintLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ panel.add(hintLabel);
+ }
+
+ return panel;
+ }
+
+ private JPanel createMethodOption(String type, String title, String description, String iconPath) {
+ JPanel option = new JPanel(new BorderLayout(15, 0));
+ option.setBackground(WHITE);
+ option.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(BORDER_COLOR, 2),
+ BorderFactory.createEmptyBorder(18, 20, 18, 20)));
+ option.putClientProperty("paddingInsets", new int[]{18, 20, 18, 20});
+ option.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ option.setAlignmentX(Component.LEFT_ALIGNMENT);
+ option.setMaximumSize(new Dimension(520, 120));
+
+ JLabel iconLabel = new JLabel();
+ iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
+ iconLabel.setPreferredSize(new Dimension(80, 80));
+ if (iconPath != null && !iconPath.trim().isEmpty()) {
+ ImageIcon rawIcon = new ImageIcon(iconPath);
+ if (rawIcon.getIconWidth() > 0 && rawIcon.getIconHeight() > 0) {
+ Image scaled = rawIcon.getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH);
+ iconLabel.setIcon(new ImageIcon(scaled));
+ }
+ }
+ if (iconLabel.getIcon() == null) {
+ iconLabel.setText("mower".equals(type) ? "馃殰" : "馃摫");
+ iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 44));
+ }
+ option.add(iconLabel, BorderLayout.WEST);
+
+ JPanel textPanel = new JPanel();
+ textPanel.setOpaque(false);
+ textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS));
+
+ JLabel titleLabel = new JLabel(title);
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 16));
+ titleLabel.setForeground(TEXT_COLOR);
+ titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ option.putClientProperty("titleLabel", titleLabel);
+ textPanel.add(titleLabel);
+
+ if (description != null && !description.trim().isEmpty()) {
+ textPanel.add(Box.createRigidArea(new Dimension(0, 8)));
+ JLabel descLabel = new JLabel(description);
+ descLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ descLabel.setForeground(LIGHT_TEXT);
+ descLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ textPanel.add(descLabel);
+ }
+
+ option.add(textPanel, BorderLayout.CENTER);
+ option.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ selectMethodOption(option, type, true);
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (option != selectedMethodPanel) {
+ option.setBackground(new Color(245, 245, 245));
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (option != selectedMethodPanel) {
+ option.setBackground(WHITE);
+ }
+ }
+ });
+
+ methodOptionPanels.put(type, option);
+ return option;
+ }
+
+ private JPanel createShapeOption(String type, String title, String description) {
+ JPanel option = new JPanel();
+ option.setLayout(new BoxLayout(option, BoxLayout.Y_AXIS));
+ option.setBackground(WHITE);
+ option.setAlignmentX(Component.LEFT_ALIGNMENT);
+ option.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(BORDER_COLOR, 2),
+ BorderFactory.createEmptyBorder(12, 18, 12, 18)));
+ option.putClientProperty("paddingInsets", new int[]{12, 18, 12, 18});
+ option.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+
+ JLabel titleLabel = new JLabel(title);
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 15));
+ titleLabel.setForeground(TEXT_COLOR);
+ titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ option.putClientProperty("titleLabel", titleLabel);
+ option.add(titleLabel);
+
+ if (description != null && !description.trim().isEmpty()) {
+ option.add(Box.createRigidArea(new Dimension(0, 4)));
+ JLabel descLabel = new JLabel(description);
+ descLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
+ descLabel.setForeground(LIGHT_TEXT);
+ descLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ option.add(descLabel);
+ }
+
+ option.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ selectShapeOption(option, type, true);
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (option != selectedShapePanel) {
+ option.setBackground(new Color(245, 245, 245));
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (option != selectedShapePanel) {
+ option.setBackground(WHITE);
+ }
+ }
+ });
+
+ shapeOptionPanels.put(type, option);
+ return option;
+ }
+
+ private void selectMethodOption(JPanel option, String type, boolean userTriggered) {
+ if (option == null) {
+ return;
+ }
+ if (selectedMethodPanel != null && selectedMethodPanel != option) {
+ resetOptionAppearance(selectedMethodPanel);
+ }
+ selectedMethodPanel = option;
+ highlightOption(option);
+ formData.put("drawingMethod", type);
+ if (userTriggered) {
+ updateSaveButtonState();
+ }
+ }
+
+ private void selectShapeOption(JPanel option, String type, boolean userTriggered) {
+ if (option == null) {
+ return;
+ }
+ if (selectedShapePanel != null && selectedShapePanel != option) {
+ resetOptionAppearance(selectedShapePanel);
+ }
+ selectedShapePanel = option;
+ highlightOption(option);
+ formData.put("obstacleShape", type);
+ if (userTriggered) {
+ updateSaveButtonState();
+ }
+ }
+
+ private void highlightOption(JPanel option) {
+ option.setBackground(PRIMARY_LIGHT);
+ option.setBorder(buildOptionBorder(PRIMARY_COLOR, 3, option));
+ Object titleObj = option.getClientProperty("titleLabel");
+ if (titleObj instanceof JLabel) {
+ ((JLabel) titleObj).setForeground(PRIMARY_COLOR);
+ }
+ }
+
+ private void resetOptionAppearance(JPanel option) {
+ if (option == null) {
+ return;
+ }
+ option.setBackground(WHITE);
+ option.setBorder(buildOptionBorder(BORDER_COLOR, 2, option));
+ Object titleObj = option.getClientProperty("titleLabel");
+ if (titleObj instanceof JLabel) {
+ ((JLabel) titleObj).setForeground(TEXT_COLOR);
+ }
+ }
+
+ private Border buildOptionBorder(Color color, int thickness, JPanel option) {
+ Object paddingObj = option != null ? option.getClientProperty("paddingInsets") : null;
+ if (paddingObj instanceof int[]) {
+ int[] padding = (int[]) paddingObj;
+ if (padding.length == 4) {
+ Border line = BorderFactory.createLineBorder(color, thickness);
+ Border paddingBorder = BorderFactory.createEmptyBorder(padding[0], padding[1], padding[2], padding[3]);
+ return BorderFactory.createCompoundBorder(line, paddingBorder);
+ }
+ }
+ return BorderFactory.createLineBorder(color, thickness);
+ }
+
+ private void setupEventHandlers() {
+ prevButton.addActionListener(e -> {
+ if (currentStep > 1) {
+ showStep(currentStep - 1);
+ }
+ });
+
+ nextButton.addActionListener(e -> {
+ if (currentStep == 1) {
+ Coordinate.coordinates.clear();
+ showStep(2);
+ }
+ });
+
+ saveButton.addActionListener(e -> saveObstacleAndClose());
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ dispose();
+ }
+ });
+ }
+
+ private void startDrawingWorkflow() {
+ if (drawingInProgress) {
+ return;
+ }
+
+ activeDrawingShape = null;
+
+ String method = formData.get("drawingMethod");
+ if (!isMeaningfulValue(method)) {
+ JOptionPane.showMessageDialog(this, "璇烽�夋嫨缁樺埗鏂瑰紡", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ String shape = formData.get("obstacleShape");
+ if (!isMeaningfulValue(shape)) {
+ JOptionPane.showMessageDialog(this, "璇烽�夋嫨闅滅鐗╁舰鐘�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ String baseStation = resolveBaseStationCoordinates();
+ if (!isMeaningfulValue(baseStation)) {
+ JOptionPane.showMessageDialog(this, "鏈幏鍙栧埌鏈夋晥鐨勫熀鍑嗙珯鍧愭爣锛岃鍏堝湪鍩哄噯绔欑鐞嗕腑瀹屾垚璁剧疆", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ activeDrawingShape = shape.toLowerCase(Locale.ROOT);
+
+ Coordinate.coordinates.clear();
+ Coordinate.setStartSaveGngga(true);
+ drawingInProgress = true;
+ drawButton.setText("姝e湪缁樺埗...");
+ drawButton.setEnabled(false);
+ drawingStatusLabel.setText("姝e湪閲囬泦闅滅鐗╁潗鏍囷紝璇峰湪涓荤晫闈㈠畬鎴愮粯鍒躲��");
+
+ if (activeSession == null) {
+ activeSession = new ObstacleDrawingSession();
+ }
+ activeSession.target = targetDikuai;
+ activeSession.data.clear();
+ activeSession.data.putAll(formData);
+ activeSession.baseStation = baseStation;
+ activeSession.drawingCompleted = false;
+ activeSession.captureSuccessful = false;
+ activeSession.captureMessage = null;
+
+ resumeRequested = false;
+ closeForDrawingSession();
+ }
+
+ private void closeForDrawingSession() {
+ Shouye shouye = Shouye.getInstance();
+ if (shouye != null) {
+ shouye.showEndDrawingButton(addzhangaiwu::finishDrawingSession, activeDrawingShape);
+ }
+ activeDrawingShape = null;
+ setVisible(false);
+ dispose();
+ }
+
+ public static void finishDrawingSession() {
+ Coordinate.setStartSaveGngga(false);
+
+ Shouye shouye = Shouye.getInstance();
+ if (shouye != null) {
+ shouye.hideEndDrawingButton();
+ }
+
+ if (activeSession == null) {
+ return;
+ }
+
+ processCapturedCoordinates(activeSession);
+ activeSession.drawingCompleted = true;
+ resumeRequested = true;
+
+ Component parent = shouye != null ? shouye : null;
+ showDialog(parent, activeSession.target);
+ }
+
+ /**
+ * Stops an in-progress circle capture and reopens the wizard on step 2.
+ */
+ public static void abortCircleDrawingAndReturn(String message) {
+ Coordinate.setStartSaveGngga(false);
+ Coordinate.coordinates.clear();
+
+ if (activeSession == null || activeSession.target == null) {
+ return;
+ }
+
+ activeSession.captureSuccessful = false;
+ activeSession.drawingCompleted = false;
+ activeSession.captureMessage = (message != null && !message.trim().isEmpty())
+ ? message.trim()
+ : null;
+
+ resumeRequested = true;
+
+ Component parent = Shouye.getInstance();
+ Dikuai target = activeSession.target;
+ SwingUtilities.invokeLater(() -> showDialog(parent, target));
+ }
+
+ private static void processCapturedCoordinates(ObstacleDrawingSession session) {
+ List<Coordinate> captured = new ArrayList<>(Coordinate.coordinates);
+ Coordinate.coordinates.clear();
+
+ if (captured.isEmpty()) {
+ session.captureSuccessful = false;
+ session.captureMessage = "鏈噰闆嗗埌浠讳綍闅滅鐗╁潗鏍囷紝璇烽噸鏂扮粯鍒�";
+ return;
+ }
+
+ if (!isMeaningfulValue(session.baseStation)) {
+ session.captureSuccessful = false;
+ session.captureMessage = "鍩哄噯绔欏潗鏍囨棤鏁堬紝鏃犳硶杞崲闅滅鐗╁潗鏍�";
+ return;
+ }
+
+ List<double[]> xyPoints = convertToLocalXY(captured, session.baseStation);
+ if (xyPoints.isEmpty()) {
+ session.captureSuccessful = false;
+ session.captureMessage = "鍧愭爣杞崲澶辫触锛岃妫�鏌ュ熀鍑嗙珯璁剧疆";
+ return;
+ }
+
+ String shape = session.data.get("obstacleShape");
+ if ("circle".equals(shape)) {
+ if (xyPoints.size() < 2) {
+ session.captureSuccessful = false;
+ session.captureMessage = "鍦嗗舰闅滅鐗╄嚦灏戦渶瑕佷袱涓噰闆嗙偣锛堝渾蹇冨拰鍦嗗懆鐐癸級";
+ return;
+ }
+ double[] center = xyPoints.get(0);
+ double[] radiusPoint = xyPoints.get(xyPoints.size() - 1);
+ String result = String.format(Locale.US, "%.2f,%.2f;%.2f,%.2f",
+ center[0], center[1], radiusPoint[0], radiusPoint[1]);
+ session.data.put("obstacleCoordinates", result);
+ session.captureSuccessful = true;
+ session.captureMessage = "宸查噰闆嗗渾褰㈤殰纰嶇墿鍧愭爣";
+ } else {
+ if (xyPoints.size() < 3) {
+ session.captureSuccessful = false;
+ session.captureMessage = "澶氳竟褰㈤殰纰嶇墿鑷冲皯闇�瑕佷笁涓噰闆嗙偣";
+ return;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (double[] pt : xyPoints) {
+ if (sb.length() > 0) {
+ sb.append(";");
+ }
+ sb.append(String.format(Locale.US, "%.2f,%.2f", pt[0], pt[1]));
+ }
+ session.data.put("obstacleCoordinates", sb.toString());
+ session.captureSuccessful = true;
+ session.captureMessage = "宸查噰闆嗗杈瑰舰闅滅鐗╋紝鍏� " + xyPoints.size() + " 涓偣";
+ }
+ }
+
+ private static List<double[]> convertToLocalXY(List<Coordinate> coords, String baseStation) {
+ List<double[]> result = new ArrayList<>();
+ if (!isMeaningfulValue(baseStation)) {
+ return result;
+ }
+ String[] parts = baseStation.split(",");
+ if (parts.length < 4) {
+ return result;
+ }
+ double baseLat = parseDMToDecimal(parts[0], parts[1]);
+ double baseLon = parseDMToDecimal(parts[2], parts[3]);
+ if (!Double.isFinite(baseLat) || !Double.isFinite(baseLon)) {
+ return result;
+ }
+
+ for (Coordinate coord : coords) {
+ double lat = parseDMToDecimal(coord.getLatitude(), coord.getLatDirection());
+ double lon = parseDMToDecimal(coord.getLongitude(), coord.getLonDirection());
+ if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
+ continue;
+ }
+ result.add(convertLatLonToLocal(lat, lon, baseLat, baseLon));
+ }
+ return result;
+ }
+
+ private static double parseDMToDecimal(String dmm, String direction) {
+ if (dmm == null || dmm.trim().isEmpty()) {
+ return Double.NaN;
+ }
+ try {
+ String trimmed = dmm.trim();
+ int dotIndex = trimmed.indexOf('.');
+ if (dotIndex < 2) {
+ return Double.NaN;
+ }
+ int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
+ double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
+ double decimal = degrees + minutes / 60.0;
+ if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
+ decimal = -decimal;
+ }
+ return decimal;
+ } catch (NumberFormatException ex) {
+ return Double.NaN;
+ }
+ }
+
+ private static double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
+ double deltaLat = lat - baseLat;
+ double deltaLon = lon - baseLon;
+ double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
+ double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
+ double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
+ return new double[]{eastMeters, northMeters};
+ }
+
+ private List<ExistingObstacle> resolveExistingObstacles(Dikuai target, List<String> providedNames) {
+ String landNumber = target != null ? target.getLandNumber() : null;
+ Map<String, ExistingObstacle> configMap = loadObstacleDetailsFromConfig(landNumber);
+ List<ExistingObstacle> result = new ArrayList<>();
+ List<String> sanitized = sanitizeObstacleNames(providedNames);
+ if (!sanitized.isEmpty()) {
+ for (String name : sanitized) {
+ ExistingObstacle info = configMap.remove(name);
+ if (info != null) {
+ result.add(info);
+ } else {
+ result.add(ExistingObstacle.placeholder(name));
+ }
+ }
+ }
+ if (!configMap.isEmpty()) {
+ List<ExistingObstacle> remaining = new ArrayList<>(configMap.values());
+ remaining.sort(Comparator.comparing(ExistingObstacle::getName, String.CASE_INSENSITIVE_ORDER));
+ result.addAll(remaining);
+ }
+ if (result.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
+ private List<String> sanitizeObstacleNames(List<String> names) {
+ List<String> sanitized = new ArrayList<>();
+ if (names == null) {
+ return sanitized;
+ }
+ for (String name : names) {
+ if (isMeaningfulValue(name)) {
+ sanitized.add(name.trim());
+ }
+ }
+ return sanitized;
+ }
+
+ private Map<String, ExistingObstacle> loadObstacleDetailsFromConfig(String landNumber) {
+ Map<String, ExistingObstacle> details = new HashMap<>();
+ if (!isMeaningfulValue(landNumber)) {
+ return details;
+ }
+ try {
+ File configFile = new File("Obstacledge.properties");
+ if (!configFile.exists()) {
+ return details;
+ }
+ Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+ if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+ return details;
+ }
+ Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
+ if (plot == null) {
+ return details;
+ }
+ for (Obstacledge.Obstacle obstacle : plot.getObstacles()) {
+ if (obstacle == null) {
+ continue;
+ }
+ String name = obstacle.getObstacleName();
+ if (!isMeaningfulValue(name)) {
+ continue;
+ }
+ String trimmedName = name.trim();
+ Obstacledge.ObstacleShape shape = obstacle.getShape();
+ String xyCoords = obstacle.getXyCoordsString();
+ String original = obstacle.getOriginalCoordsString();
+ String coords = isMeaningfulValue(xyCoords) ? xyCoords.trim()
+ : (isMeaningfulValue(original) ? original.trim() : "");
+ details.put(trimmedName, new ExistingObstacle(trimmedName, shape, coords));
+ }
+ } catch (Exception ex) {
+ System.err.println("鍔犺浇闅滅鐗╄鎯呭け璐�: " + ex.getMessage());
+ }
+ return details;
+ }
+
+ private void preloadData() {
+ String existing = targetDikuai.getObstacleCoordinates();
+ if (isMeaningfulValue(existing)) {
+ formData.put("obstacleCoordinates", existing.trim());
+ }
+ updateDrawingStatus();
+ }
+
+ private void updateDrawingStatus() {
+ if (drawingStatusLabel == null) {
+ return;
+ }
+ String coords = formData.get("obstacleCoordinates");
+ if (isMeaningfulValue(coords)) {
+ int count = countCoordinatePairs(coords);
+ drawingStatusLabel.setText("宸查噰闆嗛殰纰嶇墿鏁版嵁锛岀偣鏁帮細" + count);
+ if (!drawingInProgress && drawButton != null) {
+ drawButton.setText("閲嶆柊缁樺埗");
+ drawButton.setEnabled(true);
+ }
+ } else {
+ drawingStatusLabel.setText("灏氭湭閲囬泦闅滅鐗╁潗鏍�");
+ if (!drawingInProgress && drawButton != null) {
+ drawButton.setText("寮�濮嬬粯鍒�");
+ drawButton.setEnabled(true);
+ }
+ }
+ updateSaveButtonState();
+ }
+
+ private int countCoordinatePairs(String coords) {
+ if (!isMeaningfulValue(coords)) {
+ return 0;
+ }
+ String[] parts = coords.split(";+");
+ int count = 0;
+ for (String part : parts) {
+ if (part == null) {
+ continue;
+ }
+ String trimmed = part.trim();
+ if (trimmed.isEmpty()) {
+ continue;
+ }
+ if (trimmed.contains(",")) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private void updateSaveButtonState() {
+ if (saveButton != null) {
+ saveButton.setEnabled(isMeaningfulValue(formData.get("obstacleCoordinates")));
+ }
+ }
+
+ private boolean validateStep2() {
+ if (!isMeaningfulValue(formData.get("obstacleCoordinates"))) {
+ JOptionPane.showMessageDialog(this, "璇峰厛瀹屾垚闅滅鐗╃粯鍒�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ private void saveObstacleAndClose() {
+ if (!validateStep2()) {
+ return;
+ }
+ String coords = formData.get("obstacleCoordinates").trim();
+ String landNumber = targetDikuai.getLandNumber();
+ if (!Dikuai.updateField(landNumber, "obstacleCoordinates", coords)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶鏇存柊闅滅鐗╁潗鏍�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ Dikuai.updateField(landNumber, "updateTime", currentTime());
+ Dikuai.saveToProperties();
+ Dikuaiguanli.notifyExternalCreation(landNumber);
+
+ JOptionPane.showMessageDialog(this, "闅滅鐗╁潗鏍囧凡淇濆瓨", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ activeSession = null;
+ dispose();
+ }
+
+ private static final class ExistingObstacle {
+ private final String name;
+ private final Obstacledge.ObstacleShape shape;
+ private final String coordinates;
+
+ ExistingObstacle(String name, Obstacledge.ObstacleShape shape, String coordinates) {
+ this.name = name != null ? name : "";
+ this.shape = shape;
+ this.coordinates = coordinates != null ? coordinates : "";
+ }
+
+ static ExistingObstacle placeholder(String name) {
+ return new ExistingObstacle(name, null, "");
+ }
+
+ String getName() {
+ return name;
+ }
+
+ String getShapeDisplay() {
+ if (shape == null) {
+ return "鏈煡褰㈢姸";
+ }
+ return shape.getDescription();
+ }
+
+ String getShapeKey() {
+ if (shape == Obstacledge.ObstacleShape.CIRCLE) {
+ return "circle";
+ }
+ if (shape == Obstacledge.ObstacleShape.POLYGON) {
+ return "polygon";
+ }
+ return null;
+ }
+
+ String getCoordinates() {
+ return coordinates;
+ }
+ }
+
+ private void showStep(int step) {
+ currentStep = step;
+ cardLayout.show(stepsPanel, "step" + step);
+ prevButton.setVisible(step > 1);
+ if (step < 2) {
+ nextButton.setVisible(true);
+ saveButton.setVisible(false);
+ } else {
+ nextButton.setVisible(false);
+ saveButton.setVisible(true);
+ updateDrawingStatus();
+ }
+ updateSaveButtonState();
+ revalidate();
+ repaint();
+ }
+
+ private void applySessionData(ObstacleDrawingSession session) {
+ drawingInProgress = false;
+ if (drawButton != null) {
+ drawButton.setEnabled(true);
+ }
+ formData.clear();
+ formData.putAll(session.data);
+
+ String method = session.data.get("drawingMethod");
+ if (method != null) {
+ JPanel panel = methodOptionPanels.get(method);
+ if (panel != null) {
+ selectMethodOption(panel, method, false);
+ }
+ }
+
+ String shape = session.data.get("obstacleShape");
+ if (shape != null) {
+ JPanel panel = shapeOptionPanels.get(shape);
+ if (panel != null) {
+ selectShapeOption(panel, shape, false);
+ }
+ }
+
+ if (session.captureMessage != null) {
+ JOptionPane.showMessageDialog(this,
+ session.captureMessage,
+ session.captureSuccessful ? "鎴愬姛" : "鎻愮ず",
+ session.captureSuccessful ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.WARNING_MESSAGE);
+ }
+
+ updateDrawingStatus();
+ currentStep = 2;
+ showStep(2);
+ }
+
+ private JPanel createInfoRow(String label, String value) {
+ return createInfoRow(label, value, null);
+ }
+
+ private JPanel createInfoRow(String label, String value, String tooltip) {
+ JPanel row = new JPanel();
+ row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
+ row.setOpaque(false);
+ row.setAlignmentX(Component.LEFT_ALIGNMENT);
+ row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
+
+ JLabel labelComp = new JLabel(label);
+ labelComp.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+ labelComp.setForeground(TEXT_COLOR);
+ labelComp.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ JLabel valueComp = new JLabel(value != null ? value : "");
+ valueComp.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 14));
+ valueComp.setForeground(TEXT_COLOR);
+ valueComp.setVerticalAlignment(SwingConstants.TOP);
+ valueComp.setAlignmentX(Component.LEFT_ALIGNMENT);
+ if (tooltip != null && !tooltip.trim().isEmpty()) {
+ valueComp.setToolTipText(tooltip);
+ }
+
+ row.add(labelComp);
+ row.add(Box.createRigidArea(new Dimension(6, 0)));
+ row.add(valueComp);
+ return row;
+ }
+
+ private JButton createPrimaryButton(String text, int fontSize) {
+ JButton button = new JButton(text);
+ button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, fontSize));
+ button.setBackground(PRIMARY_COLOR);
+ button.setForeground(WHITE);
+ button.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(PRIMARY_DARK, 2),
+ BorderFactory.createEmptyBorder(10, 22, 10, 22)));
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ button.setFocusPainted(false);
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (button.isEnabled()) {
+ button.setBackground(PRIMARY_DARK);
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (button.isEnabled()) {
+ button.setBackground(PRIMARY_COLOR);
+ }
+ }
+ });
+ return button;
+ }
+
+ private JButton createSecondaryButton(String text) {
+ JButton button = new JButton(text);
+ button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 16));
+ button.setBackground(MEDIUM_GRAY);
+ button.setForeground(TEXT_COLOR);
+ button.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(BORDER_COLOR, 2),
+ BorderFactory.createEmptyBorder(10, 22, 10, 22)));
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ button.setFocusPainted(false);
+ return button;
+ }
+
+ private static boolean isMeaningfulValue(String value) {
+ if (value == null) {
+ return false;
+ }
+ String trimmed = value.trim();
+ return !trimmed.isEmpty() && !"-1".equals(trimmed);
+ }
+
+ private String resolveBaseStationCoordinates() {
+ String coords = targetDikuai.getBaseStationCoordinates();
+ if (isMeaningfulValue(coords)) {
+ return coords.trim();
+ }
+
+ Device device = new Device();
+ device.initFromProperties();
+ coords = device.getBaseStationCoordinates();
+ if (isMeaningfulValue(coords)) {
+ return coords.trim();
+ }
+
+ BaseStation baseStation = new BaseStation();
+ baseStation.load();
+ coords = baseStation.getInstallationCoordinates();
+ if (isMeaningfulValue(coords)) {
+ return coords.trim();
+ }
+ return null;
+ }
+
+ private String safeValue(String value, String fallback) {
+ if (!isMeaningfulValue(value)) {
+ return fallback;
+ }
+ return value.trim();
+ }
+
+ private String currentTime() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ return sdf.format(new Date());
+ }
+
+ /**
+ * 鏄剧ず鏂板闅滅鐗╁璇濇銆�
+ */
+ public static void showDialog(Component parent, Dikuai target) {
+ showDialog(parent, target, null);
+ }
+
+ public static void showDialog(Component parent, Dikuai target, List<String> obstacleNames) {
+ Window owner = null;
+ if (parent instanceof Window) {
+ owner = (Window) parent;
+ } else if (parent != null) {
+ owner = SwingUtilities.getWindowAncestor(parent);
+ }
+
+ addzhangaiwu dialog = new addzhangaiwu(owner, target, obstacleNames);
+ if (resumeRequested && activeSession != null && activeSession.target == target) {
+ dialog.applySessionData(activeSession);
+ resumeRequested = false;
+ }
+ dialog.setVisible(true);
+ }
+}
diff --git a/src/gecaoji/Device.java b/src/gecaoji/Device.java
index 0a54671..91d9bcc 100644
--- a/src/gecaoji/Device.java
+++ b/src/gecaoji/Device.java
@@ -297,10 +297,7 @@
return;
}
}
- if (hasMeaningfulValue(mowerNumber) && hasMeaningfulValue(incomingDeviceId) && !mowerNumber.equals(incomingDeviceId)) {
- return;
- }
- if (hasMeaningfulValue(incomingDeviceId)) {
+ if (hasMeaningfulValue(incomingDeviceId) && !incomingDeviceId.equals(mowerNumber)) {
mowerNumber = incomingDeviceId;
}
diff --git a/src/lujing/Lunjingguihua.java b/src/lujing/Lunjingguihua.java
new file mode 100644
index 0000000..45ecc9c
--- /dev/null
+++ b/src/lujing/Lunjingguihua.java
@@ -0,0 +1,568 @@
+package lujing;
+
+import java.awt.geom.Line2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.operation.union.CascadedPolygonUnion;
+
+/**
+ * 鍓茶崏璺緞瑙勫垝瀹炵敤绫伙紝渚涘叾浠栭」鐩洿鎺ヨ皟鐢ㄣ��
+ * 鎻愪緵瀛楃涓插叆鍙傜殑鍓茶崏璺緞鐢熸垚鑳藉姏锛屽苟灏佽蹇呰鐨勮В鏋愪笌鍑犱綍澶勭悊閫昏緫銆�
+ */
+public final class Lunjingguihua {
+
+ private Lunjingguihua() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ /**
+ * 鐢熸垚鍓茶崏璺緞娈靛垪琛ㄣ��
+ *
+ * @param polygonCoords 澶氳竟褰㈣竟鐣屽潗鏍囷紝鏍煎紡濡� "x1,y1;x2,y2;..."锛堢背锛�
+ * @param obstaclesCoords 闅滅鐗╁潗鏍囷紝鏀寔澶氫釜鎷彿娈垫垨鍦嗗舰瀹氫箟锛屼緥 "(x1,y1;x2,y2)(cx,cy;px,py)"
+ * @param mowingWidth 鍓茶崏瀹藉害瀛楃涓诧紝绫冲崟浣嶏紝鍏佽淇濈暀涓や綅灏忔暟
+ * @param modeStr 鍓茶崏妯″紡锛�"0"/绌轰负骞宠绾匡紝"1" 鎴� "spiral" 琛ㄧず铻烘棆妯″紡锛堝綋鍓嶄粎骞宠绾垮疄鐜帮級
+ * @return 璺緞娈靛垪琛紝鎸夎椹堕『搴忔帓鍒�
+ */
+ public static List<PathSegment> generatePathSegments(String polygonCoords,
+ String obstaclesCoords,
+ String mowingWidth,
+ String modeStr) {
+ List<Coordinate> polygon = parseCoordinates(polygonCoords);
+ if (polygon.size() < 4) {
+ throw new IllegalArgumentException("澶氳竟褰㈠潗鏍囨暟閲忎笉瓒筹紝鑷冲皯闇�瑕佷笁涓偣");
+ }
+
+ double width = parseDoubleOrDefault(mowingWidth, 2.0);
+ if (width <= 0) {
+ throw new IllegalArgumentException("鍓茶崏瀹藉害蹇呴』澶т簬 0");
+ }
+
+ List<List<Coordinate>> obstacles = parseObstacles(obstaclesCoords);
+ String mode = normalizeMode(modeStr);
+
+ PlannerCore planner = new PlannerCore(polygon, width, mode, obstacles);
+ return planner.generate();
+ }
+
+ /**
+ * 閫氳繃瀛楃涓插弬鏁扮敓鎴愬壊鑽夎矾寰勫潗鏍囥��
+ *
+ * @param polygonCoords 澶氳竟褰㈣竟鐣屽潗鏍囷紝鏍煎紡濡� "x1,y1;x2,y2;..."锛堢背锛�
+ * @param obstaclesCoords 闅滅鐗╁潗鏍囷紝鏀寔澶氫釜鎷彿娈垫垨鍦嗗舰瀹氫箟锛屼緥 "(x1,y1;x2,y2)(cx,cy;px,py)"
+ * @param mowingWidth 鍓茶崏瀹藉害瀛楃涓诧紝绫冲崟浣嶏紝鍏佽淇濈暀涓や綅灏忔暟
+ * @param modeStr 鍓茶崏妯″紡锛�"0"/绌轰负骞宠绾匡紝"1" 鎴� "spiral" 琛ㄧず铻烘棆妯″紡锛堝綋鍓嶄粎骞宠绾垮疄鐜帮級
+ * @return 杩炵画璺緞鍧愭爣瀛楃涓诧紝椤哄簭绱ц窡鍓茶崏鏈鸿杩涜矾绾�
+ */
+ public static String generatePathFromStrings(String polygonCoords,
+ String obstaclesCoords,
+ String mowingWidth,
+ String modeStr) {
+ List<PathSegment> segments = generatePathSegments(polygonCoords, obstaclesCoords, mowingWidth, modeStr);
+ return formatPathSegments(segments);
+ }
+
+ /**
+ * 灏嗚矾寰勬鍒楄〃杞崲涓哄潗鏍囧瓧绗︿覆銆�
+ */
+ public static String formatPathSegments(List<PathSegment> path) {
+ if (path == null || path.isEmpty()) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ Coordinate last = null;
+ for (PathSegment segment : path) {
+ if (!equals2D(last, segment.start)) {
+ appendPoint(sb, segment.start);
+ last = new Coordinate(segment.start);
+ }
+ if (!equals2D(last, segment.end)) {
+ appendPoint(sb, segment.end);
+ last = new Coordinate(segment.end);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 瑙f瀽鍧愭爣瀛楃涓层��
+ */
+ public static List<Coordinate> parseCoordinates(String s) {
+ List<Coordinate> list = new ArrayList<>();
+ if (s == null || s.trim().isEmpty()) {
+ return list;
+ }
+ String[] pts = s.split("[;\\s]+");
+ for (String p : pts) {
+ String trimmed = p.trim().replace("(", "").replace(")", "");
+ if (trimmed.isEmpty()) {
+ continue;
+ }
+ String[] xy = trimmed.split("[,锛孿\s]+");
+ if (xy.length >= 2) {
+ try {
+ list.add(new Coordinate(Double.parseDouble(xy[0].trim()),
+ Double.parseDouble(xy[1].trim())));
+ } catch (NumberFormatException ex) {
+ System.err.println("鍧愭爣瑙f瀽澶辫触: " + trimmed);
+ }
+ }
+ }
+ if (list.size() > 2 && !list.get(0).equals2D(list.get(list.size() - 1))) {
+ list.add(new Coordinate(list.get(0)));
+ }
+ return list;
+ }
+
+ /**
+ * 瑙f瀽闅滅鐗╁瓧绗︿覆锛屽吋瀹瑰闅滅鐗╀笌鍦嗗舰瀹氫箟銆�
+ */
+ public static List<List<Coordinate>> parseObstacles(String str) {
+ List<List<Coordinate>> obs = new ArrayList<>();
+ if (str == null || str.trim().isEmpty()) {
+ return obs;
+ }
+ java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\(([^)]+)\\)");
+ java.util.regex.Matcher matcher = pattern.matcher(str);
+ while (matcher.find()) {
+ List<Coordinate> coords = parseCoordinates(matcher.group(1));
+ if (coords.size() >= 3) {
+ obs.add(coords);
+ } else if (coords.size() == 2) {
+ List<Coordinate> circle = approximateCircle(coords.get(0), coords.get(1));
+ if (!circle.isEmpty()) {
+ obs.add(circle);
+ }
+ }
+ }
+ if (obs.isEmpty()) {
+ List<Coordinate> coords = parseCoordinates(str);
+ if (coords.size() >= 3) {
+ obs.add(coords);
+ } else if (coords.size() == 2) {
+ List<Coordinate> circle = approximateCircle(coords.get(0), coords.get(1));
+ if (!circle.isEmpty()) {
+ obs.add(circle);
+ }
+ }
+ }
+ return obs;
+ }
+
+ private static double parseDoubleOrDefault(String value, double defaultValue) {
+ if (value == null || value.trim().isEmpty()) {
+ return defaultValue;
+ }
+ try {
+ return Double.parseDouble(value.trim());
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException("鍓茶崏瀹藉害鏍煎紡涓嶆纭�: " + value, ex);
+ }
+ }
+
+ private static String normalizeMode(String modeStr) {
+ if (modeStr == null) {
+ return "parallel";
+ }
+ String trimmed = modeStr.trim().toLowerCase();
+ if ("1".equals(trimmed) || "spiral".equals(trimmed)) {
+ return "spiral";
+ }
+ return "parallel";
+ }
+
+ private static boolean equals2D(Coordinate a, Coordinate b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ return a.equals2D(b);
+ }
+
+ private static void appendPoint(StringBuilder sb, Coordinate point) {
+ if (sb.length() > 0) {
+ sb.append(";");
+ }
+ sb.append(String.format("%.2f,%.2f", point.x, point.y));
+ }
+
+ private static List<Coordinate> approximateCircle(Coordinate center, Coordinate edge) {
+ double radius = center.distance(edge);
+ if (radius <= 0) {
+ return Collections.emptyList();
+ }
+ int segments = 36;
+ List<Coordinate> circle = new ArrayList<>(segments + 1);
+ for (int i = 0; i < segments; i++) {
+ double angle = 2 * Math.PI * i / segments;
+ double x = center.x + radius * Math.cos(angle);
+ double y = center.y + radius * Math.sin(angle);
+ circle.add(new Coordinate(x, y));
+ }
+ circle.add(new Coordinate(circle.get(0)));
+ return circle;
+ }
+
+ /**
+ * 璺緞娈垫暟鎹粨鏋勩��
+ */
+ public static final class PathSegment {
+ public Coordinate start;
+ public Coordinate end;
+ public boolean isMowing;
+ public boolean isStartPoint;
+ public boolean isEndPoint;
+
+ PathSegment(Coordinate start, Coordinate end, boolean isMowing) {
+ this.start = start;
+ this.end = end;
+ this.isMowing = isMowing;
+ }
+
+ public void setAsStartPoint() {
+ this.isStartPoint = true;
+ }
+
+ public void setAsEndPoint() {
+ this.isEndPoint = true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PathSegment[(%.2f,%.2f)->(%.2f,%.2f) mowing=%b start=%b end=%b]",
+ start.x, start.y, end.x, end.y, isMowing, isStartPoint, isEndPoint);
+ }
+ }
+
+ /**
+ * 鍐呴儴鏍稿績瑙勫垝鍣紝瀹炵幇涓� MowingPathPlanner 绛夋晥鐨勯�昏緫銆�
+ */
+ private static final class PlannerCore {
+ private final List<Coordinate> polygon;
+ private final double width;
+ private final String mode;
+ private final List<List<Coordinate>> obstacles;
+ private final GeometryFactory gf = new GeometryFactory();
+
+ PlannerCore(List<Coordinate> polygon, double width, String mode, List<List<Coordinate>> obstacles) {
+ this.polygon = polygon;
+ this.width = width;
+ this.mode = mode;
+ this.obstacles = obstacles != null ? obstacles : new ArrayList<>();
+ }
+
+ List<PathSegment> generate() {
+ if (!"spiral".equals(mode)) {
+ return generateParallelPath();
+ }
+ return generateParallelPath();
+ }
+
+ private List<PathSegment> generateParallelPath() {
+ List<PathSegment> path = new ArrayList<>();
+ Geometry safeArea = buildSafeArea();
+ if (safeArea == null || safeArea.isEmpty()) {
+ System.err.println("瀹夊叏鍖哄煙涓虹┖锛屾棤娉曠敓鎴愯矾寰�");
+ return path;
+ }
+
+ LineSegment longest = findLongestEdge(polygon);
+ Vector2D baseDir = new Vector2D(longest.end.x - longest.start.x,
+ longest.end.y - longest.start.y).normalize();
+ Vector2D perp = baseDir.rotate90CCW();
+
+ Envelope env = safeArea.getEnvelopeInternal();
+ double minProj = perp.dot(new Vector2D(env.getMinX(), env.getMinY()));
+ double maxProj = perp.dot(new Vector2D(env.getMaxX(), env.getMaxY()));
+ double first = minProj - width / 2.0;
+
+ Geometry originalPoly = createPolygonFromCoordinates(polygon);
+ Coordinate lastEnd = null;
+ int idx = 0;
+
+ for (double offset = first; offset <= maxProj + width / 2.0; offset += width) {
+ Line2D.Double raw = createInfiniteLine(longest, perp, offset);
+ List<Line2D.Double> segs = clipLineToPolygon(raw, safeArea);
+ if (segs.isEmpty()) {
+ continue;
+ }
+
+ List<Line2D.Double> finalSegs = new ArrayList<>();
+ for (Line2D.Double seg : segs) {
+ finalSegs.addAll(clipLineToPolygon(seg, originalPoly));
+ }
+ if (finalSegs.isEmpty()) {
+ continue;
+ }
+
+ finalSegs.sort((a, b) -> Double.compare(baseDir.dot(midV(a)), baseDir.dot(midV(b))));
+ boolean even = (idx % 2 == 0);
+ for (Line2D.Double seg : finalSegs) {
+ Coordinate entry = even ? new Coordinate(seg.x1, seg.y1)
+ : new Coordinate(seg.x2, seg.y2);
+ Coordinate exit = even ? new Coordinate(seg.x2, seg.y2)
+ : new Coordinate(seg.x1, seg.y1);
+
+ if (lastEnd != null && lastEnd.distance(entry) > 1e-3) {
+ path.add(new PathSegment(lastEnd, entry, false));
+ }
+
+ PathSegment mowingSeg = new PathSegment(entry, exit, true);
+ if (path.isEmpty()) {
+ mowingSeg.setAsStartPoint();
+ }
+ path.add(mowingSeg);
+ lastEnd = exit;
+ }
+ idx++;
+ }
+
+ if (!path.isEmpty()) {
+ path.get(path.size() - 1).setAsEndPoint();
+ }
+
+ postProcess(path);
+ return path;
+ }
+
+ private Geometry buildSafeArea() {
+ try {
+ Coordinate[] coords = polygon.toArray(new Coordinate[0]);
+ if (!coords[0].equals2D(coords[coords.length - 1])) {
+ coords = Arrays.copyOf(coords, coords.length + 1);
+ coords[coords.length - 1] = coords[0];
+ }
+ Polygon origin = gf.createPolygon(gf.createLinearRing(coords));
+ Geometry result = origin;
+
+ if (!obstacles.isEmpty()) {
+ List<Geometry> obsGeom = new ArrayList<>();
+ for (List<Coordinate> obs : obstacles) {
+ Geometry g = createPolygonFromCoordinates(obs);
+ if (g != null && !g.isEmpty()) {
+ obsGeom.add(g);
+ }
+ }
+ if (!obsGeom.isEmpty()) {
+ Geometry union = CascadedPolygonUnion.union(obsGeom);
+ result = origin.difference(union);
+ }
+ }
+
+ Geometry shrunk = shrinkStraight(result, width / 2.0);
+ return shrunk.isEmpty() ? result : shrunk;
+ } catch (Exception ex) {
+ System.err.println("鏋勫缓瀹夊叏鍖哄煙澶辫触: " + ex.getMessage());
+ return gf.createPolygon();
+ }
+ }
+
+ private LineSegment findLongestEdge(List<Coordinate> ring) {
+ double max = -1.0;
+ LineSegment best = null;
+ for (int i = 0; i < ring.size() - 1; i++) {
+ double d = ring.get(i).distance(ring.get(i + 1));
+ if (d > max) {
+ max = d;
+ best = new LineSegment(ring.get(i), ring.get(i + 1), i);
+ }
+ }
+ return best;
+ }
+
+ private Line2D.Double createInfiniteLine(LineSegment base, Vector2D perp, double offset) {
+ Vector2D baseStart = new Vector2D(base.start.x, base.start.y);
+ Vector2D baseDir = new Vector2D(base.end.x - base.start.x,
+ base.end.y - base.start.y).normalize();
+ Vector2D center = baseStart.add(perp.mul(offset));
+ double ext = 1.5 * diagonalLength();
+ Vector2D p1 = center.sub(baseDir.mul(ext));
+ Vector2D p2 = center.add(baseDir.mul(ext));
+ return new Line2D.Double(p1.x, p1.y, p2.x, p2.y);
+ }
+
+ private List<Line2D.Double> clipLineToPolygon(Line2D.Double line, Geometry poly) {
+ List<Line2D.Double> list = new ArrayList<>();
+ LineString ls = gf.createLineString(new Coordinate[]{
+ new Coordinate(line.x1, line.y1),
+ new Coordinate(line.x2, line.y2)
+ });
+ Geometry inter = poly.intersection(ls);
+ if (inter.isEmpty()) {
+ return list;
+ }
+
+ if (inter instanceof LineString) {
+ addCoords((LineString) inter, list);
+ } else if (inter instanceof MultiLineString) {
+ MultiLineString mls = (MultiLineString) inter;
+ for (int i = 0; i < mls.getNumGeometries(); i++) {
+ addCoords((LineString) mls.getGeometryN(i), list);
+ }
+ }
+ return list;
+ }
+
+ private void addCoords(LineString ls, List<Line2D.Double> bucket) {
+ Coordinate[] cs = ls.getCoordinateSequence().toCoordinateArray();
+ for (int i = 0; i < cs.length - 1; i++) {
+ bucket.add(new Line2D.Double(cs[i].x, cs[i].y, cs[i + 1].x, cs[i + 1].y));
+ }
+ }
+
+ private Geometry createPolygonFromCoordinates(List<Coordinate> coords) {
+ if (coords.size() < 3) {
+ return gf.createPolygon();
+ }
+ List<Coordinate> closed = new ArrayList<>(coords);
+ if (!closed.get(0).equals2D(closed.get(closed.size() - 1))) {
+ closed.add(new Coordinate(closed.get(0)));
+ }
+ LinearRing shell = gf.createLinearRing(closed.toArray(new Coordinate[0]));
+ Polygon polygonGeom = gf.createPolygon(shell);
+ return polygonGeom.isValid() ? polygonGeom : (Polygon) polygonGeom.buffer(0);
+ }
+
+ private double diagonalLength() {
+ double minX = polygon.stream().mapToDouble(c -> c.x).min().orElse(0);
+ double maxX = polygon.stream().mapToDouble(c -> c.x).max().orElse(0);
+ double minY = polygon.stream().mapToDouble(c -> c.y).min().orElse(0);
+ double maxY = polygon.stream().mapToDouble(c -> c.y).max().orElse(0);
+ return Math.hypot(maxX - minX, maxY - minY);
+ }
+
+ private Vector2D midV(Line2D.Double l) {
+ return new Vector2D((l.x1 + l.x2) / 2.0, (l.y1 + l.y2) / 2.0);
+ }
+
+ private void postProcess(List<PathSegment> path) {
+ if (path == null || path.isEmpty()) {
+ return;
+ }
+ List<PathSegment> tmp = new ArrayList<>(path);
+ path.clear();
+ Coordinate prevEnd = null;
+ for (PathSegment seg : tmp) {
+ if (prevEnd != null && seg.start.distance(prevEnd) < 1e-3) {
+ seg.start = prevEnd;
+ }
+ if (!seg.isMowing && !path.isEmpty()) {
+ PathSegment last = path.get(path.size() - 1);
+ if (!last.isMowing && isCollinear(last.start, last.end, seg.end)) {
+ last.end = seg.end;
+ prevEnd = seg.end;
+ continue;
+ }
+ }
+ path.add(seg);
+ prevEnd = seg.end;
+ }
+ }
+
+ private boolean isCollinear(Coordinate a, Coordinate b, Coordinate c) {
+ double dx1 = b.x - a.x;
+ double dy1 = b.y - a.y;
+ double dx2 = c.x - b.x;
+ double dy2 = c.y - b.y;
+ double cross = dx1 * dy2 - dy1 * dx2;
+ return Math.abs(cross) < 1e-6;
+ }
+
+ private Geometry shrinkStraight(Geometry outer, double dist) {
+ Geometry buf = outer.buffer(-dist);
+ if (buf.isEmpty()) {
+ return buf;
+ }
+
+ Geometry poly = (buf instanceof Polygon) ? buf
+ : (buf instanceof MultiPolygon) ? ((MultiPolygon) buf).getGeometryN(0) : null;
+ if (!(poly instanceof Polygon)) {
+ return buf;
+ }
+
+ Coordinate[] ring = ((Polygon) poly).getExteriorRing().getCoordinateSequence().toCoordinateArray();
+ List<Coordinate> straight = new ArrayList<>();
+ final double EPS = 1e-3;
+ for (int i = 0; i < ring.length - 1; i++) {
+ Coordinate prev = (i == 0) ? ring[ring.length - 2] : ring[i - 1];
+ Coordinate curr = ring[i];
+ Coordinate next = ring[i + 1];
+ double cross = Math.abs((next.x - curr.x) * (curr.y - prev.y)
+ - (curr.x - prev.x) * (next.y - curr.y));
+ if (cross > EPS) {
+ straight.add(curr);
+ }
+ }
+ if (straight.isEmpty()) {
+ return buf;
+ }
+ straight.add(new Coordinate(straight.get(0)));
+ return straight.size() < 4 ? gf.createPolygon()
+ : gf.createPolygon(gf.createLinearRing(straight.toArray(new Coordinate[0])));
+ }
+ }
+
+ private static final class Vector2D {
+ final double x;
+ final double y;
+
+ Vector2D(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ Vector2D normalize() {
+ double len = Math.hypot(x, y);
+ if (len < 1e-12) {
+ return new Vector2D(1, 0);
+ }
+ return new Vector2D(x / len, y / len);
+ }
+
+ Vector2D rotate90CCW() {
+ return new Vector2D(-y, x);
+ }
+
+ double dot(Vector2D v) {
+ return x * v.x + y * v.y;
+ }
+
+ Vector2D sub(Vector2D v) {
+ return new Vector2D(x - v.x, y - v.y);
+ }
+
+ Vector2D add(Vector2D v) {
+ return new Vector2D(x + v.x, y + v.y);
+ }
+
+ Vector2D mul(double k) {
+ return new Vector2D(x * k, y * k);
+ }
+ }
+
+ private static final class LineSegment {
+ final Coordinate start;
+ final Coordinate end;
+ final int index;
+
+ LineSegment(Coordinate start, Coordinate end, int index) {
+ this.start = start;
+ this.end = end;
+ this.index = index;
+ }
+ }
+}
diff --git a/src/udpdell/UDPServer.java b/src/udpdell/UDPServer.java
index ee7b975..181086a 100644
--- a/src/udpdell/UDPServer.java
+++ b/src/udpdell/UDPServer.java
@@ -110,28 +110,28 @@
System.out.println("savenum:"+count);
Device.updateFromGNGGA(message, fields[15]);
- // // 鎵撳嵃瑙f瀽鍚庣殑鏁版嵁
- // System.out.println("UTC鏃堕棿: " + fields[1].toUpperCase());
- // System.out.println("绾害: " + fields[2].toUpperCase());
- // System.out.println("绾害鍗婄悆: " + fields[3].toUpperCase());
- // System.out.println("缁忓害: " + fields[4].toUpperCase());
- // System.out.println("缁忓害鍗婄悆: " + fields[5].toUpperCase());
- // System.out.println("瀹氫綅璐ㄩ噺鎸囩ず: " + fields[6].toUpperCase());
- // System.out.println("鍗槦鏁伴噺: " + fields[7].toUpperCase());
- // System.out.println("姘村钩绮惧害鍥犲瓙: " + fields[8].toUpperCase());
- // System.out.println("娴锋嫈楂樺害: " + fields[9].toUpperCase());
- // System.out.println("娴锋嫈楂樺害鍗曚綅: " + fields[10].toUpperCase());
- // System.out.println("澶у湴姘村噯闈㈤珮搴�: " + fields[11].toUpperCase());
- // System.out.println("澶у湴姘村噯闈㈤珮搴﹀崟浣�: " + fields[12].toUpperCase());
- // System.out.println("宸垎鏃堕棿: " + fields[13].toUpperCase());
- // System.out.println("鏍¢獙鍜�: " + fields[14].toUpperCase());
- // System.out.println("璁惧缂栧彿: " + fields[15].toUpperCase());
- // System.out.println("璁惧鐢甸噺: " + fields[16].toUpperCase());
- // System.out.println("鍗槦淇″彿寮哄害: " + fields[17].toUpperCase());
- // System.out.println("淇濈暀浣�1: " + fields[18].toUpperCase());
- // System.out.println("淇濈暀浣�2: " + fields[19].toUpperCase());
- // System.out.println("淇濈暀浣�3: " + fields[20].toUpperCase());
- // System.out.println("----------------------------------------");
+ // 鎵撳嵃瑙f瀽鍚庣殑鏁版嵁
+ //System.out.println("UTC鏃堕棿: " + fields[1].toUpperCase());
+ //System.out.println("绾害: " + fields[2].toUpperCase());
+ //System.out.println("绾害鍗婄悆: " + fields[3].toUpperCase());
+ //System.out.println("缁忓害: " + fields[4].toUpperCase());
+ //System.out.println("缁忓害鍗婄悆: " + fields[5].toUpperCase());
+ //System.out.println("瀹氫綅璐ㄩ噺鎸囩ず: " + fields[6].toUpperCase());
+ //System.out.println("鍗槦鏁伴噺: " + fields[7].toUpperCase());
+ //System.out.println("姘村钩绮惧害鍥犲瓙: " + fields[8].toUpperCase());
+ //System.out.println("娴锋嫈楂樺害: " + fields[9].toUpperCase());
+ //System.out.println("娴锋嫈楂樺害鍗曚綅: " + fields[10].toUpperCase());
+ //System.out.println("澶у湴姘村噯闈㈤珮搴�: " + fields[11].toUpperCase());
+ //System.out.println("澶у湴姘村噯闈㈤珮搴﹀崟浣�: " + fields[12].toUpperCase());
+ //System.out.println("宸垎鏃堕棿: " + fields[13].toUpperCase());
+ //System.out.println("鏍¢獙鍜�: " + fields[14].toUpperCase());
+ //System.out.println("璁惧缂栧彿: " + fields[15].toUpperCase());
+ //System.out.println("璁惧鐢甸噺: " + fields[16].toUpperCase());
+ //System.out.println("鍗槦淇″彿寮哄害: " + fields[17].toUpperCase());
+ //System.out.println("淇濈暀浣�1: " + fields[18].toUpperCase());
+ //System.out.println("淇濈暀浣�2: " + fields[19].toUpperCase());
+ //System.out.println("淇濈暀浣�3: " + fields[20].toUpperCase());
+ //System.out.println("----------------------------------------");
}
}
}
\ No newline at end of file
diff --git a/src/ui/UIConfig.java b/src/ui/UIConfig.java
index 8f2f173..f0c9920 100644
--- a/src/ui/UIConfig.java
+++ b/src/ui/UIConfig.java
@@ -9,8 +9,8 @@
private UIConfig() {}
// 6.5 瀵哥珫灞忓父鐢ㄥ璇濇灏哄锛堝 x 楂橈級
- public static final int DIALOG_WIDTH = 360;
- public static final int DIALOG_HEIGHT = 640;
+ public static final int DIALOG_WIDTH = 400;
+ public static final int DIALOG_HEIGHT = 800;
// 琛ㄩ」鏈�澶ч珮搴︼紝閬垮厤鍥炬爣鎴栨寜閽瑁佸垏
public static final int ITEM_ROW_MAX_HEIGHT = 32;
diff --git a/src/dikuai/AddDikuai.java b/src/zhangaiwu/AddDikuai.java
similarity index 78%
rename from src/dikuai/AddDikuai.java
rename to src/zhangaiwu/AddDikuai.java
index 8c59d2a..017bc55 100644
--- a/src/dikuai/AddDikuai.java
+++ b/src/zhangaiwu/AddDikuai.java
@@ -1,4 +1,4 @@
-package dikuai;
+package zhangaiwu;
import javax.swing.*;
@@ -14,11 +14,15 @@
import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import bianjie.jisuanmianjie;
+import dikuai.Dikuai;
+import dikuai.Dikuaiguanli;
import gecaoji.Device;
import bianjie.bianjieguihua2;
+import lujing.Lunjingguihua;
import ui.UIConfig;
import zhuye.MowerLocationData;
import zhuye.Shouye;
@@ -62,6 +66,7 @@
private JButton nextButton;
private JButton createButton;
private JLabel boundaryCountLabel;
+ private JPanel obstacleListContainer;
// 鍦板潡鏁版嵁
private Map<String, String> dikuaiData = new HashMap<>();
@@ -218,6 +223,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());
@@ -226,6 +235,256 @@
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<>();
+
+ String landNumber = getPendingLandNumber();
+ String raw = null;
+ if (landNumber != null) {
+ Dikuai dikuai = Dikuai.getDikuai(landNumber);
+ if (dikuai != null) {
+ raw = normalizeCoordinateValue(dikuai.getObstacleCoordinates());
+ }
+ }
+ if (raw == null) {
+ raw = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates"));
+ }
+ if (!isMeaningfulValue(raw)) {
+ return summaries;
+ }
+
+ String normalized = stripInlineComment(raw);
+ if (normalized.isEmpty()) {
+ return summaries;
+ }
+
+ List<String> entries = splitObstacleEntries(normalized);
+ int defaultIndex = 1;
+
+ for (String entry : entries) {
+ String trimmedEntry = stripInlineComment(entry);
+ if (trimmedEntry.isEmpty()) {
+ continue;
+ }
+
+ String nameToken = null;
+ String shapeToken = null;
+ String coordsSection = trimmedEntry;
+
+ if (trimmedEntry.contains("::")) {
+ String[] parts = trimmedEntry.split("::", 3);
+ if (parts.length == 3) {
+ nameToken = parts[0].trim();
+ shapeToken = parts[1].trim();
+ coordsSection = parts[2].trim();
+ }
+ } else if (trimmedEntry.contains("@")) {
+ String[] parts = trimmedEntry.split("@", 3);
+ if (parts.length == 3) {
+ nameToken = parts[0].trim();
+ shapeToken = parts[1].trim();
+ coordsSection = parts[2].trim();
+ } else if (parts.length == 2) {
+ shapeToken = parts[0].trim();
+ coordsSection = parts[1].trim();
+ }
+ } else if (trimmedEntry.contains(":")) {
+ String[] parts = trimmedEntry.split(":", 3);
+ if (parts.length == 3) {
+ nameToken = parts[0].trim();
+ shapeToken = parts[1].trim();
+ coordsSection = parts[2].trim();
+ } else if (parts.length == 2) {
+ if (looksLikeShapeToken(parts[0])) {
+ shapeToken = parts[0].trim();
+ coordsSection = parts[1].trim();
+ } else {
+ nameToken = parts[0].trim();
+ coordsSection = parts[1].trim();
+ }
+ }
+ }
+
+ String sanitizedCoords = sanitizeCoordinateString(coordsSection);
+ if (!isMeaningfulValue(sanitizedCoords)) {
+ continue;
+ }
+
+ String resolvedName;
+ if (nameToken != null && !nameToken.isEmpty()) {
+ resolvedName = nameToken;
+ } else {
+ resolvedName = "闅滅鐗�" + defaultIndex++;
+ }
+
+ String displayCoords = truncateCoordinateForDisplay(sanitizedCoords, 12);
+ summaries.add(new ObstacleSummary(resolvedName, sanitizedCoords, displayCoords));
+ }
+
+ return summaries;
+ }
+
+ 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 JPanel createStep2Panel() {
JPanel stepPanel = new JPanel();
@@ -675,9 +934,89 @@
showStep(2);
return;
}
- dikuaiData.put("mowingPattern", (String) mowingPatternCombo.getSelectedItem());
- dikuaiData.put("mowingWidth", mowingWidthSpinner.getValue().toString());
- JOptionPane.showMessageDialog(this, "宸叉牴鎹綋鍓嶈缃敓鎴愬壊鑽夎矾寰�", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ Dikuai dikuai = getOrCreatePendingDikuai();
+ String boundaryCoords = null;
+ if (dikuai != null) {
+ boundaryCoords = normalizeCoordinateValue(dikuai.getBoundaryCoordinates());
+ }
+ if (boundaryCoords == null) {
+ boundaryCoords = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates"));
+ }
+ if (boundaryCoords == null) {
+ JOptionPane.showMessageDialog(this, "鏈壘鍒版湁鏁堢殑鍦板潡杈圭晫鍧愭爣锛屾棤娉曠敓鎴愯矾寰�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ if (createButton != null) {
+ createButton.setEnabled(false);
+ }
+ return;
+ }
+
+ String obstacleCoords = null;
+ if (dikuai != null) {
+ obstacleCoords = normalizeCoordinateValue(dikuai.getObstacleCoordinates());
+ }
+ if (obstacleCoords == null) {
+ obstacleCoords = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates"));
+ }
+
+ String patternDisplay = (String) mowingPatternCombo.getSelectedItem();
+ dikuaiData.put("mowingPattern", patternDisplay);
+
+ Object widthObj = mowingWidthSpinner.getValue();
+ if (!(widthObj instanceof Number)) {
+ JOptionPane.showMessageDialog(this, "鍓茶崏瀹藉害杈撳叆鏃犳晥", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ if (createButton != null) {
+ createButton.setEnabled(false);
+ }
+ return;
+ }
+ double widthCm = ((Number) widthObj).doubleValue();
+ if (widthCm <= 0) {
+ JOptionPane.showMessageDialog(this, "鍓茶崏瀹藉害蹇呴』澶т簬0", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ if (createButton != null) {
+ createButton.setEnabled(false);
+ }
+ return;
+ }
+ dikuaiData.put("mowingWidth", widthObj.toString());
+
+ String widthMeters = String.format(Locale.US, "%.2f", widthCm / 100.0);
+ String plannerMode = resolvePlannerMode(patternDisplay);
+
+ try {
+ List<Lunjingguihua.PathSegment> segments = Lunjingguihua.generatePathSegments(
+ boundaryCoords,
+ obstacleCoords,
+ widthMeters,
+ plannerMode
+ );
+ String plannedPath = Lunjingguihua.formatPathSegments(segments);
+ if (!isMeaningfulValue(plannedPath)) {
+ JOptionPane.showMessageDialog(this, "鐢熸垚鍓茶崏璺緞澶辫触: 鐢熸垚缁撴灉涓虹┖", "閿欒", JOptionPane.ERROR_MESSAGE);
+ if (createButton != null) {
+ createButton.setEnabled(false);
+ }
+ return;
+ }
+ dikuaiData.put("plannedPath", plannedPath);
+ if (createButton != null) {
+ createButton.setEnabled(true);
+ }
+ JOptionPane.showMessageDialog(this,
+ "宸叉牴鎹綋鍓嶈缃敓鎴愬壊鑽夎矾寰勶紝鍏辩敓鎴� " + segments.size() + " 娈点��",
+ "鎴愬姛",
+ JOptionPane.INFORMATION_MESSAGE);
+ } catch (IllegalArgumentException ex) {
+ JOptionPane.showMessageDialog(this, "鐢熸垚鍓茶崏璺緞澶辫触: " + ex.getMessage(), "閿欒", JOptionPane.ERROR_MESSAGE);
+ if (createButton != null) {
+ createButton.setEnabled(false);
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ JOptionPane.showMessageDialog(this, "鐢熸垚鍓茶崏璺緞鏃跺彂鐢熷紓甯�: " + ex.getMessage(), "閿欒", JOptionPane.ERROR_MESSAGE);
+ if (createButton != null) {
+ createButton.setEnabled(false);
+ }
+ }
}
private JButton createPrimaryButton(String text, int fontSize) {
@@ -732,6 +1071,7 @@
nextButton = createPrimaryButton("涓嬩竴姝�", 16);
createButton = createPrimaryButton("淇濆瓨", 16);
createButton.setVisible(false);
+ createButton.setEnabled(false);
buttonPanel.add(prevButton);
buttonPanel.add(Box.createHorizontalGlue());
@@ -880,6 +1220,36 @@
boundaryCountLabel.setVisible(false);
}
}
+
+ private boolean hasGeneratedPath() {
+ return isMeaningfulValue(dikuaiData.get("plannedPath"));
+ }
+
+ private String normalizeCoordinateValue(String value) {
+ return isMeaningfulValue(value) ? value.trim() : null;
+ }
+
+ private boolean isMeaningfulValue(String value) {
+ if (value == null) {
+ return false;
+ }
+ String trimmed = value.trim();
+ return !trimmed.isEmpty() && !"-1".equals(trimmed);
+ }
+
+ 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() {
// 涓婁竴姝ユ寜閽�
@@ -922,6 +1292,10 @@
currentStep = step;
cardLayout.show(stepsPanel, "step" + step);
+ if (step == 1) {
+ updateObstacleSummary();
+ }
+
// 鏇存柊鎸夐挳鐘舵��
updateButtonState(step);
}
@@ -932,9 +1306,11 @@
if (step < 3) {
nextButton.setVisible(true);
createButton.setVisible(false);
+ createButton.setEnabled(false);
} else {
nextButton.setVisible(false);
createButton.setVisible(true);
+ createButton.setEnabled(hasGeneratedPath());
}
Container parent = prevButton.getParent();
@@ -977,6 +1353,10 @@
case 3:
dikuaiData.put("mowingPattern", (String) mowingPatternCombo.getSelectedItem());
dikuaiData.put("mowingWidth", mowingWidthSpinner.getValue().toString());
+ if (!hasGeneratedPath()) {
+ JOptionPane.showMessageDialog(this, "璇峰厛鐢熸垚鍓茶崏璺緞", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
break;
}
return true;
@@ -1271,6 +1651,11 @@
dikuai.setMowingWidth(dikuaiData.get("mowingWidth"));
}
+ String plannedPath = dikuaiData.get("plannedPath");
+ if (isMeaningfulValue(plannedPath)) {
+ dikuai.setPlannedPath(plannedPath);
+ }
+
Dikuai.putDikuai(landNumber, dikuai);
Dikuai.saveToProperties();
diff --git a/src/zhangaiwu/Obstacledge.java b/src/zhangaiwu/Obstacledge.java
new file mode 100644
index 0000000..2bd39e4
--- /dev/null
+++ b/src/zhangaiwu/Obstacledge.java
@@ -0,0 +1,827 @@
+package zhangaiwu;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鍓茶崏鏈哄湴鍧楅殰纰嶇墿绫�
+ * 鐢ㄤ簬淇濆瓨鍜岀鐞嗗湴鍧楅殰纰嶇墿鐨勭浉鍏充俊鎭�
+ */
+public class Obstacledge {
+
+ /**
+ * 闅滅鐗╁舰鐘舵灇涓�
+ */
+ public enum ObstacleShape {
+ CIRCLE(0, "鍦嗗舰"),
+ POLYGON(1, "澶氳竟褰�");
+
+ private final int code;
+ private final String description;
+
+ ObstacleShape(int code, String description) {
+ this.code = code;
+ this.description = description;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * 鏍规嵁浠g爜鑾峰彇褰㈢姸鏋氫妇
+ * @param code 褰㈢姸浠g爜
+ * @return 瀵瑰簲鐨勫舰鐘舵灇涓撅紝濡傛灉鎵句笉鍒拌繑鍥瀗ull
+ */
+ public static ObstacleShape getByCode(int code) {
+ for (ObstacleShape shape : values()) {
+ if (shape.getCode() == code) {
+ return shape;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * 鍧愭爣鐐圭被 - 鐢ㄤ簬瀛樺偍搴﹀垎鏍煎紡鐨勫潗鏍�
+ */
+ public static class DMCoordinate {
+ private double degreeMinute; // 搴﹀垎鍊硷紝濡�2324.200273
+ private char direction; // 鏂瑰悜锛孨/S/E/W
+
+ public DMCoordinate() {
+ }
+
+ public DMCoordinate(double degreeMinute, char direction) {
+ this.degreeMinute = degreeMinute;
+ this.direction = direction;
+ }
+
+ public double getDegreeMinute() {
+ return degreeMinute;
+ }
+
+ public void setDegreeMinute(double degreeMinute) {
+ this.degreeMinute = degreeMinute;
+ }
+
+ public char getDirection() {
+ return direction;
+ }
+
+ public void setDirection(char direction) {
+ this.direction = direction;
+ }
+
+ /**
+ * 灏嗗害鍒嗘牸寮忚浆鎹负搴︽牸寮�
+ * @return 鍗佽繘鍒跺害鏁�
+ */
+ public double toDecimalDegree() {
+ // 鍒嗙搴﹀拰鍒�
+ double dm = Math.abs(degreeMinute);
+ int degrees = (int)(dm / 100); // 搴﹀湪鐧句綅浠ヤ笂
+ double minutes = dm - degrees * 100;
+
+ // 璁$畻鍗佽繘鍒跺害鏁�
+ double decimalDegrees = degrees + minutes / 60.0;
+
+ // 鏍规嵁鏂瑰悜纭畾姝h礋
+ if (direction == 'S' || direction == 'W') {
+ decimalDegrees = -decimalDegrees;
+ }
+
+ return decimalDegrees;
+ }
+
+ /**
+ * 灏嗗潗鏍囩偣杞崲涓哄瓧绗︿覆鏍煎紡
+ * @return 鏍煎紡濡� "2324.200273,N"
+ */
+ @Override
+ public String toString() {
+ return String.format("%.6f,%c", degreeMinute, direction);
+ }
+
+ /**
+ * 浠庡瓧绗︿覆瑙f瀽鍧愭爣鐐�
+ * @param coordStr 鏍煎紡濡� "2324.200273,N"
+ * @return DMCoordinate瀵硅薄
+ */
+ public static DMCoordinate fromString(String coordStr) {
+ String[] parts = coordStr.split(",");
+ if (parts.length != 2) {
+ throw new IllegalArgumentException("Invalid coordinate format: " + coordStr);
+ }
+
+ DMCoordinate coord = new DMCoordinate();
+ coord.setDegreeMinute(Double.parseDouble(parts[0]));
+ coord.setDirection(parts[1].charAt(0));
+
+ return coord;
+ }
+ }
+
+ /**
+ * XY鍧愭爣鐐圭被 - 鐢ㄤ簬瀛樺偍鐩稿浜庡熀鍑嗙珯鐨勫钩闈㈠潗鏍�
+ */
+ public static class XYCoordinate {
+ private double x; // X鍧愭爣锛堜笢鏂瑰悜锛�
+ private double y; // Y鍧愭爣锛堝寳鏂瑰悜锛�
+
+ public XYCoordinate() {
+ }
+
+ public XYCoordinate(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public void setX(double x) {
+ this.x = x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public void setY(double y) {
+ this.y = y;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%.2f,%.2f", x, y);
+ }
+
+ /**
+ * 浠庡瓧绗︿覆瑙f瀽XY鍧愭爣鐐�
+ * @param xyStr 鏍煎紡濡� "5.2,3.8"
+ * @return XYCoordinate瀵硅薄
+ */
+ public static XYCoordinate fromString(String xyStr) {
+ String[] parts = xyStr.split(",");
+ if (parts.length != 2) {
+ throw new IllegalArgumentException("Invalid XY coordinate format: " + xyStr);
+ }
+
+ XYCoordinate coord = new XYCoordinate();
+ coord.setX(Double.parseDouble(parts[0]));
+ coord.setY(Double.parseDouble(parts[1]));
+
+ return coord;
+ }
+ }
+
+ /**
+ * 闅滅鐗╃被
+ */
+ public static class Obstacle {
+ private String plotId; // 鍦板潡缂栧彿
+ private String obstacleName; // 闅滅鐗╁悕绉帮紙鍞竴锛�
+ private ObstacleShape shape; // 闅滅鐗╁舰鐘�
+ private List<DMCoordinate> originalCoordinates = new ArrayList<>(); // 鍘熷鍧愭爣鍒楄〃
+ private List<XYCoordinate> xyCoordinates = new ArrayList<>(); // XY鍧愭爣鍒楄〃
+
+ public Obstacle() {
+ }
+
+ public Obstacle(String plotId, String obstacleName, ObstacleShape shape) {
+ this.plotId = plotId;
+ this.obstacleName = obstacleName;
+ this.shape = shape;
+ }
+
+ // Getter鍜孲etter鏂规硶
+
+ public String getPlotId() {
+ return plotId;
+ }
+
+ public void setPlotId(String plotId) {
+ this.plotId = plotId;
+ }
+
+ public String getObstacleName() {
+ return obstacleName;
+ }
+
+ public void setObstacleName(String obstacleName) {
+ this.obstacleName = obstacleName;
+ }
+
+ public ObstacleShape getShape() {
+ return shape;
+ }
+
+ public void setShape(ObstacleShape shape) {
+ this.shape = shape;
+ }
+
+ /**
+ * 璁剧疆褰㈢姸锛堥�氳繃浠g爜锛�
+ * @param shapeCode 褰㈢姸浠g爜锛�0=鍦嗗舰锛�1=澶氳竟褰級
+ */
+ public void setShapeByCode(int shapeCode) {
+ this.shape = ObstacleShape.getByCode(shapeCode);
+ }
+
+ public List<DMCoordinate> getOriginalCoordinates() {
+ return originalCoordinates;
+ }
+
+ public void setOriginalCoordinates(List<DMCoordinate> originalCoordinates) {
+ this.originalCoordinates = originalCoordinates;
+ }
+
+ /**
+ * 娣诲姞鍘熷鍧愭爣鐐�
+ * @param coordinate 搴﹀垎鏍煎紡鍧愭爣鐐�
+ */
+ public void addOriginalCoordinate(DMCoordinate coordinate) {
+ this.originalCoordinates.add(coordinate);
+ }
+
+ /**
+ * 娣诲姞鍘熷鍧愭爣鐐�
+ * @param degreeMinute 搴﹀垎鍊�
+ * @param direction 鏂瑰悜
+ */
+ public void addOriginalCoordinate(double degreeMinute, char direction) {
+ this.originalCoordinates.add(new DMCoordinate(degreeMinute, direction));
+ }
+
+ public List<XYCoordinate> getXyCoordinates() {
+ return xyCoordinates;
+ }
+
+ public void setXyCoordinates(List<XYCoordinate> xyCoordinates) {
+ this.xyCoordinates = xyCoordinates;
+ }
+
+ /**
+ * 娣诲姞XY鍧愭爣鐐�
+ * @param coordinate XY鍧愭爣鐐�
+ */
+ public void addXyCoordinate(XYCoordinate coordinate) {
+ this.xyCoordinates.add(coordinate);
+ }
+
+ /**
+ * 娣诲姞XY鍧愭爣鐐�
+ * @param x X鍧愭爣
+ * @param y Y鍧愭爣
+ */
+ public void addXyCoordinate(double x, double y) {
+ this.xyCoordinates.add(new XYCoordinate(x, y));
+ }
+
+ /**
+ * 鑾峰彇鍘熷鍧愭爣瀛楃涓�
+ * @return 鏍煎紡濡� "2324.200273,N,11330.666730,E;2324.200400,N,11330.666850,E"
+ */
+ public String getOriginalCoordsString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < originalCoordinates.size(); i++) {
+ if (i > 0) {
+ sb.append(";");
+ }
+ sb.append(originalCoordinates.get(i).toString());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 璁剧疆鍘熷鍧愭爣瀛楃涓�
+ * @param coordsStr 鏍煎紡濡� "2324.200273,N,11330.666730,E;2324.200400,N,11330.666850,E"
+ */
+ public void setOriginalCoordsString(String coordsStr) {
+ originalCoordinates.clear();
+ String[] points = coordsStr.split(";");
+ for (String point : points) {
+ // 姣忎釜鐐瑰彲鑳芥槸涓や釜DMCoordinate鐨勭粍鍚堬紙绾害+缁忓害锛�
+ String[] coords = point.split(",");
+ if (coords.length == 4) {
+ // 鏍煎紡锛氱含搴﹀害鍒�,绾害鏂瑰悜,缁忓害搴﹀垎,缁忓害鏂瑰悜
+ double latDM = Double.parseDouble(coords[0]);
+ char latDir = coords[1].charAt(0);
+ double lonDM = Double.parseDouble(coords[2]);
+ char lonDir = coords[3].charAt(0);
+
+ // 娉ㄦ剰锛氬疄闄呭瓨鍌ㄦ椂闇�瑕佸垎鍒瓨鍌ㄧ含搴﹀拰缁忓害鍧愭爣鐐�
+ // 杩欓噷绠�鍖栧鐞嗭紝瀛樺偍涓轰袱涓狣MCoordinate瀵硅薄
+ addOriginalCoordinate(latDM, latDir);
+ addOriginalCoordinate(lonDM, lonDir);
+ } else if (coords.length == 2) {
+ // 鏍煎紡锛氬害鍒�,鏂瑰悜
+ originalCoordinates.add(DMCoordinate.fromString(point));
+ }
+ }
+ }
+
+ /**
+ * 鑾峰彇XY鍧愭爣瀛楃涓�
+ * @return 鏍煎紡濡� "5.2,3.8;7.5,6.2"
+ */
+ public String getXyCoordsString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < xyCoordinates.size(); i++) {
+ if (i > 0) {
+ sb.append(";");
+ }
+ sb.append(xyCoordinates.get(i).toString());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 璁剧疆XY鍧愭爣瀛楃涓�
+ * @param xyCoordsStr 鏍煎紡濡� "5.2,3.8;7.5,6.2"
+ */
+ public void setXyCoordsString(String xyCoordsStr) {
+ xyCoordinates.clear();
+ String[] points = xyCoordsStr.split(";");
+ for (String point : points) {
+ xyCoordinates.add(XYCoordinate.fromString(point));
+ }
+ }
+
+ /**
+ * 楠岃瘉闅滅鐗╂暟鎹殑鏈夋晥鎬�
+ * @return 鏄惁鏈夋晥
+ */
+ public boolean isValid() {
+ if (plotId == null || plotId.isEmpty()) {
+ return false;
+ }
+
+ if (obstacleName == null || obstacleName.isEmpty()) {
+ return false;
+ }
+
+ if (shape == null) {
+ return false;
+ }
+
+ // 鏍规嵁褰㈢姸楠岃瘉鍧愭爣鐐规暟閲�
+ if (shape == ObstacleShape.CIRCLE) {
+ // 鍦嗗舰闇�瑕佽嚦灏�2涓潗鏍囩偣锛堝渾蹇冨拰鍦嗕笂涓�鐐癸級
+ return originalCoordinates.size() >= 2 && xyCoordinates.size() >= 2;
+ } else if (shape == ObstacleShape.POLYGON) {
+ // 澶氳竟褰㈤渶瑕佽嚦灏�3涓潗鏍囩偣
+ return originalCoordinates.size() >= 6 && xyCoordinates.size() >= 3; // 鍘熷鍧愭爣姣忎釜鐐规湁绾害鍜岀粡搴︼紝鎵�浠ユ槸2鍊�
+ }
+
+ return false;
+ }
+
+ /**
+ * 璁$畻鍦嗗舰闅滅鐗╃殑鍗婂緞锛堝鏋滃舰鐘舵槸鍦嗗舰锛�
+ * @return 鍗婂緞锛堢背锛夛紝濡傛灉涓嶆槸鍦嗗舰杩斿洖-1
+ */
+ public double calculateRadius() {
+ if (shape != ObstacleShape.CIRCLE || xyCoordinates.size() < 2) {
+ return -1;
+ }
+
+ XYCoordinate center = xyCoordinates.get(0);
+ XYCoordinate pointOnCircle = xyCoordinates.get(1);
+
+ double dx = pointOnCircle.getX() - center.getX();
+ double dy = pointOnCircle.getY() - center.getY();
+
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+
+ /**
+ * 璁$畻澶氳竟褰㈤殰纰嶇墿鐨勯潰绉紙濡傛灉褰㈢姸鏄杈瑰舰锛�
+ * @return 闈㈢Н锛堝钩鏂圭背锛夛紝濡傛灉涓嶆槸澶氳竟褰㈣繑鍥�-1
+ */
+ public double calculateArea() {
+ if (shape != ObstacleShape.POLYGON || xyCoordinates.size() < 3) {
+ return -1;
+ }
+
+ double area = 0;
+ int n = xyCoordinates.size();
+
+ for (int i = 0; i < n; i++) {
+ XYCoordinate current = xyCoordinates.get(i);
+ XYCoordinate next = xyCoordinates.get((i + 1) % n);
+
+ area += current.getX() * next.getY();
+ area -= current.getY() * next.getX();
+ }
+
+ return Math.abs(area) / 2.0;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Obstacle{plotId='%s', name='%s', shape=%s, points=%d}",
+ plotId, obstacleName, shape.getDescription(), originalCoordinates.size() / 2);
+ }
+ }
+
+ /**
+ * 鍦板潡绫�
+ */
+ public static class Plot {
+ private String plotId; // 鍦板潡缂栧彿锛堝敮涓�锛�
+ private DMCoordinate baseStationLat; // 鍩哄噯绔欑含搴�
+ private DMCoordinate baseStationLon; // 鍩哄噯绔欑粡搴�
+ private List<Obstacle> obstacles = new ArrayList<>(); // 鍦板潡鍐呯殑闅滅鐗╁垪琛�
+
+ public Plot() {
+ }
+
+ public Plot(String plotId) {
+ this.plotId = plotId;
+ }
+
+ // Getter鍜孲etter鏂规硶
+
+ public String getPlotId() {
+ return plotId;
+ }
+
+ public void setPlotId(String plotId) {
+ this.plotId = plotId;
+ }
+
+ public DMCoordinate getBaseStationLat() {
+ return baseStationLat;
+ }
+
+ public void setBaseStationLat(DMCoordinate baseStationLat) {
+ this.baseStationLat = baseStationLat;
+ }
+
+ public DMCoordinate getBaseStationLon() {
+ return baseStationLon;
+ }
+
+ public void setBaseStationLon(DMCoordinate baseStationLon) {
+ this.baseStationLon = baseStationLon;
+ }
+
+ /**
+ * 璁剧疆鍩哄噯绔欏潗鏍�
+ * @param lat 绾害搴﹀垎鍧愭爣
+ * @param lon 缁忓害搴﹀垎鍧愭爣
+ */
+ public void setBaseStation(DMCoordinate lat, DMCoordinate lon) {
+ this.baseStationLat = lat;
+ this.baseStationLon = lon;
+ }
+
+ /**
+ * 璁剧疆鍩哄噯绔欏潗鏍囧瓧绗︿覆
+ * @param baseStationStr 鏍煎紡濡� "2324.200273,N,11330.666730,E"
+ */
+ public void setBaseStationString(String baseStationStr) {
+ String[] parts = baseStationStr.split(",");
+ if (parts.length != 4) {
+ throw new IllegalArgumentException("Invalid base station format: " + baseStationStr);
+ }
+
+ double latDM = Double.parseDouble(parts[0]);
+ char latDir = parts[1].charAt(0);
+ double lonDM = Double.parseDouble(parts[2]);
+ char lonDir = parts[3].charAt(0);
+
+ this.baseStationLat = new DMCoordinate(latDM, latDir);
+ this.baseStationLon = new DMCoordinate(lonDM, lonDir);
+ }
+
+ /**
+ * 鑾峰彇鍩哄噯绔欏潗鏍囧瓧绗︿覆
+ * @return 鏍煎紡濡� "2324.200273,N,11330.666730,E"
+ */
+ public String getBaseStationString() {
+ if (baseStationLat == null || baseStationLon == null) {
+ return "";
+ }
+ return String.format("%s,%s", baseStationLat.toString(), baseStationLon.toString());
+ }
+
+ public List<Obstacle> getObstacles() {
+ return obstacles;
+ }
+
+ public void setObstacles(List<Obstacle> obstacles) {
+ this.obstacles = obstacles;
+ }
+
+ /**
+ * 娣诲姞闅滅鐗�
+ * @param obstacle 闅滅鐗╁璞�
+ */
+ public void addObstacle(Obstacle obstacle) {
+ this.obstacles.add(obstacle);
+ }
+
+ /**
+ * 鏍规嵁闅滅鐗╁悕绉拌幏鍙栭殰纰嶇墿
+ * @param obstacleName 闅滅鐗╁悕绉�
+ * @return 闅滅鐗╁璞★紝濡傛灉鎵句笉鍒拌繑鍥瀗ull
+ */
+ public Obstacle getObstacleByName(String obstacleName) {
+ for (Obstacle obstacle : obstacles) {
+ if (obstacle.getObstacleName().equals(obstacleName)) {
+ return obstacle;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鏍规嵁闅滅鐗╁悕绉扮Щ闄ら殰纰嶇墿
+ * @param obstacleName 闅滅鐗╁悕绉�
+ * @return 鏄惁鎴愬姛绉婚櫎
+ */
+ public boolean removeObstacleByName(String obstacleName) {
+ return obstacles.removeIf(obstacle -> obstacle.getObstacleName().equals(obstacleName));
+ }
+
+ /**
+ * 楠岃瘉鍦板潡鏁版嵁鐨勬湁鏁堟��
+ * @return 鏄惁鏈夋晥
+ */
+ public boolean isValid() {
+ if (plotId == null || plotId.isEmpty()) {
+ return false;
+ }
+
+ if (baseStationLat == null || baseStationLon == null) {
+ return false;
+ }
+
+ // 楠岃瘉鎵�鏈夐殰纰嶇墿
+ for (Obstacle obstacle : obstacles) {
+ if (!obstacle.isValid()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 鑾峰彇鍦板潡涓殰纰嶇墿鐨勬暟閲�
+ * @return 闅滅鐗╂暟閲�
+ */
+ public int getObstacleCount() {
+ return obstacles.size();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Plot{id='%s', baseStation=%s, obstacles=%d}",
+ plotId, getBaseStationString(), obstacles.size());
+ }
+ }
+
+ /**
+ * 閰嶇疆鏂囦欢绠$悊鍣ㄧ被
+ */
+ public static class ConfigManager {
+ private List<Plot> plots = new ArrayList<>();
+
+ public ConfigManager() {
+ }
+
+ // Getter鍜孲etter鏂规硶
+
+ public List<Plot> getPlots() {
+ return plots;
+ }
+
+ public void setPlots(List<Plot> plots) {
+ this.plots = plots;
+ }
+
+ /**
+ * 娣诲姞鍦板潡
+ * @param plot 鍦板潡瀵硅薄
+ */
+ public void addPlot(Plot plot) {
+ this.plots.add(plot);
+ }
+
+ /**
+ * 鏍规嵁鍦板潡缂栧彿鑾峰彇鍦板潡
+ * @param plotId 鍦板潡缂栧彿
+ * @return 鍦板潡瀵硅薄锛屽鏋滄壘涓嶅埌杩斿洖null
+ */
+ public Plot getPlotById(String plotId) {
+ for (Plot plot : plots) {
+ if (plot.getPlotId().equals(plotId)) {
+ return plot;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 鏍规嵁鍦板潡缂栧彿绉婚櫎鍦板潡
+ * @param plotId 鍦板潡缂栧彿
+ * @return 鏄惁鎴愬姛绉婚櫎
+ */
+ public boolean removePlotById(String plotId) {
+ return plots.removeIf(plot -> plot.getPlotId().equals(plotId));
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夊湴鍧椾腑鐨勯殰纰嶇墿鎬绘暟
+ * @return 闅滅鐗╂�绘暟
+ */
+ public int getTotalObstacleCount() {
+ int count = 0;
+ for (Plot plot : plots) {
+ count += plot.getObstacleCount();
+ }
+ return count;
+ }
+
+ /**
+ * 楠岃瘉鎵�鏈夊湴鍧楁暟鎹殑鏈夋晥鎬�
+ * @return 鏄惁鎵�鏈夋暟鎹兘鏈夋晥
+ */
+ public boolean validateAll() {
+ for (Plot plot : plots) {
+ if (!plot.isValid()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 杞崲涓簆roperties鏂囦欢鏍煎紡瀛楃涓�
+ * @return properties鏍煎紡瀛楃涓�
+ */
+ public String toPropertiesString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("# 鍓茶崏鏈哄湴鍧楅殰纰嶇墿閰嶇疆鏂囦欢\n");
+ sb.append("# 鐢熸垚鏃堕棿锛�").append(java.time.LocalDateTime.now()).append("\n");
+ sb.append("# 鍧愭爣绯伙細WGS84锛堝害鍒嗘牸寮忥級\n\n");
+
+ sb.append("# ============ 鍦板潡鍩哄噯绔欓厤缃� ============\n");
+ sb.append("# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].baseStation=[缁忓害],[N/S],[绾害],[E/W]\n");
+
+ // 娣诲姞鍩哄噯绔欓厤缃�
+ for (Plot plot : plots) {
+ sb.append("plot.").append(plot.getPlotId())
+ .append(".baseStation=").append(plot.getBaseStationString())
+ .append("\n");
+ }
+
+ sb.append("\n# ============ 闅滅鐗╅厤缃� ============\n");
+ sb.append("# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].obstacle.[闅滅鐗╁悕绉癩.shape=[0|1]\n");
+ sb.append("# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].obstacle.[闅滅鐗╁悕绉癩.originalCoords=[鍧愭爣涓瞉\n");
+ sb.append("# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].obstacle.[闅滅鐗╁悕绉癩.xyCoords=[鍧愭爣涓瞉\n\n");
+
+ // 娣诲姞闅滅鐗╅厤缃�
+ for (Plot plot : plots) {
+ if (plot.getObstacleCount() > 0) {
+ sb.append("# --- 鍦板潡").append(plot.getPlotId()).append("鐨勯殰纰嶇墿 ---\n");
+
+ for (Obstacle obstacle : plot.getObstacles()) {
+ sb.append("plot.").append(plot.getPlotId())
+ .append(".obstacle.").append(obstacle.getObstacleName())
+ .append(".shape=").append(obstacle.getShape().getCode())
+ .append("\n");
+
+ sb.append("plot.").append(plot.getPlotId())
+ .append(".obstacle.").append(obstacle.getObstacleName())
+ .append(".originalCoords=").append(obstacle.getOriginalCoordsString())
+ .append("\n");
+
+ sb.append("plot.").append(plot.getPlotId())
+ .append(".obstacle.").append(obstacle.getObstacleName())
+ .append(".xyCoords=").append(obstacle.getXyCoordsString())
+ .append("\n");
+ }
+ sb.append("\n");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * 淇濆瓨閰嶇疆鍒版枃浠�
+ * @param filePath 鏂囦欢璺緞
+ * @return 鏄惁淇濆瓨鎴愬姛
+ */
+ public boolean saveToFile(String filePath) {
+ try {
+ String propertiesContent = toPropertiesString();
+ java.nio.file.Files.write(
+ java.nio.file.Paths.get(filePath),
+ propertiesContent.getBytes(java.nio.charset.StandardCharsets.UTF_8)
+ );
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 浠巔roperties鏂囦欢鍔犺浇閰嶇疆
+ * @param filePath 鏂囦欢璺緞
+ * @return 鏄惁鍔犺浇鎴愬姛
+ */
+ public boolean loadFromFile(String filePath) {
+ try {
+ List<String> lines = java.nio.file.Files.readAllLines(
+ java.nio.file.Paths.get(filePath),
+ java.nio.charset.StandardCharsets.UTF_8
+ );
+
+ plots.clear();
+ Plot currentPlot = null;
+ Obstacle currentObstacle = null;
+
+ for (String line : lines) {
+ line = line.trim();
+
+ // 璺宠繃绌鸿鍜屾敞閲�
+ if (line.isEmpty() || line.startsWith("#")) {
+ continue;
+ }
+
+ // 瑙f瀽閿�煎
+ if (line.contains("=")) {
+ String[] parts = line.split("=", 2);
+ String key = parts[0].trim();
+ String value = parts[1].trim();
+
+ // 瑙f瀽閿殑缁撴瀯
+ String[] keyParts = key.split("\\.");
+ if (keyParts.length >= 3) {
+ String plotId = keyParts[1];
+
+ // 鏌ユ壘鎴栧垱寤哄湴鍧�
+ if (currentPlot == null || !currentPlot.getPlotId().equals(plotId)) {
+ currentPlot = getPlotById(plotId);
+ if (currentPlot == null) {
+ currentPlot = new Plot(plotId);
+ addPlot(currentPlot);
+ }
+ }
+
+ if ("baseStation".equals(keyParts[2])) {
+ // 鍩哄噯绔欏潗鏍�
+ currentPlot.setBaseStationString(value);
+ } else if ("obstacle".equals(keyParts[2]) && keyParts.length >= 5) {
+ String obstacleName = keyParts[3];
+ String property = keyParts[4];
+
+ // 鏌ユ壘鎴栧垱寤洪殰纰嶇墿
+ if (currentObstacle == null || !currentObstacle.getObstacleName().equals(obstacleName)) {
+ currentObstacle = currentPlot.getObstacleByName(obstacleName);
+ if (currentObstacle == null) {
+ currentObstacle = new Obstacle();
+ currentObstacle.setPlotId(plotId);
+ currentObstacle.setObstacleName(obstacleName);
+ currentPlot.addObstacle(currentObstacle);
+ }
+ }
+
+ // 璁剧疆闅滅鐗╁睘鎬�
+ switch (property) {
+ case "shape":
+ currentObstacle.setShapeByCode(Integer.parseInt(value));
+ break;
+ case "originalCoords":
+ currentObstacle.setOriginalCoordsString(value);
+ break;
+ case "xyCoords":
+ currentObstacle.setXyCoordsString(value);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/zhangaiwu/Obstacledraw.java b/src/zhangaiwu/Obstacledraw.java
new file mode 100644
index 0000000..a01e0e0
--- /dev/null
+++ b/src/zhangaiwu/Obstacledraw.java
@@ -0,0 +1,635 @@
+package zhangaiwu;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * 闅滅鐗╃粯鍒剁被 - 璐熻矗缁樺埗鍦板潡涓殑闅滅鐗�
+ */
+public class Obstacledraw {
+
+ // 棰滆壊瀹氫箟
+ private static final Color CIRCLE_FILL_COLOR = new Color(255, 182, 193, 120); // 鍦嗗舰濉厖鑹� - 娴呯矇绾�
+ private static final Color CIRCLE_BORDER_COLOR = new Color(199, 21, 133); // 鍦嗗舰杈规鑹� - 娣辩矇绾�
+ private static final Color POLYGON_FILL_COLOR = new Color(173, 216, 230, 120); // 澶氳竟褰㈠~鍏呰壊 - 娴呰摑
+ private static final Color POLYGON_BORDER_COLOR = new Color(25, 25, 112); // 澶氳竟褰㈣竟妗嗚壊 - 娣辫摑
+ private static final Color OBSTACLE_LABEL_COLOR = Color.BLACK;
+ private static final Color OBSTACLE_POINT_COLOR = Color.RED;
+
+ // 灏哄瀹氫箟
+ private static final double OBSTACLE_POINT_SIZE = 0.1; // 闅滅鐗╂帶鍒剁偣澶у皬锛堢背锛�
+ private static final float DEFAULT_BORDER_WIDTH = 1.0f;
+ private static final float SELECTED_BORDER_WIDTH = 2.5f;
+
+ /**
+ * 缁樺埗鍦板潡鐨勬墍鏈夐殰纰嶇墿
+ *
+ * @param g2d 鍥惧舰涓婁笅鏂�
+ * @param obstacles 闅滅鐗╁垪琛�
+ * @param scale 缂╂斁姣斾緥
+ * @param selectedObstacleName 閫変腑鐨勯殰纰嶇墿鍚嶇О锛堝彲涓簄ull锛�
+ */
+ public static void drawObstacles(Graphics2D g2d, List<Obstacledge.Obstacle> obstacles,
+ double scale, String selectedObstacleName) {
+ if (obstacles == null || obstacles.isEmpty()) {
+ return;
+ }
+
+ // 淇濆瓨鍘熷鍙樻崲
+ AffineTransform originalTransform = g2d.getTransform();
+
+ // 搴旂敤鍙嶉敮榻�
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // 缁樺埗姣忎釜闅滅鐗�
+ for (Obstacledge.Obstacle obstacle : obstacles) {
+ if (obstacle == null || !obstacle.isValid()) {
+ continue;
+ }
+
+ // 妫�鏌ユ槸鍚﹁閫変腑
+ boolean isSelected = selectedObstacleName != null &&
+ selectedObstacleName.equals(obstacle.getObstacleName());
+
+ // 鏍规嵁褰㈢姸缁樺埗
+ switch (obstacle.getShape()) {
+ case CIRCLE:
+ drawCircleObstacle(g2d, obstacle, scale, isSelected);
+ break;
+ case POLYGON:
+ drawPolygonObstacle(g2d, obstacle, scale, isSelected);
+ break;
+ }
+
+ // 缁樺埗闅滅鐗╂爣绛�
+ drawObstacleLabel(g2d, obstacle, scale);
+ }
+
+ // 鎭㈠鍘熷鍙樻崲
+ g2d.setTransform(originalTransform);
+ }
+
+ /**
+ * 缁樺埗鍦嗗舰闅滅鐗�
+ */
+ private static void drawCircleObstacle(Graphics2D g2d, Obstacledge.Obstacle obstacle,
+ double scale, boolean isSelected) {
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.size() < 2) {
+ return;
+ }
+
+ // 鑾峰彇鍦嗗績鍜屽渾涓婁竴鐐�
+ Obstacledge.XYCoordinate center = xyCoords.get(0);
+ Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1);
+
+ // 璁$畻鍗婂緞
+ double dx = pointOnCircle.getX() - center.getX();
+ double dy = pointOnCircle.getY() - center.getY();
+ double radius = Math.sqrt(dx * dx + dy * dy);
+
+ if (radius <= 0) {
+ return;
+ }
+
+ // 璁$畻缁樺埗浣嶇疆
+ double x = center.getX() - radius;
+ double y = center.getY() - radius;
+ double diameter = radius * 2;
+
+ // 缁樺埗鍦嗗舰
+ Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter);
+
+ // 璁剧疆濉厖棰滆壊
+ g2d.setColor(CIRCLE_FILL_COLOR);
+ g2d.fill(circle);
+
+ // 璁剧疆杈规棰滆壊鍜屽搴�
+ if (isSelected) {
+ g2d.setColor(CIRCLE_BORDER_COLOR.darker());
+ g2d.setStroke(new BasicStroke(SELECTED_BORDER_WIDTH / (float)scale));
+ } else {
+ g2d.setColor(CIRCLE_BORDER_COLOR);
+ g2d.setStroke(new BasicStroke(DEFAULT_BORDER_WIDTH / (float)scale));
+ }
+ g2d.draw(circle);
+
+ // 缁樺埗鍦嗗績鍜屾帶鍒剁偣
+ drawControlPoints(g2d, obstacle, scale, isSelected);
+ }
+
+ /**
+ * 缁樺埗澶氳竟褰㈤殰纰嶇墿
+ */
+ private static void drawPolygonObstacle(Graphics2D g2d, Obstacledge.Obstacle obstacle,
+ double scale, boolean isSelected) {
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.size() < 3) {
+ return;
+ }
+
+ // 鍒涘缓澶氳竟褰㈣矾寰�
+ Path2D.Double polygon = new Path2D.Double();
+
+ // 绉诲姩鍒扮涓�涓偣
+ Obstacledge.XYCoordinate firstCoord = xyCoords.get(0);
+ polygon.moveTo(firstCoord.getX(), firstCoord.getY());
+
+ // 娣诲姞鍏朵粬鐐�
+ for (int i = 1; i < xyCoords.size(); i++) {
+ Obstacledge.XYCoordinate coord = xyCoords.get(i);
+ polygon.lineTo(coord.getX(), coord.getY());
+ }
+
+ // 闂悎澶氳竟褰�
+ polygon.closePath();
+
+ // 璁剧疆濉厖棰滆壊
+ g2d.setColor(POLYGON_FILL_COLOR);
+ g2d.fill(polygon);
+
+ // 璁剧疆杈规棰滆壊鍜屽搴�
+ if (isSelected) {
+ g2d.setColor(POLYGON_BORDER_COLOR.darker());
+ g2d.setStroke(new BasicStroke(SELECTED_BORDER_WIDTH / (float)scale));
+ } else {
+ g2d.setColor(POLYGON_BORDER_COLOR);
+ g2d.setStroke(new BasicStroke(DEFAULT_BORDER_WIDTH / (float)scale));
+ }
+ g2d.draw(polygon);
+
+ // 缁樺埗鎺у埗鐐�
+ drawControlPoints(g2d, obstacle, scale, isSelected);
+ }
+
+ /**
+ * 缁樺埗闅滅鐗╂帶鍒剁偣
+ */
+ private static void drawControlPoints(Graphics2D g2d, Obstacledge.Obstacle obstacle,
+ double scale, boolean isSelected) {
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.isEmpty()) {
+ return;
+ }
+
+ // 璁剧疆鎺у埗鐐归鑹插拰澶у皬
+ g2d.setColor(OBSTACLE_POINT_COLOR);
+ double pointSize = OBSTACLE_POINT_SIZE;
+
+ // 濡傛灉鏄�変腑鐨勯殰纰嶇墿锛岀粯鍒舵墍鏈夋帶鍒剁偣
+ if (isSelected) {
+ for (Obstacledge.XYCoordinate coord : xyCoords) {
+ double x = coord.getX() - pointSize / 2;
+ double y = coord.getY() - pointSize / 2;
+ Ellipse2D.Double point = new Ellipse2D.Double(x, y, pointSize, pointSize);
+ g2d.fill(point);
+ }
+ }
+ // 鍚﹀垯锛屽彧缁樺埗绗竴涓帶鍒剁偣锛堝渾褰㈤殰纰嶇墿涓哄渾蹇冿紝澶氳竟褰㈤殰纰嶇墿涓虹涓�涓《鐐癸級
+ else {
+ Obstacledge.XYCoordinate firstCoord = xyCoords.get(0);
+ double x = firstCoord.getX() - pointSize / 2;
+ double y = firstCoord.getY() - pointSize / 2;
+ Ellipse2D.Double point = new Ellipse2D.Double(x, y, pointSize, pointSize);
+ g2d.fill(point);
+ }
+ }
+
+ /**
+ * 缁樺埗闅滅鐗╂爣绛�
+ */
+ private static void drawObstacleLabel(Graphics2D g2d, Obstacledge.Obstacle obstacle,
+ double scale) {
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.isEmpty()) {
+ return;
+ }
+
+ // 璁$畻鏍囩浣嶇疆锛堜腑蹇冪偣锛�
+ double centerX = 0;
+ double centerY = 0;
+
+ for (Obstacledge.XYCoordinate coord : xyCoords) {
+ centerX += coord.getX();
+ centerY += coord.getY();
+ }
+
+ centerX /= xyCoords.size();
+ centerY /= xyCoords.size();
+
+ // 鑾峰彇闅滅鐗╁悕绉板拰褰㈢姸
+ String obstacleName = obstacle.getObstacleName();
+ String shapeDesc = obstacle.getShape().getDescription();
+
+ // 璁剧疆瀛椾綋鍜岄鑹�
+ g2d.setColor(OBSTACLE_LABEL_COLOR);
+
+ // 鏍规嵁缂╂斁姣斾緥璋冩暣瀛椾綋澶у皬
+ int fontSize = (int)(10 / scale);
+ fontSize = Math.max(8, Math.min(fontSize, 14)); // 闄愬埗瀛椾綋澶у皬鑼冨洿
+
+ g2d.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, fontSize));
+
+ // 缁樺埗鏍囩
+ String label = String.format("%s(%s)", obstacleName, shapeDesc);
+ FontMetrics metrics = g2d.getFontMetrics();
+ int textWidth = metrics.stringWidth(label);
+ int textHeight = metrics.getHeight();
+
+ // 鍦ㄤ腑蹇冪偣缁樺埗鏍囩
+ int textX = (int)(centerX - textWidth / 2.0);
+ int textY = (int)(centerY + textHeight / 4.0); // 绋嶅井鍚戜笅鍋忕Щ
+
+ g2d.drawString(label, textX, textY);
+ }
+
+ /**
+ * 妫�鏌ョ偣鏄惁鍦ㄩ殰纰嶇墿鍐�
+ *
+ * @param worldPoint 涓栫晫鍧愭爣鐐�
+ * @param obstacle 闅滅鐗�
+ * @return 濡傛灉鐐瑰湪闅滅鐗╁唴杩斿洖true锛屽惁鍒欒繑鍥瀎alse
+ */
+ public static boolean isPointInObstacle(Point2D.Double worldPoint, Obstacledge.Obstacle obstacle) {
+ if (worldPoint == null || obstacle == null || !obstacle.isValid()) {
+ return false;
+ }
+
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.isEmpty()) {
+ return false;
+ }
+
+ // 鏍规嵁闅滅鐗╁舰鐘惰繘琛屽垽鏂�
+ switch (obstacle.getShape()) {
+ case CIRCLE:
+ return isPointInCircleObstacle(worldPoint, xyCoords);
+ case POLYGON:
+ return isPointInPolygonObstacle(worldPoint, xyCoords);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * 妫�鏌ョ偣鏄惁鍦ㄥ渾褰㈤殰纰嶇墿鍐�
+ */
+ private static boolean isPointInCircleObstacle(Point2D.Double worldPoint,
+ List<Obstacledge.XYCoordinate> xyCoords) {
+ if (xyCoords.size() < 2) {
+ return false;
+ }
+
+ // 鍦嗗績
+ Obstacledge.XYCoordinate center = xyCoords.get(0);
+
+ // 鍦嗕笂涓�鐐�
+ Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1);
+
+ // 璁$畻鍗婂緞
+ double dx = pointOnCircle.getX() - center.getX();
+ double dy = pointOnCircle.getY() - center.getY();
+ double radius = Math.sqrt(dx * dx + dy * dy);
+
+ // 璁$畻鐐瑰埌鍦嗗績鐨勮窛绂�
+ double pointDx = worldPoint.x - center.getX();
+ double pointDy = worldPoint.y - center.getY();
+ double distance = Math.sqrt(pointDx * pointDx + pointDy * pointDy);
+
+ return distance <= radius;
+ }
+
+ /**
+ * 妫�鏌ョ偣鏄惁鍦ㄥ杈瑰舰闅滅鐗╁唴
+ */
+ private static boolean isPointInPolygonObstacle(Point2D.Double worldPoint,
+ List<Obstacledge.XYCoordinate> xyCoords) {
+ if (xyCoords.size() < 3) {
+ return false;
+ }
+
+ // 浣跨敤灏勭嚎娉曞垽鏂偣鏄惁鍦ㄥ杈瑰舰鍐�
+ boolean inside = false;
+ int n = xyCoords.size();
+
+ for (int i = 0, j = n - 1; i < n; j = i++) {
+ double xi = xyCoords.get(i).getX();
+ double yi = xyCoords.get(i).getY();
+ double xj = xyCoords.get(j).getX();
+ double yj = xyCoords.get(j).getY();
+
+ boolean intersect = ((yi > worldPoint.y) != (yj > worldPoint.y)) &&
+ (worldPoint.x < (xj - xi) * (worldPoint.y - yi) / (yj - yi) + xi);
+
+ if (intersect) {
+ inside = !inside;
+ }
+ }
+
+ return inside;
+ }
+
+ /**
+ * 妫�鏌ョ偣鏄惁鍦ㄩ殰纰嶇墿鐨勬帶鍒剁偣涓�
+ *
+ * @param worldPoint 涓栫晫鍧愭爣鐐�
+ * @param obstacle 闅滅鐗�
+ * @param scale 缂╂斁姣斾緥
+ * @return 鎺у埗鐐圭储寮曪紙浠�0寮�濮嬶級锛屽鏋滀笉鍦ㄦ帶鍒剁偣涓婅繑鍥�-1
+ */
+ public static int getControlPointIndex(Point2D.Double worldPoint,
+ Obstacledge.Obstacle obstacle,
+ double scale) {
+ if (worldPoint == null || obstacle == null || !obstacle.isValid()) {
+ return -1;
+ }
+
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.isEmpty()) {
+ return -1;
+ }
+
+ // 鏍规嵁闅滅鐗╁舰鐘惰绠楅�夋嫨闃堝��
+ double selectionThreshold = OBSTACLE_POINT_SIZE * 2 / scale;
+
+ // 妫�鏌ユ瘡涓帶鍒剁偣
+ for (int i = 0; i < xyCoords.size(); i++) {
+ Obstacledge.XYCoordinate coord = xyCoords.get(i);
+
+ double dx = worldPoint.x - coord.getX();
+ double dy = worldPoint.y - coord.getY();
+ double distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance <= selectionThreshold) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * 鑾峰彇闅滅鐗╃殑杈圭晫妗�
+ *
+ * @param obstacle 闅滅鐗�
+ * @return 杈圭晫妗嗭紙minX, minY, maxX, maxY锛夛紝濡傛灉鏃犳晥杩斿洖null
+ */
+ public static double[] getObstacleBounds(Obstacledge.Obstacle obstacle) {
+ if (obstacle == null || !obstacle.isValid()) {
+ return null;
+ }
+
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.isEmpty()) {
+ return null;
+ }
+
+ double minX = Double.MAX_VALUE;
+ double minY = Double.MAX_VALUE;
+ double maxX = -Double.MAX_VALUE;
+ double maxY = -Double.MAX_VALUE;
+
+ // 鏍规嵁闅滅鐗╁舰鐘惰绠楄竟鐣�
+ switch (obstacle.getShape()) {
+ case CIRCLE:
+ return getCircleObstacleBounds(xyCoords);
+ case POLYGON:
+ return getPolygonObstacleBounds(xyCoords);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇鍦嗗舰闅滅鐗╃殑杈圭晫妗�
+ */
+ private static double[] getCircleObstacleBounds(List<Obstacledge.XYCoordinate> xyCoords) {
+ if (xyCoords.size() < 2) {
+ return null;
+ }
+
+ // 鍦嗗績
+ Obstacledge.XYCoordinate center = xyCoords.get(0);
+
+ // 鍦嗕笂涓�鐐�
+ Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1);
+
+ // 璁$畻鍗婂緞
+ double dx = pointOnCircle.getX() - center.getX();
+ double dy = pointOnCircle.getY() - center.getY();
+ double radius = Math.sqrt(dx * dx + dy * dy);
+
+ // 璁$畻杈圭晫
+ double minX = center.getX() - radius;
+ double minY = center.getY() - radius;
+ double maxX = center.getX() + radius;
+ double maxY = center.getY() + radius;
+
+ return new double[]{minX, minY, maxX, maxY};
+ }
+
+ /**
+ * 鑾峰彇澶氳竟褰㈤殰纰嶇墿鐨勮竟鐣屾
+ */
+ private static double[] getPolygonObstacleBounds(List<Obstacledge.XYCoordinate> xyCoords) {
+ if (xyCoords.size() < 3) {
+ return null;
+ }
+
+ double minX = Double.MAX_VALUE;
+ double minY = Double.MAX_VALUE;
+ double maxX = -Double.MAX_VALUE;
+ double maxY = -Double.MAX_VALUE;
+
+ for (Obstacledge.XYCoordinate coord : xyCoords) {
+ minX = Math.min(minX, coord.getX());
+ minY = Math.min(minY, coord.getY());
+ maxX = Math.max(maxX, coord.getX());
+ maxY = Math.max(maxY, coord.getY());
+ }
+
+ return new double[]{minX, minY, maxX, maxY};
+ }
+
+ /**
+ * 灏嗛殰纰嶇墿鍒楄〃杞崲涓虹粯鍥剧敤鐨勭偣鍒楄〃
+ *
+ * @param obstacles 闅滅鐗╁垪琛�
+ * @return 鎵�鏈夐殰纰嶇墿鎺у埗鐐圭殑鍒楄〃
+ */
+ public static List<Point2D.Double> getObstaclePoints(List<Obstacledge.Obstacle> obstacles) {
+ List<Point2D.Double> points = new ArrayList<>();
+
+ if (obstacles == null || obstacles.isEmpty()) {
+ return points;
+ }
+
+ for (Obstacledge.Obstacle obstacle : obstacles) {
+ if (obstacle == null || !obstacle.isValid()) {
+ continue;
+ }
+
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ for (Obstacledge.XYCoordinate coord : xyCoords) {
+ points.add(new Point2D.Double(coord.getX(), coord.getY()));
+ }
+ }
+
+ return points;
+ }
+
+ /**
+ * 璁$畻鎵�鏈夐殰纰嶇墿鐨勬�昏竟鐣屾
+ *
+ * @param obstacles 闅滅鐗╁垪琛�
+ * @return 杈圭晫妗嗭紙minX, minY, maxX, maxY锛夛紝濡傛灉娌℃湁闅滅鐗╄繑鍥瀗ull
+ */
+ public static double[] getAllObstaclesBounds(List<Obstacledge.Obstacle> obstacles) {
+ if (obstacles == null || obstacles.isEmpty()) {
+ return null;
+ }
+
+ double minX = Double.MAX_VALUE;
+ double minY = Double.MAX_VALUE;
+ double maxX = -Double.MAX_VALUE;
+ double maxY = -Double.MAX_VALUE;
+ boolean hasBounds = false;
+
+ for (Obstacledge.Obstacle obstacle : obstacles) {
+ double[] bounds = getObstacleBounds(obstacle);
+ if (bounds != null) {
+ minX = Math.min(minX, bounds[0]);
+ minY = Math.min(minY, bounds[1]);
+ maxX = Math.max(maxX, bounds[2]);
+ maxY = Math.max(maxY, bounds[3]);
+ hasBounds = true;
+ }
+ }
+
+ if (!hasBounds) {
+ return null;
+ }
+
+ return new double[]{minX, minY, maxX, maxY};
+ }
+
+ /**
+ * 缁樺埗闅滅鐗╃紪杈戞ā寮忎笅鐨勮緟鍔╃嚎
+ *
+ * @param g2d 鍥惧舰涓婁笅鏂�
+ * @param obstacle 闅滅鐗�
+ * @param scale 缂╂斁姣斾緥
+ * @param isDragging 鏄惁姝e湪鎷栨嫿
+ * @param dragPointIndex 鎷栨嫿鐨勬帶鍒剁偣绱㈠紩
+ */
+ public static void drawEditingGuide(Graphics2D g2d, Obstacledge.Obstacle obstacle,
+ double scale, boolean isDragging, int dragPointIndex) {
+ if (obstacle == null || !obstacle.isValid()) {
+ return;
+ }
+
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ if (xyCoords.isEmpty()) {
+ return;
+ }
+
+ // 璁剧疆杈呭姪绾挎牱寮�
+ g2d.setColor(new Color(0, 100, 0, 150)); // 鍗婇�忔槑鐨勬繁缁胯壊
+ g2d.setStroke(new BasicStroke(1.0f / (float)scale,
+ BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND,
+ 1.0f,
+ new float[]{5.0f / (float)scale, 5.0f / (float)scale},
+ 0.0f));
+
+ // 鏍规嵁闅滅鐗╁舰鐘剁粯鍒惰緟鍔╃嚎
+ switch (obstacle.getShape()) {
+ case CIRCLE:
+ drawCircleEditingGuide(g2d, xyCoords, isDragging, dragPointIndex);
+ break;
+ case POLYGON:
+ drawPolygonEditingGuide(g2d, xyCoords, isDragging, dragPointIndex);
+ break;
+ }
+ }
+
+ /**
+ * 缁樺埗鍦嗗舰闅滅鐗╃紪杈戞ā寮忎笅鐨勮緟鍔╃嚎
+ */
+ private static void drawCircleEditingGuide(Graphics2D g2d,
+ List<Obstacledge.XYCoordinate> xyCoords,
+ boolean isDragging, int dragPointIndex) {
+ if (xyCoords.size() < 2) {
+ return;
+ }
+
+ Obstacledge.XYCoordinate center = xyCoords.get(0);
+ Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1);
+
+ // 缁樺埗鍗婂緞绾�
+ g2d.drawLine((int)center.getX(), (int)center.getY(),
+ (int)pointOnCircle.getX(), (int)pointOnCircle.getY());
+
+ // 缁樺埗涓績鍗佸瓧绾�
+ int crossSize = 5;
+ g2d.drawLine((int)center.getX() - crossSize, (int)center.getY(),
+ (int)center.getX() + crossSize, (int)center.getY());
+ g2d.drawLine((int)center.getX(), (int)center.getY() - crossSize,
+ (int)center.getX(), (int)center.getY() + crossSize);
+
+ // 濡傛灉姝e湪鎷栨嫿鎺у埗鐐癸紝缁樺埗鎷栨嫿绾�
+ if (isDragging && dragPointIndex >= 0 && dragPointIndex < xyCoords.size()) {
+ g2d.setColor(Color.RED);
+ g2d.setStroke(new BasicStroke(2.0f));
+
+ Obstacledge.XYCoordinate draggedPoint = xyCoords.get(dragPointIndex);
+
+ // 缁樺埗浠庢帶鍒剁偣鍒伴紶鏍囩殑杩炵嚎
+ // 杩欓噷闇�瑕佸閮ㄤ紶鍏ラ紶鏍囦綅缃紝鏆傛椂涓嶅疄鐜�
+ }
+ }
+
+ /**
+ * 缁樺埗澶氳竟褰㈤殰纰嶇墿缂栬緫妯″紡涓嬬殑杈呭姪绾�
+ */
+ private static void drawPolygonEditingGuide(Graphics2D g2d,
+ List<Obstacledge.XYCoordinate> xyCoords,
+ boolean isDragging, int dragPointIndex) {
+ if (xyCoords.size() < 3) {
+ return;
+ }
+
+ // 缁樺埗椤剁偣涔嬮棿鐨勮緟鍔╃嚎
+ int n = xyCoords.size();
+ for (int i = 0; i < n; i++) {
+ Obstacledge.XYCoordinate current = xyCoords.get(i);
+ Obstacledge.XYCoordinate next = xyCoords.get((i + 1) % n);
+
+ g2d.drawLine((int)current.getX(), (int)current.getY(),
+ (int)next.getX(), (int)next.getY());
+
+ // 缁樺埗姣忎釜椤剁偣鐨勫崄瀛楃嚎
+ int crossSize = 3;
+ g2d.drawLine((int)current.getX() - crossSize, (int)current.getY(),
+ (int)current.getX() + crossSize, (int)current.getY());
+ g2d.drawLine((int)current.getX(), (int)current.getY() - crossSize,
+ (int)current.getX(), (int)current.getY() + crossSize);
+ }
+
+ // 濡傛灉姝e湪鎷栨嫿鎺у埗鐐癸紝缁樺埗鎷栨嫿绾�
+ if (isDragging && dragPointIndex >= 0 && dragPointIndex < xyCoords.size()) {
+ g2d.setColor(Color.RED);
+ g2d.setStroke(new BasicStroke(2.0f));
+
+ Obstacledge.XYCoordinate draggedPoint = xyCoords.get(dragPointIndex);
+
+ // 缁樺埗浠庢帶鍒剁偣鍒伴紶鏍囩殑杩炵嚎
+ // 杩欓噷闇�瑕佸閮ㄤ紶鍏ラ紶鏍囦綅缃紝鏆傛椂涓嶅疄鐜�
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/zhuye/MapRenderer.java b/src/zhuye/MapRenderer.java
index 32049b4..aebc291 100644
--- a/src/zhuye/MapRenderer.java
+++ b/src/zhuye/MapRenderer.java
@@ -11,10 +11,13 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.Locale;
import gecaoji.Device;
import gecaoji.Gecaoji;
import dikuai.Dikuaiguanli;
import dikuai.Dikuai;
+import zhangaiwu.Obstacledraw;
+import zhangaiwu.Obstacledge;
/**
* 鍦板浘娓叉煋鍣� - 璐熻矗鍧愭爣绯荤粯鍒躲�佽鍥惧彉鎹㈢瓑鍔熻兘
@@ -43,6 +46,9 @@
private Rectangle2D.Double boundaryBounds;
private List<Point2D.Double> currentPlannedPath;
private Rectangle2D.Double plannedPathBounds;
+ private List<Obstacledge.Obstacle> currentObstacles;
+ private Rectangle2D.Double obstacleBounds;
+ private String selectedObstacleName;
private String boundaryName;
private boolean boundaryPointsVisible;
private String currentBoundaryLandNumber;
@@ -202,11 +208,16 @@
boolean hasBoundary = currentBoundary != null && currentBoundary.size() >= 2;
boolean hasPlannedPath = currentPlannedPath != null && currentPlannedPath.size() >= 2;
+ boolean hasObstacles = currentObstacles != null && !currentObstacles.isEmpty();
if (hasBoundary) {
drawCurrentBoundary(g2d);
}
+ if (hasObstacles) {
+ Obstacledraw.drawObstacles(g2d, currentObstacles, scale, selectedObstacleName);
+ }
+
if (hasPlannedPath) {
drawCurrentPlannedPath(g2d);
}
@@ -759,6 +770,264 @@
currentBoundaryLandNumber = null;
}
+ public void setCurrentObstacles(String obstaclesData, String landNumber) {
+ if (landNumber == null) {
+ clearObstacleData();
+ if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) {
+ resetView();
+ } else {
+ visualizationPanel.repaint();
+ }
+ return;
+ }
+
+ List<Obstacledge.Obstacle> parsed = parseObstacles(obstaclesData, landNumber);
+ if (parsed.isEmpty()) {
+ clearObstacleData();
+ if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) {
+ resetView();
+ } else {
+ visualizationPanel.repaint();
+ }
+ return;
+ }
+
+ currentObstacles = parsed;
+ obstacleBounds = convertObstacleBounds(Obstacledraw.getAllObstaclesBounds(parsed));
+ selectedObstacleName = null;
+
+ if (!hasRenderableBoundary() && !hasRenderablePlannedPath() && obstacleBounds != null) {
+ Rectangle2D.Double bounds = obstacleBounds;
+ SwingUtilities.invokeLater(() -> {
+ fitBoundsToView(bounds);
+ visualizationPanel.repaint();
+ });
+ } else {
+ visualizationPanel.repaint();
+ }
+ }
+
+ private void clearObstacleData() {
+ currentObstacles = null;
+ obstacleBounds = null;
+ selectedObstacleName = null;
+ }
+
+ private List<Obstacledge.Obstacle> parseObstacles(String obstaclesData, String landNumber) {
+ List<Obstacledge.Obstacle> obstacles = new ArrayList<>();
+ if (obstaclesData == null) {
+ return obstacles;
+ }
+
+ String normalized = stripInlineComment(obstaclesData.trim());
+ if (normalized.isEmpty() || "-1".equals(normalized)) {
+ return obstacles;
+ }
+
+ List<String> entries = splitObstacleEntries(normalized);
+ int defaultIndex = 1;
+
+ for (String entry : entries) {
+ String trimmedEntry = stripInlineComment(entry);
+ if (trimmedEntry.isEmpty()) {
+ continue;
+ }
+
+ String nameToken = null;
+ String shapeToken = null;
+ String coordsSection = trimmedEntry;
+
+ if (trimmedEntry.contains("::")) {
+ String[] parts = trimmedEntry.split("::", 3);
+ if (parts.length == 3) {
+ nameToken = parts[0].trim();
+ shapeToken = parts[1].trim();
+ coordsSection = parts[2].trim();
+ }
+ } else if (trimmedEntry.contains("@")) {
+ String[] parts = trimmedEntry.split("@", 3);
+ if (parts.length == 3) {
+ nameToken = parts[0].trim();
+ shapeToken = parts[1].trim();
+ coordsSection = parts[2].trim();
+ } else if (parts.length == 2) {
+ shapeToken = parts[0].trim();
+ coordsSection = parts[1].trim();
+ }
+ } else if (trimmedEntry.contains(":")) {
+ String[] parts = trimmedEntry.split(":", 3);
+ if (parts.length == 3) {
+ nameToken = parts[0].trim();
+ shapeToken = parts[1].trim();
+ coordsSection = parts[2].trim();
+ } else if (parts.length == 2) {
+ if (looksLikeShapeToken(parts[0])) {
+ shapeToken = parts[0].trim();
+ coordsSection = parts[1].trim();
+ } else {
+ nameToken = parts[0].trim();
+ coordsSection = parts[1].trim();
+ }
+ }
+ }
+
+ List<Obstacledge.XYCoordinate> xyCoordinates = parseObstacleCoordinates(coordsSection);
+ if (xyCoordinates.size() < 2) {
+ continue;
+ }
+
+ Obstacledge.ObstacleShape shape = resolveObstacleShape(shapeToken, xyCoordinates.size());
+ if (shape == null) {
+ continue;
+ }
+
+ String obstacleName = (nameToken != null && !nameToken.isEmpty())
+ ? nameToken
+ : "闅滅鐗�" + defaultIndex++;
+
+ Obstacledge.Obstacle obstacle = new Obstacledge.Obstacle(landNumber, obstacleName, shape);
+ obstacle.setXyCoordinates(new ArrayList<>(xyCoordinates));
+ populateDummyOriginalCoordinates(obstacle, xyCoordinates.size());
+
+ if (obstacle.isValid()) {
+ obstacles.add(obstacle);
+ }
+ }
+
+ return obstacles;
+ }
+
+ 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 List<Obstacledge.XYCoordinate> parseObstacleCoordinates(String coordsSection) {
+ List<Obstacledge.XYCoordinate> coords = new ArrayList<>();
+ if (coordsSection == null) {
+ return coords;
+ }
+
+ String sanitized = stripInlineComment(coordsSection.trim());
+ if (sanitized.isEmpty() || "-1".equals(sanitized)) {
+ return coords;
+ }
+
+ String[] pairs = sanitized.split(";");
+ for (String pair : pairs) {
+ if (pair == null) {
+ continue;
+ }
+ String trimmed = stripInlineComment(pair.trim());
+ if (trimmed.isEmpty()) {
+ continue;
+ }
+ String[] parts = trimmed.split(",");
+ if (parts.length < 2) {
+ continue;
+ }
+ try {
+ double x = Double.parseDouble(parts[0].trim());
+ double y = Double.parseDouble(parts[1].trim());
+ coords.add(new Obstacledge.XYCoordinate(x, y));
+ } catch (NumberFormatException ignored) {
+ // Skip malformed coordinate pair
+ }
+ }
+
+ return coords;
+ }
+
+ private Obstacledge.ObstacleShape resolveObstacleShape(String shapeToken, int coordinateCount) {
+ if (shapeToken != null && !shapeToken.trim().isEmpty()) {
+ String normalized = shapeToken.trim().toLowerCase(Locale.ROOT);
+ if ("circle".equals(normalized) || "鍦嗗舰".equals(normalized) || "0".equals(normalized)) {
+ return Obstacledge.ObstacleShape.CIRCLE;
+ }
+ if ("polygon".equals(normalized) || "澶氳竟褰�".equals(normalized) || "1".equals(normalized)) {
+ return Obstacledge.ObstacleShape.POLYGON;
+ }
+ }
+
+ if (coordinateCount == 2) {
+ return Obstacledge.ObstacleShape.CIRCLE;
+ }
+ if (coordinateCount >= 3) {
+ return Obstacledge.ObstacleShape.POLYGON;
+ }
+ return null;
+ }
+
+ private void populateDummyOriginalCoordinates(Obstacledge.Obstacle obstacle, int xyCount) {
+ List<Obstacledge.DMCoordinate> dmCoordinates = new ArrayList<>();
+ int points = Math.max(1, xyCount);
+ for (int i = 0; i < points; i++) {
+ dmCoordinates.add(new Obstacledge.DMCoordinate(0.0, 'N'));
+ dmCoordinates.add(new Obstacledge.DMCoordinate(0.0, 'E'));
+ }
+ obstacle.setOriginalCoordinates(dmCoordinates);
+ }
+
+ 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 Rectangle2D.Double convertObstacleBounds(double[] bounds) {
+ if (bounds == null || bounds.length < 4) {
+ return null;
+ }
+ double minX = bounds[0];
+ double minY = bounds[1];
+ double maxX = bounds[2];
+ double maxY = bounds[3];
+ return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
+ }
+
+ private boolean hasRenderableBoundary() {
+ return currentBoundary != null && currentBoundary.size() >= 2;
+ }
+
+ private boolean hasRenderablePlannedPath() {
+ return currentPlannedPath != null && currentPlannedPath.size() >= 2;
+ }
+
private void adjustViewAfterBoundaryReset() {
if (plannedPathBounds != null) {
Rectangle2D.Double bounds = plannedPathBounds;
@@ -766,16 +1035,26 @@
fitBoundsToView(bounds);
visualizationPanel.repaint();
});
- } else {
- resetView();
+ return;
}
+
+ if (obstacleBounds != null) {
+ Rectangle2D.Double bounds = obstacleBounds;
+ SwingUtilities.invokeLater(() -> {
+ fitBoundsToView(bounds);
+ visualizationPanel.repaint();
+ });
+ return;
+ }
+
+ resetView();
}
public void setCurrentPlannedPath(String plannedPath) {
if (plannedPath == null) {
currentPlannedPath = null;
plannedPathBounds = null;
- if (currentBoundary == null || currentBoundary.size() < 2) {
+ if (!hasRenderableBoundary()) {
resetView();
} else {
visualizationPanel.repaint();
@@ -787,7 +1066,7 @@
if (parsed.size() < 2) {
currentPlannedPath = null;
plannedPathBounds = null;
- if (currentBoundary == null || currentBoundary.size() < 2) {
+ if (!hasRenderableBoundary()) {
resetView();
} else {
visualizationPanel.repaint();
@@ -800,7 +1079,7 @@
Rectangle2D.Double bounds = plannedPathBounds;
SwingUtilities.invokeLater(() -> {
- if (currentBoundary == null || currentBoundary.size() < 2) {
+ if (!hasRenderableBoundary()) {
fitBoundsToView(bounds);
}
visualizationPanel.repaint();
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index 5a0d4be..14565cb 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -10,6 +10,7 @@
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
+import dikuai.addzhangaiwu;
import gecaoji.Device;
import set.Sets;
import udpdell.UDPServer;
@@ -75,6 +76,14 @@
private ImageIcon pauseIcon;
private ImageIcon pauseActiveIcon;
private ImageIcon endIcon;
+ private JPanel circleGuidancePanel;
+ private JLabel circleGuidanceLabel;
+ private JButton circleGuidancePrimaryButton;
+ private JButton circleGuidanceSecondaryButton;
+ private int circleGuidanceStep;
+ private JDialog circleGuidanceDialog;
+ private boolean circleDialogMode;
+ private ComponentAdapter circleDialogOwnerAdapter;
public Shouye() {
instance = this;
@@ -117,6 +126,7 @@
setNavigationActive(homeNavBtn);
initializeDefaultAreaSelection();
+ refreshMapForSelectedArea();
}
private void createHeaderPanel() {
@@ -250,8 +260,10 @@
JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 20, 0));
buttonPanel.setBackground(PANEL_BACKGROUND);
- startBtn = createControlButton("寮�濮�", THEME_COLOR);
- pauseBtn = createControlButton("鏆傚仠", Color.ORANGE);
+ startBtn = createControlButton("寮�濮�", THEME_COLOR);
+ applyButtonIcon(startBtn, "image/startzuoye.png");
+ pauseBtn = createControlButton("鏆傚仠", Color.ORANGE);
+ applyButtonIcon(pauseBtn, "image/zantingzuoye.png");
pauseBtn.setEnabled(false);
buttonPanel.add(startBtn);
@@ -349,6 +361,20 @@
return button;
}
+
+ private void applyButtonIcon(JButton button, String imagePath) {
+ try {
+ ImageIcon icon = new ImageIcon(imagePath);
+ Image scaledImage = icon.getImage().getScaledInstance(28, 28, Image.SCALE_SMOOTH);
+ button.setIcon(new ImageIcon(scaledImage));
+ button.setHorizontalAlignment(SwingConstants.CENTER);
+ button.setIconTextGap(10);
+ button.setHorizontalTextPosition(SwingConstants.RIGHT);
+ button.setVerticalTextPosition(SwingConstants.CENTER);
+ } catch (Exception e) {
+ System.err.println("鏃犳硶鍔犺浇鎸夐挳鍥炬爣: " + imagePath + " -> " + e.getMessage());
+ }
+ }
private JButton createNavButton(String text, String icon) {
JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
@@ -625,8 +651,45 @@
}
public void showEndDrawingButton(Runnable callback) {
- ensureFloatingIconsLoaded();
+ showEndDrawingButton(callback, null);
+ }
+ public void showEndDrawingButton(Runnable callback, String drawingShape) {
+ endDrawingCallback = callback;
+ applyDrawingPauseState(false, false);
+ circleDialogMode = drawingShape != null && "circle".equalsIgnoreCase(drawingShape);
+
+ if (circleDialogMode) {
+ hideFloatingDrawingControls();
+ ensureCircleGuidancePanel();
+ showCircleGuidanceStep(1);
+ visualizationPanel.revalidate();
+ visualizationPanel.repaint();
+ return;
+ }
+
+ circleDialogMode = false;
+ hideCircleGuidancePanel();
+
+ ensureFloatingIconsLoaded();
+ ensureFloatingButtonInfrastructure();
+
+ endDrawingButton.setVisible(true);
+ if (drawingPauseButton != null) {
+ drawingPauseButton.setVisible(true);
+ }
+
+ floatingButtonPanel.setVisible(true);
+ if (floatingButtonPanel.getParent() != visualizationPanel) {
+ visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
+ }
+
+ rebuildFloatingButtonColumn();
+ visualizationPanel.revalidate();
+ visualizationPanel.repaint();
+ }
+
+ private void ensureFloatingButtonInfrastructure() {
if (endDrawingButton == null) {
endDrawingButton = createFloatingIconButton();
endDrawingButton.addActionListener(e -> {
@@ -657,41 +720,288 @@
floatingButtonColumn = new JPanel();
floatingButtonColumn.setOpaque(false);
floatingButtonColumn.setLayout(new BoxLayout(floatingButtonColumn, BoxLayout.Y_AXIS));
- floatingButtonColumn.add(drawingPauseButton);
- floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
- floatingButtonColumn.add(endDrawingButton);
floatingButtonPanel.add(floatingButtonColumn, BorderLayout.EAST);
- } else if (floatingButtonColumn != null) {
- floatingButtonColumn.removeAll();
- floatingButtonColumn.add(drawingPauseButton);
- floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
- floatingButtonColumn.add(endDrawingButton);
}
-
- endDrawingCallback = callback;
- endDrawingButton.setVisible(true);
- if (drawingPauseButton != null) {
- drawingPauseButton.setVisible(true);
- }
- applyDrawingPauseState(false, false);
- floatingButtonPanel.setVisible(true);
- if (floatingButtonPanel.getParent() != visualizationPanel) {
- visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
- }
- visualizationPanel.revalidate();
- visualizationPanel.repaint();
}
- public void hideEndDrawingButton() {
- if (endDrawingButton != null) {
- endDrawingButton.setVisible(false);
- }
+ private void hideFloatingDrawingControls() {
if (drawingPauseButton != null) {
drawingPauseButton.setVisible(false);
}
+ if (endDrawingButton != null) {
+ endDrawingButton.setVisible(false);
+ }
if (floatingButtonPanel != null) {
floatingButtonPanel.setVisible(false);
}
+ if (!circleDialogMode) {
+ rebuildFloatingButtonColumn();
+ }
+ }
+
+ private void rebuildFloatingButtonColumn() {
+ if (floatingButtonColumn == null) {
+ return;
+ }
+ floatingButtonColumn.removeAll();
+ boolean added = false;
+ if (!circleDialogMode && circleGuidancePanel != null && circleGuidancePanel.isVisible()) {
+ floatingButtonColumn.add(circleGuidancePanel);
+ added = true;
+ }
+ if (!circleDialogMode && drawingPauseButton != null && drawingPauseButton.isVisible()) {
+ if (added) {
+ floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
+ }
+ floatingButtonColumn.add(drawingPauseButton);
+ added = true;
+ }
+ if (!circleDialogMode && endDrawingButton != null && endDrawingButton.isVisible()) {
+ if (added) {
+ floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
+ }
+ floatingButtonColumn.add(endDrawingButton);
+ }
+ floatingButtonColumn.revalidate();
+ floatingButtonColumn.repaint();
+ }
+
+ private void showCircleGuidanceStep(int step) {
+ ensureCircleGuidancePanel();
+ if (circleGuidancePanel == null) {
+ return;
+ }
+ circleGuidanceStep = step;
+ if (step == 1) {
+ circleGuidanceLabel.setText("鏄惁灏嗗綋鍓嶇偣璁剧疆涓哄渾蹇冿紵");
+ circleGuidancePrimaryButton.setText("鏄�");
+ circleGuidanceSecondaryButton.setText("杩斿洖");
+ circleGuidanceSecondaryButton.setVisible(true);
+ } else if (step == 2) {
+ circleGuidanceLabel.setText("鏄惁灏嗗綋鍓嶇偣璁剧疆涓哄崐寰勭偣锛�");
+ circleGuidancePrimaryButton.setText("鏄�");
+ circleGuidanceSecondaryButton.setVisible(false);
+ } else {
+ hideCircleGuidancePanel();
+ return;
+ }
+ circleGuidancePanel.setVisible(true);
+
+ if (circleDialogMode) {
+ ensureCircleGuidanceDialog();
+ if (circleGuidanceDialog != null) {
+ circleGuidanceDialog.pack();
+ positionCircleGuidanceDialog();
+ circleGuidanceDialog.setVisible(true);
+ circleGuidanceDialog.toFront();
+ }
+ } else {
+ rebuildFloatingButtonColumn();
+ }
+ }
+
+ private void ensureCircleGuidancePanel() {
+ if (circleGuidancePanel != null) {
+ return;
+ }
+ circleGuidancePanel = new JPanel();
+ circleGuidancePanel.setLayout(new BoxLayout(circleGuidancePanel, BoxLayout.Y_AXIS));
+ circleGuidancePanel.setOpaque(true);
+ circleGuidancePanel.setBackground(new Color(255, 255, 255, 235));
+ circleGuidancePanel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(THEME_COLOR, 1),
+ BorderFactory.createEmptyBorder(10, 12, 10, 12)));
+ circleGuidancePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ circleGuidanceLabel = new JLabel();
+ circleGuidanceLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 13));
+ circleGuidanceLabel.setForeground(new Color(33, 37, 41));
+ circleGuidanceLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ JPanel buttonRow = new JPanel();
+ buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.X_AXIS));
+ buttonRow.setOpaque(false);
+ buttonRow.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ circleGuidancePrimaryButton = createGuidanceButton("鏄�", THEME_COLOR, Color.WHITE, true);
+ circleGuidancePrimaryButton.addActionListener(e -> onCircleGuidancePrimary());
+
+ circleGuidanceSecondaryButton = createGuidanceButton("鍚�", Color.WHITE, THEME_COLOR, false);
+ circleGuidanceSecondaryButton.addActionListener(e -> onCircleGuidanceSecondary());
+
+ buttonRow.add(circleGuidancePrimaryButton);
+ buttonRow.add(Box.createRigidArea(new Dimension(8, 0)));
+ buttonRow.add(circleGuidanceSecondaryButton);
+
+ circleGuidancePanel.add(circleGuidanceLabel);
+ circleGuidancePanel.add(Box.createRigidArea(new Dimension(0, 8)));
+ circleGuidancePanel.add(buttonRow);
+ circleGuidancePanel.setVisible(false);
+ }
+
+ private void ensureCircleGuidanceDialog() {
+ if (circleGuidancePanel == null) {
+ return;
+ }
+ if (circleGuidanceDialog != null) {
+ if (circleGuidancePanel.getParent() != circleGuidanceDialog.getContentPane()) {
+ detachCircleGuidancePanel();
+ circleGuidanceDialog.getContentPane().removeAll();
+ circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
+ circleGuidanceDialog.pack();
+ }
+ return;
+ }
+
+ Window owner = SwingUtilities.getWindowAncestor(this);
+ circleGuidanceDialog = new JDialog(owner, "缁樺埗鎻愮ず", Dialog.ModalityType.MODELESS);
+ circleGuidanceDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
+ circleGuidanceDialog.setResizable(false);
+ circleGuidanceDialog.setAlwaysOnTop(true);
+
+ if (owner != null && circleDialogOwnerAdapter == null) {
+ circleDialogOwnerAdapter = new ComponentAdapter() {
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ positionCircleGuidanceDialog();
+ }
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ positionCircleGuidanceDialog();
+ }
+ };
+ owner.addComponentListener(circleDialogOwnerAdapter);
+ }
+
+ detachCircleGuidancePanel();
+ circleGuidanceDialog.getContentPane().setLayout(new BorderLayout());
+ circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
+ circleGuidanceDialog.pack();
+ }
+
+ private void detachCircleGuidancePanel() {
+ if (circleGuidancePanel == null) {
+ return;
+ }
+ Container parent = circleGuidancePanel.getParent();
+ if (parent != null) {
+ parent.remove(circleGuidancePanel);
+ parent.revalidate();
+ parent.repaint();
+ }
+ }
+
+ private void positionCircleGuidanceDialog() {
+ if (circleGuidanceDialog == null) {
+ return;
+ }
+ Window owner = SwingUtilities.getWindowAncestor(this);
+ if (owner == null || !owner.isShowing()) {
+ return;
+ }
+ try {
+ Point ownerLocation = owner.getLocationOnScreen();
+ int x = ownerLocation.x + owner.getWidth() - circleGuidanceDialog.getWidth() - 30;
+ int y = ownerLocation.y + owner.getHeight() - circleGuidanceDialog.getHeight() - 40;
+ x = Math.max(ownerLocation.x, x);
+ y = Math.max(ownerLocation.y, y);
+ circleGuidanceDialog.setLocation(x, y);
+ } catch (IllegalComponentStateException ex) {
+ // Owner not yet displayable; skip positioning
+ }
+ }
+
+ private JButton createGuidanceButton(String text, Color bg, Color fg, boolean filled) {
+ JButton button = new JButton(text);
+ button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 12));
+ button.setForeground(fg);
+ button.setBackground(bg);
+ button.setOpaque(true);
+ button.setFocusPainted(false);
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ button.setAlignmentX(Component.LEFT_ALIGNMENT);
+ if (filled) {
+ button.setBorder(BorderFactory.createEmptyBorder(6, 14, 6, 14));
+ } else {
+ button.setBackground(Color.WHITE);
+ button.setOpaque(true);
+ button.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(THEME_COLOR, 1),
+ BorderFactory.createEmptyBorder(5, 12, 5, 12)));
+ }
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (filled) {
+ button.setBackground(THEME_HOVER_COLOR);
+ } else {
+ button.setBackground(new Color(245, 245, 245));
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (filled) {
+ button.setBackground(bg);
+ } else {
+ button.setBackground(Color.WHITE);
+ }
+ }
+ });
+ return button;
+ }
+
+ private void onCircleGuidancePrimary() {
+ if (circleGuidanceStep == 1) {
+ showCircleGuidanceStep(2);
+ } else {
+ handleCircleCompletion();
+ }
+ }
+
+ private void onCircleGuidanceSecondary() {
+ if (circleGuidanceStep == 1) {
+ handleCircleAbort(null);
+ } else if (circleGuidanceStep == 2) {
+ handleCircleAbort(null);
+ } else {
+ hideCircleGuidancePanel();
+ }
+ }
+
+ private void hideCircleGuidancePanel() {
+ circleGuidanceStep = 0;
+ if (circleGuidancePanel != null) {
+ circleGuidancePanel.setVisible(false);
+ }
+ if (circleGuidanceDialog != null) {
+ circleGuidanceDialog.setVisible(false);
+ }
+ if (!circleDialogMode) {
+ rebuildFloatingButtonColumn();
+ }
+ }
+
+ private void handleCircleAbort(String message) {
+ // Return the wizard to step 2 without committing any captured points
+ hideEndDrawingButton();
+ addzhangaiwu.abortCircleDrawingAndReturn(message);
+ }
+
+ private void handleCircleCompletion() {
+ hideCircleGuidancePanel();
+ if (endDrawingCallback != null) {
+ endDrawingCallback.run();
+ } else {
+ addzhangaiwu.finishDrawingSession();
+ }
+ }
+
+ public void hideEndDrawingButton() {
+ hideCircleGuidancePanel();
+ hideFloatingDrawingControls();
+ circleDialogMode = false;
applyDrawingPauseState(false, false);
endDrawingCallback = null;
visualizationPanel.revalidate();
@@ -738,6 +1048,59 @@
}
}
+ private void refreshMapForSelectedArea() {
+ if (mapRenderer == null) {
+ return;
+ }
+
+ String currentLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
+ if (isMeaningfulValue(currentLandNumber)) {
+ Dikuai current = Dikuai.getDikuai(currentLandNumber);
+ String landName = current != null ? current.getLandName() : null;
+ Dikuaiguanli.setCurrentWorkLand(currentLandNumber, landName);
+ return;
+ }
+
+ String labelName = areaNameLabel != null ? areaNameLabel.getText() : null;
+ if (!isMeaningfulAreaName(labelName)) {
+ Dikuaiguanli.setCurrentWorkLand(null, null);
+ return;
+ }
+
+ Map<String, Dikuai> all = Dikuai.getAllDikuai();
+ for (Dikuai dikuai : all.values()) {
+ if (dikuai == null) {
+ continue;
+ }
+ String candidateName = dikuai.getLandName();
+ if (candidateName != null && candidateName.trim().equals(labelName.trim())) {
+ Dikuaiguanli.setCurrentWorkLand(dikuai.getLandNumber(), candidateName);
+ return;
+ }
+ }
+
+ Dikuaiguanli.setCurrentWorkLand(null, null);
+ }
+
+ private boolean isMeaningfulValue(String value) {
+ if (value == null) {
+ return false;
+ }
+ String trimmed = value.trim();
+ return !trimmed.isEmpty() && !"-1".equals(trimmed);
+ }
+
+ private boolean isMeaningfulAreaName(String value) {
+ if (value == null) {
+ return false;
+ }
+ String trimmed = value.trim();
+ if (trimmed.isEmpty()) {
+ return false;
+ }
+ return !"鏈�夋嫨鍦板潡".equals(trimmed);
+ }
+
// 娴嬭瘯鏂规硶
public static void main(String[] args) {
JFrame frame = new JFrame("AutoMow - 棣栭〉");
--
Gitblit v1.10.0