From 32524195d474b74e48916867b2a6c2f022a40d98 Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期二, 09 十二月 2025 19:36:32 +0800
Subject: [PATCH] 20251209
---
src/chuankou/SerialPortNativeLoader.java | 77 +
src/gecaoji/Gecaoji.java | 48
dikuai.properties | 36
src/zhuye/adddikuaiyulan.java | 29
src/zhuye/MapRenderer.java | 126 ++
src/zhuye/pointandnumber.java | 9
set.properties | 9
src/dikuai/Dikuaiguanli.java | 182 +++
src/set/Sets.java | 72 +
src/denglu/RegistrationFrame.java | 50
lib/native/windows/x86_64/jSerialComm.dll | 0
src/gecaoji/lujingdraw.java | 11
src/chuankou/SerialPortService.java | 6
src/denglu/Denglu.java | 95 +
src/lujing/Lunjingguihua.java | 37
image/URT.png | 0
src/zhuye/zijian.java | 127 ++
src/dikuai/addzhangaiwu.java | 10
src/homein/Homein.java | 35
src/zhangaiwu/AddDikuai.java | 392 ++++++
src/zhuye/Shouye.java | 755 +++++++++++++-
user.properties | 12
Obstacledge.properties | 3
src/bianjie/bianjieguihua2.java | 37
/dev/null | 229 ----
src/TestPlanner.java | 6
src/lujing/luoxuan.java | 408 ++++++++
basestation.properties | 4
src/baseStation/BaseStationDialog.java | 162 ++-
src/bianjie/BoundaryAlgorithm.java | 41
30 files changed, 2,449 insertions(+), 559 deletions(-)
diff --git a/Obstacledge.properties b/Obstacledge.properties
index ff75a3a..8845548 100644
--- a/Obstacledge.properties
+++ b/Obstacledge.properties
@@ -1,10 +1,11 @@
# 鍓茶崏鏈哄湴鍧楅殰纰嶇墿閰嶇疆鏂囦欢
-# 鐢熸垚鏃堕棿锛�2025-12-05T15:37:09.106292500
+# 鐢熸垚鏃堕棿锛�2025-12-09T11:53:37.128295200
# 鍧愭爣绯伙細WGS84锛堝害鍒嗘牸寮忥級
# ============ 鍦板潡鍩哄噯绔欓厤缃� ============
# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].baseStation=[缁忓害],[N/S],[绾害],[E/W]
plot.DK-001.baseStation=3949.902389,N,11616.756920,E
+plot.LAND1.baseStation=3949.902389,N,11616.756920,E
# ============ 闅滅鐗╅厤缃� ============
# 鏍煎紡锛歱lot.[鍦板潡缂栧彿].obstacle.[闅滅鐗╁悕绉癩.shape=[0|1]
diff --git a/basestation.properties b/basestation.properties
index 9018b20..1a3f4a0 100644
--- a/basestation.properties
+++ b/basestation.properties
@@ -1,7 +1,7 @@
#Base station properties
-#Sat Nov 29 15:05:51 CST 2025
+#Tue Dec 09 19:03:31 CST 2025
dataUpdateTime=-1
deviceActivationTime=-1
-deviceId=1234
+deviceId=4567
installationCoordinates=3949.90238860,N,11616.75692000,E
iotSimCardNumber=-1
diff --git a/dikuai.properties b/dikuai.properties
index 482dd1d..9254a7b 100644
--- a/dikuai.properties
+++ b/dikuai.properties
@@ -1,19 +1,19 @@
#Dikuai Properties
-#Fri Dec 05 19:16:23 CST 2025
-DK-001.angleThreshold=-1
-DK-001.baseStationCoordinates=3949.90238860,N,11616.75692000,E
-DK-001.boundaryCoordinates=-3.74,1.88;-0.86,2.51;-0.32,0.12;1.15,-1.75;2.31,-7.15;1.56,-10.27;-0.45,-10.60;-1.73,-9.94;-3.61,0.51;-3.74,1.88
-DK-001.boundaryOriginalCoordinates=-1
-DK-001.boundaryPointInterval=-1
-DK-001.createTime=-1
-DK-001.intelligentSceneAnalysis=-1
-DK-001.landArea=300
-DK-001.landName=鍓嶉櫌鑽夊潽
-DK-001.landNumber=DK-001
-DK-001.mowingPattern=parallel
-DK-001.mowingTrack=-1
-DK-001.mowingWidth=100
-DK-001.plannedPath=1.51,-8.35;0.26,-1.43;-1.35,1.89;0.77,-9.89;-0.22,-10.06;-2.33,1.68
-DK-001.returnPointCoordinates=-1
-DK-001.updateTime=2025-12-05 19\:16\:23
-DK-001.userId=-1
+#Tue Dec 09 11:53:37 CST 2025
+LAND1.angleThreshold=-1
+LAND1.baseStationCoordinates=3949.90238860,N,11616.75692000,E
+LAND1.boundaryCoordinates=1.31,-9.59;1.86,-11.61;3.12,-12.49;5.50,-12.06;5.95,-10.88;4.97,-3.86;3.16,-0.87;2.79,-2.61;2.42,-4.35;2.05,-6.10;1.68,-7.84;1.31,-9.59
+LAND1.boundaryOriginalCoordinates=39.831620,116.279297,39.70;39.831618,116.279298,39.80;39.831616,116.279298,39.70;39.831614,116.279298,39.70;39.831612,116.279299,39.70;39.831610,116.279299,39.70;39.831608,116.279300,39.70;39.831606,116.279301,39.70;39.831604,116.279303,39.70;39.831602,116.279304,39.70;39.831600,116.279306,39.70;39.831598,116.279307,39.70;39.831598,116.279309,39.70;39.831597,116.279312,39.60;39.831596,116.279314,39.60;39.831595,116.279317,39.60;39.831594,116.279319,39.60;39.831594,116.279320,39.60;39.831594,116.279323,39.60;39.831595,116.279326,39.60;39.831595,116.279329,39.60;39.831595,116.279331,39.70;39.831595,116.279334,39.70;39.831596,116.279337,39.60;39.831596,116.279339,39.60;39.831596,116.279342,39.60;39.831597,116.279344,39.60;39.831598,116.279346,39.70;39.831600,116.279348,39.70;39.831601,116.279349,39.70;39.831603,116.279350,39.80;39.831605,116.279350,39.70;39.831606,116.279351,39.70;39.831609,116.279352,39.80;39.831611,116.279352,39.70;39.831613,116.279352,39.70;39.831615,116.279352,39.70;39.831617,116.279353,39.70;39.831619,116.279353,39.70;39.831621,116.279353,39.80;39.831623,116.279353,39.80;39.831625,116.279353,39.80;39.831627,116.279353,39.80;39.831629,116.279352,39.80;39.831631,116.279352,39.80;39.831634,116.279351,39.70;39.831636,116.279351,39.70;39.831637,116.279350,39.70;39.831639,116.279350,39.80;39.831641,116.279349,39.70;39.831643,116.279348,39.70;39.831645,116.279348,39.70;39.831647,116.279347,39.70;39.831649,116.279346,39.80;39.831651,116.279346,39.80;39.831653,116.279345,39.80;39.831655,116.279345,39.80;39.831657,116.279344,39.80;39.831659,116.279343,39.70;39.831661,116.279343,39.70;39.831663,116.279342,39.70;39.831665,116.279342,39.70;39.831667,116.279341,39.70;39.831670,116.279341,39.70;39.831672,116.279340,39.70;39.831674,116.279339,39.80;39.831676,116.279338,39.80;39.831678,116.279337,39.80;39.831679,116.279336,39.70;39.831680,116.279334,39.70;39.831681,116.279332,39.70;39.831683,116.279331,39.70;39.831684,116.279329,39.70;39.831686,116.279327,39.70;39.831687,116.279325,39.70;39.831689,116.279323,39.70;39.831691,116.279322,39.70;39.831693,116.279321,39.70;39.831694,116.279320,39.70;39.831696,116.279319,39.70;39.831699,116.279319,39.70
+LAND1.boundaryPointInterval=-1
+LAND1.createTime=2025-12-09 11\:16\:40
+LAND1.intelligentSceneAnalysis=-1
+LAND1.landArea=35.87
+LAND1.landName=1234
+LAND1.landNumber=LAND1
+LAND1.mowingPattern=铻烘棆寮�
+LAND1.mowingTrack=-1
+LAND1.mowingWidth=40
+LAND1.plannedPath=1.88,-7.88;2.62,-4.39;3.25,-1.41;4.78,-3.93;5.74,-10.86;5.35,-11.88;3.17,-12.28;2.03,-11.49;1.52,-9.58;1.88,-7.88;2.27,-7.96;3.01,-4.47;3.43,-2.48;4.39,-4.07;5.33,-10.81;5.06,-11.53;3.26,-11.86;2.38,-11.24;1.93,-9.57;2.27,-7.96;2.66,-8.05;3.40,-4.56;3.61,-3.55;4.01,-4.20;4.92,-10.76;4.77,-11.18;3.35,-11.43;2.73,-11.00;2.34,-9.56;2.66,-8.05;3.05,-8.13;3.71,-4.99;4.51,-10.72;4.47,-10.82;3.44,-11.01;3.08,-10.75;2.75,-9.55;3.05,-8.13;3.44,-8.21;3.63,-7.30;4.08,-10.49;3.54,-10.59;3.43,-10.51;3.16,-9.54;3.44,-8.21
+LAND1.returnPointCoordinates=-1
+LAND1.updateTime=2025-12-09 11\:53\:37
+LAND1.userId=-1
diff --git a/homein.lock b/homein.lock
deleted file mode 100644
index e69de29..0000000
--- a/homein.lock
+++ /dev/null
diff --git a/image/URT.png b/image/URT.png
index 539a79c..79d895d 100644
--- a/image/URT.png
+++ b/image/URT.png
Binary files differ
diff --git a/lib/native/windows/x86_64/jSerialComm.dll b/lib/native/windows/x86_64/jSerialComm.dll
new file mode 100644
index 0000000..d79031c
--- /dev/null
+++ b/lib/native/windows/x86_64/jSerialComm.dll
Binary files differ
diff --git a/set.properties b/set.properties
index c56ad27..19c65d8 100644
--- a/set.properties
+++ b/set.properties
@@ -1,11 +1,12 @@
-#Mower Configuration Properties - Updated
-#Fri Dec 05 14:26:38 CST 2025
+#Current work land selection updated
+#Tue Dec 09 19:35:30 CST 2025
appVersion=-1
+currentWorkLandNumber=LAND1
cuttingWidth=200
firmwareVersion=-1
-handheldMarkerId=2354
+handheldMarkerId=
idleTrailDurationSeconds=60
-mowerId=1872
+mowerId=1234
serialAutoConnect=true
serialBaudRate=921600
serialPortName=COM15
diff --git a/src/TestPlanner.java b/src/TestPlanner.java
index a2a9498..098e1ed 100644
--- a/src/TestPlanner.java
+++ b/src/TestPlanner.java
@@ -2,10 +2,10 @@
public class TestPlanner {
public static void main(String[] args) {
- String polygon = "-3.74,1.88;-0.86,2.51;-0.32,0.12;1.15,-1.75;2.31,-7.15;1.56,-10.27;-0.45,-10.60;-1.73,-9.94;-3.61,0.51;-3.74,1.88";
+ String polygon = "-3.74,1.88;-0.86,2.51;-0.32,0.12;1.15,-1.75;2.31,-7.15;1.56,-10.27;-0.45,-10.60;-1.73,-9.94;-3.61,0.51;-3.74,1.88";
String obstacles = "";
- String widthMeters = "0.5";
- String mode = "parallel";
+ String widthMeters = "0.5";
+ String mode = "parallel";
String pathCoords = Lunjingguihua.generatePathFromStrings(polygon, obstacles, widthMeters, mode);
System.out.println(pathCoords);
diff --git a/src/baseStation/BaseStationDialog.java b/src/baseStation/BaseStationDialog.java
index 5ea81ea..2b46be8 100644
--- a/src/baseStation/BaseStationDialog.java
+++ b/src/baseStation/BaseStationDialog.java
@@ -13,6 +13,7 @@
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Locale;
import java.util.Properties;
public class BaseStationDialog extends JDialog {
@@ -180,26 +181,24 @@
baseStation.load();
String currentRaw = baseStation.getInstallationCoordinates();
- String normalizedCurrent = currentRaw == null ? "-1" : currentRaw.trim();
- if (normalizedCurrent.isEmpty()) {
- normalizedCurrent = "-1";
- }
+ String normalizedCurrent = canonicalizeCoordinateValue(currentRaw);
+ boolean hasExistingCoordinate = normalizedCurrent != null && !"-1".equals(normalizedCurrent);
- JTextField inputField = new JTextField("-1".equals(normalizedCurrent) ? "" : normalizedCurrent);
+ JTextField inputField = new JTextField(hasExistingCoordinate ? normalizedCurrent : "");
inputField.setColumns(28);
JPanel dialogPanel = new JPanel(new BorderLayout(0, 8));
- JLabel hintLabel = new JLabel("璇疯緭鍏ユ柊鐨勫熀鍑嗙珯鍧愭爣锛堟牸寮忕ず渚嬶細2324.194945,N,11330.938547,E锛�");
+ JLabel hintLabel = new JLabel("璇疯緭鍏ユ柊鐨勫熀鍑嗙珯鍧愭爣锛堢ず渚嬶細3949.90238860,N,11616.75692000,E锛�");
hintLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
dialogPanel.add(hintLabel, BorderLayout.NORTH);
dialogPanel.add(inputField, BorderLayout.CENTER);
- JOptionPane optionPane = new JOptionPane(dialogPanel,
- JOptionPane.PLAIN_MESSAGE,
- JOptionPane.OK_CANCEL_OPTION);
- JDialog dialog = optionPane.createDialog(this, "淇敼鍩哄噯绔欎綅缃�");
- dialog.setModal(true);
- dialog.pack();
+ JOptionPane optionPane = new JOptionPane(dialogPanel,
+ JOptionPane.PLAIN_MESSAGE,
+ JOptionPane.OK_CANCEL_OPTION);
+ JDialog dialog = optionPane.createDialog(this, "淇敼鍩哄噯绔欎綅缃�");
+ dialog.setModal(true);
+ dialog.pack();
Dimension packedSize = dialog.getSize();
Window ownerWindow = getOwner();
@@ -208,12 +207,11 @@
referenceWidth = 400;
}
dialog.setSize(new Dimension(referenceWidth, packedSize.height));
- dialog.setResizable(false);
- dialog.setLocationRelativeTo(this);
- dialog.setVisible(true);
-
- Object selectedValue = optionPane.getValue();
- if (!(selectedValue instanceof Integer) || ((Integer) selectedValue) != JOptionPane.OK_OPTION) {
+ dialog.setResizable(false);
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+ Object selectedValue = optionPane.getValue();
+ if (!(selectedValue instanceof Integer) || ((Integer) selectedValue) != JOptionPane.OK_OPTION) {
return;
}
@@ -234,7 +232,7 @@
}
}
- String valueToSave = trimmed.isEmpty() ? "-1" : trimmed;
+ String valueToSave = trimmed.isEmpty() ? "-1" : canonicalizeCoordinateValue(trimmed);
if (!"-1".equals(valueToSave) && !looksLikeCoordinateFormat(valueToSave)) {
int confirm = JOptionPane.showConfirmDialog(this,
@@ -247,7 +245,7 @@
}
}
- if (valueToSave.equals(normalizedCurrent)) {
+ if (normalizedCurrent.equals(valueToSave)) {
JOptionPane.showMessageDialog(this,
"鍩哄噯绔欎綅缃湭鍙戠敓鍙樺寲銆�",
"鎻愮ず",
@@ -425,26 +423,11 @@
return "璁惧娌℃湁瀹夎鍥哄畾";
}
String coordinates = baseStation.getInstallationCoordinates();
- if (coordinates == null) {
+ String canonical = canonicalizeCoordinateValue(coordinates);
+ if (canonical == null || "-1".equals(canonical)) {
return "璁惧娌℃湁瀹夎鍥哄畾";
}
- String trimmed = coordinates.trim();
- if (trimmed.isEmpty() || "-1".equals(trimmed)) {
- return "璁惧娌℃湁瀹夎鍥哄畾";
- }
- try {
- String[] parts = trimmed.split(",");
- if (parts.length == 4) {
- String lat = formatCoordinate(parts[0].trim(), true);
- String latDir = parts[1].trim();
- String lon = formatCoordinate(parts[2].trim(), false);
- String lonDir = parts[3].trim();
- return String.format("%s掳%s, %s掳%s", lat, latDir, lon, lonDir);
- }
- } catch (Exception e) {
- // ignore formatting errors and fall back to raw value
- }
- return trimmed;
+ return canonical;
}
private String getSimCardDisplay() {
@@ -497,22 +480,93 @@
return !trimmed.isEmpty() && !"-1".equals(trimmed);
}
- private String formatCoordinate(String coordinate, boolean isLatitude) {
- // 鏍煎紡鍖栧潗鏍囷細2324.194945 -> 23掳24.1949'
- try {
- // 搴︽暟鏄墠2浣嶏紙绾害锛夋垨3浣嶏紙缁忓害锛�
- int degreeDigits = isLatitude ? 2 : 3;
- String degreeStr = coordinate.substring(0, degreeDigits);
- String minuteStr = coordinate.substring(degreeDigits);
-
- // 淇濈暀4浣嶅皬鏁�
- double minutes = Double.parseDouble(minuteStr);
- String formattedMinutes = String.format("%.4f", minutes);
-
- return degreeStr + "掳" + formattedMinutes + "'";
- } catch (Exception e) {
- return coordinate;
+ // Normalizes coordinate strings into degree-minute format when possible for consistent display/storage
+ private String canonicalizeCoordinateValue(String value) {
+ if (value == null) {
+ return "-1";
}
+ String trimmed = value.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ return "-1";
+ }
+ String[] parts = trimmed.split(",");
+ if (parts.length != 4) {
+ return trimmed;
+ }
+ String latDirRaw = parts[1] == null ? "" : parts[1].trim();
+ String lonDirRaw = parts[3] == null ? "" : parts[3].trim();
+ if (latDirRaw.isEmpty() || lonDirRaw.isEmpty()) {
+ return trimmed;
+ }
+ String latDir = latDirRaw.toUpperCase(Locale.ROOT);
+ String lonDir = lonDirRaw.toUpperCase(Locale.ROOT);
+ if (!isValidDirection(latDir, true) || !isValidDirection(lonDir, false)) {
+ return trimmed;
+ }
+ String formattedLat = convertToDegreeMinuteString(parts[0], latDir, true);
+ String formattedLon = convertToDegreeMinuteString(parts[2], lonDir, false);
+ if (formattedLat == null || formattedLon == null) {
+ return trimmed;
+ }
+ return formattedLat + "," + latDir + "," + formattedLon + "," + lonDir;
+ }
+
+ private boolean isValidDirection(String direction, boolean isLatitude) {
+ if (direction == null) {
+ return false;
+ }
+ if (isLatitude) {
+ return "N".equals(direction) || "S".equals(direction);
+ }
+ return "E".equals(direction) || "W".equals(direction);
+ }
+
+ // Converts decimal degrees or degree-minute input into a canonical degree-minute string (8 decimal places)
+ private String convertToDegreeMinuteString(String rawValue, String direction, boolean isLatitude) {
+ double decimal = parseCoordinateToDecimalDegrees(rawValue, direction, isLatitude);
+ if (!Double.isFinite(decimal)) {
+ return null;
+ }
+ double absDecimal = Math.abs(decimal);
+ int degrees = (int) Math.floor(absDecimal);
+ double minutes = (absDecimal - degrees) * 60.0d;
+ double degreeMinutes = degrees * 100.0d + minutes;
+ return String.format(Locale.US, "%.8f", degreeMinutes);
+ }
+
+ private double parseCoordinateToDecimalDegrees(String rawValue, String direction, boolean isLatitude) {
+ if (rawValue == null) {
+ return Double.NaN;
+ }
+ String trimmed = rawValue.trim();
+ if (trimmed.isEmpty()) {
+ return Double.NaN;
+ }
+ double numeric;
+ try {
+ numeric = Double.parseDouble(trimmed);
+ } catch (NumberFormatException ex) {
+ return Double.NaN;
+ }
+
+ double abs = Math.abs(numeric);
+ double boundary = isLatitude ? 90d : 180d;
+ double decimal;
+ if (abs <= boundary) {
+ decimal = abs;
+ } else {
+ double degrees = Math.floor(abs / 100d);
+ double minutes = abs - degrees * 100d;
+ decimal = degrees + minutes / 60d;
+ }
+
+ String dirUpper = direction == null ? "" : direction.trim().toUpperCase(Locale.ROOT);
+ if ("S".equals(dirUpper) || "W".equals(dirUpper)) {
+ decimal = -decimal;
+ } else if (!"N".equals(dirUpper) && !"E".equals(dirUpper) && numeric < 0d) {
+ decimal = -decimal;
+ }
+ return decimal;
}
private void lockBaseStationPosition() {
@@ -542,7 +596,7 @@
JOptionPane.INFORMATION_MESSAGE);
// 鏇存柊鍩哄噯绔欎綅缃紙杩欓噷浣跨敤妯℃嫙鏁版嵁锛屽疄闄呭簲浠嶨PS鑾峰彇锛�
- String newPosition = "2324.194945,N,11330.938547,E";
+ String newPosition = canonicalizeCoordinateValue("2324.194945,N,11330.938547,E");
String timestamp = String.valueOf(System.currentTimeMillis());
if (!hasBaseStationId()) {
diff --git a/src/bianjie/BoundaryAlgorithm.java b/src/bianjie/BoundaryAlgorithm.java
index a38f076..aa4254c 100644
--- a/src/bianjie/BoundaryAlgorithm.java
+++ b/src/bianjie/BoundaryAlgorithm.java
@@ -488,19 +488,22 @@
if (!optimized.get(0).equals(optimized.get(optimized.size() - 1))) {
Coordinate first = optimized.get(0);
Coordinate last = optimized.get(optimized.size() - 1);
- double closingDistance = calculateDistance(first, last);
-
+ double closingDistance = calculateDistance(last, first);
+
if (closingDistance > params.interval) {
- // 濡傛灉棣栧熬璺濈杈冭繙锛屾坊鍔犱腑闂寸偣
- List<Coordinate> interpolated = interpolateBoundary(
- List.of(last, first), params.interval
- );
- optimized.addAll(interpolated.subList(1, interpolated.size() - 1));
+ int segments = (int) Math.ceil(closingDistance / params.interval);
+ for (int i = 1; i < segments; i++) {
+ double ratio = (double) i / segments;
+ Coordinate interpolatedPoint = interpolate(last, first, ratio);
+ if (!interpolatedPoint.equals(last)) {
+ optimized.add(interpolatedPoint);
+ }
+ }
}
optimized.add(first);
}
-
- return optimized;
+
+ return removeConsecutiveDuplicates(optimized);
}
// 閬撴牸鎷夋柉-鏅厠绠楁硶
@@ -580,6 +583,26 @@
return interpolated;
}
+ private List<Coordinate> removeConsecutiveDuplicates(List<Coordinate> points) {
+ if (points == null || points.isEmpty()) {
+ return points;
+ }
+
+ List<Coordinate> cleaned = new ArrayList<>();
+ Coordinate previous = null;
+ for (Coordinate point : points) {
+ if (previous == null || !point.equals(previous)) {
+ cleaned.add(point);
+ previous = point;
+ }
+ }
+
+ if (!cleaned.isEmpty() && !cleaned.get(0).equals(cleaned.get(cleaned.size() - 1))) {
+ cleaned.add(cleaned.get(0));
+ }
+ return cleaned;
+ }
+
// 绾挎�ф彃鍊�
private Coordinate interpolate(Coordinate p1, Coordinate p2, double ratio) {
double x = p1.x + (p2.x - p1.x) * ratio;
diff --git a/src/bianjie/bianjieguihua.java b/src/bianjie/bianjieguihua.java
deleted file mode 100644
index 9d2233e..0000000
--- a/src/bianjie/bianjieguihua.java
+++ /dev/null
@@ -1,339 +0,0 @@
-package bianjie;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
-public class bianjieguihua {
-
- /**
- * 闈欐�佹柟娉曪細澶勭悊杈圭晫鏁版嵁骞惰繑鍥炶竟鐣岀偣鍧愭爣
- *
- * @param filePath GNGGA鏁版嵁鏂囦欢璺緞锛堜粠鏍圭洰褰曠殑GNGGA.txt鏂囦欢鑾峰彇锛�
- * @param baseStation 鍩哄噯绔欏潗鏍囷紝鏍煎紡锛�"绾害,N/S,缁忓害,E/W" 渚嬪锛�"2324.194945,N,11330.938547,E"
- * @param interval 杈圭晫鐐归棿闅旓紙绫筹級
- * @param angleThreshold 瑙掑害闃堝�硷紙搴︼級
- * @return 杈圭晫鐐瑰潗鏍囧瓧绗︿覆锛屾牸寮忥細"x1,y1;x2,y2;xn,yn"
- */
- public static String processBoundary(String filePath, String baseStation,
- double interval, double angleThreshold) {
- try {
- // 1. 浠嶵XT鏂囦欢璇诲彇GNGGA鏁版嵁
- String gnggaData = readGNGGAFromTxtFile(filePath);
-
- if (gnggaData == null || gnggaData.trim().isEmpty()) {
- throw new RuntimeException("鏃犳硶浠庢枃浠惰鍙朑NGGA鏁版嵁鎴栨暟鎹负绌�");
- }
-
- // 2. 浣跨敤BoundaryProcessor澶勭悊鏁版嵁
- String boundaryCoordinates = BoundaryProcessor.processBoundaryData(
- gnggaData, baseStation, interval, angleThreshold);
-
- return boundaryCoordinates;
-
- } catch (Exception e) {
- throw new RuntimeException("澶勭悊杈圭晫鏁版嵁鏃跺彂鐢熼敊璇�: " + e.getMessage(), e);
- }
- }
-
- /**
- * 浠嶵XT鏂囦欢璇诲彇GNGGA鏁版嵁
- * 鏂囦欢鏍煎紡锛氬涓�$GNGGA鏁版嵁璁板綍锛岀敤$GNGGA鍒嗛殧
- */
- private static String readGNGGAFromTxtFile(String filePath) {
- try {
- // 棣栧厛灏濊瘯浠庡綋鍓嶅伐浣滅洰褰曪紙鏍圭洰褰曪級鍔犺浇
- File file = new File(filePath);
-
- // 濡傛灉鏂囦欢涓嶅瓨鍦紝灏濊瘯鍦ㄧ敤鎴峰綋鍓嶇洰褰曚笅鏌ユ壘
- if (!file.exists()) {
- file = new File(System.getProperty("user.dir") + File.separator + filePath);
- }
-
- // 濡傛灉杩樻槸涓嶅瓨鍦紝灏濊瘯鍦ㄧ被璺緞涓煡鎵�
- if (!file.exists()) {
- InputStream input = bianjieguihua.class.getClassLoader().getResourceAsStream(filePath);
- if (input != null) {
- // 浠庣被璺緞璇诲彇
- return readFromInputStream(input);
- } else {
- throw new RuntimeException("鏂囦欢鏈壘鍒�: " + filePath +
- " (鎼滅储璺緞: " + new File(".").getAbsolutePath() + ")");
- }
- }
-
- // 浠庢枃浠剁郴缁熻鍙�
- String content = new String(Files.readAllBytes(Paths.get(file.getAbsolutePath())));
-
- // 娓呯悊鏁版嵁锛氱Щ闄ゅ浣欑殑绌烘牸鍜屾崲琛岋紝纭繚鏍煎紡姝g‘
- content = content.replaceAll("\\r\\n|\\r|\\n", ""); // 绉婚櫎鎹㈣绗�
- content = content.replaceAll("\\s+", ""); // 绉婚櫎绌虹櫧瀛楃
- content = content.trim();
-
- // 纭繚鏁版嵁浠�$GNGGA寮�澶达紙濡傛灉涓嶆槸锛屽彲鑳芥枃浠舵牸寮忎笉瀵癸級
- if (!content.startsWith("$GNGGA") && content.contains("$GNGGA")) {
- // 濡傛灉鍖呭惈$GNGGA浣嗕笉浠ュ畠寮�澶达紝鍙兘闇�瑕佸鐞�
- System.out.println("璀﹀憡: GNGGA鏁版嵁鏍煎紡鍙兘闇�瑕佽皟鏁�");
- }
-
- return content;
-
- } catch (Exception e) {
- throw new RuntimeException("璇诲彇GNGGA TXT鏂囦欢鏃跺彂鐢熼敊璇�: " + e.getMessage(), e);
- }
- }
-
- /**
- * 浠庤緭鍏ユ祦璇诲彇鏁版嵁锛堢敤浜庣被璺緞璧勬簮锛�
- */
- private static String readFromInputStream(InputStream inputStream) throws IOException {
- StringBuilder resultStringBuilder = new StringBuilder();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
- String line;
- while ((line = br.readLine()) != null) {
- resultStringBuilder.append(line.trim());
- }
- }
- return resultStringBuilder.toString();
- }
-
- /**
- * 璁$畻鍧愭爣鐐规暟閲�
- */
- private static int countCoordinates(String coordinates) {
- if (coordinates == null || coordinates.trim().isEmpty()) {
- return 0;
- }
- // 鎸夊垎鍙峰垎鍓插潗鏍囩偣
- String[] points = coordinates.split(";");
- return points.length;
- }
-
- /**
- * 楂樼骇澶勭悊鏂规硶 - 鍏佽浣跨敤鑷畾涔夊弬鏁�
- *
- * @param filePath GNGGA鏁版嵁鏂囦欢璺緞
- * @param baseStation 鍩哄噯绔欏潗鏍�
- * @param params 杈圭晫鍙傛暟瀵硅薄
- * @return 杈圭晫鐐瑰潗鏍囧瓧绗︿覆
- */
- public static String processBoundaryAdvanced(String filePath, String baseStation,
- BoundaryAlgorithm.BoundaryParameters params) {
- try {
- // 浠庢枃浠惰鍙朑NGGA鏁版嵁
- String gnggaData = readGNGGAFromTxtFile(filePath);
-
- if (gnggaData == null || gnggaData.trim().isEmpty()) {
- throw new RuntimeException("鏃犳硶浠庢枃浠惰鍙朑NGGA鏁版嵁鎴栨暟鎹负绌�");
- }
-
- // 浣跨敤BoundaryProcessor鐨勯珮绾у鐞嗘柟娉�
- String boundaryCoordinates = BoundaryProcessor.processBoundaryDataAdvanced(
- gnggaData, baseStation, params);
- return boundaryCoordinates;
-
- } catch (Exception e) {
- throw new RuntimeException("澶勭悊杈圭晫鏁版嵁鏃跺彂鐢熼敊璇�: " + e.getMessage(), e);
- }
- }
-
- /**
- * 浠呰繘琛屽満鏅垎鏋愶紝涓嶇敓鎴愯竟鐣�
- *
- * @param filePath GNGGA鏁版嵁鏂囦欢璺緞
- * @param baseStation 鍩哄噯绔欏潗鏍�
- * @return 鍦烘櫙鍒嗘瀽缁撴灉
- */
- public static BoundaryAlgorithm.SceneAnalysis analyzeScene(String filePath, String baseStation) {
- try {
- // 浠庢枃浠惰鍙朑NGGA鏁版嵁
- String gnggaData = readGNGGAFromTxtFile(filePath);
-
- if (gnggaData == null || gnggaData.trim().isEmpty()) {
- throw new RuntimeException("鏃犳硶浠庢枃浠惰鍙朑NGGA鏁版嵁鎴栨暟鎹负绌�");
- }
-
- // 浣跨敤BoundaryProcessor杩涜鍦烘櫙鍒嗘瀽
- return BoundaryProcessor.analyzeScene(gnggaData, baseStation);
-
- } catch (Exception e) {
- throw new RuntimeException("鍦烘櫙鍒嗘瀽鏃跺彂鐢熼敊璇�: " + e.getMessage(), e);
- }
- }
-
- /**
- * 瀹屾暣澶勭悊娴佺▼锛氬満鏅垎鏋� + 鑷姩鍙傛暟閫夋嫨 + 杈圭晫鐢熸垚
- *
- * @param filePath GNGGA鏁版嵁鏂囦欢璺緞
- * @param baseStation 鍩哄噯绔欏潗鏍�
- * @return 杈圭晫鐐瑰潗鏍囧瓧绗︿覆
- */
- public static String processBoundaryAuto(String filePath, String baseStation) {
- try {
- // 1. 鍦烘櫙鍒嗘瀽
- BoundaryAlgorithm.SceneAnalysis analysis = analyzeScene(filePath, baseStation);
-
- // 2. 鏍规嵁鍦烘櫙鍒嗘瀽缁撴灉鑾峰彇鍙傛暟
- BoundaryAlgorithm algorithm = new BoundaryAlgorithm();
- BoundaryAlgorithm.BoundaryParameters params =
- algorithm.getParametersForPreset(analysis.suggestedPreset);
-
- // 3. 浣跨敤楂樼骇澶勭悊鏂规硶鐢熸垚杈圭晫
- return processBoundaryAdvanced(filePath, baseStation, params);
-
- } catch (Exception e) {
- throw new RuntimeException("鑷姩杈圭晫澶勭悊鏃跺彂鐢熼敊璇�: " + e.getMessage(), e);
- }
- }
-
- /**
- * 瑙f瀽GNGGA鏁版嵁骞舵彁鍙栨湁鏁堢殑缁忕含搴︾偣闆嗗悎锛堝害鍒嗘牸寮忎繚瀛橈級
- *
- * @param gnggaData 鍖呭惈澶氭潯GNGGA璁板綍鐨勫瓧绗︿覆
- * @return 鏈夋晥鐨勭粡绾害鐐归泦鍚堬紝鏍煎紡涓�"绾害鏂瑰悜,缁忓害鏂瑰悜;绾害鏂瑰悜,缁忓害鏂瑰悜;..."
- */
- public static String parseGNGGAToCoordinates(String gnggaData) {
- try {
- if (gnggaData == null || gnggaData.trim().isEmpty()) {
- return "";
- }
-
- StringBuilder coordinatesBuilder = new StringBuilder();
-
- // 鎸�$GNGGA鍒嗗壊璁板綍
- String[] records = gnggaData.split("\\$GNGGA");
-
- for (String record : records) {
- try {
- String trimmedRecord = record.trim();
- if (trimmedRecord.isEmpty()) continue;
-
- // 纭繚璁板綍浠ラ�楀彿寮�澶翠互渚挎纭垎鍓插瓧娈�
- if (!trimmedRecord.startsWith(",")) {
- trimmedRecord = "," + trimmedRecord;
- }
-
- String[] fields = trimmedRecord.split(",");
- if (fields.length < 7) {
- continue; // 瀛楁涓嶈冻锛岃烦杩�
- }
-
- // 妫�鏌ュ畾浣嶈川閲� (绗�7涓瓧娈碉紝绱㈠紩6)
- String fixQualityStr = fields[6];
- if (fixQualityStr.isEmpty()) {
- continue;
- }
-
- int fixQuality;
- try {
- fixQuality = Integer.parseInt(fixQualityStr);
- } catch (NumberFormatException e) {
- continue; // 瀹氫綅璐ㄩ噺鏍煎紡閿欒锛岃烦杩�
- }
-
- // 鍙噰鐢ㄩ珮绮惧害瀹氫綅鐐� (4 = RTK鍥哄畾瑙�)
- if (fixQuality != 4) {
- continue;
- }
-
- // 鎻愬彇绾害搴﹀垎鏍煎紡 (绗�3涓瓧娈碉紝绱㈠紩2) 鍜屾柟鍚� (绗�4涓瓧娈碉紝绱㈠紩3)
- String latitudeStr = fields[2];
- String latDirection = fields[3];
-
- // 鎻愬彇缁忓害搴﹀垎鏍煎紡 (绗�5涓瓧娈碉紝绱㈠紩4) 鍜屾柟鍚� (绗�6涓瓧娈碉紝绱㈠紩5)
- String longitudeStr = fields[4];
- String lonDirection = fields[5];
-
- if (latitudeStr.isEmpty() || longitudeStr.isEmpty() ||
- latDirection.isEmpty() || lonDirection.isEmpty()) {
- continue; // 鍧愭爣鏁版嵁涓嶅畬鏁达紝璺宠繃
- }
-
- // 鐩存帴淇濆瓨搴﹀垎鏍煎紡鍜屾柟鍚�
- if (coordinatesBuilder.length() > 0) {
- coordinatesBuilder.append(";");
- }
- coordinatesBuilder.append(latitudeStr)
- .append(",")
- .append(latDirection)
- .append(",")
- .append(longitudeStr)
- .append(",")
- .append(lonDirection);
-
- } catch (Exception e) {
- // 鍗曟潯璁板綍瑙f瀽澶辫触锛岀户缁鐞嗕笅涓�鏉�
- System.err.println("瑙f瀽GNGGA璁板綍澶辫触: " + record + ", 閿欒: " + e.getMessage());
- }
- }
-
- return coordinatesBuilder.toString();
-
- } catch (Exception e) {
- throw new RuntimeException("瑙f瀽GNGGA鏁版嵁鏃跺彂鐢熼敊璇�: " + e.getMessage(), e);
- }
- }
-
- /**
- * 灏嗗害鍒嗘牸寮忓潗鏍囪浆鎹负鍗佽繘鍒跺害鏍煎紡
- *
- * @param dmCoord 搴﹀垎鏍煎紡鍧愭爣 (DDMM.MMMMM)
- * @param direction 鏂瑰悜 (N/S/E/W)
- * @return 鍗佽繘鍒跺害鏍煎紡鍧愭爣
- */
- private static double parseDMToDecimal(String dmCoord, String direction) {
- try {
- if (dmCoord == null || dmCoord.isEmpty()) {
- return 0;
- }
-
- int dotIndex = dmCoord.indexOf('.');
- if (dotIndex < 2) {
- return 0;
- }
-
- // 鎻愬彇搴﹂儴鍒嗗拰鍒嗛儴鍒�
- int degrees = Integer.parseInt(dmCoord.substring(0, dotIndex - 2));
- double minutes = Double.parseDouble(dmCoord.substring(dotIndex - 2));
-
- // 杞崲涓哄崄杩涘埗搴�
- double decimal = degrees + minutes / 60.0;
-
- // 鏍规嵁鏂瑰悜璋冩暣姝h礋
- if ("S".equals(direction) || "W".equals(direction)) {
- decimal = -decimal;
- }
-
- return decimal;
-
- } catch (Exception e) {
- throw new IllegalArgumentException("搴﹀垎鍧愭爣瑙f瀽閿欒: " + dmCoord, e);
- }
- }
-
-
-
- /**
- * 娴嬭瘯鏂规硶
- */
- public static void main(String[] args) {
- try {
- // 鍒涘缓绀轰緥鏂囦欢锛堝鏋滀笉瀛樺湪锛�
- File testFile = new File("GNGGA.txt");
-
-
- // 娴嬭瘯鏁版嵁
- String filePath = "GNGGA.txt";
- String baseStation = "3949.91202005,N,11616.85440851,E";
-
-
- // 鏂规硶2: 鑷姩澶勭悊锛堟帹鑽愶級
- String result2 = processBoundaryAuto(filePath, baseStation);
- System.out.println("鑷姩澶勭悊鏂规硶缁撴灉: " + result2);
-
- } catch (Exception e) {
- System.err.println("娴嬭瘯澶辫触: " + e.getMessage());
- e.printStackTrace();
- }
- }
-}
\ No newline at end of file
diff --git a/src/bianjie/bianjieguihua2.java b/src/bianjie/bianjieguihua2.java
index bb6e290..0129d75 100644
--- a/src/bianjie/bianjieguihua2.java
+++ b/src/bianjie/bianjieguihua2.java
@@ -34,9 +34,30 @@
double baseLat = parseDMToDecimal(baseParts[0], baseParts[1]);
double baseLon = parseDMToDecimal(baseParts[2], baseParts[3]);
- // 灏咰oordinate鍒楄〃杞崲涓哄眬閮ㄥ潗鏍囩郴鍧愭爣
- List<BoundaryAlgorithm.Coordinate> localCoordinates =
- convertToLocalCoordinates(coordinates, baseLat, baseLon);
+ // 灏咰oordinate鍒楄〃杞崲涓哄眬閮ㄥ潗鏍囩郴鍧愭爣
+ List<BoundaryAlgorithm.Coordinate> localCoordinates =
+ convertToLocalCoordinates(coordinates, baseLat, baseLon);
+
+ // 涓夎褰㈠皬鍖哄煙鐗规畩澶勭悊锛岄伩鍏嶈繃搴︽彃鍊煎鑷寸偣鏁版墿澧�
+ if (localCoordinates.size() == 3) {
+ double triangleArea = calculatePolygonArea(localCoordinates);
+ double trianglePerimeter = calculatePerimeter(localCoordinates);
+
+ System.out.println("妫�娴嬪埌涓夎褰㈣竟鐣岋紝闈㈢Н=" + String.format("%.2f", triangleArea) +
+ "m虏, 鍛ㄩ暱=" + String.format("%.2f", trianglePerimeter) + "m");
+
+ if (triangleArea < 100.0 || trianglePerimeter < 30.0) {
+ System.out.println("灏忎笁瑙掑舰锛岃烦杩囨彃鍊间紭鍖�");
+ BoundaryAlgorithm.Coordinate firstPoint = localCoordinates.get(0);
+ List<BoundaryAlgorithm.Coordinate> trianglePoints = new ArrayList<>(localCoordinates);
+ trianglePoints.add(new BoundaryAlgorithm.Coordinate(
+ firstPoint.x,
+ firstPoint.y,
+ firstPoint.lat,
+ firstPoint.lon));
+ return convertBoundaryPointsToString(trianglePoints);
+ }
+ }
// 鍒涘缓绠楁硶瀹炰緥
BoundaryAlgorithm algorithm = new BoundaryAlgorithm();
@@ -264,6 +285,16 @@
return Math.abs(area) / 2.0;
}
+
+ private static double calculatePerimeter(List<BoundaryAlgorithm.Coordinate> points) {
+ if (points == null || points.size() != 3) {
+ return 0.0;
+ }
+ double d1 = calculateDistance(points.get(0), points.get(1));
+ double d2 = calculateDistance(points.get(1), points.get(2));
+ double d3 = calculateDistance(points.get(2), points.get(0));
+ return d1 + d2 + d3;
+ }
// ============ 鍏朵粬鏂规硶淇濇寔涓嶅彉 ============
diff --git a/src/chuankou/SerialPortNativeLoader.java b/src/chuankou/SerialPortNativeLoader.java
new file mode 100644
index 0000000..83eae1b
--- /dev/null
+++ b/src/chuankou/SerialPortNativeLoader.java
@@ -0,0 +1,77 @@
+package chuankou;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Ensures the correct jSerialComm native library is available on Windows/x86_64 systems.
+ */
+public final class SerialPortNativeLoader {
+ private static final String LIB_PROPERTY = "com.fazecast.jSerialComm.library.path";
+ private static final String EXPECTED_DLL = "jSerialComm.dll";
+
+ private SerialPortNativeLoader() {
+ // utility class
+ }
+
+ public static void ensureLibraryPresent() {
+ if (System.getProperty(LIB_PROPERTY) != null) {
+ return;
+ }
+
+ String osName = System.getProperty("os.name", "").toLowerCase();
+ if (!osName.contains("win")) {
+ return;
+ }
+
+ String arch = System.getProperty("os.arch", "").toLowerCase();
+ if (!arch.contains("64")) {
+ return;
+ }
+
+ Path candidateDir = Paths.get("lib", "native", "windows", "x86_64").toAbsolutePath();
+ File dllFile = candidateDir.resolve(EXPECTED_DLL).toFile();
+ if (!dllFile.isFile()) {
+ candidateDir = resolveFromCodeSource();
+ if (candidateDir != null) {
+ dllFile = candidateDir.resolve(EXPECTED_DLL).toFile();
+ }
+ }
+
+ if (dllFile.isFile()) {
+ System.setProperty(LIB_PROPERTY, candidateDir.toString());
+ } else {
+ System.err.println("Expected jSerialComm native library not found. Checked " + dllFile);
+ }
+ }
+
+ private static Path resolveFromCodeSource() {
+ try {
+ java.security.CodeSource codeSource = SerialPortNativeLoader.class.getProtectionDomain().getCodeSource();
+ if (codeSource == null) {
+ return null;
+ }
+
+ Path location = Paths.get(codeSource.getLocation().toURI()).toAbsolutePath();
+ Path baseDir = location.getParent();
+ if (baseDir == null) {
+ return null;
+ }
+
+ Path siblingLibDir = baseDir.resolveSibling("lib").resolve("native").resolve("windows").resolve("x86_64");
+ if (siblingLibDir.toFile().isDirectory()) {
+ return siblingLibDir;
+ }
+
+ Path relativeLibDir = baseDir.resolve("lib").resolve("native").resolve("windows").resolve("x86_64");
+ if (relativeLibDir.toFile().isDirectory()) {
+ return relativeLibDir;
+ }
+ } catch (URISyntaxException | SecurityException ignored) {
+ // ignore
+ }
+ return null;
+ }
+}
diff --git a/src/chuankou/SerialPortService.java b/src/chuankou/SerialPortService.java
index f1c7aa7..64a0b60 100644
--- a/src/chuankou/SerialPortService.java
+++ b/src/chuankou/SerialPortService.java
@@ -1,13 +1,17 @@
package chuankou;
import com.fazecast.jSerialComm.SerialPort;
-import java.util.function.Consumer;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.function.Consumer;
public class SerialPortService {
+ static {
+ SerialPortNativeLoader.ensureLibraryPresent();
+ }
+
private SerialPort port;
private volatile boolean capturing = false;
private volatile boolean paused = true;
diff --git a/src/denglu/Denglu.java b/src/denglu/Denglu.java
index 93078df..82e306a 100644
--- a/src/denglu/Denglu.java
+++ b/src/denglu/Denglu.java
@@ -1,6 +1,7 @@
package denglu;
import homein.WenJianSuo;
+import ui.UIConfig;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
@@ -35,7 +36,7 @@
private void initializeTranslations() {
translations = new HashMap<>();
-
+
// 涓枃缈昏瘧
Map<String, String> zh = new HashMap<>();
zh.put("username_label", "鐢ㄦ埛鍚嶆垨閭");
@@ -45,7 +46,9 @@
zh.put("login_button", "鐧诲綍");
zh.put("no_account", "杩樻病鏈夎处鎴凤紵");
zh.put("sign_up", "绔嬪嵆娉ㄥ唽");
- zh.put("app_info", "AutoMow 漏 2023 - 鏅鸿兘鍓茶崏瑙e喅鏂规");
+ zh.put("app_title", "鏅鸿兘鍓茶崏绯荤粺");
+ zh.put("window_title", "鏅鸿兘鍓茶崏绯荤粺 - 鐧诲綍");
+ zh.put("app_info", "鏅鸿兘鍓茶崏绯荤粺 漏 2023 - 鏅鸿兘鍓茶崏瑙e喅鏂规");
zh.put("register_button", "娉ㄥ唽鏂拌处鎴�");
translations.put("zh", zh);
@@ -58,7 +61,9 @@
en.put("login_button", "Sign In");
en.put("no_account", "Don't have an account?");
en.put("sign_up", "Sign Up");
- en.put("app_info", "AutoMow 漏 2023 - Smart Mowing Solutions");
+ en.put("app_title", "Smart Mowing System");
+ en.put("window_title", "Smart Mowing System - Login");
+ en.put("app_info", "Smart Mowing System 漏 2023 - Intelligent Lawn Care");
en.put("register_button", "Create New Account");
translations.put("en", en);
@@ -71,7 +76,9 @@
es.put("login_button", "Iniciar Sesi贸n");
es.put("no_account", "驴No tienes una cuenta?");
es.put("sign_up", "Reg铆strate");
- es.put("app_info", "AutoMow 漏 2023 - Soluciones de Corte Inteligente");
+ es.put("app_title", "Sistema de Corte Inteligente");
+ es.put("window_title", "Sistema de Corte Inteligente - Iniciar Sesi贸n");
+ es.put("app_info", "Sistema de Corte Inteligente 漏 2023 - Soluciones Inteligentes");
es.put("register_button", "Crear Nueva Cuenta");
translations.put("es", es);
@@ -84,8 +91,10 @@
fr.put("login_button", "Se connecter");
fr.put("no_account", "Vous n'avez pas de compte?");
fr.put("sign_up", "S'inscrire");
- fr.put("app_info", "AutoMow 漏 2023 - Solutions de Tonte Intelligente");
- es.put("register_button", "Cr茅er un Nouveau Compte");
+ fr.put("app_title", "Syst猫me de Tonte Intelligent");
+ fr.put("window_title", "Syst猫me de Tonte Intelligent - Connexion");
+ fr.put("app_info", "Syst猫me de Tonte Intelligent 漏 2023 - Solutions de Tonte");
+ fr.put("register_button", "Cr茅er un Nouveau Compte");
translations.put("fr", fr);
// 寰疯缈昏瘧
@@ -97,18 +106,20 @@
de.put("login_button", "Anmelden");
de.put("no_account", "Sie haben noch kein Konto?");
de.put("sign_up", "Registrieren");
- de.put("app_info", "AutoMow 漏 2023 - Intelligente M盲h-L枚sungen");
+ de.put("app_title", "Intelligentes M盲hsystem");
+ de.put("window_title", "Intelligentes M盲hsystem - Anmeldung");
+ de.put("app_info", "Intelligentes M盲hsystem 漏 2023 - M盲hl枚sungen");
de.put("register_button", "Neues Konto Erstellen");
translations.put("de", de);
}
private void initializeUI() {
- setTitle("AutoMow - 鐧诲綍");
+ setTitle(getTranslationValue(currentLanguageCode, "window_title", "绯荤粺鐧诲綍"));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 璁剧疆绱у噾甯冨眬灏哄
- setSize(320, 520);
- setMinimumSize(new Dimension(300, 500));
+ setSize(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
+ setMinimumSize(new Dimension(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT));
setResizable(false);
setupCompactLayout();
@@ -159,11 +170,6 @@
));
// 鐧诲綍鏍囬
- JLabel loginTitle = new JLabel("AutoMow", SwingConstants.CENTER);
- loginTitle.setFont(new Font("PingFang SC", Font.BOLD, 24));
- loginTitle.setForeground(THEME_COLOR);
- loginTitle.setAlignmentX(Component.CENTER_ALIGNMENT);
-
// 鐢ㄦ埛鍚嶈緭鍏�
usernameLabel = new JLabel("鐢ㄦ埛鍚嶆垨閭", SwingConstants.CENTER);
usernameLabel.setFont(new Font("PingFang SC", Font.BOLD, 13));
@@ -212,7 +218,7 @@
rememberForgotPanel.add(rememberMe, BorderLayout.WEST);
rememberForgotPanel.add(forgotPassword, BorderLayout.EAST);
- // 鐧诲綍鎸夐挳 - 姘村钩灞呬腑锛岄暱搴︿笌鏂囨湰妗嗙浉鍚�
+ // 鐧诲綍鎸夐挳 - 姘村钩灞呬腑锛岄暱搴︿笌鏂囨湰妗嗗搴︿繚鎸佺浉杩�
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonPanel.setBackground(Color.WHITE);
buttonPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 50));
@@ -221,8 +227,8 @@
loginButton.setBackground(THEME_COLOR);
loginButton.setForeground(Color.WHITE);
loginButton.setFont(new Font("PingFang SC", Font.BOLD, 15));
- // 璁剧疆鐧诲綍鎸夐挳闀垮害涓庢枃鏈鐩稿悓
- loginButton.setPreferredSize(new Dimension(260, 42));
+ // 璁剧疆鐧诲綍鎸夐挳闀垮害鎺ヨ繎鏂囨湰妗嗗搴�
+ loginButton.setPreferredSize(new Dimension(300, 48));
loginButton.setBorderPainted(false);
loginButton.setFocusPainted(false);
loginButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
@@ -248,7 +254,7 @@
registerButton.setBackground(Color.WHITE);
registerButton.setForeground(THEME_COLOR);
registerButton.setFont(new Font("PingFang SC", Font.BOLD, 14));
- registerButton.setPreferredSize(new Dimension(140, 38));
+ registerButton.setPreferredSize(new Dimension(180, 42));
registerButton.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(THEME_COLOR),
BorderFactory.createEmptyBorder(5, 15, 5, 15)
@@ -282,12 +288,11 @@
signupPanel.add(signupLink);
// 缁勮琛ㄥ崟
- formPanel.add(loginTitle);
- formPanel.add(Box.createRigidArea(new Dimension(0, 25)));
+ formPanel.add(Box.createRigidArea(new Dimension(0, 15)));
formPanel.add(usernameLabel);
formPanel.add(Box.createRigidArea(new Dimension(0, 5)));
formPanel.add(usernameField);
- formPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+ formPanel.add(Box.createRigidArea(new Dimension(0, 30)));
formPanel.add(passwordLabel);
formPanel.add(Box.createRigidArea(new Dimension(0, 5)));
formPanel.add(passwordField);
@@ -307,7 +312,9 @@
JPanel appInfoPanel = new JPanel();
appInfoPanel.setBackground(Color.WHITE);
appInfoPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 5, 0));
- appInfoLabel = new JLabel("AutoMow 漏 2023 - 鏅鸿兘鍓茶崏瑙e喅鏂规");
+ appInfoLabel = new JLabel(
+ getTranslationValue(currentLanguageCode, "app_info", "鏅鸿兘鍓茶崏绯荤粺 漏 2023")
+ );
appInfoLabel.setFont(new Font("PingFang SC", Font.PLAIN, 10));
appInfoLabel.setForeground(new Color(150, 150, 150));
appInfoPanel.add(appInfoLabel);
@@ -356,20 +363,20 @@
if (translation != null) {
// 鏇存柊鎵�鏈夌晫闈㈡枃鏈�
- usernameLabel.setText(translation.get("username_label"));
- passwordLabel.setText(translation.get("password_label"));
- rememberMe.setText(translation.get("remember_me"));
- forgotPassword.setText(translation.get("forgot_password"));
- loginButton.setText(translation.get("login_button"));
- noAccountLabel.setText(translation.get("no_account"));
- signupLink.setText(translation.get("sign_up"));
- appInfoLabel.setText(translation.get("app_info"));
-
+ usernameLabel.setText(getTranslationValue(langCode, "username_label", usernameLabel.getText()));
+ passwordLabel.setText(getTranslationValue(langCode, "password_label", passwordLabel.getText()));
+ rememberMe.setText(getTranslationValue(langCode, "remember_me", rememberMe.getText()));
+ forgotPassword.setText(getTranslationValue(langCode, "forgot_password", forgotPassword.getText()));
+ loginButton.setText(getTranslationValue(langCode, "login_button", loginButton.getText()));
+ noAccountLabel.setText(getTranslationValue(langCode, "no_account", noAccountLabel.getText()));
+ signupLink.setText(getTranslationValue(langCode, "sign_up", signupLink.getText()));
+ appInfoLabel.setText(getTranslationValue(langCode, "app_info", appInfoLabel.getText()));
+
// 鏇存柊娉ㄥ唽鎸夐挳鏂囨湰
- registerButton.setText(translation.get("register_button"));
-
+ registerButton.setText(getTranslationValue(langCode, "register_button", registerButton.getText()));
+
// 鏇存柊绐楀彛鏍囬
- setTitle("AutoMow - " + (langCode.equals("zh") ? "鐧诲綍" : "Login"));
+ setTitle(getTranslationValue(langCode, "window_title", getTitle()));
// 鏇存柊璇█璁剧疆鍒版枃浠�
updateLanguagePreference(langCode);
@@ -387,6 +394,17 @@
}
}
+ private String getTranslationValue(String languageCode, String key, String defaultValue) {
+ Map<String, String> translation = translations.get(languageCode);
+ if (translation != null) {
+ String value = translation.get(key);
+ if (value != null && !value.trim().isEmpty()) {
+ return value;
+ }
+ }
+ return defaultValue;
+ }
+
private void updateLanguagePreference(String languageCode) {
String currentLanguage = UserChuShiHua.getProperty("language");
if (!languageCode.equals(currentLanguage)) {
@@ -512,10 +530,5 @@
RegistrationFrame registerFrame = new RegistrationFrame(this, currentLanguageCode);
registerFrame.setVisible(true);
}
-
- public static void main(String[] args) {
- SwingUtilities.invokeLater(() -> {
- new Denglu().setVisible(true);
- });
- }
+
}
\ No newline at end of file
diff --git a/src/denglu/RegistrationFrame.java b/src/denglu/RegistrationFrame.java
index ace2dbf..7e5cb96 100644
--- a/src/denglu/RegistrationFrame.java
+++ b/src/denglu/RegistrationFrame.java
@@ -1,5 +1,6 @@
package denglu;
+import ui.UIConfig;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
@@ -44,7 +45,8 @@
// 涓枃缈昏瘧
Map<String, String> zh = new HashMap<>();
- zh.put("title", "鐢ㄦ埛娉ㄥ唽");
+ zh.put("title", "鐢ㄦ埛娉ㄥ唽");
+ zh.put("window_title", "鏅鸿兘鍓茶崏绯荤粺 - 鐢ㄦ埛娉ㄥ唽");
zh.put("username_label", "鐢ㄦ埛鍚�");
zh.put("email_label", "閭鍦板潃");
zh.put("verification_code_label", "楠岃瘉鐮�");
@@ -66,7 +68,8 @@
// 鑻辨枃缈昏瘧
Map<String, String> en = new HashMap<>();
- en.put("title", "User Registration");
+ en.put("title", "User Registration");
+ en.put("window_title", "Smart Mowing System - User Registration");
en.put("username_label", "Username");
en.put("email_label", "Email Address");
en.put("verification_code_label", "Verification Code");
@@ -88,7 +91,8 @@
// 瑗跨彮鐗欒缈昏瘧
Map<String, String> es = new HashMap<>();
- es.put("title", "Registro de Usuario");
+ es.put("title", "Registro de Usuario");
+ es.put("window_title", "Sistema de Corte Inteligente - Registro de Usuario");
es.put("username_label", "Usuario");
es.put("email_label", "Correo Electr贸nico");
es.put("verification_code_label", "C贸digo de Verificaci贸n");
@@ -110,7 +114,8 @@
// 娉曡缈昏瘧
Map<String, String> fr = new HashMap<>();
- fr.put("title", "Inscription Utilisateur");
+ fr.put("title", "Inscription Utilisateur");
+ fr.put("window_title", "Syst猫me de Tonte Intelligent - Inscription");
fr.put("username_label", "Nom d'utilisateur");
fr.put("email_label", "Adresse Email");
fr.put("verification_code_label", "Code de V茅rification");
@@ -132,7 +137,8 @@
// 寰疯缈昏瘧
Map<String, String> de = new HashMap<>();
- de.put("title", "Benutzerregistrierung");
+ de.put("title", "Benutzerregistrierung");
+ de.put("window_title", "Intelligentes M盲hsystem - Registrierung");
de.put("username_label", "Benutzername");
de.put("email_label", "E-Mail-Adresse");
de.put("verification_code_label", "Verifizierungscode");
@@ -153,10 +159,22 @@
translations.put("de", de);
}
+ private String getTranslationValue(String languageCode, String key, String defaultValue) {
+ Map<String, String> translation = translations.get(languageCode);
+ if (translation != null) {
+ String value = translation.get(key);
+ if (value != null && !value.trim().isEmpty()) {
+ return value;
+ }
+ }
+ return defaultValue;
+ }
+
private void initializeUI() {
- setTitle("AutoMow - 鐢ㄦ埛娉ㄥ唽");
+ setTitle(getTranslationValue(currentLanguageCode, "window_title", "鐢ㄦ埛娉ㄥ唽"));
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
- setSize(350, 500);
+ setSize(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
+ setMinimumSize(new Dimension(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT));
setLocationRelativeTo(parentFrame);
setResizable(false);
@@ -372,15 +390,15 @@
if (translation != null) {
// 鏇存柊鎵�鏈夌晫闈㈡枃鏈�
- setTitle("AutoMow - " + translation.get("title"));
- userLabel.setText(translation.get("username_label"));
- emailLabel.setText(translation.get("email_label"));
- verificationCodeLabel.setText(translation.get("verification_code_label"));
- passLabel.setText(translation.get("password_label"));
- confirmPassLabel.setText(translation.get("confirm_password_label"));
- registerButton.setText(translation.get("register_button"));
- cancelButton.setText(translation.get("cancel_button"));
- sendCodeButton.setText(translation.get("send_code_button"));
+ setTitle(getTranslationValue(languageCode, "window_title", getTitle()));
+ userLabel.setText(getTranslationValue(languageCode, "username_label", userLabel.getText()));
+ emailLabel.setText(getTranslationValue(languageCode, "email_label", emailLabel.getText()));
+ verificationCodeLabel.setText(getTranslationValue(languageCode, "verification_code_label", verificationCodeLabel.getText()));
+ passLabel.setText(getTranslationValue(languageCode, "password_label", passLabel.getText()));
+ confirmPassLabel.setText(getTranslationValue(languageCode, "confirm_password_label", confirmPassLabel.getText()));
+ registerButton.setText(getTranslationValue(languageCode, "register_button", registerButton.getText()));
+ cancelButton.setText(getTranslationValue(languageCode, "cancel_button", cancelButton.getText()));
+ sendCodeButton.setText(getTranslationValue(languageCode, "send_code_button", sendCodeButton.getText()));
}
}
diff --git a/src/dikuai/Dikuaiguanli.java b/src/dikuai/Dikuaiguanli.java
index 3d01450..a5e31b6 100644
--- a/src/dikuai/Dikuaiguanli.java
+++ b/src/dikuai/Dikuaiguanli.java
@@ -6,6 +6,9 @@
import java.awt.datatransfer.*;
import java.awt.datatransfer.StringSelection;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import ui.UIConfig;
@@ -17,6 +20,8 @@
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Objects;
+import java.util.Properties;
import lujing.Lunjingguihua;
import zhangaiwu.AddDikuai;
@@ -58,6 +63,8 @@
private JButton addLandBtn;
private static String currentWorkLandNumber;
+ private static final String WORK_LAND_KEY = "currentWorkLandNumber";
+ private static final String PROPERTIES_FILE = "set.properties";
private static final Map<String, Boolean> boundaryPointVisibility = new HashMap<>();
private ImageIcon workSelectedIcon;
private ImageIcon workUnselectedIcon;
@@ -504,6 +511,7 @@
boolean isCurrent = currentWorkLandNumber != null && currentWorkLandNumber.equals(landNumber);
if (isCurrent) {
renderer.setBoundaryPointsVisible(desiredState);
+ renderer.setBoundaryPointSizeScale(desiredState ? 0.5d : 1.0d);
}
}
}
@@ -583,17 +591,69 @@
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setPreferredSize(new Dimension(360, 240));
- int option = JOptionPane.showConfirmDialog(
- this,
- scrollPane,
- title,
- JOptionPane.OK_CANCEL_OPTION,
- JOptionPane.PLAIN_MESSAGE);
-
- if (option == JOptionPane.OK_OPTION) {
- return textArea.getText();
+ Window owner = SwingUtilities.getWindowAncestor(this);
+ JDialog dialog;
+ if (owner instanceof Frame) {
+ dialog = new JDialog((Frame) owner, title, true);
+ } else if (owner instanceof Dialog) {
+ dialog = new JDialog((Dialog) owner, title, true);
+ } else {
+ dialog = new JDialog((Frame) null, title, true);
}
- return null;
+ dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+ JPanel contentPanel = new JPanel(new BorderLayout());
+ contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ contentPanel.add(scrollPane, BorderLayout.CENTER);
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ JButton okButton = new JButton("纭畾");
+ JButton cancelButton = new JButton("鍙栨秷");
+ JButton copyButton = new JButton("澶嶅埗");
+
+ final boolean[] confirmed = new boolean[] {false};
+ final String[] resultHolder = new String[1];
+
+ okButton.addActionListener(e -> {
+ resultHolder[0] = textArea.getText();
+ confirmed[0] = true;
+ dialog.dispose();
+ });
+
+ cancelButton.addActionListener(e -> dialog.dispose());
+
+ copyButton.addActionListener(e -> {
+ String text = textArea.getText();
+ if (text == null) {
+ text = "";
+ }
+ String trimmed = text.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ JOptionPane.showMessageDialog(dialog, title + " 鏈缃�", "鎻愮ず", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ try {
+ StringSelection selection = new StringSelection(text);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clipboard.setContents(selection, selection);
+ JOptionPane.showMessageDialog(dialog, title + " 宸插鍒跺埌鍓创鏉�", "鎻愮ず", JOptionPane.INFORMATION_MESSAGE);
+ } catch (Exception ex) {
+ JOptionPane.showMessageDialog(dialog, "澶嶅埗澶辫触: " + ex.getMessage(), "閿欒", JOptionPane.ERROR_MESSAGE);
+ }
+ });
+
+ buttonPanel.add(okButton);
+ buttonPanel.add(cancelButton);
+ buttonPanel.add(copyButton);
+
+ contentPanel.add(buttonPanel, BorderLayout.SOUTH);
+ dialog.setContentPane(contentPanel);
+ dialog.getRootPane().setDefaultButton(okButton);
+ dialog.pack();
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+
+ return confirmed[0] ? resultHolder[0] : null;
}
private boolean saveFieldAndRefresh(Dikuai dikuai, String fieldName, String value) {
@@ -733,10 +793,26 @@
switch (trimmed) {
case "1":
case "spiral":
+ case "铻烘棆":
+ case "铻烘棆妯″紡":
return "spiral";
case "0":
case "parallel":
+ case "骞宠":
+ case "骞宠妯″紡":
default:
+ if (trimmed.contains("铻烘棆")) {
+ return "spiral";
+ }
+ if (trimmed.contains("spiral")) {
+ return "spiral";
+ }
+ if (trimmed.contains("parallel")) {
+ return "parallel";
+ }
+ if (trimmed.contains("骞宠")) {
+ return "parallel";
+ }
return "parallel";
}
}
@@ -1446,10 +1522,19 @@
}
public static void setCurrentWorkLand(String landNumber, String landName) {
- currentWorkLandNumber = landNumber;
+ String sanitizedLandNumber = sanitizeLandNumber(landNumber);
+ boolean changed = !Objects.equals(currentWorkLandNumber, sanitizedLandNumber);
+ if (!changed) {
+ String persisted = readPersistedWorkLandNumber();
+ if (!Objects.equals(persisted, sanitizedLandNumber)) {
+ changed = true;
+ }
+ }
+ currentWorkLandNumber = sanitizedLandNumber;
+
Dikuai dikuai = null;
- if (landNumber != null) {
- dikuai = Dikuai.getDikuai(landNumber);
+ if (sanitizedLandNumber != null) {
+ dikuai = Dikuai.getDikuai(sanitizedLandNumber);
if (dikuai != null && (landName == null || "-1".equals(landName) || landName.trim().isEmpty())) {
landName = dikuai.getLandName();
}
@@ -1460,7 +1545,7 @@
Shouye shouye = Shouye.getInstance();
if (shouye != null) {
- if (landNumber == null) {
+ if (sanitizedLandNumber == null) {
shouye.updateCurrentAreaName(null);
} else {
shouye.updateCurrentAreaName(landName);
@@ -1470,24 +1555,85 @@
renderer.applyLandMetadata(dikuai);
String boundary = (dikuai != null) ? dikuai.getBoundaryCoordinates() : null;
String plannedPath = (dikuai != null) ? dikuai.getPlannedPath() : null;
- List<Obstacledge.Obstacle> configuredObstacles = (landNumber != null) ? loadObstaclesFromConfig(landNumber) : Collections.emptyList();
+ List<Obstacledge.Obstacle> configuredObstacles = (sanitizedLandNumber != null) ? loadObstaclesFromConfig(sanitizedLandNumber) : Collections.emptyList();
if (configuredObstacles == null) {
configuredObstacles = Collections.emptyList();
}
- renderer.setCurrentBoundary(boundary, landNumber, landNumber == null ? null : landName);
+ renderer.setCurrentBoundary(boundary, sanitizedLandNumber, sanitizedLandNumber == null ? null : landName);
renderer.setCurrentPlannedPath(plannedPath);
- renderer.setCurrentObstacles(configuredObstacles, landNumber);
- boolean showBoundaryPoints = landNumber != null && boundaryPointVisibility.getOrDefault(landNumber, false);
+ renderer.setCurrentObstacles(configuredObstacles, sanitizedLandNumber);
+ boolean showBoundaryPoints = sanitizedLandNumber != null && boundaryPointVisibility.getOrDefault(sanitizedLandNumber, false);
renderer.setBoundaryPointsVisible(showBoundaryPoints);
+ renderer.setBoundaryPointSizeScale(showBoundaryPoints ? 0.5d : 1.0d);
}
shouye.refreshMowingIndicators();
}
+
+ if (changed) {
+ persistCurrentWorkLand(sanitizedLandNumber);
+ }
}
public static String getCurrentWorkLandNumber() {
return currentWorkLandNumber;
}
+ public static String getPersistedWorkLandNumber() {
+ return readPersistedWorkLandNumber();
+ }
+
+ private static String sanitizeLandNumber(String landNumber) {
+ if (landNumber == null) {
+ return null;
+ }
+ String trimmed = landNumber.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ return null;
+ }
+ return trimmed;
+ }
+
+ private static void persistCurrentWorkLand(String landNumber) {
+ synchronized (Dikuaiguanli.class) {
+ Properties props = new Properties();
+ try (FileInputStream in = new FileInputStream(PROPERTIES_FILE)) {
+ props.load(in);
+ } catch (IOException ignored) {
+ // Use empty defaults when the configuration file is missing.
+ }
+
+ if (landNumber == null) {
+ props.setProperty(WORK_LAND_KEY, "-1");
+ } else {
+ props.setProperty(WORK_LAND_KEY, landNumber);
+ }
+
+ try (FileOutputStream out = new FileOutputStream(PROPERTIES_FILE)) {
+ props.store(out, "Current work land selection updated");
+ } catch (IOException ex) {
+ System.err.println("鏃犳硶淇濆瓨褰撳墠浣滀笟鍦板潡: " + ex.getMessage());
+ }
+ }
+ }
+
+ private static String readPersistedWorkLandNumber() {
+ Properties props = new Properties();
+ try (FileInputStream in = new FileInputStream(PROPERTIES_FILE)) {
+ props.load(in);
+ String value = props.getProperty(WORK_LAND_KEY);
+ if (value == null) {
+ return null;
+ }
+ String trimmed = value.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ return null;
+ }
+ return trimmed;
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
private ImageIcon loadIcon(String path, int width, int height) {
try {
ImageIcon rawIcon = new ImageIcon(path);
diff --git a/src/dikuai/addzhangaiwu.java b/src/dikuai/addzhangaiwu.java
index b1c1405..f14b481 100644
--- a/src/dikuai/addzhangaiwu.java
+++ b/src/dikuai/addzhangaiwu.java
@@ -46,6 +46,7 @@
import set.Setsys;
import ui.UIConfig;
import zhuye.Coordinate;
+import zhuye.MapRenderer;
import zhuye.Shouye;
import zhangaiwu.AddDikuai;
import zhangaiwu.Obstacledge;
@@ -887,6 +888,15 @@
activeDrawingShape = shape.toLowerCase(Locale.ROOT);
+ Shouye shouyeInstance = Shouye.getInstance();
+ if (shouyeInstance != null) {
+ shouyeInstance.setHandheldMowerIconActive("handheld".equalsIgnoreCase(method));
+ MapRenderer renderer = shouyeInstance.getMapRenderer();
+ if (renderer != null) {
+ renderer.clearIdleTrail();
+ }
+ }
+
Coordinate.coordinates.clear();
Coordinate.setActiveDeviceIdFilter(deviceId);
Coordinate.setStartSaveGngga(true);
diff --git a/src/gecaoji/Gecaoji.java b/src/gecaoji/Gecaoji.java
index 177832f..2aca713 100644
--- a/src/gecaoji/Gecaoji.java
+++ b/src/gecaoji/Gecaoji.java
@@ -15,27 +15,35 @@
private static final double ICON_SCALE_FACTOR = 0.8;
private static final double MIN_SCALE = 1e-6;
private static final Color FALLBACK_FILL = new Color(0, 150, 0);
+ private static final String DEFAULT_ICON_PATH = "image/gecaoji.png";
+ private static final String HANDHELD_ICON_PATH = "image/URT.png";
- private final Image mowerIcon;
+ private final Image defaultIcon;
+ private final Image handheldIcon;
+ private Image activeIcon;
+ private boolean handheldIconActive;
private final Ellipse2D.Double fallbackShape = new Ellipse2D.Double();
private Point2D.Double position = new Point2D.Double();
private boolean positionValid;
private double headingDegrees;
public Gecaoji() {
- mowerIcon = loadIcon();
+ defaultIcon = loadIcon(DEFAULT_ICON_PATH);
+ handheldIcon = loadIcon(HANDHELD_ICON_PATH);
+ activeIcon = defaultIcon != null ? defaultIcon : handheldIcon;
+ handheldIconActive = false;
refreshFromDevice();
}
- private Image loadIcon() {
+ private Image loadIcon(String path) {
try {
- ImageIcon icon = new ImageIcon("image/gecaoji.png");
+ ImageIcon icon = new ImageIcon(path);
if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) {
return null;
}
return icon.getImage();
} catch (Exception ex) {
- System.err.println("Unable to load mower icon: " + ex.getMessage());
+ System.err.println("Unable to load mower icon from " + path + ": " + ex.getMessage());
return null;
}
}
@@ -51,7 +59,7 @@
double y = parseCoordinate(device.getRealtimeY());
double heading = parseHeading(device.getHeading());
if (Double.isNaN(x) || Double.isNaN(y)) {
- positionValid = false;
+ // Keep showing the last known mower position when temporary sensor glitches occur.
return;
}
@@ -109,16 +117,17 @@
}
double worldSize = (ICON_PIXEL_SIZE * ICON_SCALE_FACTOR) / Math.max(scale, MIN_SCALE);
- if (mowerIcon != null && mowerIcon.getWidth(null) > 0 && mowerIcon.getHeight(null) > 0) {
- drawIcon(g2d, worldSize);
+ Image icon = activeIcon;
+ if (icon != null && icon.getWidth(null) > 0 && icon.getHeight(null) > 0) {
+ drawIcon(g2d, worldSize, icon);
} else {
drawFallback(g2d, worldSize);
}
}
- private void drawIcon(Graphics2D g2d, double worldSize) {
- double iconWidth = mowerIcon.getWidth(null);
- double iconHeight = mowerIcon.getHeight(null);
+ private void drawIcon(Graphics2D g2d, double worldSize, Image icon) {
+ double iconWidth = icon.getWidth(null);
+ double iconHeight = icon.getHeight(null);
double maxSide = Math.max(iconWidth, iconHeight);
double scaleFactor = worldSize / Math.max(maxSide, MIN_SCALE);
double rotationRadians = Math.toRadians(-headingDegrees);
@@ -132,7 +141,7 @@
transformed.scale(scaleFactor, scaleFactor);
transformed.translate(-iconWidth / 2.0, -iconHeight / 2.0);
g2d.setTransform(transformed);
- g2d.drawImage(mowerIcon, 0, 0, null);
+ g2d.drawImage(icon, 0, 0, null);
g2d.setTransform(original);
}
@@ -177,4 +186,19 @@
double worldSize = (ICON_PIXEL_SIZE * ICON_SCALE_FACTOR) / Math.max(scale, MIN_SCALE);
return worldSize / 2.0;
}
+
+ public boolean useHandheldIcon(boolean handheldMode) {
+ if (handheldIconActive == handheldMode) {
+ return false;
+ }
+ handheldIconActive = handheldMode;
+ Image next = handheldMode
+ ? (handheldIcon != null ? handheldIcon : defaultIcon)
+ : defaultIcon;
+ if (next == null) {
+ next = handheldIcon;
+ }
+ activeIcon = next;
+ return true;
+ }
}
diff --git a/src/gecaoji/lujingdraw.java b/src/gecaoji/lujingdraw.java
index 15a4c46..813472d 100644
--- a/src/gecaoji/lujingdraw.java
+++ b/src/gecaoji/lujingdraw.java
@@ -70,7 +70,7 @@
/** // 鏂囨。娉ㄩ噴寮�濮�
* Draw the planned mowing path. // 缁樺埗璺緞
*/ // 鏂囨。娉ㄩ噴缁撴潫
- public static void drawPlannedPath(Graphics2D g2d, List<Point2D.Double> path, double scale) { // 缁樺埗涓绘柟娉�
+ public static void drawPlannedPath(Graphics2D g2d, List<Point2D.Double> path, double scale, double arrowScale) { // 缁樺埗涓绘柟娉�
if (path == null || path.size() < 2) { // 鍒ゅ畾鐐规暟
return; // 鏁版嵁涓嶈冻鐩存帴杩斿洖
} // if缁撴潫
@@ -97,13 +97,13 @@
Point2D.Double end = path.get(path.size() - 1); // 缁堢偣
Point2D.Double prev = path.get(path.size() - 2); // 鍊掓暟绗簩涓偣
- drawArrowMarker(g2d, start, second, START_POINT_COLOR, scale); // 缁樺埗璧风偣绠ご
- drawArrowMarker(g2d, prev, end, END_POINT_COLOR, scale); // 缁樺埗缁堢偣绠ご
+ drawArrowMarker(g2d, start, second, START_POINT_COLOR, scale, arrowScale); // 缁樺埗璧风偣绠ご
+ drawArrowMarker(g2d, prev, end, END_POINT_COLOR, scale, arrowScale); // 缁樺埗缁堢偣绠ご
g2d.setStroke(previous); // 鎭㈠鍘熸弿杈�
} // 鏂规硶缁撴潫
- private static void drawArrowMarker(Graphics2D g2d, Point2D.Double from, Point2D.Double to, Color color, double scale) { // 缁樺埗绠ご杈呭姪
+ private static void drawArrowMarker(Graphics2D g2d, Point2D.Double from, Point2D.Double to, Color color, double scale, double sizeScale) { // 缁樺埗绠ご杈呭姪
if (from == null || to == null) { // 鍒ょ┖
return; // 鏁版嵁涓嶈冻杩斿洖
} // if缁撴潫
@@ -115,7 +115,8 @@
} // if缁撴潫
double arrowLength = Math.max(2.5, 5.5 / Math.max(0.5, scale)); // 璁$畻绠ご闀垮害
- arrowLength *= 0.5; // 缂╁皬绠ご灏哄涓哄師鏉ョ殑涓�鍗�
+ double clampedScale = sizeScale > 0 ? sizeScale : 1.0; // 闃叉闈炴硶缂╂斁
+ arrowLength *= 0.25 * clampedScale; // 缂╁皬绠ご鑷冲師鏉ョ殑涓�鍗�
double arrowWidth = arrowLength * 0.45; // 璁$畻绠ご瀹藉害
double ux = dx / length; // 鍗曚綅鍚戦噺X
diff --git a/src/homein/Homein.java b/src/homein/Homein.java
index fd2ad95..acd24f6 100644
--- a/src/homein/Homein.java
+++ b/src/homein/Homein.java
@@ -3,9 +3,11 @@
import denglu.UserChuShiHua;
import gecaoji.Device;
import chuankou.SerialPortAutoConnector;
+import chuankou.SerialPortNativeLoader;
import set.Setsys;
import udpdell.UDPServer;
import denglu.Denglu;
+import java.awt.EventQueue;
import javax.swing.JOptionPane;
public class Homein {
@@ -26,15 +28,16 @@
}));
try {
- // 鍒濆鍖栨暟鎹�
- UserChuShiHua.initialize();
+ SerialPortNativeLoader.ensureLibraryPresent();
+ // 鍒濆鍖栨暟鎹�
+ UserChuShiHua.initialize();
- Setsys setsys = new Setsys();
- setsys.initializeFromProperties();
- Device.initializeActiveDevice(setsys.getMowerId());
+ Setsys setsys = new Setsys();
+ setsys.initializeFromProperties();
+ Device.initializeActiveDevice(setsys.getMowerId());
UDPServer.startAsync();//鍚姩鏁版嵁鎺ユ敹绾跨▼
- SerialPortAutoConnector.initialize();//鍚姩涓插彛鑷姩杩炴帴
+// SerialPortAutoConnector.initialize();//鍚姩涓插彛鑷姩杩炴帴
// 鏄剧ず鍒濆鏁版嵁
System.out.println("鍒濆鐢ㄦ埛鍚�: " + UserChuShiHua.getProperty("userName"));
@@ -54,17 +57,15 @@
private static void startLoginInterface() {
// 鍦‥DT涓惎鍔ㄧ櫥褰曠晫闈�
- javax.swing.SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- try {
- new Denglu().setVisible(true);
- } catch (Exception e) {
- e.printStackTrace();
- JOptionPane.showMessageDialog(null,
- "鐧诲綍鐣岄潰鍚姩澶辫触: " + e.getMessage(),
- "閿欒",
- JOptionPane.ERROR_MESSAGE);
- }
+ EventQueue.invokeLater(() -> {
+ try {
+ new Denglu().setVisible(true);
+ } catch (Exception e) {
+ e.printStackTrace();
+ JOptionPane.showMessageDialog(null,
+ "鐧诲綍鐣岄潰鍚姩澶辫触: " + e.getMessage(),
+ "閿欒",
+ JOptionPane.ERROR_MESSAGE);
}
});
}
diff --git a/src/lujing/Lunjingguihua.java b/src/lujing/Lunjingguihua.java
index 3b1c99b..907a4f7 100644
--- a/src/lujing/Lunjingguihua.java
+++ b/src/lujing/Lunjingguihua.java
@@ -266,8 +266,8 @@
}
List<PathSegment> generate() {
- if (!"spiral".equals(mode)) {
- return generateParallelPath();
+ if ("spiral".equals(mode)) {
+ return generateSpiralPath();
}
return generateParallelPath();
}
@@ -361,6 +361,39 @@
return path;
}
+ private List<PathSegment> generateSpiralPath() {
+ Geometry safeArea = buildSafeArea();
+ if (safeArea == null || safeArea.isEmpty()) {
+ System.err.println("瀹夊叏鍖哄煙涓虹┖锛屾棤娉曠敓鎴愯灪鏃嬭矾寰�");
+ return new ArrayList<>();
+ }
+
+ List<PathSegment> spiral = luoxuan.generateSpiralPath(safeArea, width);
+ if (spiral.isEmpty()) {
+ return spiral;
+ }
+
+ postProcess(spiral);
+ PathSegment firstMowing = null;
+ PathSegment endCandidate = null;
+ for (int i = 0; i < spiral.size(); i++) {
+ PathSegment seg = spiral.get(i);
+ if (seg != null && seg.isMowing) {
+ if (firstMowing == null) {
+ firstMowing = seg;
+ }
+ endCandidate = seg;
+ }
+ }
+ if (firstMowing != null) {
+ firstMowing.setAsStartPoint();
+ }
+ if (endCandidate != null && endCandidate != firstMowing) {
+ endCandidate.setAsEndPoint();
+ }
+ return spiral;
+ }
+
private Geometry buildSafeArea() {
try {
Coordinate[] coords = polygon.toArray(new Coordinate[0]);
diff --git a/src/lujing/luoxuan.java b/src/lujing/luoxuan.java
new file mode 100644
index 0000000..c660e05
--- /dev/null
+++ b/src/lujing/luoxuan.java
@@ -0,0 +1,408 @@
+package lujing;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.TopologyException;
+import org.locationtech.jts.operation.buffer.BufferParameters;
+
+import lujing.Lunjingguihua.PathSegment;
+
+/**
+ * Utility class that produces spiral mowing paths by iteratively offsetting a safe area polygon.
+ * 浼樺寲鐗堬細鏀硅繘璺緞鐢熸垚閫昏緫锛屽噺灏戠┖椹惰窛绂伙紝浼樺寲璺緞杩炵画鎬�
+ */
+public final class luoxuan {
+
+ private static final int MAX_ITERATIONS = 512;
+ private static final double AREA_EPSILON = 1e-2;
+ private static final double LENGTH_EPSILON = 1e-6;
+ private static final double MIN_BUFFER_RATIO = 0.6; // 鏈�灏忕紦鍐叉瘮渚�
+
+ private luoxuan() {
+ }
+
+ /**
+ * Generate optimized spiral mowing paths with improved continuity
+ */
+ public static List<PathSegment> generateOptimizedSpiralPath(Geometry safeArea, double laneWidth) {
+ if (safeArea == null || safeArea.isEmpty() || !Double.isFinite(laneWidth) || laneWidth <= 0) {
+ return Collections.emptyList();
+ }
+
+ // 1. 娓呯悊鍑犱綍浣擄紝纭繚鏈夋晥鎬�
+ Geometry working = cleanGeometry(safeArea);
+ if (working.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ // 2. 鎻愬彇涓诲杈瑰舰锛堥�夋嫨闈㈢Н鏈�澶х殑锛�
+ Polygon mainPolygon = extractMainPolygon(working);
+ if (mainPolygon == null) {
+ return Collections.emptyList();
+ }
+
+ // 3. 璁$畻铻烘棆璺緞
+ List<PathSegment> segments = new ArrayList<>();
+ Coordinate cursor = clone(findOptimalStartPoint(mainPolygon));
+ Geometry currentLayer = mainPolygon; // start from the outer boundaryand peel inwards
+
+ for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
+ Geometry layerGeometry = cleanGeometry(currentLayer);
+ if (layerGeometry == null || layerGeometry.isEmpty()) {
+ break;
+ }
+
+ List<Polygon> polygons = extractPolygons(layerGeometry);
+ if (polygons.isEmpty()) {
+ break;
+ }
+ polygons.sort(Comparator.comparingDouble(Polygon::getArea).reversed());
+
+ try {
+ for (Polygon polygon : polygons) {
+ LinearRing outer = polygon.getExteriorRing();
+ if (outer == null || outer.getNumPoints() < 4) {
+ continue;
+ }
+
+ cursor = processRing(outer.getCoordinates(), true, cursor, segments);
+
+ for (int holeIndex = 0; holeIndex < polygon.getNumInteriorRing(); holeIndex++) {
+ LinearRing hole = polygon.getInteriorRingN(holeIndex);
+ if (hole == null || hole.getNumPoints() < 4) {
+ continue;
+ }
+ cursor = processRing(hole.getCoordinates(), false, cursor, segments);
+ }
+ }
+ } catch (TopologyException ex) {
+ break;
+ }
+
+ if (!canShrinkFurther(polygons, laneWidth)) {
+ break;
+ }
+
+ Geometry nextLayer;
+ try {
+ nextLayer = layerGeometry.buffer(
+ -laneWidth,
+ BufferParameters.DEFAULT_QUADRANT_SEGMENTS,
+ BufferParameters.CAP_FLAT
+ );
+ } catch (TopologyException ex) {
+ break;
+ }
+
+ if (nextLayer.isEmpty() || nextLayer.getArea() < AREA_EPSILON) {
+ break;
+ }
+
+ double areaDelta = Math.abs(layerGeometry.getArea() - nextLayer.getArea());
+ if (areaDelta <= AREA_EPSILON) {
+ break;
+ }
+
+ currentLayer = nextLayer;
+ }
+
+ // 4. 浼樺寲璺緞杩炴帴
+ optimizePathConnections(segments);
+
+ // 5. 鏍囪绔偣
+ markEndpoints(segments);
+
+ return segments;
+ }
+
+ /**
+ * Backward compatible entry that delegates to the optimized implementation.
+ */
+ public static List<PathSegment> generateSpiralPath(Geometry safeArea, double laneWidth) {
+ return generateOptimizedSpiralPath(safeArea, laneWidth);
+ }
+
+ /**
+ * 娓呯悊鍑犱綍浣擄紝纭繚鏈夋晥鎬�
+ */
+ private static Geometry cleanGeometry(Geometry geometry) {
+ if (geometry == null) return null;
+ try {
+ return geometry.buffer(0.0);
+ } catch (Exception e) {
+ return geometry;
+ }
+ }
+
+ /**
+ * 鎻愬彇涓诲杈瑰舰锛堥潰绉渶澶х殑锛�
+ */
+ private static Polygon extractMainPolygon(Geometry geometry) {
+ List<Polygon> polygons = extractPolygons(geometry);
+ if (polygons.isEmpty()) return null;
+
+ // 鎸夐潰绉帓搴忥紝閫夋嫨鏈�澶х殑
+ polygons.sort((p1, p2) -> Double.compare(p2.getArea(), p1.getArea()));
+ return polygons.get(0);
+ }
+
+ /**
+ * 瀵绘壘鏈�浼樿捣鐐癸紙绂诲杈瑰舰涓績鏈�杩戠殑鐐癸級
+ */
+ private static Coordinate findOptimalStartPoint(Polygon polygon) {
+ if (polygon == null) return null;
+
+ Coordinate center = polygon.getCentroid().getCoordinate();
+ LinearRing ring = polygon.getExteriorRing();
+ Coordinate[] coords = ring.getCoordinates();
+
+ Coordinate nearest = coords[0];
+ double minDist = Double.MAX_VALUE;
+
+ for (Coordinate coord : coords) {
+ double dist = coord.distance(center);
+ if (dist < minDist) {
+ minDist = dist;
+ nearest = coord;
+ }
+ }
+
+ return nearest;
+ }
+
+ /**
+ * 浼樺寲璺緞杩炴帴锛屽噺灏戠┖椹惰窛绂�
+ */
+ private static void optimizePathConnections(List<PathSegment> segments) {
+ if (segments == null || segments.size() < 2) {
+ return;
+ }
+
+ List<PathSegment> optimized = new ArrayList<>(segments.size());
+ PathSegment previous = null;
+
+ for (PathSegment segment : segments) {
+ if (segment == null || segment.start == null || segment.end == null) {
+ continue;
+ }
+ if (isDegenerate(segment)) {
+ continue;
+ }
+
+ if (previous != null
+ && previous.isMowing == segment.isMowing
+ && equals2D(previous.start, segment.start)
+ && equals2D(previous.end, segment.end)) {
+ continue; // 璺宠繃閲嶅娈�
+ }
+
+ optimized.add(segment);
+ previous = segment;
+ }
+
+ segments.clear();
+ segments.addAll(optimized);
+ }
+
+ /**
+ * 妫�鏌ユ槸鍚﹀彲浠ョ户缁紦鍐�
+ */
+ private static boolean canShrinkFurther(List<Polygon> polygons, double bufferDistance) {
+ if (polygons == null || polygons.isEmpty()) {
+ return false;
+ }
+
+ for (Polygon polygon : polygons) {
+ if (polygon == null || polygon.isEmpty()) {
+ continue;
+ }
+
+ double width = polygon.getEnvelopeInternal().getWidth();
+ double height = polygon.getEnvelopeInternal().getHeight();
+ double minDimension = Math.min(width, height);
+
+ if (minDimension <= bufferDistance * 2 * MIN_BUFFER_RATIO) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 鏍囪璧风偣鍜岀粓鐐�
+ */
+ private static void markEndpoints(List<PathSegment> segments) {
+ if (segments == null || segments.isEmpty()) {
+ return;
+ }
+
+ // 瀵绘壘绗竴涓壊鑽夋浣滀负璧风偣
+ PathSegment firstMowing = null;
+ for (PathSegment seg : segments) {
+ if (seg != null && seg.isMowing) {
+ firstMowing = seg;
+ break;
+ }
+ }
+
+ // 瀵绘壘鏈�鍚庝竴涓壊鑽夋浣滀负缁堢偣
+ PathSegment lastMowing = null;
+ for (int i = segments.size() - 1; i >= 0; i--) {
+ PathSegment seg = segments.get(i);
+ if (seg != null && seg.isMowing) {
+ lastMowing = seg;
+ break;
+ }
+ }
+
+ if (firstMowing != null) {
+ firstMowing.setAsStartPoint();
+ }
+ if (lastMowing != null && lastMowing != firstMowing) {
+ lastMowing.setAsEndPoint();
+ }
+ }
+
+ /**
+ * 妫�鏌ョ嚎娈垫槸鍚﹂��鍖栵紙闀垮害杩囧皬锛�
+ */
+ private static boolean isDegenerate(PathSegment segment) {
+ if (segment == null || segment.start == null || segment.end == null) {
+ return true;
+ }
+ double dx = segment.start.x - segment.end.x;
+ double dy = segment.start.y - segment.end.y;
+ return Math.hypot(dx, dy) <= LENGTH_EPSILON;
+ }
+
+ /**
+ * 鎻愬彇澶氳竟褰紙涓庡師鏂规硶鐩稿悓锛�
+ */
+ private static List<Polygon> extractPolygons(Geometry geometry) {
+ if (geometry == null || geometry.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<Polygon> result = new ArrayList<>();
+
+ if (geometry instanceof Polygon) {
+ result.add((Polygon) geometry);
+ } else if (geometry instanceof org.locationtech.jts.geom.MultiPolygon) {
+ org.locationtech.jts.geom.MultiPolygon mp = (org.locationtech.jts.geom.MultiPolygon) geometry;
+ for (int i = 0; i < mp.getNumGeometries(); i++) {
+ Geometry g = mp.getGeometryN(i);
+ if (g instanceof Polygon) {
+ result.add((Polygon) g);
+ }
+ }
+ } else if (geometry instanceof org.locationtech.jts.geom.GeometryCollection) {
+ org.locationtech.jts.geom.GeometryCollection gc = (org.locationtech.jts.geom.GeometryCollection) geometry;
+ for (int i = 0; i < gc.getNumGeometries(); i++) {
+ Geometry child = gc.getGeometryN(i);
+ result.addAll(extractPolygons(child));
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * 澶嶅埗鍧愭爣
+ */
+ private static Coordinate clone(Coordinate source) {
+ return source == null ? null : new Coordinate(source.x, source.y);
+ }
+
+ /**
+ * 姣旇緝涓や釜鍧愭爣鏄惁鐩稿悓锛�2D锛�
+ */
+ private static boolean equals2D(Coordinate a, Coordinate b) {
+ if (a == b) return true;
+ if (a == null || b == null) return false;
+ return a.distance(b) <= LENGTH_EPSILON;
+ }
+
+ private static Coordinate processRing(Coordinate[] coords,
+ boolean forward,
+ Coordinate cursor,
+ List<PathSegment> segments) {
+ if (coords == null || coords.length < 4) {
+ return cursor;
+ }
+
+ List<Coordinate> base = new ArrayList<>(coords.length - 1);
+ for (int i = 0; i < coords.length - 1; i++) {
+ Coordinate cloned = clone(coords[i]);
+ if (cloned != null) {
+ base.add(cloned);
+ }
+ }
+
+ if (base.size() < 2) {
+ return cursor;
+ }
+
+ if (!forward) {
+ Collections.reverse(base);
+ }
+
+ int startIndex = 0;
+ if (cursor != null) {
+ startIndex = findNearestIndex(base, cursor);
+ }
+
+ List<Coordinate> ordered = new ArrayList<>(base.size());
+ for (int i = 0; i < base.size(); i++) {
+ int index = (startIndex + i) % base.size();
+ ordered.add(clone(base.get(index)));
+ }
+
+ Coordinate firstCoord = ordered.get(0);
+ if (cursor != null && !equals2D(cursor, firstCoord)) {
+ PathSegment transfer = new PathSegment(clone(cursor), clone(firstCoord), false);
+ if (!isDegenerate(transfer)) {
+ segments.add(transfer);
+ }
+ }
+
+ for (int i = 0; i < ordered.size(); i++) {
+ Coordinate start = ordered.get(i);
+ Coordinate end = ordered.get((i + 1) % ordered.size());
+ if (equals2D(start, end)) {
+ continue;
+ }
+ PathSegment mowing = new PathSegment(clone(start), clone(end), true);
+ segments.add(mowing);
+ }
+
+ return clone(firstCoord);
+ }
+
+ private static int findNearestIndex(List<Coordinate> coordinates, Coordinate reference) {
+ if (coordinates == null || coordinates.isEmpty() || reference == null) {
+ return 0;
+ }
+ double bestDistance = Double.MAX_VALUE;
+ int bestIndex = 0;
+ for (int i = 0; i < coordinates.size(); i++) {
+ Coordinate candidate = coordinates.get(i);
+ if (candidate == null) {
+ continue;
+ }
+ double distance = reference.distance(candidate);
+ if (distance < bestDistance) {
+ bestDistance = distance;
+ bestIndex = i;
+ }
+ }
+ return bestIndex;
+ }
+}
\ No newline at end of file
diff --git a/src/set/Sets.java b/src/set/Sets.java
index 224795f..deaacd8 100644
--- a/src/set/Sets.java
+++ b/src/set/Sets.java
@@ -30,6 +30,7 @@
// 璁剧疆椤圭粍浠�
private JLabel mowerIdLabel;
+ private JLabel baseStationIdLabel;
private JLabel handheldMarkerLabel;
private JLabel simCardNumberLabel;
private JLabel baseStationSimLabel;
@@ -38,6 +39,7 @@
private JLabel idleTrailDurationLabel;
private JButton mowerIdEditBtn;
+ private JButton baseStationIdEditBtn;
private JButton handheldEditBtn;
private JButton checkUpdateBtn;
private JButton systemDebugButton;
@@ -105,6 +107,11 @@
setData.getMowerId() != null ? setData.getMowerId() : "鏈缃�", true);
mowerIdLabel = (JLabel) mowerIdPanel.getClientProperty("valueLabel");
mowerIdEditBtn = (JButton) mowerIdPanel.getClientProperty("editButton");
+
+ JPanel baseStationIdPanel = createSettingItemPanel("宸垎鍩哄噯绔欑紪鍙�",
+ resolveBaseStationId(), true);
+ baseStationIdLabel = (JLabel) baseStationIdPanel.getClientProperty("valueLabel");
+ baseStationIdEditBtn = (JButton) baseStationIdPanel.getClientProperty("editButton");
JPanel handheldPanel = createSettingItemPanel("渚挎惡鎵撶偣鍣ㄧ紪鍙�",
setData.getHandheldMarkerId() != null ? setData.getHandheldMarkerId() : "鏈缃�", true);
@@ -135,7 +142,8 @@
// APP鐗堟湰
JPanel appVersionPanel = createAppVersionPanel();
- addRowWithSpacing(panel, mowerIdPanel);
+ addRowWithSpacing(panel, mowerIdPanel);
+ addRowWithSpacing(panel, baseStationIdPanel);
addRowWithSpacing(panel, handheldPanel);
addRowWithSpacing(panel, simCardPanel);
addRowWithSpacing(panel, baseStationSimPanel);
@@ -432,6 +440,10 @@
mowerIdLabel.setText(setData.getMowerId() != null ? setData.getMowerId() : "鏈缃�");
}
+ if (baseStationIdLabel != null) {
+ baseStationIdLabel.setText(resolveBaseStationId());
+ }
+
if (handheldMarkerLabel != null) {
handheldMarkerLabel.setText(setData.getHandheldMarkerId() != null ? setData.getHandheldMarkerId() : "鏈缃�");
}
@@ -477,12 +489,31 @@
}
return trimmed;
}
+
+ private String resolveBaseStationId() {
+ if (baseStation == null) {
+ return "鏈缃�";
+ }
+ String value = baseStation.getDeviceId();
+ if (value == null) {
+ return "鏈缃�";
+ }
+ String trimmed = value.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ return "鏈缃�";
+ }
+ return trimmed;
+ }
private void setupEventHandlers() {
// 鍓茶崏鏈虹紪鍙风紪杈戞寜閽簨浠�
if (mowerIdEditBtn != null) {
mowerIdEditBtn.addActionListener(e -> editMowerId());
}
+
+ if (baseStationIdEditBtn != null) {
+ baseStationIdEditBtn.addActionListener(e -> editBaseStationId());
+ }
// 妫�鏌ユ洿鏂版寜閽簨浠�
if (checkUpdateBtn != null) {
@@ -559,6 +590,45 @@
}
}
+ private void editBaseStationId() {
+ String currentValue = "鏈缃�".equals(resolveBaseStationId()) ? "" : resolveBaseStationId();
+ String newValue = (String) JOptionPane.showInputDialog(this,
+ "璇疯緭鍏ュ樊鍒嗗熀鍑嗙珯缂栧彿:",
+ "淇敼宸垎鍩哄噯绔欑紪鍙�",
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ null,
+ currentValue);
+
+ if (newValue == null) {
+ return;
+ }
+
+ newValue = newValue.trim();
+ if (newValue.isEmpty()) {
+ JOptionPane.showMessageDialog(this, "宸垎鍩哄噯绔欑紪鍙蜂笉鑳戒负绌�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ try {
+ baseStation.updateByDeviceId(newValue,
+ baseStation.getInstallationCoordinates(),
+ baseStation.getIotSimCardNumber(),
+ baseStation.getDeviceActivationTime(),
+ baseStation.getDataUpdateTime());
+ baseStation.load();
+ if (baseStationIdLabel != null) {
+ baseStationIdLabel.setText(resolveBaseStationId());
+ }
+ JOptionPane.showMessageDialog(this, "宸垎鍩哄噯绔欑紪鍙锋洿鏂版垚鍔�", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ } catch (IllegalArgumentException ex) {
+ JOptionPane.showMessageDialog(this, ex.getMessage(), "杈撳叆閿欒", JOptionPane.WARNING_MESSAGE);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ JOptionPane.showMessageDialog(this, "宸垎鍩哄噯绔欑紪鍙锋洿鏂板け璐�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
private void editIdleTrailDuration() {
int currentSeconds = setData != null ? setData.getIdleTrailDurationSeconds() : Setsys.DEFAULT_IDLE_TRAIL_DURATION_SECONDS;
if (currentSeconds <= 0) {
diff --git a/src/zhangaiwu/AddDikuai.java b/src/zhangaiwu/AddDikuai.java
index ee81258..263b539 100644
--- a/src/zhangaiwu/AddDikuai.java
+++ b/src/zhangaiwu/AddDikuai.java
@@ -14,16 +14,17 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Comparator;
import java.awt.geom.Point2D;
+import baseStation.BaseStation;
import bianjie.jisuanmianjie;
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
-import gecaoji.Device;
import bianjie.bianjieguihua2;
import lujing.Lunjingguihua;
import ui.UIConfig;
@@ -49,6 +50,9 @@
private final Color LIGHT_TEXT = new Color(108, 117, 125);
private final Color BORDER_COLOR = new Color(222, 226, 230);
private final Color SUCCESS_COLOR = new Color(40, 167, 69);
+ private final Color ERROR_COLOR = new Color(220, 53, 69);
+ private static final String KEY_PATH_MESSAGE_TEXT = "__pathMessageText";
+ private static final String KEY_PATH_MESSAGE_SUCCESS = "__pathMessageSuccess";
// 姝ラ闈㈡澘
private JPanel mainPanel;
@@ -68,8 +72,12 @@
private JButton prevButton;
private JButton nextButton;
private JButton createButton;
+ private JButton previewButton;
+ private Component previewButtonSpacer;
private JLabel boundaryCountLabel;
private JPanel obstacleListContainer;
+ private JTextArea pathGenerationMessageArea;
+ private JPanel pathMessageWrapper;
// 鍦板潡鏁版嵁
private Map<String, String> dikuaiData = new HashMap<>();
@@ -967,6 +975,29 @@
stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
stepPanel.add(generatePathButton);
+ stepPanel.add(Box.createRigidArea(new Dimension(0, 12)));
+
+ pathMessageWrapper = new JPanel(new BorderLayout());
+ pathMessageWrapper.setAlignmentX(Component.LEFT_ALIGNMENT);
+ pathMessageWrapper.setBackground(PRIMARY_LIGHT);
+ pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(PRIMARY_COLOR, 1),
+ BorderFactory.createEmptyBorder(12, 12, 12, 12)
+ ));
+ pathMessageWrapper.setVisible(false);
+
+ pathGenerationMessageArea = new JTextArea();
+ pathGenerationMessageArea.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 14));
+ pathGenerationMessageArea.setForeground(TEXT_COLOR);
+ pathGenerationMessageArea.setOpaque(false);
+ pathGenerationMessageArea.setEditable(false);
+ pathGenerationMessageArea.setLineWrap(true);
+ pathGenerationMessageArea.setWrapStyleWord(true);
+ pathGenerationMessageArea.setFocusable(false);
+ pathGenerationMessageArea.setBorder(null);
+
+ pathMessageWrapper.add(pathGenerationMessageArea, BorderLayout.CENTER);
+ stepPanel.add(pathMessageWrapper);
stepPanel.add(Box.createVerticalGlue());
return stepPanel;
@@ -1026,6 +1057,9 @@
private void generateMowingPath() {
if (!dikuaiData.containsKey("boundaryDrawn")) {
JOptionPane.showMessageDialog(this, "璇峰厛瀹屾垚杈圭晫缁樺埗鍚庡啀鐢熸垚璺緞", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ dikuaiData.remove("plannedPath");
+ showPathGenerationMessage("璇峰厛瀹屾垚杈圭晫缁樺埗鍚庡啀鐢熸垚璺緞銆�", false);
+ setPathAvailability(false);
showStep(2);
return;
}
@@ -1039,9 +1073,9 @@
}
if (boundaryCoords == null) {
JOptionPane.showMessageDialog(this, "鏈壘鍒版湁鏁堢殑鍦板潡杈圭晫鍧愭爣锛屾棤娉曠敓鎴愯矾寰�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
- if (createButton != null) {
- createButton.setEnabled(false);
- }
+ dikuaiData.remove("plannedPath");
+ showPathGenerationMessage("鏈壘鍒版湁鏁堢殑鍦板潡杈圭晫鍧愭爣锛屾棤娉曠敓鎴愯矾寰勩��", false);
+ setPathAvailability(false);
return;
}
@@ -1060,17 +1094,17 @@
Object widthObj = mowingWidthSpinner.getValue();
if (!(widthObj instanceof Number)) {
JOptionPane.showMessageDialog(this, "鍓茶崏瀹藉害杈撳叆鏃犳晥", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
- if (createButton != null) {
- createButton.setEnabled(false);
- }
+ dikuaiData.remove("plannedPath");
+ showPathGenerationMessage("鍓茶崏瀹藉害杈撳叆鏃犳晥锛岃閲嶆柊杈撳叆銆�", false);
+ setPathAvailability(false);
return;
}
double widthCm = ((Number) widthObj).doubleValue();
if (widthCm <= 0) {
JOptionPane.showMessageDialog(this, "鍓茶崏瀹藉害蹇呴』澶т簬0", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
- if (createButton != null) {
- createButton.setEnabled(false);
- }
+ dikuaiData.remove("plannedPath");
+ showPathGenerationMessage("鍓茶崏瀹藉害蹇呴』澶т簬0锛岃閲嶆柊璁剧疆銆�", false);
+ setPathAvailability(false);
return;
}
dikuaiData.put("mowingWidth", widthObj.toString());
@@ -1088,31 +1122,141 @@
String plannedPath = Lunjingguihua.formatPathSegments(segments);
if (!isMeaningfulValue(plannedPath)) {
JOptionPane.showMessageDialog(this, "鐢熸垚鍓茶崏璺緞澶辫触: 鐢熸垚缁撴灉涓虹┖", "閿欒", JOptionPane.ERROR_MESSAGE);
- if (createButton != null) {
- createButton.setEnabled(false);
- }
+ dikuaiData.remove("plannedPath");
+ showPathGenerationMessage("鐢熸垚鍓茶崏璺緞澶辫触锛氱敓鎴愮粨鏋滀负绌恒��", false);
+ setPathAvailability(false);
return;
}
- dikuaiData.put("plannedPath", plannedPath);
- if (createButton != null) {
- createButton.setEnabled(true);
+ if (isMeaningfulValue(boundaryCoords)) {
+ dikuaiData.put("boundaryCoordinates", boundaryCoords);
}
- JOptionPane.showMessageDialog(this,
- "宸叉牴鎹綋鍓嶈缃敓鎴愬壊鑽夎矾寰勶紝鍏辩敓鎴� " + segments.size() + " 娈点��",
- "鎴愬姛",
- JOptionPane.INFORMATION_MESSAGE);
+ if (isMeaningfulValue(obstacleCoords)) {
+ dikuaiData.put("obstacleCoordinates", obstacleCoords);
+ }
+ dikuaiData.put("plannedPath", plannedPath);
+ setPathAvailability(true);
+ showPathGenerationMessage(
+ "宸叉牴鎹綋鍓嶈缃敓鎴愬壊鑽夎矾寰勶紝鍏辩敓鎴� " + segments.size() + " 娈点�俓n鐐瑰嚮鈥滈瑙堚�濇寜閽彲鍦ㄤ富椤甸潰鏌ョ湅鏁堟灉銆�",
+ true);
} catch (IllegalArgumentException ex) {
JOptionPane.showMessageDialog(this, "鐢熸垚鍓茶崏璺緞澶辫触: " + ex.getMessage(), "閿欒", JOptionPane.ERROR_MESSAGE);
- if (createButton != null) {
- createButton.setEnabled(false);
- }
+ dikuaiData.remove("plannedPath");
+ showPathGenerationMessage("鐢熸垚鍓茶崏璺緞澶辫触锛�" + ex.getMessage(), false);
+ setPathAvailability(false);
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(this, "鐢熸垚鍓茶崏璺緞鏃跺彂鐢熷紓甯�: " + ex.getMessage(), "閿欒", JOptionPane.ERROR_MESSAGE);
- if (createButton != null) {
- createButton.setEnabled(false);
+ dikuaiData.remove("plannedPath");
+ showPathGenerationMessage("鐢熸垚鍓茶崏璺緞鏃跺彂鐢熷紓甯革細" + ex.getMessage(), false);
+ setPathAvailability(false);
+ }
+ }
+
+ private void previewMowingPath() {
+ if (!hasGeneratedPath()) {
+ showPathGenerationMessage("璇峰厛鐢熸垚鍓茶崏璺緞鍚庡啀棰勮銆�", false);
+ setPathAvailability(false);
+ return;
+ }
+
+ persistStep3Inputs();
+
+ String landNumber = getPendingLandNumber();
+ String trimmedAreaName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
+ String displayAreaName = isMeaningfulValue(trimmedAreaName) ? trimmedAreaName : landNumber;
+
+ String plannedPath = dikuaiData.get("plannedPath");
+ if (!isMeaningfulValue(plannedPath)) {
+ showPathGenerationMessage("璇峰厛鐢熸垚鍓茶崏璺緞鍚庡啀棰勮銆�", false);
+ setPathAvailability(false);
+ return;
+ }
+
+ String boundary = null;
+ Dikuai pending = getOrCreatePendingDikuai();
+ if (pending != null) {
+ boundary = normalizeCoordinateValue(pending.getBoundaryCoordinates());
+ }
+ if (boundary == null) {
+ boundary = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates"));
+ }
+
+ String obstacles = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates"));
+ if (!isMeaningfulValue(obstacles)) {
+ obstacles = resolveObstaclePayloadFromConfig(landNumber);
+ if (isMeaningfulValue(obstacles)) {
+ dikuaiData.put("obstacleCoordinates", obstacles);
}
}
+
+ Shouye shouye = Shouye.getInstance();
+ if (shouye == null) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶鎵撳紑涓婚〉闈紝璇风◢鍚庨噸璇�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ dikuaiData.put("areaName", trimmedAreaName);
+ if (isMeaningfulValue(boundary)) {
+ dikuaiData.put("boundaryCoordinates", boundary);
+ }
+
+ pendingLandNumber = landNumber;
+ captureSessionSnapshot();
+
+ resumeRequested = true;
+ boolean started = shouye.startMowingPathPreview(
+ landNumber,
+ displayAreaName,
+ boundary,
+ obstacles,
+ plannedPath,
+ AddDikuai::resumeFromPreview
+ );
+ if (!started) {
+ resumeRequested = false;
+ JOptionPane.showMessageDialog(this, "鏃犳硶鍚姩棰勮锛岃绋嶅悗鍐嶈瘯", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ closePreviewAndDispose();
+ }
+
+ private void persistStep3Inputs() {
+ String trimmedName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
+ dikuaiData.put("areaName", trimmedName);
+
+ if (mowingPatternCombo != null) {
+ Object selection = mowingPatternCombo.getSelectedItem();
+ if (selection != null) {
+ dikuaiData.put("mowingPattern", selection.toString());
+ }
+ }
+
+ if (mowingWidthSpinner != null) {
+ Object widthValue = mowingWidthSpinner.getValue();
+ if (widthValue instanceof Number) {
+ int widthInt = ((Number) widthValue).intValue();
+ dikuaiData.put("mowingWidth", Integer.toString(widthInt));
+ } else if (widthValue != null) {
+ dikuaiData.put("mowingWidth", widthValue.toString());
+ }
+ }
+ }
+
+ private void captureSessionSnapshot() {
+ if (activeSession == null) {
+ activeSession = new DrawingSession();
+ }
+ String landNumber = getPendingLandNumber();
+ activeSession.landNumber = landNumber;
+ activeSession.areaName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
+ activeSession.drawingCompleted = true;
+ activeSession.data = new HashMap<>(dikuaiData);
+ }
+
+ private void closePreviewAndDispose() {
+ setVisible(false);
+ dispose();
}
private JButton createPrimaryButton(String text, int fontSize) {
@@ -1146,6 +1290,44 @@
return button;
}
+
+ private void showPathGenerationMessage(String message, boolean success) {
+ if (pathGenerationMessageArea == null || pathMessageWrapper == null) {
+ return;
+ }
+ String display = message == null ? "" : message.trim();
+ if (display.isEmpty()) {
+ dikuaiData.remove(KEY_PATH_MESSAGE_TEXT);
+ dikuaiData.remove(KEY_PATH_MESSAGE_SUCCESS);
+ } else {
+ dikuaiData.put(KEY_PATH_MESSAGE_TEXT, display);
+ dikuaiData.put(KEY_PATH_MESSAGE_SUCCESS, success ? "true" : "false");
+ }
+ pathGenerationMessageArea.setText(display);
+ Color borderColor = success ? PRIMARY_COLOR : ERROR_COLOR;
+ Color textColor = success ? PRIMARY_DARK : ERROR_COLOR;
+ Color backgroundColor = success ? PRIMARY_LIGHT : new Color(255, 235, 238);
+ pathGenerationMessageArea.setForeground(textColor);
+ pathMessageWrapper.setBackground(backgroundColor);
+ pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(borderColor, 1),
+ BorderFactory.createEmptyBorder(12, 12, 12, 12)
+ ));
+ pathMessageWrapper.setVisible(!display.isEmpty());
+ pathMessageWrapper.revalidate();
+ pathMessageWrapper.repaint();
+ }
+
+ private void setPathAvailability(boolean available) {
+ boolean effective = available && currentStep == 3;
+ if (createButton != null) {
+ createButton.setEnabled(effective);
+ }
+ if (previewButton != null) {
+ boolean visible = previewButton.isVisible();
+ previewButton.setEnabled(effective && visible);
+ }
+ }
private JPanel createButtonPanel() {
JPanel buttonPanel = new JPanel();
@@ -1167,11 +1349,20 @@
nextButton = createPrimaryButton("涓嬩竴姝�", 16);
createButton = createPrimaryButton("淇濆瓨", 16);
createButton.setVisible(false);
- createButton.setEnabled(false);
+ createButton.setEnabled(false);
+
+ previewButton = createPrimaryButton("棰勮", 16);
+ previewButton.setVisible(false);
+ previewButton.setEnabled(false);
+
+ previewButtonSpacer = Box.createHorizontalStrut(15);
+ previewButtonSpacer.setVisible(false);
buttonPanel.add(prevButton);
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(nextButton);
+ buttonPanel.add(previewButtonSpacer);
+ buttonPanel.add(previewButton);
buttonPanel.add(Box.createHorizontalStrut(15));
buttonPanel.add(createButton);
@@ -1223,14 +1414,40 @@
return true;
}
- private static String buildOriginalBoundaryString() {
- if (Coordinate.coordinates == null || Coordinate.coordinates.isEmpty()) {
+ private static List<Coordinate> sanitizeCoordinateList(List<Coordinate> source) {
+ if (source == null || source.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<Coordinate> snapshot = new ArrayList<>();
+ for (Coordinate coordinate : source) {
+ if (coordinate != null) {
+ snapshot.add(coordinate);
+ }
+ }
+ if (snapshot.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ DecimalFormat latLonFormat = new DecimalFormat("0.000000");
+ LinkedHashMap<String, Coordinate> unique = new LinkedHashMap<>();
+ for (Coordinate coord : snapshot) {
+ double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection());
+ double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection());
+ String key = latLonFormat.format(lat) + "," + latLonFormat.format(lon);
+ unique.putIfAbsent(key, coord);
+ }
+ return new ArrayList<>(unique.values());
+ }
+
+ private static String buildOriginalBoundaryString(List<Coordinate> coordinates) {
+ if (coordinates == null || coordinates.isEmpty()) {
return "-1";
}
StringBuilder sb = new StringBuilder();
DecimalFormat latLonFormat = new DecimalFormat("0.000000");
DecimalFormat elevationFormat = new DecimalFormat("0.00");
- for (Coordinate coord : Coordinate.coordinates) {
+ for (Coordinate coord : coordinates) {
double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection());
double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection());
double elevation = coord.getElevation();
@@ -1334,8 +1551,14 @@
}
private static BoundarySnapshotResult computeBoundarySnapshot() {
- int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0;
- if (count < 3) {
+ List<Coordinate> uniqueCoordinates;
+ synchronized (Coordinate.coordinates) {
+ uniqueCoordinates = sanitizeCoordinateList(Coordinate.coordinates);
+ Coordinate.coordinates.clear();
+ Coordinate.coordinates.addAll(uniqueCoordinates);
+ }
+
+ if (uniqueCoordinates.size() < 3) {
return BoundarySnapshotResult.failure("閲囬泦鐨勮竟鐣岀偣涓嶈冻锛屾棤娉曠敓鎴愬湴鍧楄竟鐣�", JOptionPane.WARNING_MESSAGE);
}
@@ -1344,9 +1567,9 @@
return BoundarySnapshotResult.failure("褰撳墠鍦板潡闈㈢Н涓�0锛屾棤娉曠户缁�", JOptionPane.WARNING_MESSAGE);
}
- Device device = new Device();
- device.initFromProperties();
- String baseStationCoordinates = normalizeCoordinateValue(device.getBaseStationCoordinates());
+ BaseStation baseStation = new BaseStation();
+ baseStation.load();
+ String baseStationCoordinates = normalizeCoordinateValue(baseStation.getInstallationCoordinates());
if (!isMeaningfulValue(baseStationCoordinates)) {
return BoundarySnapshotResult.failure("鏈幏鍙栧埌鏈夋晥鐨勫熀鍑嗙珯鍧愭爣锛岃鍏堝湪鍩哄噯绔欑鐞嗕腑璁剧疆", JOptionPane.WARNING_MESSAGE);
}
@@ -1359,7 +1582,7 @@
return BoundarySnapshotResult.failure("鐢熸垚鍦板潡杈圭晫澶辫触: " + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
}
- String originalBoundary = buildOriginalBoundaryString();
+ String originalBoundary = buildOriginalBoundaryString(uniqueCoordinates);
DecimalFormat areaFormat = new DecimalFormat("0.00");
String areaString = areaFormat.format(area);
@@ -1407,6 +1630,10 @@
// 鍒涘缓鍦板潡鎸夐挳
createButton.addActionListener(e -> createDikuai());
+
+ if (previewButton != null) {
+ previewButton.addActionListener(e -> previewMowingPath());
+ }
// 鍏抽棴瀵硅瘽妗�
addWindowListener(new WindowAdapter() {
@@ -1435,11 +1662,24 @@
if (step < 3) {
nextButton.setVisible(true);
createButton.setVisible(false);
- createButton.setEnabled(false);
+ setPathAvailability(false);
+ if (previewButton != null) {
+ previewButton.setVisible(false);
+ previewButton.setEnabled(false);
+ }
+ if (previewButtonSpacer != null) {
+ previewButtonSpacer.setVisible(false);
+ }
} else {
nextButton.setVisible(false);
createButton.setVisible(true);
- createButton.setEnabled(hasGeneratedPath());
+ if (previewButton != null) {
+ previewButton.setVisible(true);
+ }
+ if (previewButtonSpacer != null) {
+ previewButtonSpacer.setVisible(true);
+ }
+ setPathAvailability(hasGeneratedPath());
}
Container parent = prevButton.getParent();
@@ -1736,6 +1976,71 @@
showStep(1);
hideBoundaryPointSummary();
}
+
+ restoreGeneratedPathState(session);
+ }
+
+ private void restoreGeneratedPathState(DrawingSession session) {
+ if (session == null || session.data == null) {
+ showPathGenerationMessage("", true);
+ return;
+ }
+
+ Map<String, String> data = session.data;
+
+ if (mowingPatternCombo != null) {
+ String pattern = data.get("mowingPattern");
+ if (pattern != null) {
+ ComboBoxModel<String> model = mowingPatternCombo.getModel();
+ for (int i = 0; i < model.getSize(); i++) {
+ String candidate = model.getElementAt(i);
+ if (pattern.equals(candidate)) {
+ mowingPatternCombo.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+ }
+
+ if (mowingWidthSpinner != null) {
+ String width = data.get("mowingWidth");
+ if (isMeaningfulValue(width)) {
+ try {
+ double parsed = Double.parseDouble(width.trim());
+ SpinnerNumberModel model = (SpinnerNumberModel) mowingWidthSpinner.getModel();
+ int min = ((Number) model.getMinimum()).intValue();
+ int max = ((Number) model.getMaximum()).intValue();
+ int rounded = (int) Math.round(parsed);
+ if (rounded < min) {
+ rounded = min;
+ } else if (rounded > max) {
+ rounded = max;
+ }
+ mowingWidthSpinner.setValue(rounded);
+ } catch (NumberFormatException ignored) {
+ // 淇濇寔褰撳墠鍊�
+ }
+ }
+ }
+
+ boolean hasPath = isMeaningfulValue(data.get("plannedPath"));
+ if (!hasPath) {
+ showPathGenerationMessage("", true);
+ if (currentStep == 3) {
+ setPathAvailability(false);
+ }
+ return;
+ }
+
+ String message = data.get(KEY_PATH_MESSAGE_TEXT);
+ boolean success = !"false".equalsIgnoreCase(data.get(KEY_PATH_MESSAGE_SUCCESS));
+ showStep(3);
+ if (isMeaningfulValue(message)) {
+ showPathGenerationMessage(message, success);
+ } else {
+ showPathGenerationMessage("宸茬敓鎴愬壊鑽夎矾寰勶紝鍙偣鍑烩�滈瑙堚�濇寜閽煡鐪嬫晥鏋溿��", true);
+ }
+ setPathAvailability(true);
}
public static void finishDrawingSession() {
@@ -1769,6 +2074,19 @@
Component parent = shouye != null ? shouye : null;
showAddDikuaiDialog(parent);
}
+
+ public static void resumeFromPreview() {
+ Shouye shouye = Shouye.getInstance();
+ if (shouye != null) {
+ shouye.exitMowingPathPreview();
+ }
+ if (activeSession == null) {
+ return;
+ }
+ resumeRequested = true;
+ Component parent = shouye != null ? shouye : null;
+ SwingUtilities.invokeLater(() -> showAddDikuaiDialog(parent));
+ }
private void createDikuai() {
if (!validateCurrentStep()) {
diff --git a/src/zhuye/HandheldBoundaryCaptureDialog.java b/src/zhuye/HandheldBoundaryCaptureDialog.java
deleted file mode 100644
index bb56d7b..0000000
--- a/src/zhuye/HandheldBoundaryCaptureDialog.java
+++ /dev/null
@@ -1,229 +0,0 @@
-package zhuye;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.IllegalComponentStateException;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-
-import gecaoji.Device;
-
-/**
- * 鎵嬫寔缁樺埗杈圭晫鐨勯�愮偣閲囬泦瀵硅瘽妗嗐��
- */
-class HandheldBoundaryCaptureDialog extends JDialog {
- private final Shouye owner;
- private final JLabel instructionLabel;
- private final JButton confirmButton;
- private final JButton finishButton;
- private final JPanel mapPanel;
- private final Timer statusMonitor;
- private final Color enabledColor;
- private int nextPointIndex = 1;
-
- HandheldBoundaryCaptureDialog(Window parent, Shouye owner, JPanel mapPanel, Color enabledColor) {
- super(parent, "鎵嬫寔閲囬泦杈圭晫", ModalityType.APPLICATION_MODAL);
- this.owner = owner;
- this.mapPanel = mapPanel;
- this.enabledColor = enabledColor != null ? enabledColor : new Color(46, 139, 87);
- setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
- setResizable(false);
- setAlwaysOnTop(true);
-
- instructionLabel = new JLabel("", SwingConstants.CENTER);
- instructionLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, Math.max(11, (int) Math.round(16 * 0.7))));
- instructionLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
- updateInstructionText();
-
- confirmButton = new JButton("纭畾");
- confirmButton.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
- confirmButton.setFocusPainted(false);
- confirmButton.setOpaque(true);
- confirmButton.setBorder(BorderFactory.createEmptyBorder(8, 18, 8, 18));
- confirmButton.addActionListener(e -> handleConfirm());
-
- finishButton = new JButton("缁撴潫");
- finishButton.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
- finishButton.setFocusPainted(false);
- finishButton.setOpaque(true);
- finishButton.setBackground(new Color(255, 153, 0));
- finishButton.setForeground(Color.WHITE);
- finishButton.setEnabled(false);
- finishButton.setVisible(false);
- finishButton.setBorder(BorderFactory.createEmptyBorder(8, 18, 8, 18));
- finishButton.addActionListener(e -> handleFinish());
-
- buildLayout();
- alignButtonSizes();
- getRootPane().setDefaultButton(confirmButton);
- pack();
- Dimension initialSize = getSize();
- if (initialSize != null && initialSize.width > 0) {
- setSize(new Dimension(initialSize.width * 2, initialSize.height));
- }
- adjustPreferredSize();
- positionNearMap();
- updateActionButtons();
- SwingUtilities.invokeLater(confirmButton::requestFocusInWindow);
-
- statusMonitor = new Timer(400, e -> refreshStatus());
- statusMonitor.setRepeats(true);
- statusMonitor.start();
- refreshStatus();
-
- addWindowListener(new WindowAdapter() {
- @Override
- public void windowClosing(WindowEvent e) {
- dispose();
- }
- });
- }
-
- private void buildLayout() {
- JPanel content = new JPanel(new BorderLayout());
- content.setBorder(BorderFactory.createEmptyBorder(20, 24, 20, 24));
- content.add(instructionLabel, BorderLayout.CENTER);
-
- JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 16, 0));
- buttonPanel.add(confirmButton);
- buttonPanel.add(finishButton);
- content.add(buttonPanel, BorderLayout.SOUTH);
-
- setContentPane(content);
- }
-
- private void adjustPreferredSize() {
- Dimension size = getSize();
- if (size.height > 0) {
- int adjustedHeight = Math.max((int) (size.height * 0.7), 160);
- setSize(new Dimension(size.width, adjustedHeight));
- }
- }
-
- private void positionNearMap() {
- final int margin = 16;
- if (owner != null && owner.isShowing()) {
- try {
- Point ownerLocation = owner.getLocationOnScreen();
- Dimension ownerSize = owner.getSize();
- Dimension dialogSize = getSize();
- int x = ownerLocation.x + (ownerSize.width - dialogSize.width) / 2;
- int y = ownerLocation.y + ownerSize.height - dialogSize.height - margin;
- if (x < ownerLocation.x + margin) {
- x = ownerLocation.x + margin;
- }
- if (y < ownerLocation.y + margin) {
- y = ownerLocation.y + margin;
- }
- setLocation(x, y);
- return;
- } catch (IllegalComponentStateException ignored) {
- // fallback handled below
- }
- } else if (mapPanel != null && mapPanel.isShowing()) {
- try {
- Point mapLocation = mapPanel.getLocationOnScreen();
- Dimension mapSize = mapPanel.getSize();
- Dimension dialogSize = getSize();
- int x = mapLocation.x + (mapSize.width - dialogSize.width) / 2;
- int y = mapLocation.y + mapSize.height - dialogSize.height - margin;
- setLocation(x, y);
- return;
- } catch (IllegalComponentStateException ignored) {
- // fallback handled below
- }
- }
- setLocationRelativeTo(owner);
- }
-
- private void handleConfirm() {
- if (!confirmButton.isEnabled()) {
- return;
- }
- int count = owner.captureHandheldBoundaryPoint();
- if (count <= 0) {
- return;
- }
- nextPointIndex = count + 1;
- updateInstructionText();
- updateActionButtons();
- }
-
- private void handleFinish() {
- if (owner.finishHandheldBoundaryCapture()) {
- dispose();
- }
- }
-
- @Override
- public void dispose() {
- if (statusMonitor != null) {
- statusMonitor.stop();
- }
- super.dispose();
- owner.handheldBoundaryCaptureDialogClosed(this);
- }
-
- private void refreshStatus() {
- boolean hasFix = isFixQualityAvailable();
- boolean hasValidPosition = hasFix && hasValidCurrentPosition();
- boolean duplicate = hasValidPosition && isDuplicateCurrentPosition();
- boolean enabled = hasValidPosition && !duplicate;
- confirmButton.setEnabled(enabled);
- if (enabled) {
- confirmButton.setBackground(enabledColor);
- confirmButton.setForeground(Color.WHITE);
- } else {
- confirmButton.setBackground(new Color(200, 200, 200));
- confirmButton.setForeground(new Color(130, 130, 130));
- }
- if (!hasFix) {
- confirmButton.setToolTipText("褰撳墠瀹氫綅璐ㄩ噺涓嶈冻锛屾棤娉曢噰闆�");
- } else if (!hasValidPosition) {
- confirmButton.setToolTipText("褰撳墠瀹氫綅鏁版嵁鏃犳晥锛岃绋嶅悗鍐嶈瘯");
- } else if (duplicate) {
- confirmButton.setToolTipText("褰撳墠鍧愭爣宸查噰闆嗭紝璇风Щ鍔ㄥ埌鏂扮殑浣嶇疆");
- } else {
- confirmButton.setToolTipText(null);
- }
- }
-
- private boolean isDuplicateCurrentPosition() {
- return owner != null && owner.isCurrentHandheldPointDuplicate();
- }
-
- private boolean hasValidCurrentPosition() {
- return owner != null && owner.hasValidRealtimeHandheldPosition();
- }
-
- private void updateInstructionText() {
- instructionLabel.setText("鏄惁閲囬泦褰撳墠鐐�" + nextPointIndex);
- }
-
- private void alignButtonSizes() {
- Dimension confirmSize = confirmButton.getPreferredSize();
- finishButton.setPreferredSize(confirmSize);
- finishButton.setMinimumSize(confirmSize);
- finishButton.setMaximumSize(confirmSize);
- }
-
- private void updateActionButtons() {
- int totalPoints = owner != null ? owner.getHandheldCapturedPointCount() : 0;
- boolean hasMinimumPoints = totalPoints >= 3;
- finishButton.setVisible(hasMinimumPoints);
- finishButton.setEnabled(hasMinimumPoints);
- confirmButton.setVisible(true);
- }
-
- private boolean isFixQualityAvailable() {
- Device device = Device.getGecaoji();
- if (device == null) {
- return false;
- }
- String status = device.getPositioningStatus();
- if (status == null) {
- return false;
- }
- return "4".equals(status.trim());
- }
-}
diff --git a/src/zhuye/MapRenderer.java b/src/zhuye/MapRenderer.java
index 5f5dd06..4562a15 100644
--- a/src/zhuye/MapRenderer.java
+++ b/src/zhuye/MapRenderer.java
@@ -50,6 +50,7 @@
private static final double CIRCLE_SAMPLE_SIZE = 0.54d;
private static final double BOUNDARY_POINT_MERGE_THRESHOLD = 0.05;
private static final double BOUNDARY_CONTAINS_TOLERANCE = 0.05;
+ private static final double PREVIEW_BOUNDARY_MARKER_SCALE = 0.25d;
// 缁勪欢寮曠敤
private JPanel visualizationPanel;
@@ -64,6 +65,8 @@
private String currentObstacleLandNumber;
private String boundaryName;
private boolean boundaryPointsVisible;
+ private double boundaryPointSizeScale = 1.0d;
+ private boolean previewSizingEnabled;
private String currentBoundaryLandNumber;
private boolean dragInProgress;
private final Gecaoji mower;
@@ -74,6 +77,7 @@
private final List<Point2D.Double> realtimeMowingTrack = new ArrayList<>();
private final Deque<tuowei.TrailSample> idleMowerTrail = new ArrayDeque<>();
private final List<Point2D.Double> handheldBoundaryPreview = new ArrayList<>();
+ private double boundaryPreviewMarkerScale = 1.0d;
private boolean realtimeTrackRecording;
private String realtimeTrackLandNumber;
private double mowerEffectiveWidthMeters;
@@ -264,19 +268,21 @@
drawCircleCaptureOverlay(g2d, circleCaptureOverlay, scale);
}
- adddikuaiyulan.drawPreview(g2d, handheldBoundaryPreview, scale, handheldBoundaryPreviewActive);
+ adddikuaiyulan.drawPreview(g2d, handheldBoundaryPreview, scale, handheldBoundaryPreviewActive, boundaryPreviewMarkerScale);
if (hasPlannedPath) {
drawCurrentPlannedPath(g2d);
}
if (boundaryPointsVisible && hasBoundary) {
+ double markerScale = boundaryPointSizeScale * (previewSizingEnabled ? PREVIEW_BOUNDARY_MARKER_SCALE : 1.0d);
pointandnumber.drawBoundaryPoints(
g2d,
currentBoundary,
scale,
BOUNDARY_POINT_MERGE_THRESHOLD,
- BOUNDARY_POINT_COLOR
+ BOUNDARY_POINT_COLOR,
+ markerScale
);
}
@@ -648,6 +654,10 @@
visualizationPanel.repaint();
}
+ public void clearIdleTrail() {
+ clearIdleMowerTrail();
+ }
+
public void setIdleTrailDurationSeconds(int seconds) {
int sanitized = seconds;
if (sanitized < 5 || sanitized > 600) {
@@ -865,7 +875,8 @@
}
private void drawCurrentPlannedPath(Graphics2D g2d) {
- lujingdraw.drawPlannedPath(g2d, currentPlannedPath, scale);
+ double arrowScale = previewSizingEnabled ? 0.5d : 1.0d;
+ lujingdraw.drawPlannedPath(g2d, currentPlannedPath, scale, arrowScale);
}
private void drawCircleSampleMarkers(Graphics2D g2d, List<double[]> markers, double scale) {
@@ -1054,7 +1065,11 @@
private double computeSelectionThresholdPixels() {
double scaleFactor = Math.max(0.5, scale);
- double markerDiameterWorld = Math.max(1.0, (10.0 / scaleFactor) * 0.2);
+ double diameterScale = boundaryPointSizeScale * (previewSizingEnabled ? PREVIEW_BOUNDARY_MARKER_SCALE : 1.0d);
+ if (!Double.isFinite(diameterScale) || diameterScale <= 0.0d) {
+ diameterScale = 1.0d;
+ }
+ double markerDiameterWorld = Math.max(1.0, (10.0 / scaleFactor) * 0.2 * diameterScale);
double markerDiameterPixels = markerDiameterWorld * scale;
return Math.max(8.0, markerDiameterPixels * 1.5);
}
@@ -1830,6 +1845,65 @@
visualizationPanel.repaint();
}
+ public void setBoundaryPointSizeScale(double sizeScale) {
+ double normalized = (Double.isFinite(sizeScale) && sizeScale > 0.0d) ? sizeScale : 1.0d;
+ if (Math.abs(boundaryPointSizeScale - normalized) < 1e-6) {
+ return;
+ }
+ boundaryPointSizeScale = normalized;
+ if (visualizationPanel == null) {
+ return;
+ }
+ if (SwingUtilities.isEventDispatchThread()) {
+ visualizationPanel.repaint();
+ } else {
+ SwingUtilities.invokeLater(visualizationPanel::repaint);
+ }
+ }
+
+ public void setPathPreviewSizingEnabled(boolean enabled) {
+ previewSizingEnabled = enabled;
+ if (visualizationPanel == null) {
+ return;
+ }
+ if (SwingUtilities.isEventDispatchThread()) {
+ visualizationPanel.repaint();
+ } else {
+ SwingUtilities.invokeLater(visualizationPanel::repaint);
+ }
+ }
+
+ public void setBoundaryPreviewMarkerScale(double markerScale) {
+ double normalized = Double.isFinite(markerScale) && markerScale > 0.0d ? markerScale : 1.0d;
+ if (Math.abs(boundaryPreviewMarkerScale - normalized) < 1e-6) {
+ return;
+ }
+ boundaryPreviewMarkerScale = normalized;
+ if (visualizationPanel == null) {
+ return;
+ }
+ if (SwingUtilities.isEventDispatchThread()) {
+ visualizationPanel.repaint();
+ } else {
+ SwingUtilities.invokeLater(visualizationPanel::repaint);
+ }
+ }
+
+ public boolean setHandheldMowerIconActive(boolean handheldActive) {
+ if (mower == null) {
+ return false;
+ }
+ boolean changed = mower.useHandheldIcon(handheldActive);
+ if (changed && visualizationPanel != null) {
+ if (SwingUtilities.isEventDispatchThread()) {
+ visualizationPanel.repaint();
+ } else {
+ SwingUtilities.invokeLater(visualizationPanel::repaint);
+ }
+ }
+ return changed;
+ }
+
public void beginHandheldBoundaryPreview() {
handheldBoundaryPreviewActive = true;
handheldBoundaryPreview.clear();
@@ -1859,6 +1933,7 @@
public void clearHandheldBoundaryPreview() {
handheldBoundaryPreviewActive = false;
handheldBoundaryPreview.clear();
+ boundaryPreviewMarkerScale = 1.0d;
visualizationPanel.repaint();
}
@@ -1914,8 +1989,10 @@
return;
}
- double width = Math.max(bounds.width, 1);
- double height = Math.max(bounds.height, 1);
+ Rectangle2D.Double targetBounds = includeMowerInBounds(bounds);
+
+ double width = Math.max(targetBounds.width, 1);
+ double height = Math.max(targetBounds.height, 1);
double targetWidth = width * 1.2;
double targetHeight = height * 1.2;
@@ -1927,8 +2004,41 @@
newScale = Math.max(0.05, Math.min(newScale, 50.0));
this.scale = newScale;
- this.translateX = -bounds.getCenterX();
- this.translateY = -bounds.getCenterY();
+ this.translateX = -targetBounds.getCenterX();
+ this.translateY = -targetBounds.getCenterY();
+ }
+
+ // Keep the mower marker inside the viewport whenever the camera refits to scene bounds.
+ private Rectangle2D.Double includeMowerInBounds(Rectangle2D.Double bounds) {
+ Rectangle2D.Double expanded = new Rectangle2D.Double(
+ bounds.x,
+ bounds.y,
+ Math.max(0.0, bounds.width),
+ Math.max(0.0, bounds.height)
+ );
+
+ if (mower == null || !mower.hasValidPosition()) {
+ return expanded;
+ }
+
+ Point2D.Double mowerPosition = mower.getPosition();
+ if (mowerPosition == null
+ || !Double.isFinite(mowerPosition.x)
+ || !Double.isFinite(mowerPosition.y)) {
+ return expanded;
+ }
+
+ double minX = Math.min(expanded.x, mowerPosition.x);
+ double minY = Math.min(expanded.y, mowerPosition.y);
+ double maxX = Math.max(expanded.x + expanded.width, mowerPosition.x);
+ double maxY = Math.max(expanded.y + expanded.height, mowerPosition.y);
+
+ expanded.x = minX;
+ expanded.y = minY;
+ expanded.width = Math.max(0.0, maxX - minX);
+ expanded.height = Math.max(0.0, maxY - minY);
+
+ return expanded;
}
public void dispose() {
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index 10f9ca0..1dfd462 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -92,6 +92,11 @@
private JPanel floatingButtonPanel;
private JPanel floatingButtonColumn;
private Runnable endDrawingCallback;
+ private JButton pathPreviewReturnButton;
+ private boolean pathPreviewActive;
+ private Runnable pathPreviewReturnAction;
+ private String previewRestoreLandNumber;
+ private String previewRestoreLandName;
private boolean drawingPaused;
private ImageIcon pauseIcon;
private ImageIcon pauseActiveIcon;
@@ -112,7 +117,6 @@
private double[] circleBaseLatLon;
private Timer circleDataMonitor;
private Coordinate lastCapturedCoordinate;
- private HandheldBoundaryCaptureDialog handheldCaptureDialog;
private boolean handheldCaptureActive;
private int handheldCapturedPoints;
private final List<Point2D.Double> handheldTemporaryPoints = new ArrayList<>();
@@ -127,14 +131,26 @@
private boolean stopButtonActive = false;
private boolean bluetoothConnected = false;
private Timer mowerSpeedRefreshTimer;
+ private boolean drawingControlModeActive;
+ private boolean storedStartButtonShowingPause;
+ private boolean storedStopButtonActive;
+ private String storedStatusBeforeDrawing;
+ private boolean handheldCaptureInlineUiActive;
+ private Timer handheldCaptureStatusTimer;
+ private String handheldCaptureStoredStatusText;
+ private Color handheldStartButtonOriginalBackground;
+ private Color handheldStartButtonOriginalForeground;
+ private Color handheldStopButtonOriginalBackground;
+ private Color handheldStopButtonOriginalForeground;
public Shouye() {
instance = this;
baseStation = new BaseStation();
baseStation.load();
- dellmessage.registerLineListener(serialLineListener);
+ dellmessage.registerLineListener(serialLineListener);
initializeUI();
setupEventHandlers();
+ scheduleIdentifierCheck();
}
public static Shouye getInstance() {
@@ -174,6 +190,26 @@
refreshMapForSelectedArea();
}
+ private void scheduleIdentifierCheck() {
+ HierarchyListener listener = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged(HierarchyEvent e) {
+ if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && Shouye.this.isShowing()) {
+ Shouye.this.removeHierarchyListener(this);
+ SwingUtilities.invokeLater(() -> {
+ Shouye.this.checkIdentifiersAndPromptIfNeeded();
+ Shouye.this.showInitialMowerSelfCheckDialogIfNeeded();
+ });
+ }
+ }
+ };
+ addHierarchyListener(listener);
+ }
+
+ private void showInitialMowerSelfCheckDialogIfNeeded() {
+ zijian.showInitialPromptIfNeeded(this, this::showRemoteControlDialog);
+ }
+
private void applyIdleTrailDurationFromSettings() {
if (mapRenderer == null) {
return;
@@ -584,8 +620,9 @@
}
if (remoteDialog != null) {
positionRemoteDialogBottomCenter(remoteDialog);
+ zijian.markSelfCheckCompleted();
+ remoteDialog.setVisible(true);
}
- remoteDialog.setVisible(true);
}
private void positionRemoteDialogBottomCenter(RemoteControlDialog dialog) {
@@ -663,6 +700,142 @@
baseStationDialog.setVisible(true);
}
+ private void checkIdentifiersAndPromptIfNeeded() {
+ if (baseStation == null) {
+ baseStation = new BaseStation();
+ }
+ baseStation.load();
+
+ String currentMowerId = Setsys.getPropertyValue("mowerId");
+ String currentBaseStationId = baseStation.getDeviceId();
+
+ if (!isIdentifierMissing(currentMowerId) && !isIdentifierMissing(currentBaseStationId)) {
+ return;
+ }
+
+ Window owner = SwingUtilities.getWindowAncestor(this);
+ promptForMissingIdentifiers(owner, currentMowerId, currentBaseStationId);
+ }
+
+ private void promptForMissingIdentifiers(Window owner, String currentMowerId, String currentBaseStationId) {
+ while (true) {
+ JTextField mowerField = new JTextField(10);
+ JTextField baseField = new JTextField(10);
+
+ if (!isIdentifierMissing(currentMowerId)) {
+ mowerField.setText(currentMowerId.trim());
+ }
+ if (!isIdentifierMissing(currentBaseStationId)) {
+ baseField.setText(currentBaseStationId.trim());
+ }
+
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
+
+ JLabel mowerLabel = new JLabel("鍓茶崏鏈虹紪鍙�");
+ JLabel baseLabel = new JLabel("宸垎鍩哄噯绔欑紪鍙�");
+
+ mowerField.setMaximumSize(new Dimension(Integer.MAX_VALUE, mowerField.getPreferredSize().height));
+ baseField.setMaximumSize(new Dimension(Integer.MAX_VALUE, baseField.getPreferredSize().height));
+
+ panel.add(mowerLabel);
+ panel.add(Box.createVerticalStrut(4));
+ panel.add(mowerField);
+ panel.add(Box.createVerticalStrut(10));
+ panel.add(baseLabel);
+ panel.add(Box.createVerticalStrut(4));
+ panel.add(baseField);
+
+ Object[] options = {"淇濆瓨", "鍙栨秷"};
+ int result = JOptionPane.showOptionDialog(owner, panel, "瀹屽杽璁惧淇℃伅",
+ JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
+
+ if (result != 0) {
+ break;
+ }
+
+ String mowerInput = mowerField.getText().trim();
+ String baseInput = baseField.getText().trim();
+
+ if (mowerInput.isEmpty()) {
+ JOptionPane.showMessageDialog(owner, "鍓茶崏鏈虹紪鍙蜂笉鑳戒负绌恒��", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ continue;
+ }
+
+ if (baseInput.isEmpty()) {
+ JOptionPane.showMessageDialog(owner, "宸垎鍩哄噯绔欑紪鍙蜂笉鑳戒负绌恒��", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ continue;
+ }
+
+ boolean mowerSaved = persistMowerIdentifier(mowerInput);
+ boolean baseSaved = persistBaseStationIdentifier(baseInput);
+
+ if (mowerSaved && baseSaved) {
+ JOptionPane.showMessageDialog(owner, "缂栧彿宸蹭繚瀛樸��", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ break;
+ }
+
+ StringBuilder errorBuilder = new StringBuilder();
+ if (!mowerSaved) {
+ errorBuilder.append("鍓茶崏鏈虹紪鍙蜂繚瀛樺け璐ャ��");
+ }
+ if (!baseSaved) {
+ if (errorBuilder.length() > 0) {
+ errorBuilder.append('\n');
+ }
+ errorBuilder.append("宸垎鍩哄噯绔欑紪鍙蜂繚瀛樺け璐ャ��");
+ }
+
+ JOptionPane.showMessageDialog(owner, errorBuilder.toString(), "淇濆瓨澶辫触", JOptionPane.ERROR_MESSAGE);
+
+ currentMowerId = Setsys.getPropertyValue("mowerId");
+ baseStation.load();
+ currentBaseStationId = baseStation.getDeviceId();
+ }
+ }
+
+ private boolean isIdentifierMissing(String value) {
+ if (value == null) {
+ return true;
+ }
+ String trimmed = value.trim();
+ return trimmed.isEmpty() || "-1".equals(trimmed);
+ }
+
+ private boolean persistMowerIdentifier(String mowerId) {
+ try {
+ Setsys setsys = new Setsys();
+ setsys.initializeFromProperties();
+ boolean updated = setsys.updateProperty("mowerId", mowerId);
+ if (updated) {
+ Device.initializeActiveDevice(mowerId);
+ }
+ return updated;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
+ private boolean persistBaseStationIdentifier(String baseStationId) {
+ if (baseStation == null) {
+ baseStation = new BaseStation();
+ }
+ try {
+ baseStation.updateByDeviceId(baseStationId,
+ baseStation.getInstallationCoordinates(),
+ baseStation.getIotSimCardNumber(),
+ baseStation.getDeviceActivationTime(),
+ baseStation.getDataUpdateTime());
+ baseStation.load();
+ return true;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
private boolean hasValidBaseStationId() {
if (baseStation == null) {
return false;
@@ -713,9 +886,22 @@
}
private void toggleStartPause() {
+ if (handheldCaptureInlineUiActive) {
+ handleHandheldConfirmAction();
+ return;
+ }
+ if (drawingControlModeActive) {
+ toggleDrawingPause();
+ return;
+ }
if (startBtn == null) {
return;
}
+ if (startButtonShowingPause) {
+ if (!zijian.ensureBeforeMowing(this, this::showRemoteControlDialog)) {
+ return;
+ }
+ }
startButtonShowingPause = !startButtonShowingPause;
if (!startButtonShowingPause) {
statusLabel.setText("浣滀笟涓�");
@@ -737,6 +923,14 @@
}
private void handleStopAction() {
+ if (handheldCaptureInlineUiActive) {
+ handleHandheldFinishAction();
+ return;
+ }
+ if (drawingControlModeActive) {
+ handleDrawingStopFromControlPanel();
+ return;
+ }
stopButtonActive = !stopButtonActive;
updateStopButtonIcon();
if (stopButtonActive) {
@@ -751,6 +945,268 @@
updateStartButtonAppearance();
}
+ private void handleDrawingStopFromControlPanel() {
+ if (endDrawingCallback != null) {
+ endDrawingCallback.run();
+ } else {
+ addzhangaiwu.finishDrawingSession();
+ }
+ }
+
+ private void handleHandheldConfirmAction() {
+ if (!handheldCaptureInlineUiActive) {
+ return;
+ }
+ if (!canConfirmHandheldPoint()) {
+ refreshHandheldCaptureUiState();
+ return;
+ }
+ int count = captureHandheldBoundaryPoint();
+ if (count <= 0) {
+ refreshHandheldCaptureUiState();
+ return;
+ }
+ refreshHandheldCaptureUiState();
+ }
+
+ private void handleHandheldFinishAction() {
+ if (!handheldCaptureInlineUiActive) {
+ return;
+ }
+ if (stopBtn != null && !stopBtn.isEnabled()) {
+ refreshHandheldCaptureUiState();
+ return;
+ }
+ if (!finishHandheldBoundaryCapture()) {
+ refreshHandheldCaptureUiState();
+ }
+ }
+
+ private void enterHandheldCaptureInlineUi() {
+ if (handheldCaptureInlineUiActive) {
+ refreshHandheldCaptureUiState();
+ return;
+ }
+ handheldCaptureInlineUiActive = true;
+ handheldCaptureStoredStatusText = statusLabel != null ? statusLabel.getText() : null;
+ if (statusLabel != null) {
+ statusLabel.setText("鎵嬫寔閲囬泦涓�");
+ }
+ if (startBtn != null) {
+ handheldStartButtonOriginalBackground = startBtn.getBackground();
+ handheldStartButtonOriginalForeground = startBtn.getForeground();
+ startBtn.setIcon(null);
+ startBtn.setIconTextGap(0);
+ startBtn.setHorizontalAlignment(SwingConstants.CENTER);
+ startBtn.setHorizontalTextPosition(SwingConstants.CENTER);
+ startBtn.setVerticalTextPosition(SwingConstants.CENTER);
+ }
+ if (stopBtn != null) {
+ handheldStopButtonOriginalBackground = stopBtn.getBackground();
+ handheldStopButtonOriginalForeground = stopBtn.getForeground();
+ stopBtn.setIcon(null);
+ stopBtn.setIconTextGap(0);
+ stopBtn.setHorizontalAlignment(SwingConstants.CENTER);
+ stopBtn.setHorizontalTextPosition(SwingConstants.CENTER);
+ stopBtn.setVerticalTextPosition(SwingConstants.CENTER);
+ stopBtn.setText("缁撴潫");
+ }
+ startHandheldCaptureStatusTimer();
+ refreshHandheldCaptureUiState();
+ }
+
+ private void exitHandheldCaptureInlineUi() {
+ if (!handheldCaptureInlineUiActive) {
+ return;
+ }
+ handheldCaptureInlineUiActive = false;
+ stopHandheldCaptureStatusTimer();
+ if (statusLabel != null) {
+ statusLabel.setText(handheldCaptureStoredStatusText != null ? handheldCaptureStoredStatusText : "寰呮満");
+ }
+ if (startBtn != null) {
+ startBtn.setToolTipText(null);
+ if (handheldStartButtonOriginalBackground != null) {
+ startBtn.setBackground(handheldStartButtonOriginalBackground);
+ }
+ if (handheldStartButtonOriginalForeground != null) {
+ startBtn.setForeground(handheldStartButtonOriginalForeground);
+ }
+ startBtn.setEnabled(true);
+ updateStartButtonAppearance();
+ }
+ if (stopBtn != null) {
+ stopBtn.setToolTipText(null);
+ if (handheldStopButtonOriginalBackground != null) {
+ stopBtn.setBackground(handheldStopButtonOriginalBackground);
+ }
+ if (handheldStopButtonOriginalForeground != null) {
+ stopBtn.setForeground(handheldStopButtonOriginalForeground);
+ }
+ stopBtn.setEnabled(true);
+ stopBtn.setText("缁撴潫");
+ updateStopButtonIcon();
+ }
+ handheldCaptureStoredStatusText = null;
+ handheldStartButtonOriginalBackground = null;
+ handheldStartButtonOriginalForeground = null;
+ handheldStopButtonOriginalBackground = null;
+ handheldStopButtonOriginalForeground = null;
+ }
+
+ private void startHandheldCaptureStatusTimer() {
+ if (handheldCaptureStatusTimer == null) {
+ handheldCaptureStatusTimer = new Timer(400, e -> refreshHandheldCaptureUiState());
+ handheldCaptureStatusTimer.setRepeats(true);
+ }
+ if (!handheldCaptureStatusTimer.isRunning()) {
+ handheldCaptureStatusTimer.start();
+ }
+ }
+
+ private void stopHandheldCaptureStatusTimer() {
+ if (handheldCaptureStatusTimer != null && handheldCaptureStatusTimer.isRunning()) {
+ handheldCaptureStatusTimer.stop();
+ }
+ }
+
+ // Update inline handheld capture buttons based on the current device reading.
+ private void refreshHandheldCaptureUiState() {
+ if (!handheldCaptureInlineUiActive) {
+ return;
+ }
+ int nextIndex = handheldCapturedPoints + 1;
+ boolean hasFix = hasHighPrecisionFix();
+ boolean hasValid = hasValidRealtimeHandheldPosition();
+ boolean duplicate = hasValid && isCurrentHandheldPointDuplicate();
+ boolean canConfirm = handheldCaptureActive && hasFix && hasValid && !duplicate;
+
+ if (startBtn != null) {
+ String prompt = "<html><center>閲囬泦鐐�" + nextIndex + "<br>纭畾</center></html>";
+ startBtn.setText(prompt);
+ startBtn.setEnabled(canConfirm);
+ if (canConfirm) {
+ if (handheldStartButtonOriginalBackground != null) {
+ startBtn.setBackground(handheldStartButtonOriginalBackground);
+ }
+ if (handheldStartButtonOriginalForeground != null) {
+ startBtn.setForeground(handheldStartButtonOriginalForeground);
+ }
+ startBtn.setToolTipText(null);
+ } else {
+ startBtn.setBackground(new Color(200, 200, 200));
+ startBtn.setForeground(new Color(130, 130, 130));
+ startBtn.setToolTipText(resolveHandheldConfirmTooltip(hasFix, hasValid, duplicate));
+ }
+ }
+
+ if (stopBtn != null) {
+ boolean canFinish = handheldCapturedPoints >= 3;
+ stopBtn.setText("缁撴潫");
+ stopBtn.setEnabled(canFinish);
+ if (canFinish) {
+ if (handheldStopButtonOriginalBackground != null) {
+ stopBtn.setBackground(handheldStopButtonOriginalBackground);
+ }
+ if (handheldStopButtonOriginalForeground != null) {
+ stopBtn.setForeground(handheldStopButtonOriginalForeground);
+ }
+ stopBtn.setToolTipText("缁撴潫閲囬泦骞惰繑鍥炴柊澧炲湴鍧�");
+ } else {
+ stopBtn.setBackground(new Color(220, 220, 220));
+ stopBtn.setForeground(new Color(130, 130, 130));
+ stopBtn.setToolTipText("鑷冲皯閲囬泦涓変釜鐐规墠鑳界粨鏉�");
+ }
+ }
+ }
+
+ private String resolveHandheldConfirmTooltip(boolean hasFix, boolean hasValidPosition, boolean duplicate) {
+ if (!hasFix) {
+ return "褰撳墠瀹氫綅璐ㄩ噺涓嶈冻锛屾棤娉曢噰闆�";
+ }
+ if (!hasValidPosition) {
+ return "褰撳墠瀹氫綅鏁版嵁鏃犳晥锛岃绋嶅悗鍐嶈瘯";
+ }
+ if (duplicate) {
+ return "褰撳墠鍧愭爣宸查噰闆嗭紝璇风Щ鍔ㄥ埌鏂扮殑浣嶇疆";
+ }
+ return null;
+ }
+
+ private boolean hasHighPrecisionFix() {
+ Device device = Device.getGecaoji();
+ if (device == null) {
+ return false;
+ }
+ String status = device.getPositioningStatus();
+ return status != null && "4".equals(status.trim());
+ }
+
+ private boolean canConfirmHandheldPoint() {
+ return handheldCaptureActive
+ && hasHighPrecisionFix()
+ && hasValidRealtimeHandheldPosition()
+ && !isCurrentHandheldPointDuplicate();
+ }
+
+ private void enterDrawingControlMode() {
+ if (drawingControlModeActive) {
+ return;
+ }
+ storedStartButtonShowingPause = startButtonShowingPause;
+ storedStopButtonActive = stopButtonActive;
+ storedStatusBeforeDrawing = statusLabel != null ? statusLabel.getText() : null;
+ drawingControlModeActive = true;
+ applyDrawingPauseState(false, false);
+ updateDrawingControlButtonLabels();
+ }
+
+ private void exitDrawingControlMode() {
+ if (!drawingControlModeActive) {
+ return;
+ }
+ drawingControlModeActive = false;
+ applyDrawingPauseState(false, false);
+ drawingPaused = false;
+ stopButtonActive = storedStopButtonActive;
+ startButtonShowingPause = storedStartButtonShowingPause;
+ if (startBtn != null) {
+ updateStartButtonAppearance();
+ }
+ if (stopBtn != null) {
+ stopBtn.setText("缁撴潫");
+ updateStopButtonIcon();
+ }
+ if (statusLabel != null) {
+ statusLabel.setText(storedStatusBeforeDrawing != null ? storedStatusBeforeDrawing : "寰呮満");
+ }
+ storedStatusBeforeDrawing = null;
+ }
+
+ private void updateDrawingControlButtonLabels() {
+ if (!drawingControlModeActive) {
+ return;
+ }
+ configureButtonForDrawingMode(startBtn);
+ configureButtonForDrawingMode(stopBtn);
+ if (startBtn != null) {
+ startBtn.setText(drawingPaused ? "寮�濮嬬粯鍒�" : "鏆傚仠缁樺埗");
+ }
+ if (stopBtn != null) {
+ stopBtn.setText("缁撴潫缁樺埗");
+ }
+ }
+
+ private void configureButtonForDrawingMode(JButton button) {
+ if (button == null) {
+ return;
+ }
+ button.setIcon(null);
+ button.setIconTextGap(0);
+ button.setHorizontalAlignment(SwingConstants.CENTER);
+ button.setHorizontalTextPosition(SwingConstants.CENTER);
+ }
+
private void updateStartButtonAppearance() {
if (startBtn == null) {
return;
@@ -1028,6 +1484,13 @@
refreshMowerSpeedLabel();
}
+ public void setHandheldMowerIconActive(boolean active) {
+ if (mapRenderer == null) {
+ return;
+ }
+ mapRenderer.setHandheldMowerIconActive(active);
+ }
+
public boolean startMowerBoundaryCapture() {
if (mapRenderer == null) {
return false;
@@ -1037,6 +1500,8 @@
return false;
}
+ mapRenderer.clearIdleTrail();
+
activeBoundaryMode = BoundaryCaptureMode.MOWER;
mowerBoundaryCaptureActive = true;
mowerBaseLatLon = baseLatLonCandidate;
@@ -1053,9 +1518,12 @@
Coordinate.setStartSaveGngga(true);
if (mapRenderer != null) {
+ mapRenderer.setBoundaryPreviewMarkerScale(2.0d);
mapRenderer.beginHandheldBoundaryPreview();
}
+ setHandheldMowerIconActive(false);
+
startMowerBoundaryMonitor();
return true;
}
@@ -1064,15 +1532,12 @@
if (mapRenderer == null) {
return false;
}
- if (handheldCaptureDialog != null && handheldCaptureDialog.isShowing()) {
- handheldCaptureDialog.toFront();
- return true;
- }
-
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
stopMowerBoundaryCapture();
}
+ mapRenderer.clearIdleTrail();
+
activeBoundaryMode = BoundaryCaptureMode.HANDHELD;
handheldCaptureActive = true;
handheldCapturedPoints = 0;
@@ -1084,19 +1549,10 @@
handheldTemporaryPoints.clear();
}
AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList());
- mapRenderer.beginHandheldBoundaryPreview();
-
- Window ownerWindow = SwingUtilities.getWindowAncestor(this);
- SwingUtilities.invokeLater(() -> {
- Window targetOwner = ownerWindow;
- if (targetOwner == null) {
- targetOwner = SwingUtilities.getWindowAncestor(Shouye.this);
- }
- HandheldBoundaryCaptureDialog dialog = new HandheldBoundaryCaptureDialog(targetOwner, Shouye.this, visualizationPanel, THEME_COLOR);
- handheldCaptureDialog = dialog;
- dialog.setVisible(true);
- });
-
+ mapRenderer.setBoundaryPreviewMarkerScale(1.0d);
+ mapRenderer.beginHandheldBoundaryPreview();
+ setHandheldMowerIconActive(true);
+ enterHandheldCaptureInlineUi();
return true;
}
@@ -1188,6 +1644,7 @@
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
activeBoundaryMode = BoundaryCaptureMode.NONE;
}
+ setHandheldMowerIconActive(false);
}
private void discardLatestCoordinate(Coordinate coordinate) {
@@ -1282,6 +1739,7 @@
List<Point2D.Double> closedSnapshot = createClosedHandheldPointSnapshot();
handheldCaptureActive = false;
+ activeBoundaryMode = BoundaryCaptureMode.NONE;
Coordinate.setStartSaveGngga(false);
if (mapRenderer != null) {
mapRenderer.clearHandheldBoundaryPreview();
@@ -1289,20 +1747,12 @@
AddDikuai.recordTemporaryBoundaryPoints(closedSnapshot);
+ exitHandheldCaptureInlineUi();
+
SwingUtilities.invokeLater(AddDikuai::finishDrawingSession);
return true;
}
- void handheldBoundaryCaptureDialogClosed(HandheldBoundaryCaptureDialog dialog) {
- if (handheldCaptureDialog == dialog) {
- handheldCaptureDialog = null;
- }
- handheldCaptureActive = false;
- if (activeBoundaryMode == BoundaryCaptureMode.HANDHELD) {
- activeBoundaryMode = BoundaryCaptureMode.NONE;
- }
- }
-
int getHandheldCapturedPointCount() {
return handheldCapturedPoints;
}
@@ -1571,9 +2021,9 @@
if (statusLabel == null) {
return;
}
- if ("浣滀笟涓�".equals(statusText)) {
+ if ("浣滀笟涓�".equals(statusText) || "缁樺埗涓�".equals(statusText)) {
statusLabel.setForeground(THEME_COLOR);
- } else if ("鏆傚仠涓�".equals(statusText)) {
+ } else if ("鏆傚仠涓�".equals(statusText) || "缁樺埗鏆傚仠".equals(statusText)) {
statusLabel.setForeground(STATUS_PAUSE_COLOR);
} else {
statusLabel.setForeground(Color.GRAY);
@@ -1601,6 +2051,29 @@
return button;
}
+ private JButton createFloatingTextButton(String text) {
+ JButton button = new JButton(text);
+ button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 15));
+ button.setForeground(Color.WHITE);
+ button.setBackground(THEME_COLOR);
+ button.setBorder(BorderFactory.createEmptyBorder(10, 18, 10, 18));
+ button.setFocusPainted(false);
+ button.setOpaque(true);
+ button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ button.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ button.setBackground(THEME_HOVER_COLOR);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ button.setBackground(THEME_COLOR);
+ }
+ });
+ return button;
+ }
+
private ImageIcon loadScaledIcon(String path, int width, int height) {
try {
ImageIcon icon = new ImageIcon(path);
@@ -1658,6 +2131,12 @@
if (notifyCoordinate) {
Coordinate.setStartSaveGngga(!paused);
}
+ if (drawingControlModeActive) {
+ updateDrawingControlButtonLabels();
+ if (statusLabel != null) {
+ statusLabel.setText(paused ? "缁樺埗鏆傚仠" : "缁樺埗涓�");
+ }
+ }
}
private void toggleDrawingPause() {
@@ -1670,36 +2149,33 @@
public void showEndDrawingButton(Runnable callback, String drawingShape) {
endDrawingCallback = callback;
- applyDrawingPauseState(false, false);
circleDialogMode = false;
hideCircleGuidancePanel();
-
- ensureFloatingIconsLoaded();
- ensureFloatingButtonInfrastructure();
+ enterDrawingControlMode();
boolean enableCircleGuidance = drawingShape != null
&& "circle".equalsIgnoreCase(drawingShape.trim());
if (enableCircleGuidance) {
- prepareCircleGuidanceState();
- showCircleGuidanceStep(1);
- endDrawingButton.setVisible(false);
+ ensureFloatingIconsLoaded();
+ ensureFloatingButtonInfrastructure();
if (drawingPauseButton != null) {
drawingPauseButton.setVisible(false);
}
+ if (endDrawingButton != null) {
+ endDrawingButton.setVisible(false);
+ }
+ prepareCircleGuidanceState();
+ showCircleGuidanceStep(1);
+ floatingButtonPanel.setVisible(true);
+ if (floatingButtonPanel.getParent() != visualizationPanel) {
+ visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
+ }
+ rebuildFloatingButtonColumn();
} else {
clearCircleGuidanceArtifacts();
- endDrawingButton.setVisible(true);
- if (drawingPauseButton != null) {
- drawingPauseButton.setVisible(true);
- }
+ hideFloatingDrawingControls();
}
- floatingButtonPanel.setVisible(true);
- if (floatingButtonPanel.getParent() != visualizationPanel) {
- visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
- }
-
- rebuildFloatingButtonColumn();
visualizationPanel.revalidate();
visualizationPanel.repaint();
}
@@ -1776,6 +2252,14 @@
floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
}
floatingButtonColumn.add(endDrawingButton);
+ added = true;
+ }
+ if (pathPreviewReturnButton != null && pathPreviewReturnButton.isVisible()) {
+ if (added) {
+ floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
+ }
+ floatingButtonColumn.add(pathPreviewReturnButton);
+ added = true;
}
floatingButtonColumn.revalidate();
floatingButtonColumn.repaint();
@@ -2184,21 +2668,27 @@
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 (baseStation == null) {
+ baseStation = new BaseStation();
+ }
+ baseStation.load();
+ coords = baseStation.getInstallationCoordinates();
+
+ if (!isMeaningfulValue(coords)) {
+ 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;
+ if (!isMeaningfulValue(coords)) {
+ coords = addzhangaiwu.getActiveSessionBaseStation();
+ }
+ if (!isMeaningfulValue(coords)) {
+ return null;
+ }
}
String[] parts = coords.split(",");
if (parts.length < 4) {
@@ -2330,7 +2820,9 @@
clearCircleGuidanceArtifacts();
hideFloatingDrawingControls();
circleDialogMode = false;
- applyDrawingPauseState(false, false);
+ exitHandheldCaptureInlineUi();
+ handheldCaptureActive = false;
+ exitDrawingControlMode();
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
stopMowerBoundaryCapture();
} else if (activeBoundaryMode == BoundaryCaptureMode.HANDHELD && !handheldCaptureActive) {
@@ -2339,6 +2831,129 @@
endDrawingCallback = null;
visualizationPanel.revalidate();
visualizationPanel.repaint();
+ setHandheldMowerIconActive(false);
+ }
+
+ private void showPathPreviewReturnControls() {
+ ensureFloatingButtonInfrastructure();
+ if (drawingPauseButton != null) {
+ drawingPauseButton.setVisible(false);
+ }
+ if (endDrawingButton != null) {
+ endDrawingButton.setVisible(false);
+ }
+ if (pathPreviewReturnButton == null) {
+ pathPreviewReturnButton = createFloatingTextButton("杩斿洖");
+ pathPreviewReturnButton.setToolTipText("杩斿洖鏂板鍦板潡姝ラ");
+ pathPreviewReturnButton.addActionListener(e -> handlePathPreviewReturn());
+ }
+ pathPreviewReturnButton.setVisible(true);
+ if (floatingButtonPanel != null) {
+ floatingButtonPanel.setVisible(true);
+ if (floatingButtonPanel.getParent() != visualizationPanel) {
+ visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
+ }
+ }
+ rebuildFloatingButtonColumn();
+ }
+
+ private void hidePathPreviewReturnControls() {
+ if (pathPreviewReturnButton != null) {
+ pathPreviewReturnButton.setVisible(false);
+ }
+ rebuildFloatingButtonColumn();
+ if (floatingButtonPanel != null && floatingButtonColumn != null
+ && floatingButtonColumn.getComponentCount() == 0) {
+ floatingButtonPanel.setVisible(false);
+ }
+ }
+
+ private void handlePathPreviewReturn() {
+ Runnable callback = pathPreviewReturnAction;
+ exitMowingPathPreview();
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ public boolean startMowingPathPreview(String landNumber,
+ String landName,
+ String boundary,
+ String obstacles,
+ String plannedPath,
+ Runnable returnAction) {
+ if (mapRenderer == null || !isMeaningfulValue(plannedPath)) {
+ return false;
+ }
+
+ if (pathPreviewActive) {
+ exitMowingPathPreview();
+ }
+
+ exitDrawingControlMode();
+ hideCircleGuidancePanel();
+ clearCircleGuidanceArtifacts();
+
+ pathPreviewReturnAction = returnAction;
+ pathPreviewActive = true;
+ mapRenderer.setPathPreviewSizingEnabled(true);
+
+ previewRestoreLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
+ previewRestoreLandName = null;
+ if (isMeaningfulValue(previewRestoreLandNumber)) {
+ Dikuai existing = Dikuai.getDikuai(previewRestoreLandNumber);
+ if (existing != null) {
+ previewRestoreLandName = existing.getLandName();
+ }
+ }
+
+ mapRenderer.setCurrentBoundary(boundary, landNumber, landName);
+ mapRenderer.setCurrentObstacles(obstacles, landNumber);
+ mapRenderer.setCurrentPlannedPath(plannedPath);
+ mapRenderer.clearHandheldBoundaryPreview();
+ mapRenderer.setBoundaryPointSizeScale(1.0d);
+ mapRenderer.setBoundaryPointsVisible(isMeaningfulValue(boundary));
+
+ String displayName = isMeaningfulValue(landName) ? landName : landNumber;
+ updateCurrentAreaName(displayName);
+
+ showPathPreviewReturnControls();
+ visualizationPanel.revalidate();
+ visualizationPanel.repaint();
+ return true;
+ }
+
+ public void exitMowingPathPreview() {
+ if (!pathPreviewActive) {
+ return;
+ }
+ pathPreviewActive = false;
+ if (mapRenderer != null) {
+ mapRenderer.setPathPreviewSizingEnabled(false);
+ }
+ hidePathPreviewReturnControls();
+
+ String restoreNumber = previewRestoreLandNumber;
+ String restoreName = previewRestoreLandName;
+ previewRestoreLandNumber = null;
+ previewRestoreLandName = null;
+ pathPreviewReturnAction = null;
+
+ if (restoreNumber != null) {
+ Dikuaiguanli.setCurrentWorkLand(restoreNumber, restoreName);
+ } else if (mapRenderer != null) {
+ mapRenderer.setCurrentBoundary(null, null, null);
+ mapRenderer.setCurrentObstacles((String) null, null);
+ mapRenderer.setCurrentPlannedPath(null);
+ mapRenderer.setBoundaryPointsVisible(false);
+ mapRenderer.setBoundaryPointSizeScale(1.0d);
+ mapRenderer.clearHandheldBoundaryPreview();
+ mapRenderer.resetView();
+ updateCurrentAreaName(null);
+ }
+
+ visualizationPanel.revalidate();
+ visualizationPanel.repaint();
}
/**
@@ -2370,6 +2985,16 @@
private void initializeDefaultAreaSelection() {
Dikuai.initFromProperties();
+ String persistedLandNumber = Dikuaiguanli.getPersistedWorkLandNumber();
+ if (persistedLandNumber != null) {
+ Dikuai stored = Dikuai.getDikuai(persistedLandNumber);
+ if (stored != null) {
+ Dikuaiguanli.setCurrentWorkLand(persistedLandNumber, stored.getLandName());
+ return;
+ }
+ Dikuaiguanli.setCurrentWorkLand(null, null);
+ }
+
Map<String, Dikuai> all = Dikuai.getAllDikuai();
if (all.isEmpty()) {
Dikuaiguanli.setCurrentWorkLand(null, null);
diff --git a/src/zhuye/adddikuaiyulan.java b/src/zhuye/adddikuaiyulan.java
index caa584d..3307106 100644
--- a/src/zhuye/adddikuaiyulan.java
+++ b/src/zhuye/adddikuaiyulan.java
@@ -17,6 +17,10 @@
private static final Color HANDHELD_BOUNDARY_FILL = new Color(51, 153, 255, 60);
private static final Color HANDHELD_BOUNDARY_BORDER = new Color(0, 100, 0, 220);
private static final Color HANDHELD_BOUNDARY_POINT = new Color(0, 100, 0);
+ private static final double BASE_WORLD_MARKER_SIZE = 0.27d; // halve the base diameter for subtler markers
+ private static final double MIN_PIXEL_DIAMETER = 3.0d;
+ private static final double MAX_PIXEL_DIAMETER = 9.0d;
+ private static volatile double cachedMarkerPixelDiameter = -1.0d;
private adddikuaiyulan() {
}
@@ -24,7 +28,12 @@
public static void drawPreview(Graphics2D g2d,
List<Point2D.Double> previewPoints,
double scale,
- boolean previewActive) {
+ boolean previewActive,
+ double diameterScale) {
+ if (!previewActive) {
+ cachedMarkerPixelDiameter = -1.0d;
+ }
+
if (g2d == null || !previewActive || previewPoints == null || previewPoints.isEmpty()) {
return;
}
@@ -56,13 +65,25 @@
g2d.fill(path);
}
- float outlineWidth =0.1f;
+ float outlineWidth = 0.1f;
g2d.setStroke(new BasicStroke(outlineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.setColor(HANDHELD_BOUNDARY_BORDER);
g2d.draw(path);
- double markerSize = 0.2d;
- double markerRadius = markerSize / 2.0d;
+ if (cachedMarkerPixelDiameter <= 0.0d) {
+ double previousPixelDiameter = Math.abs(BASE_WORLD_MARKER_SIZE * scale);
+ if (previousPixelDiameter <= 0.0d) {
+ previousPixelDiameter = MIN_PIXEL_DIAMETER;
+ }
+ cachedMarkerPixelDiameter = Math.max(MIN_PIXEL_DIAMETER,
+ Math.min(MAX_PIXEL_DIAMETER, previousPixelDiameter));
+ }
+
+ double effectiveScale = Math.max(0.01d, scale);
+ double markerSize = cachedMarkerPixelDiameter / effectiveScale;
+ double normalizedScale = Double.isFinite(diameterScale) && diameterScale > 0.0d ? diameterScale : 1.0d;
+ markerSize *= normalizedScale;
+ double markerRadius = markerSize / 2.0d;
for (Point2D.Double point : previewPoints) {
if (point == null || !Double.isFinite(point.x) || !Double.isFinite(point.y)) {
diff --git a/src/zhuye/pointandnumber.java b/src/zhuye/pointandnumber.java
index fb95ad8..ac1df42 100644
--- a/src/zhuye/pointandnumber.java
+++ b/src/zhuye/pointandnumber.java
@@ -20,7 +20,8 @@
List<Point2D.Double> boundary, // 杈圭晫鐐归泦鍚�
double scale, // 缂╂斁姣斾緥
double mergeThreshold, // 鍚堝苟闃堝��
- Color pointColor) { // 鐐归鑹�
+ Color pointColor, // 鐐归鑹�
+ double diameterScale) { // 鐩村緞缂╂斁鍥犲瓙
if (boundary == null || boundary.size() < 2) { // 鍒ゆ柇鏁版嵁鏄惁鏈夋晥
return; // 鏃犳晥鐩存帴杩斿洖
}
@@ -32,8 +33,10 @@
return; // 鏃犳晥杩斿洖
}
- double scaleFactor = Math.max(0.5, scale); // 闃叉杩囧皬缂╂斁
- double markerDiameter = Math.max(1.0, (10.0 / scaleFactor) * 0.2); // 鎻忕偣鐩村緞
+ double scaleFactor = Math.max(0.5, scale); // 闃叉杩囧皬缂╂斁
+ double clampedScale = diameterScale > 0 ? diameterScale : 1.0; // 闃叉闈炴硶缂╂斁
+ double minimumDiameter = clampedScale < 1.0 ? 0.5 : 1.0; // 缂╁皬鏃跺厑璁告洿灏忕殑鏈�灏忓��
+ double markerDiameter = Math.max(minimumDiameter, (10.0 / scaleFactor) * 0.2 * clampedScale); // 鎻忕偣鐩村緞
double markerRadius = markerDiameter / 2.0; // 鍗婂緞
for (int i = 0; i < effectiveCount; i++) { // 閬嶅巻鏈夋晥鐐�
diff --git a/src/zhuye/zijian.java b/src/zhuye/zijian.java
new file mode 100644
index 0000000..3241748
--- /dev/null
+++ b/src/zhuye/zijian.java
@@ -0,0 +1,127 @@
+package zhuye;
+
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.Window;
+
+/**
+ * 鑷鎻愮ず宸ュ叿绫伙紝闆嗕腑绠$悊鍓茶崏鏈轰綔涓氬墠鐨勮嚜妫�鎻愮ず閫昏緫銆�
+ */
+public final class zijian {
+ private static boolean selfCheckCompleted;
+ private static boolean initialPromptShown;
+
+ private zijian() {
+ // Utility class
+ }
+
+ public static boolean ensureBeforeMowing(Component anchorComponent, Runnable onSelfCheckConfirmed) {
+ if (selfCheckCompleted) {
+ return true;
+ }
+ showSelfCheckDialog(anchorComponent, onSelfCheckConfirmed);
+ return false;
+ }
+
+ public static void showInitialPromptIfNeeded(Component anchorComponent, Runnable onSelfCheckConfirmed) {
+ if (selfCheckCompleted || initialPromptShown) {
+ return;
+ }
+ initialPromptShown = true;
+ showSelfCheckDialog(anchorComponent, onSelfCheckConfirmed);
+ }
+
+ public static void markSelfCheckCompleted() {
+ selfCheckCompleted = true;
+ }
+
+ private static void showSelfCheckDialog(Component anchorComponent, Runnable onSelfCheckConfirmed) {
+ Component parent = resolveDialogParent(anchorComponent);
+ Object[] options = {"绔嬪嵆鑷", "鍙栨秷"};
+ String message = "<html>鍓茶崏鍓嶈鍏堝畬鎴愬鍓茶崏鏈虹殑鑷鎿嶄綔銆�<br>閬ユ帶鍓茶崏鏈哄悜鍓嶅紑绾�2绫筹紝鐒跺悗鍘熷湴杞湀瀹屾垚鑷鍔熻兘銆�</html>";
+
+ JOptionPane optionPane = new JOptionPane(
+ message,
+ JOptionPane.INFORMATION_MESSAGE,
+ JOptionPane.DEFAULT_OPTION,
+ null,
+ options,
+ options[0]);
+
+ JDialog dialog = optionPane.createDialog(parent, "鑷鎻愮ず");
+ dialog.pack();
+ applyTargetWidth(anchorComponent, dialog);
+ dialog.setLocationRelativeTo(parent instanceof Component ? (Component) parent : null);
+ dialog.setVisible(true);
+
+ Object selectedValue = optionPane.getValue();
+ dialog.dispose();
+
+ boolean confirmed = options[0].equals(selectedValue);
+ if (confirmed) {
+ selfCheckCompleted = true;
+ if (onSelfCheckConfirmed != null) {
+ SwingUtilities.invokeLater(onSelfCheckConfirmed);
+ }
+ }
+ }
+
+ private static void applyTargetWidth(Component anchorComponent, JDialog dialog) {
+ if (dialog == null) {
+ return;
+ }
+ int targetWidth = resolveTargetDialogWidth(anchorComponent);
+ if (targetWidth <= 0) {
+ return;
+ }
+ Dimension currentSize = dialog.getSize();
+ if (currentSize == null) {
+ currentSize = dialog.getPreferredSize();
+ }
+ if (currentSize == null) {
+ currentSize = new Dimension(targetWidth, 0);
+ }
+ int width = Math.max(targetWidth, currentSize.width);
+ int height = currentSize.height > 0 ? currentSize.height : dialog.getHeight();
+ if (height <= 0) {
+ height = 200;
+ }
+ dialog.setSize(new Dimension(width, height));
+ }
+
+ private static int resolveTargetDialogWidth(Component anchorComponent) {
+ int baseWidth = 0;
+ if (anchorComponent != null) {
+ baseWidth = anchorComponent.getWidth();
+ if (baseWidth <= 0) {
+ Component parent = resolveDialogParent(anchorComponent);
+ if (parent != null) {
+ baseWidth = parent.getWidth();
+ }
+ }
+ }
+ if (baseWidth <= 0) {
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ baseWidth = screenSize != null ? screenSize.width : 0;
+ }
+ if (baseWidth <= 0) {
+ return 0;
+ }
+ return (int) Math.round(baseWidth * 0.8);
+ }
+
+ private static Component resolveDialogParent(Component anchorComponent) {
+ if (anchorComponent == null) {
+ return null;
+ }
+ if (anchorComponent instanceof Window) {
+ return anchorComponent;
+ }
+ Window window = SwingUtilities.getWindowAncestor(anchorComponent);
+ return window != null ? window : anchorComponent;
+ }
+}
diff --git a/user.properties b/user.properties
index c427148..ca5d233 100644
--- a/user.properties
+++ b/user.properties
@@ -1,10 +1,10 @@
#Updated User Properties
-#Sun Nov 23 11:24:29 GMT+08:00 2025
-registrationTime=-1
+#Tue Dec 09 17:21:03 CST 2025
+email=789
+language=zh
lastLoginTime=-1
password=123
-language=zh
-userName=233
-userId=-1
-email=789
+registrationTime=-1
status=-1
+userId=-1
+userName=233
--
Gitblit v1.10.0