From a3b05960fe629e9006b45d61618b01f724e757fd Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期五, 19 十二月 2025 17:41:08 +0800
Subject: [PATCH] 美化了地块管理的排版

---
 set.properties               |    8 
 src/zhuye/Shouye.java        |   46 +++
 src/zhuye/daohangyulan.java  |  487 ++++++++++++++++++++++++++++++++++++++++
 src/dikuai/Dikuaiguanli.java |  130 +++++-----
 4 files changed, 602 insertions(+), 69 deletions(-)

diff --git a/set.properties b/set.properties
index 9e70bde..357c32f 100644
--- a/set.properties
+++ b/set.properties
@@ -1,5 +1,5 @@
 #Mower Configuration Properties - Updated
-#Fri Dec 19 16:56:03 CST 2025
+#Fri Dec 19 17:40:36 CST 2025
 appVersion=-1
 boundaryLengthVisible=false
 currentWorkLandNumber=LAND1
@@ -8,12 +8,12 @@
 handheldMarkerId=1872
 idleTrailDurationSeconds=60
 manualBoundaryDrawingMode=false
-mapScale=11.63
+mapScale=5.63
 measurementModeEnabled=false
 mowerId=860
 serialAutoConnect=true
 serialBaudRate=115200
 serialPortName=COM15
 simCardNumber=-1
-viewCenterX=-17.03
-viewCenterY=4.72
+viewCenterX=-15.67
+viewCenterY=9.92
diff --git a/src/dikuai/Dikuaiguanli.java b/src/dikuai/Dikuaiguanli.java
index 2c6f253..b70c499 100644
--- a/src/dikuai/Dikuaiguanli.java
+++ b/src/dikuai/Dikuaiguanli.java
@@ -72,7 +72,7 @@
 	private ImageIcon workUnselectedIcon;
 	private ImageIcon boundaryVisibleIcon;
 	private ImageIcon boundaryHiddenIcon;
-	private static final int BOUNDARY_TOGGLE_ICON_SIZE = 48;
+	private static final int BOUNDARY_TOGGLE_ICON_SIZE = 24;
 	private Map<String, ObstacleSummary> obstacleSummaryCache = Collections.emptyMap();
 
 	public Dikuaiguanli(String landNumber) {
@@ -166,7 +166,7 @@
 			for (Dikuai dikuai : allDikuai.values()) {
 				JPanel card = createDikuaiCard(dikuai);
 				cardsPanel.add(card);
-				cardsPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+				cardsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 			}
 		}
 		
@@ -246,11 +246,11 @@
 		
 		// 鍦板潡缂栧彿
 		contentPanel.add(createCardInfoItem("鍦板潡缂栧彿:", getDisplayValue(dikuai.getLandNumber(), "鏈煡")));
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
 		// 娣诲姞鏃堕棿
 		contentPanel.add(createCardInfoItem("娣诲姞鏃堕棿:", getDisplayValue(dikuai.getCreateTime(), "鏈煡")));
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
 		// 鍦板潡闈㈢Н
 		String landArea = dikuai.getLandArea();
