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