826220679@qq.com
17 小时以前 f94b1436d7a28c8e28d010b2cb657ab7c064e353
修改了导航预览
已修改10个文件
已添加1个文件
810 ■■■■■ 文件已修改
Obstacledge.properties 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
dikuai.properties 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
set.properties 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/dikuai/daohangyulan.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/gecaoji/Gecaoji.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lujing/DebugPath.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lujing/YixinglujingHaveObstacel.java 357 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lujing/YixinglujingNoObstacle.java 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhangaiwu/AddDikuai.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhuye/MapRenderer.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhuye/Shouye.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Obstacledge.properties
@@ -1,5 +1,5 @@
# å‰²è‰æœºåœ°å—障碍物配置文件
# ç”Ÿæˆæ—¶é—´ï¼š2025-12-27T23:31:09.655894400
# ç”Ÿæˆæ—¶é—´ï¼š2025-12-28T20:21:18.532091
# åæ ‡ç³»ï¼šWGS84(度分格式)
# ============ åœ°å—基准站配置 ============
dikuai.properties
@@ -1,5 +1,5 @@
#Dikuai Properties
#Sat Dec 27 23:31:09 GMT+08:00 2025
#Sun Dec 28 20:21:18 GMT+08:00 2025
LAND1.intelligentSceneAnalysis=-1
LAND1.mowingSafetyDistance=0.53
LAND1.landArea=577.12
@@ -11,8 +11,8 @@
LAND1.returnPathRawCoordinates=-1
LAND1.boundaryOriginalXY=-1
LAND1.mowingWidth=1.00
LAND1.plannedPath=73.82,49.86;50.78,49.99,M;41.98,22.49,M;49.63,20.04,M;51.80,34.27,M;66.28,36.75,M;66.34,21.40,M;78.08,24.80,M;73.82,49.86,M;73.87,49.87;73.87,49.87,T;73.87,49.87,T;73.87,49.87,T;73.87,49.87,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.91,49.36;50.66,49.52,M;50.62,49.54;50.62,49.54,T;50.62,49.54,T;50.62,49.54,T;50.62,49.54,T;50.30,48.54,T;50.30,48.54,T;50.30,48.54,T;50.30,48.54,T;50.30,48.54,T;50.35,48.53;74.07,48.36,M;74.12,48.37;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.24,47.36;50.03,47.53,M;49.98,47.54;49.98,47.54,T;49.98,47.54,T;49.98,47.54,T;49.98,47.54,T;49.66,46.55,T;49.66,46.55,T;49.66,46.55,T;49.66,46.55,T;49.66,46.55,T;49.71,46.53;74.41,46.36,M;74.46,46.37;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.58,45.36;49.39,45.53,M;49.34,45.55;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.07,44.53;74.75,44.36,M;74.80,44.37;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.91,43.36;48.75,43.54,M;48.71,43.55;48.71,43.55,T;48.71,43.55,T;48.71,43.55,T;48.71,43.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.43,42.54;75.08,42.36,M;75.13,42.36;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.25,41.35;48.12,41.54,M;48.07,41.56;48.07,41.56,T;48.07,41.56,T;48.07,41.56,T;48.07,41.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;75.47,40.36,M;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.59,39.35;47.48,39.55,M;47.43,39.56;47.43,39.56,T;47.43,39.56,T;47.43,39.56,T;47.43,39.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.16,38.55;75.75,38.35,M;75.80,38.36;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;46.79,37.57,M;46.79,37.57,T;46.79,37.57,T;46.79,37.57,T;46.79,37.57,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.34,36.43;76.09,36.35,M;76.14,36.36;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;66.29,35.43,M;66.29,35.43,T;66.29,35.43,T;66.29,35.43,T;66.29,35.43,T;66.29,34.43,T;66.29,34.43,T;66.29,34.43,T;66.29,34.43,T;66.29,34.43,T;66.34,34.43;76.43,34.35,M;76.48,34.35;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.60,33.34;66.34,33.43,M;66.29,33.43;66.29,33.43,T;66.29,33.43,T;66.29,33.43,T;66.29,33.43,T;66.29,32.43,T;66.29,32.43,T;66.29,32.43,T;66.29,32.43,T;66.29,32.43,T;66.34,32.43;76.76,32.34,M;76.81,32.35;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.93,31.34;66.34,31.43,M;66.29,31.43;66.29,31.43,T;66.29,31.43,T;66.29,31.43,T;66.29,31.43,T;66.29,30.43,T;66.29,30.43,T;66.29,30.43,T;66.29,30.43,T;66.29,30.43,T;66.34,30.43;77.10,30.34,M;77.15,30.35;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.27,29.34;66.34,29.43,M;66.29,29.43;66.29,29.43,T;66.29,29.43,T;66.29,29.43,T;66.29,29.43,T;66.29,28.43,T;66.29,28.43,T;66.29,28.43,T;66.29,28.43,T;66.29,28.43,T;66.34,28.43;77.44,28.34,M;77.48,28.35;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.60,27.34;66.34,27.43,M;66.29,27.43;66.29,27.43,T;66.29,27.43,T;66.29,27.43,T;66.29,27.43,T;66.29,26.43,T;66.29,26.43,T;66.29,26.43,T;66.29,26.43,T;66.29,26.43,T;66.34,26.43;77.77,26.34,M;77.82,26.34;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.94,25.34;66.34,25.43,M;66.29,25.43;66.29,25.43,T;66.29,25.43,T;66.29,25.43,T;66.29,25.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.34,24.43;76.67,24.40,M;76.69,24.35;76.69,24.35,T;76.69,24.35,T;76.69,24.35,T;76.69,24.35,T;73.25,23.38,T;73.25,23.38,T;73.25,23.38,T;73.25,23.38,T;73.25,23.38,T;73.24,23.43;66.34,23.43,M;66.29,23.43;66.29,23.43,T;66.29,23.43,T;66.29,23.43,T;66.29,23.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.34,22.43;69.80,22.45,M;69.81,22.40;69.81,22.40,T;69.81,22.40,T;69.81,22.40,T;69.81,22.40,T;66.38,21.43,T;66.38,21.43,T;66.38,21.43,T;66.38,21.43,T;66.38,21.43,T;66.36,21.47;66.34,21.43,M;66.29,21.43;66.29,21.43,T;66.29,21.43,T;66.29,21.43,T;66.29,21.43,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;66.29,36.70,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.87,20.60;49.65,20.55,M;49.70,20.54;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;41.97,22.44,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.52,36.55;64.75,36.49,M;64.75,36.44;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.06,35.53;46.20,35.55,M;46.16,35.57;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.89,34.56;53.37,34.57,M;53.38,34.52;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.68,33.54;45.57,33.56,M;45.52,33.57;45.52,33.57,T;45.52,33.57,T;45.52,33.57,T;45.52,33.57,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.25,32.56;51.53,32.54,M;51.57,32.53;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.37,31.54;44.93,31.56,M;44.88,31.58;44.88,31.58,T;44.88,31.58,T;44.88,31.58,T;44.88,31.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.61,30.57;51.21,30.54,M;51.26,30.53;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.06,29.54;44.29,29.57,M;44.25,29.58;44.25,29.58,T;44.25,29.58,T;44.25,29.58,T;44.25,29.58,T;43.93,28.59,T;43.93,28.59,T;43.93,28.59,T;43.93,28.59,T;43.93,28.59,T;43.97,28.57;50.90,28.54,M;50.95,28.54;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.74,27.54;43.66,27.57,M;43.61,27.59;43.61,27.59,T;43.61,27.59,T;43.61,27.59,T;43.61,27.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.34,26.57;50.59,26.55,M;50.64,26.54;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.43,25.55;43.02,25.58,M;42.97,25.59;42.97,25.59,T;42.97,25.59,T;42.97,25.59,T;42.97,25.59,T;42.65,24.59,T;42.65,24.59,T;42.65,24.59,T;42.65,24.59,T;42.65,24.59,T;42.70,24.58;50.27,24.55,M;50.32,24.54;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.12,23.55;42.38,23.58,M;42.33,23.60;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.06,22.58;49.96,22.55,M;50.01,22.54;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.81,21.55;44.68,21.63,M
LAND1.updateTime=2025-12-27 23\:31\:09
LAND1.plannedPath=73.82,49.86;50.78,49.99,M;41.98,22.49,M;49.63,20.04,M;51.80,34.27,M;66.28,36.75,M;66.34,21.40,M;78.08,24.80,M;73.82,49.86,M;73.87,49.87;73.87,49.87,T;73.87,49.87,T;73.87,49.87,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.96,49.37,T;73.91,49.36;50.66,49.52,M;50.62,49.54;50.30,48.54,T;50.35,48.53;74.07,48.36,M;74.12,48.37;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.12,48.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.29,47.37,T;74.24,47.36;50.03,47.53,M;49.98,47.54;49.66,46.55,T;49.71,46.53;74.41,46.36,M;74.46,46.37;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.46,46.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.63,45.37,T;74.58,45.36;49.39,45.53,M;49.34,45.55;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.34,45.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.02,44.55,T;49.07,44.53;74.75,44.36,M;74.80,44.37;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.80,44.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.96,43.37,T;74.91,43.36;48.75,43.54,M;48.71,43.55;48.71,43.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.39,42.55,T;48.43,42.54;75.08,42.36,M;75.13,42.36;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.13,42.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.30,41.36,T;75.25,41.35;48.12,41.54,M;48.07,41.56;48.07,41.56,T;48.07,41.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;47.75,40.56,T;75.47,40.36,M;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.47,40.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.64,39.36,T;75.59,39.35;47.48,39.55,M;47.43,39.56;47.43,39.56,T;47.43,39.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.11,38.56,T;47.16,38.55;75.75,38.35,M;75.80,38.36;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.80,38.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;75.97,37.36,T;46.79,37.57,M;46.79,37.57,T;41.97,22.44,T;49.61,19.99,T;51.85,34.26,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;65.10,36.50,T;66.29,36.70,T;66.29,36.70,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.29,36.43,T;66.34,36.43;76.09,36.35,M;76.14,36.36;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.14,36.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;76.31,35.36,T;66.29,35.43,M;66.29,34.43,T;66.34,34.43;76.43,34.35,M;76.48,34.35;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.48,34.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.64,33.35,T;76.60,33.34;66.34,33.43,M;66.29,33.43;66.29,32.43,T;66.34,32.43;76.76,32.34,M;76.81,32.35;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.81,32.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.98,31.35,T;76.93,31.34;66.34,31.43,M;66.29,31.43;66.29,30.43,T;66.34,30.43;77.10,30.34,M;77.15,30.35;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.15,30.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.32,29.35,T;77.27,29.34;66.34,29.43,M;66.29,29.43;66.29,28.43,T;66.34,28.43;77.44,28.34,M;77.48,28.35;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.48,28.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.65,27.35,T;77.60,27.34;66.34,27.43,M;66.29,27.43;66.29,26.43,T;66.34,26.43;77.77,26.34,M;77.82,26.34;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.82,26.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.99,25.34,T;77.94,25.34;66.34,25.43,M;66.29,25.43;66.29,25.43,T;66.29,25.43,T;66.29,25.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.29,24.43,T;66.34,24.43;76.67,24.40,M;76.69,24.35;73.25,23.38,T;73.24,23.43;66.34,23.43,M;66.29,23.43;66.29,23.43,T;66.29,23.43,T;66.29,23.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.29,22.43,T;66.34,22.43;69.80,22.45,M;69.81,22.40;66.38,21.43,T;66.36,21.47;66.34,21.43,M;66.29,21.43;66.29,21.43,T;66.29,21.43,T;66.29,21.43,T;66.29,36.70,T;66.29,36.70,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.71,20.65,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.85,20.56,T;47.87,20.60;49.65,20.55,M;49.70,20.54;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.70,20.54,T;49.61,19.99,T;49.61,19.99,T;49.61,19.99,T;41.97,22.44,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.48,36.57,T;46.52,36.55;64.75,36.49,M;64.75,36.44;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;64.75,36.44,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.07,35.48,T;59.06,35.53;46.20,35.55,M;46.16,35.57;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;46.16,35.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.84,34.57,T;45.89,34.56;53.37,34.57,M;53.38,34.52;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;53.38,34.52,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.85,34.26,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.73,33.53,T;51.68,33.54;45.57,33.56,M;45.52,33.57;45.52,33.57,T;45.52,33.57,T;45.52,33.57,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.20,32.58,T;45.25,32.56;51.53,32.54,M;51.57,32.53;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.57,32.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.42,31.53,T;51.37,31.54;44.93,31.56,M;44.88,31.58;44.88,31.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.56,30.58,T;44.61,30.57;51.21,30.54,M;51.26,30.53;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.26,30.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.11,29.53,T;51.06,29.54;44.29,29.57,M;44.25,29.58;43.93,28.59,T;43.97,28.57;50.90,28.54,M;50.95,28.54;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.95,28.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.79,27.54,T;50.74,27.54;43.66,27.57,M;43.61,27.59;43.61,27.59,T;43.61,27.59,T;43.29,26.59,T;43.29,26.59,T;43.29,26.59,T;43.34,26.57;50.59,26.55,M;50.64,26.54;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.64,26.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.48,25.54,T;50.43,25.55;43.02,25.58,M;42.97,25.59;42.65,24.59,T;42.70,24.58;50.27,24.55,M;50.32,24.54;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.32,24.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.17,23.54,T;50.12,23.55;42.38,23.58,M;42.33,23.60;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.33,23.60,T;42.02,22.60,T;42.02,22.60,T;42.02,22.60,T;42.06,22.58;49.96,22.55,M;50.01,22.54;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;50.01,22.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.85,21.54,T;49.81,21.55;44.68,21.63,M
LAND1.updateTime=2025-12-28 20\:21\:18
LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E
LAND1.boundaryPointInterval=-1
LAND1.createTime=2025-12-23 17\:08\:09
set.properties
@@ -1,5 +1,5 @@
#Mower Configuration Properties - Updated
#Sat Dec 27 23:41:35 GMT+08:00 2025
#Sun Dec 28 20:35:53 GMT+08:00 2025
appVersion=-1
simCardNumber=-1
currentWorkLandNumber=LAND1
@@ -7,8 +7,8 @@
boundaryLengthVisible=false
idleTrailDurationSeconds=60
handheldMarkerId=1872
viewCenterX=-57.93
viewCenterY=-27.08
viewCenterX=-60.00
viewCenterY=-34.94
manualBoundaryDrawingMode=false
mowerId=6258
serialPortName=COM15
src/dikuai/daohangyulan.java
@@ -10,6 +10,7 @@
import zhuye.Shouye;
import zhuye.MapRenderer;
import gecaoji.Gecaoji;
import gecaoji.Device;
import gecaoji.lujingdraw;
import publicway.buttonset;
@@ -101,6 +102,11 @@
        this.currentDikuai = dikuai;
        this.currentPathIndex = 0;
        this.currentSpeed = DEFAULT_SPEED;
        // å°†å½“前速度(ç±³/秒)转换为km/h写入设备yaw属性
        try {
            double kmh = this.currentSpeed * 3.6;
            Device.getActiveDevice().setYaw(String.valueOf(kmh));
        } catch (Exception ignore) { }
        this.navigationTrack.clear();
        
        // èŽ·å–é¦–é¡µå’Œåœ°å›¾æ¸²æŸ“å™¨
