From de75ec84e295c3f952a200897aa22aa73d7d5867 Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期一, 15 十二月 2025 19:37:11 +0800
Subject: [PATCH] 新增了串口割草机拖尾和缩放比例保存功能

---
 image/xia10.png                      |    0 
 src/gecaoji/Device.java              |   24 +++
 src/zhuye/LegendDialog.java          |   59 +++++++
 src/set/Setsys.java                  |    8 
 image/xia2.png                       |    0 
 src/denglu/UserChuShiHua.java        |    1 
 src/zhuye/MapRenderer.java           |  133 ++++++++++++++++
 set.properties                       |    5 
 image/xia1.png                       |    0 
 src/zhuye/Shouye.java                |  111 +++++++++++++
 src/yaokong/RemoteControlDialog.java |  139 ++++++++--------
 image/xia20.png                      |    0 
 12 files changed, 398 insertions(+), 82 deletions(-)

diff --git a/image/xia1.png b/image/xia1.png
index 300bfad..7da633b 100644
--- a/image/xia1.png
+++ b/image/xia1.png
Binary files differ
diff --git a/image/xia10.png b/image/xia10.png
index f0d4eaa..9b26e37 100644
--- a/image/xia10.png
+++ b/image/xia10.png
Binary files differ
diff --git a/image/xia2.png b/image/xia2.png
index 9b26e37..f0d4eaa 100644
--- a/image/xia2.png
+++ b/image/xia2.png
Binary files differ
diff --git a/image/xia20.png b/image/xia20.png
index 7da633b..300bfad 100644
--- a/image/xia20.png
+++ b/image/xia20.png
Binary files differ
diff --git a/set.properties b/set.properties
index 432da08..b32ece6 100644
--- a/set.properties
+++ b/set.properties
@@ -1,11 +1,12 @@
-#Serial Port Preferences Updated
-#Mon Dec 15 15:45:14 CST 2025
+#Mower Configuration Properties - Updated
+#Mon Dec 15 19:36:26 CST 2025
 appVersion=-1
 currentWorkLandNumber=LAND1
 cuttingWidth=200
 firmwareVersion=-1
 handheldMarkerId=
 idleTrailDurationSeconds=60
+mapScale=41.66666666666667
 mowerId=1234
 serialAutoConnect=true
 serialBaudRate=115200
diff --git a/src/denglu/UserChuShiHua.java b/src/denglu/UserChuShiHua.java
index 7ba203f..e765f86 100644
--- a/src/denglu/UserChuShiHua.java
+++ b/src/denglu/UserChuShiHua.java
@@ -28,7 +28,6 @@
         
         try (OutputStream output = new FileOutputStream(FILE_PATH)) {
             userProperties.store(output, "Updated User Properties");
-            System.out.println("灞炴�� " + key + " 宸叉洿鏂颁负: " + value);
         } catch (IOException e) {
             System.err.println("鏇存柊澶辫触锛屾枃浠跺啓鍏ラ敊璇�: " + e.getMessage());
         }
diff --git a/src/gecaoji/Device.java b/src/gecaoji/Device.java
index aa4b77d..fd7cae4 100644
--- a/src/gecaoji/Device.java
+++ b/src/gecaoji/Device.java
@@ -467,6 +467,30 @@
         GupdateTime = String.valueOf(System.currentTimeMillis());
 
         updateRelativeCoordinates(latitudeValue, latitudeHemisphere, longitudeValue, longitudeHemisphere);
+        
+        // 涓插彛鏀跺埌GNGGA鏁版嵁鍚庯紝瑙﹀彂鎷栧熬鏇存柊
+        notifyMowerTrailUpdate();
+    }
+    
+    /**
+     * 閫氱煡鍦板浘娓叉煋鍣ㄦ洿鏂板壊鑽夋満鎷栧熬
+     * 褰撲覆鍙f敹鍒癎NGGA鏁版嵁骞舵洿鏂颁綅缃悗璋冪敤
+     */
+    private void notifyMowerTrailUpdate() {
+        try {
+            // 閫氳繃Shouye.getInstance()鑾峰彇瀹炰緥锛岄伩鍏嶅惊鐜緷璧�
+            zhuye.Shouye shouye = zhuye.Shouye.getInstance();
+            if (shouye != null) {
+                zhuye.MapRenderer mapRenderer = shouye.getMapRenderer();
+                if (mapRenderer != null) {
+                    // 璋冪敤鏇存柊鎷栧熬鏂规硶
+                    mapRenderer.forceUpdateIdleMowerTrail();
+                }
+            }
+        } catch (Exception e) {
+            // 濡傛灉璋冪敤澶辫触锛岄潤榛樺鐞嗭紙涓嶅奖鍝嶄富瑕佸姛鑳斤級
+            // System.err.println("閫氱煡鎷栧熬鏇存柊澶辫触: " + e.getMessage());
+        }
     }
 
     private void updateRelativeCoordinates(String latValue, String latHemisphere,
diff --git a/src/set/Setsys.java b/src/set/Setsys.java
index 2523a0f..7d12a67 100644
--- a/src/set/Setsys.java
+++ b/src/set/Setsys.java
@@ -145,9 +145,12 @@
                 this.idleTrailDurationSeconds = durationSeconds;
                 value = String.valueOf(durationSeconds);
                 break;
+            case "mapScale":
+                // mapScale涓嶉渶瑕佸湪鍐呭瓨涓瓨鍌紝鐩存帴鏇存柊鍒版枃浠�
+                break;
             default:
-                System.err.println("鏈煡鐨勫睘鎬у悕: " + propertyName);
-                return false;
+                // 瀵逛簬鍏朵粬灞炴�э紝涔熷厑璁哥洿鎺ユ洿鏂板埌鏂囦欢锛堜笉鎵撳嵃閿欒锛�
+                break;
         }
 
         // 鏇存柊properties鏂囦欢