@@ -260,59 +260,7 @@
 			landArea = "鏈煡";
 		}
 		contentPanel.add(createCardInfoItem("鍦板潡闈㈢Н:", landArea));
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
-
-		// 杩斿洖鐐瑰潗鏍囷紙甯︿慨鏀规寜閽級
-		contentPanel.add(createCardInfoItemWithButton("杩斿洖鐐瑰潗鏍�:",
-			getDisplayValue(dikuai.getReturnPointCoordinates(), "鏈缃�"),
-			"淇敼", e -> editReturnPoint(dikuai)));
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
-
-		// 鍦板潡杈圭晫鍧愭爣锛堝甫鏄剧ず椤剁偣鎸夐挳锛�
-		JPanel boundaryPanel = createBoundaryInfoItem(dikuai);
-		configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel),
-			() -> editBoundaryCoordinates(dikuai),
-			"鐐瑰嚮鏌ョ湅/缂栬緫鍦板潡杈圭晫鍧愭爣");
-		contentPanel.add(boundaryPanel);
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
-
-		ObstacleSummary obstacleSummary = getObstacleSummaryFromCache(dikuai.getLandNumber());
-		JPanel obstaclePanel = createCardInfoItemWithButton("闅滅鐗�:",
-			obstacleSummary.buildDisplayValue(),
-			"鏂板",
-			e -> addNewObstacle(dikuai));
-		setInfoItemTooltip(obstaclePanel, obstacleSummary.buildTooltip());
-		// 璁╅殰纰嶇墿鏍囬鍙偣鍑伙紝鎵撳紑闅滅鐗╃鐞嗛〉闈�
-		configureInteractiveLabel(getInfoItemTitleLabel(obstaclePanel),
-			() -> showObstacleManagementPage(dikuai),
-			"鐐瑰嚮鏌ョ湅/绠$悊闅滅鐗�");
-		contentPanel.add(obstaclePanel);
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
-
-		// 璺緞鍧愭爣锛堝甫鏌ョ湅鎸夐挳锛�
-		JPanel pathPanel = createCardInfoItemWithButtonOnly("璺緞鍧愭爣:",
-			"鏌ョ湅", e -> editPlannedPath(dikuai));
-		configureInteractiveLabel(getInfoItemTitleLabel(pathPanel),
-			() -> editPlannedPath(dikuai),
-			"鐐瑰嚮鏌ョ湅/缂栬緫璺緞鍧愭爣");
-		contentPanel.add(pathPanel);
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
-
-	JPanel baseStationPanel = createCardInfoItemWithButtonOnly("鍩虹珯鍧愭爣:",
-		"鏌ョ湅", e -> editBaseStationCoordinates(dikuai));
-	configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel),
-		() -> editBaseStationCoordinates(dikuai),
-		"鐐瑰嚮鏌ョ湅/缂栬緫鍩虹珯鍧愭爣");
-	contentPanel.add(baseStationPanel);
-	contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
-
-	JPanel boundaryOriginalPanel = createCardInfoItemWithButtonOnly("杈圭晫鍘熷鍧愭爣:",
-		"鏌ョ湅", e -> editBoundaryOriginalCoordinates(dikuai));
-	configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel),
-		() -> editBoundaryOriginalCoordinates(dikuai),
-		"鐐瑰嚮鏌ョ湅/缂栬緫杈圭晫鍘熷鍧愭爣");
-	contentPanel.add(boundaryOriginalPanel);
-		contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
 		JPanel mowingPatternPanel = createCardInfoItem("鍓茶崏妯″紡:",
 			formatMowingPatternForDisplay(dikuai.getMowingPattern()));
@@ -320,7 +268,7 @@
 			() -> editMowingPattern(dikuai),
 			"鐐瑰嚮鏌ョ湅/缂栬緫鍓茶崏妯″紡");
 		contentPanel.add(mowingPatternPanel);
-		contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+		contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
 		// 鍓茶崏鏈哄壊鍒�瀹藉害
 		String mowingBladeWidthValue = dikuai.getMowingBladeWidth();
@@ -336,7 +284,7 @@
 		}
 		JPanel mowingBladeWidthPanel = createCardInfoItem("鍓茶崏鏈哄壊鍒�瀹藉害:", displayBladeWidth);
 		contentPanel.add(mowingBladeWidthPanel);
-		contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+		contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
 		String mowingWidthValue = dikuai.getMowingWidth();
 		String displayWidth = "鏈缃�";
@@ -345,7 +293,7 @@
 		}
 		JPanel mowingWidthPanel = createCardInfoItem("鍓茶崏瀹藉害:", displayWidth);
 		contentPanel.add(mowingWidthPanel);
-		contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+		contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
 		// 鍓茶崏瀹夊叏璺濈
 		String displaySafetyDistance = "鏈缃�";
@@ -367,7 +315,59 @@
 		}
 		JPanel mowingSafetyDistancePanel = createCardInfoItem("鍓茶崏瀹夊叏璺濈:", displaySafetyDistance);
 		contentPanel.add(mowingSafetyDistancePanel);
