From 6799351be12deb2f713f2c0a2b4c467a6d1098c3 Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期二, 02 十二月 2025 19:51:00 +0800
Subject: [PATCH] 2025122
---
dikuai.properties | 8
image/stop0.png | 0
src/dikuai/addzhangaiwu.java | 854 ++++++++++++++++-
src/zhuye/Bluelink.java | 34
image/start0.png | 0
src/zhuye/MapRenderer.java | 335 ++++++
set.properties | 5
src/udpdell/UDPServer.java | 69
image/blue.png | 0
src/zhuye/Shouye.java | 550 ++++++++++
src/zhuye/Coordinate.java | 46
Obstacledge.properties | 13
image/bluelink.png | 0
src/dikuai/Dikuaiguanli.java | 205 +++
image/stop1.png | 0
src/set/Sets.java | 260 +++++
src/zhangaiwu/Obstacledraw.java | 67 +
src/set/Setsys.java | 19
image/start1.png | 0
src/zhangaiwu/yulanzhangaiwu.java | 381 +++++++
20 files changed, 2,625 insertions(+), 221 deletions(-)
diff --git a/Obstacledge.properties b/Obstacledge.properties
index b45db39..ae24933 100644
--- a/Obstacledge.properties
+++ b/Obstacledge.properties
@@ -1,5 +1,5 @@
# 鍓茶崏鏈哄湴鍧楅殰纰嶇墿閰嶇疆鏂囦欢
-# 鐢熸垚鏃堕棿锛�2025-12-01T18:24:39.295276600
+# 鐢熸垚鏃堕棿锛�2025-12-02T18:52:30.885641500
# 鍧愭爣绯伙細WGS84锛堝害鍒嗘牸寮忥級
# ============ 鍦板潡鍩哄噯绔欓厤缃� ============
@@ -11,11 +11,8 @@
# 鏍煎紡锛歱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
+# --- 鍦板潡DK-001鐨勯殰纰嶇墿 ---
+plot.DK-001.obstacle.123.shape=1
+plot.DK-001.obstacle.123.originalCoords=3949.840734,N;11616.743246,E;3949.835186,N;11616.673827,E;3949.840311,N;11616.618736,E;3949.848849,N;11616.705131,E
+plot.DK-001.obstacle.123.xyCoords=-3.74,-0.68;-102.65,-10.97;-181.14,-1.46;-58.05,14.38
diff --git a/dikuai.properties b/dikuai.properties
index fe0c49d..579f23d 100644
--- a/dikuai.properties
+++ b/dikuai.properties
@@ -1,5 +1,5 @@
#Dikuai Properties
-#Sat Nov 29 13:40:33 CST 2025
+#Tue Dec 02 18:52:30 CST 2025
DK-001.angleThreshold=-1
DK-001.baseStationCoordinates=3949.84110064,N,11616.74587312,E
DK-001.boundaryCoordinates=0,0;0.45,-2.86;1.81,-6.44;21.93,-40.79;27.22,-50.65;31.66,-54.38;33.17,-54.12;46.44,-47.04;56.61,-40.86;57.42,-39.55;53.19,-31.57;32.88,2.52;31.55,4.1;29.14,4.33;0,0
@@ -12,9 +12,9 @@
DK-001.landNumber=DK-001
DK-001.mowingPattern=-1
DK-001.mowingWidth=150
-DK-001.obstacleCoordinates=-1
-DK-001.obstacleOriginalCoordinates=-1
+DK-001.obstacleCoordinates=-3.74,-0.68;-102.65,-10.97;-181.14,-1.46;-58.05,14.38
+DK-001.obstacleOriginalCoordinates=3949.8407343,N,11616.7432457,E;3949.8351859,N,11616.6738272,E;3949.8403113,N,11616.6187363,E;3949.8488492,N,11616.7051305,E
DK-001.plannedPath=1.17,-2.58;30.94,-53.42;30.94,-53.42;32.69,-53.44;32.69,-53.44;1.69,-0.51;1.69,-0.51;3.29,-0.27;3.29,-0.27;34.06,-52.80;34.06,-52.80;35.38,-52.09;35.38,-52.09;4.89,-0.03;4.89,-0.03;6.49,0.21;6.49,0.21;36.70,-51.38;36.70,-51.38;38.03,-50.68;38.03,-50.68;8.09,0.44;8.09,0.44;9.68,0.68;9.68,0.68;39.35,-49.97;39.35,-49.97;40.68,-49.26;40.68,-49.26;11.28,0.92;11.28,0.92;12.88,1.16;12.88,1.16;42.00,-48.56;42.00,-48.56;43.33,-47.85;43.33,-47.85;14.48,1.39;14.48,1.39;16.08,1.63;16.08,1.63;44.65,-47.14;44.65,-47.14;45.98,-46.44;45.98,-46.44;17.68,1.87;17.68,1.87;19.28,2.11;19.28,2.11;47.26,-45.66;47.26,-45.66;48.54,-44.88;48.54,-44.88;20.88,2.34;20.88,2.34;22.48,2.58;22.48,2.58;49.82,-44.11;49.82,-44.11;51.11,-43.33;51.11,-43.33;24.08,2.82;24.08,2.82;25.68,3.06;25.68,3.06;52.39,-42.55;52.39,-42.55;53.67,-41.77;53.67,-41.77;27.28,3.29;27.28,3.29;28.87,3.53;28.87,3.53;54.95,-40.99;54.95,-40.99;56.18,-40.12;56.18,-40.12;30.67,3.43\# 瑙勫垝璺緞鍧愭爣
DK-001.returnPointCoordinates=-1
-DK-001.updateTime=2025-11-27 17\:26\:45
+DK-001.updateTime=2025-12-02 18\:52\:30
DK-001.userId=-1
diff --git a/image/blue.png b/image/blue.png
new file mode 100644
index 0000000..61a42e9
--- /dev/null
+++ b/image/blue.png
Binary files differ
diff --git a/image/bluelink.png b/image/bluelink.png
new file mode 100644
index 0000000..daf5994
--- /dev/null
+++ b/image/bluelink.png
Binary files differ
diff --git a/image/start0.png b/image/start0.png
new file mode 100644
index 0000000..224fda6
--- /dev/null
+++ b/image/start0.png
Binary files differ
diff --git a/image/start1.png b/image/start1.png
new file mode 100644
index 0000000..1884d18
--- /dev/null
+++ b/image/start1.png
Binary files differ
diff --git a/image/stop0.png b/image/stop0.png
new file mode 100644
index 0000000..35e9630
--- /dev/null
+++ b/image/stop0.png
Binary files differ
diff --git a/image/stop1.png b/image/stop1.png
new file mode 100644
index 0000000..28f85d7
--- /dev/null
+++ b/image/stop1.png
Binary files differ
diff --git a/set.properties b/set.properties
index 66e1db7..3eb7238 100644
--- a/set.properties
+++ b/set.properties
@@ -1,7 +1,8 @@
#Mower Configuration Properties - Updated
-#Sat Nov 29 14:05:32 CST 2025
+#Tue Dec 02 15:25:38 CST 2025
appVersion=-1
cuttingWidth=200
firmwareVersion=-1
-mowerId=1124
+handheldMarkerId=2354
+mowerId=7785
simCardNumber=-1
diff --git a/src/dikuai/Dikuaiguanli.java b/src/dikuai/Dikuaiguanli.java
index 94a3d1c..a01435e 100644
--- a/src/dikuai/Dikuaiguanli.java
+++ b/src/dikuai/Dikuaiguanli.java
@@ -15,6 +15,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
+import java.util.Collections;
import zhangaiwu.AddDikuai;
import zhangaiwu.Obstacledge;
@@ -61,6 +62,7 @@
private ImageIcon boundaryVisibleIcon;
private ImageIcon boundaryHiddenIcon;
private static final int BOUNDARY_TOGGLE_ICON_SIZE = 48;
+ private Map<String, ObstacleSummary> obstacleSummaryCache = Collections.emptyMap();
public Dikuaiguanli(String landNumber) {
latestInstance = this;
@@ -136,13 +138,15 @@
cardsPanel.removeAll();
Map<String, Dikuai> allDikuai = Dikuai.getAllDikuai();
-
+
if (allDikuai.isEmpty()) {
+ obstacleSummaryCache = Collections.emptyMap();
// 鏄剧ず绌虹姸鎬�
JPanel emptyPanel = createEmptyStatePanel();
cardsPanel.add(emptyPanel);
setCurrentWorkLand(null, null);
} else {
+ obstacleSummaryCache = loadObstacleSummaries();
if (allDikuai.size() == 1) {
Dikuai onlyDikuai = allDikuai.values().iterator().next();
setCurrentWorklandIfNeeded(onlyDikuai);
@@ -241,12 +245,12 @@
contentPanel.add(boundaryPanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 20)));
- // 闅滅鐗╁潗鏍囷紙甯︽柊澧炲拰鏌ョ湅鎸夐挳锛�
- JPanel obstaclePanel = createCardInfoItemWithButton("闅滅鐗╁潗鏍�:",
- getTruncatedValue(dikuai.getObstacleCoordinates(), 12, "鏈缃�"),
+ ObstacleSummary obstacleSummary = getObstacleSummaryFromCache(dikuai.getLandNumber());
+ JPanel obstaclePanel = createCardInfoItemWithButton("闅滅鐗�:",
+ obstacleSummary.buildDisplayValue(),
"鏂板",
e -> addNewObstacle(dikuai));
- setInfoItemTooltip(obstaclePanel, dikuai.getObstacleCoordinates());
+ setInfoItemTooltip(obstaclePanel, obstacleSummary.buildTooltip());
contentPanel.add(obstaclePanel);
contentPanel.add(Box.createRigidArea(new Dimension(0, 20)));
@@ -668,9 +672,14 @@
String boundary = (dikuai != null) ? dikuai.getBoundaryCoordinates() : null;
String plannedPath = (dikuai != null) ? dikuai.getPlannedPath() : null;
String obstacles = (dikuai != null) ? dikuai.getObstacleCoordinates() : null;
+ List<Obstacledge.Obstacle> configuredObstacles = (landNumber != null) ? loadObstaclesFromConfig(landNumber) : null;
renderer.setCurrentBoundary(boundary, landNumber, landNumber == null ? null : landName);
renderer.setCurrentPlannedPath(plannedPath);
- renderer.setCurrentObstacles(obstacles, landNumber);
+ if (configuredObstacles != null) {
+ renderer.setCurrentObstacles(configuredObstacles, landNumber);
+ } else {
+ renderer.setCurrentObstacles(obstacles, landNumber);
+ }
boolean showBoundaryPoints = landNumber != null && boundaryPointVisibility.getOrDefault(landNumber, false);
renderer.setBoundaryPointsVisible(showBoundaryPoints);
}
@@ -734,39 +743,103 @@
loadDikuaiData();
}
+ private Map<String, ObstacleSummary> loadObstacleSummaries() {
+ Map<String, ObstacleSummary> summaries = new HashMap<>();
+ try {
+ File configFile = new File("Obstacledge.properties");
+ if (!configFile.exists()) {
+ return summaries;
+ }
+ Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+ if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+ return summaries;
+ }
+ for (Obstacledge.Plot plot : manager.getPlots()) {
+ if (plot == null) {
+ continue;
+ }
+ String plotId = plot.getPlotId();
+ if (plotId == null || plotId.trim().isEmpty()) {
+ continue;
+ }
+ List<String> names = new ArrayList<>();
+ 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.add(trimmed);
+ }
+ }
+ summaries.put(plotId.trim(), ObstacleSummary.of(names));
+ }
+ } catch (Exception ex) {
+ System.err.println("璇诲彇闅滅鐗╅厤缃け璐�: " + ex.getMessage());
+ }
+ return summaries;
+ }
+
+ private static List<Obstacledge.Obstacle> loadObstaclesFromConfig(String landNumber) {
+ if (landNumber == null || landNumber.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+ try {
+ File configFile = new File("Obstacledge.properties");
+ if (!configFile.exists()) {
+ return null;
+ }
+ Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+ if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+ return null;
+ }
+ Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
+ if (plot == null) {
+ return Collections.emptyList();
+ }
+ List<Obstacledge.Obstacle> obstacles = plot.getObstacles();
+ if (obstacles == null || obstacles.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(obstacles);
+ } catch (Exception ex) {
+ System.err.println("璇诲彇闅滅鐗╅厤缃け璐�: " + ex.getMessage());
+ return null;
+ }
+ }
+
+ private ObstacleSummary getObstacleSummaryFromCache(String landNumber) {
+ if (landNumber == null || landNumber.trim().isEmpty()) {
+ return ObstacleSummary.empty();
+ }
+ if (obstacleSummaryCache == null || obstacleSummaryCache.isEmpty()) {
+ return ObstacleSummary.empty();
+ }
+ ObstacleSummary summary = obstacleSummaryCache.get(landNumber.trim());
+ return summary != null ? summary : ObstacleSummary.empty();
+ }
+
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());
+ ObstacleSummary cached = getObstacleSummaryFromCache(landNumber);
+ if (!cached.isEmpty()) {
+ names.addAll(cached.copyNames());
+ return names;
+ }
+ Map<String, ObstacleSummary> latest = loadObstacleSummaries();
+ if (!latest.isEmpty()) {
+ obstacleSummaryCache = latest;
+ }
+ ObstacleSummary refreshed = getObstacleSummaryFromCache(landNumber);
+ if (!refreshed.isEmpty()) {
+ names.addAll(refreshed.copyNames());
}
return names;
}
@@ -887,4 +960,68 @@
}
boundaryPointVisibility.put(landNumber, visible);
}
+
+ private static final class ObstacleSummary {
+ private static final ObstacleSummary EMPTY = new ObstacleSummary(Collections.emptyList());
+ private final List<String> names;
+
+ private ObstacleSummary(List<String> names) {
+ this.names = names;
+ }
+
+ static ObstacleSummary of(List<String> originalNames) {
+ if (originalNames == null || originalNames.isEmpty()) {
+ return empty();
+ }
+ List<String> cleaned = new ArrayList<>();
+ for (String name : originalNames) {
+ if (name == null) {
+ continue;
+ }
+ String trimmed = name.trim();
+ if (trimmed.isEmpty()) {
+ continue;
+ }
+ boolean duplicated = false;
+ for (String existing : cleaned) {
+ if (existing.equalsIgnoreCase(trimmed)) {
+ duplicated = true;
+ break;
+ }
+ }
+ if (!duplicated) {
+ cleaned.add(trimmed);
+ }
+ }
+ if (cleaned.isEmpty()) {
+ return empty();
+ }
+ cleaned.sort(String::compareToIgnoreCase);
+ return new ObstacleSummary(Collections.unmodifiableList(cleaned));
+ }
+
+ static ObstacleSummary empty() {
+ return EMPTY;
+ }
+
+ boolean isEmpty() {
+ return names.isEmpty();
+ }
+
+ int count() {
+ return names.size();
+ }
+
+ String buildDisplayValue() {
+ return count() > 0 ? String.format("闅滅鐗�%d涓�", count()) : "鏆傛棤闅滅鐗�";
+ }
+
+ String buildTooltip() {
+ return count() > 0 ? String.join("锛�", names) : "鏆傛棤闅滅鐗�";
+ }
+
+ List<String> copyNames() {
+ return new ArrayList<>(names);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/dikuai/addzhangaiwu.java b/src/dikuai/addzhangaiwu.java
index c936a6b..bd7d8e1 100644
--- a/src/dikuai/addzhangaiwu.java
+++ b/src/dikuai/addzhangaiwu.java
@@ -36,15 +36,20 @@
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
+import javax.swing.JTextField;
import javax.swing.border.Border;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
import baseStation.BaseStation;
import gecaoji.Device;
+import set.Setsys;
import ui.UIConfig;
import zhuye.Coordinate;
import zhuye.Shouye;
import zhangaiwu.AddDikuai;
import zhangaiwu.Obstacledge;
+import zhangaiwu.yulanzhangaiwu;
/**
* 闅滅鐗╂柊澧�/缂栬緫瀵硅瘽妗嗐�傝璁¤瑷�鍙傝�� {@link AddDikuai}锛屾敮鎸侀�氳繃瀹炲湴缁樺埗閲囬泦闅滅鐗╁潗鏍囥��
@@ -61,6 +66,8 @@
private final Color MEDIUM_GRAY = new Color(233, 236, 239);
private final Color TEXT_COLOR = new Color(33, 37, 41);
private final Color LIGHT_TEXT = new Color(108, 117, 125);
+ private final Color DANGER_COLOR = new Color(220, 53, 69);
+ private final Color DANGER_LIGHT = new Color(255, 235, 238);
private final Dikuai targetDikuai;
private final List<ExistingObstacle> existingObstacles;
@@ -80,6 +87,9 @@
private JPanel selectedShapePanel;
private JButton drawButton;
private JLabel drawingStatusLabel;
+ private JTextField obstacleNameField;
+ private JPanel existingObstacleListPanel;
+ private JPanel step1NextButtonRow;
private int currentStep = 1;
private boolean drawingInProgress;
@@ -102,8 +112,11 @@
if (target == null) {
throw new IllegalArgumentException("targetDikuai 涓嶈兘涓虹┖");
}
- this.targetDikuai = target;
- this.existingObstacles = Collections.unmodifiableList(resolveExistingObstacles(target, obstacleNames));
+ Coordinate.coordinates.clear();
+ Coordinate.setStartSaveGngga(false);
+ Coordinate.clearActiveDeviceIdFilter();
+ this.targetDikuai = target;
+ this.existingObstacles = new ArrayList<>(resolveExistingObstacles(target, obstacleNames));
initializeUI();
setupEventHandlers();
preloadData();
@@ -161,7 +174,11 @@
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(Box.createRigidArea(new Dimension(0, 16)));
+ infoContainer.add(createObstacleNameRow());
+ infoContainer.add(Box.createRigidArea(new Dimension(0, 12)));
+ infoContainer.add(createStep1NextButtonRow());
+ infoContainer.add(Box.createRigidArea(new Dimension(0, 12)));
infoContainer.add(buildExistingObstacleSection());
stepPanel.add(infoContainer);
@@ -182,32 +199,64 @@
section.add(titleLabel);
section.add(Box.createRigidArea(new Dimension(0, 8)));
+ existingObstacleListPanel = new JPanel();
+ existingObstacleListPanel.setLayout(new BoxLayout(existingObstacleListPanel, BoxLayout.Y_AXIS));
+ existingObstacleListPanel.setOpaque(false);
+ existingObstacleListPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ section.add(existingObstacleListPanel);
+ refreshExistingObstacleList();
+ return section;
+ }
+
+ private JPanel createStep1NextButtonRow() {
+ if (step1NextButtonRow == null) {
+ step1NextButtonRow = new JPanel();
+ step1NextButtonRow.setLayout(new BoxLayout(step1NextButtonRow, BoxLayout.X_AXIS));
+ step1NextButtonRow.setOpaque(false);
+ step1NextButtonRow.setAlignmentX(Component.LEFT_ALIGNMENT);
+ step1NextButtonRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
+ step1NextButtonRow.add(Box.createHorizontalGlue());
+ }
+ return step1NextButtonRow;
+ }
+
+ private void attachNextButtonToStep1Row() {
+ if (step1NextButtonRow == null || nextButton == null) {
+ return;
+ }
+ step1NextButtonRow.removeAll();
+ step1NextButtonRow.add(Box.createHorizontalGlue());
+ nextButton.setAlignmentX(Component.RIGHT_ALIGNMENT);
+ step1NextButtonRow.add(nextButton);
+ step1NextButtonRow.revalidate();
+ step1NextButtonRow.repaint();
+ }
+
+ private void refreshExistingObstacleList() {
+ if (existingObstacleListPanel == null) {
+ return;
+ }
+ existingObstacleListPanel.removeAll();
if (existingObstacles.isEmpty()) {
JLabel emptyLabel = new JLabel("鏆傛棤");
emptyLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
emptyLabel.setForeground(LIGHT_TEXT);
emptyLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
- 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)));
+ existingObstacleListPanel.add(emptyLabel);
+ } else {
+ for (int i = 0; i < existingObstacles.size(); i++) {
+ ExistingObstacle obstacle = existingObstacles.get(i);
+ JPanel row = createObstacleSummaryRow(obstacle);
+ row.setAlignmentX(Component.LEFT_ALIGNMENT);
+ existingObstacleListPanel.add(row);
+ if (i < existingObstacles.size() - 1) {
+ existingObstacleListPanel.add(Box.createRigidArea(new Dimension(0, 6)));
+ }
}
}
-
- section.add(listPanel);
- return section;
+ existingObstacleListPanel.revalidate();
+ existingObstacleListPanel.repaint();
}
private JPanel createObstacleSummaryRow(ExistingObstacle obstacle) {
@@ -225,9 +274,14 @@
JButton editButton = createInlineButton("淇敼");
editButton.addActionListener(e -> populateObstacleForEditing(obstacle));
+ JButton deleteButton = createInlineIconButton("image/delete.png", "鍒犻櫎闅滅鐗�");
+ deleteButton.addActionListener(e -> handleDeleteExistingObstacle(obstacle));
+
row.add(infoLabel);
row.add(Box.createHorizontalGlue());
row.add(editButton);
+ row.add(Box.createRigidArea(new Dimension(6, 0)));
+ row.add(deleteButton);
return row;
}
@@ -257,10 +311,56 @@
return button;
}
+ private JButton createInlineIconButton(String iconPath, String tooltip) {
+ JButton button = new JButton();
+ button.setPreferredSize(new Dimension(32, 28));
+ button.setMinimumSize(new Dimension(32, 28));
+ button.setMaximumSize(new Dimension(32, 28));
+ button.setBackground(WHITE);
+ button.setBorder(BorderFactory.createLineBorder(DANGER_COLOR));
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ button.setFocusPainted(false);
+ button.setFocusable(false);
+ button.setOpaque(true);
+ button.setContentAreaFilled(true);
+ if (tooltip != null && !tooltip.trim().isEmpty()) {
+ button.setToolTipText(tooltip);
+ }
+
+ ImageIcon icon = null;
+ if (iconPath != null && !iconPath.trim().isEmpty()) {
+ ImageIcon rawIcon = new ImageIcon(iconPath);
+ if (rawIcon.getIconWidth() > 0 && rawIcon.getIconHeight() > 0) {
+ Image scaled = rawIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH);
+ icon = new ImageIcon(scaled);
+ }
+ }
+ if (icon != null) {
+ button.setIcon(icon);
+ } else {
+ button.setText("鍒�");
+ button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 12));
+ button.setForeground(DANGER_COLOR);
+ }
+
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ button.setBackground(DANGER_LIGHT);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ button.setBackground(WHITE);
+ }
+ });
+ return button;
+ }
+
private String buildObstacleSummaryText(ExistingObstacle obstacle) {
String name = obstacle.getName();
String shape = obstacle.getShapeDisplay();
- String coordPreview = buildCoordinatePreview(obstacle.getCoordinates(), 5);
+ String coordPreview = buildCoordinatePreview(obstacle.getDisplayCoordinates(), 5);
return String.format(Locale.CHINA, "%s锛�%s锛屽潗鏍�:%s", name, shape, coordPreview);
}
@@ -278,16 +378,70 @@
return sanitized.substring(0, keepLength) + "...";
}
+ private boolean deleteObstacleFromConfig(String obstacleName) {
+ String landNumber = targetDikuai != null ? targetDikuai.getLandNumber() : null;
+ if (!isMeaningfulValue(landNumber) || !isMeaningfulValue(obstacleName)) {
+ return false;
+ }
+ try {
+ File configFile = new File("Obstacledge.properties");
+ if (!configFile.exists()) {
+ return false;
+ }
+ Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+ if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+ return false;
+ }
+ Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
+ if (plot == null) {
+ return false;
+ }
+ if (!plot.removeObstacleByName(obstacleName.trim())) {
+ return false;
+ }
+ if (!manager.saveToFile(configFile.getAbsolutePath())) {
+ return false;
+ }
+ Dikuai.updateField(landNumber.trim(), "updateTime", currentTime());
+ Dikuai.saveToProperties();
+ Dikuaiguanli.notifyExternalCreation(landNumber.trim());
+ return true;
+ } catch (Exception ex) {
+ System.err.println("鍒犻櫎闅滅鐗╁け璐�: " + ex.getMessage());
+ return false;
+ }
+ }
+
private void populateObstacleForEditing(ExistingObstacle obstacle) {
if (obstacle == null) {
return;
}
- String coords = obstacle.getCoordinates();
- if (isMeaningfulValue(coords)) {
- formData.put("obstacleCoordinates", coords.trim());
+ String name = obstacle.getName();
+ if (isMeaningfulValue(name)) {
+ formData.put("obstacleName", name.trim());
+ formData.put("editingObstacleName", name.trim());
+ } else {
+ formData.remove("obstacleName");
+ formData.remove("editingObstacleName");
+ }
+ if (obstacleNameField != null) {
+ obstacleNameField.setText(isMeaningfulValue(name) ? name.trim() : "");
+ }
+ String xyCoords = obstacle.getXyCoordinates();
+ String displayCoords = obstacle.getDisplayCoordinates();
+ if (isMeaningfulValue(xyCoords)) {
+ formData.put("obstacleCoordinates", xyCoords.trim());
+ } else if (isMeaningfulValue(displayCoords)) {
+ formData.put("obstacleCoordinates", displayCoords.trim());
} else {
formData.remove("obstacleCoordinates");
}
+ String originalCoords = obstacle.getOriginalCoordinates();
+ if (isMeaningfulValue(originalCoords)) {
+ formData.put("obstacleOriginalCoordinates", originalCoords.trim());
+ } else {
+ formData.remove("obstacleOriginalCoordinates");
+ }
String shapeKey = obstacle.getShapeKey();
if (shapeKey != null) {
JPanel shapePanel = shapeOptionPanels.get(shapeKey);
@@ -297,6 +451,56 @@
showStep(2);
}
+ private void handleDeleteExistingObstacle(ExistingObstacle obstacle) {
+ if (obstacle == null) {
+ return;
+ }
+ String obstacleName = obstacle.getName();
+ if (!isMeaningfulValue(obstacleName)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶鍒犻櫎锛氶殰纰嶇墿鍚嶇О鏃犳晥", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ int choice = JOptionPane.showConfirmDialog(this,
+ "纭畾瑕佸垹闄ら殰纰嶇墿 \"" + obstacleName + "\" 鍚楋紵姝ゆ搷浣滄棤娉曟挙閿�銆�",
+ "鍒犻櫎纭",
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+ if (choice != JOptionPane.YES_OPTION) {
+ return;
+ }
+ boolean removedFromConfig = deleteObstacleFromConfig(obstacleName);
+ boolean hasPersistedData = obstacle.hasPersistedData() || obstacle.getShapeKey() != null;
+ if (!removedFromConfig && hasPersistedData) {
+ JOptionPane.showMessageDialog(this, "鍒犻櫎澶辫触锛屾湭鑳藉湪閰嶇疆涓Щ闄よ闅滅鐗┿��", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ if (!existingObstacles.remove(obstacle)) {
+ existingObstacles.removeIf(item -> obstacleName.equals(item != null ? item.getName() : null));
+ }
+ refreshExistingObstacleList();
+ clearEditingStateIfDeleted(obstacleName);
+ JOptionPane.showMessageDialog(this, "闅滅鐗╁凡鍒犻櫎", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ private void clearEditingStateIfDeleted(String obstacleName) {
+ if (!isMeaningfulValue(obstacleName)) {
+ return;
+ }
+ String editingName = formData.get("editingObstacleName");
+ if (isMeaningfulValue(editingName) && obstacleName.equals(editingName)) {
+ formData.remove("editingObstacleName");
+ formData.remove("obstacleCoordinates");
+ formData.remove("obstacleOriginalCoordinates");
+ if (obstacleNameField != null) {
+ obstacleNameField.setText("");
+ } else {
+ formData.remove("obstacleName");
+ }
+ updateDrawingStatus();
+ updateSaveButtonState();
+ }
+ }
+
private JPanel createStep2Panel() {
JPanel stepPanel = new JPanel();
stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
@@ -322,7 +526,7 @@
stepPanel.add(methodOptionsPanel);
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
- stepPanel.add(createSectionHeader("闅滅鐗╁舰鐘�", "澶氳竟褰㈤渶閲囬泦澶氫釜鐐癸紝鍦嗗舰鍙渶鍦嗗績涓庡渾鍛ㄤ笂涓�鐐�"));
+ stepPanel.add(createSectionHeader("闅滅鐗╁舰鐘�", "澶氳竟褰㈤渶閲囬泦澶氫釜鐐癸紝鍦嗗舰闇�鍦ㄥ渾鍛ㄤ笂閲囬泦鑷冲皯涓変釜鐐�"));
stepPanel.add(Box.createRigidArea(new Dimension(0, 10)));
shapeOptionsPanel = new JPanel();
@@ -331,7 +535,7 @@
shapeOptionsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
shapeOptionsPanel.add(createShapeOption("polygon", "澶氳竟褰�", "渚濇閲囬泦杞粨涓婄殑澶氫釜鐐规垨鑰呮部闅滅鐗╄竟缂樿蛋涓�鍦�"));
shapeOptionsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
- shapeOptionsPanel.add(createShapeOption("circle", "鍦嗗舰", "鍏堥噰闆嗗渾蹇冨潗鏍囷紝鍐嶉噰闆嗗渾鍛ㄤ笂涓�鐐�"));
+ shapeOptionsPanel.add(createShapeOption("circle", "鍦嗗舰", "娌垮渾鍛ㄤ换鎰忛噰闆嗕笁涓偣鑷姩鐢熸垚鍦�"));
stepPanel.add(shapeOptionsPanel);
stepPanel.add(Box.createRigidArea(new Dimension(0, 24)));
@@ -366,10 +570,11 @@
buttonPanel.add(prevButton);
buttonPanel.add(Box.createHorizontalGlue());
- buttonPanel.add(nextButton);
buttonPanel.add(Box.createRigidArea(new Dimension(12, 0)));
buttonPanel.add(saveButton);
+ attachNextButtonToStep1Row();
+
return buttonPanel;
}
@@ -616,6 +821,22 @@
nextButton.addActionListener(e -> {
if (currentStep == 1) {
+ String name = obstacleNameField != null ? obstacleNameField.getText() : null;
+ if (!isMeaningfulValue(name)) {
+ JOptionPane.showMessageDialog(this, "璇峰厛濉啓闅滅鐗╁悕绉�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ if (obstacleNameField != null) {
+ obstacleNameField.requestFocusInWindow();
+ }
+ return;
+ }
+ if (!isObstacleNameUnique(name)) {
+ JOptionPane.showMessageDialog(this, "闅滅鐗╁悕绉板凡瀛樺湪锛岃杈撳叆鍞竴鍚嶇О", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ if (obstacleNameField != null) {
+ obstacleNameField.requestFocusInWindow();
+ }
+ return;
+ }
+ formData.put("obstacleName", name.trim());
Coordinate.coordinates.clear();
showStep(2);
}
@@ -656,14 +877,28 @@
return;
}
+ String deviceId = resolveDrawingDeviceId(method);
+ if (!isMeaningfulValue(deviceId)) {
+ String idLabel = "handheld".equalsIgnoreCase(method) ? "鎵嬫寔璁惧缂栧彿" : "鍓茶崏鏈虹紪鍙�";
+ JOptionPane.showMessageDialog(this,
+ "鏈幏鍙栧埌鏈夋晥鐨�" + idLabel + "锛岃鍏堝湪绯荤粺璁剧疆涓畬鎴愰厤缃�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
activeDrawingShape = shape.toLowerCase(Locale.ROOT);
Coordinate.coordinates.clear();
+ Coordinate.setActiveDeviceIdFilter(deviceId);
Coordinate.setStartSaveGngga(true);
+ yulanzhangaiwu.startPreview(activeDrawingShape, baseStation);
drawingInProgress = true;
drawButton.setText("姝e湪缁樺埗...");
drawButton.setEnabled(false);
- drawingStatusLabel.setText("姝e湪閲囬泦闅滅鐗╁潗鏍囷紝璇峰湪涓荤晫闈㈠畬鎴愮粯鍒躲��");
+ if ("circle".equals(activeDrawingShape)) {
+ drawingStatusLabel.setText("姝e湪閲囬泦鍦嗗舰闅滅鐗╋紝璇锋部鍦嗗懆閲囬泦鑷冲皯涓変釜鐐瑰悗鍦ㄤ富鐣岄潰缁撴潫缁樺埗銆�");
+ } else {
+ drawingStatusLabel.setText("姝e湪閲囬泦闅滅鐗╁潗鏍囷紝璇峰湪涓荤晫闈㈠畬鎴愮粯鍒躲��");
+ }
if (activeSession == null) {
activeSession = new ObstacleDrawingSession();
@@ -692,6 +927,8 @@
public static void finishDrawingSession() {
Coordinate.setStartSaveGngga(false);
+ Coordinate.clearActiveDeviceIdFilter();
+ yulanzhangaiwu.stopPreview();
Shouye shouye = Shouye.getInstance();
if (shouye != null) {
@@ -710,11 +947,20 @@
showDialog(parent, activeSession.target);
}
+ public static String getActiveSessionBaseStation() {
+ if (activeSession != null && isMeaningfulValue(activeSession.baseStation)) {
+ return activeSession.baseStation.trim();
+ }
+ return null;
+ }
+
/**
* Stops an in-progress circle capture and reopens the wizard on step 2.
*/
public static void abortCircleDrawingAndReturn(String message) {
Coordinate.setStartSaveGngga(false);
+ Coordinate.clearActiveDeviceIdFilter();
+ yulanzhangaiwu.stopPreview();
Coordinate.coordinates.clear();
if (activeSession == null || activeSession.target == null) {
@@ -750,6 +996,13 @@
return;
}
+ String originalCoordStr = buildOriginalCoordinateString(captured);
+ if (isMeaningfulValue(originalCoordStr)) {
+ session.data.put("obstacleOriginalCoordinates", originalCoordStr);
+ } else {
+ session.data.remove("obstacleOriginalCoordinates");
+ }
+
List<double[]> xyPoints = convertToLocalXY(captured, session.baseStation);
if (xyPoints.isEmpty()) {
session.captureSuccessful = false;
@@ -759,18 +1012,28 @@
String shape = session.data.get("obstacleShape");
if ("circle".equals(shape)) {
- if (xyPoints.size() < 2) {
+ if (xyPoints.size() < 3) {
session.captureSuccessful = false;
- session.captureMessage = "鍦嗗舰闅滅鐗╄嚦灏戦渶瑕佷袱涓噰闆嗙偣锛堝渾蹇冨拰鍦嗗懆鐐癸級";
+ session.captureMessage = "鍦嗗舰闅滅鐗╄嚦灏戦渶瑕佷笁涓噰闆嗙偣锛堝渾鍛ㄤ笂鐨勭偣锛�";
return;
}
- double[] center = xyPoints.get(0);
- double[] radiusPoint = xyPoints.get(xyPoints.size() - 1);
+ CircleFitResult circle = fitCircleFromPoints(xyPoints);
+ if (circle == null) {
+ session.captureSuccessful = false;
+ session.captureMessage = "鏃犳硶鏍规嵁閲囬泦鐨勭偣鐢熸垚鍦嗭紝璇风‘淇濋�夋嫨浜嗕笁涓潪鍏辩嚎鐨勫渾鍛ㄧ偣";
+ return;
+ }
+ double[] radiusPoint = pickRadiusPoint(xyPoints, circle);
+ if (radiusPoint == null) {
+ session.captureSuccessful = false;
+ session.captureMessage = "閲囬泦鐨勫渾鍛ㄧ偣寮傚父锛屾棤娉曠敓鎴愬渾";
+ return;
+ }
String result = String.format(Locale.US, "%.2f,%.2f;%.2f,%.2f",
- center[0], center[1], radiusPoint[0], radiusPoint[1]);
+ circle.centerX, circle.centerY, radiusPoint[0], radiusPoint[1]);
session.data.put("obstacleCoordinates", result);
session.captureSuccessful = true;
- session.captureMessage = "宸查噰闆嗗渾褰㈤殰纰嶇墿鍧愭爣";
+ session.captureMessage = "宸查噰闆嗗渾褰㈤殰纰嶇墿锛屽叡 " + xyPoints.size() + " 涓偣";
} else {
if (xyPoints.size() < 3) {
session.captureSuccessful = false;
@@ -816,6 +1079,67 @@
return result;
}
+ private static String buildOriginalCoordinateString(List<Coordinate> coords) {
+ if (coords == null || coords.isEmpty()) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (Coordinate coord : coords) {
+ if (coord == null) {
+ continue;
+ }
+ String latToken = sanitizeCoordinateToken(coord.getLatitude());
+ String lonToken = sanitizeCoordinateToken(coord.getLongitude());
+ if (latToken == null || lonToken == null) {
+ continue;
+ }
+ char latDir = sanitizeDirection(coord.getLatDirection(), 'N');
+ char lonDir = sanitizeDirection(coord.getLonDirection(), 'E');
+ if (sb.length() > 0) {
+ sb.append(";");
+ }
+ sb.append(latToken)
+ .append(",")
+ .append(latDir)
+ .append(",")
+ .append(lonToken)
+ .append(",")
+ .append(lonDir);
+ }
+ return sb.length() > 0 ? sb.toString() : null;
+ }
+
+ private static String sanitizeCoordinateToken(String token) {
+ if (token == null) {
+ return null;
+ }
+ String trimmed = token.trim();
+ if (trimmed.isEmpty()) {
+ return null;
+ }
+ try {
+ Double.parseDouble(trimmed);
+ return trimmed;
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+
+ private static char sanitizeDirection(String direction, char fallback) {
+ if (direction == null) {
+ return fallback;
+ }
+ String trimmed = direction.trim();
+ if (trimmed.isEmpty()) {
+ return fallback;
+ }
+ char ch = Character.toUpperCase(trimmed.charAt(0));
+ if (ch != 'N' && ch != 'S' && ch != 'E' && ch != 'W') {
+ return fallback;
+ }
+ return ch;
+ }
+
private static double parseDMToDecimal(String dmm, String direction) {
if (dmm == null || dmm.trim().isEmpty()) {
return Double.NaN;
@@ -847,6 +1171,101 @@
return new double[]{eastMeters, northMeters};
}
+ private static CircleFitResult fitCircleFromPoints(List<double[]> points) {
+ if (points == null || points.size() < 3) {
+ return null;
+ }
+ CircleFitResult best = null;
+ double bestScore = 0.0;
+ int n = points.size();
+ for (int i = 0; i < n - 2; i++) {
+ double[] p1 = points.get(i);
+ for (int j = i + 1; j < n - 1; j++) {
+ double[] p2 = points.get(j);
+ for (int k = j + 1; k < n; k++) {
+ double[] p3 = points.get(k);
+ CircleFitResult candidate = circleFromThreePoints(p1, p2, p3);
+ if (candidate == null || candidate.radius <= 0) {
+ continue;
+ }
+ double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3)));
+ if (minEdge > bestScore) {
+ bestScore = minEdge;
+ best = candidate;
+ }
+ }
+ }
+ }
+ return best;
+ }
+
+ private static CircleFitResult circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
+ double x1 = p1[0];
+ double y1 = p1[1];
+ double x2 = p2[0];
+ double y2 = p2[1];
+ double x3 = p3[0];
+ double y3 = p3[1];
+
+ double determinant = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
+ if (Math.abs(determinant) < 1e-6) {
+ return null;
+ }
+
+ double x1Sq = x1 * x1 + y1 * y1;
+ double x2Sq = x2 * x2 + y2 * y2;
+ double x3Sq = x3 * x3 + y3 * y3;
+
+ double centerX = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / determinant;
+ double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / determinant;
+ double radius = Math.hypot(centerX - x1, centerY - y1);
+
+ if (!Double.isFinite(centerX) || !Double.isFinite(centerY) || !Double.isFinite(radius)) {
+ return null;
+ }
+ if (radius < 0.05) {
+ return null;
+ }
+
+ return new CircleFitResult(centerX, centerY, radius, new double[]{x1, y1});
+ }
+
+ private static double[] pickRadiusPoint(List<double[]> points, CircleFitResult circle) {
+ if (circle == null || points == null || points.isEmpty()) {
+ return null;
+ }
+ double[] best = null;
+ double bestDistance = 0.0;
+ for (double[] pt : points) {
+ double dist = Math.hypot(pt[0] - circle.centerX, pt[1] - circle.centerY);
+ if (dist > bestDistance) {
+ bestDistance = dist;
+ best = pt;
+ }
+ }
+ return best;
+ }
+
+ private static double distance(double[] a, double[] b) {
+ double dx = a[0] - b[0];
+ double dy = a[1] - b[1];
+ return Math.hypot(dx, dy);
+ }
+
+ private static final class CircleFitResult {
+ final double centerX;
+ final double centerY;
+ final double radius;
+ final double[] referencePoint;
+
+ CircleFitResult(double centerX, double centerY, double radius, double[] referencePoint) {
+ this.centerX = centerX;
+ this.centerY = centerY;
+ this.radius = radius;
+ this.referencePoint = referencePoint;
+ }
+ }
+
private List<ExistingObstacle> resolveExistingObstacles(Dikuai target, List<String> providedNames) {
String landNumber = target != null ? target.getLandNumber() : null;
Map<String, ExistingObstacle> configMap = loadObstacleDetailsFromConfig(landNumber);
@@ -914,11 +1333,11 @@
}
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));
+ String xyCoords = obstacle.getXyCoordsString();
+ String original = obstacle.getOriginalCoordsString();
+ String xy = isMeaningfulValue(xyCoords) ? xyCoords.trim() : "";
+ String originalTrimmed = isMeaningfulValue(original) ? original.trim() : "";
+ details.put(trimmedName, new ExistingObstacle(trimmedName, shape, xy, originalTrimmed));
}
} catch (Exception ex) {
System.err.println("鍔犺浇闅滅鐗╄鎯呭け璐�: " + ex.getMessage());
@@ -927,10 +1346,9 @@
}
private void preloadData() {
- String existing = targetDikuai.getObstacleCoordinates();
- if (isMeaningfulValue(existing)) {
- formData.put("obstacleCoordinates", existing.trim());
- }
+ formData.remove("obstacleCoordinates");
+ formData.remove("obstacleOriginalCoordinates");
+ formData.remove("editingObstacleName");
updateDrawingStatus();
}
@@ -979,11 +1397,40 @@
private void updateSaveButtonState() {
if (saveButton != null) {
- saveButton.setEnabled(isMeaningfulValue(formData.get("obstacleCoordinates")));
+ boolean hasCoords = isMeaningfulValue(formData.get("obstacleCoordinates"));
+ boolean hasName = isMeaningfulValue(formData.get("obstacleName"));
+ boolean enabled = hasCoords && hasName;
+ saveButton.setEnabled(enabled);
+ if (enabled) {
+ saveButton.setBackground(PRIMARY_COLOR);
+ saveButton.setForeground(WHITE);
+ saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ saveButton.setBackground(MEDIUM_GRAY);
+ saveButton.setForeground(TEXT_COLOR);
+ saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
}
}
private boolean validateStep2() {
+ String name = formData.get("obstacleName");
+ if (!isMeaningfulValue(name)) {
+ JOptionPane.showMessageDialog(this, "璇峰~鍐欓殰纰嶇墿鍚嶇О", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ showStep(1);
+ if (obstacleNameField != null) {
+ obstacleNameField.requestFocusInWindow();
+ }
+ return false;
+ }
+ if (!isObstacleNameUnique(name)) {
+ JOptionPane.showMessageDialog(this, "闅滅鐗╁悕绉板凡瀛樺湪锛岃杈撳叆鍞竴鍚嶇О", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ showStep(1);
+ if (obstacleNameField != null) {
+ obstacleNameField.requestFocusInWindow();
+ }
+ return false;
+ }
if (!isMeaningfulValue(formData.get("obstacleCoordinates"))) {
JOptionPane.showMessageDialog(this, "璇峰厛瀹屾垚闅滅鐗╃粯鍒�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
return false;
@@ -995,34 +1442,198 @@
if (!validateStep2()) {
return;
}
- String coords = formData.get("obstacleCoordinates").trim();
String landNumber = targetDikuai.getLandNumber();
+ String obstacleName = formData.get("obstacleName");
+ String coordsValue = formData.get("obstacleCoordinates");
+ String originalValue = formData.get("obstacleOriginalCoordinates");
+ String shapeKey = formData.get("obstacleShape");
+ String previousName = formData.get("editingObstacleName");
+
+ if (!isMeaningfulValue(coordsValue)) {
+ JOptionPane.showMessageDialog(this, "闅滅鐗╁潗鏍囨棤鏁�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ String coords = coordsValue.trim();
+ String originalCoords = isMeaningfulValue(originalValue) ? originalValue.trim() : null;
+ String trimmedName = isMeaningfulValue(obstacleName) ? obstacleName.trim() : null;
+ if (!isMeaningfulValue(trimmedName)) {
+ JOptionPane.showMessageDialog(this, "闅滅鐗╁悕绉版棤鏁�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ formData.put("obstacleName", trimmedName);
+ if (isMeaningfulValue(previousName)) {
+ formData.put("editingObstacleName", trimmedName);
+ }
+
+ if (!persistObstacleToConfig(landNumber, previousName, trimmedName, shapeKey, coords, originalCoords)) {
+ JOptionPane.showMessageDialog(this, "鍐欏叆闅滅鐗╅厤缃け璐ワ紝璇烽噸璇�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
if (!Dikuai.updateField(landNumber, "obstacleCoordinates", coords)) {
JOptionPane.showMessageDialog(this, "鏃犳硶鏇存柊闅滅鐗╁潗鏍�", "閿欒", JOptionPane.ERROR_MESSAGE);
return;
}
+ if (originalCoords != null) {
+ if (!Dikuai.updateField(landNumber, "obstacleOriginalCoordinates", originalCoords)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶鏇存柊闅滅鐗╁師濮嬪潗鏍�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ } else if (!Dikuai.updateField(landNumber, "obstacleOriginalCoordinates", "-1")) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶閲嶇疆闅滅鐗╁師濮嬪潗鏍�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
Dikuai.updateField(landNumber, "updateTime", currentTime());
Dikuai.saveToProperties();
Dikuaiguanli.notifyExternalCreation(landNumber);
- JOptionPane.showMessageDialog(this, "闅滅鐗╁潗鏍囧凡淇濆瓨", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ JOptionPane.showMessageDialog(this, "闅滅鐗╂暟鎹凡淇濆瓨", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
activeSession = null;
dispose();
}
+ private boolean persistObstacleToConfig(String landNumber, String previousName, String obstacleName,
+ String shapeKey, String xyCoords, String originalCoords) {
+ if (!isMeaningfulValue(landNumber) || !isMeaningfulValue(obstacleName) || !isMeaningfulValue(xyCoords)) {
+ return false;
+ }
+
+ String normalizedLandNumber = landNumber.trim();
+ String normalizedNewName = obstacleName.trim();
+ String normalizedPrevious = isMeaningfulValue(previousName) ? previousName.trim() : null;
+
+ String normalizedXy = xyCoords.trim();
+
+ try {
+ File configFile = new File("Obstacledge.properties");
+ Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+ if (configFile.exists()) {
+ if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+ return false;
+ }
+ }
+
+ Obstacledge.Plot plot = manager.getPlotById(normalizedLandNumber);
+ if (plot == null) {
+ plot = new Obstacledge.Plot(normalizedLandNumber);
+ manager.addPlot(plot);
+ }
+
+ ensurePlotBaseStation(plot);
+
+ if (normalizedPrevious != null && !normalizedPrevious.equals(normalizedNewName)) {
+ plot.removeObstacleByName(normalizedPrevious);
+ }
+
+ Obstacledge.Obstacle obstacle = plot.getObstacleByName(normalizedNewName);
+ if (obstacle == null) {
+ obstacle = new Obstacledge.Obstacle(normalizedLandNumber, normalizedNewName,
+ determineObstacleShape(shapeKey, normalizedXy));
+ plot.addObstacle(obstacle);
+ }
+
+ obstacle.setPlotId(normalizedLandNumber);
+ obstacle.setObstacleName(normalizedNewName);
+ obstacle.setShape(determineObstacleShape(shapeKey, normalizedXy));
+
+ obstacle.setXyCoordinates(new ArrayList<Obstacledge.XYCoordinate>());
+ obstacle.setOriginalCoordinates(new ArrayList<Obstacledge.DMCoordinate>());
+
+ obstacle.setXyCoordsString(normalizedXy);
+
+ if (isMeaningfulValue(originalCoords)) {
+ try {
+ obstacle.setOriginalCoordsString(originalCoords);
+ } catch (Exception parseEx) {
+ System.err.println("瑙f瀽闅滅鐗╁師濮嬪潗鏍囧け璐ワ紝灏嗕娇鐢ㄥ崰浣嶅��: " + parseEx.getMessage());
+ obstacle.setOriginalCoordinates(new ArrayList<Obstacledge.DMCoordinate>());
+ }
+ }
+
+ ensurePlaceholderOriginalCoords(obstacle);
+
+ return manager.saveToFile(configFile.getAbsolutePath());
+ } catch (Exception ex) {
+ System.err.println("淇濆瓨闅滅鐗╅厤缃け璐�: " + ex.getMessage());
+ return false;
+ }
+ }
+
+ private Obstacledge.ObstacleShape determineObstacleShape(String shapeKey, String xyCoords) {
+ if (isMeaningfulValue(shapeKey)) {
+ String normalized = shapeKey.trim().toLowerCase(Locale.ROOT);
+ if ("circle".equals(normalized) || "鍦嗗舰".equals(normalized) || "0".equals(normalized)) {
+ return Obstacledge.ObstacleShape.CIRCLE;
+ }
+ if ("polygon".equals(normalized) || "澶氳竟褰�".equals(normalized) || "1".equals(normalized)) {
+ return Obstacledge.ObstacleShape.POLYGON;
+ }
+ }
+
+ int pairCount = countCoordinatePairs(xyCoords);
+ if (pairCount <= 0) {
+ return Obstacledge.ObstacleShape.POLYGON;
+ }
+ if (pairCount <= 2) {
+ return Obstacledge.ObstacleShape.CIRCLE;
+ }
+ return Obstacledge.ObstacleShape.POLYGON;
+ }
+
+ private void ensurePlaceholderOriginalCoords(Obstacledge.Obstacle obstacle) {
+ if (obstacle == null) {
+ return;
+ }
+ List<Obstacledge.DMCoordinate> originals = obstacle.getOriginalCoordinates();
+ if (originals != null && !originals.isEmpty()) {
+ return;
+ }
+ List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+ int pointCount = (xyCoords != null && !xyCoords.isEmpty()) ? xyCoords.size() : 1;
+ List<Obstacledge.DMCoordinate> placeholders = new ArrayList<>(pointCount * 2);
+ for (int i = 0; i < pointCount; i++) {
+ placeholders.add(new Obstacledge.DMCoordinate(0.0, 'N'));
+ placeholders.add(new Obstacledge.DMCoordinate(0.0, 'E'));
+ }
+ obstacle.setOriginalCoordinates(placeholders);
+ }
+
+ private void ensurePlotBaseStation(Obstacledge.Plot plot) {
+ if (plot == null) {
+ return;
+ }
+ String existing = plot.getBaseStationString();
+ if (isMeaningfulValue(existing)) {
+ return;
+ }
+ String baseStation = resolveBaseStationCoordinates();
+ if (!isMeaningfulValue(baseStation)) {
+ return;
+ }
+ try {
+ plot.setBaseStationString(baseStation.trim());
+ } catch (Exception ex) {
+ System.err.println("璁剧疆鍩哄噯绔欏潗鏍囧け璐�: " + ex.getMessage());
+ }
+ }
+
private static final class ExistingObstacle {
private final String name;
private final Obstacledge.ObstacleShape shape;
- private final String coordinates;
+ private final String xyCoordinates;
+ private final String originalCoordinates;
- ExistingObstacle(String name, Obstacledge.ObstacleShape shape, String coordinates) {
+ ExistingObstacle(String name, Obstacledge.ObstacleShape shape, String xyCoordinates, String originalCoordinates) {
this.name = name != null ? name : "";
this.shape = shape;
- this.coordinates = coordinates != null ? coordinates : "";
+ this.xyCoordinates = safeCoordString(xyCoordinates);
+ this.originalCoordinates = safeCoordString(originalCoordinates);
}
static ExistingObstacle placeholder(String name) {
- return new ExistingObstacle(name, null, "");
+ return new ExistingObstacle(name, null, "", "");
}
String getName() {
@@ -1047,7 +1658,38 @@
}
String getCoordinates() {
- return coordinates;
+ return getDisplayCoordinates();
+ }
+
+ String getDisplayCoordinates() {
+ return hasText(xyCoordinates) ? xyCoordinates : originalCoordinates;
+ }
+
+ String getXyCoordinates() {
+ return xyCoordinates;
+ }
+
+ String getOriginalCoordinates() {
+ return originalCoordinates;
+ }
+
+ boolean hasPersistedData() {
+ return hasText(xyCoordinates) || hasText(originalCoordinates);
+ }
+
+ private static String safeCoordString(String value) {
+ if (value == null) {
+ return "";
+ }
+ return value.trim();
+ }
+
+ private static boolean hasText(String value) {
+ if (value == null) {
+ return false;
+ }
+ String trimmed = value.trim();
+ return !trimmed.isEmpty() && !"-1".equals(trimmed);
}
}
@@ -1076,6 +1718,11 @@
formData.clear();
formData.putAll(session.data);
+ String sessionName = session.data.get("obstacleName");
+ if (obstacleNameField != null) {
+ obstacleNameField.setText(sessionName != null ? sessionName : "");
+ }
+
String method = session.data.get("drawingMethod");
if (method != null) {
JPanel panel = methodOptionPanels.get(method);
@@ -1092,7 +1739,7 @@
}
}
- if (session.captureMessage != null) {
+ if (session.captureMessage != null && !session.captureSuccessful) {
JOptionPane.showMessageDialog(this,
session.captureMessage,
session.captureSuccessful ? "鎴愬姛" : "鎻愮ず",
@@ -1108,6 +1755,93 @@
return createInfoRow(label, value, null);
}
+ private JPanel createObstacleNameRow() {
+ JPanel row = new JPanel();
+ row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
+ row.setOpaque(false);
+ row.setAlignmentX(Component.LEFT_ALIGNMENT);
+ row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
+
+ JLabel label = new JLabel("闅滅鐗╁悕绉帮細");
+ label.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+ label.setForeground(TEXT_COLOR);
+ label.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ if (obstacleNameField == null) {
+ obstacleNameField = new JTextField();
+ obstacleNameField.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 14));
+ obstacleNameField.setColumns(16);
+ Dimension fieldSize = new Dimension(240, 30);
+ obstacleNameField.setPreferredSize(fieldSize);
+ obstacleNameField.setMinimumSize(fieldSize);
+ obstacleNameField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30));
+ obstacleNameField.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(BORDER_COLOR, 1),
+ BorderFactory.createEmptyBorder(4, 8, 4, 8)));
+ obstacleNameField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ handleObstacleNameChanged();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ handleObstacleNameChanged();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ handleObstacleNameChanged();
+ }
+ });
+ }
+
+ String existing = formData.get("obstacleName");
+ if (existing != null && !existing.equals(obstacleNameField.getText())) {
+ obstacleNameField.setText(existing);
+ }
+
+ row.add(label);
+ row.add(Box.createRigidArea(new Dimension(10, 0)));
+ row.add(obstacleNameField);
+ row.add(Box.createHorizontalGlue());
+ return row;
+ }
+
+ private void handleObstacleNameChanged() {
+ if (obstacleNameField == null) {
+ return;
+ }
+ String text = obstacleNameField.getText();
+ if (isMeaningfulValue(text)) {
+ formData.put("obstacleName", text.trim());
+ } else {
+ formData.remove("obstacleName");
+ }
+ updateSaveButtonState();
+ }
+
+ private boolean isObstacleNameUnique(String candidate) {
+ if (!isMeaningfulValue(candidate)) {
+ return false;
+ }
+ String trimmed = candidate.trim().toLowerCase(Locale.ROOT);
+ String original = formData.get("editingObstacleName");
+ if (isMeaningfulValue(original) && trimmed.equals(original.trim().toLowerCase(Locale.ROOT))) {
+ return true;
+ }
+ for (ExistingObstacle obstacle : existingObstacles) {
+ if (obstacle == null) {
+ continue;
+ }
+ String existingName = obstacle.getName();
+ if (isMeaningfulValue(existingName) && trimmed.equals(existingName.trim().toLowerCase(Locale.ROOT))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private JPanel createInfoRow(String label, String value, String tooltip) {
JPanel row = new JPanel();
row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
@@ -1206,6 +1940,18 @@
return null;
}
+ private String resolveDrawingDeviceId(String method) {
+ if (!isMeaningfulValue(method)) {
+ return null;
+ }
+ String key = "handheld".equalsIgnoreCase(method) ? "handheldMarkerId" : "mowerId";
+ String value = Setsys.getPropertyValue(key);
+ if (!isMeaningfulValue(value)) {
+ return null;
+ }
+ return value.trim();
+ }
+
private String safeValue(String value, String fallback) {
if (!isMeaningfulValue(value)) {
return fallback;
diff --git a/src/set/Sets.java b/src/set/Sets.java
index d33829b..a5e2863 100644
--- a/src/set/Sets.java
+++ b/src/set/Sets.java
@@ -1,9 +1,12 @@
package set;
import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
/**
* 璁剧疆瀵硅瘽妗� - 鍙傝�僑houye.java鏍峰紡
@@ -21,12 +24,15 @@
// 璁剧疆椤圭粍浠�
private JLabel mowerIdLabel;
+ private JLabel handheldMarkerLabel;
private JLabel simCardNumberLabel;
private JLabel firmwareVersionLabel;
private JLabel appVersionLabel;
private JButton mowerIdEditBtn;
+ private JButton handheldEditBtn;
private JButton checkUpdateBtn;
+ private JButton feedbackButton;
// 鏁版嵁妯″瀷
private Setsys setData;
@@ -87,6 +93,11 @@
mowerIdLabel = (JLabel) mowerIdPanel.getClientProperty("valueLabel");
mowerIdEditBtn = (JButton) mowerIdPanel.getClientProperty("editButton");
+ JPanel handheldPanel = createSettingItemPanel("渚挎惡鎵撶偣鍣ㄧ紪鍙�",
+ setData.getHandheldMarkerId() != null ? setData.getHandheldMarkerId() : "鏈缃�", true);
+ handheldMarkerLabel = (JLabel) handheldPanel.getClientProperty("valueLabel");
+ handheldEditBtn = (JButton) handheldPanel.getClientProperty("editButton");
+
// 鐗╄仈鍗″彿
JPanel simCardPanel = createSettingItemPanel("鐗╄仈鍗″彿",
setData.getSimCardNumber() != null ? setData.getSimCardNumber() : "鏈缃�", false);
@@ -96,13 +107,17 @@
JPanel firmwarePanel = createSettingItemPanel("鍥轰欢鐗堟湰",
setData.getFirmwareVersion() != null ? setData.getFirmwareVersion() : "鏈缃�", false);
firmwareVersionLabel = (JLabel) firmwarePanel.getClientProperty("valueLabel");
+
+ JPanel feedbackPanel = createFeedbackPanel();
// APP鐗堟湰
JPanel appVersionPanel = createAppVersionPanel();
addRowWithSpacing(panel, mowerIdPanel);
+ addRowWithSpacing(panel, handheldPanel);
addRowWithSpacing(panel, simCardPanel);
addRowWithSpacing(panel, firmwarePanel);
+ addRowWithSpacing(panel, feedbackPanel);
panel.add(appVersionPanel);
return panel;
@@ -226,6 +241,62 @@
return panel;
}
+
+ private JPanel createFeedbackPanel() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ panel.setBackground(PANEL_BACKGROUND);
+ panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, ROW_HEIGHT));
+ panel.setPreferredSize(new Dimension(Integer.MAX_VALUE, ROW_HEIGHT));
+ panel.setMinimumSize(new Dimension(0, ROW_HEIGHT));
+
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ JLabel titleLabel = new JLabel("闂鍙嶉鍜ㄨ");
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+ titleLabel.setForeground(Color.BLACK);
+ titleLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.weightx = 0;
+ gbc.anchor = GridBagConstraints.EAST;
+ gbc.insets = new Insets(0, 0, 0, 12);
+ panel.add(titleLabel, gbc);
+
+ feedbackButton = new JButton("鍙嶉");
+ feedbackButton.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
+ feedbackButton.setBackground(THEME_COLOR);
+ feedbackButton.setForeground(Color.WHITE);
+ feedbackButton.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 20));
+ feedbackButton.setPreferredSize(new Dimension(100, 28));
+ feedbackButton.setMinimumSize(new Dimension(100, 28));
+ feedbackButton.setMaximumSize(new Dimension(100, 28));
+ feedbackButton.setFocusPainted(false);
+
+ feedbackButton.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ feedbackButton.setBackground(new Color(
+ Math.max(THEME_COLOR.getRed() - 20, 0),
+ Math.max(THEME_COLOR.getGreen() - 20, 0),
+ Math.max(THEME_COLOR.getBlue() - 20, 0)));
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ feedbackButton.setBackground(THEME_COLOR);
+ }
+ });
+
+ gbc = new GridBagConstraints();
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ gbc.weightx = 1.0;
+ gbc.anchor = GridBagConstraints.EAST;
+ panel.add(feedbackButton, gbc);
+
+ return panel;
+ }
private JButton createEditButton() {
JButton button = new JButton();
@@ -270,6 +341,10 @@
if (mowerIdLabel != null) {
mowerIdLabel.setText(setData.getMowerId() != null ? setData.getMowerId() : "鏈缃�");
}
+
+ if (handheldMarkerLabel != null) {
+ handheldMarkerLabel.setText(setData.getHandheldMarkerId() != null ? setData.getHandheldMarkerId() : "鏈缃�");
+ }
// 鏇存柊鐗╄仈鍗″彿鏄剧ず
if (simCardNumberLabel != null) {
@@ -300,6 +375,14 @@
if (checkUpdateBtn != null) {
checkUpdateBtn.addActionListener(e -> checkForUpdates());
}
+
+ if (handheldEditBtn != null) {
+ handheldEditBtn.addActionListener(e -> editHandheldMarkerId());
+ }
+
+ if (feedbackButton != null) {
+ feedbackButton.addActionListener(e -> showFeedbackDialog());
+ }
}
@@ -327,6 +410,183 @@
}
}
}
+
+ private void editHandheldMarkerId() {
+ String currentValue = setData.getHandheldMarkerId() != null ? setData.getHandheldMarkerId() : "";
+ String newValue = (String) JOptionPane.showInputDialog(this,
+ "璇疯緭鍏ヤ究鎼烘墦鐐瑰櫒缂栧彿:",
+ "淇敼渚挎惡鎵撶偣鍣ㄧ紪鍙�",
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ null,
+ currentValue);
+
+ if (newValue == null) {
+ return;
+ }
+
+ newValue = newValue.trim();
+ if (!newValue.isEmpty()) {
+ if (setData.updateProperty("handheldMarkerId", newValue)) {
+ if (handheldMarkerLabel != null) {
+ handheldMarkerLabel.setText(newValue);
+ }
+ JOptionPane.showMessageDialog(this, "渚挎惡鎵撶偣鍣ㄧ紪鍙锋洿鏂版垚鍔�", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ } else {
+ JOptionPane.showMessageDialog(this, "渚挎惡鎵撶偣鍣ㄧ紪鍙锋洿鏂板け璐�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ private void showFeedbackDialog() {
+ JDialog dialog = new JDialog(this, "闂鍙嶉鍜ㄨ", true);
+ dialog.setLayout(new BorderLayout(0, 12));
+ dialog.setResizable(false);
+ dialog.setPreferredSize(new Dimension(400, 800));
+ dialog.setSize(new Dimension(400, 800));
+ dialog.setMinimumSize(new Dimension(400, 800));
+
+ JPanel content = new JPanel();
+ content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
+ content.setBorder(BorderFactory.createEmptyBorder(20, 24, 20, 24));
+ dialog.add(content, BorderLayout.CENTER);
+
+ JLabel descriptionLabel = new JLabel("闂鎻忚堪锛�");
+ descriptionLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 13));
+ descriptionLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ content.add(descriptionLabel);
+
+ content.add(Box.createRigidArea(new Dimension(0, 8)));
+
+ JTextArea descriptionArea = new JTextArea(5, 30);
+ descriptionArea.setLineWrap(true);
+ descriptionArea.setWrapStyleWord(true);
+ descriptionArea.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ JScrollPane scrollPane = new JScrollPane(descriptionArea);
+ scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
+ content.add(scrollPane);
+
+ content.add(Box.createRigidArea(new Dimension(0, 16)));
+
+ JPanel photoControls = new JPanel(new FlowLayout(FlowLayout.LEFT, 12, 0));
+ photoControls.setAlignmentX(Component.LEFT_ALIGNMENT);
+ JLabel photoLabel = new JLabel("閫夋嫨鐓х墖锛堟渶澶�6寮狅級锛�");
+ photoLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 13));
+ JButton selectPhotosButton = new JButton("閫夋嫨鐓х墖");
+ selectPhotosButton.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
+ selectPhotosButton.setFocusPainted(false);
+ photoControls.add(photoLabel);
+ photoControls.add(selectPhotosButton);
+ content.add(photoControls);
+
+ content.add(Box.createRigidArea(new Dimension(0, 20)));
+
+ JPanel photoGrid = new JPanel(new GridLayout(2, 3, 12, 12));
+ photoGrid.setAlignmentX(Component.LEFT_ALIGNMENT);
+ photoGrid.setMaximumSize(new Dimension(Integer.MAX_VALUE, 220));
+ List<JLabel> photoSlots = new ArrayList<>();
+ for (int i = 0; i < 6; i++) {
+ JLabel slot = buildPhotoSlot();
+ photoGrid.add(slot);
+ photoSlots.add(slot);
+ }
+ content.add(photoGrid);
+
+ content.add(Box.createRigidArea(new Dimension(0, 20)));
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ JButton cancelButton = new JButton("鏀惧純");
+ JButton submitButton = new JButton("鎻愪氦");
+ submitButton.setBackground(THEME_COLOR);
+ submitButton.setForeground(Color.WHITE);
+ submitButton.setFocusPainted(false);
+ cancelButton.setFocusPainted(false);
+
+ buttonPanel.add(cancelButton);
+ buttonPanel.add(submitButton);
+ dialog.add(buttonPanel, BorderLayout.SOUTH);
+
+ List<File> selectedPhotos = new ArrayList<>();
+
+ selectPhotosButton.addActionListener(e -> {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setMultiSelectionEnabled(true);
+ chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ chooser.setFileFilter(new FileNameExtensionFilter("鍥剧墖鏂囦欢", "jpg", "jpeg", "png", "bmp", "gif", "webp"));
+
+ if (chooser.showOpenDialog(dialog) == JFileChooser.APPROVE_OPTION) {
+ File[] files = chooser.getSelectedFiles();
+ if (files != null) {
+ if (selectedPhotos.size() >= 6) {
+ JOptionPane.showMessageDialog(dialog, "宸茶揪鍒�6寮犵収鐗囦笂闄�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+ for (File file : files) {
+ if (selectedPhotos.size() >= 6) {
+ break;
+ }
+ if (file != null && file.exists() && file.isFile()) {
+ selectedPhotos.add(file);
+ }
+ }
+ updatePhotoPreview(photoSlots, selectedPhotos);
+ }
+ }
+ });
+
+ cancelButton.addActionListener(e -> dialog.dispose());
+
+ submitButton.addActionListener(e -> {
+ String description = descriptionArea.getText() != null ? descriptionArea.getText().trim() : "";
+ if (description.isEmpty()) {
+ JOptionPane.showMessageDialog(dialog, "璇峰~鍐欓棶棰樻弿杩�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ descriptionArea.requestFocusInWindow();
+ return;
+ }
+
+ // 鎻愪氦閫昏緫鍗犱綅
+ JOptionPane.showMessageDialog(dialog,
+ "鍙嶉宸叉彁浜わ紝鎰熻阿鎮ㄧ殑鏀寔锛乗n鎻忚堪瀛楁暟锛�" + description.length() + "锛岀収鐗囨暟閲忥細" + selectedPhotos.size(),
+ "鎻愪氦鎴愬姛",
+ JOptionPane.INFORMATION_MESSAGE);
+ dialog.dispose();
+ });
+
+ dialog.pack();
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+ }
+
+ private JLabel buildPhotoSlot() {
+ JLabel slot = new JLabel();
+ slot.setPreferredSize(new Dimension(100, 100));
+ slot.setMinimumSize(new Dimension(100, 100));
+ slot.setOpaque(true);
+ slot.setBackground(new Color(245, 245, 245));
+ slot.setHorizontalAlignment(SwingConstants.CENTER);
+ slot.setVerticalAlignment(SwingConstants.CENTER);
+ slot.setBorder(BorderFactory.createLineBorder(new Color(220, 220, 220), 1));
+ slot.setText("+");
+ slot.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 18));
+ slot.setForeground(new Color(180, 180, 180));
+ return slot;
+ }
+
+ private void updatePhotoPreview(List<JLabel> slots, List<File> photos) {
+ for (int i = 0; i < slots.size(); i++) {
+ JLabel slot = slots.get(i);
+ if (i < photos.size()) {
+ File photo = photos.get(i);
+ ImageIcon icon = new ImageIcon(photo.getAbsolutePath());
+ Image scaled = icon.getImage().getScaledInstance(100, 100, Image.SCALE_SMOOTH);
+ slot.setIcon(new ImageIcon(scaled));
+ slot.setText(null);
+ } else {
+ slot.setIcon(null);
+ slot.setText("+");
+ }
+ }
+ }
private void checkForUpdates() {
// 妯℃嫙妫�鏌ユ洿鏂拌繃绋�
diff --git a/src/set/Setsys.java b/src/set/Setsys.java
index ff202d4..c7b75d7 100644
--- a/src/set/Setsys.java
+++ b/src/set/Setsys.java
@@ -7,6 +7,7 @@
private String mowerId;
private String cuttingWidth;
private String simCardNumber;
+ private String handheldMarkerId;
private String firmwareVersion;
private String appVersion;
@@ -17,10 +18,11 @@
}
// 甯﹀弬鏋勯�犳柟娉�
- public Setsys(String mowerId, String cuttingWidth, String simCardNumber, String firmwareVersion, String appVersion) {
+ public Setsys(String mowerId, String cuttingWidth, String simCardNumber, String handheldMarkerId, String firmwareVersion, String appVersion) {
this.mowerId = mowerId;
this.cuttingWidth = cuttingWidth;
this.simCardNumber = simCardNumber;
+ this.handheldMarkerId = handheldMarkerId;
this.firmwareVersion = firmwareVersion;
this.appVersion = appVersion;
}
@@ -50,6 +52,14 @@
this.simCardNumber = simCardNumber;
}
+ public String getHandheldMarkerId() {
+ return handheldMarkerId;
+ }
+
+ public void setHandheldMarkerId(String handheldMarkerId) {
+ this.handheldMarkerId = handheldMarkerId;
+ }
+
public String getFirmwareVersion() {
return firmwareVersion;
}
@@ -79,6 +89,7 @@
this.mowerId = "-1".equals(props.getProperty("mowerId")) ? null : props.getProperty("mowerId");
this.cuttingWidth = "-1".equals(props.getProperty("cuttingWidth")) ? null : props.getProperty("cuttingWidth");
this.simCardNumber = "-1".equals(props.getProperty("simCardNumber")) ? null : props.getProperty("simCardNumber");
+ this.handheldMarkerId = "-1".equals(props.getProperty("handheldMarkerId")) ? null : props.getProperty("handheldMarkerId");
this.firmwareVersion = "-1".equals(props.getProperty("firmwareVersion")) ? null : props.getProperty("firmwareVersion");
this.appVersion = "-1".equals(props.getProperty("appVersion")) ? null : props.getProperty("appVersion");
@@ -112,6 +123,9 @@
case "simCardNumber":
this.simCardNumber = value;
break;
+ case "handheldMarkerId":
+ this.handheldMarkerId = value;
+ break;
case "firmwareVersion":
this.firmwareVersion = value;
break;
@@ -161,6 +175,7 @@
this.mowerId = null;
this.cuttingWidth = null;
this.simCardNumber = null;
+ this.handheldMarkerId = null;
this.firmwareVersion = null;
this.appVersion = null;
}
@@ -173,6 +188,7 @@
System.out.println("mowerId: " + (mowerId != null ? mowerId : "鏈缃�"));
System.out.println("cuttingWidth: " + (cuttingWidth != null ? cuttingWidth : "鏈缃�"));
System.out.println("simCardNumber: " + (simCardNumber != null ? simCardNumber : "鏈缃�"));
+ System.out.println("handheldMarkerId: " + (handheldMarkerId != null ? handheldMarkerId : "鏈缃�"));
System.out.println("firmwareVersion: " + (firmwareVersion != null ? firmwareVersion : "鏈缃�"));
System.out.println("appVersion: " + (appVersion != null ? appVersion : "鏈缃�"));
}
@@ -205,6 +221,7 @@
"mowerId='" + mowerId + '\'' +
", cuttingWidth='" + cuttingWidth + '\'' +
", simCardNumber='" + simCardNumber + '\'' +
+ ", handheldMarkerId='" + handheldMarkerId + '\'' +
", firmwareVersion='" + firmwareVersion + '\'' +
", appVersion='" + appVersion + '\'' +
'}';
diff --git a/src/udpdell/UDPServer.java b/src/udpdell/UDPServer.java
index 181086a..1ee1bde 100644
--- a/src/udpdell/UDPServer.java
+++ b/src/udpdell/UDPServer.java
@@ -69,7 +69,26 @@
}
}
+ public static void processIncomingMessage(String message) {
+ String[] fields = message.split(",");
+ // 妫�鏌ュ瓧娈垫暟閲忔槸鍚﹀畬鏁�
+ if (fields.length != 21) {
+ System.err.println("Invalid message format, expected 21 fields but got " + fields.length);
+ return;
+ }
+ // 妫�鏌ュ寘澶存槸鍚︽纭�
+ if (!fields[0].equals("$GNGGA")) {
+ System.err.println("Invalid message header: " + fields[0]);
+ return;
+ }
+ System.out.println("鏀跺埌浜嗗樊鍒嗘暟鎹細" + message);
+ Coordinate.parseGNGGAToCoordinateList(message);
+ int count = Coordinate.coordinates.size();
+ System.out.println("savenum:" + count);
+
+ Device.updateFromGNGGA(message, fields[15]);
+ }
private static class PacketProcessor implements Runnable {
private final DatagramPacket packet;
@@ -78,60 +97,20 @@
this.packet = packet;
}
+ @Override
public void run() {
String receivedData = new String(packet.getData(), 0, packet.getLength());
// 澶勭悊鍙兘鐨勮繛鍖呮儏鍐�
String[] messages = receivedData.split("\\$");
for (String message : messages) {
- if (message.isEmpty()) continue;
+ if (message.isEmpty()) {
+ continue;
+ }
// 閲嶆柊娣诲姞$绗﹀彿浠ヤ究缁熶竴澶勭悊
String fullMessage = "$" + message;
- processMessage(fullMessage);
+ processIncomingMessage(fullMessage);
}
}
-
- private void processMessage(String message) {
- String[] fields = message.split(",");
- // 妫�鏌ュ瓧娈垫暟閲忔槸鍚﹀畬鏁�
- if (fields.length != 21) {
- System.err.println("Invalid message format, expected 21 fields but got " + fields.length);
- return;
- }
-
- // 妫�鏌ュ寘澶存槸鍚︽纭�
- if (!fields[0].equals("$GNGGA")) {
- System.err.println("Invalid message header: " + fields[0]);
- return;
- }
- System.out.println("鏀跺埌浜嗗樊鍒嗘暟鎹細"+message);
- Coordinate.parseGNGGAToCoordinateList(message);
- int count= Coordinate.coordinates.size();
- 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("----------------------------------------");
- }
}
}
\ No newline at end of file
diff --git a/src/zhangaiwu/Obstacledraw.java b/src/zhangaiwu/Obstacledraw.java
index a01e0e0..e5d3223 100644
--- a/src/zhangaiwu/Obstacledraw.java
+++ b/src/zhangaiwu/Obstacledraw.java
@@ -210,21 +210,27 @@
return;
}
- // 璁$畻鏍囩浣嶇疆锛堜腑蹇冪偣锛�
- double centerX = 0;
- double centerY = 0;
-
- for (Obstacledge.XYCoordinate coord : xyCoords) {
- centerX += coord.getX();
- centerY += coord.getY();
+ double centerX;
+ double centerY;
+
+ Obstacledge.ObstacleShape shape = obstacle.getShape();
+ if (shape == Obstacledge.ObstacleShape.CIRCLE) {
+ Obstacledge.XYCoordinate centerCoord = xyCoords.get(0);
+ centerX = centerCoord.getX();
+ centerY = centerCoord.getY();
+ } else {
+ Point2D.Double centroid = computePolygonCentroid(xyCoords);
+ centerX = centroid.x;
+ centerY = centroid.y;
}
- centerX /= xyCoords.size();
- centerY /= xyCoords.size();
-
- // 鑾峰彇闅滅鐗╁悕绉板拰褰㈢姸
+ // 鑾峰彇闅滅鐗╁悕绉�
String obstacleName = obstacle.getObstacleName();
- String shapeDesc = obstacle.getShape().getDescription();
+ if (obstacleName == null || obstacleName.trim().isEmpty()) {
+ obstacleName = "闅滅鐗�";
+ } else {
+ obstacleName = obstacleName.trim();
+ }
// 璁剧疆瀛椾綋鍜岄鑹�
g2d.setColor(OBSTACLE_LABEL_COLOR);
@@ -236,7 +242,7 @@
g2d.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, fontSize));
// 缁樺埗鏍囩
- String label = String.format("%s(%s)", obstacleName, shapeDesc);
+ String label = obstacleName;
FontMetrics metrics = g2d.getFontMetrics();
int textWidth = metrics.stringWidth(label);
int textHeight = metrics.getHeight();
@@ -247,6 +253,41 @@
g2d.drawString(label, textX, textY);
}
+
+ private static Point2D.Double computePolygonCentroid(List<Obstacledge.XYCoordinate> xyCoords) {
+ double area = 0.0;
+ double cx = 0.0;
+ double cy = 0.0;
+ 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);
+ double x0 = current.getX();
+ double y0 = current.getY();
+ double x1 = next.getX();
+ double y1 = next.getY();
+ double cross = x0 * y1 - x1 * y0;
+ area += cross;
+ cx += (x0 + x1) * cross;
+ cy += (y0 + y1) * cross;
+ }
+
+ double areaFactor = area * 0.5;
+ if (Math.abs(areaFactor) < 1e-9) {
+ double avgX = 0.0;
+ double avgY = 0.0;
+ for (Obstacledge.XYCoordinate coord : xyCoords) {
+ avgX += coord.getX();
+ avgY += coord.getY();
+ }
+ int size = Math.max(1, xyCoords.size());
+ return new Point2D.Double(avgX / size, avgY / size);
+ }
+
+ double centroidX = cx / (6.0 * areaFactor);
+ double centroidY = cy / (6.0 * areaFactor);
+ return new Point2D.Double(centroidX, centroidY);
+ }
/**
* 妫�鏌ョ偣鏄惁鍦ㄩ殰纰嶇墿鍐�
diff --git a/src/zhangaiwu/yulanzhangaiwu.java b/src/zhangaiwu/yulanzhangaiwu.java
new file mode 100644
index 0000000..c79ee83
--- /dev/null
+++ b/src/zhangaiwu/yulanzhangaiwu.java
@@ -0,0 +1,381 @@
+package zhangaiwu;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Path2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.swing.SwingUtilities;
+
+import zhuye.Coordinate;
+import zhuye.Shouye;
+
+/**
+ * 鍦ㄥ湴鍥句笂瀹炴椂棰勮姝e湪缁樺埗鐨勯殰纰嶇墿銆�
+ */
+public final class yulanzhangaiwu {
+ private static final Object LOCK = new Object();
+ private static final double METERS_PER_DEGREE_LAT = 111320.0d;
+ private static final Color PREVIEW_LINE_COLOR = new Color(0, 123, 255, 200);
+ private static final Color PREVIEW_FILL_COLOR = new Color(0, 123, 255, 70);
+ private static final Color PREVIEW_POINT_COLOR = new Color(255, 77, 0, 210);
+ private static final double PREVIEW_POINT_SIZE = 0.18d;
+
+ private static boolean active;
+ private static String shapeKey;
+ private static String baseStation;
+ private static List<double[]> localPoints = Collections.emptyList();
+ private static CircleState circleState;
+
+ private yulanzhangaiwu() {
+ }
+
+ public static void startPreview(String shape, String baseStationCoordinates) {
+ synchronized (LOCK) {
+ active = true;
+ shapeKey = shape != null ? shape.toLowerCase(Locale.ROOT) : null;
+ baseStation = baseStationCoordinates;
+ localPoints = Collections.emptyList();
+ circleState = null;
+ }
+ requestRepaint();
+ }
+
+ public static void stopPreview() {
+ synchronized (LOCK) {
+ active = false;
+ shapeKey = null;
+ baseStation = null;
+ localPoints = Collections.emptyList();
+ circleState = null;
+ }
+ requestRepaint();
+ }
+
+ public static void renderPreview(Graphics2D g2d, double scale) {
+ PreviewSnapshot snapshot = snapshot();
+ if (!snapshot.active) {
+ return;
+ }
+
+ Color originalColor = g2d.getColor();
+ Stroke originalStroke = g2d.getStroke();
+ try {
+ Stroke dashedStroke = new BasicStroke(
+ (float) (1.6f / scale),
+ BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND,
+ 1f,
+ new float[]{(float) (6f / scale), (float) (4f / scale)},
+ 0f
+ );
+ Stroke solidStroke = new BasicStroke((float) (1.4f / scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+
+ if ("circle".equals(snapshot.shape) && snapshot.circle != null) {
+ drawCirclePreview(g2d, snapshot, dashedStroke, scale);
+ } else {
+ drawPolygonPreview(g2d, snapshot, dashedStroke, solidStroke, scale);
+ }
+ } finally {
+ g2d.setColor(originalColor);
+ g2d.setStroke(originalStroke);
+ }
+ }
+
+ private static void drawCirclePreview(Graphics2D g2d, PreviewSnapshot snapshot, Stroke dashedStroke, double scale) {
+ CircleState circle = snapshot.circle;
+ if (circle.radius <= 0) {
+ drawSamplePoints(g2d, snapshot.points, scale);
+ return;
+ }
+
+ double diameter = circle.radius * 2.0;
+ double x = circle.centerX - circle.radius;
+ double y = circle.centerY - circle.radius;
+ Shape outline = new Ellipse2D.Double(x, y, diameter, diameter);
+
+ g2d.setColor(PREVIEW_FILL_COLOR);
+ g2d.fill(outline);
+
+ g2d.setColor(PREVIEW_LINE_COLOR);
+ g2d.setStroke(dashedStroke);
+ g2d.draw(outline);
+
+ drawPoint(g2d, circle.centerX, circle.centerY, scale, PREVIEW_POINT_COLOR);
+ if (circle.referencePoint != null) {
+ drawPoint(g2d, circle.referencePoint[0], circle.referencePoint[1], scale, PREVIEW_LINE_COLOR.darker());
+ }
+
+ drawSamplePoints(g2d, snapshot.points, scale);
+ }
+
+ private static void drawPolygonPreview(Graphics2D g2d, PreviewSnapshot snapshot, Stroke dashedStroke, Stroke solidStroke, double scale) {
+ List<double[]> pts = snapshot.points;
+ if (pts.isEmpty()) {
+ return;
+ }
+
+ Path2D.Double path = new Path2D.Double();
+ double[] first = pts.get(0);
+ path.moveTo(first[0], first[1]);
+ for (int i = 1; i < pts.size(); i++) {
+ double[] pt = pts.get(i);
+ path.lineTo(pt[0], pt[1]);
+ }
+
+ if (pts.size() >= 3) {
+ path.closePath();
+ g2d.setColor(PREVIEW_FILL_COLOR);
+ g2d.fill(path);
+ }
+
+ g2d.setColor(PREVIEW_LINE_COLOR);
+ g2d.setStroke(pts.size() >= 3 ? dashedStroke : solidStroke);
+ g2d.draw(path);
+
+ drawSamplePoints(g2d, pts, scale);
+ }
+
+ private static void drawSamplePoints(Graphics2D g2d, List<double[]> points, double scale) {
+ if (points.isEmpty()) {
+ return;
+ }
+ for (double[] pt : points) {
+ drawPoint(g2d, pt[0], pt[1], scale, PREVIEW_POINT_COLOR);
+ }
+ }
+
+ private static void drawPoint(Graphics2D g2d, double x, double y, double scale, Color color) {
+ double size = PREVIEW_POINT_SIZE;
+ double half = size / 2.0;
+ Shape dot = new Ellipse2D.Double(x - half, y - half, size, size);
+ g2d.setColor(color);
+ g2d.fill(dot);
+ }
+
+ private static PreviewSnapshot snapshot() {
+ synchronized (LOCK) {
+ if (!active || shapeKey == null || baseStation == null) {
+ return PreviewSnapshot.inactive();
+ }
+ refreshLocked();
+ return PreviewSnapshot.of(shapeKey, localPoints, circleState);
+ }
+ }
+
+ private static void refreshLocked() {
+ String base = baseStation;
+ if (base == null) {
+ localPoints = Collections.emptyList();
+ circleState = null;
+ return;
+ }
+
+ double[] baseLatLon = parseBaseStation(base);
+ if (baseLatLon == null) {
+ localPoints = Collections.emptyList();
+ circleState = null;
+ return;
+ }
+
+ List<Coordinate> copy;
+ synchronized (Coordinate.coordinates) {
+ copy = new ArrayList<>(Coordinate.coordinates);
+ }
+ if (copy.isEmpty()) {
+ localPoints = Collections.emptyList();
+ circleState = null;
+ return;
+ }
+
+ List<double[]> converted = new ArrayList<>(copy.size());
+ for (Coordinate coord : copy) {
+ double lat = parseDMToDecimal(coord.getLatitude(), coord.getLatDirection());
+ double lon = parseDMToDecimal(coord.getLongitude(), coord.getLonDirection());
+ if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
+ continue;
+ }
+ converted.add(convertLatLonToLocal(lat, lon, baseLatLon[0], baseLatLon[1]));
+ }
+
+ localPoints = converted.isEmpty() ? Collections.emptyList() : converted;
+
+ if ("circle".equals(shapeKey)) {
+ circleState = fitCircle(localPoints);
+ } else {
+ circleState = null;
+ }
+ }
+
+ private static double[] parseBaseStation(String baseStationCoordinates) {
+ String[] parts = baseStationCoordinates.split(",");
+ if (parts.length < 4) {
+ return null;
+ }
+ double lat = parseDMToDecimal(parts[0], parts[1]);
+ double lon = parseDMToDecimal(parts[2], parts[3]);
+ if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
+ return null;
+ }
+ return new double[]{lat, lon};
+ }
+
+ private static double parseDMToDecimal(String dmm, String direction) {
+ if (dmm == null || dmm.trim().isEmpty()) {
+ return Double.NaN;
+ }
+ try {
+ String trimmed = dmm.trim();
+ int dotIndex = trimmed.indexOf('.');
+ if (dotIndex < 2) {
+ return Double.NaN;
+ }
+ int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
+ double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
+ double decimal = degrees + minutes / 60.0;
+ if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
+ decimal = -decimal;
+ }
+ return decimal;
+ } catch (NumberFormatException ex) {
+ return Double.NaN;
+ }
+ }
+
+ private static double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
+ double deltaLat = lat - baseLat;
+ double deltaLon = lon - baseLon;
+ double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
+ double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
+ double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
+ return new double[]{eastMeters, northMeters};
+ }
+
+ private static CircleState fitCircle(List<double[]> points) {
+ if (points == null || points.size() < 3) {
+ return null;
+ }
+ CircleState best = null;
+ double bestScore = 0.0;
+ int n = points.size();
+ for (int i = 0; i < n - 2; i++) {
+ double[] p1 = points.get(i);
+ for (int j = i + 1; j < n - 1; j++) {
+ double[] p2 = points.get(j);
+ for (int k = j + 1; k < n; k++) {
+ double[] p3 = points.get(k);
+ CircleState candidate = circleFromThreePoints(p1, p2, p3);
+ if (candidate == null || candidate.radius <= 0) {
+ continue;
+ }
+ double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3)));
+ if (minEdge > bestScore) {
+ bestScore = minEdge;
+ best = candidate;
+ }
+ }
+ }
+ }
+ return best;
+ }
+
+ private static CircleState circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
+ double x1 = p1[0];
+ double y1 = p1[1];
+ double x2 = p2[0];
+ double y2 = p2[1];
+ double x3 = p3[0];
+ double y3 = p3[1];
+
+ double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
+ double d = 2.0 * a;
+ if (Math.abs(d) < 1e-6) {
+ return null;
+ }
+
+ double x1Sq = x1 * x1 + y1 * y1;
+ double x2Sq = x2 * x2 + y2 * y2;
+ double x3Sq = x3 * x3 + y3 * y3;
+
+ double ux = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / d;
+ double uy = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / d;
+ double radius = Math.hypot(ux - x1, uy - y1);
+ if (!Double.isFinite(ux) || !Double.isFinite(uy) || !Double.isFinite(radius)) {
+ return null;
+ }
+ if (radius < 0.05) {
+ return null;
+ }
+ double[] reference = new double[]{x1, y1};
+ return new CircleState(ux, uy, radius, reference);
+ }
+
+ private static double distance(double[] a, double[] b) {
+ double dx = a[0] - b[0];
+ double dy = a[1] - b[1];
+ return Math.hypot(dx, dy);
+ }
+
+ private static void requestRepaint() {
+ SwingUtilities.invokeLater(() -> {
+ Shouye shouye = Shouye.getInstance();
+ if (shouye != null) {
+ shouye.repaint();
+ }
+ });
+ }
+
+ private static final class CircleState {
+ final double centerX;
+ final double centerY;
+ final double radius;
+ final double[] referencePoint;
+
+ CircleState(double centerX, double centerY, double radius, double[] referencePoint) {
+ this.centerX = centerX;
+ this.centerY = centerY;
+ this.radius = radius;
+ this.referencePoint = referencePoint;
+ }
+ }
+
+ private static final class PreviewSnapshot {
+ final boolean active;
+ final String shape;
+ final List<double[]> points;
+ final CircleState circle;
+
+ private PreviewSnapshot(boolean active, String shape, List<double[]> points, CircleState circle) {
+ this.active = active;
+ this.shape = shape;
+ this.points = points;
+ this.circle = circle;
+ }
+
+ static PreviewSnapshot inactive() {
+ return new PreviewSnapshot(false, null, Collections.emptyList(), null);
+ }
+
+ static PreviewSnapshot of(String shape, List<double[]> pts, CircleState circle) {
+ List<double[]> safePoints = new ArrayList<>(pts.size());
+ for (double[] pt : pts) {
+ safePoints.add(new double[]{pt[0], pt[1]});
+ }
+ CircleState safeCircle = null;
+ if (circle != null) {
+ double[] refCopy = circle.referencePoint != null
+ ? new double[]{circle.referencePoint[0], circle.referencePoint[1]}
+ : null;
+ safeCircle = new CircleState(circle.centerX, circle.centerY, circle.radius, refCopy);
+ }
+ return new PreviewSnapshot(true, shape, safePoints, safeCircle);
+ }
+ }
+}
diff --git a/src/zhuye/Bluelink.java b/src/zhuye/Bluelink.java
new file mode 100644
index 0000000..cc2cc68
--- /dev/null
+++ b/src/zhuye/Bluelink.java
@@ -0,0 +1,34 @@
+package zhuye;
+
+/**
+ * 钃濈墮閫氫俊鍗犱綅瀹炵幇銆傛闈㈢幆澧冧腑浠呬繚鐣� UI 鍥炬爣涓庣姸鎬佸垏鎹紝
+ * 鐪熸鐨勮摑鐗欓�昏緫灏嗗湪 Android 鐜涓嬪疄鐜般��
+ */
+public final class Bluelink {
+ private static boolean connected;
+
+ private Bluelink() {
+ }
+
+ /**
+ * 浼繛鎺ユ柟娉曪紝浠呭垏鎹负鈥滃凡杩炴帴鈥濈姸鎬併��
+ */
+ public static boolean connect() {
+ connected = true;
+ return true;
+ }
+
+ /**
+ * 浼柇寮�鏂规硶锛屼粎鍒囨崲涓衡�滄湭杩炴帴鈥濈姸鎬併��
+ */
+ public static void disconnect() {
+ connected = false;
+ }
+
+ /**
+ * 杩斿洖褰撳墠浼繛鎺ョ姸鎬併��
+ */
+ public static boolean isConnected() {
+ return connected;
+ }
+}
diff --git a/src/zhuye/Coordinate.java b/src/zhuye/Coordinate.java
index cb44765..9fa4fc8 100644
--- a/src/zhuye/Coordinate.java
+++ b/src/zhuye/Coordinate.java
@@ -20,6 +20,8 @@
Coordinate.isStartSaveGngga = isStartSaveGngga;
}
+ private static volatile String activeDeviceIdFilter;
+
private String latitude; // 绾害锛堝害鍒嗘牸寮忥級
private String latDirection; // 绾害鏂瑰悜
private String longitude; // 缁忓害锛堝害鍒嗘牸寮忥級
@@ -97,6 +99,11 @@
continue;
}
+ String deviceId = fields.length > 15 ? sanitizeDeviceId(fields[15]) : null;
+ if (!isDeviceAccepted(deviceId)) {
+ continue;
+ }
+
// 妫�鏌ュ畾浣嶈川閲�
String fixQualityStr = fields[6];
if (fixQualityStr.isEmpty()) continue;
@@ -144,6 +151,45 @@
}
}
+ private static boolean isDeviceAccepted(String deviceId) {
+ String filter = activeDeviceIdFilter;
+ if (filter == null || filter.isEmpty()) {
+ return true;
+ }
+ if (deviceId == null || deviceId.isEmpty()) {
+ return false;
+ }
+ return filter.equalsIgnoreCase(deviceId);
+ }
+
+ private static String sanitizeDeviceId(String raw) {
+ if (raw == null) {
+ return null;
+ }
+ String trimmed = raw.trim();
+ if (trimmed.isEmpty()) {
+ return null;
+ }
+ int starIndex = trimmed.indexOf('*');
+ if (starIndex >= 0) {
+ trimmed = trimmed.substring(0, starIndex);
+ }
+ return trimmed;
+ }
+
+ public static void setActiveDeviceIdFilter(String deviceId) {
+ if (deviceId == null) {
+ activeDeviceIdFilter = null;
+ return;
+ }
+ String trimmed = deviceId.trim();
+ activeDeviceIdFilter = trimmed.isEmpty() ? null : trimmed;
+ }
+
+ public static void clearActiveDeviceIdFilter() {
+ activeDeviceIdFilter = null;
+ }
+
/**
* 浠庢牴鐩綍鐨凣NGGA.txt鏂囦欢鍔犺浇鏁版嵁骞惰В鏋愪繚瀛樺埌coordinates闆嗗悎涓�
*
diff --git a/src/zhuye/MapRenderer.java b/src/zhuye/MapRenderer.java
index aebc291..9cb5475 100644
--- a/src/zhuye/MapRenderer.java
+++ b/src/zhuye/MapRenderer.java
@@ -3,21 +3,27 @@
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
+import java.awt.FontMetrics;
import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import gecaoji.Device;
import gecaoji.Gecaoji;
import dikuai.Dikuaiguanli;
import dikuai.Dikuai;
import zhangaiwu.Obstacledraw;
import zhangaiwu.Obstacledge;
+import zhangaiwu.yulanzhangaiwu;
/**
* 鍦板浘娓叉煋鍣� - 璐熻矗鍧愭爣绯荤粯鍒躲�佽鍥惧彉鎹㈢瓑鍔熻兘
@@ -37,6 +43,8 @@
private static final Color GRASS_BORDER_COLOR = new Color(60, 179, 113);
private static final Color BOUNDARY_POINT_COLOR = new Color(128, 0, 128);
private static final Color BOUNDARY_LABEL_COLOR = Color.BLACK;
+ private static final Color CIRCLE_SAMPLE_COLOR = new Color(220, 20, 60, 230);
+ private static final double CIRCLE_SAMPLE_SIZE = 0.54d;
private static final double BOUNDARY_POINT_MERGE_THRESHOLD = 0.05;
private static final SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -49,6 +57,7 @@
private List<Obstacledge.Obstacle> currentObstacles;
private Rectangle2D.Double obstacleBounds;
private String selectedObstacleName;
+ private String currentObstacleLandNumber;
private String boundaryName;
private boolean boundaryPointsVisible;
private String currentBoundaryLandNumber;
@@ -65,6 +74,8 @@
private JLabel realtimeSpeedValueLabel;
private JLabel headingValueLabel;
private JLabel updateTimeValueLabel;
+ private CircleCaptureOverlay circleCaptureOverlay;
+ private final List<double[]> circleSampleMarkers = new ArrayList<>();
public MapRenderer(JPanel visualizationPanel) {
this.visualizationPanel = visualizationPanel;
@@ -218,6 +229,16 @@
Obstacledraw.drawObstacles(g2d, currentObstacles, scale, selectedObstacleName);
}
+ yulanzhangaiwu.renderPreview(g2d, scale);
+
+ if (!circleSampleMarkers.isEmpty()) {
+ drawCircleSampleMarkers(g2d, circleSampleMarkers, scale);
+ }
+
+ if (circleCaptureOverlay != null) {
+ drawCircleCaptureOverlay(g2d, circleCaptureOverlay, scale);
+ }
+
if (hasPlannedPath) {
drawCurrentPlannedPath(g2d);
}
@@ -296,6 +317,137 @@
lujingdraw.drawPlannedPath(g2d, currentPlannedPath, scale);
}
+ private void drawCircleSampleMarkers(Graphics2D g2d, List<double[]> markers, double scale) {
+ if (markers == null || markers.isEmpty()) {
+ return;
+ }
+ Shape markerShape;
+ double half = CIRCLE_SAMPLE_SIZE / 2.0;
+ g2d.setColor(CIRCLE_SAMPLE_COLOR);
+ g2d.setStroke(new BasicStroke((float) (1.2f / scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+ Font originalFont = g2d.getFont();
+ float baseSize = (float) Math.max(12f / scale, 9f);
+ float reducedSize = Math.max(baseSize / 3f, 3f);
+ Font labelFont = originalFont.deriveFont(reducedSize);
+ g2d.setFont(labelFont);
+ FontMetrics metrics = g2d.getFontMetrics();
+ for (double[] pt : markers) {
+ if (pt == null || pt.length < 2 || !Double.isFinite(pt[0]) || !Double.isFinite(pt[1])) {
+ continue;
+ }
+ double x = pt[0];
+ double y = pt[1];
+ markerShape = new Ellipse2D.Double(x - half, y - half, CIRCLE_SAMPLE_SIZE, CIRCLE_SAMPLE_SIZE);
+ g2d.fill(markerShape);
+ String label = String.format(Locale.US, "%.2f,%.2f", x, y);
+ int textWidth = metrics.stringWidth(label);
+ float textX = (float) (x - textWidth / 2.0);
+ float textY = (float) (y - half - 0.2d) - metrics.getDescent();
+ g2d.setColor(new Color(33, 37, 41, 220));
+ g2d.drawString(label, textX, textY);
+ g2d.setColor(CIRCLE_SAMPLE_COLOR);
+ }
+ g2d.setFont(originalFont);
+ }
+
+ private void drawCircleCaptureOverlay(Graphics2D g2d, CircleCaptureOverlay overlay, double scale) {
+ double diameter = overlay.radius * 2.0;
+ Ellipse2D outline = new Ellipse2D.Double(
+ overlay.centerX - overlay.radius,
+ overlay.centerY - overlay.radius,
+ diameter,
+ diameter);
+
+ Color fillColor = new Color(255, 152, 0, 80);
+ Color borderColor = new Color(255, 87, 34, 230);
+ Color centerColor = new Color(46, 139, 87, 230);
+
+ g2d.setColor(fillColor);
+ g2d.fill(outline);
+
+ g2d.setColor(borderColor);
+ g2d.setStroke(new BasicStroke((float) (1.8f / scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+ g2d.draw(outline);
+
+ double markerSize = 0.18d;
+ double centerMarkerSize = 0.22d;
+
+ Ellipse2D centerMarker = new Ellipse2D.Double(
+ overlay.centerX - centerMarkerSize / 2.0,
+ overlay.centerY - centerMarkerSize / 2.0,
+ centerMarkerSize,
+ centerMarkerSize);
+ g2d.setColor(centerColor);
+ g2d.fill(centerMarker);
+ }
+
+ public void showCircleCaptureOverlay(double centerX, double centerY, double radius, List<double[]> samplePoints) {
+ List<double[]> copies = new ArrayList<>();
+ if (samplePoints != null) {
+ for (double[] pt : samplePoints) {
+ if (pt == null || pt.length < 2) {
+ continue;
+ }
+ copies.add(new double[]{pt[0], pt[1]});
+ }
+ }
+ circleCaptureOverlay = new CircleCaptureOverlay(centerX, centerY, radius, copies);
+ updateCircleSampleMarkers(samplePoints);
+ if (visualizationPanel != null) {
+ visualizationPanel.repaint();
+ }
+ }
+
+ public void clearCircleCaptureOverlay() {
+ circleCaptureOverlay = null;
+ if (visualizationPanel != null) {
+ visualizationPanel.repaint();
+ }
+ }
+
+ public void updateCircleSampleMarkers(List<double[]> samplePoints) {
+ circleSampleMarkers.clear();
+ if (samplePoints != null) {
+ for (double[] pt : samplePoints) {
+ if (pt == null || pt.length < 2) {
+ continue;
+ }
+ double x = pt[0];
+ double y = pt[1];
+ if (!Double.isFinite(x) || !Double.isFinite(y)) {
+ continue;
+ }
+ circleSampleMarkers.add(new double[]{x, y});
+ }
+ }
+ if (visualizationPanel != null) {
+ visualizationPanel.repaint();
+ }
+ }
+
+ public void clearCircleSampleMarkers() {
+ if (!circleSampleMarkers.isEmpty()) {
+ circleSampleMarkers.clear();
+ if (visualizationPanel != null) {
+ visualizationPanel.repaint();
+ }
+ }
+ }
+
+ private static final class CircleCaptureOverlay {
+ final double centerX;
+ final double centerY;
+ final double radius;
+ final List<double[]> samplePoints;
+
+ CircleCaptureOverlay(double centerX, double centerY, double radius, List<double[]> samplePoints) {
+ this.centerX = centerX;
+ this.centerY = centerY;
+ this.radius = radius;
+ this.samplePoints = samplePoints;
+ }
+ }
+
private void showMowerInfo() {
Device device = Device.getGecaoji();
if (device == null) {
@@ -771,18 +923,24 @@
}
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()) {
+ applyObstaclesToRenderer(parsed, landNumber);
+ }
+
+ public void setCurrentObstacles(List<Obstacledge.Obstacle> obstacles, String landNumber) {
+ List<Obstacledge.Obstacle> cloned = cloneObstacles(obstacles, landNumber);
+ applyObstaclesToRenderer(cloned, landNumber);
+ }
+
+ private void applyObstaclesToRenderer(List<Obstacledge.Obstacle> obstacles, String landNumber) {
+ List<Obstacledge.Obstacle> safeList = obstacles != null ? obstacles : Collections.emptyList();
+ String normalizedLand = (landNumber != null) ? landNumber.trim() : null;
+
+ if (normalizedLand != null && !normalizedLand.isEmpty()) {
+ safeList = filterObstaclesForLand(safeList, normalizedLand);
+ }
+
+ if (normalizedLand == null || normalizedLand.isEmpty()) {
clearObstacleData();
if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) {
resetView();
@@ -792,8 +950,19 @@
return;
}
- currentObstacles = parsed;
- obstacleBounds = convertObstacleBounds(Obstacledraw.getAllObstaclesBounds(parsed));
+ if (safeList.isEmpty()) {
+ clearObstacleData();
+ if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) {
+ resetView();
+ } else {
+ visualizationPanel.repaint();
+ }
+ return;
+ }
+
+ currentObstacles = Collections.unmodifiableList(new ArrayList<>(safeList));
+ currentObstacleLandNumber = normalizedLand;
+ obstacleBounds = convertObstacleBounds(Obstacledraw.getAllObstaclesBounds(currentObstacles));
selectedObstacleName = null;
if (!hasRenderableBoundary() && !hasRenderablePlannedPath() && obstacleBounds != null) {
@@ -807,10 +976,150 @@
}
}
+ private List<Obstacledge.Obstacle> cloneObstacles(List<Obstacledge.Obstacle> obstacles, String landNumber) {
+ List<Obstacledge.Obstacle> result = new ArrayList<>();
+ if (obstacles == null || obstacles.isEmpty()) {
+ return result;
+ }
+
+ String normalizedLandNumber = landNumber != null ? landNumber.trim() : null;
+ Set<String> usedNames = new LinkedHashSet<>();
+ int fallbackIndex = 1;
+
+ for (Obstacledge.Obstacle source : obstacles) {
+ if (source == null) {
+ continue;
+ }
+
+ if (normalizedLandNumber != null && !normalizedLandNumber.isEmpty()) {
+ String sourcePlotId = source.getPlotId();
+ if (sourcePlotId != null && !sourcePlotId.trim().isEmpty()
+ && !normalizedLandNumber.equalsIgnoreCase(sourcePlotId.trim())) {
+ continue;
+ }
+ }
+
+ Obstacledge.ObstacleShape shape = source.getShape();
+ List<Obstacledge.XYCoordinate> xySource = source.getXyCoordinates();
+ if (shape == null || xySource == null) {
+ continue;
+ }
+
+ List<Obstacledge.XYCoordinate> xyCopy = copyXYCoordinates(xySource);
+ if (shape == Obstacledge.ObstacleShape.CIRCLE && xyCopy.size() < 2) {
+ continue;
+ }
+ if (shape == Obstacledge.ObstacleShape.POLYGON && xyCopy.size() < 3) {
+ continue;
+ }
+
+ String desiredName = source.getObstacleName();
+ if (desiredName == null || desiredName.trim().isEmpty()) {
+ desiredName = "闅滅鐗�" + fallbackIndex++;
+ }
+ String uniqueName = ensureUniqueName(usedNames, desiredName.trim());
+
+ Obstacledge.Obstacle copy = new Obstacledge.Obstacle(
+ normalizedLandNumber != null ? normalizedLandNumber : source.getPlotId(),
+ uniqueName,
+ shape
+ );
+
+ copy.setXyCoordinates(xyCopy);
+
+ List<Obstacledge.DMCoordinate> dmCopy = copyDMCoordinates(source.getOriginalCoordinates());
+ if (dmCopy.isEmpty()) {
+ populateDummyOriginalCoordinates(copy, xyCopy.size());
+ } else {
+ copy.setOriginalCoordinates(dmCopy);
+ }
+
+ result.add(copy);
+ }
+
+ return result;
+ }
+
+ private List<Obstacledge.Obstacle> filterObstaclesForLand(List<Obstacledge.Obstacle> obstacles, String landNumber) {
+ if (obstacles == null || obstacles.isEmpty()) {
+ return Collections.emptyList();
+ }
+ if (landNumber == null || landNumber.trim().isEmpty()) {
+ return Collections.emptyList();
+ }
+ String normalized = landNumber.trim();
+ List<Obstacledge.Obstacle> filtered = new ArrayList<>();
+ for (Obstacledge.Obstacle obstacle : obstacles) {
+ if (obstacle == null) {
+ continue;
+ }
+ String plotId = obstacle.getPlotId();
+ if (plotId == null || plotId.trim().isEmpty()) {
+ filtered.add(obstacle);
+ continue;
+ }
+ if (normalized.equalsIgnoreCase(plotId.trim())) {
+ filtered.add(obstacle);
+ }
+ }
+ return filtered;
+ }
+
+ private String ensureUniqueName(Set<String> usedNames, String preferredName) {
+ String base = (preferredName == null || preferredName.trim().isEmpty()) ? "闅滅鐗�" : preferredName.trim();
+ String normalized = base.toLowerCase(Locale.ROOT);
+ if (usedNames.add(normalized)) {
+ return base;
+ }
+ int suffix = 2;
+ while (true) {
+ String attempt = base + suffix;
+ String attemptKey = attempt.toLowerCase(Locale.ROOT);
+ if (usedNames.add(attemptKey)) {
+ return attempt;
+ }
+ suffix++;
+ }
+ }
+
+ private List<Obstacledge.XYCoordinate> copyXYCoordinates(List<Obstacledge.XYCoordinate> source) {
+ List<Obstacledge.XYCoordinate> copy = new ArrayList<>();
+ if (source == null) {
+ return copy;
+ }
+ for (Obstacledge.XYCoordinate coord : source) {
+ if (coord == null) {
+ continue;
+ }
+ double x = coord.getX();
+ double y = coord.getY();
+ if (!Double.isFinite(x) || !Double.isFinite(y)) {
+ continue;
+ }
+ copy.add(new Obstacledge.XYCoordinate(x, y));
+ }
+ return copy;
+ }
+
+ private List<Obstacledge.DMCoordinate> copyDMCoordinates(List<Obstacledge.DMCoordinate> source) {
+ List<Obstacledge.DMCoordinate> copy = new ArrayList<>();
+ if (source == null) {
+ return copy;
+ }
+ for (Obstacledge.DMCoordinate coord : source) {
+ if (coord == null) {
+ continue;
+ }
+ copy.add(new Obstacledge.DMCoordinate(coord.getDegreeMinute(), coord.getDirection()));
+ }
+ return copy;
+ }
+
private void clearObstacleData() {
currentObstacles = null;
obstacleBounds = null;
selectedObstacleName = null;
+ currentObstacleLandNumber = null;
}
private List<Obstacledge.Obstacle> parseObstacles(String obstaclesData, String landNumber) {
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index 14565cb..4f20092 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -1,6 +1,7 @@
package zhuye;
import javax.swing.*;
+import javax.swing.Timer;
import baseStation.BaseStation;
import baseStation.BaseStationDialog;
@@ -14,6 +15,8 @@
import gecaoji.Device;
import set.Sets;
import udpdell.UDPServer;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
@@ -28,6 +31,7 @@
private final Color THEME_HOVER_COLOR = new Color(30, 107, 69);
private final Color BACKGROUND_COLOR = new Color(250, 250, 250);
private final Color PANEL_BACKGROUND = new Color(255, 255, 255);
+ private final Color STATUS_PAUSE_COLOR = new Color(255, 165, 0);
// 缁勪欢
private JPanel headerPanel;
@@ -38,11 +42,12 @@
// 鎸夐挳
private JButton startBtn;
- private JButton pauseBtn;
+ private JButton stopBtn;
private JButton legendBtn;
private JButton remoteBtn;
private JButton areaSelectBtn;
private JButton baseStationBtn;
+ private JButton bluetoothBtn;
// 瀵艰埅鎸夐挳
private JButton homeNavBtn;
@@ -76,6 +81,8 @@
private ImageIcon pauseIcon;
private ImageIcon pauseActiveIcon;
private ImageIcon endIcon;
+ private ImageIcon bluetoothIcon;
+ private ImageIcon bluetoothLinkedIcon;
private JPanel circleGuidancePanel;
private JLabel circleGuidanceLabel;
private JButton circleGuidancePrimaryButton;
@@ -84,6 +91,14 @@
private JDialog circleGuidanceDialog;
private boolean circleDialogMode;
private ComponentAdapter circleDialogOwnerAdapter;
+ private static final double METERS_PER_DEGREE_LAT = 111320.0d;
+ private final List<double[]> circleCapturedPoints = new ArrayList<>();
+ private double[] circleBaseLatLon;
+ private Timer circleDataMonitor;
+ private Coordinate lastCapturedCoordinate;
+ private boolean startButtonShowingPause = true;
+ private boolean stopButtonActive = false;
+ private boolean bluetoothConnected = false;
public Shouye() {
instance = this;
@@ -163,6 +178,11 @@
statusLabel = new JLabel("寰呮満");
statusLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 14));
statusLabel.setForeground(Color.GRAY);
+ statusLabel.addPropertyChangeListener("text", evt -> {
+ Object newValue = evt.getNewValue();
+ applyStatusLabelColor(newValue instanceof String ? (String) newValue : null);
+ });
+ applyStatusLabelColor(statusLabel.getText());
leftInfoPanel.add(areaNameLabel);
leftInfoPanel.add(statusLabel);
@@ -171,7 +191,7 @@
JPanel rightActionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
rightActionPanel.setBackground(PANEL_BACKGROUND);
- JButton notificationBtn = createIconButton("馃敂", 40);
+ bluetoothBtn = createBluetoothButton();
// 淇敼璁剧疆鎸夐挳锛氫娇鐢ㄥ浘鐗囨浛浠nicode鍥炬爣
JButton settingsBtn = new JButton();
@@ -203,7 +223,7 @@
// 娣诲姞璁剧疆鎸夐挳浜嬩欢
settingsBtn.addActionListener(e -> showSettingsDialog());
- rightActionPanel.add(notificationBtn);
+ rightActionPanel.add(bluetoothBtn);
rightActionPanel.add(settingsBtn);
headerPanel.add(leftInfoPanel, BorderLayout.WEST);
@@ -260,14 +280,14 @@
JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 20, 0));
buttonPanel.setBackground(PANEL_BACKGROUND);
- startBtn = createControlButton("寮�濮�", THEME_COLOR);
- applyButtonIcon(startBtn, "image/startzuoye.png");
- pauseBtn = createControlButton("鏆傚仠", Color.ORANGE);
- applyButtonIcon(pauseBtn, "image/zantingzuoye.png");
- pauseBtn.setEnabled(false);
+ startBtn = createControlButton("鏆傚仠", THEME_COLOR);
+ updateStartButtonAppearance();
+
+ stopBtn = createControlButton("缁撴潫", Color.ORANGE);
+ updateStopButtonIcon();
buttonPanel.add(startBtn);
- buttonPanel.add(pauseBtn);
+ buttonPanel.add(stopBtn);
controlPanel.add(buttonPanel, BorderLayout.CENTER);
}
@@ -310,6 +330,35 @@
return button;
}
+
+ private JButton createBluetoothButton() {
+ JButton button = new JButton();
+ button.setPreferredSize(new Dimension(40, 40));
+ button.setBackground(PANEL_BACKGROUND);
+ button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ button.setFocusPainted(false);
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ button.setBackground(new Color(240, 240, 240));
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ button.setBackground(PANEL_BACKGROUND);
+ }
+ });
+ ensureBluetoothIconsLoaded();
+ bluetoothConnected = Bluelink.isConnected();
+ ImageIcon initialIcon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
+ if (initialIcon != null) {
+ button.setIcon(initialIcon);
+ } else {
+ button.setText(bluetoothConnected ? "宸茶繛" : "钃濈墮");
+ }
+ return button;
+ }
private JButton createFunctionButton(String text, String icon) {
JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
@@ -424,6 +473,9 @@
// 鍔熻兘鎸夐挳浜嬩欢
legendBtn.addActionListener(e -> showLegendDialog());
+ if (bluetoothBtn != null) {
+ bluetoothBtn.addActionListener(e -> toggleBluetoothConnection());
+ }
remoteBtn.addActionListener(e -> showRemoteControlDialog());
areaSelectBtn.addActionListener(e -> {
// 鐐瑰嚮鈥滃湴鍧椻�濈洿鎺ユ墦寮�鍦板潡绠$悊瀵硅瘽妗嗭紙鑻ラ渶瑕佸彲浼犲叆鐗瑰畾鍦板潡缂栧彿锛�
@@ -432,8 +484,8 @@
baseStationBtn.addActionListener(e -> showBaseStationDialog());
// 鎺у埗鎸夐挳浜嬩欢
- startBtn.addActionListener(e -> startMowing());
- pauseBtn.addActionListener(e -> pauseMowing());
+ startBtn.addActionListener(e -> toggleStartPause());
+ stopBtn.addActionListener(e -> handleStopAction());
}
private void showSettingsDialog() {
@@ -563,16 +615,107 @@
}
}
- private void startMowing() {
- statusLabel.setText("浣滀笟涓�");
- startBtn.setEnabled(false);
- pauseBtn.setEnabled(true);
+ private void toggleStartPause() {
+ if (startBtn == null) {
+ return;
+ }
+ startButtonShowingPause = !startButtonShowingPause;
+ if (startButtonShowingPause) {
+ statusLabel.setText("浣滀笟涓�");
+ if (stopButtonActive) {
+ stopButtonActive = false;
+ updateStopButtonIcon();
+ }
+ } else {
+ statusLabel.setText("鏆傚仠涓�");
+ }
+ updateStartButtonAppearance();
}
-
- private void pauseMowing() {
- statusLabel.setText("鏆傚仠涓�");
- startBtn.setEnabled(true);
- pauseBtn.setEnabled(false);
+
+ private void handleStopAction() {
+ stopButtonActive = !stopButtonActive;
+ updateStopButtonIcon();
+ if (stopButtonActive) {
+ statusLabel.setText("宸茬粨鏉�");
+ startButtonShowingPause = false;
+ } else {
+ statusLabel.setText("寰呮満");
+ startButtonShowingPause = true;
+ }
+ updateStartButtonAppearance();
+ }
+
+ private void updateStartButtonAppearance() {
+ if (startBtn == null) {
+ return;
+ }
+ String iconPath = startButtonShowingPause ? "image/start0.png" : "image/start1.png";
+ startBtn.setText(startButtonShowingPause ? "鏆傚仠" : "寮�濮�");
+ applyButtonIcon(startBtn, iconPath);
+ }
+
+ private void updateStopButtonIcon() {
+ if (stopBtn == null) {
+ return;
+ }
+ String iconPath = stopButtonActive ? "image/stop1.png" : "image/stop0.png";
+ applyButtonIcon(stopBtn, iconPath);
+ }
+
+ private void toggleBluetoothConnection() {
+ if (bluetoothBtn == null) {
+ return;
+ }
+ if (Bluelink.isConnected()) {
+ Bluelink.disconnect();
+ bluetoothConnected = false;
+ } else {
+ boolean success = Bluelink.connect();
+ if (success) {
+ bluetoothConnected = true;
+ } else {
+ bluetoothConnected = false;
+ JOptionPane.showMessageDialog(this, "钃濈墮杩炴帴澶辫触锛岃閲嶈瘯", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ }
+ }
+ updateBluetoothButtonIcon();
+ }
+
+ private void updateBluetoothButtonIcon() {
+ if (bluetoothBtn == null) {
+ return;
+ }
+ ensureBluetoothIconsLoaded();
+ bluetoothConnected = Bluelink.isConnected();
+ ImageIcon icon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
+ if (icon != null) {
+ bluetoothBtn.setIcon(icon);
+ bluetoothBtn.setText(null);
+ } else {
+ bluetoothBtn.setText(bluetoothConnected ? "宸茶繛" : "钃濈墮");
+ }
+ }
+
+ private void applyStatusLabelColor(String statusText) {
+ if (statusLabel == null) {
+ return;
+ }
+ if ("浣滀笟涓�".equals(statusText)) {
+ statusLabel.setForeground(THEME_COLOR);
+ } else if ("鏆傚仠涓�".equals(statusText)) {
+ statusLabel.setForeground(STATUS_PAUSE_COLOR);
+ } else {
+ statusLabel.setForeground(Color.GRAY);
+ }
+ }
+
+ private void ensureBluetoothIconsLoaded() {
+ if (bluetoothIcon == null) {
+ bluetoothIcon = loadScaledIcon("image/blue.png", 28, 28);
+ }
+ if (bluetoothLinkedIcon == null) {
+ bluetoothLinkedIcon = loadScaledIcon("image/bluelink.png", 28, 28);
+ }
}
private JButton createFloatingIconButton() {
@@ -657,26 +800,27 @@
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);
+ boolean enableCircleGuidance = drawingShape != null
+ && "circle".equalsIgnoreCase(drawingShape.trim());
+ if (enableCircleGuidance) {
+ prepareCircleGuidanceState();
+ showCircleGuidanceStep(1);
+ endDrawingButton.setVisible(false);
+ if (drawingPauseButton != null) {
+ drawingPauseButton.setVisible(false);
+ }
+ } else {
+ clearCircleGuidanceArtifacts();
+ endDrawingButton.setVisible(true);
+ if (drawingPauseButton != null) {
+ drawingPauseButton.setVisible(true);
+ }
}
floatingButtonPanel.setVisible(true);
@@ -773,20 +917,33 @@
}
circleGuidanceStep = step;
if (step == 1) {
- circleGuidanceLabel.setText("鏄惁灏嗗綋鍓嶇偣璁剧疆涓哄渾蹇冿紵");
- circleGuidancePrimaryButton.setText("鏄�");
+ circleGuidanceLabel.setText("閲囬泦绗�1涓偣");
+ circleGuidancePrimaryButton.setText("纭绗�1鐐�");
circleGuidanceSecondaryButton.setText("杩斿洖");
circleGuidanceSecondaryButton.setVisible(true);
} else if (step == 2) {
- circleGuidanceLabel.setText("鏄惁灏嗗綋鍓嶇偣璁剧疆涓哄崐寰勭偣锛�");
- circleGuidancePrimaryButton.setText("鏄�");
- circleGuidanceSecondaryButton.setVisible(false);
+ circleGuidanceLabel.setText("閲囬泦绗�2涓偣");
+ circleGuidancePrimaryButton.setText("纭绗�2鐐�");
+ circleGuidanceSecondaryButton.setText("杩斿洖");
+ circleGuidanceSecondaryButton.setVisible(true);
+ } else if (step == 3) {
+ circleGuidanceLabel.setText("閲囬泦绗�3涓偣");
+ circleGuidancePrimaryButton.setText("纭绗�3鐐�");
+ circleGuidanceSecondaryButton.setText("杩斿洖");
+ circleGuidanceSecondaryButton.setVisible(true);
+ } else if (step == 4) {
+ circleGuidanceLabel.setText("宸查噰闆嗕笁涓偣");
+ circleGuidancePrimaryButton.setText("缁撴潫缁樺埗");
+ circleGuidanceSecondaryButton.setText("閲嶆柊閲囬泦");
+ circleGuidanceSecondaryButton.setVisible(true);
} else {
hideCircleGuidancePanel();
return;
}
circleGuidancePanel.setVisible(true);
+ refreshCircleGuidanceButtonAvailability();
+
if (circleDialogMode) {
ensureCircleGuidanceDialog();
if (circleGuidanceDialog != null) {
@@ -933,6 +1090,9 @@
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
+ if (!button.isEnabled()) {
+ return;
+ }
if (filled) {
button.setBackground(THEME_HOVER_COLOR);
} else {
@@ -942,6 +1102,9 @@
@Override
public void mouseExited(MouseEvent e) {
+ if (!button.isEnabled()) {
+ return;
+ }
if (filled) {
button.setBackground(bg);
} else {
@@ -953,20 +1116,25 @@
}
private void onCircleGuidancePrimary() {
- if (circleGuidanceStep == 1) {
- showCircleGuidanceStep(2);
- } else {
+ if (circleGuidanceStep >= 1 && circleGuidanceStep <= 3) {
+ if (!recordCircleSamplePoint()) {
+ return;
+ }
+ if (circleGuidanceStep == 3) {
+ showCircleGuidanceStep(4);
+ } else {
+ showCircleGuidanceStep(circleGuidanceStep + 1);
+ }
+ } else if (circleGuidanceStep == 4) {
handleCircleCompletion();
}
}
private void onCircleGuidanceSecondary() {
- if (circleGuidanceStep == 1) {
+ if (circleGuidanceStep == 1 || circleGuidanceStep == 2 || circleGuidanceStep == 3) {
handleCircleAbort(null);
- } else if (circleGuidanceStep == 2) {
- handleCircleAbort(null);
- } else {
- hideCircleGuidancePanel();
+ } else if (circleGuidanceStep == 4) {
+ restartCircleCapture();
}
}
@@ -991,6 +1159,7 @@
private void handleCircleCompletion() {
hideCircleGuidancePanel();
+ clearCircleGuidanceArtifacts();
if (endDrawingCallback != null) {
endDrawingCallback.run();
} else {
@@ -998,8 +1167,295 @@
}
}
+ private void prepareCircleGuidanceState() {
+ clearCircleGuidanceArtifacts();
+ circleBaseLatLon = resolveCircleBaseLatLon();
+ startCircleDataMonitor();
+ }
+
+ private void clearCircleGuidanceArtifacts() {
+ stopCircleDataMonitor();
+ circleCapturedPoints.clear();
+ circleBaseLatLon = null;
+ lastCapturedCoordinate = null;
+ if (mapRenderer != null) {
+ mapRenderer.clearCircleCaptureOverlay();
+ mapRenderer.clearCircleSampleMarkers();
+ }
+ }
+
+ private void restartCircleCapture() {
+ clearCircleGuidanceArtifacts();
+ synchronized (Coordinate.coordinates) {
+ Coordinate.coordinates.clear();
+ }
+ Coordinate.setStartSaveGngga(true);
+ circleBaseLatLon = resolveCircleBaseLatLon();
+ showCircleGuidanceStep(1);
+ startCircleDataMonitor();
+ }
+
+ private boolean recordCircleSamplePoint() {
+ Coordinate latest = getLatestCoordinate();
+ if (latest == null) {
+ JOptionPane.showMessageDialog(this, "鏈幏鍙栧埌褰撳墠浣嶇疆鍧愭爣锛岃绋嶅悗閲嶈瘯銆�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+
+ double[] base = ensureCircleBaseLatLon();
+ if (base == null) {
+ JOptionPane.showMessageDialog(this, "鍩哄噯绔欏潗鏍囨棤鏁堬紝璇峰厛鍦ㄥ熀鍑嗙珯绠$悊涓畬鎴愯缃��", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+
+ double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
+ double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
+ if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
+ JOptionPane.showMessageDialog(this, "閲囬泦鐐瑰潗鏍囨棤鏁堬紝璇烽噸鏂伴噰闆嗐��", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return false;
+ }
+
+ double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
+ circleCapturedPoints.add(local);
+ if (mapRenderer != null) {
+ mapRenderer.updateCircleSampleMarkers(circleCapturedPoints);
+ }
+ lastCapturedCoordinate = latest;
+ refreshCircleGuidanceButtonAvailability();
+
+ if (circleCapturedPoints.size() >= 3) {
+ CircleSolution solution = fitCircleFromPoints(circleCapturedPoints);
+ if (solution == null) {
+ circleCapturedPoints.remove(circleCapturedPoints.size() - 1);
+ JOptionPane.showMessageDialog(this, "鏃犳硶鏍规嵁褰撳墠涓変釜鐐圭敓鎴愬渾锛岃閲嶆柊閲囬泦鐐广��", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ restartCircleCapture();
+ return false;
+ }
+ if (mapRenderer != null) {
+ mapRenderer.showCircleCaptureOverlay(solution.centerX, solution.centerY, solution.radius, circleCapturedPoints);
+ }
+ }
+
+ return true;
+ }
+
+ private void startCircleDataMonitor() {
+ if (circleDataMonitor == null) {
+ circleDataMonitor = new Timer(600, e -> refreshCircleGuidanceButtonAvailability());
+ circleDataMonitor.setRepeats(true);
+ }
+ if (!circleDataMonitor.isRunning()) {
+ circleDataMonitor.start();
+ }
+ refreshCircleGuidanceButtonAvailability();
+ }
+
+ private void stopCircleDataMonitor() {
+ if (circleDataMonitor != null) {
+ circleDataMonitor.stop();
+ }
+ }
+
+ private void refreshCircleGuidanceButtonAvailability() {
+ if (circleGuidancePrimaryButton == null) {
+ return;
+ }
+ boolean shouldEnable = circleGuidanceStep >= 1 && circleGuidanceStep <= 3
+ ? isCircleDataAvailable()
+ : true;
+ applyCirclePrimaryButtonState(shouldEnable);
+ }
+
+ private boolean isCircleDataAvailable() {
+ Coordinate latest = getLatestCoordinate();
+ if (latest == null) {
+ return false;
+ }
+ return lastCapturedCoordinate == null || latest != lastCapturedCoordinate;
+ }
+
+ private void applyCirclePrimaryButtonState(boolean enabled) {
+ if (circleGuidancePrimaryButton == null) {
+ return;
+ }
+ if (circleGuidanceStep >= 4) {
+ enabled = true;
+ }
+ circleGuidancePrimaryButton.setEnabled(enabled);
+ if (enabled) {
+ circleGuidancePrimaryButton.setBackground(THEME_COLOR);
+ circleGuidancePrimaryButton.setForeground(Color.WHITE);
+ circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ circleGuidancePrimaryButton.setBackground(new Color(200, 200, 200));
+ circleGuidancePrimaryButton.setForeground(new Color(120, 120, 120));
+ circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+
+ private Coordinate getLatestCoordinate() {
+ synchronized (Coordinate.coordinates) {
+ int size = Coordinate.coordinates.size();
+ if (size == 0) {
+ return null;
+ }
+ return Coordinate.coordinates.get(size - 1);
+ }
+ }
+
+ private double[] ensureCircleBaseLatLon() {
+ if (circleBaseLatLon == null) {
+ circleBaseLatLon = resolveCircleBaseLatLon();
+ }
+ return circleBaseLatLon;
+ }
+
+ private double[] resolveCircleBaseLatLon() {
+ String coords = null;
+ String landNumber = Dikuaiguanli.getCurrentWorkLandNumber();
+ if (isMeaningfulValue(landNumber)) {
+ Dikuai current = Dikuai.getDikuai(landNumber);
+ if (current != null) {
+ coords = current.getBaseStationCoordinates();
+ }
+ }
+ if (!isMeaningfulValue(coords)) {
+ coords = addzhangaiwu.getActiveSessionBaseStation();
+ }
+ if (!isMeaningfulValue(coords) && baseStation != null) {
+ coords = baseStation.getInstallationCoordinates();
+ }
+ if (!isMeaningfulValue(coords)) {
+ return null;
+ }
+ String[] parts = coords.split(",");
+ if (parts.length < 4) {
+ return null;
+ }
+ double baseLat = parseDMToDecimal(parts[0], parts[1]);
+ double baseLon = parseDMToDecimal(parts[2], parts[3]);
+ if (!Double.isFinite(baseLat) || !Double.isFinite(baseLon)) {
+ return null;
+ }
+ return new double[]{baseLat, baseLon};
+ }
+
+ private 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 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 CircleSolution fitCircleFromPoints(List<double[]> points) {
+ if (points == null || points.size() < 3) {
+ return null;
+ }
+ CircleSolution best = null;
+ double bestScore = 0.0;
+ int n = points.size();
+ for (int i = 0; i < n - 2; i++) {
+ double[] p1 = points.get(i);
+ for (int j = i + 1; j < n - 1; j++) {
+ double[] p2 = points.get(j);
+ for (int k = j + 1; k < n; k++) {
+ double[] p3 = points.get(k);
+ CircleSolution candidate = circleFromThreePoints(p1, p2, p3);
+ if (candidate == null || candidate.radius <= 0) {
+ continue;
+ }
+ double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3)));
+ if (minEdge > bestScore) {
+ bestScore = minEdge;
+ best = candidate;
+ }
+ }
+ }
+ }
+ return best;
+ }
+
+ private CircleSolution circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
+ if (p1 == null || p2 == null || p3 == null) {
+ return null;
+ }
+ double x1 = p1[0];
+ double y1 = p1[1];
+ double x2 = p2[0];
+ double y2 = p2[1];
+ double x3 = p3[0];
+ double y3 = p3[1];
+
+ double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
+ double d = 2.0 * a;
+ if (Math.abs(d) < 1e-6) {
+ return null;
+ }
+
+ double x1Sq = x1 * x1 + y1 * y1;
+ double x2Sq = x2 * x2 + y2 * y2;
+ double x3Sq = x3 * x3 + y3 * y3;
+
+ double centerX = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / d;
+ double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / d;
+ double radius = Math.hypot(centerX - x1, centerY - y1);
+ if (!Double.isFinite(centerX) || !Double.isFinite(centerY) || !Double.isFinite(radius)) {
+ return null;
+ }
+ if (radius < 0.05) {
+ return null;
+ }
+ return new CircleSolution(centerX, centerY, radius);
+ }
+
+ private double distance(double[] a, double[] b) {
+ if (a == null || b == null) {
+ return 0.0;
+ }
+ double dx = a[0] - b[0];
+ double dy = a[1] - b[1];
+ return Math.hypot(dx, dy);
+ }
+
+ private static final class CircleSolution {
+ final double centerX;
+ final double centerY;
+ final double radius;
+
+ CircleSolution(double centerX, double centerY, double radius) {
+ this.centerX = centerX;
+ this.centerY = centerY;
+ this.radius = radius;
+ }
+ }
+
public void hideEndDrawingButton() {
hideCircleGuidancePanel();
+ clearCircleGuidanceArtifacts();
hideFloatingDrawingControls();
circleDialogMode = false;
applyDrawingPauseState(false, false);
--
Gitblit v1.10.0