@@ -173,7 +176,6 @@
         // 鍐欏洖鏂囦欢
         try (FileOutputStream output = new FileOutputStream(PROPERTIES_FILE)) {
             props.store(output, "Mower Configuration Properties - Updated");
-            System.out.println("灞炴�� " + propertyName + " 宸叉洿鏂颁负: " + value);
             return true;
         } catch (IOException e) {
             System.err.println("鏇存柊灞炴�ф枃浠跺け璐�: " + e.getMessage());
diff --git a/src/yaokong/RemoteControlDialog.java b/src/yaokong/RemoteControlDialog.java
index 850a14c..ca29763 100644
--- a/src/yaokong/RemoteControlDialog.java
+++ b/src/yaokong/RemoteControlDialog.java
@@ -25,6 +25,9 @@
     private Timer steeringControlTimer;  // 杞悜鎺у埗瀹氭椂鍣�
     private int targetForwardSpeed = 0;  // 鐩爣鍓嶈繘/鍚庨��閫熷害
     private int targetSteeringSpeed = 0;  // 鐩爣杞悜閫熷害
+    // 鐙珛璺熻釜姣忎釜鎽囨潌鐨勫綋鍓嶉�熷害锛岄伩鍏嶇浉浜掑奖鍝�
+    private int independentForwardSpeed = 0;  // 鐙珛鐨勫墠杩涢�熷害锛堜笉鍙楄浆鍚戞憞鏉嗗奖鍝嶏級
+    private int independentSteeringSpeed = 0;  // 鐙珛鐨勮浆鍚戦�熷害锛堜笉鍙楀墠杩涙憞鏉嗗奖鍝嶏級
     private List<JButton> bladeButtons = new ArrayList<>();  // 瀛樺偍鍒�鐩樻帶鍒舵寜閽紝鐢ㄤ簬娓呯悊瀹氭椂鍣�
     private String bladeUpDefaultText = "鍒�鐩樺崌";  // 鍒�鐩樺崌鎸夐挳榛樿鏂囧瓧
     private String bladeDownDefaultText = "鍒�鐩橀檷";  // 鍒�鐩橀檷鎸夐挳榛樿鏂囧瓧
@@ -158,27 +161,38 @@
         moveJoystick.setJoystickListener(new JoystickListener() {
             @Override
             public void onJoystickMoved(double x, double y) {
-                // 鍙娇鐢╕杞存帶鍒跺墠杩涘悗閫�锛屽悜涓婏紙鍖楋級涓烘
-                // 璁$畻骞跺洓鑸嶄簲鍏ュ埌鏁存暟閫熷害鍊硷紝姝d负鍓嶈繘锛岃礋涓哄悗閫�
+                // 鍙娇鐢╕杞存帶鍒跺墠杩涘悗閫�
+                // y鍊艰寖鍥达細-1.0锛堝悜涓婏級鍒� 1.0锛堝悜涓嬶級
+                // 璁$畻閫熷害鍊硷細鍚戜笅锛坹>0锛変负鍚庨��锛堣礋鍊硷級锛屽悜涓婏紙y<0锛変负鍓嶈繘锛堟鍊硷級
+                // 鍚庨��鍊艰寖鍥达細0 鍒� -100锛屽墠杩涘�艰寖鍥达細0 鍒� 100
                 int forwardVal = (int) Math.round(-y * 100.0);
                 // 闄愬埗鍦� [-100, 100]
                 forwardVal = Math.max(-100, Math.min(100, forwardVal));
 
-                // 鏇存柊鐩爣閫熷害
-                targetForwardSpeed = forwardVal;
-
-                if (Math.abs(y) > 0.1) {
-                    // 鎽囨潌涓嶅湪涓績浣嶇疆锛屽惎鍔ㄦ寔缁彂閫佸畾鏃跺櫒
-                    startForwardControlTimer();
-                } else {
-                    // 鎽囨潌鍥炲埌涓績浣嶇疆锛屽仠姝㈠彂閫�
-                    stopForwardControlTimer();
-                    stopForward();
+                // 姝诲尯澶勭悊锛氬鏋滈�熷害鍊煎湪-10鍒�10涔嬮棿锛岃涓�0锛堥伩鍏嶅井灏忔姈鍔級
+                if (Math.abs(forwardVal) <= 10) {
+                    forwardVal = 0;
                 }
 
-                // 鏇存柊椤堕儴鏄剧ず锛堢Щ鍔ㄦ樉绀哄綋鍓嶅墠杩�/鍚庨��閫熷害锛岃浆鍚戝彇褰撳墠杞悜閫熷害浣滀负鍙傝�冿級
-                int steeringVal = Control03.getCurrentSteeringSpeed();
-                updateJoystickValues(forwardVal, steeringVal);
+                // 鏇存柊鐩爣閫熷害鍜岀嫭绔嬮�熷害
+                targetForwardSpeed = forwardVal;
+                independentForwardSpeed = forwardVal;
+
+                if (forwardVal != 0) {
+                    // 鎽囨潌涓嶅湪姝诲尯锛屽惎鍔ㄦ寔缁彂閫佸畾鏃跺櫒
+                    // 鍚庨��鏃� forwardVal 涓鸿礋鍊硷紙-100鍒�-11锛夛紝鍓嶈繘鏃� forwardVal 涓烘鍊硷紙11鍒�100锛�
+                    startForwardControlTimer();
+                } else {
+                    // 鎽囨潌鍦ㄦ鍖烘垨涓績浣嶇疆锛屽仠姝㈠畾鏃跺櫒
+                    stopForwardControlTimer();
+                    // 灏嗙嫭绔嬬殑鍓嶈繘閫熷害璁剧疆涓�0
+                    independentForwardSpeed = 0;
+                    // 鍙戦�佸仠姝㈡寚浠わ紙淇濇寔杞悜閫熷害涓嶅彉锛�
+                    Control03.setAndSendSpeeds(independentSteeringSpeed, 0);
+                }
+
+                // 鏇存柊椤堕儴鏄剧ず锛堜娇鐢ㄧ嫭绔嬬殑閫熷害鍊硷級
+                updateJoystickValues(forwardVal, independentSteeringSpeed);
             }
         });
         // 杞悜鎽囨潌锛堣摑鑹蹭富棰橈級
@@ -187,26 +201,37 @@
         turnJoystick.setJoystickListener(new JoystickListener() {
             @Override
             public void onJoystickMoved(double x, double y) {
-                // 鍙娇鐢╔杞存帶鍒跺乏鍙宠浆鍚戯紝鍚戝彸涓烘
-                // 璁$畻骞跺洓鑸嶄簲鍏ュ埌鏁存暟杞悜鍊硷紝姝d负鍙宠浆锛岃礋涓哄乏杞�
+                // 鍙娇鐢╔杞存帶鍒跺乏鍙宠浆鍚�
+                // x鍊艰寖鍥达細-1.0锛堝悜宸︼級鍒� 1.0锛堝悜鍙筹級
+                // 璁$畻杞悜鍊硷細鍚戝乏锛坸<0锛変负宸﹁浆锛堣礋鍊硷級锛屽悜鍙筹紙x>0锛変负鍙宠浆锛堟鍊硷級
+                // 宸﹁浆鍊艰寖鍥达細0 鍒� -100锛屽彸杞�艰寖鍥达細0 鍒� 100
                 int steeringVal = (int) Math.round(x * 100.0);
                 steeringVal = Math.max(-100, Math.min(100, steeringVal));
 
-                // 鏇存柊鐩爣閫熷害
-                targetSteeringSpeed = steeringVal;
-
-                if (Math.abs(x) > 0.1) {
-                    // 鎽囨潌涓嶅湪涓績浣嶇疆锛屽惎鍔ㄦ寔缁彂閫佸畾鏃跺櫒
-                    startSteeringControlTimer();
-                } else {
-                    // 鎽囨潌鍥炲埌涓績浣嶇疆锛屽仠姝㈠彂閫�
-                    stopSteeringControlTimer();
-                    stopSteering();
+                // 姝诲尯澶勭悊锛氬鏋滈�熷害鍊煎湪-10鍒�10涔嬮棿锛岃涓�0锛堥伩鍏嶅井灏忔姈鍔級
+                if (Math.abs(steeringVal) <= 10) {
+                    steeringVal = 0;
                 }
 
-                // 鏇存柊椤堕儴鏄剧ず锛堣浆鍚戞樉绀哄綋鍓嶈浆鍚戦�熷害锛岀Щ鍔ㄦ樉绀哄綋鍓嶅墠杩涢�熷害锛�
-                int forwardVal = Control03.getCurrentForwardSpeed();
-                updateJoystickValues(forwardVal, steeringVal);
+                // 鏇存柊鐩爣閫熷害鍜岀嫭绔嬮�熷害
+                targetSteeringSpeed = steeringVal;
+                independentSteeringSpeed = steeringVal;
+
+                if (steeringVal != 0) {
+                    // 鎽囨潌涓嶅湪姝诲尯锛屽惎鍔ㄦ寔缁彂閫佸畾鏃跺櫒
+                    // 宸﹁浆鏃� steeringVal 涓鸿礋鍊硷紙-100鍒�-11锛夛紝鍙宠浆鏃� steeringVal 涓烘鍊硷紙11鍒�100锛�
+                    startSteeringControlTimer();
+                } else {
+                    // 鎽囨潌鍦ㄦ鍖烘垨涓績浣嶇疆锛屽仠姝㈠畾鏃跺櫒
+                    stopSteeringControlTimer();
+                    // 灏嗙嫭绔嬬殑杞悜閫熷害璁剧疆涓�0
+                    independentSteeringSpeed = 0;
+                    // 鍙戦�佸仠姝㈡寚浠わ紙淇濇寔鍓嶈繘閫熷害涓嶅彉锛�
+                    Control03.setAndSendSpeeds(0, independentForwardSpeed);
+                }
+
+                // 鏇存柊椤堕儴鏄剧ず锛堜娇鐢ㄧ嫭绔嬬殑閫熷害鍊硷級
+                updateJoystickValues(independentForwardSpeed, steeringVal);
             }
         });
         joystickPanel.add(moveJoystick);
@@ -625,14 +650,9 @@
     }
 
     private void stopForward() {
-        if (Control03.getCurrentForwardSpeed() != 0) {
-            boolean success = Control03.approachForwardSpeedToZero(20);
-            if (!success) {
-                showSerialClosedWarning();
-                return;
-            }
-            serialWarningShown = false;
-        }
+        // 灏嗙嫭绔嬬殑鍓嶈繘閫熷害璁剧疆涓�0锛屼繚鎸佽浆鍚戦�熷害涓嶅彉
+        independentForwardSpeed = 0;
+        Control03.setAndSendSpeeds(independentSteeringSpeed, 0);
     }
 
     private void applySteeringSpeed(int speed) {
@@ -655,14 +675,9 @@
     }
 
     private void stopSteering() {
-        if (Control03.getCurrentSteeringSpeed() != 0) {
-            boolean success = Control03.approachSteeringSpeedToZero(25);
-            if (!success) {
-                showSerialClosedWarning();
-                return;
-            }
-            serialWarningShown = false;
-        }
+        // 灏嗙嫭绔嬬殑杞悜閫熷害璁剧疆涓�0锛屼繚鎸佸墠杩涢�熷害涓嶅彉
+        independentSteeringSpeed = 0;
+        Control03.setAndSendSpeeds(0, independentForwardSpeed);
     }
 
     /**
@@ -729,36 +744,24 @@
      * 鎸佺画鍙戦�佸墠杩�/鍚庨��閫熷害鎸囦护
      */
     private void applyForwardSpeedContinuously(int targetSpeed) {
-        int currentSpeed = Control03.getCurrentForwardSpeed();
-        int currentSteeringSpeed = Control03.getCurrentSteeringSpeed();
+        // 鏇存柊鐙珛鐨勫墠杩涢�熷害
+        independentForwardSpeed = targetSpeed;
         
-        // 濡傛灉宸茬粡杈惧埌鐩爣閫熷害锛岀洿鎺ュ彂閫佷竴娆′互淇濇寔鐘舵��
-        if (currentSpeed == targetSpeed) {
-            // 鐩存帴鍙戦�佺洰鏍囬�熷害鎸囦护浠ヤ繚鎸佺姸鎬侊紙鍗充娇閫熷害鐩稿悓涔熻鍙戦�侊級
-            Control03.setAndSendSpeeds(currentSteeringSpeed, targetSpeed);
-        } else {
-            // 閫愭璋冩暣鍒扮洰鏍囬�熷害
-            int delta = targetSpeed > currentSpeed ? 10 : -10;
-            Control03.adjustForwardSpeed(delta);
-        }
+        // 浣跨敤鐙珛鐨勮浆鍚戦�熷害锛堜笉鍙楀墠杩涙憞鏉嗗奖鍝嶏級锛屽彧鏇存柊鍓嶈繘閫熷害
+        // 杩欐牱鍓嶈繘鎽囨潌鐨勬搷浣滀笉浼氬奖鍝嶈浆鍚戦�熷害
+        Control03.setAndSendSpeeds(independentSteeringSpeed, independentForwardSpeed);
     }
 
     /**
      * 鎸佺画鍙戦�佽浆鍚戦�熷害鎸囦护
      */
     private void applySteeringSpeedContinuously(int targetSpeed) {
-        int currentSpeed = Control03.getCurrentSteeringSpeed();
-        int currentForwardSpeed = Control03.getCurrentForwardSpeed();
+        // 鏇存柊鐙珛鐨勮浆鍚戦�熷害
+        independentSteeringSpeed = targetSpeed;
         
-        // 濡傛灉宸茬粡杈惧埌鐩爣閫熷害锛岀洿鎺ュ彂閫佷竴娆′互淇濇寔鐘舵��
-        if (currentSpeed == targetSpeed) {
-            // 鐩存帴鍙戦�佺洰鏍囬�熷害鎸囦护浠ヤ繚鎸佺姸鎬侊紙鍗充娇閫熷害鐩稿悓涔熻鍙戦�侊級
-            Control03.setAndSendSpeeds(targetSpeed, currentForwardSpeed);
-        } else {
-            // 閫愭璋冩暣鍒扮洰鏍囬�熷害
-            int delta = targetSpeed > currentSpeed ? 15 : -15;
-            Control03.adjustSteeringSpeed(delta);
-        }
+        // 浣跨敤鐙珛鐨勫墠杩涢�熷害锛堜笉鍙楄浆鍚戞憞鏉嗗奖鍝嶏級锛屽彧鏇存柊杞悜閫熷害
+        // 杩欐牱杞悜鎽囨潌鐨勬搷浣滀笉浼氬奖鍝嶅墠杩涢�熷害
+        Control03.setAndSendSpeeds(independentSteeringSpeed, independentForwardSpeed);
     }
 
     // 鏇存柊椤堕儴鏄剧ず鐨勬憞鏉嗘暟鍊硷紙鍦� EDT 涓婅皟鐢級锛屾枃瀛楁牴鎹暟鍊兼槧灏勪负鏂瑰悜鎻忚堪
diff --git a/src/zhuye/LegendDialog.java b/src/zhuye/LegendDialog.java
index d595034..7469b67 100644
--- a/src/zhuye/LegendDialog.java
+++ b/src/zhuye/LegendDialog.java
@@ -32,6 +32,29 @@
         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));