-		contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
+		contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+		// 杩斿洖鐐瑰潗鏍囷紙甯︿慨鏀规寜閽級
+		contentPanel.add(createCardInfoItemWithButton("杩斿洖鐐瑰潗鏍�:",
+			getDisplayValue(dikuai.getReturnPointCoordinates(), "鏈缃�"),
+			"淇敼", e -> editReturnPoint(dikuai)));
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+		ObstacleSummary obstacleSummary = getObstacleSummaryFromCache(dikuai.getLandNumber());
+		JPanel obstaclePanel = createCardInfoItemWithButton("闅滅鐗�:",
+			obstacleSummary.buildDisplayValue(),
+			"鏂板",
+			e -> addNewObstacle(dikuai));
+		setInfoItemTooltip(obstaclePanel, obstacleSummary.buildTooltip());
+		// 璁╅殰纰嶇墿鏍囬鍙偣鍑伙紝鎵撳紑闅滅鐗╃鐞嗛〉闈�
+		configureInteractiveLabel(getInfoItemTitleLabel(obstaclePanel),
+			() -> showObstacleManagementPage(dikuai),
+			"鐐瑰嚮鏌ョ湅/绠$悊闅滅鐗�");
+		contentPanel.add(obstaclePanel);
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+		// 鍦板潡杈圭晫鍧愭爣锛堝甫鏄剧ず椤剁偣鎸夐挳锛�
+		JPanel boundaryPanel = createBoundaryInfoItem(dikuai);
+		configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel),
+			() -> editBoundaryCoordinates(dikuai),
+			"鐐瑰嚮鏌ョ湅/缂栬緫鍦板潡杈圭晫鍧愭爣");
+		contentPanel.add(boundaryPanel);
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+		// 璺緞鍧愭爣锛堝甫鏌ョ湅鎸夐挳锛�
+		JPanel pathPanel = createCardInfoItemWithButtonOnly("璺緞鍧愭爣:",
+			"鏌ョ湅", e -> editPlannedPath(dikuai));
+		configureInteractiveLabel(getInfoItemTitleLabel(pathPanel),
+			() -> editPlannedPath(dikuai),
+			"鐐瑰嚮鏌ョ湅/缂栬緫璺緞鍧愭爣");
+		contentPanel.add(pathPanel);
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+	JPanel baseStationPanel = createCardInfoItemWithButtonOnly("鍩虹珯鍧愭爣:",
+		"鏌ョ湅", e -> editBaseStationCoordinates(dikuai));
+	configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel),
+		() -> editBaseStationCoordinates(dikuai),
+		"鐐瑰嚮鏌ョ湅/缂栬緫鍩虹珯鍧愭爣");
+	contentPanel.add(baseStationPanel);
+	contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+
+	JPanel boundaryOriginalPanel = createCardInfoItemWithButtonOnly("杈圭晫鍘熷鍧愭爣:",
+		"鏌ョ湅", e -> editBoundaryOriginalCoordinates(dikuai));
+	configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel),
+		() -> editBoundaryOriginalCoordinates(dikuai),
+		"鐐瑰嚮鏌ョ湅/缂栬緫杈圭晫鍘熷鍧愭爣");
+	contentPanel.add(boundaryOriginalPanel);
+		contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
 		JPanel completedTrackPanel = createCardInfoItemWithButton("宸插畬鎴愬壊鑽夎矾寰�:",
 			getTruncatedValue(dikuai.getMowingTrack(), 12, "鏈褰�"),
@@ -487,12 +487,12 @@
 		private JPanel createBoundaryInfoItem(Dikuai dikuai) {
 			JPanel itemPanel = new JPanel(new BorderLayout());
 			itemPanel.setBackground(CARD_BACKGROUND);
-			// 澧炲姞楂樺害浠ョ‘淇濇寜閽笅杈圭紭瀹屾暣鏄剧ず锛堟寜閽珮搴�56锛屽姞涓婁笂涓嬭竟璺濓級
-			int rowHeight = Math.max(60, BOUNDARY_TOGGLE_ICON_SIZE + 16);
+			// 澧炲姞楂樺害浠ョ‘淇濇寜閽笅杈圭紭瀹屾暣鏄剧ず锛堟寜閽珮搴�28锛屽姞涓婁笂涓嬭竟璺濓級
+			int rowHeight = Math.max(30, BOUNDARY_TOGGLE_ICON_SIZE + 8);
 			Dimension rowDimension = new Dimension(Integer.MAX_VALUE, rowHeight);
 			itemPanel.setMaximumSize(rowDimension);
 			itemPanel.setPreferredSize(rowDimension);
-			itemPanel.setMinimumSize(new Dimension(0, 56));
+			itemPanel.setMinimumSize(new Dimension(0, 28));
 
 			JLabel labelComp = new JLabel("鍦板潡杈圭晫:");
 			labelComp.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 14));
@@ -537,7 +537,7 @@
 			button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
 			button.setMargin(new Insets(0, 0, 0, 0));
 			button.setIconTextGap(0);
