From 1cf1ecbc75c6d14b40efb3161e7db0b8b64f7de2 Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期三, 17 十二月 2025 12:05:27 +0800
Subject: [PATCH] 新增有障碍物的路径规划算法和优化没有障碍物的路径算法
---
src/zhangaiwu/Obstacledraw.java | 63 +
dikuai.properties | 20
src/lujing/MowingPathGenerationPage.java | 634 +++++++++++++++++++++
src/lujing/ObstaclePathPlanner.java | 536 ++++++++++++++++++
src/zhuye/LegendDialog.java | 26
src/zhuye/MapRenderer.java | 11
set.properties | 8
src/zhuye/Shouye.java | 20
Obstacledge.properties | 2
src/lujing/Lunjingguihua.java | 95 ++
src/dikuai/Dikuaiguanli.java | 280 +++-----
11 files changed, 1,435 insertions(+), 260 deletions(-)
diff --git a/Obstacledge.properties b/Obstacledge.properties
index 8845548..2bc1283 100644
--- a/Obstacledge.properties
+++ b/Obstacledge.properties
@@ -1,5 +1,5 @@
# 鍓茶崏鏈哄湴鍧楅殰纰嶇墿閰嶇疆鏂囦欢
-# 鐢熸垚鏃堕棿锛�2025-12-09T11:53:37.128295200
+# 鐢熸垚鏃堕棿锛�2025-12-17T12:03:32.881913900
# 鍧愭爣绯伙細WGS84锛堝害鍒嗘牸寮忥級
# ============ 鍦板潡鍩哄噯绔欓厤缃� ============
diff --git a/dikuai.properties b/dikuai.properties
index c24c87e..026cea7 100644
--- a/dikuai.properties
+++ b/dikuai.properties
@@ -1,19 +1,19 @@
#Dikuai Properties
-#Fri Dec 12 15:42:15 CST 2025
+#Wed Dec 17 12:03:32 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.boundaryCoordinates=77.19,-32.68;80.71,-54.97;80.99,-55.90;83.54,-56.46;85.04,-55.55;85.94,-53.74;83.24,-35.82;84.55,-34.54;94.02,-31.92;94.10,-31.11;90.88,-20.39;90.35,-19.53;88.33,-19.00;84.12,-19.47;78.92,-22.36;76.63,-25.55;76.93,-29.84;77.06,-31.26;77.19,-32.68
+LAND1.boundaryOriginalCoordinates=39.831413,116.280186,49.12;39.831409,116.280188,49.09;39.831403,116.280187,49.12;39.831395,116.280189,49.13;39.831388,116.280191,49.16;39.831379,116.280193,49.18;39.831370,116.280194,49.16;39.831362,116.280195,49.15;39.831353,116.280197,49.11;39.831344,116.280200,49.15;39.831335,116.280202,49.15;39.831326,116.280204,49.08;39.831317,116.280206,49.19;39.831309,116.280209,49.18;39.831301,116.280210,49.19;39.831293,116.280212,49.11;39.831284,116.280214,49.06;39.831275,116.280215,49.16;39.831266,116.280217,49.14;39.831258,116.280220,49.08;39.831249,116.280223,49.09;39.831240,116.280225,49.10;39.831231,116.280226,49.04;39.831222,116.280226,49.17;39.831212,116.280227,49.11;39.831204,116.280230,49.09;39.831201,116.280238,49.10;39.831199,116.280249,49.07;39.831199,116.280260,49.21;39.831202,116.280270,49.16;39.831207,116.280278,49.06;39.831212,116.280284,49.04;39.831217,116.280287,49.05;39.831223,116.280288,49.09;39.831229,116.280287,49.10;39.831237,116.280286,49.05;39.831245,116.280286,49.08;39.831254,116.280284,49.07;39.831263,116.280283,49.05;39.831272,116.280280,49.11;39.831282,116.280278,49.10;39.831291,116.280276,49.11;39.831300,116.280274,49.16;39.831308,116.280270,49.13;39.831318,116.280268,49.10;39.831327,116.280267,49.14;39.831337,116.280266,49.08;39.831347,116.280263,49.10;39.831356,116.280261,49.20;39.831366,116.280258,49.14;39.831375,116.280256,49.09;39.831384,116.280257,49.13;39.831392,116.280263,49.10;39.831396,116.280272,49.12;39.831398,116.280283,49.16;39.831401,116.280294,49.11;39.831403,116.280307,49.13;39.831405,116.280318,49.19;39.831406,116.280328,49.20;39.831408,116.280340,49.22;39.831411,116.280353,49.19;39.831414,116.280363,49.26;39.831416,116.280374,49.22;39.831419,116.280383,49.20;39.831427,116.280384,49.21;39.831433,116.280379,49.17;39.831441,116.280375,49.19;39.831451,116.280372,49.09;39.831459,116.280370,49.16;39.831467,116.280364,49.21;39.831476,116.280360,49.22;39.831485,116.280357,49.20;39.831495,116.280355,49.26;39.831505,116.280351,49.21;39.831514,116.280348,49.17;39.831523,116.280346,49.20;39.831531,116.280340,49.04;39.831535,116.280328,49.08;39.831536,116.280316,49.03;39.831535,116.280304,49.03;39.831533,116.280291,49.06;39.831532,116.280279,49.07;39.831531,116.280267,49.11;39.831528,116.280257,49.09;39.831525,116.280246,49.11;39.831521,116.280237,49.09;39.831516,116.280227,49.08;39.831511,116.280216,49.12;39.831505,116.280206,49.14;39.831499,116.280197,49.12;39.831492,116.280189,49.15;39.831484,116.280184,49.14;39.831477,116.280179,49.12;39.831469,116.280178,49.12;39.831462,116.280181,49.13;39.831454,116.280182,49.12;39.831445,116.280183,49.12;39.831439,116.280183,49.14;39.831438,116.280183,49.12
LAND1.boundaryPointInterval=-1
-LAND1.createTime=2025-12-09 11\:16\:40
+LAND1.createTime=2025-12-16 15\:43\:39
LAND1.intelligentSceneAnalysis=-1
-LAND1.landArea=35.87
-LAND1.landName=1234
+LAND1.landArea=327.17
+LAND1.landName=yyii
LAND1.landNumber=LAND1
-LAND1.mowingPattern=铻烘棆寮�
-LAND1.mowingTrack=5.952,-10.672
+LAND1.mowingPattern=骞宠绾�
+LAND1.mowingTrack=
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.plannedPath=77.17,-29.65;81.28,-55.71;81.70,-55.80;76.93,-25.57;77.26,-25.10;82.12,-55.89;82.54,-55.98;77.59,-24.64;77.92,-24.18;82.96,-56.08;83.38,-56.17;78.25,-23.72;78.59,-23.25;83.76,-56.03;84.13,-55.81;78.92,-22.79;79.27,-22.45;84.50,-55.58;84.87,-55.33;79.64,-22.24;80.01,-22.04;85.18,-54.72;85.48,-54.10;80.39,-21.83;80.76,-21.62;83.00,-35.83;83.34,-35.38;81.13,-21.42;81.50,-21.21;83.69,-35.03;84.04,-34.69;81.88,-21.00;82.25,-20.80;84.39,-34.35;84.77,-34.22;82.62,-20.59;82.99,-20.38;85.16,-34.11;85.55,-34.00;83.37,-20.18;83.74,-19.97;85.94,-33.90;86.32,-33.79;84.11,-19.76;84.50,-19.68;86.71,-33.68;87.10,-33.57;84.90,-19.63;85.30,-19.59;87.49,-33.47;87.88,-33.36;85.70,-19.55;86.09,-19.50;88.26,-33.25;88.65,-33.15;86.49,-19.46;86.89,-19.41;89.04,-33.04;89.43,-32.93;87.29,-19.37;87.69,-19.32;89.82,-32.82;90.20,-32.72;88.08,-19.28;88.49,-19.30;90.59,-32.61;90.98,-32.50;88.91,-19.41;89.34,-19.52;91.37,-32.39;91.76,-32.29;89.76,-19.63;90.18,-19.74;92.14,-32.18;92.53,-32.07;90.76,-20.88;91.62,-23.72;92.92,-31.96;93.31,-31.86;92.47,-26.56;93.33,-29.40;93.70,-31.75
LAND1.returnPointCoordinates=-1
-LAND1.updateTime=2025-12-09 11\:53\:37
+LAND1.updateTime=2025-12-17 12\:03\:32
LAND1.userId=-1
diff --git a/set.properties b/set.properties
index 4bc632d..4e56433 100644
--- a/set.properties
+++ b/set.properties
@@ -1,16 +1,16 @@
#Mower Configuration Properties - Updated
-#Tue Dec 16 16:27:10 CST 2025
+#Wed Dec 17 12:04:00 CST 2025
appVersion=-1
currentWorkLandNumber=LAND1
cuttingWidth=200
firmwareVersion=-1
handheldMarkerId=
idleTrailDurationSeconds=60
-mapScale=20.09
+mapScale=15.31
mowerId=1234
serialAutoConnect=true
serialBaudRate=115200
serialPortName=COM15
simCardNumber=-1
-viewCenterX=0.55
-viewCenterY=12.53
+viewCenterX=-85.37
+viewCenterY=37.73
diff --git a/src/dikuai/Dikuaiguanli.java b/src/dikuai/Dikuaiguanli.java
index a5e31b6..5f0218e 100644
--- a/src/dikuai/Dikuaiguanli.java
+++ b/src/dikuai/Dikuaiguanli.java
@@ -24,6 +24,7 @@
import java.util.Properties;
import lujing.Lunjingguihua;
+import lujing.MowingPathGenerationPage;
import zhangaiwu.AddDikuai;
import zhangaiwu.Obstacledge;
import zhuye.MapRenderer;
@@ -334,8 +335,8 @@
JButton deleteBtn = createDeleteButton();
deleteBtn.addActionListener(e -> deleteDikuai(dikuai));
- JButton generatePathBtn = createPrimaryFooterButton("鐢熸垚鍓茶崏璺緞");
- generatePathBtn.addActionListener(e -> generateMowingPath(dikuai));
+ JButton generatePathBtn = createPrimaryFooterButton("璺緞瑙勫垝");
+ generatePathBtn.addActionListener(e -> showPathPlanningPage(dikuai));
JPanel footerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
footerPanel.setBackground(CARD_BACKGROUND);
@@ -828,6 +829,77 @@
return trimmed;
}
+ /**
+ * 鏄剧ず璺緞瑙勫垝椤甸潰
+ */
+ private void showPathPlanningPage(Dikuai dikuai) {
+ if (dikuai == null) {
+ return;
+ }
+
+ Window owner = SwingUtilities.getWindowAncestor(this);
+
+ // 鑾峰彇鍦板潡鍩烘湰鏁版嵁
+ String baseStationValue = prepareCoordinateForEditor(dikuai.getBaseStationCoordinates());
+ String boundaryValue = prepareCoordinateForEditor(dikuai.getBoundaryCoordinates());
+ List<Obstacledge.Obstacle> configuredObstacles = getConfiguredObstacles(dikuai);
+ String obstacleValue = determineInitialObstacleValue(dikuai, configuredObstacles);
+ String widthValue = sanitizeWidthString(dikuai.getMowingWidth());
+ if (widthValue != null) {
+ try {
+ double widthCm = Double.parseDouble(widthValue);
+ widthValue = formatWidthForStorage(widthCm);
+ } catch (NumberFormatException ignored) {
+ // 淇濇寔鍘熷瀛楃涓诧紝绋嶅悗鏍¢獙鎻愮ず
+ }
+ }
+ String modeValue = sanitizeValueOrNull(dikuai.getMowingPattern());
+ String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath());
+
+ // 鍒涘缓淇濆瓨鍥炶皟鎺ュ彛瀹炵幇
+ MowingPathGenerationPage.PathSaveCallback callback = new MowingPathGenerationPage.PathSaveCallback() {
+ @Override
+ public boolean saveBaseStationCoordinates(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "baseStationCoordinates", value);
+ }
+
+ @Override
+ public boolean saveBoundaryCoordinates(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "boundaryCoordinates", value);
+ }
+
+ @Override
+ public boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue) {
+ return persistObstaclesForLand(dikuai, baseStationValue, obstacleValue);
+ }
+
+ @Override
+ public boolean saveMowingWidth(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "mowingWidth", value);
+ }
+
+ @Override
+ public boolean savePlannedPath(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "plannedPath", value);
+ }
+ };
+
+ // 鏄剧ず璺緞瑙勫垝椤甸潰
+ MowingPathGenerationPage dialog = new MowingPathGenerationPage(
+ owner,
+ dikuai,
+ baseStationValue,
+ boundaryValue,
+ obstacleValue,
+ widthValue,
+ modeValue,
+ existingPath,
+ callback
+ );
+
+ dialog.setVisible(true);
+ }
+
private void generateMowingPath(Dikuai dikuai) {
if (dikuai == null) {
return;
@@ -866,178 +938,48 @@
String modeValue,
String initialGeneratedPath) {
Window owner = SwingUtilities.getWindowAncestor(this);
- JDialog dialog = new JDialog(owner, "鐢熸垚鍓茶崏璺緞", Dialog.ModalityType.APPLICATION_MODAL);
- dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
- dialog.getContentPane().setLayout(new BorderLayout());
- dialog.getContentPane().setBackground(BACKGROUND_COLOR);
-
- JPanel contentPanel = new JPanel();
- contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
- contentPanel.setBackground(BACKGROUND_COLOR);
- contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 16, 12, 16));
-
- String landName = getDisplayValue(dikuai.getLandName(), "鏈煡鍦板潡");
- String landNumber = getDisplayValue(dikuai.getLandNumber(), "鏈煡缂栧彿");
- JLabel headerLabel = new JLabel(landName + " / " + landNumber);
- headerLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 16));
- headerLabel.setForeground(TEXT_COLOR);
- headerLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
- contentPanel.add(headerLabel);
- contentPanel.add(Box.createVerticalStrut(12));
-
- JTextField baseStationField = createInfoTextField(baseStationValue != null ? baseStationValue : "", true);
- contentPanel.add(createTextFieldSection("鍩虹珯鍧愭爣", baseStationField));
-
- JTextArea boundaryArea = createInfoTextArea(boundaryValue != null ? boundaryValue : "", true, 6);
- contentPanel.add(createTextAreaSection("鍦板潡杈圭晫", boundaryArea));
-
- JTextArea obstacleArea = createInfoTextArea(obstacleValue != null ? obstacleValue : "", true, 6);
- contentPanel.add(createTextAreaSection("闅滅鐗╁潗鏍�", obstacleArea));
-
- JTextField widthField = createInfoTextField(widthValue != null ? widthValue : "", true);
- contentPanel.add(createTextFieldSection("鍓茶崏瀹藉害 (鍘樼背)", widthField));
- contentPanel.add(createInfoValueSection("鍓茶崏妯″紡", formatMowingPatternForDialog(modeValue)));
-
- String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath());
- String pathSeed = initialGeneratedPath != null ? initialGeneratedPath : existingPath;
- JTextArea pathArea = createInfoTextArea(pathSeed != null ? pathSeed : "", true, 10);
- contentPanel.add(createTextAreaSection("鍓茶崏璺緞鍧愭爣", pathArea));
-
- JScrollPane dialogScrollPane = new JScrollPane(contentPanel);
- dialogScrollPane.setBorder(BorderFactory.createEmptyBorder());
- dialogScrollPane.getVerticalScrollBar().setUnitIncrement(16);
- dialog.add(dialogScrollPane, BorderLayout.CENTER);
-
- JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 12, 12));
- buttonPanel.setBackground(BACKGROUND_COLOR);
-
- JButton generateBtn = createPrimaryFooterButton("鐢熸垚鍓茶崏璺緞");
- JButton saveBtn = createPrimaryFooterButton("淇濆瓨璺緞");
- JButton cancelBtn = createPrimaryFooterButton("鍙栨秷");
-
- generateBtn.addActionListener(e -> {
- String sanitizedWidth = sanitizeWidthString(widthField.getText());
- if (sanitizedWidth != null) {
- try {
- double widthCm = Double.parseDouble(sanitizedWidth);
- widthField.setText(formatWidthForStorage(widthCm));
- sanitizedWidth = formatWidthForStorage(widthCm);
- } catch (NumberFormatException ex) {
- widthField.setText(sanitizedWidth);
- }
+
+ // 鍒涘缓淇濆瓨鍥炶皟鎺ュ彛瀹炵幇
+ MowingPathGenerationPage.PathSaveCallback callback = new MowingPathGenerationPage.PathSaveCallback() {
+ @Override
+ public boolean saveBaseStationCoordinates(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "baseStationCoordinates", value);
}
- String generated = attemptMowingPathPreview(
- boundaryArea.getText(),
- obstacleArea.getText(),
- sanitizedWidth,
- modeValue,
- dialog,
- true
- );
- if (generated != null) {
- pathArea.setText(generated);
- pathArea.setCaretPosition(0);
+
+ @Override
+ public boolean saveBoundaryCoordinates(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "boundaryCoordinates", value);
}
- });
-
- saveBtn.addActionListener(e -> {
- String baseStationNormalized = normalizeCoordinateInput(baseStationField.getText());
- String boundaryNormalized = normalizeCoordinateInput(boundaryArea.getText());
- if (!"-1".equals(boundaryNormalized)) {
- boundaryNormalized = boundaryNormalized
- .replace("\r\n", ";")
- .replace('\r', ';')
- .replace('\n', ';')
- .replaceAll(";+", ";")
- .replaceAll("\\s*;\\s*", ";")
- .trim();
- if (boundaryNormalized.isEmpty()) {
- boundaryNormalized = "-1";
- }
+
+ @Override
+ public boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue) {
+ return persistObstaclesForLand(dikuai, baseStationValue, obstacleValue);
}
- String obstacleNormalized = normalizeCoordinateInput(obstacleArea.getText());
- if (!"-1".equals(obstacleNormalized)) {
- obstacleNormalized = obstacleNormalized
- .replace("\r\n", " ")
- .replace('\r', ' ')
- .replace('\n', ' ')
- .replaceAll("\\s{2,}", " ")
- .trim();
- if (obstacleNormalized.isEmpty()) {
- obstacleNormalized = "-1";
- }
+
+ @Override
+ public boolean saveMowingWidth(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "mowingWidth", value);
}
- String rawWidthInput = widthField.getText() != null ? widthField.getText().trim() : "";
- String widthSanitized = sanitizeWidthString(widthField.getText());
- if (widthSanitized == null) {
- String message = rawWidthInput.isEmpty() ? "璇峰厛璁剧疆鍓茶崏瀹藉害(鍘樼背)" : "鍓茶崏瀹藉害鏍煎紡涓嶆纭�";
- JOptionPane.showMessageDialog(dialog, message, "鎻愮ず", JOptionPane.WARNING_MESSAGE);
- return;
+
+ @Override
+ public boolean savePlannedPath(Dikuai dikuai, String value) {
+ return saveFieldAndRefresh(dikuai, "plannedPath", value);
}
- double widthCm;
- try {
- widthCm = Double.parseDouble(widthSanitized);
- } catch (NumberFormatException ex) {
- JOptionPane.showMessageDialog(dialog, "鍓茶崏瀹藉害鏍煎紡涓嶆纭�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
- return;
- }
- if (widthCm <= 0) {
- JOptionPane.showMessageDialog(dialog, "鍓茶崏瀹藉害蹇呴』澶т簬0", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
- return;
- }
- String widthNormalized = formatWidthForStorage(widthCm);
- widthField.setText(widthNormalized);
- String pathNormalized = normalizeCoordinateInput(pathArea.getText());
- if (!"-1".equals(pathNormalized)) {
- pathNormalized = pathNormalized
- .replace("\r\n", ";")
- .replace('\r', ';')
- .replace('\n', ';')
- .replaceAll(";+", ";")
- .replaceAll("\\s*;\\s*", ";")
- .trim();
- if (pathNormalized.isEmpty()) {
- pathNormalized = "-1";
- }
- }
- if ("-1".equals(pathNormalized)) {
- JOptionPane.showMessageDialog(dialog, "璇峰厛鐢熸垚鍓茶崏璺緞", "鎻愮ず", JOptionPane.INFORMATION_MESSAGE);
- return;
- }
- if (!saveFieldAndRefresh(dikuai, "baseStationCoordinates", baseStationNormalized)) {
- JOptionPane.showMessageDialog(dialog, "鏃犳硶淇濆瓨鍩虹珯鍧愭爣", "閿欒", JOptionPane.ERROR_MESSAGE);
- return;
- }
- if (!saveFieldAndRefresh(dikuai, "boundaryCoordinates", boundaryNormalized)) {
- JOptionPane.showMessageDialog(dialog, "鏃犳硶淇濆瓨鍦板潡杈圭晫", "閿欒", JOptionPane.ERROR_MESSAGE);
- return;
- }
- if (!persistObstaclesForLand(dikuai, baseStationNormalized, obstacleNormalized)) {
- JOptionPane.showMessageDialog(dialog, "鏃犳硶淇濆瓨闅滅鐗╁潗鏍�", "閿欒", JOptionPane.ERROR_MESSAGE);
- return;
- }
- if (!saveFieldAndRefresh(dikuai, "mowingWidth", widthNormalized)) {
- JOptionPane.showMessageDialog(dialog, "鏃犳硶淇濆瓨鍓茶崏瀹藉害", "閿欒", JOptionPane.ERROR_MESSAGE);
- return;
- }
- if (!saveFieldAndRefresh(dikuai, "plannedPath", pathNormalized)) {
- JOptionPane.showMessageDialog(dialog, "鏃犳硶淇濆瓨鍓茶崏璺緞", "閿欒", JOptionPane.ERROR_MESSAGE);
- return;
- }
- JOptionPane.showMessageDialog(dialog, "鍓茶崏璺緞宸蹭繚瀛�", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
- dialog.dispose();
- });
-
- cancelBtn.addActionListener(e -> dialog.dispose());
-
- buttonPanel.add(generateBtn);
- buttonPanel.add(saveBtn);
- buttonPanel.add(cancelBtn);
- dialog.add(buttonPanel, BorderLayout.SOUTH);
-
- dialog.pack();
- dialog.setSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
- dialog.setLocationRelativeTo(owner);
+ };
+
+ // 浣跨敤鏂扮殑鐙珛椤甸潰绫�
+ MowingPathGenerationPage dialog = new MowingPathGenerationPage(
+ owner,
+ dikuai,
+ baseStationValue,
+ boundaryValue,
+ obstacleValue,
+ widthValue,
+ modeValue,
+ initialGeneratedPath,
+ callback
+ );
+
dialog.setVisible(true);
}
diff --git a/src/lujing/Lunjingguihua.java b/src/lujing/Lunjingguihua.java
index 907a4f7..3e54d7b 100644
--- a/src/lujing/Lunjingguihua.java
+++ b/src/lujing/Lunjingguihua.java
@@ -30,16 +30,18 @@
/**
* 鐢熸垚鍓茶崏璺緞娈靛垪琛ㄣ��
*
- * @param polygonCoords 澶氳竟褰㈣竟鐣屽潗鏍囷紝鏍煎紡濡� "x1,y1;x2,y2;..."锛堢背锛�
+ * @param polygonCoords 澶氳竟褰㈣竟鐣屽潗鏍囷紝鏍煎紡濡� "x1,y1;x2,y2;..."锛堢背锛�
* @param obstaclesCoords 闅滅鐗╁潗鏍囷紝鏀寔澶氫釜鎷彿娈垫垨鍦嗗舰瀹氫箟锛屼緥 "(x1,y1;x2,y2)(cx,cy;px,py)"
- * @param mowingWidth 鍓茶崏瀹藉害瀛楃涓诧紝绫冲崟浣嶏紝鍏佽淇濈暀涓や綅灏忔暟
- * @param modeStr 鍓茶崏妯″紡锛�"0"/绌轰负骞宠绾匡紝"1" 鎴� "spiral" 琛ㄧず铻烘棆妯″紡锛堝綋鍓嶄粎骞宠绾垮疄鐜帮級
+ * @param mowingWidth 鍓茶崏瀹藉害瀛楃涓诧紝绫冲崟浣嶏紝鍏佽淇濈暀涓や綅灏忔暟
+ * @param safetyDistStr 瀹夊叏璺濈瀛楃涓诧紝绫冲崟浣嶃�傝矾寰勫皢涓庤竟鐣屽拰闅滅鐗╀繚鎸佹璺濈銆�
+ * @param modeStr 鍓茶崏妯″紡锛�"0"/绌轰负骞宠绾匡紝"1" 鎴� "spiral" 琛ㄧず铻烘棆妯″紡锛堝綋鍓嶄粎骞宠绾垮疄鐜帮級
* @return 璺緞娈靛垪琛紝鎸夎椹堕『搴忔帓鍒�
*/
public static List<PathSegment> generatePathSegments(String polygonCoords,
- String obstaclesCoords,
- String mowingWidth,
- String modeStr) {
+ String obstaclesCoords,
+ String mowingWidth,
+ String safetyDistStr,
+ String modeStr) {
List<Coordinate> polygon = parseCoordinates(polygonCoords);
if (polygon.size() < 4) {
throw new IllegalArgumentException("澶氳竟褰㈠潗鏍囨暟閲忎笉瓒筹紝鑷冲皯闇�瑕佷笁涓偣");
@@ -49,30 +51,55 @@
if (width <= 0) {
throw new IllegalArgumentException("鍓茶崏瀹藉害蹇呴』澶т簬 0");
}
+
+ // 瑙f瀽瀹夊叏璺濈锛屽鏋滄湭璁剧疆鎴栨棤鏁堬紝榛樿涓� NaN (鍦� PlannerCore 涓鐞嗛粯璁ゅ��)
+ double safetyDistance = parseDoubleOrDefault(safetyDistStr, Double.NaN);
List<List<Coordinate>> obstacles = parseObstacles(obstaclesCoords);
String mode = normalizeMode(modeStr);
- PlannerCore planner = new PlannerCore(polygon, width, mode, obstacles);
+ PlannerCore planner = new PlannerCore(polygon, width, safetyDistance, mode, obstacles);
return planner.generate();
}
/**
+ * 淇濇寔鍚戝悗鍏煎鐨勯噸杞芥柟娉曪紙涓嶅甫 safeDistance锛屼娇鐢ㄩ粯璁よ绠楋級
+ */
+ public static List<PathSegment> generatePathSegments(String polygonCoords,
+ String obstaclesCoords,
+ String mowingWidth,
+ String modeStr) {
+ return generatePathSegments(polygonCoords, obstaclesCoords, mowingWidth, null, modeStr);
+ }
+
+ /**
* 閫氳繃瀛楃涓插弬鏁扮敓鎴愬壊鑽夎矾寰勫潗鏍囥��
*
- * @param polygonCoords 澶氳竟褰㈣竟鐣屽潗鏍囷紝鏍煎紡濡� "x1,y1;x2,y2;..."锛堢背锛�
+ * @param polygonCoords 澶氳竟褰㈣竟鐣屽潗鏍囷紝鏍煎紡濡� "x1,y1;x2,y2;..."锛堢背锛�
* @param obstaclesCoords 闅滅鐗╁潗鏍囷紝鏀寔澶氫釜鎷彿娈垫垨鍦嗗舰瀹氫箟锛屼緥 "(x1,y1;x2,y2)(cx,cy;px,py)"
- * @param mowingWidth 鍓茶崏瀹藉害瀛楃涓诧紝绫冲崟浣嶏紝鍏佽淇濈暀涓や綅灏忔暟
- * @param modeStr 鍓茶崏妯″紡锛�"0"/绌轰负骞宠绾匡紝"1" 鎴� "spiral" 琛ㄧず铻烘棆妯″紡锛堝綋鍓嶄粎骞宠绾垮疄鐜帮級
+ * @param mowingWidth 鍓茶崏瀹藉害瀛楃涓诧紝绫冲崟浣嶏紝鍏佽淇濈暀涓や綅灏忔暟
+ * @param safetyDistStr 瀹夊叏璺濈瀛楃涓诧紝绫冲崟浣嶃��
+ * @param modeStr 鍓茶崏妯″紡锛�"0"/绌轰负骞宠绾匡紝"1" 鎴� "spiral" 琛ㄧず铻烘棆妯″紡锛堝綋鍓嶄粎骞宠绾垮疄鐜帮級
* @return 杩炵画璺緞鍧愭爣瀛楃涓诧紝椤哄簭绱ц窡鍓茶崏鏈鸿杩涜矾绾�
*/
public static String generatePathFromStrings(String polygonCoords,
String obstaclesCoords,
String mowingWidth,
+ String safetyDistStr,
String modeStr) {
- List<PathSegment> segments = generatePathSegments(polygonCoords, obstaclesCoords, mowingWidth, modeStr);
+ List<PathSegment> segments = generatePathSegments(polygonCoords, obstaclesCoords, mowingWidth, safetyDistStr, modeStr);
return formatPathSegments(segments);
}
+
+ /**
+ * 淇濇寔鍚戝悗鍏煎鐨勯噸杞芥柟娉�
+ */
+ public static String generatePathFromStrings(String polygonCoords,
+ String obstaclesCoords,
+ String mowingWidth,
+ String modeStr) {
+ return generatePathFromStrings(polygonCoords, obstaclesCoords, mowingWidth, null, modeStr);
+ }
/**
* 灏嗚矾寰勬鍒楄〃杞崲涓哄潗鏍囧瓧绗︿覆銆�
@@ -168,7 +195,7 @@
try {
return Double.parseDouble(value.trim());
} catch (NumberFormatException ex) {
- throw new IllegalArgumentException("鍓茶崏瀹藉害鏍煎紡涓嶆纭�: " + value, ex);
+ throw new IllegalArgumentException("鏍煎紡涓嶆纭�: " + value, ex);
}
}
@@ -227,7 +254,7 @@
public boolean isStartPoint;
public boolean isEndPoint;
- PathSegment(Coordinate start, Coordinate end, boolean isMowing) {
+ public PathSegment(Coordinate start, Coordinate end, boolean isMowing) {
this.start = start;
this.end = end;
this.isMowing = isMowing;
@@ -251,28 +278,53 @@
/**
* 鍐呴儴鏍稿績瑙勫垝鍣紝瀹炵幇涓� MowingPathPlanner 绛夋晥鐨勯�昏緫銆�
*/
- private static final class PlannerCore {
+ static final class PlannerCore {
private final List<Coordinate> polygon;
private final double width;
+ private final double safetyDistance; // 鏂板瀹夊叏璺濈瀛楁
private final String mode;
private final List<List<Coordinate>> obstacles;
private final GeometryFactory gf = new GeometryFactory();
- PlannerCore(List<Coordinate> polygon, double width, String mode, List<List<Coordinate>> obstacles) {
+ PlannerCore(List<Coordinate> polygon, double width, double safetyDistance, String mode, List<List<Coordinate>> obstacles) {
this.polygon = polygon;
this.width = width;
this.mode = mode;
this.obstacles = obstacles != null ? obstacles : new ArrayList<>();
+
+ // 鍒濆鍖栧畨鍏ㄨ窛绂婚�昏緫
+ if (Double.isNaN(safetyDistance)) {
+ // 濡傛灉鏈彁渚涳紝浣跨敤榛樿鍊硷細瀹藉害鐨勪竴鍗� + 0.05绫�
+ this.safetyDistance = width / 2.0 + 0.05;
+ } else {
+ // 濡傛灉鎻愪緵浜嗭紝浣跨敤鎻愪緵鐨勫�硷紝浣嗚嚦灏戣淇濊瘉鏈哄櫒涓績涓嶇澹侊紙瀹藉害涓�鍗婏級
+ // 鍏佽鐢ㄦ埛璁剧疆姣� width/2 鏇村ぇ鐨勫�兼潵杩滅杈圭晫
+ this.safetyDistance = Math.max(safetyDistance, width / 2.0);
+ }
+ }
+
+ // 鍏煎鏃ф瀯閫犲嚱鏁�
+ PlannerCore(List<Coordinate> polygon, double width, String mode, List<List<Coordinate>> obstacles) {
+ this(polygon, width, Double.NaN, mode, obstacles);
}
List<PathSegment> generate() {
+ // 濡傛灉鏈夐殰纰嶇墿锛屼娇鐢ㄥ甫闅滅鐗╅伩璁╃殑璺緞瑙勫垝鍣�
+ if (!obstacles.isEmpty()) {
+ // 浣跨敤璁$畻濂界殑瀹夊叏璺濈
+ ObstaclePathPlanner obstaclePlanner = new ObstaclePathPlanner(
+ polygon, width, mode, obstacles, this.safetyDistance);
+ return obstaclePlanner.generate();
+ }
+
+ // 娌℃湁闅滅鐗╂椂浣跨敤鍘熸湁閫昏緫
if ("spiral".equals(mode)) {
return generateSpiralPath();
}
return generateParallelPath();
}
- private List<PathSegment> generateParallelPath() {
+ List<PathSegment> generateParallelPath() {
List<PathSegment> path = new ArrayList<>();
Geometry safeArea = buildSafeArea();
if (safeArea == null || safeArea.isEmpty()) {
@@ -285,7 +337,7 @@
longest.end.y - longest.start.y).normalize();
Vector2D perp = baseDir.rotate90CCW();
Vector2D baseStartVec = new Vector2D(longest.start.x, longest.start.y);
- double baseProjection = perp.dot(baseStartVec); // keep offsets relative to the longest edge start
+ double baseProjection = perp.dot(baseStartVec);
double minProj = Double.POSITIVE_INFINITY;
double maxProj = Double.NEGATIVE_INFINITY;
@@ -361,7 +413,7 @@
return path;
}
- private List<PathSegment> generateSpiralPath() {
+ List<PathSegment> generateSpiralPath() {
Geometry safeArea = buildSafeArea();
if (safeArea == null || safeArea.isEmpty()) {
System.err.println("瀹夊叏鍖哄煙涓虹┖锛屾棤娉曠敓鎴愯灪鏃嬭矾寰�");
@@ -418,7 +470,10 @@
}
}
- Geometry shrunk = shrinkStraight(result, width / 2.0);
+ // 淇敼锛氫娇鐢ㄤ紶鍏ョ殑 safetyDistance 鏉ヨ繘琛岃竟鐣屽唴缂�
+ // 涔嬪墠鏄� width / 2.0锛岀幇鍦ㄤ娇鐢� this.safetyDistance
+ // 杩欑‘淇濅簡璺緞瑙勫垝鍖哄煙涓庤竟鐣屼繚鎸佺敤鎴锋寚瀹氱殑璺濈
+ Geometry shrunk = shrinkStraight(result, this.safetyDistance);
return shrunk.isEmpty() ? result : shrunk;
} catch (Exception ex) {
System.err.println("鏋勫缓瀹夊叏鍖哄煙澶辫触: " + ex.getMessage());
@@ -620,4 +675,4 @@
this.index = index;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/lujing/MowingPathGenerationPage.java b/src/lujing/MowingPathGenerationPage.java
new file mode 100644
index 0000000..8f96af2
--- /dev/null
+++ b/src/lujing/MowingPathGenerationPage.java
@@ -0,0 +1,634 @@
+package lujing;
+
+import javax.swing.*;
+import java.awt.*;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+
+import dikuai.Dikuai;
+import lujing.Lunjingguihua;
+import lujing.ObstaclePathPlanner;
+import org.locationtech.jts.geom.Coordinate;
+
+/**
+ * 鐢熸垚鍓茶崏璺緞椤甸潰
+ * 鐙珛鐨勫璇濇绫伙紝鐢ㄤ簬鐢熸垚鍜岀紪杈戝壊鑽夎矾寰�
+ */
+public class MowingPathGenerationPage extends JDialog {
+ private static final long serialVersionUID = 1L;
+
+ // 灏哄甯搁噺
+ private static final int SCREEN_WIDTH = 400;
+ private static final int SCREEN_HEIGHT = 800;
+
+ // 棰滆壊甯搁噺
+ private static final Color PRIMARY_COLOR = new Color(46, 139, 87);
+ private static final Color PRIMARY_DARK = new Color(30, 107, 69);
+ private static final Color TEXT_COLOR = new Color(51, 51, 51);
+ private static final Color WHITE = Color.WHITE;
+ private static final Color BORDER_COLOR = new Color(200, 200, 200);
+ private static final Color BACKGROUND_COLOR = new Color(250, 250, 250);
+
+ // 鏁版嵁淇濆瓨鍥炶皟鎺ュ彛
+ public interface PathSaveCallback {
+ boolean saveBaseStationCoordinates(Dikuai dikuai, String value);
+ boolean saveBoundaryCoordinates(Dikuai dikuai, String value);
+ boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue);
+ boolean saveMowingWidth(Dikuai dikuai, String value);
+ boolean savePlannedPath(Dikuai dikuai, String value);
+ }
+
+ private final Dikuai dikuai;
+ private final PathSaveCallback saveCallback;
+
+ // UI缁勪欢
+ private JTextField baseStationField;
+ private JTextArea boundaryArea;
+ private JTextArea obstacleArea;
+ private JTextField widthField;
+ private JTextArea pathArea;
+
+ /**
+ * 鏋勯�犲嚱鏁�
+ * @param owner 鐖剁獥鍙�
+ * @param dikuai 鍦板潡瀵硅薄
+ * @param baseStationValue 鍩虹珯鍧愭爣
+ * @param boundaryValue 鍦板潡杈圭晫
+ * @param obstacleValue 闅滅鐗╁潗鏍�
+ * @param widthValue 鍓茶崏瀹藉害
+ * @param modeValue 鍓茶崏妯″紡
+ * @param initialGeneratedPath 鍒濆鐢熸垚鐨勮矾寰�
+ * @param saveCallback 淇濆瓨鍥炶皟鎺ュ彛
+ */
+ public MowingPathGenerationPage(Window owner,
+ Dikuai dikuai,
+ String baseStationValue,
+ String boundaryValue,
+ String obstacleValue,
+ String widthValue,
+ String modeValue,
+ String initialGeneratedPath,
+ PathSaveCallback saveCallback) {
+ super(owner, "璺緞瑙勫垝椤甸潰", Dialog.ModalityType.APPLICATION_MODAL);
+ this.dikuai = dikuai;
+ this.saveCallback = saveCallback;
+
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().setBackground(BACKGROUND_COLOR);
+
+ initializeUI(baseStationValue, boundaryValue, obstacleValue,
+ widthValue, modeValue, initialGeneratedPath);
+
+ pack();
+ setSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
+ setLocationRelativeTo(owner);
+ }
+
+ /**
+ * 鍒濆鍖朥I
+ */
+ private void initializeUI(String baseStationValue, String boundaryValue,
+ String obstacleValue, String widthValue,
+ String modeValue, String initialGeneratedPath) {
+ JPanel contentPanel = new JPanel();
+ contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
+ contentPanel.setBackground(BACKGROUND_COLOR);
+ contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 16, 12, 16));
+
+ // 鏍囬
+ String landName = getDisplayValue(dikuai.getLandName(), "鏈煡鍦板潡");
+ String landNumber = getDisplayValue(dikuai.getLandNumber(), "鏈煡缂栧彿");
+ JLabel headerLabel = new JLabel(landName + " / " + landNumber);
+ headerLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 16));
+ headerLabel.setForeground(TEXT_COLOR);
+ headerLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ contentPanel.add(headerLabel);
+ contentPanel.add(Box.createVerticalStrut(12));
+
+ // 鍩虹珯鍧愭爣
+ baseStationField = createInfoTextField(baseStationValue != null ? baseStationValue : "", true);
+ contentPanel.add(createTextFieldSection("鍩虹珯鍧愭爣", baseStationField));
+
+ // 鍦板潡杈圭晫
+ boundaryArea = createInfoTextArea(boundaryValue != null ? boundaryValue : "", true, 6);
+ contentPanel.add(createTextAreaSection("鍦板潡杈圭晫", boundaryArea));
+
+ // 闅滅鐗╁潗鏍�
+ obstacleArea = createInfoTextArea(obstacleValue != null ? obstacleValue : "", true, 6);
+ contentPanel.add(createTextAreaSection("闅滅鐗╁潗鏍�", obstacleArea));
+
+ // 鍓茶崏瀹藉害
+ widthField = createInfoTextField(widthValue != null ? widthValue : "", true);
+ contentPanel.add(createTextFieldSection("鍓茶崏瀹藉害 (鍘樼背)", widthField));
+
+ // 鍓茶崏妯″紡锛堝彧璇绘樉绀猴級
+ contentPanel.add(createInfoValueSection("鍓茶崏妯″紡", formatMowingPatternForDialog(modeValue)));
+
+ // 鍓茶崏璺緞鍧愭爣
+ String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath());
+ String pathSeed = initialGeneratedPath != null ? initialGeneratedPath : existingPath;
+ pathArea = createInfoTextArea(pathSeed != null ? pathSeed : "", true, 10);
+ contentPanel.add(createTextAreaSection("鍓茶崏璺緞鍧愭爣", pathArea));
+
+ JScrollPane dialogScrollPane = new JScrollPane(contentPanel);
+ dialogScrollPane.setBorder(BorderFactory.createEmptyBorder());
+ dialogScrollPane.getVerticalScrollBar().setUnitIncrement(16);
+ add(dialogScrollPane, BorderLayout.CENTER);
+
+ // 鎸夐挳闈㈡澘
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 12, 12));
+ buttonPanel.setBackground(BACKGROUND_COLOR);
+
+ JButton generateBtn = createPrimaryFooterButton("鐢熸垚鍓茶崏璺緞");
+ JButton saveBtn = createPrimaryFooterButton("淇濆瓨璺緞");
+ JButton cancelBtn = createPrimaryFooterButton("鍙栨秷");
+
+ generateBtn.addActionListener(e -> generatePath(modeValue));
+ saveBtn.addActionListener(e -> savePath());
+ cancelBtn.addActionListener(e -> dispose());
+
+ buttonPanel.add(generateBtn);
+ buttonPanel.add(saveBtn);
+ buttonPanel.add(cancelBtn);
+ add(buttonPanel, BorderLayout.SOUTH);
+ }
+
+ /**
+ * 鐢熸垚璺緞
+ */
+ private void generatePath(String modeValue) {
+ String sanitizedWidth = sanitizeWidthString(widthField.getText());
+ if (sanitizedWidth != null) {
+ try {
+ double widthCm = Double.parseDouble(sanitizedWidth);
+ widthField.setText(formatWidthForStorage(widthCm));
+ sanitizedWidth = formatWidthForStorage(widthCm);
+ } catch (NumberFormatException ex) {
+ widthField.setText(sanitizedWidth);
+ }
+ }
+
+ String generated = attemptMowingPathPreview(
+ boundaryArea.getText(),
+ obstacleArea.getText(),
+ sanitizedWidth,
+ modeValue,
+ this,
+ true
+ );
+
+ if (generated != null) {
+ pathArea.setText(generated);
+ pathArea.setCaretPosition(0);
+ }
+ }
+
+ /**
+ * 淇濆瓨璺緞
+ */
+ private void savePath() {
+ String baseStationNormalized = normalizeCoordinateInput(baseStationField.getText());
+ String boundaryNormalized = normalizeCoordinateInput(boundaryArea.getText());
+ if (!"-1".equals(boundaryNormalized)) {
+ boundaryNormalized = boundaryNormalized
+ .replace("\r\n", ";")
+ .replace('\r', ';')
+ .replace('\n', ';')
+ .replaceAll(";+", ";")
+ .replaceAll("\\s*;\\s*", ";")
+ .trim();
+ if (boundaryNormalized.isEmpty()) {
+ boundaryNormalized = "-1";
+ }
+ }
+
+ String obstacleNormalized = normalizeCoordinateInput(obstacleArea.getText());
+ if (!"-1".equals(obstacleNormalized)) {
+ obstacleNormalized = obstacleNormalized
+ .replace("\r\n", " ")
+ .replace('\r', ' ')
+ .replace('\n', ' ')
+ .replaceAll("\\s{2,}", " ")
+ .trim();
+ if (obstacleNormalized.isEmpty()) {
+ obstacleNormalized = "-1";
+ }
+ }
+
+ String rawWidthInput = widthField.getText() != null ? widthField.getText().trim() : "";
+ String widthSanitized = sanitizeWidthString(widthField.getText());
+ if (widthSanitized == null) {
+ String message = rawWidthInput.isEmpty() ? "璇峰厛璁剧疆鍓茶崏瀹藉害(鍘樼背)" : "鍓茶崏瀹藉害鏍煎紡涓嶆纭�";
+ JOptionPane.showMessageDialog(this, message, "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ double widthCm;
+ try {
+ widthCm = Double.parseDouble(widthSanitized);
+ } catch (NumberFormatException ex) {
+ JOptionPane.showMessageDialog(this, "鍓茶崏瀹藉害鏍煎紡涓嶆纭�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ if (widthCm <= 0) {
+ JOptionPane.showMessageDialog(this, "鍓茶崏瀹藉害蹇呴』澶т簬0", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ String widthNormalized = formatWidthForStorage(widthCm);
+ widthField.setText(widthNormalized);
+
+ String pathNormalized = normalizeCoordinateInput(pathArea.getText());
+ if (!"-1".equals(pathNormalized)) {
+ pathNormalized = pathNormalized
+ .replace("\r\n", ";")
+ .replace('\r', ';')
+ .replace('\n', ';')
+ .replaceAll(";+", ";")
+ .replaceAll("\\s*;\\s*", ";")
+ .trim();
+ if (pathNormalized.isEmpty()) {
+ pathNormalized = "-1";
+ }
+ }
+
+ if ("-1".equals(pathNormalized)) {
+ JOptionPane.showMessageDialog(this, "璇峰厛鐢熸垚鍓茶崏璺緞", "鎻愮ず", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+
+ // 璋冪敤鍥炶皟淇濆瓨鏁版嵁
+ if (saveCallback != null) {
+ if (!saveCallback.saveBaseStationCoordinates(dikuai, baseStationNormalized)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶淇濆瓨鍩虹珯鍧愭爣", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ if (!saveCallback.saveBoundaryCoordinates(dikuai, boundaryNormalized)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶淇濆瓨鍦板潡杈圭晫", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ if (!saveCallback.saveObstacleCoordinates(dikuai, baseStationNormalized, obstacleNormalized)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶淇濆瓨闅滅鐗╁潗鏍�", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ if (!saveCallback.saveMowingWidth(dikuai, widthNormalized)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶淇濆瓨鍓茶崏瀹藉害", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ if (!saveCallback.savePlannedPath(dikuai, pathNormalized)) {
+ JOptionPane.showMessageDialog(this, "鏃犳硶淇濆瓨鍓茶崏璺緞", "閿欒", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ }
+
+ JOptionPane.showMessageDialog(this, "鍓茶崏璺緞宸蹭繚瀛�", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ dispose();
+ }
+
+ /**
+ * 灏濊瘯鐢熸垚璺緞棰勮
+ */
+ private String attemptMowingPathPreview(String boundaryInput, String obstacleInput,
+ String widthCmInput, String modeInput,
+ Component parentComponent, boolean showMessages) {
+ String boundary = sanitizeValueOrNull(boundaryInput);
+ if (boundary == null) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "褰撳墠鍦板潡鏈缃竟鐣屽潗鏍囷紝鏃犳硶鐢熸垚璺緞",
+ "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ }
+ return null;
+ }
+
+ String rawWidth = widthCmInput != null ? widthCmInput.trim() : "";
+ String widthStr = sanitizeWidthString(widthCmInput);
+ if (widthStr == null) {
+ if (showMessages) {
+ String message = rawWidth.isEmpty() ? "璇峰厛璁剧疆鍓茶崏瀹藉害(鍘樼背)" : "鍓茶崏瀹藉害鏍煎紡涓嶆纭�";
+ JOptionPane.showMessageDialog(parentComponent, message, "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ }
+ return null;
+ }
+
+ double widthCm;
+ try {
+ widthCm = Double.parseDouble(widthStr);
+ } catch (NumberFormatException ex) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "鍓茶崏瀹藉害鏍煎紡涓嶆纭�",
+ "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ }
+ return null;
+ }
+
+ if (widthCm <= 0) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "鍓茶崏瀹藉害蹇呴』澶т簬0",
+ "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ }
+ return null;
+ }
+
+ double widthMeters = widthCm / 100.0d;
+ String plannerWidth = BigDecimal.valueOf(widthMeters)
+ .setScale(3, RoundingMode.HALF_UP)
+ .stripTrailingZeros()
+ .toPlainString();
+
+ // 妫�鏌ュ師濮嬭緭鍏ユ槸鍚︽湁闅滅鐗╋紙鍦╯anitize涔嬪墠妫�鏌ワ紝閬垮厤涓㈠け淇℃伅锛�
+ String rawObstacleInput = obstacleInput != null ? obstacleInput.trim() : "";
+ boolean hasObstacleInput = !rawObstacleInput.isEmpty() && !"-1".equals(rawObstacleInput);
+
+ String obstacles = sanitizeValueOrNull(obstacleInput);
+ if (obstacles != null) {
+ obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' ');
+ }
+
+ String mode = normalizeExistingMowingPattern(modeInput);
+ try {
+ // 瑙f瀽闅滅鐗╁垪琛�
+ List<List<Coordinate>> obstacleList = Lunjingguihua.parseObstacles(obstacles);
+ if (obstacleList == null) {
+ obstacleList = new ArrayList<>();
+ }
+
+ // 鍒ゆ柇鏄惁鏈夐殰纰嶇墿锛氬彧瑕佸師濮嬭緭鍏ユ湁闅滅鐗╁唴瀹癸紝灏变娇鐢∣bstaclePathPlanner
+ // 鍗充娇瑙f瀽鍚庡垪琛ㄤ负绌猴紝涔熷皾璇曚娇鐢∣bstaclePathPlanner锛堝畠浼氬鐞嗙┖闅滅鐗╁垪琛ㄧ殑鎯呭喌锛�
+ boolean hasObstacles = hasObstacleInput && !obstacleList.isEmpty();
+
+ // 濡傛灉鍘熷杈撳叆鏈夐殰纰嶇墿浣嗚В鏋愬け璐ワ紝缁欏嚭鎻愮ず
+ if (hasObstacleInput && obstacleList.isEmpty()) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent,
+ "闅滅鐗╁潗鏍囨牸寮忓彲鑳戒笉姝g‘锛屽皢灏濊瘯鐢熸垚璺緞銆傚鏋滆矾寰勪笉姝g‘锛岃妫�鏌ラ殰纰嶇墿鍧愭爣鏍煎紡銆�",
+ "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+ }
+ // 浠嶇劧灏濊瘯浣跨敤ObstaclePathPlanner锛屽嵆浣块殰纰嶇墿鍒楄〃涓虹┖
+ // 杩欐牱鑷冲皯鍙互纭繚浣跨敤姝g‘鐨勮矾寰勮鍒掑櫒
+ }
+
+ String generated;
+
+ if (!hasObstacles && !hasObstacleInput) {
+ // 瀹屽叏娌℃湁闅滅鐗╄緭鍏ユ椂锛屼娇鐢↙unjingguihua绫荤殑鏂规硶鐢熸垚璺緞
+ generated = Lunjingguihua.generatePathFromStrings(
+ boundary,
+ obstacles != null ? obstacles : "",
+ plannerWidth,
+ mode
+ );
+ } else {
+ // 鏈夐殰纰嶇墿杈撳叆鏃讹紙鍗充娇瑙f瀽澶辫触锛夛紝浣跨敤ObstaclePathPlanner澶勭悊璺緞鐢熸垚
+ List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary);
+ if (polygon.size() < 4) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "澶氳竟褰㈠潗鏍囨暟閲忎笉瓒筹紝鑷冲皯闇�瑕佷笁涓偣",
+ "閿欒", JOptionPane.ERROR_MESSAGE);
+ }
+ return null;
+ }
+
+ // 鏍规嵁鏄惁鏈夐殰纰嶇墿璁剧疆涓嶅悓鐨勫畨鍏ㄨ窛绂�
+ double safetyDistance;
+ if (!obstacleList.isEmpty()) {
+ // 鏈夐殰纰嶇墿鏃朵娇鐢ㄥ壊鑽夊搴︾殑涓�鍗� + 0.05绫抽澶栧畨鍏ㄨ窛绂�
+ safetyDistance = widthMeters / 2.0 + 0.05;
+ } else {
+ // 闅滅鐗╄В鏋愬け璐ヤ絾杈撳叆瀛樺湪锛屼娇鐢ㄨ緝灏忕殑瀹夊叏璺濈
+ safetyDistance = 0.01;
+ }
+
+ ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner(
+ polygon, widthMeters, mode, obstacleList, safetyDistance);
+ List<Lunjingguihua.PathSegment> segments = pathPlanner.generate();
+ generated = Lunjingguihua.formatPathSegments(segments);
+ }
+
+ String trimmed = generated != null ? generated.trim() : "";
+ if (trimmed.isEmpty()) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "鏈敓鎴愭湁鏁堢殑鍓茶崏璺緞锛岃妫�鏌ュ湴鍧楁暟鎹�",
+ "鎻愮ず", JOptionPane.INFORMATION_MESSAGE);
+ }
+ return null;
+ }
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "鍓茶崏璺緞宸茬敓鎴�",
+ "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+ }
+ return trimmed;
+ } catch (IllegalArgumentException ex) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "鐢熸垚鍓茶崏璺緞澶辫触: " + ex.getMessage(),
+ "閿欒", JOptionPane.ERROR_MESSAGE);
+ }
+ } catch (Exception ex) {
+ if (showMessages) {
+ JOptionPane.showMessageDialog(parentComponent, "鐢熸垚鍓茶崏璺緞鏃跺彂鐢熷紓甯�: " + ex.getMessage(),
+ "閿欒", JOptionPane.ERROR_MESSAGE);
+ }
+ ex.printStackTrace();
+ }
+ return null;
+ }
+
+ // ========== UI杈呭姪鏂规硶 ==========
+
+ private JTextArea createInfoTextArea(String text, boolean editable, int rows) {
+ JTextArea area = new JTextArea(text);
+ area.setEditable(editable);
+ area.setLineWrap(true);
+ area.setWrapStyleWord(true);
+ area.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ area.setRows(Math.max(rows, 2));
+ area.setCaretPosition(0);
+ area.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
+ area.setBackground(editable ? WHITE : new Color(245, 245, 245));
+ return area;
+ }
+
+ private JPanel createTextAreaSection(String title, JTextArea textArea) {
+ JPanel section = new JPanel(new BorderLayout(0, 6));
+ section.setBackground(BACKGROUND_COLOR);
+ section.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ JLabel titleLabel = new JLabel(title);
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+ titleLabel.setForeground(TEXT_COLOR);
+ section.add(titleLabel, BorderLayout.NORTH);
+
+ JScrollPane scrollPane = new JScrollPane(textArea);
+ scrollPane.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
+ scrollPane.getVerticalScrollBar().setUnitIncrement(12);
+ section.add(scrollPane, BorderLayout.CENTER);
+
+ section.setBorder(BorderFactory.createEmptyBorder(4, 0, 12, 0));
+ return section;
+ }
+
+ private JTextField createInfoTextField(String text, boolean editable) {
+ JTextField field = new JTextField(text);
+ field.setEditable(editable);
+ field.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
+ field.setForeground(TEXT_COLOR);
+ field.setBackground(editable ? WHITE : new Color(245, 245, 245));
+ field.setCaretPosition(0);
+ field.setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6));
+ field.setFocusable(true);
+ field.setOpaque(true);
+ return field;
+ }
+
+ private JPanel createTextFieldSection(String title, JTextField textField) {
+ JPanel section = new JPanel(new BorderLayout(0, 6));
+ section.setBackground(BACKGROUND_COLOR);
+ section.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ JLabel titleLabel = new JLabel(title);
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+ titleLabel.setForeground(TEXT_COLOR);
+ section.add(titleLabel, BorderLayout.NORTH);
+
+ JPanel fieldWrapper = new JPanel(new BorderLayout());
+ fieldWrapper.setBackground(textField.isEditable() ? WHITE : new Color(245, 245, 245));
+ fieldWrapper.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
+ fieldWrapper.add(textField, BorderLayout.CENTER);
+ section.add(fieldWrapper, BorderLayout.CENTER);
+
+ section.setBorder(BorderFactory.createEmptyBorder(4, 0, 12, 0));
+ return section;
+ }
+
+ private JPanel createInfoValueSection(String title, String value) {
+ JPanel section = new JPanel();
+ section.setLayout(new BoxLayout(section, BoxLayout.X_AXIS));
+ section.setBackground(BACKGROUND_COLOR);
+ section.setAlignmentX(Component.LEFT_ALIGNMENT);
+ section.setBorder(BorderFactory.createEmptyBorder(4, 0, 4, 0));
+
+ JLabel titleLabel = new JLabel(title + ":");
+ titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+ titleLabel.setForeground(TEXT_COLOR);
+ section.add(titleLabel);
+ section.add(Box.createHorizontalStrut(8));
+
+ JLabel valueLabel = new JLabel(value);
+ valueLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 14));
+ valueLabel.setForeground(TEXT_COLOR);
+ section.add(valueLabel);
+ section.add(Box.createHorizontalGlue());
+ return section;
+ }
+
+ private JButton createPrimaryFooterButton(String text) {
+ JButton button = new JButton(text);
+ button.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
+ button.setBackground(PRIMARY_COLOR);
+ button.setForeground(WHITE);
+ button.setBorder(BorderFactory.createEmptyBorder(6, 12, 6, 12));
+ button.setFocusPainted(false);
+ button.setCursor(new Cursor(Cursor.HAND_CURSOR));
+
+ button.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseEntered(java.awt.event.MouseEvent e) {
+ button.setBackground(PRIMARY_DARK);
+ }
+
+ public void mouseExited(java.awt.event.MouseEvent e) {
+ button.setBackground(PRIMARY_COLOR);
+ }
+ });
+
+ return button;
+ }
+
+ // ========== 鏁版嵁澶勭悊杈呭姪鏂规硶 ==========
+
+ private String getDisplayValue(String value, String defaultValue) {
+ if (value == null || value.equals("-1") || value.trim().isEmpty()) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ private String prepareCoordinateForEditor(String value) {
+ if (value == null) {
+ return "";
+ }
+ String trimmed = value.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ return "";
+ }
+ return trimmed;
+ }
+
+ private String normalizeCoordinateInput(String input) {
+ if (input == null) {
+ return "-1";
+ }
+ String trimmed = input.trim();
+ return trimmed.isEmpty() ? "-1" : trimmed;
+ }
+
+ private String sanitizeValueOrNull(String input) {
+ if (input == null) {
+ return null;
+ }
+ String trimmed = input.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ return null;
+ }
+ return trimmed;
+ }
+
+ private String sanitizeWidthString(String input) {
+ if (input == null) {
+ return null;
+ }
+ String trimmed = input.trim();
+ if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+ return null;
+ }
+ String cleaned = trimmed.replaceAll("[^0-9.+-]", "");
+ return cleaned.isEmpty() ? null : cleaned;
+ }
+
+ private String formatWidthForStorage(double widthCm) {
+ return BigDecimal.valueOf(widthCm)
+ .setScale(2, RoundingMode.HALF_UP)
+ .stripTrailingZeros()
+ .toPlainString();
+ }
+
+ private String formatMowingPatternForDialog(String patternValue) {
+ String sanitized = sanitizeValueOrNull(patternValue);
+ if (sanitized == null) {
+ return "鏈缃�";
+ }
+ String normalized = normalizeExistingMowingPattern(sanitized);
+ if ("parallel".equals(normalized)) {
+ return "骞宠妯″紡 (parallel)";
+ }
+ if ("spiral".equals(normalized)) {
+ return "铻烘棆妯″紡 (spiral)";
+ }
+ return sanitized;
+ }
+
+ private String normalizeExistingMowingPattern(String patternValue) {
+ if (patternValue == null) {
+ return "parallel";
+ }
+ String trimmed = patternValue.trim().toLowerCase();
+ if ("1".equals(trimmed) || "spiral".equals(trimmed)) {
+ return "spiral";
+ }
+ return "parallel";
+ }
+}
+
+
diff --git a/src/lujing/ObstaclePathPlanner.java b/src/lujing/ObstaclePathPlanner.java
new file mode 100644
index 0000000..8c89235
--- /dev/null
+++ b/src/lujing/ObstaclePathPlanner.java
@@ -0,0 +1,536 @@
+package lujing;
+
+import org.locationtech.jts.algorithm.Angle;
+import org.locationtech.jts.geom.*;
+import org.locationtech.jts.operation.distance.DistanceOp;
+import org.locationtech.jts.operation.union.CascadedPolygonUnion;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 闅滅鐗╄矾寰勮鍒掑櫒
+ * * 浼樺寲鏂规锛�
+ * 1. 棰勮鍒掞細鍏堜笉鑰冭檻闅滅鐗╋紝瑙勫垝鍑鸿鐩栧叏鍥剧殑骞宠绾胯矾寰勶紙寮撳瓧褰級銆�
+ * 2. 鍑犱綍鍒囧壊锛氭牴鎹殰纰嶇墿鐨勫鎵╋紙瀹夊叏璺濈锛夊嚑浣曚綋锛屽皢璺緞鍒囨柇锛岀Щ闄よ惤鍦ㄩ殰纰嶇墿鍐呯殑閮ㄥ垎銆�
+ * 3. 鍖哄煙閲嶇粍锛氬皢鍓╀綑鐨勮矾寰勬鎸夎繛閫氭�ц仛绫讳负鑻ュ共涓�滆繛缁尯鍩熲�濓紝姣忎釜鍖哄煙鍐呴儴閫氳繃寮撳瓧褰㈣繛鎺ャ��
+ * 4. 鍏ㄥ眬杩炴帴锛氫娇鐢ㄩ伩闅滅畻娉曪紙A*鍙鍥撅級灏嗚繖浜涘绔嬬殑鍖哄煙涓茶仈璧锋潵銆�
+ * * * 淇闂锛�
+ * 1. 淇璺緞绌胯秺闅滅鐗╃殑闂锛堥�氳繃鏇翠弗鏍肩殑 Interior Intersection 妫�鏌ワ級銆�
+ * 2. 淇璺緞瓒呭嚭鍦板潡杈圭晫鐨勯棶棰橈紙閫氳繃鍔犲叆 Boundary Covers 绾︽潫锛夈��
+ * 3. [鏈淇] 淇 IntersectionMatrix 绫诲瀷鎶ラ敊锛屼娇鐢ㄦ纭殑鐭╅樀鍗曞厓鏍兼鏌ユ浛浠d笉瀛樺湪鐨勬柟娉曘��
+ * * * 浜屾浼樺寲锛堥拡瀵圭敤鎴峰弽棣堬級锛�
+ * 1. 寮曞叆 Tolerance Buffer (鍐呯缉) 鐢ㄤ簬鐩镐氦妫�娴嬶紝瑙e喅鈥滄帴瑙﹀嵆鐩镐氦鈥濆鑷寸殑鍚堟硶杈圭紭璺緞琚鍒ら棶棰樸��
+ * 2. 澧炲己 isLineSafe 鐨勮竟鐣岀害鏉燂紝纭繚璺緞涓ユ牸鍦� Boundary 鍐呴儴銆�
+ * 3. 浼樺寲 findSafePath锛屽鍔犵偣浣嶆牎楠屼笌鍚搁檮锛圫nap锛夛紝闃叉鍥犳诞鐐硅宸鑷寸殑瀵昏矾澶辫触鑰屽洖閫�鍒扮洿绾裤��
+ */
+public class ObstaclePathPlanner {
+ private final List<Coordinate> polygon; // 鍓茶崏鍖哄煙杈圭晫鐐归泦
+ private final double width; // 鍓茶崏瀹藉害
+ private final String mode; // 妯″紡
+ private final List<List<Coordinate>> obstacles; // 闅滅鐗╁垪琛�
+ private final double safetyDistance; // 瀹夊叏璺濈
+ private final GeometryFactory gf = new GeometryFactory();
+
+ // 淇濆瓨鍦板潡鐨勫嚑浣曞舰鐘讹紝鐢ㄤ簬杈圭晫绾︽潫妫�鏌�
+ private Geometry boundaryGeom;
+ // 鐢ㄤ簬妫�娴嬬殑闅滅鐗╁嚑浣曚綋锛堝彲鑳界粡杩囧井璋冿級
+ private Geometry checkObstacleGeom;
+
+ public ObstaclePathPlanner(List<Coordinate> polygon, double width, String mode,
+ List<List<Coordinate>> obstacles, double safetyDistance) {
+ this.polygon = polygon;
+ this.width = width;
+ this.mode = mode;
+ this.obstacles = obstacles != null ? obstacles : new ArrayList<>();
+ this.safetyDistance = safetyDistance;
+
+ initBoundaryGeom();
+ }
+
+ /**
+ * 鍒濆鍖栬竟鐣屽嚑浣曚綋锛岀敤浜庡悗缁殑绾︽潫妫�鏌�
+ */
+ private void initBoundaryGeom() {
+ if (polygon == null || polygon.size() < 3) {
+ this.boundaryGeom = gf.createPolygon();
+ return;
+ }
+ List<Coordinate> closed = new ArrayList<>(polygon);
+ if (!closed.get(0).equals2D(closed.get(closed.size() - 1))) {
+ closed.add(new Coordinate(closed.get(0)));
+ }
+ LinearRing ring = gf.createLinearRing(closed.toArray(new Coordinate[0]));
+ Polygon poly = gf.createPolygon(ring);
+ // 纭繚鍑犱綍鏈夋晥鎬�
+ this.boundaryGeom = poly.isValid() ? poly : poly.buffer(0);
+ }
+
+ /**
+ * 鐢熸垚璺緞鐨勪富鍏ュ彛
+ */
+ public List<Lunjingguihua.PathSegment> generate() {
+ // 1. 鐢熸垚鍒濆鐨勫畬鏁村钩琛岀嚎璺緞锛堝拷鐣ラ殰纰嶇墿锛�
+ List<Lunjingguihua.PathSegment> fullPath = generateFullPathWithoutObstacles();
+ if (fullPath.isEmpty()) return fullPath;
+
+ // 2. 鏋勫缓闅滅鐗╃殑澶栨墿瀹夊叏鍖哄煙 (Buffer)
+ Geometry obstacleBuffer = createObstacleBuffer();
+
+ // 鍒濆鍖栫敤浜庢娴嬬殑鍑犱綍浣擄紙鍐呯缉涓�鐐圭偣锛屽厑璁歌矾寰勮创杈癸級
+ if (!obstacleBuffer.isEmpty()) {
+ this.checkObstacleGeom = obstacleBuffer.buffer(-0.01); // 鍐呯缉1cm锛屽蹇嶆诞鐐硅宸�
+ } else {
+ this.checkObstacleGeom = obstacleBuffer;
+ }
+
+ if (obstacleBuffer.isEmpty()) return fullPath;
+
+ // 3. 鍒囧壊璺緞锛氱Щ闄や笌闅滅鐗╅噸鍙犵殑閮ㄥ垎
+ List<Lunjingguihua.PathSegment> clippedSegments = clipPathWithObstacles(fullPath, obstacleBuffer);
+ if (clippedSegments.isEmpty()) return new ArrayList<>();
+
+ // 4. 閲嶆柊瑙勫垝锛氬皢纰庣墖鍖栫殑绾挎閲嶇粍鎴愯繛缁殑寮撳瓧褰㈣矾寰勶紝骞惰繘琛岄伩闅滆繛鎺�
+ List<Lunjingguihua.PathSegment> finalPath = reorganizeAndConnectPath(clippedSegments, obstacleBuffer);
+
+ // 5. 鍚庡鐞嗭紙鏍囪璧风粓鐐圭瓑锛�
+ postProcessPath(finalPath);
+
+ return finalPath;
+ }
+
+ /**
+ * 姝ラ1锛氳皟鐢ㄦ牳蹇冨簱鐢熸垚鏃犻殰纰嶇殑骞宠绾胯矾寰�
+ */
+ private List<Lunjingguihua.PathSegment> generateFullPathWithoutObstacles() {
+ Lunjingguihua.PlannerCore tempPlanner = new Lunjingguihua.PlannerCore(
+ polygon, width, mode, new ArrayList<>());
+ return tempPlanner.generateParallelPath();
+ }
+
+ /**
+ * 姝ラ2锛氬垱寤烘墍鏈夐殰纰嶇墿鐨勫苟闆� + 瀹夊叏璺濈澶栨墿
+ */
+ private Geometry createObstacleBuffer() {
+ if (obstacles.isEmpty()) return gf.createPolygon();
+
+ List<Geometry> geoms = new ArrayList<>();
+ for (List<Coordinate> obs : obstacles) {
+ if (obs == null || obs.size() < 3) continue;
+ List<Coordinate> closed = new ArrayList<>(obs);
+ if (!closed.get(0).equals2D(closed.get(closed.size() - 1))) {
+ closed.add(new Coordinate(closed.get(0)));
+ }
+ LinearRing ring = gf.createLinearRing(closed.toArray(new Coordinate[0]));
+ Polygon poly = gf.createPolygon(ring);
+ if (!poly.isValid()) poly = (Polygon) poly.buffer(0);
+ geoms.add(poly);
+ }
+
+ if (geoms.isEmpty()) return gf.createPolygon();
+
+ Geometry union = CascadedPolygonUnion.union(geoms);
+ // 瀵瑰悎骞跺悗鐨勯殰纰嶇墿杩涜澶栨墿锛圔uffer锛�
+ Geometry buffered = union.buffer(safetyDistance, 8);
+ return buffered.isValid() ? buffered : buffered.buffer(0);
+ }
+
+ /**
+ * 姝ラ3锛氱敤闅滅鐗╁嚑浣曚綋鍒囧壊璺緞
+ */
+ private List<Lunjingguihua.PathSegment> clipPathWithObstacles(List<Lunjingguihua.PathSegment> fullPath, Geometry obstacleBuffer) {
+ List<Lunjingguihua.PathSegment> validSegments = new ArrayList<>();
+
+ for (Lunjingguihua.PathSegment seg : fullPath) {
+ if (!seg.isMowing) continue;
+
+ LineString line = gf.createLineString(new Coordinate[]{seg.start, seg.end});
+ Geometry diff;
+ try {
+ diff = line.difference(obstacleBuffer);
+ } catch (Exception e) {
+ continue;
+ }
+
+ if (diff.isEmpty()) continue;
+
+ for (int i = 0; i < diff.getNumGeometries(); i++) {
+ Geometry g = diff.getGeometryN(i);
+ if (g instanceof LineString) {
+ Coordinate[] coords = g.getCoordinates();
+ for (int k = 0; k < coords.length - 1; k++) {
+ Coordinate p1 = coords[k];
+ Coordinate p2 = coords[k+1];
+ if (p1.distance(p2) > 0.1) {
+ validSegments.add(new Lunjingguihua.PathSegment(p1, p2, true));
+ }
+ }
+ }
+ }
+ }
+ return validSegments;
+ }
+
+ /**
+ * 姝ラ4锛氭牳蹇冮噸缁勯�昏緫
+ */
+ private List<Lunjingguihua.PathSegment> reorganizeAndConnectPath(List<Lunjingguihua.PathSegment> segments, Geometry obstacleBuffer) {
+ if (segments.isEmpty()) return new ArrayList<>();
+
+ // --- A. 鍒嗘瀽鎵弿绾挎柟鍚� ---
+ Lunjingguihua.PathSegment firstSeg = segments.get(0);
+ double angle = Math.atan2(firstSeg.end.y - firstSeg.start.y, firstSeg.end.x - firstSeg.start.x);
+ double sin = Math.sin(angle);
+ double cos = Math.cos(angle);
+
+ // --- B. 缁欐瘡涓嚎娈靛垎閰嶆壂鎻忕嚎绱㈠紩 (Grid Index) ---
+ double gridStep = width;
+
+ class IndexedSegment {
+ final Lunjingguihua.PathSegment segment;
+ final int gridIndex;
+ final double projectVal;
+
+ IndexedSegment(Lunjingguihua.PathSegment s) {
+ this.segment = s;
+ double cx = (s.start.x + s.end.x) / 2;
+ double cy = (s.start.y + s.end.y) / 2;
+ double perpDist = -cx * sin + cy * cos;
+ this.gridIndex = (int) Math.floor(perpDist / gridStep);
+ this.projectVal = cx * cos + cy * sin;
+ }
+ }
+
+ List<IndexedSegment> indexedSegments = segments.stream()
+ .map(IndexedSegment::new)
+ .sorted(Comparator.comparingInt((IndexedSegment s) -> s.gridIndex)
+ .thenComparingDouble(s -> s.projectVal))
+ .collect(Collectors.toList());
+
+ // --- C. 鏋勫缓鈥滃尯鍩熼摼鈥� (Zones) ---
+ List<List<Lunjingguihua.PathSegment>> zones = new ArrayList<>();
+ Set<IndexedSegment> visited = new HashSet<>();
+
+ while (visited.size() < indexedSegments.size()) {
+ IndexedSegment startNode = null;
+ for (IndexedSegment is : indexedSegments) {
+ if (!visited.contains(is)) {
+ startNode = is;
+ break;
+ }
+ }
+ if (startNode == null) break;
+
+ List<Lunjingguihua.PathSegment> zone = new ArrayList<>();
+ zone.add(startNode.segment);
+ visited.add(startNode);
+
+ IndexedSegment current = startNode;
+ boolean lookingForNext = true;
+
+ while (lookingForNext) {
+ IndexedSegment bestNext = null;
+ double minDistance = Double.MAX_VALUE;
+
+ // 鎼滅储鏈�浣冲悗缁嚎娈�
+ for (int i = 0; i < indexedSegments.size(); i++) {
+ IndexedSegment candidate = indexedSegments.get(i);
+ if (visited.contains(candidate)) continue;
+
+ if (Math.abs(candidate.gridIndex - current.gridIndex) > 1) continue;
+
+ double d = current.segment.end.distance(candidate.segment.start);
+
+ if (d > width * 3.0) continue;
+
+ if (d < minDistance) {
+ // 浣跨敤 checkObstacleGeom 杩涜妫�娴�
+ if (isLineSafe(current.segment.end, candidate.segment.start)) {
+ minDistance = d;
+ bestNext = candidate;
+ }
+ }
+ }
+
+ if (bestNext != null) {
+ zone.add(bestNext.segment);
+ visited.add(bestNext);
+ current = bestNext;
+ } else {
+ lookingForNext = false;
+ }
+ }
+ zones.add(zone);
+ }
+
+ // --- D. 杩炴帴鎵�鏈� Zones ---
+ List<Lunjingguihua.PathSegment> resultPath = new ArrayList<>();
+
+ List<List<Lunjingguihua.PathSegment>> remainingZones = new ArrayList<>(zones);
+ List<Lunjingguihua.PathSegment> currentProcessingZone = remainingZones.remove(0);
+
+ addZoneToPath(resultPath, currentProcessingZone, obstacleBuffer, false);
+
+ while (!remainingZones.isEmpty()) {
+ Lunjingguihua.PathSegment lastSeg = resultPath.get(resultPath.size() - 1);
+ Coordinate currentPos = lastSeg.end;
+
+ int bestZoneIdx = -1;
+ double minDist = Double.MAX_VALUE;
+
+ for (int i = 0; i < remainingZones.size(); i++) {
+ List<Lunjingguihua.PathSegment> z = remainingZones.get(i);
+ if (z.isEmpty()) continue;
+ double d = currentPos.distance(z.get(0).start);
+ if (d < minDist) {
+ minDist = d;
+ bestZoneIdx = i;
+ }
+ }
+
+ if (bestZoneIdx != -1) {
+ List<Lunjingguihua.PathSegment> nextZone = remainingZones.remove(bestZoneIdx);
+ addZoneToPath(resultPath, nextZone, obstacleBuffer, true);
+ } else {
+ break;
+ }
+ }
+
+ return resultPath;
+ }
+
+ /**
+ * 灏嗕竴涓� Zone 娣诲姞鍒扮粨鏋滆矾寰勪腑
+ */
+ private void addZoneToPath(List<Lunjingguihua.PathSegment> path,
+ List<Lunjingguihua.PathSegment> zone,
+ Geometry obstacleBuffer,
+ boolean needConnectToZoneStart) {
+ if (zone.isEmpty()) return;
+
+ // 1. 杩炴帴鍒� Zone 鐨勮捣鐐�
+ if (needConnectToZoneStart && !path.isEmpty()) {
+ Coordinate from = path.get(path.size() - 1).end;
+ Coordinate to = zone.get(0).start;
+ List<Coordinate> travel = findSafePath(from, to, obstacleBuffer);
+ if (travel.size() > 1) {
+ for (int i = 0; i < travel.size() - 1; i++) {
+ path.add(new Lunjingguihua.PathSegment(travel.get(i), travel.get(i+1), false));
+ }
+ }
+ }
+
+ // 2. 澶勭悊 Zone 鍐呴儴
+ for (int i = 0; i < zone.size(); i++) {
+ Lunjingguihua.PathSegment seg = zone.get(i);
+
+ if (i > 0) {
+ Coordinate prevEnd = zone.get(i-1).end;
+ Coordinate currStart = seg.start;
+ if (!prevEnd.equals2D(currStart)) {
+ if (isLineSafe(prevEnd, currStart)) {
+ path.add(new Lunjingguihua.PathSegment(prevEnd, currStart, false));
+ } else {
+ List<Coordinate> detour = findSafePath(prevEnd, currStart, obstacleBuffer);
+ if (detour.size() > 1) {
+ for (int k = 0; k < detour.size() - 1; k++) {
+ path.add(new Lunjingguihua.PathSegment(detour.get(k), detour.get(k+1), false));
+ }
+ }
+ }
+ }
+ }
+ path.add(seg);
+ }
+ }
+
+ /**
+ * 妫�鏌ヤ袱鐐硅繛绾挎槸鍚﹀畨鍏�
+ * 淇敼鐐癸細
+ * 1. 涓ユ牸妫�鏌� Boundary (Covers)
+ * 2. 浣跨敤 checkObstacleGeom (鍐呯缉鐗�) 妫�鏌ラ殰纰嶇墿锛屽厑璁歌创杈�
+ * 3. [Fix] 浣跨敤 matrix.get() 浠f浛涓嶅瓨鍦ㄧ殑 isIntersects(int)
+ */
+ private boolean isLineSafe(Coordinate p1, Coordinate p2) {
+ if (p1.equals2D(p2)) return true;
+ LineString line = gf.createLineString(new Coordinate[]{p1, p2});
+
+ // 1. 杈圭晫绾︽潫锛氱嚎娈靛繀椤诲畬鍏ㄥ湪鍦板潡鍐呴儴
+ if (boundaryGeom != null && !boundaryGeom.covers(line)) {
+ return false;
+ }
+
+ // 2. 閬块殰绾︽潫锛氫娇鐢ㄥ唴缂╁悗鐨� buffer 妫�鏌�
+ // 濡傛灉 checkObstacleGeom 涓虹┖锛堟棤闅滅锛夛紝鍒欏畨鍏�
+ if (checkObstacleGeom == null || checkObstacleGeom.isEmpty()) return true;
+
+ IntersectionMatrix matrix = line.relate(checkObstacleGeom);
+
+ // 鎴戜滑瑕佹鏌ワ細绾挎鐨勪换浣曢儴鍒嗭紙Interior锛夋槸鍚︾┛杩囬殰纰嶇墿鍐呴儴锛圛nterior锛�
+ // 鎴栬�� 绾挎鐨勭鐐癸紙Boundary锛夋槸鍚﹀湪闅滅鐗╁唴閮紙Interior锛�
+ // 濡傛灉涓よ�呴兘鏄� Dimension.FALSE (-1)锛屽垯璇存槑娌℃湁绌胯繃鍐呴儴
+
+ boolean interiorIntersects = matrix.get(Location.INTERIOR, Location.INTERIOR) != Dimension.FALSE;
+ boolean boundaryIntersects = matrix.get(Location.BOUNDARY, Location.INTERIOR) != Dimension.FALSE;
+
+ return !interiorIntersects && !boundaryIntersects;
+ }
+
+ /**
+ * 瀵绘壘涓ょ偣闂寸殑瀹夊叏璺緞
+ * 淇敼鐐癸細
+ * 1. 澧炲姞鐐逛綅鏍¢獙涓庡惛闄勶紙Snap锛夛紝纭繚璧风偣缁堢偣鏈夋晥
+ * 2. 绉婚櫎鐩寸嚎寮哄埗鍥為��锛岃嫢瀵昏矾澶辫触鍒欒繑鍥炵┖锛堟垨淇濈暀璧风偣锛夛紝閬垮厤绌垮
+ */
+ private List<Coordinate> findSafePath(Coordinate start, Coordinate end, Geometry obstacleBuffer) {
+ // 0. 鏁版嵁娓呮礂锛氬惛闄勮捣鐐圭粓鐐瑰埌鍚堟硶鍖哄煙
+ Coordinate safeStart = snapPointToValid(start);
+ Coordinate safeEnd = snapPointToValid(end);
+
+ List<Coordinate> path = new ArrayList<>();
+
+ // 1. 灏濊瘯鐩磋繛
+ if (isLineSafe(safeStart, safeEnd)) {
+ path.add(safeStart);
+ path.add(safeEnd);
+ return path;
+ }
+
+ // 2. 鏋勫缓鍙鍥�
+ Set<Coordinate> nodes = new HashSet<>();
+ nodes.add(safeStart);
+ nodes.add(safeEnd);
+
+ // 鎻愬彇闅滅鐗╅《鐐�
+ addPolygonVertices(obstacleBuffer, nodes);
+ // 鎻愬彇杈圭晫椤剁偣锛堝叧閿細澶勭悊鍑瑰舰杈圭晫锛�
+ addPolygonVertices(boundaryGeom, nodes);
+
+ List<Coordinate> nodeList = new ArrayList<>(nodes);
+
+ // 鏋勫缓閭绘帴琛�
+ Map<Coordinate, List<Coordinate>> graph = new HashMap<>();
+ for (Coordinate c1 : nodeList) {
+ for (Coordinate c2 : nodeList) {
+ if (c1 == c2) continue;
+ if (isLineSafe(c1, c2)) {
+ graph.computeIfAbsent(c1, k -> new ArrayList<>()).add(c2);
+ }
+ }
+ }
+
+ // Dijkstra 瀵昏矾
+ Map<Coordinate, Double> dist = new HashMap<>();
+ Map<Coordinate, Coordinate> prev = new HashMap<>();
+ PriorityQueue<Coordinate> pq = new PriorityQueue<>(Comparator.comparingDouble(dist::get));
+
+ for (Coordinate n : nodeList) dist.put(n, Double.MAX_VALUE);
+ dist.put(safeStart, 0.0);
+ pq.add(safeStart);
+
+ while (!pq.isEmpty()) {
+ Coordinate u = pq.poll();
+ if (u.equals2D(safeEnd)) break;
+ if (dist.get(u) == Double.MAX_VALUE) break;
+
+ if (graph.containsKey(u)) {
+ for (Coordinate v : graph.get(u)) {
+ double alt = dist.get(u) + u.distance(v);
+ if (alt < dist.get(v)) {
+ dist.put(v, alt);
+ prev.put(v, u);
+ pq.add(v);
+ }
+ }
+ }
+ }
+
+ // 閲嶆瀯璺緞
+ if (prev.containsKey(safeEnd)) {
+ LinkedList<Coordinate> p = new LinkedList<>();
+ Coordinate curr = safeEnd;
+ while (curr != null) {
+ p.addFirst(curr);
+ curr = prev.get(curr);
+ }
+ return p;
+ }
+
+ // 瀵昏矾澶辫触锛岃繑鍥炵┖锛堥伩鍏嶉敊璇殑鐩寸嚎绌胯秺锛�
+ return path;
+ }
+
+ // 杈呭姪锛氶獙璇佸苟鍚搁檮鐐瑰埌鍚堟硶鍖哄煙
+ private Coordinate snapPointToValid(Coordinate p) {
+ Point point = gf.createPoint(p);
+ boolean inBoundary = (boundaryGeom == null) || boundaryGeom.covers(point);
+ boolean outObstacle = (checkObstacleGeom == null) || !checkObstacleGeom.contains(point); // 浣跨敤 contains 鑰屼笉鏄� intersects interior锛岀◢寰弗鏍肩偣
+
+ if (inBoundary && outObstacle) return p;
+
+ // 濡傛灉鐐规棤鏁堬紝灏濊瘯鎵炬渶杩戠殑鏈夋晥鐐癸紙杈圭晫鎴栭殰纰嶇墿杈圭紭锛�
+ // 杩欓噷绠�鍖栧鐞嗭細濡傛灉鍦ㄩ殰纰嶇墿鍐咃紝鍚搁檮鍒伴殰纰嶇墿杈圭晫锛涘鏋滃湪杈圭晫澶栵紝鍚搁檮鍒拌竟鐣�
+ // 瀹為檯涓� JTS DistanceOp.nearestPoints 鍙互鍋氳繖涓�
+
+ Geometry target = boundaryGeom;
+ if (!outObstacle && checkObstacleGeom != null) {
+ // 鍦ㄩ殰纰嶇墿鍐咃紝浼樺厛鍚搁檮鍑洪殰纰嶇墿
+ Coordinate[] nearest = DistanceOp.nearestPoints(point, checkObstacleGeom.getBoundary());
+ return nearest[1];
+ }
+
+ if (!inBoundary && boundaryGeom != null) {
+ Coordinate[] nearest = DistanceOp.nearestPoints(point, boundaryGeom);
+ return nearest[1];
+ }
+
+ return p;
+ }
+
+ private void addPolygonVertices(Geometry geom, Set<Coordinate> nodes) {
+ if (geom == null) return;
+ if (geom instanceof Polygon) {
+ Collections.addAll(nodes, ((Polygon) geom).getExteriorRing().getCoordinates());
+ for(int i=0; i<((Polygon)geom).getNumInteriorRing(); i++) {
+ Collections.addAll(nodes, ((Polygon)geom).getInteriorRingN(i).getCoordinates());
+ }
+ } else if (geom instanceof MultiPolygon) {
+ MultiPolygon mp = (MultiPolygon) geom;
+ for (int i = 0; i < mp.getNumGeometries(); i++) {
+ addPolygonVertices(mp.getGeometryN(i), nodes);
+ }
+ } else if (geom instanceof GeometryCollection) {
+ GeometryCollection gc = (GeometryCollection) geom;
+ for (int i = 0; i < gc.getNumGeometries(); i++) {
+ addPolygonVertices(gc.getGeometryN(i), nodes);
+ }
+ }
+ }
+
+ /**
+ * 鍚庡鐞嗭細绉婚櫎鐭嚎娈碉紝鏍囪璧风粓鐐�
+ */
+ private void postProcessPath(List<Lunjingguihua.PathSegment> path) {
+ if (path.isEmpty()) return;
+ path.removeIf(seg -> seg.start.distance(seg.end) < 0.05);
+ for (Lunjingguihua.PathSegment seg : path) {
+ seg.isStartPoint = false;
+ seg.isEndPoint = false;
+ }
+ boolean startFound = false;
+ for (Lunjingguihua.PathSegment seg : path) {
+ if (seg.isMowing) {
+ seg.setAsStartPoint();
+ startFound = true;
+ break;
+ }
+ }
+ for (int i = path.size() - 1; i >= 0; i--) {
+ Lunjingguihua.PathSegment seg = path.get(i);
+ if (seg.isMowing) {
+ seg.setAsEndPoint();
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/zhangaiwu/Obstacledraw.java b/src/zhangaiwu/Obstacledraw.java
index e5d3223..3125dc7 100644
--- a/src/zhangaiwu/Obstacledraw.java
+++ b/src/zhangaiwu/Obstacledraw.java
@@ -10,21 +10,28 @@
/**
* 闅滅鐗╃粯鍒剁被 - 璐熻矗缁樺埗鍦板潡涓殑闅滅鐗�
+ *
+ * 娉ㄦ剰锛氶殰纰嶇墿鍥惧眰闇�瑕佸浜庡湴鍧楀拰瀵艰埅璺緞涓婃柟銆�
+ * 鍦� MapRenderer.renderMap() 涓紝缁樺埗椤哄簭搴斾负锛�
+ * 1. 鍦板潡杈圭晫锛堝簳灞傦級
+ * 2. 瀵艰埅璺緞锛堜腑灞傦級
+ * 3. 闅滅鐗╋紙椤跺眰锛屾樉绀哄湪鍦板潡鍜屽鑸矾寰勪笂鏂癸級
*/
public class Obstacledraw {
// 棰滆壊瀹氫箟
- private static final Color CIRCLE_FILL_COLOR = new Color(255, 182, 193, 120); // 鍦嗗舰濉厖鑹� - 娴呯矇绾�
+ private static final Color CIRCLE_FILL_COLOR = new Color(128, 128, 128, 128); // 鍦嗗舰濉厖鑹� - 鐏拌壊锛岄�忔槑搴�50%
private static final Color CIRCLE_BORDER_COLOR = new Color(199, 21, 133); // 鍦嗗舰杈规鑹� - 娣辩矇绾�
- private static final Color POLYGON_FILL_COLOR = new Color(173, 216, 230, 120); // 澶氳竟褰㈠~鍏呰壊 - 娴呰摑
+ private static final Color POLYGON_FILL_COLOR = new Color(128, 128, 128, 128); // 澶氳竟褰㈠~鍏呰壊 - 鐏拌壊锛岄�忔槑搴�50%
private static final Color POLYGON_BORDER_COLOR = new Color(25, 25, 112); // 澶氳竟褰㈣竟妗嗚壊 - 娣辫摑
private static final Color OBSTACLE_LABEL_COLOR = Color.BLACK;
private static final Color OBSTACLE_POINT_COLOR = Color.RED;
// 灏哄瀹氫箟
private static final double OBSTACLE_POINT_SIZE = 0.1; // 闅滅鐗╂帶鍒剁偣澶у皬锛堢背锛�
- private static final float DEFAULT_BORDER_WIDTH = 1.0f;
- private static final float SELECTED_BORDER_WIDTH = 2.5f;
+ // 杈圭晫绾垮搴︿笌鍦板潡杈圭晫绾垮搴︿竴鑷达細3 / Math.max(0.5, scale)
+ private static final float BOUNDARY_STROKE_BASE = 3.0f; // 涓庡湴鍧楄竟鐣岀嚎瀹藉害涓�鑷�
+ private static final float SELECTED_WIDTH_MULTIPLIER = 1.5f; // 閫変腑鏃剁殑瀹藉害鍊嶆暟
/**
* 缁樺埗鍦板潡鐨勬墍鏈夐殰纰嶇墿
@@ -109,13 +116,15 @@
g2d.setColor(CIRCLE_FILL_COLOR);
g2d.fill(circle);
- // 璁剧疆杈规棰滆壊鍜屽搴�
+ // 璁剧疆杈规棰滆壊鍜屽搴︼紙涓庡湴鍧楄竟鐣岀嚎瀹藉害涓�鑷达級
+ float strokeWidth = (float)(BOUNDARY_STROKE_BASE / Math.max(0.5, scale));
if (isSelected) {
g2d.setColor(CIRCLE_BORDER_COLOR.darker());
- g2d.setStroke(new BasicStroke(SELECTED_BORDER_WIDTH / (float)scale));
+ // 閫変腑鏃剁◢寰姞绮�
+ g2d.setStroke(new BasicStroke(strokeWidth * SELECTED_WIDTH_MULTIPLIER));
} else {
g2d.setColor(CIRCLE_BORDER_COLOR);
- g2d.setStroke(new BasicStroke(DEFAULT_BORDER_WIDTH / (float)scale));
+ g2d.setStroke(new BasicStroke(strokeWidth));
}
g2d.draw(circle);
@@ -153,13 +162,15 @@
g2d.setColor(POLYGON_FILL_COLOR);
g2d.fill(polygon);
- // 璁剧疆杈规棰滆壊鍜屽搴�
+ // 璁剧疆杈规棰滆壊鍜屽搴︼紙涓庡湴鍧楄竟鐣岀嚎瀹藉害涓�鑷达級
+ float strokeWidth = (float)(BOUNDARY_STROKE_BASE / Math.max(0.5, scale));
if (isSelected) {
g2d.setColor(POLYGON_BORDER_COLOR.darker());
- g2d.setStroke(new BasicStroke(SELECTED_BORDER_WIDTH / (float)scale));
+ // 閫変腑鏃剁◢寰姞绮�
+ g2d.setStroke(new BasicStroke(strokeWidth * SELECTED_WIDTH_MULTIPLIER));
} else {
g2d.setColor(POLYGON_BORDER_COLOR);
- g2d.setStroke(new BasicStroke(DEFAULT_BORDER_WIDTH / (float)scale));
+ g2d.setStroke(new BasicStroke(strokeWidth));
}
g2d.draw(polygon);
@@ -202,6 +213,7 @@
/**
* 缁樺埗闅滅鐗╂爣绛�
+ * 鏂囧瓧澶у皬涓�"缂╂斁"鏂囧瓧涓�鑷达紙11鍙峰瓧浣擄級锛屼笖涓嶉殢鍦板浘缂╂斁鍙樺寲
*/
private static void drawObstacleLabel(Graphics2D g2d, Obstacledge.Obstacle obstacle,
double scale) {
@@ -210,6 +222,9 @@
return;
}
+ // 淇濆瓨褰撳墠鍙樻崲
+ AffineTransform originalTransform = g2d.getTransform();
+
double centerX;
double centerY;
@@ -224,6 +239,14 @@
centerY = centroid.y;
}
+ // 灏嗕笘鐣屽潗鏍囪浆鎹负灞忓箷鍧愭爣
+ Point2D.Double worldPoint = new Point2D.Double(centerX, centerY);
+ Point2D.Double screenPoint = new Point2D.Double();
+ originalTransform.transform(worldPoint, screenPoint);
+
+ // 鎭㈠鍘熷鍙樻崲浠ヤ娇鐢ㄥ睆骞曞潗鏍囩粯鍒�
+ g2d.setTransform(new AffineTransform());
+
// 鑾峰彇闅滅鐗╁悕绉�
String obstacleName = obstacle.getObstacleName();
if (obstacleName == null || obstacleName.trim().isEmpty()) {
@@ -232,26 +255,24 @@
obstacleName = obstacleName.trim();
}
- // 璁剧疆瀛椾綋鍜岄鑹�
+ // 璁剧疆瀛椾綋鍜岄鑹诧紙涓�"缂╂斁"鏂囧瓧涓�鑷达級
g2d.setColor(OBSTACLE_LABEL_COLOR);
-
- // 鏍规嵁缂╂斁姣斾緥璋冩暣瀛椾綋澶у皬
- int fontSize = (int)(10 / scale);
- fontSize = Math.max(8, Math.min(fontSize, 14)); // 闄愬埗瀛椾綋澶у皬鑼冨洿
-
- g2d.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, fontSize));
+ g2d.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 11)); // 涓�"缂╂斁"鏂囧瓧澶у皬涓�鑷�
// 缁樺埗鏍囩
- String label = obstacleName;
+ String label = obstacleName;
FontMetrics metrics = g2d.getFontMetrics();
int textWidth = metrics.stringWidth(label);
int textHeight = metrics.getHeight();
- // 鍦ㄤ腑蹇冪偣缁樺埗鏍囩
- int textX = (int)(centerX - textWidth / 2.0);
- int textY = (int)(centerY + textHeight / 4.0); // 绋嶅井鍚戜笅鍋忕Щ
+ // 鍦ㄥ睆骞曞潗鏍囦腑蹇冪偣缁樺埗鏍囩
+ int textX = (int)(screenPoint.x - textWidth / 2.0);
+ int textY = (int)(screenPoint.y + textHeight / 4.0); // 绋嶅井鍚戜笅鍋忕Щ
g2d.drawString(label, textX, textY);
+
+ // 鎭㈠鍘熷鍙樻崲
+ g2d.setTransform(originalTransform);
}
private static Point2D.Double computePolygonCentroid(List<Obstacledge.XYCoordinate> xyCoords) {
diff --git a/src/zhuye/LegendDialog.java b/src/zhuye/LegendDialog.java
index 7469b67..096e239 100644
--- a/src/zhuye/LegendDialog.java
+++ b/src/zhuye/LegendDialog.java
@@ -32,29 +32,6 @@
mainPanel.setBackground(Color.WHITE);
mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 10, 15));
- // 璁$畻鍥句緥鍐呭闈㈡澘鐨勫搴︼紙鐢ㄤ簬璁剧疆鍥炬爣灏哄锛�
- // 鍥句緥瀵硅瘽妗嗗搴� = DIALOG_WIDTH * 0.8
- // 涓婚潰鏉垮乏鍙宠竟妗嗗悇15鍍忕礌锛屽浘渚嬪唴瀹归潰鏉垮乏鍙冲唴杈硅窛鍚�10鍍忕礌
- int adjustedWidth = (int) Math.round(UIConfig.DIALOG_WIDTH * 0.8);
- int iconSize = adjustedWidth - 30 - 20; // 鍑忓幓涓婚潰鏉垮乏鍙宠竟妗�(15*2)鍜屽浘渚嬪唴瀹归潰鏉垮乏鍙冲唴杈硅窛(10*2)
-
- // 鍒涘缓鍓茶崏鏈哄浘鏍囬潰鏉�
- JPanel iconPanel = new JPanel(new BorderLayout());
- iconPanel.setBackground(Color.WHITE);
- iconPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); // 搴曢儴闂磋窛10鍍忕礌
-
- JLabel gecaojiLabel = new JLabel();
- gecaojiLabel.setHorizontalAlignment(SwingConstants.CENTER);
- ImageIcon gecaojiIcon = loadIcon("image/gecaoji.png", iconSize, iconSize);
- if (gecaojiIcon != null) {
- gecaojiLabel.setIcon(gecaojiIcon);
- } else {
- // 濡傛灉鍥炬爣鍔犺浇澶辫触锛屾樉绀哄崰浣嶆枃鏈�
- gecaojiLabel.setText("鍓茶崏鏈哄浘鏍�");
- gecaojiLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
- }
- iconPanel.add(gecaojiLabel, BorderLayout.CENTER);
-
// 鍥句緥鍐呭闈㈡澘 - 鐩存帴娣诲姞锛屼笉浣跨敤婊氬姩鏉�
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
@@ -84,8 +61,7 @@
contentPanel.remove(contentPanel.getComponentCount() - 1);
}
- // 娣诲姞鍥炬爣闈㈡澘鍜屽浘渚嬪唴瀹归潰鏉�
- mainPanel.add(iconPanel, BorderLayout.NORTH);
+ // 娣诲姞鍥句緥鍐呭闈㈡澘
mainPanel.add(contentPanel, BorderLayout.CENTER);
getContentPane().add(mainPanel);
diff --git a/src/zhuye/MapRenderer.java b/src/zhuye/MapRenderer.java
index 8406d2b..e36d3c3 100644
--- a/src/zhuye/MapRenderer.java
+++ b/src/zhuye/MapRenderer.java
@@ -346,14 +346,11 @@
boolean hasPlannedPath = currentPlannedPath != null && currentPlannedPath.size() >= 2;
boolean hasObstacles = currentObstacles != null && !currentObstacles.isEmpty();
+ // 缁樺埗鍦板潡杈圭晫锛堝簳灞傦級
if (hasBoundary) {
drawCurrentBoundary(g2d);
}
- if (hasObstacles) {
- Obstacledraw.drawObstacles(g2d, currentObstacles, scale, selectedObstacleName);
- }
-
yulanzhangaiwu.renderPreview(g2d, scale);
if (!circleSampleMarkers.isEmpty()) {
@@ -366,10 +363,16 @@
adddikuaiyulan.drawPreview(g2d, handheldBoundaryPreview, scale, handheldBoundaryPreviewActive, boundaryPreviewMarkerScale);
+ // 缁樺埗瀵艰埅璺緞锛堜腑灞傦級
if (hasPlannedPath) {
drawCurrentPlannedPath(g2d);
}
+ // 缁樺埗闅滅鐗╋紙椤跺眰锛屾樉绀哄湪鍦板潡鍜屽鑸矾寰勪笂鏂癸級
+ if (hasObstacles) {
+ Obstacledraw.drawObstacles(g2d, currentObstacles, scale, selectedObstacleName);
+ }
+
if (boundaryPointsVisible && hasBoundary) {
double markerScale = boundaryPointSizeScale * (previewSizingEnabled ? PREVIEW_BOUNDARY_MARKER_SCALE : 1.0d);
pointandnumber.drawBoundaryPoints(
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index 7c20720..1aa16f7 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -11,6 +11,8 @@
import java.awt.event.*;
import chuankou.dellmessage;
+import chuankou.sendmessage;
+import chuankou.SerialPortService;
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
import dikuai.addzhangaiwu;
@@ -287,6 +289,8 @@
// 杈圭晫妫�鏌ュ畾鏃跺櫒锛氭瘡500ms妫�鏌ヤ竴娆″壊鑽夋満鏄惁鍦ㄨ竟鐣屽唴
boundaryWarningTimer = new Timer(500, e -> {
checkMowerBoundaryStatus();
+ // 鍚屾椂鏇存柊钃濈墮鍥炬爣鐘舵��
+ updateBluetoothButtonIcon();
});
boundaryWarningTimer.setInitialDelay(0);
boundaryWarningTimer.start();
@@ -957,12 +961,14 @@
}
});
ensureBluetoothIconsLoaded();
- bluetoothConnected = Bluelink.isConnected();
- ImageIcon initialIcon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
+ // 鏍规嵁涓插彛杩炴帴鐘舵�佹樉绀哄浘鏍�
+ SerialPortService service = sendmessage.getActiveService();
+ boolean serialConnected = (service != null && service.isOpen());
+ ImageIcon initialIcon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
if (initialIcon != null) {
button.setIcon(initialIcon);
} else {
- button.setText(bluetoothConnected ? "宸茶繛" : "钃濈墮");
+ button.setText(serialConnected ? "宸茶繛" : "钃濈墮");
}
return button;
}
@@ -1862,13 +1868,15 @@
return;
}
ensureBluetoothIconsLoaded();
- bluetoothConnected = Bluelink.isConnected();
- ImageIcon icon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
+ // 鏍规嵁涓插彛杩炴帴鐘舵�佹樉绀哄浘鏍�
+ SerialPortService service = sendmessage.getActiveService();
+ boolean serialConnected = (service != null && service.isOpen());
+ ImageIcon icon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
if (icon != null) {
bluetoothBtn.setIcon(icon);
bluetoothBtn.setText(null);
} else {
- bluetoothBtn.setText(bluetoothConnected ? "宸茶繛" : "钃濈墮");
+ bluetoothBtn.setText(serialConnected ? "宸茶繛" : "钃濈墮");
}
}
--
Gitblit v1.10.0