@@ -61,7 +84,8 @@
             contentPanel.remove(contentPanel.getComponentCount() - 1);
         }
         
-        // 鐩存帴娣诲姞鍐呭闈㈡澘锛屼笉浣跨敤婊氬姩鏉�
+        // 娣诲姞鍥炬爣闈㈡澘鍜屽浘渚嬪唴瀹归潰鏉�
+        mainPanel.add(iconPanel, BorderLayout.NORTH);
         mainPanel.add(contentPanel, BorderLayout.CENTER);
         
         getContentPane().add(mainPanel);
@@ -154,4 +178,37 @@
         
         return itemPanel;
     }
+    
+    /**
+     * 鍔犺浇骞剁缉鏀惧浘鏍�
+     * @param iconPath 鍥炬爣璺緞
+     * @param width 鐩爣瀹藉害
+     * @param height 鐩爣楂樺害
+     * @return 缂╂斁鍚庣殑鍥炬爣
+     */
+    private ImageIcon loadIcon(String iconPath, int width, int height) {
+        try {
+            java.net.URL imgURL = getClass().getClassLoader().getResource(iconPath);
+            if (imgURL == null) {
+                // 灏濊瘯浠庢枃浠剁郴缁熷姞杞�
+                java.io.File imgFile = new java.io.File(iconPath);
+                if (imgFile.exists()) {
+                    ImageIcon originalIcon = new ImageIcon(imgFile.getAbsolutePath());
+                    Image scaledImage = originalIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
+                    ImageIcon scaledIcon = new ImageIcon(scaledImage);
+                    scaledIcon.setDescription(iconPath);
+                    return scaledIcon;
+                }
+            } else {
+                ImageIcon originalIcon = new ImageIcon(imgURL);
+                Image scaledImage = originalIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
+                ImageIcon scaledIcon = new ImageIcon(scaledImage);
+                scaledIcon.setDescription(iconPath);
+                return scaledIcon;
+            }
+        } catch (Exception e) {
+            System.err.println("鏃犳硶鍔犺浇鍥炬爣: " + iconPath + " - " + e.getMessage());
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/src/zhuye/MapRenderer.java b/src/zhuye/MapRenderer.java
index 635a1b1..8083749 100644
--- a/src/zhuye/MapRenderer.java
+++ b/src/zhuye/MapRenderer.java
@@ -18,6 +18,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import set.Setsys;
 import gecaoji.Device;
 import gecaoji.Gecaoji;
 import gecaoji.GecaojiMeg;
@@ -35,13 +36,15 @@
  */
 public class MapRenderer {
     // 瑙嗗浘鍙樻崲鍙傛暟
-    private double scale = 1.0;
+    private static final double DEFAULT_SCALE = 20.0; // 榛樿缂╂斁姣斾緥
+    private double scale = DEFAULT_SCALE;
     private double translateX = 0.0;
     private double translateY = 0.0;
     private Point lastDragPoint;
     private static final double MIN_SCALE = 0.05d;
     private static final double MAX_SCALE = 50.0d;
     private static final double SCALE_EPSILON = 1e-6d;
+    private static final String MAP_SCALE_PROPERTY = "mapScale"; // 灞炴�ф枃浠朵腑鐨勯敭鍚�
     
     // 涓婚棰滆壊
     private final Color THEME_COLOR = new Color(46, 139, 87);
@@ -111,6 +114,40 @@
         this.mowerUpdateTimer = createMowerTimer();
         this.mowerInfoManager = new GecaojiMeg(visualizationPanel, mower);
         setupMouseListeners();
+        // 浠庨厤缃枃浠惰鍙栦笂娆′繚瀛樼殑缂╂斁姣斾緥
+        loadScaleFromProperties();
+    }
+    
+    /**
+     * 浠庨厤缃枃浠惰鍙栫缉鏀炬瘮渚�
+     */
+    private void loadScaleFromProperties() {
+        String scaleValue = Setsys.getPropertyValue(MAP_SCALE_PROPERTY);
+        if (scaleValue != null && !scaleValue.trim().isEmpty()) {
+            try {
+                double savedScale = Double.parseDouble(scaleValue.trim());
+                // 楠岃瘉缂╂斁姣斾緥鏄惁鍦ㄦ湁鏁堣寖鍥村唴
+                if (savedScale >= MIN_SCALE && savedScale <= MAX_SCALE) {
+                    scale = savedScale;
+                } else {
+                    scale = DEFAULT_SCALE;
+                }
+            } catch (NumberFormatException e) {
+                // 濡傛灉瑙f瀽澶辫触锛屼娇鐢ㄩ粯璁ゅ��
+                scale = DEFAULT_SCALE;
+            }
+        } else {
+            // 濡傛灉娌℃湁淇濆瓨鐨勫�硷紝浣跨敤榛樿鍊�
+            scale = DEFAULT_SCALE;
+        }
+    }
+    
+    /**
+     * 淇濆瓨缂╂斁姣斾緥鍒伴厤缃枃浠�
+     */
+    private void saveScaleToProperties() {
+        Setsys setsys = new Setsys();
+        setsys.updateProperty(MAP_SCALE_PROPERTY, String.valueOf(scale));
     }
     
     /**
@@ -218,6 +255,8 @@
         translateX += (newWorldX - worldX);
         translateY += (newWorldY - worldY);
 
+        // 淇濆瓨缂╂斁姣斾緥鍒伴厤缃枃浠�
+        saveScaleToProperties();
         visualizationPanel.repaint();
     }
 
@@ -253,9 +292,11 @@
      * 閲嶇疆瑙嗗浘
      */
     public void resetView() {
-        scale = 1.0;
+        scale = DEFAULT_SCALE;
         translateX = 0.0;
         translateY = 0.0;
+        // 淇濆瓨缂╂斁姣斾緥鍒伴厤缃枃浠�
+        saveScaleToProperties();
         visualizationPanel.repaint();
     }
     
@@ -482,7 +523,8 @@
         if (device == null) {
             return;
         }
-        if (!isHighPrecisionFix(device.getPositioningStatus())) {
+        // 浣跨敤鏇村鏉剧殑瀹氫綅鐘舵�佸垽鏂紝鍏佽鐘舵��1鍜�4鏄剧ず鎷栧熬
+        if (!isValidFixForTrail(device.getPositioningStatus())) {
             return;
         }
 
@@ -504,6 +546,56 @@
         idleMowerTrail.addLast(new tuowei.TrailSample(now, new Point2D.Double(position.x, position.y)));
         pruneIdleMowerTrail(now);
     }
+    
+    /**
+     * 寮哄埗鏇存柊鎷栧熬锛堢敤浜庢敹鍒�$GNGGA鏁版嵁鏃剁珛鍗虫洿鏂帮級
+     * 杩欎釜鏂规硶浼氬埛鏂癿ower浣嶇疆骞剁珛鍗虫坊鍔犲埌鎷栧熬
+     */
+    public void forceUpdateIdleMowerTrail() {
+        long now = System.currentTimeMillis();
+        pruneIdleMowerTrail(now);
+
+        if (idleTrailSuppressed || realtimeTrackRecording) {
+            if (!idleMowerTrail.isEmpty()) {
+                clearIdleMowerTrail();
+            }
+            return;
+        }
+
+        Device device = Device.getGecaoji();
+        if (device == null) {
+            return;
+        }
+        // 浣跨敤鏇村鏉剧殑瀹氫綅鐘舵�佸垽鏂紝鍏佽鐘舵��1鍜�4鏄剧ず鎷栧熬
+        if (!isValidFixForTrail(device.getPositioningStatus())) {
+            return;
+        }
+
+        // 鍒锋柊mower浣嶇疆锛屼娇鐢ㄦ渶鏂扮殑Device鏁版嵁
+        mower.refreshFromDevice();
+        Point2D.Double position = mower.getPosition();
+        if (position == null || !Double.isFinite(position.x) || !Double.isFinite(position.y)) {
+            return;
+        }
+
+        tuowei.TrailSample lastSample = idleMowerTrail.peekLast();
+        if (lastSample != null) {
+            Point2D.Double lastPoint = lastSample.getPoint();
+            double dx = position.x - lastPoint.x;
+            double dy = position.y - lastPoint.y;
+            if (Math.hypot(dx, dy) < IDLE_TRAIL_SAMPLE_DISTANCE_METERS) {
+                return;
+            }
+        }
+
+        idleMowerTrail.addLast(new tuowei.TrailSample(now, new Point2D.Double(position.x, position.y)));
+        pruneIdleMowerTrail(now);
+        
+        // 绔嬪嵆閲嶇粯锛岀‘淇濇嫋灏惧強鏃舵樉绀�
+        if (visualizationPanel != null) {
+            visualizationPanel.repaint();
+        }
+    }
 
     private void pruneIdleMowerTrail(long now) {
         if (idleMowerTrail.isEmpty()) {
@@ -1233,6 +1325,31 @@
             return false;
         }
     }
+    
+    /**
+     * 鍒ゆ柇瀹氫綅鐘舵�佹槸鍚︽湁鏁堬紝鍙敤浜庢樉绀烘嫋灏�
+     * 鎺ュ彈鐘舵��1锛堝崟鐐瑰畾浣嶏級鍜�4锛堝浐瀹氳В锛�
+     */
+    private boolean isValidFixForTrail(String fixQuality) {
+        if (fixQuality == null) {
+            return false;
+        }
+        String trimmed = fixQuality.trim();
+        if (trimmed.isEmpty()) {
+            return false;
+        }
+        // 鎺ュ彈鐘舵��1锛堝崟鐐瑰畾浣嶏級鍜�4锛堝浐瀹氳В锛�
+        if ("1".equals(trimmed) || "4".equals(trimmed)) {
+            return true;
+        }
+        try {
+            double value = Double.parseDouble(trimmed);
+            // 鎺ュ彈1.0鎴�4.0
+            return Math.abs(value - 1.0d) < 1e-6 || Math.abs(value - 4.0d) < 1e-6;
+        } catch (NumberFormatException ex) {
+            return false;
+        }
+    }
 
     private boolean isPointInsideActiveBoundary(Point2D.Double point) {
         if (point == null || !Double.isFinite(point.x) || !Double.isFinite(point.y)) {
@@ -1339,7 +1456,15 @@
      * 璁剧疆瑙嗗浘鍙樻崲鍙傛暟锛堢敤浜庣▼搴忓寲鎺у埗锛�
      */
     public void setViewTransform(double scale, double translateX, double translateY) {
-        this.scale = scale;
+        // 闄愬埗缂╂斁鑼冨洿
+        scale = Math.max(MIN_SCALE, Math.min(scale, MAX_SCALE));
+        // 濡傛灉缂╂斁姣斾緥鏀瑰彉浜嗭紝淇濆瓨鍒伴厤缃枃浠�
+        if (Math.abs(this.scale - scale) > SCALE_EPSILON) {
+            this.scale = scale;
+            saveScaleToProperties();
+        } else {
+            this.scale = scale;
+        }
         this.translateX = translateX;
         this.translateY = translateY;
         visualizationPanel.repaint();
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index 29a287f..7d78ab2 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -91,10 +91,20 @@
 	private Sets settingsDialog;
 	private BaseStation baseStation;
 
-	private final Consumer<String> serialLineListener = line -> SwingUtilities.invokeLater(this::updateDataPacketCountLabel);
-
 	// 鍦板浘娓叉煋鍣�
 	private MapRenderer mapRenderer;
+	
+	private final Consumer<String> serialLineListener = line -> {
+		SwingUtilities.invokeLater(() -> {
+			updateDataPacketCountLabel();
+			// 濡傛灉鏀跺埌$GNGGA鏁版嵁锛岀珛鍗虫洿鏂版嫋灏�
+			if (line != null && line.trim().startsWith("$GNGGA")) {
+				if (mapRenderer != null) {
+					mapRenderer.forceUpdateIdleMowerTrail();
+				}
+			}
+		});
+	};
 	private static final int FLOAT_ICON_SIZE = 32;
 	private JButton endDrawingButton;
 	private JButton drawingPauseButton;
@@ -208,12 +218,42 @@
 					SwingUtilities.invokeLater(() -> {
 						Shouye.this.checkIdentifiersAndPromptIfNeeded();
 						Shouye.this.showInitialMowerSelfCheckDialogIfNeeded();
+						// 璁剧疆绐楀彛鍏抽棴鐩戝惉鍣紝鍦ㄥ叧闂椂淇濆瓨缂╂斁姣斾緥
+						setupWindowCloseListener();
 					});
 				}
 			}
 		};
 		addHierarchyListener(listener);
 	}
+	
+	/**
+	 * 璁剧疆绐楀彛鍏抽棴鐩戝惉鍣紝鍦ㄧ獥鍙e叧闂椂淇濆瓨褰撳墠缂╂斁姣斾緥
+	 */
+	private void setupWindowCloseListener() {
+		Window window = SwingUtilities.getWindowAncestor(this);
+		if (window != null && window instanceof JFrame) {
+			JFrame frame = (JFrame) window;
+			frame.addWindowListener(new WindowAdapter() {
+				@Override
+				public void windowClosing(WindowEvent e) {
+					// 淇濆瓨褰撳墠缂╂斁姣斾緥
+					saveCurrentScale();
+				}
+			});
+		}
+	}
+	
+	/**
+	 * 淇濆瓨褰撳墠鍦板浘缂╂斁姣斾緥鍒伴厤缃枃浠�
+	 */
+	public void saveCurrentScale() {
+		if (mapRenderer != null) {
+			double currentScale = mapRenderer.getScale();
+			Setsys setsys = new Setsys();
+			setsys.updateProperty("mapScale", String.valueOf(currentScale));
+		}
+	}
 
 	private void showInitialMowerSelfCheckDialogIfNeeded() {
 		// 宸茬Щ闄よ繘鍏ヤ富椤垫椂鐨勮嚜妫�鎻愮ず锛堟寜鐢ㄦ埛瑕佹眰鍒犻櫎锛�
@@ -352,6 +392,37 @@
 
 		// 鍙鍖栧尯鍩� - 浣跨敤MapRenderer杩涜缁樺埗
 		visualizationPanel = new JPanel() {
+			private ImageIcon gecaojiIcon = null;
+			private static final int GECAOJI_ICON_X = 37;
+			private static final int GECAOJI_ICON_Y = 10;
+			private static final int GECAOJI_ICON_SIZE = 20;
+			
+			{
+				// 鍔犺浇鍓茶崏鏈哄浘鏍囷紝澶у皬20x20鍍忕礌
+				gecaojiIcon = loadScaledIcon("image/gecaoji.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
+				// 鍚敤宸ュ叿鎻愮ず
+				setToolTipText("");
+			}
+			
+			/**
+			 * 妫�鏌ラ紶鏍囦綅缃槸鍚﹀湪鍓茶崏鏈哄浘鏍囧尯鍩熷唴
+			 */
+			private boolean isMouseOnGecaojiIcon(Point mousePoint) {
+				return mousePoint.x >= GECAOJI_ICON_X && 
+				       mousePoint.x <= GECAOJI_ICON_X + GECAOJI_ICON_SIZE &&
+				       mousePoint.y >= GECAOJI_ICON_Y && 
+				       mousePoint.y <= GECAOJI_ICON_Y + GECAOJI_ICON_SIZE;
+			}
+			
+			@Override
+			public String getToolTipText(MouseEvent event) {
+				// 濡傛灉榧犳爣鍦ㄥ壊鑽夋満鍥炬爣鍖哄煙鍐咃紝鏄剧ず鎻愮ず鏂囧瓧
+				if (isMouseOnGecaojiIcon(event.getPoint())) {
+					return "浠ュ壊鑽夋満涓轰腑蹇�";
+				}
+				return super.getToolTipText(event);
+			}
+			
 			@Override
 			protected void paintComponent(Graphics g) {
 				super.paintComponent(g);
@@ -359,14 +430,48 @@
 				if (mapRenderer != null) {
 					mapRenderer.renderMap(g);
 				}
+				// 鍦ㄥ湴鍥惧乏涓婅缁樺埗鍓茶崏鏈哄浘鏍�
+				// 姘村钩鏂瑰悜涓庨�熷害鎸囩ず鍣ㄥ榻愶紙x=37锛�
+				// 鍨傜洿鏂瑰悜涓庡崼鏄熺姸鎬佸浘鏍囧榻愶紙y=10锛岄�熷害鎸囩ず鍣ㄩ潰鏉块《閮ㄨ竟璺�10鍍忕礌锛屼娇鍥炬爣涓績瀵归綈锛�
+				if (gecaojiIcon != null) {
+					g.drawImage(gecaojiIcon.getImage(), GECAOJI_ICON_X, GECAOJI_ICON_Y, null);
+				}
 			}
 		};
 		visualizationPanel.setLayout(new BorderLayout());
+		
+		// 娣诲姞榧犳爣鐐瑰嚮鐩戝惉鍣紝妫�娴嬫槸鍚︾偣鍑讳簡鍓茶崏鏈哄浘鏍�
+		visualizationPanel.addMouseListener(new MouseAdapter() {
+			@Override
+			public void mouseClicked(MouseEvent e) {
+				if (SwingUtilities.isLeftMouseButton(e)) {
+					Point clickPoint = e.getPoint();
+					// 妫�鏌ユ槸鍚︾偣鍑讳簡鍓茶崏鏈哄浘鏍囧尯鍩燂紙37, 10, 20, 20锛�
+					if (clickPoint.x >= 37 && clickPoint.x <= 57 && 
+					    clickPoint.y >= 10 && clickPoint.y <= 30) {
+						// 鐐瑰嚮浜嗗壊鑽夋満鍥炬爣锛屽皢鍦板浘瑙嗗浘涓績绉诲姩鍒板壊鑽夋満浣嶇疆
+						if (mapRenderer != null) {
+							Gecaoji mower = mapRenderer.getMower();
+							if (mower != null && mower.hasValidPosition()) {
+								Point2D.Double mowerPosition = mower.getPosition();
+								if (mowerPosition != null) {
+									// 鑾峰彇褰撳墠缂╂斁姣斾緥
+									double currentScale = mapRenderer.getScale();
+									// 璁剧疆瑙嗗浘鍙樻崲锛屼娇鍓茶崏鏈轰綅缃搴斿埌灞忓箷涓績
+									// translateX = -mowerX, translateY = -mowerY 鍙互璁╁壊鑽夋満鍦ㄥ睆骞曚腑蹇�
+									mapRenderer.setViewTransform(currentScale, -mowerPosition.x, -mowerPosition.y);
+								}
+							}
+						}
+					}
+				}
+			}
+		});
 
 		JPanel speedIndicatorPanel = createSpeedIndicatorPanel();
 		visualizationPanel.add(speedIndicatorPanel, BorderLayout.NORTH);
 
-		// 鍒涘缓鍔熻兘鎸夐挳闈㈡澘锛堟斁鍦ㄥ乏涓婅锛�
+		// 鍒涘缓鍔熻兘鎸夐挳闈㈡澘
 		JPanel functionButtonsPanel = new JPanel();
 		functionButtonsPanel.setLayout(new BoxLayout(functionButtonsPanel, BoxLayout.Y_AXIS));
 		functionButtonsPanel.setOpaque(false);

--
Gitblit v1.10.0