@@ -449,9 +455,9 @@
    
    /**
     * è®¡ç®—两点之间的方向角(度)
     * å›¾æ ‡é»˜è®¤æœä¸Šï¼Œå‘右旋转90度车头朝右
     * atan2返回的角度:向右是0度,向上是90度
     * éœ€è¦è½¬æ¢ä¸ºå›¾æ ‡æ—‹è½¬è§’度:向右需要90度,向上需要0度
     * è½¦è¾†å›¾æ ‡çš„车头默认是在屏幕正下方(270度)
     * atan2返回的角度:向右是0度,向上是90度,向左是180度,向下是-90度(270度)
     * éœ€è¦è½¬æ¢ä¸ºå›¾æ ‡æ—‹è½¬è§’度,使车头朝向行驶方向
     */
    private double calculateHeading(Point2D.Double from, Point2D.Double to) {
        double dx = to.x - from.x;
@@ -464,13 +470,13 @@
            atan2Angle += 360;
        }
        
        // å›¾æ ‡é»˜è®¤æœä¸Šï¼ˆ0度),向右旋转90度车头朝右
        // æ‰€ä»¥ï¼šè¿åŠ¨æ–¹å‘å‘å³ï¼ˆ0度)→ éœ€è¦æ—‹è½¬90度
        //      è¿åŠ¨æ–¹å‘å‘ä¸Šï¼ˆ90度)→ éœ€è¦æ—‹è½¬0度
        //      è¿åŠ¨æ–¹å‘å‘å·¦ï¼ˆ180度)→ éœ€è¦æ—‹è½¬270度
        //      è¿åŠ¨æ–¹å‘å‘ä¸‹ï¼ˆ270度)→ éœ€è¦æ—‹è½¬180度
        // å…¬å¼ï¼šheading = (90 - atan2Angle + 360) % 360
        double heading = (90.0 - atan2Angle + 360.0) % 360.0;
        // å›¾æ ‡é»˜è®¤æœä¸‹ï¼ˆ270度),需要计算旋转角度使车头朝向行驶方向
        // å¦‚果运动方向向上(90度)→ éœ€è¦æ—‹è½¬180度(270 - 90 = 180)
        // å¦‚果运动方向向右(0度)→ éœ€è¦æ—‹è½¬270度(270 - 0 = 270)
        // å¦‚果运动方向向左(180度)→ éœ€è¦æ—‹è½¬90度(270 - 180 = 90)
        // å¦‚果运动方向向下(270度)→ éœ€è¦æ—‹è½¬0度(270 - 270 = 0)
        // å…¬å¼ï¼šheading = (270 - atan2Angle + 360) % 360
        double heading = (270.0 - atan2Angle + 360.0) % 360.0;
        
        return heading;
    }