-			button.setPreferredSize(new Dimension(56, 56));
+			button.setPreferredSize(new Dimension(28, 28));
 
 			String landNumber = dikuai.getLandNumber();
 			boolean isVisible = boundaryPointVisibility.getOrDefault(landNumber, false);
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index c661ee5..1dbc337 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -3957,6 +3957,52 @@
 	}
 
 	/**
+	 * 鏇存柊瀵艰埅棰勮鐘舵�佹樉绀�
+	 * @param active 鏄惁澶勪簬瀵艰埅棰勮妯″紡
+	 */
+	public void updateNavigationPreviewStatus(boolean active) {
+		setNavigationPreviewLabelVisible(active);
+	}
+
+	/**
+	 * 鏇存柊鍓茶崏杩涘害鏄剧ず
+	 * @param percentage 瀹屾垚鐧惧垎姣�
+	 * @param completedArea 宸插畬鎴愰潰绉紙骞虫柟绫筹級
+	 * @param totalArea 鎬婚潰绉紙骞虫柟绫筹級
+	 */
+	public void updateMowingProgress(double percentage, double completedArea, double totalArea) {
+		if (mowingProgressLabel == null) {
+			return;
+		}
+		if (totalArea <= 0) {
+			mowingProgressLabel.setText("--%");
+			mowingProgressLabel.setToolTipText("鏆傛棤鍦板潡闈㈢Н鏁版嵁");
+			return;
+		}
+		double percent = Math.max(0.0, Math.min(100.0, percentage));
+		mowingProgressLabel.setText(String.format(Locale.US, "%.1f%%", percent));
+		mowingProgressLabel.setToolTipText(String.format(Locale.US, "%.1f銕� / %.1f銕�", completedArea, totalArea));
+	}
+
+	/**
+	 * 鏇存柊鍓茶崏鏈洪�熷害鏄剧ず
+	 * @param speed 閫熷害鍊硷紙鍗曚綅锛歬m/h锛�
+	 */
+	public void updateMowerSpeed(double speed) {
+		if (mowerSpeedValueLabel == null) {
+			return;
+		}
+		if (speed < 0) {
+			mowerSpeedValueLabel.setText("--");
+		} else {
+			mowerSpeedValueLabel.setText(String.format(Locale.US, "%.1f", speed));
+		}
+		if (mowerSpeedUnitLabel != null) {
+			mowerSpeedUnitLabel.setText("km/h");
+		}
+	}
+
+	/**
 	 * 鑾峰彇鍙鍖栭潰鏉垮疄渚�
 	 */
 	public JPanel getVisualizationPanel() {
diff --git a/src/zhuye/daohangyulan.java b/src/zhuye/daohangyulan.java
new file mode 100644
index 0000000..edafba9
--- /dev/null
+++ b/src/zhuye/daohangyulan.java
@@ -0,0 +1,487 @@
+package zhuye;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import dikuai.Dikuai;
+import gecaoji.Gecaoji;
+import gecaoji.lujingdraw;
+import zhangaiwu.Obstacledge;
+
+/**
+ * 瀵艰埅棰勮 - 鍦ㄥ湴鍥句笂棰勮瀵艰埅璺緞鍜屽凡鍓插尯鍩�
+ */
+public class daohangyulan {
+    private static boolean active = false;
+    private static Shouye shouyeInstance;
+    private static Dikuai currentDikuai;
+    private static Runnable returnCallback;
+    
+    // 瀵艰埅妯℃嫙鐩稿叧
+    private static List<Point2D.Double> pathPoints = new ArrayList<>();
+    private static int currentPathIndex = 0;
+    private static double currentSpeed = 1.0; // 榛樿1绫�/绉�
+    private static Timer navigationTimer;
+    private static double totalPathLength = 0.0;
+    private static double traveledDistance = 0.0;
+    private static List<Point2D.Double> mowedTrack = new ArrayList<>();
+    
+    // UI缁勪欢
+    private static JButton exitButton;
+    private static JButton speedUpButton;
+    private static JButton speedDownButton;
+    private static JPanel navigationButtonPanel;
+    
+    private daohangyulan() {
+    }
+    
+    /**
+     * 鍚姩瀵艰埅棰勮
+     * @param shouye 涓婚〉闈㈠疄渚�
+     * @param dikuai 鍦板潡鏁版嵁
+     * @param callback 杩斿洖鍥炶皟鍑芥暟
+     */
+    public static void startNavigationPreview(Shouye shouye, Dikuai dikuai, Runnable callback) {
+        if (shouye == null || dikuai == null) {
+            return;
+        }
+        
+        active = true;
+        shouyeInstance = shouye;
+        currentDikuai = dikuai;
+        returnCallback = callback;
+        
+        // 棣栧厛鑾峰彇璺緞鍧愭爣
+        String plannedPath = dikuai.getPlannedPath();
+        if (plannedPath == null || plannedPath.trim().isEmpty() || "-1".equals(plannedPath.trim())) {
+            // 濡傛灉璺緞涓嶅瓨鍦紝鎻愮ず鐢ㄦ埛
+            SwingUtilities.invokeLater(() -> {
+                javax.swing.JOptionPane.showMessageDialog(shouye, 
+                    "鏃犳硶鑾峰彇鏈夋晥鐨勮矾寰勫潗鏍囷紝璇峰厛璁剧疆鍦板潡杈圭晫鍧愭爣鍜屽壊鑽夊搴︼紝鐒跺悗鐢熸垚璺緞瑙勫垝", 
+                    "鎻愮ず", javax.swing.JOptionPane.WARNING_MESSAGE);
+            });
+            return;
+        }
+        
+        // 瑙f瀽璺緞鍧愭爣
+        pathPoints = parsePlannedPath(plannedPath);
+        if (pathPoints.isEmpty()) {
+            SwingUtilities.invokeLater(() -> {
+                javax.swing.JOptionPane.showMessageDialog(shouye, 
+                    "璺緞鍧愭爣瑙f瀽澶辫触锛岃妫�鏌ヨ矾寰勬暟鎹�", 
+                    "鎻愮ず", javax.swing.JOptionPane.WARNING_MESSAGE);
+            });
+            return;
+        }
+        
+        // 璁$畻鎬昏矾寰勯暱搴�
+        totalPathLength = calculatePathLength(pathPoints);
+        traveledDistance = 0.0;
+        currentPathIndex = 0;
+        currentSpeed = 1.0; // 榛樿1绫�/绉�
+        mowedTrack.clear();
+        
+        // 鑾峰彇鍦板潡鏁版嵁
+        String landNumber = dikuai.getLandNumber();
+        String landName = dikuai.getLandName();
+        String boundary = dikuai.getBoundaryCoordinates();
+        
+        // 鑾峰彇闅滅鐗╁潗鏍�
+        String obstacles = getObstacleCoordinates(landNumber);
+        
+        // 鑾峰彇鍓茶崏瀹藉害锛堣浆鎹负绫筹級
+        double mowingWidthMeters = getMowingWidthInMeters(dikuai);
+        
+        // 璁剧疆瀵艰埅棰勮杞ㄨ抗鍜屽搴﹀埌MapRenderer
+        MapRenderer renderer = shouye.getMapRenderer();
+        if (renderer != null) {
+            renderer.setNavigationPreviewTrack(new ArrayList<>());
+            renderer.setNavigationPreviewWidth(mowingWidthMeters);
+        }
+        
+        // 浣跨敤startMowingPathPreview鏉ユ樉绀鸿繑鍥炴寜閽拰璁剧疆棰勮鐘舵��
+        Runnable returnAction = () -> {
+            handleReturn();
+        };
+        shouye.startMowingPathPreview(landNumber, landName, boundary, obstacles, plannedPath, returnAction);
+        
+        // 鏇存柊瀵艰埅棰勮鐘舵�佹樉绀猴紙鍦ㄤ綔涓氱姸鎬佸乏杈癸紝闂撮殧10鍍忕礌锛�
+        shouye.updateNavigationPreviewStatus(true);
+        
+        // 鍒涘缓骞舵樉绀哄鑸帶鍒舵寜閽�
+        createNavigationButtons(shouye);
+        
+        // 鍒濆鍖栧壊鑽夋満浣嶇疆鍒拌矾寰勮捣鐐�
+        if (!pathPoints.isEmpty()) {
+            Point2D.Double startPoint = pathPoints.get(0);
+            Gecaoji mower = renderer != null ? renderer.getMower() : null;
+            if (mower != null) {
+                mower.setPosition(startPoint.x, startPoint.y);
+            }
+            // 娣诲姞璧风偣鍒板凡鍓茶建杩�
+            mowedTrack.add(new Point2D.Double(startPoint.x, startPoint.y));
+            if (renderer != null) {
+                renderer.addNavigationPreviewTrackPoint(new Point2D.Double(startPoint.x, startPoint.y));
+            }
+        }
+        
+        // 鍚姩瀵艰埅妯℃嫙瀹氭椂鍣�
+        startNavigationSimulation(shouye, renderer, mowingWidthMeters);
+        
+        // 鍒锋柊鍦板浘鏄剧ず
+        shouye.repaint();
+    }
+    
+    /**
+     * 鍋滄瀵艰埅棰勮
+     */
+    public static void stopNavigationPreview() {
+        if (!active) {
+            return;
+        }
+        
+        active = false;
+        
+        // 鍋滄瀵艰埅妯℃嫙瀹氭椂鍣�
+        stopNavigationSimulation();
+        
+        // 闅愯棌瀵艰埅鎺у埗鎸夐挳
+        hideNavigationButtons();
+        
+        // 娓呴櫎瀵艰埅棰勮杞ㄨ抗
+        if (shouyeInstance != null) {
+            MapRenderer renderer = shouyeInstance.getMapRenderer();
+            if (renderer != null) {
+                renderer.clearNavigationPreviewTrack();
+            }
+            
+            // 鏇存柊瀵艰埅棰勮鐘舵�佹樉绀�
+            shouyeInstance.updateNavigationPreviewStatus(false);
+            
+            // 鍒锋柊鍦板浘鏄剧ず
+            shouyeInstance.repaint();
+        }
+        
+        // 鎵ц杩斿洖鍥炶皟
+        if (returnCallback != null) {
+            Runnable callback = returnCallback;
+            returnCallback = null;
+            SwingUtilities.invokeLater(callback);
+        }
+        
+        shouyeInstance = null;
+        currentDikuai = null;
+        pathPoints.clear();
+        mowedTrack.clear();
+    }
+    
+    /**
+     * 妫�鏌ユ槸鍚﹀浜庡鑸瑙堟ā寮�
+     */
+    public static boolean isActive() {
+        return active;
+    }
+    
+    /**
+     * 澶勭悊杩斿洖鎸夐挳鐐瑰嚮
+     */
+    public static void handleReturn() {
+        stopNavigationPreview();
+    }
+    
+    /**
+     * 鍒涘缓瀵艰埅鎺у埗鎸夐挳
+     */
+    private static void createNavigationButtons(Shouye shouye) {
+        if (shouye == null) {
+            return;
+        }
+        
+        JPanel visualizationPanel = shouye.getVisualizationPanel();
+        if (visualizationPanel == null) {
+            return;
+        }
+        
+        // 鍒涘缓鎸夐挳闈㈡澘
+        navigationButtonPanel = new JPanel();
+        navigationButtonPanel.setLayout(new BoxLayout(navigationButtonPanel, BoxLayout.Y_AXIS));
+        navigationButtonPanel.setOpaque(false);
+        navigationButtonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+        
+        // 鍒涘缓鎸夐挳
+        exitButton = createNavigationButton("閫�鍑�");
+        exitButton.addActionListener(e -> handleReturn());
+        
+        speedUpButton = createNavigationButton("鍔犻��");
+        speedUpButton.addActionListener(e -> {
+            currentSpeed *= 2.0;
+            updateSpeedDisplay(shouye);
+        });
+        
+        speedDownButton = createNavigationButton("鍑忛��");
+        speedDownButton.addActionListener(e -> {
+            currentSpeed /= 2.0;
+            if (currentSpeed < 0.1) {
+                currentSpeed = 0.1;
+            }
+            updateSpeedDisplay(shouye);
+        });
+        
+        // 鍨傜洿鎺掑垪
+        navigationButtonPanel.add(speedUpButton);
+        navigationButtonPanel.add(Box.createRigidArea(new Dimension(0, 8)));
+        navigationButtonPanel.add(speedDownButton);
+        navigationButtonPanel.add(Box.createRigidArea(new Dimension(0, 8)));
+        navigationButtonPanel.add(exitButton);
+        
+        // 璁剧疆闈㈡澘澶у皬
+        navigationButtonPanel.setPreferredSize(new Dimension(80, 120));
+        navigationButtonPanel.setSize(80, 120);
+        
+        // 娣诲姞鍒板鍣�
+        visualizationPanel.add(navigationButtonPanel);
+        visualizationPanel.setComponentZOrder(navigationButtonPanel, 0);
+        
+        // 鍒濆鍖栦綅缃�
+        SwingUtilities.invokeLater(() -> {
+            updateButtonPositions(visualizationPanel);
+        });
+        
+        // 鐩戝惉缂╂斁/璋冩暣澶у皬
+        visualizationPanel.addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                updateButtonPositions(visualizationPanel);
+            }
+        });
+    }
+    
+    private static JButton createNavigationButton(String text) {
+        return buttonset.createStyledButton(text, new Color(46, 139, 87));
+    }
+    
+    /**
+     * 鏇存柊鎸夐挳浣嶇疆 - 淇敼姝ゅ浠ュ尮閰嶅浘涓孩妗嗕綅缃�
+     */
+    private static void updateButtonPositions(Component panel) {
+        if (navigationButtonPanel == null || panel == null) {
+            return;
+        }
+        
+        int panelWidth = panel.getWidth();
+        int panelHeight = panel.getHeight();
+        if (panelWidth <= 0 || panelHeight <= 0) {
+            Component parent = panel.getParent();
+            if (parent != null) {
+                if (panelWidth <= 0) {
+                    panelWidth = parent.getWidth();
+                }
+                if (panelHeight <= 0) {
+                    panelHeight = parent.getHeight();
+                }
+            }
+            if (panelWidth <= 0) {
+                panelWidth = 800; // 榛樿瀹藉害
+            }
+            if (panelHeight <= 0) {
+                panelHeight = 600; // 榛樿楂樺害
+            }
+        }
+        
+        // 鎸夐挳闈㈡澘灏哄
+        int buttonPanelWidth = 80;
+        int buttonPanelHeight = 120;
+        
+        // 鍙充笅瑙掍綅缃細璺濈鍙宠竟20鍍忕礌锛岃窛绂诲簳閮�20鍍忕礌锛堝湪缂╂斁鏂囧瓧涓婃柟锛�
+        // 缂╂斁鎺у埗闈㈡澘鍦ㄥ彸涓嬭锛屾湁20鍍忕礌鐨勫彸杈硅窛鍜屽簳閮ㄨ竟璺�
+        // 鎸夐挳闈㈡澘鏀惧湪缂╂斁鎺у埗闈㈡澘宸︿晶锛岄伩鍏嶉噸鍙�
+        int x = panelWidth - buttonPanelWidth - 20; // 璺濈鍙宠竟20鍍忕礌 
+        
+        // Y鍧愭爣璁$畻璇存槑锛�
+        // 鎸夐挳闈㈡澘楂樺害涓� 120銆�
+        // 涓轰簡閬垮紑搴曢儴鐨勨�滅缉鏀�: 3.91x鈥濇枃瀛楋紙璇ユ枃瀛楅�氬父鍦ㄥ簳閮ㄥ悜涓�20-30鍍忕礌澶勶級锛�
+        // 鎴戜滑灏嗘寜閽潰鏉挎斁鍦ㄨ窛绂诲簳閮ㄧ害 50-60 鍍忕礌鐨勪綅缃��
+        // y = 鎬婚珮搴� - 鎸夐挳闈㈡澘楂樺害 - 搴曢儴棰勭暀绌洪棿
+        int y = panelHeight - buttonPanelHeight - 20; // 璺濈搴曢儴20鍍忕礌 
+        
+        navigationButtonPanel.setBounds(x, y, buttonPanelWidth, buttonPanelHeight);
+        navigationButtonPanel.setVisible(true);
+        
+        // 纭繚灞傜骇鍦ㄦ渶鍓�
+        Component buttonParent = navigationButtonPanel.getParent();
+        if (buttonParent instanceof java.awt.Container) {
+            ((java.awt.Container) buttonParent).setComponentZOrder(navigationButtonPanel, 0);
+        }
+    }
+    
+    private static void hideNavigationButtons() {
+        if (navigationButtonPanel != null) {
+            navigationButtonPanel.setVisible(false);
+            if (navigationButtonPanel.getParent() != null) {
+                navigationButtonPanel.getParent().remove(navigationButtonPanel);
+            }
+        }
+        exitButton = null;
+        speedUpButton = null;
+        speedDownButton = null;
+        navigationButtonPanel = null;
+    }
+    
+    private static void startNavigationSimulation(Shouye shouye, MapRenderer renderer, double mowingWidthMeters) {
+        if (pathPoints.isEmpty() || renderer == null) return;
+        
+        if (navigationTimer != null && navigationTimer.isRunning()) {
+            navigationTimer.stop();
+        }
+        
+        navigationTimer = new Timer(100, e -> {
+            if (!active) {
+                stopNavigationSimulation();
+                return;
+            }
+            if (currentPathIndex >= pathPoints.size() - 1) {
+                stopNavigationSimulation();
+                return;
+            }
+            updateNavigation(shouye, renderer, mowingWidthMeters);
+        });
+        navigationTimer.start();
+    }
+    
+    private static void stopNavigationSimulation() {
+        if (navigationTimer != null) {
+            navigationTimer.stop();
+            navigationTimer = null;
+        }
+    }
+    
+    private static void updateNavigation(Shouye shouye, MapRenderer renderer, double mowingWidthMeters) {
+        if (pathPoints.isEmpty() || currentPathIndex >= pathPoints.size() - 1) {
+            stopNavigationSimulation();
+            return;
+        }
+        
+        double deltaTime = 0.1;
+        double moveDistance = currentSpeed * deltaTime;
+        
+        Point2D.Double currentPos = pathPoints.get(currentPathIndex);
+        Point2D.Double nextPos = pathPoints.get(currentPathIndex + 1);
+        double segmentLength = Math.hypot(nextPos.x - currentPos.x, nextPos.y - currentPos.y);
+        
+        if (segmentLength < 0.001) {
+            currentPathIndex++;
+            return;
+        }
+        
+        Gecaoji mower = renderer.getMower();
+        if (moveDistance >= segmentLength) {
+            currentPathIndex++;
+            traveledDistance += segmentLength;
+            if (mower != null && currentPathIndex < pathPoints.size()) {
+                Point2D.Double newPos = pathPoints.get(currentPathIndex);
+                mower.setPosition(newPos.x, newPos.y);
+                mowedTrack.add(new Point2D.Double(newPos.x, newPos.y));
+                renderer.addNavigationPreviewTrackPoint(new Point2D.Double(newPos.x, newPos.y));
+            }
+        } else {
+            double ratio = moveDistance / segmentLength;
+            double newX = currentPos.x + (nextPos.x - currentPos.x) * ratio;
+            double newY = currentPos.y + (nextPos.y - currentPos.y) * ratio;
+            if (mower != null) {
+                mower.setPosition(newX, newY);
+                if (mowedTrack.isEmpty() || Math.hypot(newX - mowedTrack.get(mowedTrack.size() - 1).x, newY - mowedTrack.get(mowedTrack.size() - 1).y) > 0.05) {
+                    mowedTrack.add(new Point2D.Double(newX, newY));
+                    renderer.addNavigationPreviewTrackPoint(new Point2D.Double(newX, newY));
+                }
+            }
+            traveledDistance += moveDistance;
+        }
+        
+        updateProgressAndSpeed(shouye, renderer, mowingWidthMeters);
+        shouye.repaint();
+    }
+    
+    private static void updateProgressAndSpeed(Shouye shouye, MapRenderer renderer, double mowingWidthMeters) {
+        if (shouye == null || renderer == null) return;
+        double completedArea = traveledDistance * mowingWidthMeters;
+        double totalArea = renderer.getTotalLandAreaSqMeters();
+        if (totalArea <= 0 && currentDikuai != null) {
+            try {
+                String areaStr = currentDikuai.getLandArea();
+                if (areaStr != null) totalArea = Double.parseDouble(areaStr.trim());
+            } catch (Exception e) {}
+        }
+        if (totalArea > 0) {
+            double percentage = Math.max(0.0, Math.min(100.0, (completedArea / totalArea) * 100.0));
+            shouye.updateMowingProgress(percentage, completedArea, totalArea);
+        }
+        shouye.updateMowerSpeed(currentSpeed * 3.6);
+    }
+    
+    private static void updateSpeedDisplay(Shouye shouye) {
+        if (shouye != null) shouye.updateMowerSpeed(currentSpeed * 3.6);
+    }
+    
+    private static double calculatePathLength(List<Point2D.Double> points) {
+        double total = 0.0;
+        for (int i = 0; i < points.size() - 1; i++) {
+            total += Math.hypot(points.get(i+1).x - points.get(i).x, points.get(i+1).y - points.get(i).y);
+        }
+        return total;
+    }
+    
+    private static List<Point2D.Double> parsePlannedPath(String plannedPath) {
+        List<Point2D.Double> points = lujingdraw.parsePlannedPath(plannedPath);
+        return points != null ? points : new ArrayList<>();
+    }
+    
+    private static double getMowingWidthInMeters(Dikuai dikuai) {
+        if (dikuai == null) return 0.4;
+        try {
+            String bladeWidth = dikuai.getMowingBladeWidth();
+            if (bladeWidth != null && !"-1".equals(bladeWidth)) return Double.parseDouble(bladeWidth);
+            String mowWidth = dikuai.getMowingWidth();
+            if (mowWidth != null && !"-1".equals(mowWidth)) {
+                double val = Double.parseDouble(mowWidth);
+                return val > 10 ? val / 100.0 : val;
+            }
+        } catch (Exception e) {}
+        return 0.4;
+    }
+    
+    private static String getObstacleCoordinates(String landNumber) {
+        if (landNumber == null || "-1".equals(landNumber)) return null;
+        try {
+            java.io.File configFile = new java.io.File("Obstacledge.properties");
+            if (!configFile.exists()) return null;
+            Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+            if (!manager.loadFromFile(configFile.getAbsolutePath())) return null;
+            Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
+            if (plot == null) return null;
+            List<Obstacledge.Obstacle> obsList = plot.getObstacles();
+            if (obsList == null) return null;
+            StringBuilder sb = new StringBuilder();
+            for (Obstacledge.Obstacle obs : obsList) {
+                String coords = obs.getXyCoordsString();
+                if (coords != null && !"-1".equals(coords)) {
+                    if (sb.length() > 0) sb.append(" ");
+                    sb.append("(").append(coords).append(")");
+                }
+            }
+            return sb.length() > 0 ? sb.toString() : null;
+        } catch (Exception e) { return null; }
+    }
+}
\ No newline at end of file

--
Gitblit v1.10.0