@@ -489,6 +495,11 @@
     */
    private void speedUp() {
        currentSpeed += SPEED_MULTIPLIER;
        // åŒæ­¥åˆ°è®¾å¤‡yaw属性(km/h)
        try {
            double kmh = currentSpeed * 3.6;
            Device.getActiveDevice().setYaw(String.valueOf(kmh));
        } catch (Exception ignore) { }
        updateSpeedDisplay();
    }
    
@@ -502,6 +513,11 @@
                currentSpeed = 0.1;
            }
        }
        // åŒæ­¥åˆ°è®¾å¤‡yaw属性(km/h)
        try {
            double kmh = currentSpeed * 3.6;
            Device.getActiveDevice().setYaw(String.valueOf(kmh));
        } catch (Exception ignore) { }
        updateSpeedDisplay();
    }
    
@@ -509,8 +525,11 @@
     * æ›´æ–°é€Ÿåº¦æ˜¾ç¤º
     */
    private void updateSpeedDisplay() {
        // å¯ä»¥åœ¨åœ°å›¾ä¸Šæ˜¾ç¤ºå½“前速度
        // è¿™é‡Œæš‚时不实现,如果需要可以在MapRenderer中添加速度显示
        // å¯¼èˆªé¢„览下在割草机图标上方实时显示速度(固定像素间距)
        if (mapRenderer != null) {
            mapRenderer.setNavigationPreviewSpeed(currentSpeed);
            mapRenderer.repaint();
        }
    }
    
    /**
@@ -543,6 +562,11 @@
            mapRenderer.repaint();
        }
        
        // é€€å‡ºæ—¶å°†è®¾å¤‡yaw属性重置为0
        try {
            Device.getActiveDevice().setYaw("0");
        } catch (Exception ignore) { }
        // æ¢å¤åœ°å—管理页面
        // åœ¨æ¸…空currentDikuai之前保存地块编号,使用final变量以便在lambda中使用
        final String landNumber = (currentDikuai != null) ? currentDikuai.getLandNumber() : null;
src/gecaoji/Gecaoji.java
@@ -151,8 +151,9 @@
        double iconHeight = icon.getHeight(null);
        double maxSide = Math.max(iconWidth, iconHeight);
        double scaleFactor = worldSize / Math.max(maxSide, MIN_SCALE);
        // å‰²è‰æœºå›¾æ ‡é»˜è®¤æœå—,Yaw=0表示正北,需要旋转180度
        double rotationRadians = Math.toRadians(headingDegrees + 180);
        // è½¦è¾†å›¾æ ‡çš„车头默认是在屏幕正下方(270度)
        // headingDegrees已经是计算好的旋转角度(0-360度),直接使用
        double rotationRadians = Math.toRadians(headingDegrees);
        AffineTransform original = g2d.getTransform();
        AffineTransform transformed = new AffineTransform(original);
src/lujing/DebugPath.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
package lujing;
import java.util.*;
public class DebugPath {
    public static void main(String[] args) {
        String coordinates = "50.39,50.57;74.32,50.40;78.69,24.37;65.76,20.70;65.76,36.07;52.31,33.80;50.04,19.30;41.30,22.10";
        String widthStr = "1";
        String marginStr = "0.53";
        List<YixinglujingNoObstacle.PathSegment> path = YixinglujingNoObstacle.planPath(coordinates, widthStr, marginStr);
        // Validate that non-mowing segments do not cross outside polygon
        List<YixinglujingNoObstacle.Point> rawPoints = parseCoordinates("50.39,50.57;74.32,50.40;78.69,24.37;65.76,20.70;65.76,36.07;52.31,33.80;50.04,19.30;41.30,22.10");
        YixinglujingNoObstacle.ensureCounterClockwise(rawPoints);
        List<YixinglujingNoObstacle.Point> polygonInset = YixinglujingNoObstacle.getInsetPolygon(rawPoints, Double.parseDouble(marginStr));
        int total = path.size();
        int mowing = 0, travel = 0, unsafe = 0;
        for (YixinglujingNoObstacle.PathSegment s : path) {
            if (s.isMowing) {
                mowing++;
                continue;
            }
            travel++;
            boolean safe = isSegmentSafe(s.start, s.end, polygonInset);
            if (!safe) {
                // å…è®¸â€œæ²¿è¾¹ç•Œâ€çš„æ—…行段:两端都贴近边界即可视为安全
                double d1 = minDistanceToBoundary(s.start, polygonInset);
                double d2 = minDistanceToBoundary(s.end, polygonInset);
                if (d1 <= 0.2 && d2 <= 0.2) {
                    // treat as safe boundary-following segment
                    continue;
                }
                unsafe++;
            }
        }
        System.out.println("Segments total=" + total + ", mowing=" + mowing + ", travel=" + travel + ", unsafeTravel=" + unsafe);
        if (unsafe == 0) {
            System.out.println("Validation OK: no travel segment crosses the inset boundary.");
        } else {
            System.out.println("Validation FAILED: some travel segments cross boundary: " + unsafe);
        }
    }
    // --- Helpers copied for validation ---
    private static List<YixinglujingNoObstacle.Point> parseCoordinates(String coordinates) {
        List<YixinglujingNoObstacle.Point> points = new ArrayList<>();
        String[] pairs = coordinates.split(";");
        for (String pair : pairs) {
            String[] xy = pair.split(",");
            if (xy.length == 2) points.add(new YixinglujingNoObstacle.Point(Double.parseDouble(xy[0]), Double.parseDouble(xy[1])));
        }
        return points;
    }
    private static boolean isSegmentSafe(YixinglujingNoObstacle.Point p1, YixinglujingNoObstacle.Point p2, List<YixinglujingNoObstacle.Point> polygon) {
        YixinglujingNoObstacle.Point mid = new YixinglujingNoObstacle.Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
        if (!isPointInPolygon(mid, polygon)) return false;
        for (int i = 0; i < polygon.size(); i++) {
            YixinglujingNoObstacle.Point a = polygon.get(i);
            YixinglujingNoObstacle.Point b = polygon.get((i + 1) % polygon.size());
            if (segmentsIntersect(p1, p2, a, b)) return false;
        }
        return true;
    }
    private static boolean segmentsIntersect(YixinglujingNoObstacle.Point a, YixinglujingNoObstacle.Point b, YixinglujingNoObstacle.Point c, YixinglujingNoObstacle.Point d) {
        return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d);
    }
    private static boolean ccw(YixinglujingNoObstacle.Point a, YixinglujingNoObstacle.Point b, YixinglujingNoObstacle.Point c) {
        return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
    }
    private static boolean isPointInPolygon(YixinglujingNoObstacle.Point p, List<YixinglujingNoObstacle.Point> polygon) {
        boolean result = false;
        for (int i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) {
            if ((polygon.get(i).y > p.y) != (polygon.get(j).y > p.y) &&
                (p.x < (polygon.get(j).x - polygon.get(i).x) * (p.y - polygon.get(i).y) / (polygon.get(j).y - polygon.get(i).y) + polygon.get(i).x)) {
                result = !result;
            }
        }
        return result;
    }
    private static double minDistanceToBoundary(YixinglujingNoObstacle.Point p, List<YixinglujingNoObstacle.Point> poly) {
        double minD = Double.MAX_VALUE;
        for (int i = 0; i < poly.size(); i++) {
            YixinglujingNoObstacle.Point s = poly.get(i);
            YixinglujingNoObstacle.Point e = poly.get((i + 1) % poly.size());
            double l2 = (s.x - e.x)*(s.x - e.x) + (s.y - e.y)*(s.y - e.y);
            if (l2 == 0) {
                minD = Math.min(minD, Math.hypot(p.x - s.x, p.y - s.y));
                continue;
            }
            double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2;
            t = Math.max(0, Math.min(1, t));
            double px = s.x + t * (e.x - s.x);
            double py = s.y + t * (e.y - s.y);
            double d = Math.hypot(p.x - px, p.y - py);
            minD = Math.min(minD, d);
        }
        return minD;
    }
}
src/lujing/YixinglujingHaveObstacel.java
@@ -102,8 +102,8 @@
            remainingSegments.addAll(clippedSegments);
        }
        
        // 3. é‡æ–°è¿žæŽ¥è·¯å¾„段(弓字形连接)
        return reconnectSegments(remainingSegments);
        // 3. é‡æ–°è¿žæŽ¥è·¯å¾„段(弓字形连接,智能处理边界穿越)
        return reconnectSegments(remainingSegments, polygon);
    }
    
    /**
@@ -154,7 +154,6 @@
            return result;
        } else {
            // ä¸€ç«¯åœ¨å†…部,一端在外部
            Point insidePoint = startInside ? segment.start : segment.end;
            Point outsidePoint = startInside ? segment.end : segment.start;
            
            List<Point> intersections = obstacle.getIntersections(segment);
@@ -178,8 +177,9 @@
    
    /**
     * é‡æ–°è¿žæŽ¥è·¯å¾„段,形成连续弓字形路径
     * ä¼˜åŒ–:智能处理边界穿越,当换行路径穿越边界时,沿边界行走
     */
    private static List<PathSegment> reconnectSegments(List<PathSegment> segments) {
    private static List<PathSegment> reconnectSegments(List<PathSegment> segments, List<Point> boundary) {
        if (segments.isEmpty()) return new ArrayList<>();
        
        List<PathSegment> reconnected = new ArrayList<>();
@@ -189,7 +189,9 @@
            if (seg.isMowing) {
                // å‰²è‰æ®µï¼šæ£€æŸ¥æ˜¯å¦éœ€è¦æ·»åŠ ç©ºèµ°æ®µ
                if (distance(currentPos, seg.start) > 0.01) {
                    reconnected.add(new PathSegment(currentPos, seg.start, false));
                    // ä½¿ç”¨æ™ºèƒ½è¿žæŽ¥æ–¹æ³•生成换行路径
                    List<PathSegment> connectionPath = buildSmartConnection(currentPos, seg.start, boundary);
                    reconnected.addAll(connectionPath);
                }
                reconnected.add(seg);
                currentPos = seg.end;
@@ -204,6 +206,351 @@
    }
    
    /**
     * æ™ºèƒ½è¿žæŽ¥ä¸¤ç‚¹ï¼šå¦‚果直线不穿越边界则直接连接,否则使用直线+边界混合路径
     * ä¼˜åŒ–逻辑:
     * 1. å¦‚æžœAB线不穿越边界C,直接使用AB作为换行路线
     * 2. å¦‚æžœAB线穿越了边界C,找到所有交点,将AB分成多个段
     *    - å¯¹äºŽåœ¨è¾¹ç•Œå†…部的段(如DF段、GH段),沿边界行走
     *    - å¯¹äºŽåœ¨è¾¹ç•Œå¤–部的段,沿AB直线行走
     *    è·¯å¾„示例:A â†’ D(直线) â†’ F(沿边界) â†’ G(直线) â†’ H(沿边界) â†’ B(直线)
     *
     * @param pointA èµ·ç‚¹ï¼ˆä¸Šä¸€æ®µç»“束的终点)
     * @param pointB ç»ˆç‚¹ï¼ˆä¸‹ä¸€æ®µéœ€è¦å‰²è‰è·¯å¾„的起始点)
     * @param boundary å®‰å…¨å†…缩边界C
     * @return è¿žæŽ¥è·¯å¾„段列表(全部为isMowing=false的空走段)
     */
    private static List<PathSegment> buildSmartConnection(Point pointA, Point pointB, List<Point> boundary) {
        List<PathSegment> result = new ArrayList<>();
        // 1. æ£€æŸ¥AB直线是否穿越边界C
        if (!segmentIntersectsBoundary(pointA, pointB, boundary)) {
            // ä¸ç©¿è¶Šè¾¹ç•Œï¼Œç›´æŽ¥ä½¿ç”¨AB作为换行路线
            result.add(new PathSegment(pointA, pointB, false));
            return result;
        }
        // 2. AB线穿越了边界C,需要找到所有交点
        List<IntersectionInfo> intersections = getAllBoundaryIntersections(pointA, pointB, boundary);
        if (intersections.isEmpty()) {
            // æ²¡æœ‰äº¤ç‚¹ï¼ˆä¸åº”该发生,但安全处理),使用直线
            result.add(new PathSegment(pointA, pointB, false));
            return result;
        }
        // 3. æŒ‰è·ç¦»èµ·ç‚¹A的距离排序交点
        intersections.sort(Comparator.comparingDouble(inter -> distance(pointA, inter.point)));
        // 4. æž„建完整的点序列:A, I1, I2, ..., In, B(I为交点)
        List<Point> pointSequence = new ArrayList<>();
        pointSequence.add(pointA);
        for (IntersectionInfo inter : intersections) {
            pointSequence.add(inter.point);
        }
        pointSequence.add(pointB);
        // 5. å¤„理每两个相邻点之间的段
        Point currentPos = pointA;
        for (int i = 0; i < pointSequence.size() - 1; i++) {
            Point p1 = pointSequence.get(i);
            Point p2 = pointSequence.get(i + 1);
            // åˆ¤æ–­p1到p2的段(AB线段的一部分)是否在边界C内部(检查中点)
            Point midPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
            boolean segmentInsideBoundary = isPointInPolygon(midPoint, boundary);
            if (segmentInsideBoundary) {
                // æ®µåœ¨è¾¹ç•Œå†…部(如DF段、GH段),需要沿边界行走
                if (i == 0) {
                    // ç¬¬ä¸€ä¸ªæ®µï¼šä»ŽA到第一个交点D
                    // å¦‚果段在边界内部,说明A在边界内部,需要先从A沿边界走到第一个交点
                    IntersectionInfo firstInter = intersections.get(0);
                    SnapResult snapA = snapToBoundary(currentPos, boundary);
                    List<PathSegment> boundaryPath = getBoundaryPathBetweenPoints(
                        snapA.onEdge, snapA.edgeIndex,
                        firstInter.point, firstInter.edgeIndex,
                        boundary);
                    result.addAll(boundaryPath);
                    currentPos = firstInter.point;
                } else if (i == pointSequence.size() - 2) {
                    // æœ€åŽä¸€ä¸ªæ®µï¼šä»Žæœ€åŽä¸€ä¸ªäº¤ç‚¹H到B
                    // éœ€è¦æ²¿è¾¹ç•Œä»Žå½“前点(应该是最后一个交点H)到B在边界上的投影
                    IntersectionInfo lastInter = intersections.get(intersections.size() - 1);
                    SnapResult snapB = snapToBoundary(pointB, boundary);
                    List<PathSegment> boundaryPath = getBoundaryPathBetweenPoints(
                        currentPos, lastInter.edgeIndex,
                        snapB.onEdge, snapB.edgeIndex,
                        boundary);
                    result.addAll(boundaryPath);
                    // å¦‚æžœB不在边界上,从边界投影直线到B
                    if (distance(snapB.onEdge, pointB) > 1e-6) {
                        result.add(new PathSegment(snapB.onEdge, pointB, false));
                    }
                    currentPos = pointB;
                } else {
                    // ä¸­é—´æ®µï¼šä¸¤ä¸ªäº¤ç‚¹ä¹‹é—´çš„æ®µï¼ˆéƒ½åœ¨è¾¹ç•Œä¸Šï¼‰ï¼Œæ²¿è¾¹ç•Œè¡Œèµ°
                    IntersectionInfo inter1 = intersections.get(i - 1);
                    IntersectionInfo inter2 = intersections.get(i);
                    List<PathSegment> boundaryPath = getBoundaryPathBetweenPoints(
                        inter1.point, inter1.edgeIndex,
                        inter2.point, inter2.edgeIndex,
                        boundary);
                    result.addAll(boundaryPath);
                    currentPos = inter2.point;
                }
            } else {
                // æ®µåœ¨è¾¹ç•Œå¤–部,可以直接沿AB直线连接(如A到D,F到G,H到B)
                if (distance(p1, p2) > 1e-6) {
                    // å¦‚果当前点不在p1,先连接到p1
                    if (distance(currentPos, p1) > 1e-6) {
                        result.add(new PathSegment(currentPos, p1, false));
                    }
                    // ä»Žp1直线到p2
                        result.add(new PathSegment(p1, p2, false));
                    currentPos = p2;
                }
            }
        }
        return result;
    }
    /**
     * æ£€æŸ¥çº¿æ®µæ˜¯å¦ç©¿è¶Šè¾¹ç•Œï¼ˆä¸Žè¾¹ç•Œè¾¹ç›¸äº¤ï¼Œä¸åŒ…括端点)
     */
    private static boolean segmentIntersectsBoundary(Point a, Point b, List<Point> boundary) {
        for (int i = 0; i < boundary.size(); i++) {
            Point c = boundary.get(i);
            Point d = boundary.get((i + 1) % boundary.size());
            // å¿½ç•¥å…±äº«ç«¯ç‚¹çš„相交
            if (isSamePoint(a, c) || isSamePoint(a, d) || isSamePoint(b, c) || isSamePoint(b, d)) {
                continue;
            }
            if (segmentsIntersect(a, b, c, d)) {
                return true;
            }
        }
        return false;
    }
    /**
     * èŽ·å–çº¿æ®µä¸Žè¾¹ç•Œçš„æ‰€æœ‰äº¤ç‚¹ä¿¡æ¯ï¼ˆåŒ…æ‹¬ç‚¹å’Œå¯¹åº”è¾¹ç´¢å¼•ï¼‰
     */
    private static List<IntersectionInfo> getAllBoundaryIntersections(Point a, Point b, List<Point> boundary) {
        List<IntersectionInfo> intersections = new ArrayList<>();
        for (int i = 0; i < boundary.size(); i++) {
            Point c = boundary.get(i);
            Point d = boundary.get((i + 1) % boundary.size());
            // å¿½ç•¥å…±äº«ç«¯ç‚¹
            if (isSamePoint(a, c) || isSamePoint(a, d) || isSamePoint(b, c) || isSamePoint(b, d)) {
                continue;
            }
            Point intersection = getLineIntersection(a, b, c, d);
            if (intersection != null) {
                intersections.add(new IntersectionInfo(intersection, i));
            }
        }
        return intersections;
    }
    /**
     * èŽ·å–è¾¹ç•Œä¸Šä¸¤ç‚¹ä¹‹é—´çš„è·¯å¾„ï¼ˆæ²¿è¾¹ç•Œè¡Œèµ°ï¼‰
     * @param start èµ·ç‚¹ï¼ˆå¿…须在边界上)
     * @param startEdgeIndex èµ·ç‚¹æ‰€åœ¨çš„边索引
     * @param end ç»ˆç‚¹ï¼ˆå¿…须在边界上)
     * @param endEdgeIndex ç»ˆç‚¹æ‰€åœ¨çš„边索引
     * @param boundary è¾¹ç•Œç‚¹åˆ—表
     * @return æ²¿è¾¹ç•Œçš„路径段列表
     */
    private static List<PathSegment> getBoundaryPathBetweenPoints(
            Point start, int startEdgeIndex,
            Point end, int endEdgeIndex,
            List<Point> boundary) {
        List<PathSegment> result = new ArrayList<>();
        if (startEdgeIndex == endEdgeIndex) {
            // åœ¨åŒä¸€æ¡è¾¹ä¸Šï¼Œç›´æŽ¥è¿žæŽ¥
            if (distance(start, end) > 1e-6) {
                result.add(new PathSegment(start, end, false));
            }
            return result;
        }
        int n = boundary.size();
        // è®¡ç®—顺时针路径
        List<Point> pathClockwise = new ArrayList<>();
        pathClockwise.add(start);
        int curr = startEdgeIndex;
        while (curr != endEdgeIndex) {
            pathClockwise.add(boundary.get((curr + 1) % n));
            curr = (curr + 1) % n;
        }
        pathClockwise.add(end);
        // è®¡ç®—逆时针路径
        List<Point> pathCounterClockwise = new ArrayList<>();
        pathCounterClockwise.add(start);
        curr = startEdgeIndex;
        while (curr != endEdgeIndex) {
            pathCounterClockwise.add(boundary.get(curr));
            curr = (curr - 1 + n) % n;
        }
        pathCounterClockwise.add(end);
        // é€‰æ‹©è¾ƒçŸ­çš„路径
        List<Point> chosenPath = getPathLength(pathClockwise) < getPathLength(pathCounterClockwise)
            ? pathClockwise : pathCounterClockwise;
        // è½¬æ¢ä¸ºè·¯å¾„段
        for (int i = 0; i < chosenPath.size() - 1; i++) {
            if (distance(chosenPath.get(i), chosenPath.get(i + 1)) > 1e-6) {
                result.add(new PathSegment(chosenPath.get(i), chosenPath.get(i + 1), false));
            }
        }
        return result;
    }
    /**
     * è®¡ç®—路径总长度
     */
    private static double getPathLength(List<Point> path) {
        double len = 0;
        for (int i = 0; i < path.size() - 1; i++) {
            len += distance(path.get(i), path.get(i + 1));
        }
        return len;
    }
    /**
     * åˆ¤æ–­ä¸¤ä¸ªç‚¹æ˜¯å¦ç›¸åŒï¼ˆè€ƒè™‘浮点误差)
     */
    private static boolean isSamePoint(Point a, Point b) {
        return Math.abs(a.x - b.x) < 1e-6 && Math.abs(a.y - b.y) < 1e-6;
    }
    /**
     * åˆ¤æ–­ä¸¤æ¡çº¿æ®µæ˜¯å¦ç›¸äº¤ï¼ˆä¸åŒ…括端点)
     */
    private static boolean segmentsIntersect(Point a, Point b, Point c, Point d) {
        return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d);
    }
    /**
     * åˆ¤æ–­ä¸‰ç‚¹æ˜¯å¦é€†æ—¶é’ˆæŽ’列
     */
    private static boolean ccw(Point a, Point b, Point c) {
        return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
    }
    /**
     * äº¤ç‚¹ä¿¡æ¯å†…部类
     */
    private static class IntersectionInfo {
        Point point;        // äº¤ç‚¹åæ ‡
        int edgeIndex;      // äº¤ç‚¹æ‰€åœ¨çš„边界边索引
        IntersectionInfo(Point point, int edgeIndex) {
            this.point = point;
            this.edgeIndex = edgeIndex;
        }
    }
    /**
     * è¾¹ç•Œå¸é™„结果内部类
     */
    private static class SnapResult {
        Point onEdge;       // åœ¨è¾¹ç•Œä¸Šçš„æŠ•影点
        int edgeIndex;      // æ‰€åœ¨çš„边界边索引
        SnapResult(Point p, int idx) {
            this.onEdge = p;
            this.edgeIndex = idx;
        }
    }
    /**
     * è®¡ç®—点到边界最近的投影点以及所在边索引
     * @param p è¦å¸é™„的点
     * @param poly è¾¹ç•Œå¤šè¾¹å½¢
     * @return å¸é™„结果
     */
    private static SnapResult snapToBoundary(Point p, List<Point> poly) {
        double minD = Double.MAX_VALUE;
        Point bestProj = p;
        int bestIdx = -1;
        for (int i = 0; i < poly.size(); i++) {
            Point s = poly.get(i);
            Point e = poly.get((i + 1) % poly.size());
            double l2 = (s.x - e.x) * (s.x - e.x) + (s.y - e.y) * (s.y - e.y);
            if (l2 < 1e-10) {
                double d = Math.hypot(p.x - s.x, p.y - s.y);
                if (d < minD) {
                    minD = d;
                    bestProj = s;
                    bestIdx = i;
                }
                continue;
            }
            double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2;
            t = Math.max(0, Math.min(1, t));
            Point proj = new Point(s.x + t * (e.x - s.x), s.y + t * (e.y - s.y));
            double d = Math.hypot(p.x - proj.x, p.y - proj.y);
            if (d < minD) {
                minD = d;
                bestProj = proj;
                bestIdx = i;
            }
        }
        return new SnapResult(bestProj, bestIdx == -1 ? 0 : bestIdx);
    }
    /**
     * åˆ¤æ–­ç‚¹æ˜¯å¦åœ¨è¾¹ç•Œä¸Šï¼ˆè·ç¦»è¾¹ç•Œå¾ˆè¿‘)
     * @param p è¦æ£€æŸ¥çš„点
     * @param boundary è¾¹ç•Œå¤šè¾¹å½¢
     * @return æ˜¯å¦åœ¨è¾¹ç•Œä¸Š
     */
    @SuppressWarnings("unused")
    private static boolean isPointOnBoundary(Point p, List<Point> boundary) {
        double threshold = 1e-4; // é˜ˆå€¼ï¼Œè€ƒè™‘浮点误差
        for (int i = 0; i < boundary.size(); i++) {
            Point s = boundary.get(i);
            Point e = boundary.get((i + 1) % boundary.size());
            double dist = distToSegment(p, s, e);
            if (dist < threshold) {
                return true;
            }
        }
        return false;
    }
    /**
     * è®¡ç®—点到线段的距离
     * @param p ç‚¹
     * @param s çº¿æ®µèµ·ç‚¹
     * @param e çº¿æ®µç»ˆç‚¹
     * @return è·ç¦»
     */
    private static double distToSegment(Point p, Point s, Point e) {
        double l2 = (s.x - e.x) * (s.x - e.x) + (s.y - e.y) * (s.y - e.y);
        if (l2 < 1e-10) {
            return Math.hypot(p.x - s.x, p.y - s.y);
        }
        double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2;
        t = Math.max(0, Math.min(1, t));
        return Math.hypot(p.x - (s.x + t * (e.x - s.x)), p.y - (s.y + t * (e.y - s.y)));
    }
    /**
     * ç”ŸæˆåŽŸå§‹æ‰«æè·¯å¾„ï¼ˆæ— éšœç¢ç‰©ç‰ˆæœ¬ï¼‰
     */
    private static List<PathSegment> generateGlobalScanPath(
src/lujing/YixinglujingNoObstacle.java
@@ -90,11 +90,15 @@
            boolean endInside = isPointInPolygon(s.end, polygon);
            boolean intersects = segmentIntersectsBoundary(s.start, s.end, polygon);
            if (!s.isMowing) {
                // éžä½œä¸šæ®µç»Ÿä¸€æ›¿æ¢ä¸ºæ²¿è¾¹ç•Œè·¯å¾„
                // éžä½œä¸šæ®µï¼šè‹¥AB直线安全(不跨越边界且中点在内),保留直线;否则沿边界替换
                if (isSegmentSafe(s.start, s.end, polygon)) {
                    sanitized.add(s);
                } else {
                List<Point> path = getBoundaryPathWithSnap(s.start, s.end, polygon);
                for (int i = 0; i < path.size() - 1; i++) {
                    sanitized.add(new PathSegment(path.get(i), path.get(i+1), false));
                }
                }
            } else {
                if (!startInside || !endInside || intersects) {
                    SnapResult s1 = snapToBoundary(s.start, polygon);
@@ -240,9 +244,9 @@
            }
            PathSegment s = lineSegmentsInRow.get(idxInRow);
            // é¦–次连接或跨区连接均强制沿边界,避免穿越凹陷区
            // é¦–次或跨区连接:使用智能换行路径(优先直线最短,必要时沿边界)
            if (Math.hypot(currentPos.x - s.start.x, currentPos.y - s.start.y) > 0.01) {
                addBoundaryConnection(segments, currentPos, s.start, polygon);
                addSafeConnection(segments, currentPos, s.start, polygon);
                firstSegmentConnected = true;
            }
            segments.add(s);
@@ -369,14 +373,139 @@
    }
    private static void addSafeConnection(List<PathSegment> segments, Point start, Point end, List<Point> polygon) {
        if (!FORCE_BOUNDARY_TRAVEL && isSegmentSafe(start, end, polygon)) {
            segments.add(new PathSegment(start, end, false));
            return;
        // ä½¿ç”¨æ–°çš„æ™ºèƒ½æ¢è¡Œè·¯å¾„算法
        List<PathSegment> connectionPath = generateSmartConnectionPath(start, end, polygon);
        segments.addAll(connectionPath);
        }
        List<Point> path = getBoundaryPathWithSnap(start, end, polygon);
    /**
     * æ™ºèƒ½æ¢è¡Œè·¯å¾„生成:根据AB线段与安全边界C的交点,生成混合路径
     * é€»è¾‘:
     * 1. å¦‚æžœAB线段不穿越边界,直接返回AB直线
     * 2. å¦‚æžœAB线段穿越边界,找到所有交点(如D, F, G, H),生成路径:
     *    A -> D -> (沿边界D到F) -> F -> G -> (沿边界G到H) -> H -> B
     */
    private static List<PathSegment> generateSmartConnectionPath(Point A, Point B, List<Point> polygon) {
        List<PathSegment> result = new ArrayList<>();
        // 1. æ£€æŸ¥AB直线是否安全(不穿越边界且中点在内部);与是否强制沿边界无关
        if (isSegmentSafe(A, B, polygon)) {
            result.add(new PathSegment(A, B, false));
            return result;
        }
        // 2. èŽ·å–AB与边界的所有交点
        List<IntersectionPoint> intersections = getSegmentBoundaryIntersections(A, B, polygon);
        // 3. å¦‚果没有交点但不安全,说明整条线都在外部,强制沿边界
        if (intersections.isEmpty()) {
            List<Point> path = getBoundaryPathWithSnap(A, B, polygon);
        for (int i = 0; i < path.size() - 1; i++) {
            segments.add(new PathSegment(path.get(i), path.get(i+1), false));
                result.add(new PathSegment(path.get(i), path.get(i+1), false));
        }
            return result;
        }
        // 4. æ ¹æ®äº¤ç‚¹æˆå¯¹å¤„理,考虑A/B是否在内侧
        Point currentPos = A;
        boolean startInside = isPointInPolygon(A, polygon);
        boolean endInside = isPointInPolygon(B, polygon);
        for (int i = 0; i < intersections.size(); i += 2) {
            IntersectionPoint entry = intersections.get(i);
            // ä»Žå½“前位置到第一个交点:起点在内→直线;起点在外→沿边界到交点
            if (Math.hypot(currentPos.x - entry.point.x, currentPos.y - entry.point.y) > 1e-6) {
                if (startInside) {
                    result.add(new PathSegment(currentPos, entry.point, false));
                } else {
                    List<Point> pathToEntry = getBoundaryPathWithSnap(currentPos, entry.point, polygon);
                    for (int j = 0; j < pathToEntry.size() - 1; j++) {
                        result.add(new PathSegment(pathToEntry.get(j), pathToEntry.get(j+1), false));
                    }
                }
            }
            // æ£€æŸ¥æ˜¯å¦æœ‰é…å¯¹çš„离开点
            if (i + 1 < intersections.size()) {
                IntersectionPoint exit = intersections.get(i + 1);
                // ä»Žè¿›å…¥ç‚¹D到离开点F:沿边界行走
                List<Point> boundaryPath = getBoundaryPathBetweenPoints(
                    entry.point, entry.edgeIndex,
                    exit.point, exit.edgeIndex,
                    polygon
                );
                for (int j = 0; j < boundaryPath.size() - 1; j++) {
                    result.add(new PathSegment(boundaryPath.get(j), boundaryPath.get(j+1), false));
                }
                currentPos = exit.point;
                // ä¹‹åŽçš„起点视为内侧
                startInside = true;
            } else {
                // å¦‚果没有配对的离开点,说明终点在外部,沿边界到B
                List<Point> path = getBoundaryPathWithSnap(entry.point, B, polygon);
                for (int j = 0; j < path.size() - 1; j++) {
                    result.add(new PathSegment(path.get(j), path.get(j+1), false));
                }
                return result;
            }
        }
        // 5. ä»Žæœ€åŽä¸€ä¸ªç¦»å¼€ç‚¹åˆ°ç»ˆç‚¹B:直线段(在内部)
        if (Math.hypot(currentPos.x - B.x, currentPos.y - B.y) > 1e-6) {
            if (endInside) {
                result.add(new PathSegment(currentPos, B, false));
            } else {
                List<Point> pathToB = getBoundaryPathWithSnap(currentPos, B, polygon);
                for (int j = 0; j < pathToB.size() - 1; j++) {
                    result.add(new PathSegment(pathToB.get(j), pathToB.get(j+1), false));
                }
            }
        }
        return result;
    }
    /**
     * åœ¨å·²çŸ¥è¾¹ç•Œè¾¹ç´¢å¼•的情况下,沿边界获取两点之间的最短路径
     */
    private static List<Point> getBoundaryPathBetweenPoints(
        Point start, int startEdgeIdx,
        Point end, int endEdgeIdx,
        List<Point> polygon) {
        int n = polygon.size();
        // å¦‚果在同一条边上,直接连接
        if (startEdgeIdx == endEdgeIdx) {
            return Arrays.asList(start, end);
        }
        // æ­£å‘路径(顺边)
        List<Point> pathFwd = new ArrayList<>();
        pathFwd.add(start);
        int curr = startEdgeIdx;
        while (curr != endEdgeIdx) {
            pathFwd.add(polygon.get((curr + 1) % n));
            curr = (curr + 1) % n;
        }
        pathFwd.add(end);
        // åå‘路径(逆边)
        List<Point> pathRev = new ArrayList<>();
        pathRev.add(start);
        curr = startEdgeIdx;
        while (curr != endEdgeIdx) {
            pathRev.add(polygon.get(curr));
            curr = (curr - 1 + n) % n;
        }
        pathRev.add(end);
        // è¿”回几何长度更短的路径
        return getPathLength(pathFwd) <= getPathLength(pathRev) ? pathFwd : pathRev;
    }
    // å¼ºåˆ¶æ²¿è¾¹ç•Œç»•行的连接(不做直线安全判断),用来在同一扫描行的多个作业段之间跳转
@@ -470,6 +599,70 @@
        return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d);
    }
    // èŽ·å–çº¿æ®µAB与边界多边形的所有交点,按照距离A点的顺序排序
    private static class IntersectionPoint {
        Point point;           // äº¤ç‚¹åæ ‡
        int edgeIndex;        // äº¤ç‚¹æ‰€åœ¨çš„边界边索引
        double distFromStart; // è·ç¦»èµ·ç‚¹A的距离
        boolean isEntry;      // true表示进入边界,false表示离开边界
        IntersectionPoint(Point p, int idx, double dist, boolean entry) {
            this.point = p;
            this.edgeIndex = idx;
            this.distFromStart = dist;
            this.isEntry = entry;
        }
    }
    // è®¡ç®—线段AB与多边形边界的所有交点
    private static List<IntersectionPoint> getSegmentBoundaryIntersections(Point A, Point B, List<Point> polygon) {
        List<IntersectionPoint> intersections = new ArrayList<>();
        for (int i = 0; i < polygon.size(); i++) {
            Point C = polygon.get(i);
            Point D = polygon.get((i + 1) % polygon.size());
            Point intersection = getLineSegmentIntersection(A, B, C, D);
            if (intersection != null) {
                if (isSamePoint(intersection, A) || isSamePoint(intersection, B)) continue;
                double dist = Math.hypot(intersection.x - A.x, intersection.y - A.y);
                intersections.add(new IntersectionPoint(intersection, i, dist, false));
            }
        }
        // è·ç¦»æŽ’序
        Collections.sort(intersections, (i1, i2) -> Double.compare(i1.distFromStart, i2.distFromStart));
        // åŽ»é‡è¿‘ä¼¼ç›¸åŒäº¤ç‚¹ï¼ˆé¡¶ç‚¹åŒäº¤ï¼‰
        List<IntersectionPoint> deduped = new ArrayList<>();
        for (IntersectionPoint ip : intersections) {
            boolean dup = false;
            for (IntersectionPoint kept : deduped) {
                if (Math.abs(ip.point.x - kept.point.x) < 1e-6 && Math.abs(ip.point.y - kept.point.y) < 1e-6) { dup = true; break; }
            }
            if (!dup) deduped.add(ip);
        }
        return deduped;
    }
    // è®¡ç®—两条线段的交点(如果存在)
    private static Point getLineSegmentIntersection(Point p1, Point p2, Point p3, Point p4) {
        double x1 = p1.x, y1 = p1.y;
        double x2 = p2.x, y2 = p2.y;
        double x3 = p3.x, y3 = p3.y;
        double x4 = p4.x, y4 = p4.y;
        double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
        if (Math.abs(denom) < 1e-10) return null; // å¹³è¡Œæˆ–重合
        double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
        double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
        // æ£€æŸ¥äº¤ç‚¹æ˜¯å¦åœ¨ä¸¤æ¡çº¿æ®µä¸Šï¼ˆä½¿ç”¨ç•¥å¾®å®½æ¾çš„范围以处理浮点误差)
        double epsilon = 1e-6;
        if (t >= -epsilon && t <= 1 + epsilon && u >= -epsilon && u <= 1 + epsilon) {
            return new Point(x1 + t * (x2 - x1), y1 + t * (y2 - y1));
        }
        return null;
    }
    private static boolean ccw(Point a, Point b, Point c) {
        return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
    }
src/zhangaiwu/AddDikuai.java
@@ -1573,12 +1573,6 @@
     * æ·»åŠ  AoxinglujingNoObstacle.Point åˆ°å­—符串构建器
     */
    private void appendAoxingPoint(StringBuilder sb, AoxinglujingNoObstacle.Point point) {
            private void appendAoxingPointWithType(StringBuilder sb, AoxinglujingNoObstacle.Point point, boolean isMowing) {
                if (sb.length() > 0) {
                    sb.append(";");
                }
                sb.append(String.format(java.util.Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T"));
            }
        if (sb.length() > 0) {
            sb.append(";");
        }
@@ -1586,6 +1580,16 @@
    }
    
    /**
     * æ·»åŠ  AoxinglujingNoObstacle.Point åˆ°å­—符串构建器(带类型标记)
     */
    private void appendAoxingPointWithType(StringBuilder sb, AoxinglujingNoObstacle.Point point, boolean isMowing) {
        if (sb.length() > 0) {
            sb.append(";");
        }
        sb.append(String.format(Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T"));
    }
    /**
     * æ¯”较两个 YixinglujingNoObstacle.Point æ˜¯å¦ç›¸åŒï¼ˆä½¿ç”¨å°çš„容差)
     */
    private boolean equalsYixingPoint(YixinglujingNoObstacle.Point p1, YixinglujingNoObstacle.Point p2) {
@@ -1600,18 +1604,22 @@
     * æ·»åŠ  YixinglujingNoObstacle.Point åˆ°å­—符串构建器
     */
    private void appendYixingPoint(StringBuilder sb, YixinglujingNoObstacle.Point point) {
            private void appendYixingPointWithType(StringBuilder sb, YixinglujingNoObstacle.Point point, boolean isMowing) {
                if (sb.length() > 0) {
                    sb.append(";");
                }
                sb.append(String.format(java.util.Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T"));
            }
        if (sb.length() > 0) {
            sb.append(";");
        }
        sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y));
    }
    
    /**
     * æ·»åŠ  YixinglujingNoObstacle.Point åˆ°å­—符串构建器(带类型标记)
     */
    private void appendYixingPointWithType(StringBuilder sb, YixinglujingNoObstacle.Point point, boolean isMowing) {
        if (sb.length() > 0) {
            sb.append(";");
        }
        sb.append(String.format(Locale.US, "%.2f,%.2f,%s", point.x, point.y, isMowing ? "M" : "T"));
    }
    private void previewMowingPath() {
        if (!hasGeneratedPath()) {
            showPathGenerationMessage("请先生成割草路径后再预览。", false);
src/zhuye/MapRenderer.java
@@ -489,10 +489,7 @@
        drawMower(g2d);
        
        // ç»˜åˆ¶å¯¼èˆªé¢„览速度(如果正在导航预览)
        if (navigationPreviewSpeed > 0 && mower != null && mower.hasValidPosition()) {
            drawNavigationPreviewSpeed(g2d, scale);
        }
        // å·²æŒ‰éœ€æ±‚移除:不在割草机图标上方显示速度
        
        // ç»˜åˆ¶æµ‹é‡æ¨¡å¼ï¼ˆå¦‚果激活)
        if (measurementModeActive) {
@@ -564,16 +561,21 @@
        g2d.setFont(labelFont);
        FontMetrics metrics = g2d.getFontMetrics(labelFont);
        
        // è®¡ç®—文字位置(在割草机图标上方)
        // è®¡ç®—文字位置(在割草机图标正上方,间隔20像素固定)
        int textWidth = metrics.stringWidth(speedText);
        int textHeight = metrics.getHeight();
        int textX = (int)Math.round(screenPos.x - textWidth / 2.0);
        // åœ¨å‰²è‰æœºå›¾æ ‡ä¸Šæ–¹ï¼Œç•™å‡ºä¸€å®šé—´è·
        // å›¾æ ‡åœ¨ä¸–界坐标系中的大小约为 48 * 0.8 / scale ç±³
        // è½¬æ¢ä¸ºå±å¹•像素:图标高度(像素)= (48 * 0.8 / scale) * scale = 48 * 0.8 = 38.4 åƒç´ 
        double iconSizePixels = 48.0 * 0.8; // å›¾æ ‡åœ¨å±å¹•上的大小(像素)
        int spacing = 8; // é—´è·ï¼ˆåƒç´ ï¼‰
        int textY = (int)Math.round(screenPos.y - iconSizePixels / 2.0 - spacing - textHeight);
        // åœ¨å‰²è‰æœºå›¾æ ‡æ­£ä¸Šæ–¹ï¼Œé—´éš”20像素
        // ä»Žmower对象获取图标在世界坐标系中的半径,然后转换为屏幕像素
        double iconWorldRadius = mower.getWorldRadius(scale);
        double iconSizePixels = Double.isNaN(iconWorldRadius) ? 38.4 : (iconWorldRadius * 2.0 * scale); // å›¾æ ‡åœ¨å±å¹•上的大小(像素)
        int spacing = 20; // é—´è·ï¼ˆåƒç´ ï¼‰
        // å›¾æ ‡é¡¶éƒ¨ä½ç½® = å›¾æ ‡ä¸­å¿ƒY - å›¾æ ‡é«˜åº¦/2
        double iconTopY = screenPos.y - iconSizePixels / 2.0;
        // ç²¾ç¡®çº¦æŸï¼šè®©èƒŒæ™¯çŸ©å½¢çš„底边与图标顶部相距固定spacing像素
        // èƒŒæ™¯åº•è¾¹ = textY + metrics.getDescent() + 2(矩形下方内边距)
        // ä»¤ èƒŒæ™¯åº•è¾¹ = iconTopY - spacing,解得:
        int textY = (int)Math.round(iconTopY - spacing - metrics.getDescent() - 2);
        
        // ç»˜åˆ¶æ–‡å­—背景(半透明白色,增强可读性)
        g2d.setColor(new Color(255, 255, 255, 200));
src/zhuye/Shouye.java
@@ -2249,7 +2249,8 @@
        mowingProgressLabel.setForeground(THEME_COLOR);
        mowerSpeedValueLabel = new JLabel("--");
        mowerSpeedValueLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
        // æ•°å€¼å¤§å°ä¸Žå•位 km/h æ˜¾ç¤ºå¤§å°ä¸€è‡´
        mowerSpeedValueLabel.setFont(new Font("微软雅黑", Font.BOLD, 9));
        mowerSpeedValueLabel.setForeground(THEME_COLOR);
        mowerSpeedUnitLabel = new JLabel("km/h");
@@ -2303,10 +2304,21 @@
        String display = "--";
        Device device = Device.getGecaoji();
        if (device != null) {
            String sanitized = sanitizeSpeedValue(device.getRealtimeSpeed());
            // ä½¿ç”¨ yaw å€¼ä½œä¸ºé€Ÿåº¦æ˜¾ç¤ºï¼ˆå•位:km/h)
            String sanitized = sanitizeSpeedValue(device.getYaw());
            if (sanitized != null) {
                // yaw为0时显示为"0"
                try {
                    double v = Double.parseDouble(sanitized);
                    if (Math.abs(v) < 1e-9) {
                        display = "0";
                    } else {
                display = sanitized;
            }
                } catch (NumberFormatException ex) {
                    display = sanitized;
                }
            }
        }
        mowerSpeedValueLabel.setText(display);
        if (mowerSpeedUnitLabel != null) {