826220679@qq.com
3 小时以前 cbfd1df513c473dd5550d78740c92fc1677b6e9b
异形有障碍物路径规划没完成
已修改12个文件
1819 ■■■■■ 文件已修改
.classpath 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Obstacledge.properties 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dikuai.properties 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
set.properties 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
shoudongbianjie.properties 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/chuankou/SerialPortService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/chuankou/dellmessage.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/denglu/Denglu.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lujing/MowingPathGenerationPage.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lujing/YixinglujingHaveObstacel.java 1546 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lujing/YixinglujingNoObstacle.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhuye/Shouye.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.classpath
@@ -6,15 +6,15 @@
            <attribute name="module" value="true"/>
        </attributes>
    </classpathentry>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/jackson-annotations-2.15.2.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/jackson-core-2.15.2.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/jackson-databind-2.15.2.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/jSerialComm-2.10.4.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/jts-core-1.19.0.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/lombok-1.18.36.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/MQTT-1.0-SNAPSHOT.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/org.eclipse.paho.client.mqttv3-1.2.2.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/slf4j-api-1.7.30.jar"/>
    <classpathentry kind="lib" path="E:/Users/hxzk/eclipse-workspace/GeCaoAPP/lib/slf4j-simple-1.7.30.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/jackson-annotations-2.15.2.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/jackson-core-2.15.2.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/jackson-databind-2.15.2.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/jSerialComm-2.10.4.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/jts-core-1.19.0.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/lombok-1.18.36.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/MQTT-1.0-SNAPSHOT.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/org.eclipse.paho.client.mqttv3-1.2.2.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/slf4j-api-1.7.30.jar"/>
    <classpathentry kind="lib" path="D:/eclipseworkspace/GIT/GeCaoAPP/lib/slf4j-simple-1.7.30.jar"/>
    <classpathentry kind="output" path="bin"/>
</classpath>
Obstacledge.properties
@@ -1,5 +1,5 @@
# 割草机地块障碍物配置文件
# 生成时间:2025-12-26T19:43:09.374455600
# 生成时间:2025-12-27T13:39:05.917584700
# 坐标系:WGS84(度分格式)
# ============ 地块基准站配置 ============
@@ -14,6 +14,6 @@
# --- 地块LAND1的障碍物 ---
plot.LAND1.obstacle.障碍物1.shape=1
plot.LAND1.obstacle.障碍物1.originalCoords=0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E
plot.LAND1.obstacle.障碍物1.xyCoords=25.17,9.94;17.74,67.19;113.88,72.00;120.00,19.12;74.99,-4.48
plot.LAND1.obstacle.障碍物1.originalCoords=0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E;0.000000,N;0.000000,E
plot.LAND1.obstacle.障碍物1.xyCoords=53.95,39.88;55.58,45.69;64.53,45.45;66.97,40.57
dikuai.properties
@@ -1,26 +1,26 @@
#Dikuai Properties
#Fri Dec 26 19:43:09 CST 2025
LAND1.angleThreshold=-1
LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E
LAND1.boundaryCoordinates=4.30,87.65;-2.36,-65.51;44.25,-66.72;49.70,-14.05;98.13,-15.87;99.34,-69.75;137.48,-67.93;134.45,90.07;4.30,87.65
LAND1.boundaryOriginalCoordinates=39.831522,116.279873,49.25;39.831524,116.279878,49.25;39.831525,116.279878,49.24;39.831524,116.279912,49.30;39.831524,116.279911,49.29;39.831523,116.279911,49.23;39.831521,116.279915,49.31;39.831517,116.279925,49.34;39.831514,116.279940,49.30;39.831514,116.279957,49.28;39.831516,116.279974,49.28;39.831518,116.279991,49.29;39.831521,116.280008,49.24;39.831524,116.280025,49.30;39.831526,116.280042,49.24;39.831529,116.280059,49.29;39.831529,116.280076,49.26;39.831530,116.280093,49.32;39.831531,116.280110,49.28;39.831533,116.280127,49.28;39.831535,116.280144,49.26;39.831539,116.280161,49.27;39.831544,116.280175,49.25;39.831551,116.280190,49.24;39.831558,116.280204,49.26;39.831566,116.280219,49.26;39.831574,116.280234,49.22;39.831583,116.280248,49.24;39.831591,116.280260,49.24;39.831600,116.280272,49.23;39.831608,116.280285,49.18;39.831615,116.280298,49.12;39.831618,116.280312,49.11;39.831618,116.280328,49.12;39.831615,116.280342,49.15;39.831610,116.280356,49.21;39.831602,116.280369,49.23;39.831592,116.280379,49.25;39.831581,116.280388,49.25;39.831569,116.280394,49.19;39.831559,116.280395,49.23;39.831552,116.280387,49.28;39.831547,116.280373,49.32;39.831544,116.280357,49.33;39.831541,116.280340,49.29;39.831539,116.280324,49.27;39.831536,116.280307,49.24;39.831534,116.280290,49.25;39.831531,116.280273,49.26;39.831527,116.280257,49.28;39.831522,116.280242,49.21;39.831514,116.280232,49.28;39.831504,116.280229,49.24;39.831491,116.280230,49.33;39.831478,116.280233,49.34;39.831466,116.280236,49.31;39.831454,116.280239,49.31;39.831441,116.280242,49.26;39.831429,116.280244,49.23;39.831416,116.280247,49.25;39.831402,116.280250,49.22;39.831389,116.280253,49.25;39.831376,116.280256,49.26;39.831364,116.280258,49.24;39.831351,116.280261,49.25;39.831338,116.280265,49.26;39.831324,116.280268,49.20;39.831311,116.280271,49.16;39.831298,116.280274,49.17;39.831285,116.280277,49.22;39.831271,116.280278,49.16;39.831261,116.280273,49.23
#Sat Dec 27 13:39:05 GMT+08:00 2025
LAND1.intelligentSceneAnalysis=-1
LAND1.mowingSafetyDistance=0.53
LAND1.landArea=577.12
LAND1.returnPointCoordinates=-1
LAND1.landNumber=LAND1
LAND1.returnPathCoordinates=-1
LAND1.mowingPattern=平行线
LAND1.mowingOverlapDistance=0.06
LAND1.returnPathRawCoordinates=-1
LAND1.boundaryOriginalXY=-1
LAND1.mowingWidth=1.00
LAND1.plannedPath=73.871028,49.873176;50.776259,50.037243;41.965934,22.443191;49.612130,19.993609;51.845415,34.259086;66.290000,36.696945;66.290000,21.401369;78.088741,24.750277;73.871028,49.873176;73.955072,49.372566;50.616974,49.538362;50.298405,48.540599;74.123160,48.371347;74.291248,47.370128;49.979836,47.542837;49.661267,46.545075;74.459336,46.368908;74.627423,45.367689;49.342698,45.547313;49.024129,44.549551;55.663908,44.502382;64.552007,44.439240;74.795511,44.366470;74.963599,43.365250;65.098159,43.435335;55.361561,43.504504;48.705560,43.551789;48.386992,42.554027;55.059215,42.506627;65.644310,42.431430;75.131687,42.364031;75.299775,41.362812;66.190462,41.427525;54.756869,41.508750;48.068423,41.556265;47.749854,40.558503;54.454523,40.510872;59.489469,40.475104;75.467863,40.361592;75.635951,39.360373;47.431285,39.560741;47.112716,38.562978;75.804039,38.359154;75.972127,37.357934;46.794147,37.565216;46.475578,36.567454;64.753396,36.437608;66.290000,36.426692;76.140215,36.356715;76.308303,35.355496;66.290000,35.426666;59.067471,35.477976;46.157009,35.569692;45.838440,34.571930;53.381545,34.518343;66.290000,34.426641;76.476391,34.354276;76.644478,33.353057;66.290000,33.426616;51.731282,33.530042;45.519871,33.574168;45.201302,32.576406;51.574900,32.531127;66.290000,32.426591;76.812566,32.351838;76.980654,31.350618;66.290000,31.426565;51.418519,31.532213;44.882733,31.578644;44.564164,30.580882;51.262137,30.533299;66.290000,30.426540;77.148742,30.349399;77.316830,29.348180;66.290000,29.426515;51.105755,29.534385;44.245595,29.583120;43.927026,28.585357;50.949373,28.535470;66.290000,28.426490;77.484918,28.346960;77.653006,27.345741;66.290000,27.426464;50.792992,27.536556;43.608458,27.587595;43.289889,26.589833;50.636610,26.537642;66.290000,26.426439;77.821094,26.344522;77.989182,25.343302;66.290000,25.426414;50.480228,25.538727;42.971320,25.592071;42.652751,24.594309;50.323846,24.539813;66.290000,24.426389;76.687398,24.352525;73.250178,23.376918;66.290000,23.426363;50.167465,23.540899;42.334182,23.596547;42.015613,22.598785;50.011083,22.541985;66.290000,22.426338;69.812957,22.401311;66.375737,21.425704;66.290000,21.426313;49.854701,21.543070;44.660415,21.579971;47.852711,20.557267;49.698319,20.544156
LAND1.updateTime=2025-12-27 13\:39\:05
LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E
LAND1.boundaryPointInterval=-1
LAND1.createTime=2025-12-23 17\:08\:09
LAND1.intelligentSceneAnalysis=-1
LAND1.landArea=577.12
LAND1.landName=123
LAND1.landNumber=LAND1
LAND1.mowingBladeWidth=0.51
LAND1.mowingOverlapDistance=0.06
LAND1.mowingPattern=平行线
LAND1.mowingSafetyDistance=0.53
LAND1.mowingTrack=-1
LAND1.mowingWidth=0.50
LAND1.obstacleCoordinates=(25.17,9.94;17.74,67.19;113.88,72.00;120.00,19.12;74.99,-4.48)
LAND1.plannedPath=133.930254,89.530244;4.309853,87.120092;4.829500,87.626975;4.690221,87.491117;4.690221,87.127164;5.190221,87.136461;5.190221,-65.175826;4.690221,-65.162846;4.690221,84.423985;5.690221,87.145758;5.690221,-65.188806;6.190221,-65.201786;6.190221,87.155055;6.690221,87.164352;6.690221,-65.214766;7.190221,-65.227746;7.190221,87.173649;7.690221,87.182946;7.690221,-65.240726;8.190221,-65.253706;8.190221,87.192243;8.690221,87.201540;8.690221,-65.266686;9.190221,-65.279666;9.190221,87.210837;9.690221,87.220134;9.690221,-65.292646;10.190221,-65.305626;10.190221,87.229431;10.690221,87.238728;10.690221,-65.318606;11.190221,-65.331586;11.190221,87.248025;11.690221,87.257322;11.690221,-65.344567;12.190221,-65.357547;12.190221,87.266619;12.690221,87.275916;12.690221,-65.370527;13.190221,-65.383507;13.190221,87.285213;13.690221,87.294510;13.690221,-65.396487;14.190221,-65.409467;14.190221,87.303806;14.690221,87.313103;14.690221,-65.422447;15.190221,-65.435427;15.190221,87.322400;15.690221,87.331697;15.690221,-65.448407;16.190221,-65.461387;16.190221,87.340994;16.690221,87.350291;16.690221,-65.474367;17.190221,-65.487347;17.190221,67.308153;17.190221,67.693157;17.190221,87.359588;17.690221,87.368885;17.690221,67.718172;18.190221,67.743188;18.190221,87.378182;18.690221,87.387479;18.690221,67.768204;19.190221,67.793219;19.190221,87.396776;19.690221,87.406073;19.690221,67.818235;20.190221,67.843250;20.190221,87.415370;20.690221,87.424667;20.690221,67.868266;21.190221,67.893282;21.190221,87.433964;21.690221,87.443261;21.690221,67.918297;22.190221,67.943313;22.190221,87.452558;22.690221,87.461855;22.690221,67.968328;23.190221,67.993344;23.190221,87.471152;23.690221,87.480449;23.690221,68.018360;24.190221,68.043375;24.190221,87.489746;24.690221,87.499043;24.690221,68.068391;25.190221,68.093406;25.190221,87.508340;25.690221,87.517637;25.690221,68.118422;26.190221,68.143438;26.190221,87.526934;26.690221,87.536231;26.690221,68.168453;27.190221,68.193469;27.190221,87.545528;27.690221,87.554825;27.690221,68.218484;28.190221,68.243500;28.190221,87.564121;28.690221,87.573418;28.690221,68.268516;29.190221,68.293531;29.190221,87.582715;29.690221,87.592012;29.690221,68.318547;30.190221,68.343562;30.190221,87.601309;30.690221,87.610606;30.690221,68.368578;31.190221,68.393594;31.190221,87.619903;31.690221,87.629200;31.690221,68.418609;32.190221,68.443625;32.190221,87.638497;32.690221,87.647794;32.690221,68.468640;33.190221,68.493656;33.190221,87.657091;33.690221,87.666388;33.690221,68.518672;34.190221,68.543687;34.190221,87.675685;34.690221,87.684982;34.690221,68.568703;35.190221,68.593718;35.190221,87.694279;35.690221,87.703576;35.690221,68.618734;36.190221,68.643750;36.190221,87.712873;36.690221,87.722170;36.690221,68.668765;37.190221,68.693781;37.190221,87.731467;37.690221,87.740764;37.690221,68.718797;38.190221,68.743812;38.190221,87.750061;38.690221,87.759358;38.690221,68.768828;39.190221,68.793843;39.190221,87.768655;39.690221,87.777952;39.690221,68.818859;40.190221,68.843875;40.190221,87.787249;40.690221,87.796546;40.690221,68.868890;41.190221,68.893906;41.190221,87.805843;41.690221,87.815140;41.690221,68.918921;42.190221,68.943937;42.190221,87.824437;42.690221,87.833733;42.690221,68.968953;43.190221,68.993968;43.190221,87.843030;43.690221,87.852327;43.690221,69.018984;44.190221,69.043999;44.190221,87.861624;44.690221,87.870921;44.690221,69.069015;45.190221,69.094031;45.190221,87.880218;45.690221,87.889515;45.690221,69.119046;46.190221,69.144062;46.190221,87.898812;46.690221,87.908109;46.690221,69.169077;47.190221,69.194093;47.190221,87.917406;47.690221,87.926703;47.690221,69.219109;48.190221,69.244124;48.190221,87.936000;48.690221,87.945297;48.690221,69.269140;49.190221,69.294155;49.190221,87.954594;49.690221,87.963891;49.690221,69.319171;50.190221,69.344187;50.190221,87.973188;50.690221,87.982485;50.690221,69.369202;51.190221,69.394218;51.190221,87.991782;51.690221,88.001079;51.690221,69.419233;52.190221,69.444249;52.190221,88.010376;52.690221,88.019673;52.690221,69.469265;53.190221,69.494280;53.190221,88.028970;53.690221,88.038267;53.690221,69.519296;54.190221,69.544311;54.190221,88.047564;54.690221,88.056861;54.690221,69.569327;55.190221,69.594343;55.190221,88.066158;55.690221,88.075455;55.690221,69.619358;56.190221,69.644374;56.190221,88.084752;56.690221,88.094048;56.690221,69.669389;57.190221,69.694405;57.190221,88.103345;57.690221,88.112642;57.690221,69.719421;58.190221,69.744436;58.190221,88.121939;58.690221,88.131236;58.690221,69.769452;59.190221,69.794467;59.190221,88.140533;59.690221,88.149830;59.690221,69.819483;60.190221,69.844499;60.190221,88.159127;60.690221,88.168424;60.690221,69.869514;61.190221,69.894530;61.190221,88.177721;61.690221,88.187018;61.690221,69.919545;62.190221,69.944561;62.190221,88.196315;62.690221,88.205612;62.690221,69.969577;63.190221,69.994592;63.190221,88.214909;63.690221,88.224206;63.690221,70.019608;64.190221,70.044623;64.190221,88.233503;64.690221,88.242800;64.690221,70.069639;65.190221,70.094655;65.190221,88.252097;65.690221,88.261394;65.690221,70.119670;66.190221,70.144686;66.190221,88.270691;66.690221,88.279988;66.690221,70.169701;67.190221,70.194717;67.190221,88.289285;67.690221,88.298582;67.690221,70.219733;68.190221,70.244748;68.190221,88.307879;68.690221,88.317176;68.690221,70.269764;69.190221,70.294779;69.190221,88.326473;69.690221,88.335770;69.690221,70.319795;70.190221,70.344811;70.190221,88.345067;70.690221,88.354364;70.690221,70.369826;71.190221,70.394842;71.190221,88.363660;71.690221,88.372957;71.690221,70.419857;72.190221,70.444873;72.190221,88.382254;72.690221,88.391551;72.690221,70.469889;73.190221,70.494904;73.190221,88.400848;73.690221,88.410145;73.690221,70.519920;74.190221,70.544935;74.190221,88.419442;74.690221,88.428739;74.690221,70.569951;75.190221,70.594967;75.190221,88.438036;75.690221,88.447333;75.690221,70.619982;76.190221,70.644998;76.190221,88.456630;76.690221,88.465927;76.690221,70.670013;77.190221,70.695029;77.190221,88.475224;77.690221,88.484521;77.690221,70.720045;78.190221,70.745060;78.190221,88.493818;78.690221,88.503115;78.690221,70.770076;79.190221,70.795091;79.190221,88.512412;79.690221,88.521709;79.690221,70.820107;80.190221,70.845123;80.190221,88.531006;80.690221,88.540303;80.690221,70.870138;81.190221,70.895154;81.190221,88.549600;81.690221,88.558897;81.690221,70.920169;82.190221,70.945185;82.190221,88.568194;82.690221,88.577491;82.690221,70.970201;83.190221,70.995216;83.190221,88.586788;83.690221,88.596085;83.690221,71.020232;84.190221,71.045248;84.190221,88.605382;84.690221,88.614679;84.690221,71.070263;85.190221,71.095279;85.190221,88.623976;85.690221,88.633272;85.690221,71.120294;86.190221,71.145310;86.190221,88.642569;86.690221,88.651866;86.690221,71.170326;87.190221,71.195341;87.190221,88.661163;87.690221,88.670460;87.690221,71.220357;88.190221,71.245372;88.190221,88.679757;88.690221,88.689054;88.690221,71.270388;89.190221,71.295404;89.190221,88.698351;89.690221,88.707648;89.690221,71.320419;90.190221,71.345435;90.190221,88.716945;90.690221,88.726242;90.690221,71.370450;91.190221,71.395466;91.190221,88.735539;91.690221,88.744836;91.690221,71.420482;92.190221,71.445497;92.190221,88.754133;92.690221,88.763430;92.690221,71.470513;93.190221,71.495528;93.190221,88.772727;93.690221,88.782024;93.690221,71.520544;94.190221,71.545560;94.190221,88.791321;94.690221,88.800618;94.690221,71.570575;95.190221,71.595591;95.190221,88.809915;95.690221,88.819212;95.690221,71.620606;96.190221,71.645622;96.190221,88.828509;96.690221,88.837806;96.690221,71.670638;97.190221,71.695653;97.190221,88.847103;97.690221,88.856400;97.690221,71.720669;98.190221,71.745684;98.190221,88.865697;98.690221,88.874994;98.690221,71.770700;99.190221,71.795716;99.190221,88.884291;99.690221,88.893587;99.690221,71.820731;100.190221,71.845747;100.190221,88.902884;100.690221,88.912181;100.690221,71.870762;101.190221,71.895778;101.190221,88.921478;101.690221,88.930775;101.690221,71.920794;102.190221,71.945809;102.190221,88.940072;102.690221,88.949369;102.690221,71.970825;103.190221,71.995840;103.190221,88.958666;103.690221,88.967963;103.690221,72.020856;104.190221,72.045872;104.190221,88.977260;104.690221,88.986557;104.690221,72.070887;105.190221,72.095903;105.190221,88.995854;105.690221,89.005151;105.690221,72.120918;106.190221,72.145934;106.190221,89.014448;106.690221,89.023745;106.690221,72.170950;107.190221,72.195965;107.190221,89.033042;107.690221,89.042339;107.690221,72.220981;108.190221,72.245996;108.190221,89.051636;108.690221,89.060933;108.690221,72.271012;109.190221,72.296028;109.190221,89.070230;109.690221,89.079527;109.690221,72.321043;110.190221,72.346059;110.190221,89.088824;110.690221,89.098121;110.690221,72.371074;111.190221,72.396090;111.190221,89.107418;111.690221,89.116715;111.690221,72.421106;112.190221,72.446121;112.190221,89.126012;112.690221,89.135309;112.690221,72.471137;113.190221,72.496152;113.190221,89.144606;113.690221,89.153903;113.690221,72.521168;114.190221,72.546184;114.190221,89.163199;114.690221,89.172496;114.690221,69.609311;115.190221,65.289050;115.190221,89.181793;115.690221,89.191090;115.690221,60.968788;116.190221,56.648527;116.190221,89.200387;116.690221,89.209684;116.690221,52.328266;117.190221,48.008004;117.190221,89.218981;117.690221,89.228278;117.690221,43.687743;118.190221,39.367481;118.190221,89.237575;118.690221,89.246872;118.690221,35.047220;119.190221,30.726958;119.190221,89.256169;119.690221,89.265466;119.690221,26.406697;120.190221,22.086435;120.190221,89.274763;120.690221,89.284060;120.690221,-68.200587;121.190221,-68.176728;121.190221,89.293357;121.690221,89.302654;121.690221,-68.152868;122.190221,-68.129009;122.190221,89.311951;122.690221,89.321248;122.690221,-68.105149;123.190221,-68.081290;123.190221,89.330545;123.690221,89.339842;123.690221,-68.057430;124.190221,-68.033571;124.190221,89.349139;124.690221,89.358436;124.690221,-68.009711;125.190221,-67.985852;125.190221,89.367733;125.690221,89.377030;125.690221,-67.961993;126.190221,-67.938133;126.190221,89.386327;126.690221,89.395624;126.690221,-67.914274;127.190221,-67.890414;127.190221,89.404921;127.690221,89.414218;127.690221,-67.866555;128.190221,-67.842695;128.190221,89.423514;128.690221,89.432811;128.690221,-67.818836;129.190221,-67.794976;129.190221,89.442108;129.690221,89.451405;129.690221,-67.771117;130.190221,-67.747257;130.190221,89.460702;130.690221,89.469999;130.690221,-67.723398;131.190221,-67.699538;131.190221,89.479296;131.690221,89.488593;131.690221,-67.675679;132.190221,-67.651820;132.190221,89.497890;132.690221,89.507187;132.690221,-67.627960;133.190221,-67.604101;133.190221,89.516484;133.690221,89.525781;133.690221,-67.580241;134.190221,-67.556382;134.190221,75.974185;134.690221,49.901578;134.690221,-67.532522;135.190221,23.828971;119.690221,-68.248306;119.690221,18.359139;119.190221,18.096975;119.190221,-68.272166;118.690221,-68.296025;118.690221,17.834811;118.190221,17.572647;118.190221,-68.319885;117.690221,-68.343744;117.690221,17.310483;117.190221,17.048319;117.190221,-68.367603;116.690221,-68.391463;116.690221,16.786155;116.190221,16.523991;116.190221,-68.415322;115.690221,-68.439182;115.690221,16.261827;115.190221,15.999663;115.190221,-68.463041;114.690221,-68.486901;114.690221,15.737499;114.190221,15.475335;114.190221,-68.510760;113.690221,-68.534620;113.690221,15.213171;113.190221,14.951007;113.190221,-68.558479;112.690221,-68.582339;112.690221,14.688843;112.190221,14.426679;112.190221,-68.606198;111.690221,-68.630058;111.690221,14.164515;111.190221,13.902351;111.190221,-68.653917;110.690221,-68.677777;110.690221,13.640187;110.190221,13.378023;110.190221,-68.701636;109.690221,-68.725495;109.690221,13.115860;109.190221,12.853696;109.190221,-68.749355;108.690221,-68.773214;108.690221,12.591532;108.190221,12.329368;108.190221,-68.797074;107.690221,-68.820933;107.690221,12.067204;107.190221,11.805040;107.190221,-68.844793;106.690221,-68.868652;106.690221,11.542876;106.190221,11.280712;106.190221,-68.892512;105.690221,-68.916371;105.690221,11.018548;105.190221,10.756384;105.190221,-68.940231;104.690221,-68.964090;104.690221,10.494220;104.190221,10.232056;104.190221,-68.987950;103.690221,-69.011809;103.690221,9.969892;103.190221,9.707728;103.190221,-69.035668;102.690221,-69.059528;102.690221,9.445564;102.190221,9.183400;102.190221,-69.083387;101.690221,-69.107247;101.690221,8.921236;101.190221,8.659072;101.190221,-69.131106;100.690221,-69.154966;100.690221,8.396908;100.190221,8.134744;100.190221,-69.178825;99.690221,-61.738685;99.690221,7.872580;99.190221,7.610416;99.190221,-39.474222;98.690221,-17.209759;98.690221,7.348252;98.190221,7.086088;98.190221,-15.341889;97.690221,-15.323099;97.690221,6.823924;97.190221,6.561760;97.190221,-15.304309;96.690221,-15.285519;96.690221,6.299596;96.190221,6.037433;96.190221,-15.266729;95.690221,-15.247939;95.690221,5.775269;95.190221,5.513105;95.190221,-15.229149;94.690221,-15.210359;94.690221,5.250941;94.190221,4.988777;94.190221,-15.191569;93.690221,-15.172779;93.690221,4.726613;93.190221,4.464449;93.190221,-15.153989;92.690221,-15.135199;92.690221,4.202285;92.190221,3.940121;92.190221,-15.116409;91.690221,-15.097619;91.690221,3.677957;91.190221,3.415793;91.190221,-15.078829;90.690221,-15.060039;90.690221,3.153629;90.190221,2.891465;90.190221,-15.041249;89.690221,-15.022459;89.690221,2.629301;89.190221,2.367137;89.190221,-15.003669;88.690221,-14.984879;88.690221,2.104973;88.190221,1.842809;88.190221,-14.966089;87.690221,-14.947299;87.690221,1.580645;87.190221,1.318481;87.190221,-14.928509;86.690221,-14.909719;86.690221,1.056317;86.190221,0.794153;86.190221,-14.890929;85.690221,-14.872139;85.690221,0.531989;85.190221,0.269825;85.190221,-14.853349;84.690221,-14.834559;84.690221,0.007661;84.190221,-0.254503;84.190221,-14.815769;83.690221,-14.796979;83.690221,-0.516667;83.190221,-0.778831;83.190221,-14.778189;82.690221,-14.759399;82.690221,-1.040995;82.190221,-1.303158;82.190221,-14.740609;81.690221,-14.721819;81.690221,-1.565322;81.190221,-1.827486;81.190221,-14.703029;80.690221,-14.684239;80.690221,-2.089650;80.190221,-2.351814;80.190221,-14.665449;79.690221,-14.646659;79.690221,-2.613978;79.190221,-2.876142;79.190221,-14.627869;78.690221,-14.609079;78.690221,-3.138306;78.190221,-3.400470;78.190221,-14.590289;77.690221,-14.571499;77.690221,-3.662634;77.190221,-3.924798;77.190221,-14.552709;76.690221,-14.533919;76.690221,-4.186962;76.190221,-4.449126;76.190221,-14.515129;75.690221,-14.496339;75.690221,-4.711290;75.190221,-4.973454;75.190221,-14.477549;74.690221,-14.458759;74.690221,-4.944986;74.190221,-4.800265;74.190221,-14.439969;73.690221,-14.421179;73.690221,-4.655544;73.190221,-4.510823;73.190221,-14.402389;72.690221,-14.383599;72.690221,-4.366102;72.190221,-4.221381;72.190221,-14.364809;71.690221,-14.346019;71.690221,-4.076660;71.190221,-3.931939;71.190221,-14.327229;70.690221,-14.308439;70.690221,-3.787218;70.190221,-3.642497;70.190221,-14.289649;69.690221,-14.270859;69.690221,-3.497776;69.190221,-3.353055;69.190221,-14.252069;68.690221,-14.233279;68.690221,-3.208334;68.190221,-3.063613;68.190221,-14.214489;67.690221,-14.195699;67.690221,-2.918892;67.190221,-2.774171;67.190221,-14.176909;66.690221,-14.158119;66.690221,-2.629450;66.190221,-2.484729;66.190221,-14.139329;65.690221,-14.120539;65.690221,-2.340008;65.190221,-2.195287;65.190221,-14.101749;64.690221,-14.082959;64.690221,-2.050566;64.190221,-1.905845;64.190221,-14.064169;63.690221,-14.045379;63.690221,-1.761124;63.190221,-1.616403;63.190221,-14.026589;62.690221,-14.007799;62.690221,-1.471682;62.190221,-1.326961;62.190221,-13.989009;61.690221,-13.970219;61.690221,-1.182240;61.190221,-1.037519;61.190221,-13.951429;60.690221,-13.932639;60.690221,-0.892798;60.190221,-0.748077;60.190221,-13.913849;59.690221,-13.895059;59.690221,-0.603356;59.190221,-0.458635;59.190221,-13.876269;58.690221,-13.857479;58.690221,-0.313914;58.190221,-0.169193;58.190221,-13.838688;57.690221,-13.819898;57.690221,-0.024472;57.190221,0.120249;57.190221,-13.801108;56.690221,-13.782318;56.690221,0.264970;56.190221,0.409691;56.190221,-13.763528;55.690221,-13.744738;55.690221,0.554412;55.190221,0.699133;55.190221,-13.725948;54.690221,-13.707158;54.690221,0.843854;54.190221,0.988575;54.190221,-13.688368;53.690221,-13.669578;53.690221,1.133296;53.190221,1.278017;53.190221,-13.650788;52.690221,-13.631998;52.690221,1.422738;52.190221,1.567459;52.190221,-13.613208;51.690221,-13.594418;51.690221,1.712180;51.190221,1.856901;51.190221,-13.575628;50.690221,-13.556838;50.690221,2.001622;50.190221,2.146343;50.190221,-13.538048;49.690221,-13.519258;49.690221,2.291064;49.190221,2.435785;49.190221,-13.827232;48.690221,-18.659342;48.690221,2.580506;48.190221,2.725227;48.190221,-23.491452;47.690221,-28.323562;47.690221,2.869948;47.190221,3.014669;47.190221,-33.155672;46.690221,-37.987782;46.690221,3.159390;46.190221,3.304111;46.190221,-42.819892;45.690221,-47.652003;45.690221,3.448832;45.190221,3.593553;45.190221,-52.484113;44.690221,-57.316223;44.690221,3.738274;44.190221,3.882995;44.190221,-62.148333;43.690221,-66.175290;43.690221,4.027716;43.190221,4.172437;43.190221,-66.162309;42.690221,-66.149329;42.690221,4.317158;42.190221,4.461879;42.190221,-66.136349;41.690221,-66.123369;41.690221,4.606600;41.190221,4.751321;41.190221,-66.110389;40.690221,-66.097409;40.690221,4.896042;40.190221,5.040763;40.190221,-66.084429;39.690221,-66.071449;39.690221,5.185484;39.190221,5.330205;39.190221,-66.058469;38.690221,-66.045489;38.690221,5.474926;38.190221,5.619647;38.190221,-66.032509;37.690221,-66.019529;37.690221,5.764368;37.190221,5.909089;37.190221,-66.006549;36.690221,-65.993569;36.690221,6.053810;36.190221,6.198531;36.190221,-65.980589;35.690221,-65.967609;35.690221,6.343252;35.190221,6.487973;35.190221,-65.954629;34.690221,-65.941649;34.690221,6.632694;34.190221,6.777415;34.190221,-65.928669;33.690221,-65.915689;33.690221,6.922136;33.190221,7.066857;33.190221,-65.902709;32.690221,-65.889728;32.690221,7.211578;32.190221,7.356299;32.190221,-65.876748;31.690221,-65.863768;31.690221,7.501020;31.190221,7.645741;31.190221,-65.850788;30.690221,-65.837808;30.690221,7.790462;30.190221,7.935183;30.190221,-65.824828;29.690221,-65.811848;29.690221,8.079904;29.190221,8.224625;29.190221,-65.798868;28.690221,-65.785888;28.690221,8.369346;28.190221,8.514067;28.190221,-65.772908;27.690221,-65.759928;27.690221,8.658788;27.190221,8.803509;27.190221,-65.746948;26.690221,-65.733968;26.690221,8.948230;26.190221,9.092951;26.190221,-65.720988;25.690221,-65.708008;25.690221,9.237672;25.190221,9.382393;25.190221,-65.695028;24.690221,-65.682048;24.690221,9.527114;24.190221,13.371410;24.190221,-65.669068;23.690221,-65.656088;23.690221,17.224035;23.190221,21.076659;23.190221,-65.643108;22.690221,-65.630128;22.690221,24.929284;22.190221,28.781908;22.190221,-65.617147;21.690221,-65.604167;21.690221,32.634533;21.190221,36.487157;21.190221,-65.591187;20.690221,-65.578207;20.690221,40.339782;20.190221,44.192406;20.190221,-65.565227;19.690221,-65.552247;19.690221,48.045031;19.190221,51.897655;19.190221,-65.539267;18.690221,-65.526287;18.690221,55.750280;18.190221,59.602904;18.190221,-65.513307;3.690221,61.426988;3.190221,-65.123906;3.190221,49.928490;2.690221,38.429991;2.690221,-65.110926;2.190221,-65.097946;2.190221,26.931493;1.690221,15.432994;1.690221,-65.084966;1.190221,-65.071986;1.190221,3.934496;0.690221,-7.564003;0.690221,-65.059005;0.190221,-65.046025;0.190221,-19.062501;-0.309779,-30.561000;-0.309779,-65.033045;-0.809779,-65.020065;-0.809779,-42.059498;-1.309779,-53.557997;-1.309779,-65.007085;135.690221,-2.243636;136.190221,-67.460944;136.190221,-28.316244;136.690221,-54.388851;136.690221,-67.437084
LAND1.returnPathCoordinates=-1
LAND1.returnPathRawCoordinates=-1
LAND1.returnPointCoordinates=-1
LAND1.updateTime=2025-12-26 19\:43\:09
LAND1.angleThreshold=-1
LAND1.userId=-1
LAND1.landName=123
LAND1.mowingTrack=-1
LAND1.boundaryOriginalCoordinates=39.831522,116.279873,49.25;39.831524,116.279878,49.25;39.831525,116.279878,49.24;39.831524,116.279912,49.30;39.831524,116.279911,49.29;39.831523,116.279911,49.23;39.831521,116.279915,49.31;39.831517,116.279925,49.34;39.831514,116.279940,49.30;39.831514,116.279957,49.28;39.831516,116.279974,49.28;39.831518,116.279991,49.29;39.831521,116.280008,49.24;39.831524,116.280025,49.30;39.831526,116.280042,49.24;39.831529,116.280059,49.29;39.831529,116.280076,49.26;39.831530,116.280093,49.32;39.831531,116.280110,49.28;39.831533,116.280127,49.28;39.831535,116.280144,49.26;39.831539,116.280161,49.27;39.831544,116.280175,49.25;39.831551,116.280190,49.24;39.831558,116.280204,49.26;39.831566,116.280219,49.26;39.831574,116.280234,49.22;39.831583,116.280248,49.24;39.831591,116.280260,49.24;39.831600,116.280272,49.23;39.831608,116.280285,49.18;39.831615,116.280298,49.12;39.831618,116.280312,49.11;39.831618,116.280328,49.12;39.831615,116.280342,49.15;39.831610,116.280356,49.21;39.831602,116.280369,49.23;39.831592,116.280379,49.25;39.831581,116.280388,49.25;39.831569,116.280394,49.19;39.831559,116.280395,49.23;39.831552,116.280387,49.28;39.831547,116.280373,49.32;39.831544,116.280357,49.33;39.831541,116.280340,49.29;39.831539,116.280324,49.27;39.831536,116.280307,49.24;39.831534,116.280290,49.25;39.831531,116.280273,49.26;39.831527,116.280257,49.28;39.831522,116.280242,49.21;39.831514,116.280232,49.28;39.831504,116.280229,49.24;39.831491,116.280230,49.33;39.831478,116.280233,49.34;39.831466,116.280236,49.31;39.831454,116.280239,49.31;39.831441,116.280242,49.26;39.831429,116.280244,49.23;39.831416,116.280247,49.25;39.831402,116.280250,49.22;39.831389,116.280253,49.25;39.831376,116.280256,49.26;39.831364,116.280258,49.24;39.831351,116.280261,49.25;39.831338,116.280265,49.26;39.831324,116.280268,49.20;39.831311,116.280271,49.16;39.831298,116.280274,49.17;39.831285,116.280277,49.22;39.831271,116.280278,49.16;39.831261,116.280273,49.23
LAND1.obstacleCoordinates=(53.95,39.88;55.58,45.69;64.53,45.45;66.97,40.57)
LAND1.boundaryCoordinates=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
LAND1.mowingBladeWidth=0.51
set.properties
@@ -1,19 +1,19 @@
#Mower Configuration Properties - Updated
#Fri Dec 26 19:43:47 CST 2025
#Sat Dec 27 13:39:12 GMT+08:00 2025
appVersion=-1
boundaryLengthVisible=false
currentWorkLandNumber=LAND1
cuttingWidth=200
firmwareVersion=-1
handheldMarkerId=1872
idleTrailDurationSeconds=60
manualBoundaryDrawingMode=false
mapScale=11.81
measurementModeEnabled=false
mowerId=6258
serialAutoConnect=true
serialBaudRate=115200
serialPortName=COM15
simCardNumber=-1
viewCenterX=-134.26
viewCenterY=20.96
currentWorkLandNumber=LAND1
serialBaudRate=115200
boundaryLengthVisible=false
idleTrailDurationSeconds=60
handheldMarkerId=1872
viewCenterX=-60.00
viewCenterY=-34.94
manualBoundaryDrawingMode=false
mowerId=6258
serialPortName=COM15
serialAutoConnect=true
mapScale=10.32
measurementModeEnabled=false
firmwareVersion=-1
cuttingWidth=200
shoudongbianjie.properties
@@ -1,12 +1,12 @@
#\u624B\u52A8\u7ED8\u5236\u8FB9\u754C\u5750\u6807 - \u683C\u5F0F: x1,y1;x2,y2;...;xn,yn (\u5355\u4F4D:\u7C73,\u7CBE\u786E\u5230\u5C0F\u6570\u70B9\u540E2\u4F4D)
#Fri Dec 26 15:33:26 CST 2025
boundaryCoordinates=25.17,9.94;17.74,67.19;113.88,72.00;120.00,19.12;74.99,-4.48
email=789
language=zh
#Sat Dec 27 13:37:22 GMT+08:00 2025
registrationTime=-1
lastLoginTime=-1
password=123
pointCount=5
registrationTime=-1
status=-1
userId=-1
pointCount=4
boundaryCoordinates=53.95,39.88;55.58,45.69;64.53,45.45;66.97,40.57
language=zh
userName=233
userId=-1
email=789
status=-1
src/chuankou/SerialPortService.java
@@ -4,7 +4,7 @@
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.function.Consumer;
// import java.util.function.Consumer;
import com.fazecast.jSerialComm.SerialPort;
@@ -18,12 +18,12 @@
    private volatile boolean capturing = false;
    private volatile boolean paused = true;
    private Thread readerThread;
    private Consumer<byte[]> responseConsumer;
    private DataListener<byte[]> responseConsumer;
    // 优化:重用缓冲区,减少内存分配
    private byte[] readBuffer = new byte[200];
    private ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
    private Consumer<byte[]> dataReceivedCallback;
    private DataListener<byte[]> dataReceivedCallback;
    // 新增:数据条数计数器
@@ -97,7 +97,7 @@
        if (dataReceivedCallback != null) {
            startCapture(dataReceivedCallback);
        } else {
            System.err.println("No data received callback set. Please call startCapture(Consumer<byte[]> onReceived) first.");
            System.err.println("No data received callback set. Please call startCapture(DataListener<byte[]> onReceived) first.");
        }
    }
@@ -115,7 +115,7 @@
        return port.openPort();
    }
    public void setResponseConsumer(Consumer<byte[]> consumer) {
    public void setResponseConsumer(DataListener<byte[]> consumer) {
        this.responseConsumer = consumer;
    }
@@ -133,13 +133,15 @@
    /**
     * 启动数据接收线程
     */
    public void startCapture(Consumer<byte[]> onReceived) {
    public void startCapture(DataListener<byte[]> onReceived) {
        this.dataReceivedCallback = onReceived;
        if (capturing || port == null || !port.isOpen()) return;
        capturing = true;
        paused = false;
        readerThread = new Thread(() -> {
        readerThread = new Thread(new Runnable() {
            @Override
            public void run() {
            buffer.reset();
            long lastReceivedTime = 0;
@@ -189,7 +191,7 @@
                    responseConsumer.accept(data);
                }
            }
        });
        } });
        readerThread.setDaemon(true);
        readerThread.start();
    }
src/chuankou/dellmessage.java
@@ -5,7 +5,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
// import java.util.function.Consumer;
/**
 * 串口实时数据调度中心。
@@ -15,8 +15,8 @@
 * GNGGA 数据的内置解析支持,复用 UDP 处理逻辑。
 */
public final class dellmessage {
    private static final CopyOnWriteArrayList<Consumer<byte[]>> RAW_CONSUMERS = new CopyOnWriteArrayList<>();
    private static final CopyOnWriteArrayList<Consumer<String>> LINE_CONSUMERS = new CopyOnWriteArrayList<>();
    private static final CopyOnWriteArrayList<DataListener<byte[]>> RAW_CONSUMERS = new CopyOnWriteArrayList<>();
    private static final CopyOnWriteArrayList<DataListener<String>> LINE_CONSUMERS = new CopyOnWriteArrayList<>();
    private static final StringBuilder LINE_BUFFER = new StringBuilder(512);
    private static final AtomicInteger LINE_COUNTER = new AtomicInteger(0);
    private static final AtomicReference<String> LAST_LINE = new AtomicReference<>("");
@@ -30,7 +30,7 @@
     *
     * @param consumer 接收完整数据帧的监听器
     */
    public static void registerRawListener(Consumer<byte[]> consumer) {
    public static void registerRawListener(DataListener<byte[]> consumer) {
        // 用法:在需要直接处理原始串口字节流的模块启动时调用,传入回调处理数据帧。
        if (consumer != null) {
            RAW_CONSUMERS.addIfAbsent(consumer);
@@ -40,7 +40,7 @@
    /**
     * 注销原始字节监听器。
     */
    public static void unregisterRawListener(Consumer<byte[]> consumer) {
    public static void unregisterRawListener(DataListener<byte[]> consumer) {
        // 用法:模块销毁或不再需要接收原始数据时调用,避免内存泄漏。
        if (consumer != null) {
            RAW_CONSUMERS.remove(consumer);
@@ -52,7 +52,7 @@
     * <p>
     * 每一条经由换行符截断的完整文本行将触发一次回调。
     */
    public static void registerLineListener(Consumer<String> consumer) {
    public static void registerLineListener(DataListener<String> consumer) {
        // 用法:需要按行读取串口文本(如 NMEA 报文)时调用,回调拿到完整文本行。
        if (consumer != null) {
            LINE_CONSUMERS.addIfAbsent(consumer);
@@ -62,7 +62,7 @@
    /**
     * 注销文本行监听器。
     */
    public static void unregisterLineListener(Consumer<String> consumer) {
    public static void unregisterLineListener(DataListener<String> consumer) {
        // 用法:对应 registerLineListener 的反注册操作,通常在窗口关闭或服务停止时调用。
        if (consumer != null) {
            LINE_CONSUMERS.remove(consumer);
@@ -125,7 +125,7 @@
    }
    private static void notifyRawConsumers(byte[] data) {
        for (Consumer<byte[]> consumer : RAW_CONSUMERS) {
        for (DataListener<byte[]> consumer : RAW_CONSUMERS) {
            try {
                consumer.accept(data);
            } catch (Exception ex) {
@@ -220,9 +220,14 @@
        }
        LAST_LINE.set(line);
        LINE_COUNTER.updateAndGet(count -> count >= 10000 ? 1 : count + 1);
        // LINE_COUNTER.updateAndGet(count -> count >= 10000 ? 1 : count + 1);
        int current, next;
        do {
            current = LINE_COUNTER.get();
            next = (current >= 10000) ? 1 : current + 1;
        } while (!LINE_COUNTER.compareAndSet(current, next));
        for (Consumer<String> consumer : LINE_CONSUMERS) {
        for (DataListener<String> consumer : LINE_CONSUMERS) {
            try {
                consumer.accept(line);
            } catch (Exception ex) {
src/denglu/Denglu.java
@@ -609,27 +609,33 @@
    public static void launchMainApp() {
        System.out.println("准备打开主应用程序...");
        SwingUtilities.invokeLater(() -> {
            JFrame mainFrame = new JFrame("智能割草系统");
            mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame mainFrame = new JFrame("智能割草系统");
                mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            Shouye homePanel = new Shouye();
            mainFrame.setContentPane(homePanel);
                Shouye homePanel = new Shouye();
                mainFrame.setContentPane(homePanel);
            // 设置与登录页面相同的尺寸
            mainFrame.setSize(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
            mainFrame.setMinimumSize(new Dimension(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT));
            mainFrame.setResizable(true);
            mainFrame.setLocationRelativeTo(null);
            mainFrame.setVisible(true);
                // 设置与登录页面相同的尺寸
                mainFrame.setSize(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
                mainFrame.setMinimumSize(new Dimension(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT));
                mainFrame.setResizable(true);
                mainFrame.setLocationRelativeTo(null);
                mainFrame.setVisible(true);
            System.out.println("主应用程序已启动");
            // 启动后连接MQTT
            new Thread(() -> {
                System.out.println("正在连接MQTT服务器...");
                Client.lianjiemqqt();
            }).start();
                System.out.println("主应用程序已启动");
                // 启动后连接MQTT
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("正在连接MQTT服务器...");
                        Client.lianjiemqqt();
                    }
                }).start();
            }
        });
    }
    
src/lujing/MowingPathGenerationPage.java
@@ -609,12 +609,14 @@
                // 无障碍物的情况
                if (grassType == 1) {
                    // 凸形地块,无障碍物 -> 调用 AoxinglujingNoObstacle
                    System.out.println("调用算法: 凸形无障碍物, 类名: AoxinglujingNoObstacle");
                    List<AoxinglujingNoObstacle.PathSegment> segments = 
                        AoxinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr);
                    generated = formatAoxingPathSegments(segments);
                } else if (grassType == 2) {
                    // 异形地块,无障碍物 -> 调用 YixinglujingNoObstacle
                    // 调用 YixinglujingNoObstacle.planPath 获取路径段列表
                    System.out.println("调用算法: 异形无障碍物, 类名: YixinglujingNoObstacle");
                    List<YixinglujingNoObstacle.PathSegment> segments = 
                        YixinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr);
                    // 格式化路径段列表为字符串
@@ -625,6 +627,7 @@
                        JOptionPane.showMessageDialog(parentComponent, "无法判断地块类型,尝试按凸形地块处理", 
                            "提示", JOptionPane.WARNING_MESSAGE);
                    }
                    System.out.println("调用算法: 无法判断类型(默认凸形无障碍物), 类名: AoxinglujingNoObstacle");
                    List<AoxinglujingNoObstacle.PathSegment> segments = 
                        AoxinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr);
                    generated = formatAoxingPathSegments(segments);
@@ -634,12 +637,14 @@
                if (grassType == 1) {
                    // 凸形地块,有障碍物 -> 调用 AoxinglujingHaveObstacel
                    // 传入参数:boundary(A), obstacles(B), plannerWidth(C), safetyMarginStr(D)
                    System.out.println("调用算法: 凸形有障碍物, 类名: AoxinglujingHaveObstacel");
                    List<AoxinglujingHaveObstacel.PathSegment> segments = 
                        AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr);
                    generated = formatAoxingHaveObstaclePathSegments(segments);
                } else if (grassType == 2) {
                    // 异形地块,有障碍物 -> 调用 YixinglujingHaveObstacel
                    // 传入参数:boundary(A), obstacles(B), plannerWidth(C), safetyMarginStr(D)
                    System.out.println("调用算法: 异形有障碍物, 类名: YixinglujingHaveObstacel");
                    List<YixinglujingHaveObstacel.PathSegment> segments = 
                        YixinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr);
                    generated = formatYixingHaveObstaclePathSegments(segments);
@@ -649,6 +654,7 @@
                        JOptionPane.showMessageDialog(parentComponent, "无法判断地块类型,尝试按凸形地块处理", 
                            "提示", JOptionPane.WARNING_MESSAGE);
                    }
                    System.out.println("调用算法: 无法判断类型(默认凸形有障碍物), 类名: AoxinglujingHaveObstacel");
                    List<AoxinglujingHaveObstacel.PathSegment> segments = 
                        AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr);
                    generated = formatAoxingHaveObstaclePathSegments(segments);
src/lujing/YixinglujingHaveObstacel.java
@@ -1,1124 +1,634 @@
package lujing;
import java.util.*;
import java.util.regex.*;
import java.util.stream.Collectors;
/**
 * 异形草地路径规划 - 优化完善版
 * 采用更完善的算法:
 * 1. 使用多边形裁剪库计算更精确的内缩边界
 * 2. 使用扫描线填充算法生成更优化的路径
 * 3. 使用可见图算法寻找最优绕行路径
 * 4. 使用路径优化算法减少空行和转弯
 * 异形草地路径规划 - 含障碍物版
 * 功能:在地块内部避开障碍物,生成连续弓字形割草路径
 */
public class YixinglujingHaveObstacel {
    private static final double EPS = 1e-10;
    private static final double MIN_SEG_LEN = 0.01; // 忽略小于1cm的碎线
    private static final double CORNER_THRESHOLD = Math.toRadians(30); // 30度以下的角度合并
    
    public static List<PathSegment> planPath(String coordinates, String obstaclesStr, String widthStr, String marginStr) {
        try {
            // 解析输入参数
            double mowWidth = Double.parseDouble(widthStr);
            double safeMargin = Double.parseDouble(marginStr);
            // 解析多边形和障碍物
            List<Point> boundary = parseCoordinates(coordinates);
            if (boundary.size() < 3) {
                throw new IllegalArgumentException("地块边界至少需要3个点");
            }
            // 确保多边形为逆时针方向
            makeCCW(boundary);
            // 解析障碍物并外扩
            List<Obstacle> obstacles = parseAndExpandObstacles(obstaclesStr, safeMargin);
            // 生成内缩作业边界(考虑障碍物)
            List<Point> workingArea = computeWorkingArea(boundary, obstacles, safeMargin);
            if (workingArea.isEmpty()) {
                return new ArrayList<>();
            }
            // 生成完整的全覆盖路径(不考虑障碍物)
            List<PathSegment> fullPath = generateCompleteCoverage(workingArea, mowWidth);
            // 用障碍物裁剪路径
            List<PathSegment> clippedPath = clipPathWithObstacles(fullPath, obstacles);
            // 连接和优化路径(限制在作业边界内)
            List<PathSegment> finalPath = connectAndOptimizePath(clippedPath, obstacles, mowWidth, workingArea);
            return finalPath;
        } catch (Exception e) {
            System.err.println("路径规划错误: " + e.getMessage());
            e.printStackTrace();
            return new ArrayList<>();
    public static List<PathSegment> planPath(String coordinates, String obstaclesStr,
                                            String widthStr, String marginStr) {
        // 1. 解析参数
        List<Point> rawPoints = parseCoordinates(coordinates);
        if (rawPoints.size() < 3) return new ArrayList<>();
        double mowWidth = Double.parseDouble(widthStr);
        double safeMargin = Double.parseDouble(marginStr);
        // 解析障碍物
        List<Obstacle> obstacles = parseObstacles(obstaclesStr);
        // 2. 预处理:确保边界逆时针
        ensureCounterClockwise(rawPoints);
        // 3. 生成内缩多边形(安全边界)
        List<Point> boundary = getInsetPolygon(rawPoints, safeMargin);
        if (boundary.size() < 3) return new ArrayList<>();
        // 4. 外扩障碍物(安全边距)
        List<Obstacle> expandedObstacles = expandObstacles(obstacles, safeMargin);
        // 5. 确定最优作业角度
        double bestAngle = findOptimalAngle(boundary);
        // 6. 获取首个作业点,用于对齐围边起点
        Point firstScanStart = getFirstScanPoint(boundary, mowWidth, bestAngle);
        // 7. 对齐围边
        List<Point> alignedBoundary = alignBoundaryStart(boundary, firstScanStart);
        // 8. 第一阶段:围边路径
        List<PathSegment> finalPath = new ArrayList<>();
        for (int i = 0; i < alignedBoundary.size(); i++) {
            Point pStart = alignedBoundary.get(i);
            Point pEnd = alignedBoundary.get((i + 1) % alignedBoundary.size());
            finalPath.add(new PathSegment(pStart, pEnd, true));
        }
        // 9. 第二阶段:生成内部扫描路径(考虑障碍物)
        Point lastEdgePos = alignedBoundary.get(0);
        List<PathSegment> scanPath = generateGlobalScanPathWithObstacles(
            boundary, expandedObstacles, mowWidth, bestAngle, lastEdgePos);
        finalPath.addAll(scanPath);
        return finalPath;
    }
    
    /**
     * 计算作业区域(考虑障碍物)
     * 生成带障碍物的扫描路径
     */
    private static List<Point> computeWorkingArea(List<Point> boundary, List<Obstacle> obstacles, double margin) {
        // 首先生成内缩边界
        List<Point> offsetBoundary = offsetPolygon(boundary, margin);
    private static List<PathSegment> generateGlobalScanPathWithObstacles(
            List<Point> polygon, List<Obstacle> obstacles,
            double width, double angle, Point startPos) {
        
        if (obstacles.isEmpty()) {
            return offsetBoundary;
        }
        // 1. 生成原始扫描线(无障碍物)
        List<PathSegment> originalSegments = generateGlobalScanPath(polygon, width, angle, startPos);
        
    // 如果存在障碍物,从内缩边界中减去障碍物区域
    // 简化处理:工作区域仍以内缩边界为主,具体裁剪在路径层面完成
    makeCCW(offsetBoundary);
    return offsetBoundary;
    }
    /**
     * 生成完整的全覆盖路径
     */
    private static List<PathSegment> generateCompleteCoverage(List<Point> polygon, double width) {
        List<PathSegment> path = new ArrayList<>();
        // 1. 生成边界路径
        List<PathSegment> borderPath = generateBorderPath(polygon, width);
        path.addAll(borderPath);
        // 2. 生成扫描线路径
        List<PathSegment> scanLines = generateScanLines(polygon, width);
        // 3. 连接扫描线
        if (!scanLines.isEmpty()) {
            Point currentPos = path.isEmpty() ? scanLines.get(0).start :
                path.get(path.size() - 1).end;
            for (PathSegment scanLine : scanLines) {
                // 添加空行连接
                if (distance(currentPos, scanLine.start) > MIN_SEG_LEN) {
                    path.add(new PathSegment(currentPos, scanLine.start, false));
                }
                path.add(scanLine);
                currentPos = scanLine.end;
            }
            // 连接回起点
            if (distance(currentPos, path.get(0).start) > MIN_SEG_LEN) {
                path.add(new PathSegment(currentPos, path.get(0).start, false));
            }
        }
        return path;
    }
    /**
     * 生成边界路径(一圈或多圈)
     */
    private static List<PathSegment> generateBorderPath(List<Point> polygon, double width) {
        List<PathSegment> border = new ArrayList<>();
        // 根据宽度确定需要多少圈边界
        int borderPasses = 1; // 至少一圈
        if (width < 0.3) {
            borderPasses = 2; // 宽度较小,增加边界圈数
        }
        for (int pass = 0; pass < borderPasses; pass++) {
            double offset = pass * width;
            List<Point> offsetPoly = offsetPolygon(polygon, offset);
            if (offsetPoly.size() < 3) break;
            for (int i = 0; i < offsetPoly.size(); i++) {
                Point start = offsetPoly.get(i);
                Point end = offsetPoly.get((i + 1) % offsetPoly.size());
                border.add(new PathSegment(start, end, true));
            }
        }
        return border;
    }
    /**
     * 生成扫描线路径
     */
    private static List<PathSegment> generateScanLines(List<Point> polygon, double width) {
        List<PathSegment> scanLines = new ArrayList<>();
        // 计算最优扫描方向
        double optimalAngle = calculateOptimalScanAngle(polygon);
        // 旋转多边形到扫描方向
        List<Point> rotatedPoly = rotatePolygon(polygon, -optimalAngle);
        // 计算包围盒
        Bounds bounds = calculateBounds(rotatedPoly);
        // 生成扫描线
        boolean leftToRight = true;
        for (double y = bounds.minY + width / 2; y <= bounds.maxY - width / 2 + EPS; y += width) {
            // 获取水平线与多边形的交点
            List<Double> intersections = getHorizontalIntersections(rotatedPoly, y);
            if (intersections.size() < 2) continue;
            // 交点排序并成对处理
            Collections.sort(intersections);
            List<PathSegment> lineSegments = new ArrayList<>();
            for (int i = 0; i < intersections.size(); i += 2) {
                if (i + 1 >= intersections.size()) break;
                double x1 = intersections.get(i);
                double x2 = intersections.get(i + 1);
                if (x2 - x1 < MIN_SEG_LEN) continue;
                // 旋转回原始坐标系
                Point start = rotatePoint(new Point(x1, y), optimalAngle);
                Point end = rotatePoint(new Point(x2, y), optimalAngle);
                lineSegments.add(new PathSegment(start, end, true));
            }
            // 方向交替
            if (!leftToRight) {
                Collections.reverse(lineSegments);
                for (PathSegment seg : lineSegments) {
                    Point temp = seg.start;
                    seg.start = seg.end;
                    seg.end = temp;
                }
            }
            scanLines.addAll(lineSegments);
            leftToRight = !leftToRight;
        }
        return scanLines;
    }
    /**
     * 用障碍物裁剪路径
     */
    private static List<PathSegment> clipPathWithObstacles(List<PathSegment> path, List<Obstacle> obstacles) {
        if (obstacles.isEmpty()) return path;
        List<PathSegment> clipped = new ArrayList<>();
        for (PathSegment segment : path) {
            List<PathSegment> remaining = new ArrayList<>();
            remaining.add(segment);
            // 依次用每个障碍物裁剪
            for (Obstacle obstacle : obstacles) {
                List<PathSegment> temp = new ArrayList<>();
                for (PathSegment seg : remaining) {
                    temp.addAll(obstacle.clipSegment(seg));
                }
                remaining = temp;
            }
            clipped.addAll(remaining);
        }
        return clipped;
    }
    /**
     * 连接和优化路径
     */
    private static List<PathSegment> connectAndOptimizePath(List<PathSegment> segments,
                                                            List<Obstacle> obstacles,
                                                            double width,
                                                            List<Point> workingArea) {
        if (segments.isEmpty()) return new ArrayList<>();
        // 1. 先按类型分组:割草段和连接段
        List<PathSegment> mowingSegments = segments.stream()
            .filter(s -> s.isMowing)
            .collect(Collectors.toList());
        // 2. 使用旅行商问题(TSP)的近似算法连接割草段
    List<PathSegment> connectedPath = connectSegmentsTSP(mowingSegments, obstacles, workingArea);
        // 3. 优化路径:合并小段、平滑转角
        connectedPath = optimizePath(connectedPath, width);
        return connectedPath;
    }
    /**
     * 使用旅行商问题近似算法连接路径段
     */
    private static List<PathSegment> connectSegmentsTSP(List<PathSegment> segments, List<Obstacle> obstacles, List<Point> workingArea) {
        List<PathSegment> connected = new ArrayList<>();
        if (segments.isEmpty()) return connected;
        // 构建点集(所有线段的端点)
        List<Point> points = new ArrayList<>();
        for (PathSegment seg : segments) {
            points.add(seg.start);
            points.add(seg.end);
        }
        // 使用最近邻算法构建路径
        boolean[] visited = new boolean[segments.size()];
        Point currentPos = segments.get(0).start;
        while (true) {
            int bestIdx = -1;
            double bestDist = Double.MAX_VALUE;
            boolean useStart = true;
            // 寻找最近的未访问线段
            for (int i = 0; i < segments.size(); i++) {
                if (visited[i]) continue;
                PathSegment seg = segments.get(i);
                double distToStart = distance(currentPos, seg.start);
                double distToEnd = distance(currentPos, seg.end);
                if (distToStart < bestDist) {
                    bestDist = distToStart;
                    bestIdx = i;
                    useStart = true;
                }
                if (distToEnd < bestDist) {
                    bestDist = distToEnd;
                    bestIdx = i;
                    useStart = false;
                }
            }
            if (bestIdx == -1) break;
            // 添加连接路径
            PathSegment bestSeg = segments.get(bestIdx);
            Point targetPoint = useStart ? bestSeg.start : bestSeg.end;
            if (distance(currentPos, targetPoint) > MIN_SEG_LEN) {
                // 寻找安全连接路径(受作业边界限制)
                List<PathSegment> detour = findSafePath(currentPos, targetPoint, obstacles, workingArea);
                connected.addAll(detour);
            }
            // 添加割草线段(可能反转方向)
            PathSegment toAdd = bestSeg;
            if (!useStart) {
                toAdd = new PathSegment(bestSeg.end, bestSeg.start, true);
            }
            connected.add(toAdd);
            currentPos = toAdd.end;
            visited[bestIdx] = true;
        }
        return connected;
    }
    /**
     * 寻找安全路径(A*算法)
     */
    private static List<PathSegment> findSafePath(Point start, Point end, List<Obstacle> obstacles, List<Point> workingArea) {
        // 如果直线路径安全,直接使用
        if (isLineSafe(start, end, obstacles, workingArea)) {
            List<PathSegment> direct = new ArrayList<>();
            direct.add(new PathSegment(start, end, false));
            return direct;
        }
        // 否则使用A*算法寻找绕行路径
        return aStarPathFinding(start, end, obstacles, workingArea);
    }
    /**
     * A*算法路径寻找
     */
    private static List<PathSegment> aStarPathFinding(Point start, Point end, List<Obstacle> obstacles, List<Point> workingArea) {
        // 简化的A*算法实现
        // 这里我们使用障碍物边界上的关键点作为路径节点
        List<Point> nodes = new ArrayList<>();
        nodes.add(start);
        nodes.add(end);
        // 添加障碍物的顶点作为候选节点
        for (Obstacle obs : obstacles) {
            nodes.addAll(obs.getKeyPoints());
        }
        // 添加作业边界顶点,允许贴边绕行
        if (workingArea != null && workingArea.size() >= 3) {
            nodes.addAll(workingArea);
        }
        // 构建图
        Map<Point, Map<Point, Double>> graph = new HashMap<>();
        for (Point p1 : nodes) {
            graph.put(p1, new HashMap<>());
            for (Point p2 : nodes) {
                if (p1 == p2) continue;
                if (isLineSafe(p1, p2, obstacles, workingArea)) {
                    graph.get(p1).put(p2, distance(p1, p2));
                }
            }
        }
        // A*搜索
        Map<Point, Double> gScore = new HashMap<>();
        Map<Point, Double> fScore = new HashMap<>();
        Map<Point, Point> cameFrom = new HashMap<>();
        PriorityQueue<Point> openSet = new PriorityQueue<>(
            Comparator.comparingDouble(p -> fScore.getOrDefault(p, Double.MAX_VALUE))
        );
        gScore.put(start, 0.0);
        fScore.put(start, heuristic(start, end));
        openSet.add(start);
        while (!openSet.isEmpty()) {
            Point current = openSet.poll();
            if (current.equals(end)) {
                return reconstructPath(cameFrom, current);
            }
            for (Map.Entry<Point, Double> neighborEntry : graph.getOrDefault(current, new HashMap<>()).entrySet()) {
                Point neighbor = neighborEntry.getKey();
                double tentativeGScore = gScore.get(current) + neighborEntry.getValue();
                if (tentativeGScore < gScore.getOrDefault(neighbor, Double.MAX_VALUE)) {
                    cameFrom.put(neighbor, current);
                    gScore.put(neighbor, tentativeGScore);
                    fScore.put(neighbor, tentativeGScore + heuristic(neighbor, end));
                    if (!openSet.contains(neighbor)) {
                        openSet.add(neighbor);
                    }
                }
            }
        }
        // 如果没有找到路径,不做不安全的连接
        return new ArrayList<>();
    }
    /**
     * 重构路径
     */
    private static List<PathSegment> reconstructPath(Map<Point, Point> cameFrom, Point current) {
        List<Point> pathPoints = new ArrayList<>();
        while (current != null) {
            pathPoints.add(current);
            current = cameFrom.get(current);
        }
        Collections.reverse(pathPoints);
        List<PathSegment> path = new ArrayList<>();
        for (int i = 0; i < pathPoints.size() - 1; i++) {
            path.add(new PathSegment(pathPoints.get(i), pathPoints.get(i + 1), false));
        }
        return path;
    }
    /**
     * 启发函数
     */
    private static double heuristic(Point a, Point b) {
        return distance(a, b);
    }
    /**
     * 优化路径
     */
    private static List<PathSegment> optimizePath(List<PathSegment> path, double width) {
        if (path.size() <= 1) return path;
        List<PathSegment> optimized = new ArrayList<>();
        PathSegment current = path.get(0);
        for (int i = 1; i < path.size(); i++) {
            PathSegment next = path.get(i);
            // 检查是否可以合并当前线段和下一线段
            if (canMergeSegments(current, next, width)) {
                // 合并线段
                current = mergeSegments(current, next);
            } else {
                // 添加当前线段,开始新的合并
                optimized.add(current);
                current = next;
            }
        }
        optimized.add(current);
        // 平滑转角
        optimized = smoothCorners(optimized, width);
        return optimized;
    }
    /**
     * 检查是否可以合并两个线段
     */
    private static boolean canMergeSegments(PathSegment a, PathSegment b, double width) {
        if (!a.isMowing || !b.isMowing) return false;
        // 检查端点是否重合
        if (!a.end.equals(b.start) && !a.end.equals(b.end)) return false;
        // 检查方向是否一致
        Point dir1 = new Point(a.end.x - a.start.x, a.end.y - a.start.y);
        Point dir2;
        if (a.end.equals(b.start)) {
            dir2 = new Point(b.end.x - b.start.x, b.end.y - b.start.y);
        } else {
            dir2 = new Point(b.start.x - b.end.x, b.start.y - b.end.y);
        }
        double angle = angleBetween(dir1, dir2);
        return angle < Math.toRadians(10); // 角度小于10度可以合并
    }
    /**
     * 合并两个线段
     */
    private static PathSegment mergeSegments(PathSegment a, PathSegment b) {
        Point newEnd = a.end.equals(b.start) ? b.end : b.start;
        return new PathSegment(a.start, newEnd, true);
    }
    /**
     * 平滑转角
     */
    private static List<PathSegment> smoothCorners(List<PathSegment> path, double width) {
        if (path.size() < 3) return path;
        List<PathSegment> smoothed = new ArrayList<>();
        smoothed.add(path.get(0));
        for (int i = 1; i < path.size() - 1; i++) {
            PathSegment prev = path.get(i - 1);
            PathSegment curr = path.get(i);
            PathSegment next = path.get(i + 1);
            if (!prev.isMowing || !curr.isMowing || !next.isMowing) {
                smoothed.add(curr);
        // 2. 移除在障碍物内部的线段
        List<PathSegment> remainingSegments = new ArrayList<>();
        for (PathSegment seg : originalSegments) {
            if (!seg.isMowing) {
                // 空走段直接保留
                remainingSegments.add(seg);
                continue;
            }
            
            // 计算转角
            Point inVec = new Point(curr.start.x - prev.end.x, curr.start.y - prev.end.y);
            Point outVec = new Point(next.start.x - curr.end.x, next.start.y - curr.end.y);
            // 将割草段与所有障碍物进行裁剪
            List<PathSegment> clippedSegments = new ArrayList<>();
            clippedSegments.add(seg);
            
            double angle = angleBetween(inVec, outVec);
            for (Obstacle obs : obstacles) {
                List<PathSegment> newSegments = new ArrayList<>();
                for (PathSegment s : clippedSegments) {
                    newSegments.addAll(clipSegmentWithObstacle(s, obs));
                }
                clippedSegments = newSegments;
            }
            
            if (angle < CORNER_THRESHOLD) {
                // 小角度,可以直接连接
                PathSegment direct = new PathSegment(prev.end, next.start, true);
                smoothed.remove(smoothed.size() - 1); // 移除上一个线段
                smoothed.add(direct);
                i++; // 跳过下一个线段
            remainingSegments.addAll(clippedSegments);
        }
        // 3. 重新连接路径段(弓字形连接)
        return reconnectSegments(remainingSegments);
    }
    /**
     * 将线段与障碍物进行裁剪
     * 返回不在障碍物内部的子线段
     */
    private static List<PathSegment> clipSegmentWithObstacle(PathSegment segment, Obstacle obstacle) {
        List<PathSegment> result = new ArrayList<>();
        // 检查线段是否完全在障碍物外部
        boolean startInside = obstacle.contains(segment.start);
        boolean endInside = obstacle.contains(segment.end);
        if (!startInside && !endInside) {
            // 线段两端都在外部,检查是否穿过障碍物
            List<Point> intersections = obstacle.getIntersections(segment);
            if (intersections.isEmpty()) {
                // 完全在外部
                result.add(segment);
            } else {
                smoothed.add(curr);
            }
        }
        if (path.size() > 1) {
            smoothed.add(path.get(path.size() - 1));
        }
        return smoothed;
    }
    // ==================== 几何计算工具 ====================
    /**
     * 多边形偏移算法
     */
    private static List<Point> offsetPolygon(List<Point> polygon, double d) {
        // 基于“偏移边直线交点”的较稳健实现。约定polygon为CCW,左法向量为外侧。
        if (polygon == null || polygon.size() < 3) return new ArrayList<>();
        List<Point> poly = new ArrayList<>(polygon);
        makeCCW(poly);
        int n = poly.size();
        List<Point> out = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            Point A = poly.get((i - 1 + n) % n);
            Point B = poly.get(i);
            Point C = poly.get((i + 1) % n);
            Point e1 = normalize(subtract(B, A));
            Point e2 = normalize(subtract(C, B));
            Point n1 = new Point(-e1.y, e1.x);
            Point n2 = new Point(-e2.y, e2.x);
            Point p1 = add(B, multiply(n1, d));
            Point p2 = add(B, multiply(n2, d));
            Point dir1 = e1;
            Point dir2 = e2;
            Point inter = intersectLines(p1, dir1, p2, dir2);
            if (inter == null) {
                // 平行或数值不稳定时退化
                Point avgN = add(n1, n2);
                if (magnitude(avgN) < EPS) avgN = n1;
                else avgN = normalize(avgN);
                inter = add(B, multiply(avgN, d));
            }
            out.add(inter);
        }
        return out;
    }
    // 计算两条参数直线的交点 p=p0+t*v, q=q0+s*w
    private static Point intersectLines(Point p0, Point v, Point q0, Point w) {
        double det = v.x * w.y - v.y * w.x;
        if (Math.abs(det) < EPS) return null;
        double t = ((q0.x - p0.x) * w.y - (q0.y - p0.y) * w.x) / det;
        return new Point(p0.x + t * v.x, p0.y + t * v.y);
    }
    /**
     * 计算最优扫描角度
     */
    private static double calculateOptimalScanAngle(List<Point> polygon) {
        double bestAngle = 0;
        double minSpan = Double.MAX_VALUE;
        // 尝试多个角度
        for (int i = 0; i < 180; i += 5) {
            double angle = Math.toRadians(i);
            List<Point> rotated = rotatePolygon(polygon, angle);
            Bounds bounds = calculateBounds(rotated);
            double span = bounds.maxY - bounds.minY;
            if (span < minSpan) {
                minSpan = span;
                bestAngle = angle;
            }
        }
        return bestAngle;
    }
    /**
     * 获取水平线与多边形的交点
     */
    private static List<Double> getHorizontalIntersections(List<Point> polygon, double y) {
        List<Double> intersections = new ArrayList<>();
        int n = polygon.size();
        for (int i = 0; i < n; i++) {
            Point p1 = polygon.get(i);
            Point p2 = polygon.get((i + 1) % n);
            // 检查边是否与水平线相交
            if ((p1.y <= y && p2.y >= y) || (p1.y >= y && p2.y <= y)) {
                if (Math.abs(p2.y - p1.y) < EPS) {
                    // 水平边,跳过
                    continue;
                }
                // 穿过障碍物,分割线段
                intersections.sort(Comparator.comparingDouble(p ->
                    distance(segment.start, p)));
                
                double t = (y - p1.y) / (p2.y - p1.y);
                if (t >= -EPS && t <= 1 + EPS) {
                    double x = p1.x + t * (p2.x - p1.x);
                    intersections.add(x);
                Point prevPoint = segment.start;
                for (Point inter : intersections) {
                    result.add(new PathSegment(prevPoint, inter, true));
                    prevPoint = inter;
                }
                result.add(new PathSegment(prevPoint, segment.end, true));
                // 移除在障碍物内部的段(奇数索引的段)
                List<PathSegment> filtered = new ArrayList<>();
                for (int i = 0; i < result.size(); i++) {
                    PathSegment s = result.get(i);
                    Point midPoint = new Point(
                        (s.start.x + s.end.x) / 2,
                        (s.start.y + s.end.y) / 2
                    );
                    if (!obstacle.contains(midPoint)) {
                        filtered.add(s);
                    }
                }
                return filtered;
            }
        } else if (startInside && endInside) {
            // 完全在内部,丢弃
            return result;
        } else {
            // 一端在内部,一端在外部
            Point insidePoint = startInside ? segment.start : segment.end;
            Point outsidePoint = startInside ? segment.end : segment.start;
            List<Point> intersections = obstacle.getIntersections(segment);
            if (!intersections.isEmpty()) {
                // 取离外部点最近的交点
                intersections.sort(Comparator.comparingDouble(p ->
                    distance(outsidePoint, p)));
                Point inter = intersections.get(0);
                // 只保留外部部分
                if (startInside) {
                    result.add(new PathSegment(inter, outsidePoint, true));
                } else {
                    result.add(new PathSegment(outsidePoint, inter, true));
                }
            }
        }
        
        // 去重并排序
        intersections = intersections.stream()
            .distinct()
            .sorted()
            .collect(Collectors.toList());
        return intersections;
        return result;
    }
    
    /**
     * 判断直线是否安全
     * 重新连接路径段,形成连续弓字形路径
     */
    private static boolean isLineSafe(Point p1, Point p2, List<Obstacle> obstacles, List<Point> workingArea) {
        // 必须完全在作业内缩边界内
        if (workingArea != null && !isSegmentInsidePolygon(p1, p2, workingArea)) {
            return false;
        }
        for (Obstacle obs : obstacles) {
            if (obs.doesSegmentIntersect(p1, p2)) {
                return false;
            }
        }
        return true;
    }
    // 判断线段是否位于多边形内部(不越界)
    private static boolean isSegmentInsidePolygon(Point a, Point b, List<Point> polygon) {
        if (polygon == null || polygon.size() < 3) return true;
        // 中点在内
        Point mid = new Point((a.x + b.x) / 2.0, (a.y + b.y) / 2.0);
        if (!pointInPolygon(mid, polygon)) return false;
        // 不与边界相交(允许端点接触)
        int n = polygon.size();
        for (int i = 0; i < n; i++) {
            Point p1 = polygon.get(i);
            Point p2 = polygon.get((i + 1) % n);
            if (lineSegmentIntersection(a, b, p1, p2)) {
                // 忽略仅在端点处的小接触
                if (distance(a, p1) < EPS || distance(a, p2) < EPS || distance(b, p1) < EPS || distance(b, p2) < EPS) {
                    continue;
    private static List<PathSegment> reconnectSegments(List<PathSegment> segments) {
        if (segments.isEmpty()) return new ArrayList<>();
        List<PathSegment> reconnected = new ArrayList<>();
        Point currentPos = segments.get(0).start;
        for (PathSegment seg : segments) {
            if (seg.isMowing) {
                // 割草段:检查是否需要添加空走段
                if (distance(currentPos, seg.start) > 0.01) {
                    reconnected.add(new PathSegment(currentPos, seg.start, false));
                }
                return false;
                reconnected.add(seg);
                currentPos = seg.end;
            } else {
                // 空走段直接添加
                reconnected.add(seg);
                currentPos = seg.end;
            }
        }
        return true;
    }
    private static boolean pointInPolygon(Point p, List<Point> poly) {
        boolean inside = false;
        for (int i = 0, j = poly.size() - 1; i < poly.size(); j = i++) {
            Point pi = poly.get(i), pj = poly.get(j);
            boolean intersect = ((pi.y > p.y) != (pj.y > p.y)) &&
                    (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y + EPS) + pi.x);
            if (intersect) inside = !inside;
        }
        return inside;
    }
    // ==================== 向量运算工具 ====================
    private static Point add(Point a, Point b) {
        return new Point(a.x + b.x, a.y + b.y);
    }
    private static Point subtract(Point a, Point b) {
        return new Point(a.x - b.x, a.y - b.y);
    }
    private static Point multiply(Point p, double scalar) {
        return new Point(p.x * scalar, p.y * scalar);
    }
    private static Point normalize(Point p) {
        double mag = magnitude(p);
        if (mag < EPS) return p;
        return new Point(p.x / mag, p.y / mag);
    }
    private static double magnitude(Point p) {
        return Math.sqrt(p.x * p.x + p.y * p.y);
    }
    private static double dot(Point a, Point b) {
        return a.x * b.x + a.y * b.y;
    }
    private static double angleBetween(Point a, Point b) {
        double dotProd = dot(a, b);
        double magA = magnitude(a);
        double magB = magnitude(b);
        
        if (magA < EPS || magB < EPS) return 0;
        return reconnected;
    }
    /**
     * 生成原始扫描路径(无障碍物版本)
     */
    private static List<PathSegment> generateGlobalScanPath(
            List<Point> polygon, double width, double angle, Point currentPos) {
        
        double cosAngle = dotProd / (magA * magB);
        cosAngle = Math.max(-1, Math.min(1, cosAngle));
        return Math.acos(cosAngle);
    }
    private static double distance(Point a, Point b) {
        return magnitude(subtract(a, b));
    }
    private static Point rotatePoint(Point p, double angle) {
        double cos = Math.cos(angle);
        double sin = Math.sin(angle);
        return new Point(p.x * cos - p.y * sin, p.x * sin + p.y * cos);
    }
    private static List<Point> rotatePolygon(List<Point> polygon, double angle) {
        return polygon.stream()
            .map(p -> rotatePoint(p, angle))
            .collect(Collectors.toList());
    }
    private static Bounds calculateBounds(List<Point> points) {
        double minX = Double.MAX_VALUE, maxX = -Double.MAX_VALUE;
        List<PathSegment> segments = new ArrayList<>();
        List<Point> rotatedPoly = new ArrayList<>();
        for (Point p : polygon) rotatedPoly.add(rotatePoint(p, -angle));
        double minY = Double.MAX_VALUE, maxY = -Double.MAX_VALUE;
        for (Point p : points) {
            minX = Math.min(minX, p.x);
            maxX = Math.max(maxX, p.x);
        for (Point p : rotatedPoly) {
            minY = Math.min(minY, p.y);
            maxY = Math.max(maxY, p.y);
        }
        
        return new Bounds(minX, maxX, minY, maxY);
    }
    private static void makeCCW(List<Point> polygon) {
        double area = 0;
        int n = polygon.size();
        for (int i = 0; i < n; i++) {
            Point p1 = polygon.get(i);
            Point p2 = polygon.get((i + 1) % n);
            area += (p2.x - p1.x) * (p2.y + p1.y);
        boolean leftToRight = true;
        for (double y = minY + width/2; y <= maxY - width/2; y += width) {
            List<Double> xIntersections = getXIntersections(rotatedPoly, y);
            if (xIntersections.size() < 2) continue;
            Collections.sort(xIntersections);
            List<PathSegment> lineSegmentsInRow = new ArrayList<>();
            for (int i = 0; i < xIntersections.size() - 1; i += 2) {
                Point pS = rotatePoint(new Point(xIntersections.get(i), y), angle);
                Point pE = rotatePoint(new Point(xIntersections.get(i + 1), y), angle);
                lineSegmentsInRow.add(new PathSegment(pS, pE, true));
            }
            if (!leftToRight) {
                Collections.reverse(lineSegmentsInRow);
                for (PathSegment s : lineSegmentsInRow) {
                    Point temp = s.start;
                    s.start = s.end;
                    s.end = temp;
                }
            }
            for (PathSegment s : lineSegmentsInRow) {
                if (distance(currentPos, s.start) > 0.01) {
                    segments.add(new PathSegment(currentPos, s.start, false));
                }
                segments.add(s);
                currentPos = s.end;
            }
            leftToRight = !leftToRight;
        }
        
        if (area > 0) {
            Collections.reverse(polygon);
        }
        return segments;
    }
    
    // ==================== 障碍物处理 ====================
    private static List<Obstacle> parseAndExpandObstacles(String obstaclesStr, double margin) {
    /**
     * 解析障碍物字符串
     * 格式:"(x1,y1;x2,y2)(x1,y1;x2,y2;x3,y3)"
     */
    private static List<Obstacle> parseObstacles(String obstaclesStr) {
        List<Obstacle> obstacles = new ArrayList<>();
        if (obstaclesStr == null || obstaclesStr.trim().isEmpty()) {
            return obstacles;
        }
        
        // 解析障碍物字符串
        Pattern pattern = Pattern.compile("\\(([^)]+)\\)");
        Matcher matcher = pattern.matcher(obstaclesStr);
        String trimmed = obstaclesStr.trim();
        List<String> obstacleStrs = new ArrayList<>();
        
        while (matcher.find()) {
            String coords = matcher.group(1);
            List<Point> points = parseCoordinates(coords);
        // 分割每个障碍物(用括号分隔)
        int start = trimmed.indexOf('(');
        while (start != -1) {
            int end = trimmed.indexOf(')', start);
            if (end == -1) break;
            String obsStr = trimmed.substring(start + 1, end);
            obstacleStrs.add(obsStr);
            start = trimmed.indexOf('(', end);
        }
        // 解析每个障碍物
        for (String obsStr : obstacleStrs) {
            List<Point> points = new ArrayList<>();
            String[] pairs = obsStr.split(";");
            for (String pair : pairs) {
                String[] xy = pair.split(",");
                if (xy.length == 2) {
                    points.add(new Point(
                        Double.parseDouble(xy[0].trim()),
                        Double.parseDouble(xy[1].trim())
                    ));
                }
            }
            
            if (points.size() == 2) {
                // 圆形障碍物
                // 圆形障碍物:第一个点为圆心,第二个点为圆上一点
                Point center = points.get(0);
                double radius = distance(center, points.get(1)) + margin;
                obstacles.add(new CircularObstacle(center, radius));
            } else if (points.size() >= 3) {
                Point onCircle = points.get(1);
                double radius = distance(center, onCircle);
                obstacles.add(new Obstacle(center, radius));
            } else if (points.size() > 2) {
                // 多边形障碍物
                makeCCW(points);
                List<Point> expanded = offsetPolygon(points, -margin);
                obstacles.add(new PolygonalObstacle(expanded));
                obstacles.add(new Obstacle(points));
            }
        }
        
        return obstacles;
    }
    
    private static List<Point> parseCoordinates(String str) {
        List<Point> points = new ArrayList<>();
    /**
     * 外扩障碍物(增加安全边距)
     */
    private static List<Obstacle> expandObstacles(List<Obstacle> obstacles, double margin) {
        List<Obstacle> expanded = new ArrayList<>();
        
        if (str == null || str.trim().isEmpty()) {
            return points;
        }
        String[] tokens = str.split(";");
        for (String token : tokens) {
            token = token.trim();
            if (token.isEmpty()) continue;
            String[] xy = token.split(",");
            if (xy.length == 2) {
                try {
                    double x = Double.parseDouble(xy[0].trim());
                    double y = Double.parseDouble(xy[1].trim());
                    points.add(new Point(x, y));
                } catch (NumberFormatException e) {
                    System.err.println("无效坐标: " + token);
                }
        for (Obstacle obs : obstacles) {
            if (obs.isCircle()) {
                // 圆形:半径增加安全边距
                expanded.add(new Obstacle(obs.center, obs.radius + margin));
            } else {
                // 多边形:向外偏移(与边界内缩方向相反)
                List<Point> expandedPoints = getOutsetPolygon(obs.points, margin);
                expanded.add(new Obstacle(expandedPoints));
            }
        }
        
        return points;
    }
    // ==================== 内部类定义 ====================
    /**
     * 障碍物基类
     */
    abstract static class Obstacle {
        abstract List<PathSegment> clipSegment(PathSegment seg);
        abstract boolean doesSegmentIntersect(Point p1, Point p2);
        abstract boolean containsPoint(Point p);
        abstract List<Point> getKeyPoints();
        return expanded;
    }
    
    /**
     * 多边形障碍物
     * 多边形外扩(与内缩方向相反)
     */
    static class PolygonalObstacle extends Obstacle {
        List<Point> vertices;
    private static List<Point> getOutsetPolygon(List<Point> points, double margin) {
        // 这里使用简化的外扩方法:沿法线向外移动
        List<Point> outset = new ArrayList<>();
        int n = points.size();
        
        PolygonalObstacle(List<Point> vertices) {
            this.vertices = vertices;
        }
        @Override
        List<PathSegment> clipSegment(PathSegment seg) {
            List<Double> tValues = new ArrayList<>();
            tValues.add(0.0);
            tValues.add(1.0);
        for (int i = 0; i < n; i++) {
            Point pPrev = points.get((i - 1 + n) % n);
            Point pCurr = points.get(i);
            Point pNext = points.get((i + 1) % n);
            
            // 收集所有交点
            for (int i = 0; i < vertices.size(); i++) {
                Point p1 = vertices.get(i);
                Point p2 = vertices.get((i + 1) % vertices.size());
                Double t = lineIntersection(seg.start, seg.end, p1, p2);
                if (t != null) {
                    tValues.add(t);
                }
            // 计算两个边的向量
            double v1x = pCurr.x - pPrev.x, v1y = pCurr.y - pPrev.y;
            double v2x = pNext.x - pCurr.x, v2y = pNext.y - pCurr.y;
            // 计算法线(确保向外)
            double nx1 = -v1y, ny1 = v1x;
            double nx2 = -v2y, ny2 = v2x;
            // 归一化
            double len1 = Math.hypot(nx1, ny1);
            double len2 = Math.hypot(nx2, ny2);
            if (len1 > 1e-6) { nx1 /= len1; ny1 /= len1; }
            if (len2 > 1e-6) { nx2 /= len2; ny2 /= len2; }
            // 计算平均法线方向
            double nx = (nx1 + nx2) / 2;
            double ny = (ny1 + ny2) / 2;
            double len = Math.hypot(nx, ny);
            if (len > 1e-6) {
                nx /= len;
                ny /= len;
            }
            
            Collections.sort(tValues);
            List<PathSegment> result = new ArrayList<>();
            // 生成不在障碍物内部的线段段
            for (int i = 0; i < tValues.size() - 1; i++) {
                double t1 = tValues.get(i);
                double t2 = tValues.get(i + 1);
                double tMid = (t1 + t2) / 2;
                Point midPoint = interpolate(seg.start, seg.end, tMid);
                if (!containsPoint(midPoint)) {
                    Point start = interpolate(seg.start, seg.end, t1);
                    Point end = interpolate(seg.start, seg.end, t2);
                    result.add(new PathSegment(start, end, seg.isMowing));
                }
            }
            return result;
            // 向外移动
            outset.add(new Point(
                pCurr.x + nx * margin,
                pCurr.y + ny * margin
            ));
        }
        
        @Override
        boolean doesSegmentIntersect(Point p1, Point p2) {
            for (int i = 0; i < vertices.size(); i++) {
                Point v1 = vertices.get(i);
                Point v2 = vertices.get((i + 1) % vertices.size());
                if (lineSegmentIntersection(p1, p2, v1, v2)) {
                    return true;
                }
            }
            return false;
        }
        @Override
        boolean containsPoint(Point p) {
            int crossings = 0;
            for (int i = 0; i < vertices.size(); i++) {
                Point v1 = vertices.get(i);
                Point v2 = vertices.get((i + 1) % vertices.size());
                if (((v1.y <= p.y && p.y < v2.y) || (v2.y <= p.y && p.y < v1.y)) &&
                    (p.x < (v2.x - v1.x) * (p.y - v1.y) / (v2.y - v1.y) + v1.x)) {
                    crossings++;
                }
            }
            return (crossings % 2) == 1;
        }
        @Override
        List<Point> getKeyPoints() {
            return new ArrayList<>(vertices);
        }
        return outset;
    }
    
    /**
     * 圆形障碍物
     * 障碍物类
     */
    static class CircularObstacle extends Obstacle {
        Point center;
        double radius;
    private static class Obstacle {
        List<Point> points; // 多边形顶点(对圆形为空)
        Point center;       // 圆心(仅对圆形有效)
        double radius;      // 半径(仅对圆形有效)
        boolean isCircle;
        
        CircularObstacle(Point center, double radius) {
            this.center = center;
        // 多边形构造函数
        Obstacle(List<Point> points) {
            this.points = new ArrayList<>(points);
            this.isCircle = false;
            ensureCounterClockwise(this.points); // 确保顺时针(对障碍物是内部区域)
        }
        // 圆形构造函数
        Obstacle(Point center, double radius) {
            this.center = new Point(center.x, center.y);
            this.radius = radius;
            this.isCircle = true;
            this.points = new ArrayList<>();
        }
        
        @Override
        List<PathSegment> clipSegment(PathSegment seg) {
            double dx = seg.end.x - seg.start.x;
            double dy = seg.end.y - seg.start.y;
            double fx = seg.start.x - center.x;
            double fy = seg.start.y - center.y;
            double a = dx * dx + dy * dy;
            double b = 2 * (fx * dx + fy * dy);
            double c = fx * fx + fy * fy - radius * radius;
            List<Double> tValues = new ArrayList<>();
            tValues.add(0.0);
            tValues.add(1.0);
            double discriminant = b * b - 4 * a * c;
            if (discriminant > 0) {
                double sqrtDisc = Math.sqrt(discriminant);
                double t1 = (-b - sqrtDisc) / (2 * a);
                double t2 = (-b + sqrtDisc) / (2 * a);
                if (t1 > EPS && t1 < 1 - EPS) tValues.add(t1);
                if (t2 > EPS && t2 < 1 - EPS) tValues.add(t2);
        // 判断点是否在障碍物内部
        boolean contains(Point p) {
            if (isCircle) {
                return distance(p, center) <= radius;
            } else {
                return isPointInPolygon(p, points);
            }
        }
        // 获取线段与障碍物的交点
        List<Point> getIntersections(PathSegment segment) {
            List<Point> intersections = new ArrayList<>();
            
            Collections.sort(tValues);
            List<PathSegment> result = new ArrayList<>();
            for (int i = 0; i < tValues.size() - 1; i++) {
                double t1 = tValues.get(i);
                double t2 = tValues.get(i + 1);
                double tMid = (t1 + t2) / 2;
            if (isCircle) {
                // 线段与圆的交点
                double dx = segment.end.x - segment.start.x;
                double dy = segment.end.y - segment.start.y;
                double a = dx * dx + dy * dy;
                double b = 2 * (dx * (segment.start.x - center.x) +
                               dy * (segment.start.y - center.y));
                double c = (segment.start.x - center.x) * (segment.start.x - center.x) +
                          (segment.start.y - center.y) * (segment.start.y - center.y) -
                          radius * radius;
                
                Point midPoint = interpolate(seg.start, seg.end, tMid);
                if (!containsPoint(midPoint)) {
                    Point start = interpolate(seg.start, seg.end, t1);
                    Point end = interpolate(seg.start, seg.end, t2);
                    result.add(new PathSegment(start, end, seg.isMowing));
                double discriminant = b * b - 4 * a * c;
                if (discriminant >= 0) {
                    discriminant = Math.sqrt(discriminant);
                    for (int sign = -1; sign <= 1; sign += 2) {
                        double t = (-b + sign * discriminant) / (2 * a);
                        if (t >= 0 && t <= 1) {
                            intersections.add(new Point(
                                segment.start.x + t * dx,
                                segment.start.y + t * dy
                            ));
                        }
                    }
                }
            } else {
                // 线段与多边形的交点
                for (int i = 0; i < points.size(); i++) {
                    Point p1 = points.get(i);
                    Point p2 = points.get((i + 1) % points.size());
                    Point inter = getLineIntersection(
                        segment.start, segment.end, p1, p2);
                    if (inter != null) {
                        intersections.add(inter);
                    }
                }
            }
            
            return result;
            return intersections;
        }
        
        @Override
        boolean doesSegmentIntersect(Point p1, Point p2) {
            Point closest = closestPointOnSegment(center, p1, p2);
            // 将与圆的相切也视为相交,避免路径擦边
            return distance(center, closest) <= radius + EPS;
        boolean isCircle() {
            return isCircle;
        }
        @Override
        boolean containsPoint(Point p) {
            return distance(center, p) < radius - EPS;
        }
        @Override
        List<Point> getKeyPoints() {
            List<Point> points = new ArrayList<>();
            int numPoints = 8; // 八边形近似
    }
    /**
     * 判断点是否在多边形内部(射线法)
     */
    private static boolean isPointInPolygon(Point p, List<Point> polygon) {
        boolean inside = false;
        for (int i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) {
            Point pi = polygon.get(i);
            Point pj = polygon.get(j);
            
            for (int i = 0; i < numPoints; i++) {
                double angle = 2 * Math.PI * i / numPoints;
                points.add(new Point(
                    center.x + radius * Math.cos(angle),
                    center.y + radius * Math.sin(angle)
                ));
            if (((pi.y > p.y) != (pj.y > p.y)) &&
                (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x)) {
                inside = !inside;
            }
            return points;
        }
        return inside;
    }
    
    /**
     * 路径段
     * 计算两条线段的交点
     */
    public static class PathSegment {
        public Point start, end;
        public boolean isMowing;
    private static Point getLineIntersection(Point p1, Point p2, Point p3, Point p4) {
        double denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
        if (Math.abs(denom) < 1e-6) return null; // 平行
        
        public PathSegment(Point start, Point end, boolean isMowing) {
            this.start = start;
            this.end = end;
            this.isMowing = isMowing;
        double t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / denom;
        double u = -((p1.x - p2.x) * (p1.y - p3.y) - (p1.y - p2.y) * (p1.x - p3.x)) / denom;
        if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
            return new Point(
                p1.x + t * (p2.x - p1.x),
                p1.y + t * (p2.y - p1.y)
            );
        }
        @Override
        public String toString() {
            return String.format("%s -> %s [%s]", start, end, isMowing ? "MOW" : "MOVE");
        }
    }
    /**
     * 点类
     */
    public static class Point {
        public double x, y;
        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (!(obj instanceof Point)) return false;
            Point other = (Point) obj;
            return Math.abs(x - other.x) < EPS && Math.abs(y - other.y) < EPS;
        }
        @Override
        public int hashCode() {
            return Double.hashCode(x) * 31 + Double.hashCode(y);
        }
        @Override
        public String toString() {
            return String.format("(%.2f, %.2f)", x, y);
        }
    }
    /**
     * 边界框
     */
    private static class Bounds {
        double minX, maxX, minY, maxY;
        Bounds(double minX, double maxX, double minY, double maxY) {
            this.minX = minX;
            this.maxX = maxX;
            this.minY = minY;
            this.maxY = maxY;
        }
    }
    // ==================== 几何工具函数 ====================
    private static Double lineIntersection(Point a1, Point a2, Point b1, Point b2) {
        double det = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x);
        if (Math.abs(det) < EPS) return null;
        double t = ((b1.x - a1.x) * (b2.y - b1.y) - (b1.y - a1.y) * (b2.x - b1.x)) / det;
        double u = ((a1.x - b1.x) * (a2.y - a1.y) - (a1.y - b1.y) * (a2.x - a1.x)) / (-det);
        if (t >= -EPS && t <= 1 + EPS && u >= -EPS && u <= 1 + EPS) {
            return Math.max(0, Math.min(1, t));
        }
        return null;
    }
    
    private static boolean lineSegmentIntersection(Point a1, Point a2, Point b1, Point b2) {
        Double t = lineIntersection(a1, a2, b1, b2);
        return t != null;
    /**
     * 计算两点距离
     */
    private static double distance(Point p1, Point p2) {
        return Math.hypot(p1.x - p2.x, p1.y - p2.y);
    }
    
    private static Point interpolate(Point a, Point b, double t) {
        return new Point(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
    // ============ 以下是从A代码复用的方法 ============
    private static Point getFirstScanPoint(List<Point> polygon, double width, double angle) {
        List<Point> rotatedPoly = new ArrayList<>();
        for (Point p : polygon) rotatedPoly.add(rotatePoint(p, -angle));
        double minY = Double.MAX_VALUE;
        for (Point p : rotatedPoly) minY = Math.min(minY, p.y);
        double firstY = minY + width/2;
        List<Double> xInter = getXIntersections(rotatedPoly, firstY);
        if (xInter.isEmpty()) return polygon.get(0);
        Collections.sort(xInter);
        return rotatePoint(new Point(xInter.get(0), firstY), angle);
    }
    
    private static Point closestPointOnSegment(Point p, Point a, Point b) {
        double ax = b.x - a.x;
        double ay = b.y - a.y;
        double bx = p.x - a.x;
        double by = p.y - a.y;
        double dot = ax * bx + ay * by;
        double lenSq = ax * ax + ay * ay;
        double t = (lenSq > EPS) ? Math.max(0, Math.min(1, dot / lenSq)) : 0;
        return new Point(a.x + t * ax, a.y + t * ay);
    }
}
    private static List<Point> alignBoundaryStart(List<Point> boundary, Point targetStart) {
        int bestIdx = 0;
        double minDist = Double.MAX_VALUE;
        for (int i = 0; i < boundary.size(); i++) {
            double d = Math.hypot(boundary.get(i).x - targetStart.x, boundary.get(i).y - targetStart.y);
            if (d < minDist) { minDist = d; bestIdx = i; }
        }
        List<Point> aligned = new ArrayList<>();
        for (int i = 0; i < boundary.size(); i++) {
            aligned.add(boundary.get((bestIdx + i) % boundary.size()));
        }
        return aligned;
    }
    private static List<Double> getXIntersections(List<Point> rotatedPoly, double y) {
        List<Double> xIntersections = new ArrayList<>();
        for (int i = 0; i < rotatedPoly.size(); i++) {
            Point p1 = rotatedPoly.get(i);
            Point p2 = rotatedPoly.get((i + 1) % rotatedPoly.size());
            if ((p1.y <= y && p2.y > y) || (p2.y <= y && p1.y > y)) {
                double x = p1.x + (y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
                xIntersections.add(x);
            }
        }
        return xIntersections;
    }
    private static double findOptimalAngle(List<Point> polygon) {
        double bestAngle = 0;
        double minHeight = Double.MAX_VALUE;
        for (int i = 0; i < polygon.size(); i++) {
            Point p1 = polygon.get(i), p2 = polygon.get((i + 1) % polygon.size());
            double angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
            double h = calculateHeightAtAngle(polygon, angle);
            if (h < minHeight) { minHeight = h; bestAngle = angle; }
        }
        return bestAngle;
    }
    private static double calculateHeightAtAngle(List<Point> poly, double angle) {
        double minY = Double.MAX_VALUE, maxY = -Double.MAX_VALUE;
        for (Point p : poly) {
            Point rp = rotatePoint(p, -angle);
            minY = Math.min(minY, rp.y); maxY = Math.max(maxY, rp.y);
        }
        return maxY - minY;
    }
    private static List<Point> getInsetPolygon(List<Point> points, double margin) {
        List<Point> result = new ArrayList<>();
        int n = points.size();
        for (int i = 0; i < n; i++) {
            Point pPrev = points.get((i - 1 + n) % n);
            Point pCurr = points.get(i);
            Point pNext = points.get((i + 1) % n);
            double d1x = pCurr.x - pPrev.x, d1y = pCurr.y - pPrev.y;
            double l1 = Math.hypot(d1x, d1y);
            double d2x = pNext.x - pCurr.x, d2y = pNext.y - pCurr.y;
            double l2 = Math.hypot(d2x, d2y);
            if (l1 < 1e-6 || l2 < 1e-6) continue;
            double n1x = -d1y / l1, n1y = d1x / l1;
            double n2x = -d2y / l2, n2y = d2x / l2;
            double bisectorX = n1x + n2x, bisectorY = n1y + n2y;
            double bLen = Math.hypot(bisectorX, bisectorY);
            if (bLen < 1e-6) { bisectorX = n1x; bisectorY = n1y; }
            else { bisectorX /= bLen; bisectorY /= bLen; }
            double cosHalfAngle = n1x * bisectorX + n1y * bisectorY;
            double dist = margin / Math.max(cosHalfAngle, 0.1);
            dist = Math.min(dist, margin * 5);
            result.add(new Point(pCurr.x + bisectorX * dist, pCurr.y + bisectorY * dist));
        }
        return result;
    }
    private static Point rotatePoint(Point p, double angle) {
        double cos = Math.cos(angle), sin = Math.sin(angle);
        return new Point(p.x * cos - p.y * sin, p.x * sin + p.y * cos);
    }
    private static void ensureCounterClockwise(List<Point> points) {
        double sum = 0;
        for (int i = 0; i < points.size(); i++) {
            Point p1 = points.get(i), p2 = points.get((i + 1) % points.size());
            sum += (p2.x - p1.x) * (p2.y + p1.y);
        }
        if (sum > 0) Collections.reverse(points);
    }
    private static List<Point> parseCoordinates(String coordinates) {
        List<Point> points = new ArrayList<>();
        String[] pairs = coordinates.split(";");
        for (String pair : pairs) {
            String[] xy = pair.split(",");
            if (xy.length == 2) points.add(new Point(Double.parseDouble(xy[0]), Double.parseDouble(xy[1])));
        }
        if (points.size() > 1 && points.get(0).equals(points.get(points.size()-1))) points.remove(points.size()-1);
        return points;
    }
    public static class Point {
        public double x, y;
        public Point(double x, double y) { this.x = x; this.y = y; }
        @Override
        public boolean equals(Object o) {
            if (!(o instanceof Point)) return false;
            Point p = (Point) o;
            return Math.abs(x - p.x) < 1e-4 && Math.abs(y - p.y) < 1e-4;
        }
    }
    public static class PathSegment {
        public Point start, end;
        public boolean isMowing;
        public PathSegment(Point s, Point e, boolean m) {
            this.start = s;
            this.end = e;
            this.isMowing = m;
        }
    }
}
src/lujing/YixinglujingNoObstacle.java
@@ -7,7 +7,20 @@
 * 修复:解决凹多边形扫描线跨越边界的问题,优化路径对齐
 */
public class YixinglujingNoObstacle {
    // 用法说明(无障碍物路径规划):
    // - 方法用途:根据地块边界、割草宽度与安全边距,生成覆盖全区域的割草路径。
    // - 参数:
    //   coordinates:地块边界坐标字符串,格式 "x1,y1;x2,y2;...",至少3个点,单位为米。
    //   widthStr:割草宽度(字符串,单位米),用于确定扫描线间距。
    //   marginStr:安全边距(字符串,单位米),用于将地块边界向内收缩,避免贴边作业。
    // - 返回值:List<PathSegment>,其中 PathSegment.start/end 为坐标点,isMowing 为 true 表示割草段,false 表示空走段。
    // - 失败情况:当边界点不足或内缩后区域过小,返回空列表。
    // - 使用示例:
    //   String boundary = "0,0;20,0;20,15;0,15";
    //   String width = "0.3";
    //   String margin = "0.5";
    //   List<YixinglujingNoObstacle.PathSegment> path =
    //       YixinglujingNoObstacle.planPath(boundary, width, margin);
    public static List<PathSegment> planPath(String coordinates, String widthStr, String marginStr) {
        List<Point> rawPoints = parseCoordinates(coordinates);
        if (rawPoints.size() < 3) return new ArrayList<>();
src/zhuye/Shouye.java
@@ -31,7 +31,8 @@
import java.util.Map;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
// import java.util.function.Consumer;
import chuankou.DataListener;
import java.awt.geom.Point2D;
import publicway.Gpstoxuzuobiao;
@@ -110,19 +111,25 @@
    
    private boolean pathPreviewActive;
    
    private final Consumer<String> serialLineListener = line -> {
        SwingUtilities.invokeLater(() -> {
            updateDataPacketCountLabel();
            // 如果收到GGA数据,立即更新拖尾
            if (line != null) {
                String trimmed = line.trim();
                if (trimmed.startsWith("$GNGGA") || trimmed.startsWith("$GPGGA") || trimmed.startsWith("$GBGGA")) {
                    if (mapRenderer != null && !pathPreviewActive) {
                        mapRenderer.forceUpdateIdleMowerTrail();
    private final DataListener<String> serialLineListener = new DataListener<String>() {
        @Override
        public void accept(final String line) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    updateDataPacketCountLabel();
                    // 如果收到GGA数据,立即更新拖尾
                    if (line != null) {
                        String trimmed = line.trim();
                        if (trimmed.startsWith("$GNGGA") || trimmed.startsWith("$GPGGA") || trimmed.startsWith("$GBGGA")) {
                            if (mapRenderer != null && !pathPreviewActive) {
                                mapRenderer.forceUpdateIdleMowerTrail();
                            }
                        }
                    }
                }
            }
        });
            });
        }
    };
    private static final int FLOAT_ICON_SIZE = 32;
    private JButton endDrawingButton;
@@ -193,6 +200,10 @@
        scheduleIdentifierCheck();
    }
    private static boolean isFinite(double d) {
        return !Double.isNaN(d) && !Double.isInfinite(d);
    }
    public static Shouye getInstance() {
        return instance;
    }
@@ -2555,7 +2566,7 @@
        double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
        double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
        if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
        if (!isFinite(lat) || !isFinite(lon)) {
            discardLatestCoordinate(latest);
            lastMowerCoordinate = latest;
            return;
@@ -2563,7 +2574,7 @@
        double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
        Point2D.Double candidate = new Point2D.Double(local[0], local[1]);
        if (!Double.isFinite(candidate.x) || !Double.isFinite(candidate.y)) {
        if (!isFinite(candidate.x) || !isFinite(candidate.y)) {
            discardLatestCoordinate(latest);
            lastMowerCoordinate = latest;
            return;
@@ -2661,7 +2672,7 @@
        double x = parseMetersValue(device.getRealtimeX());
        double y = parseMetersValue(device.getRealtimeY());
        if (!Double.isFinite(x) || !Double.isFinite(y)) {
        if (!isFinite(x) || !isFinite(y)) {
            JOptionPane.showMessageDialog(this, "当前定位数据无效,请稍后再试。", "提示", JOptionPane.WARNING_MESSAGE);
            return -1;
        }
@@ -2739,7 +2750,7 @@
        }
        double x = parseMetersValue(device.getRealtimeX());
        double y = parseMetersValue(device.getRealtimeY());
        if (!Double.isFinite(x) || !Double.isFinite(y)) {
        if (!isFinite(x) || !isFinite(y)) {
            return false;
        }
        return isDuplicateHandheldPoint(x, y);
@@ -2752,7 +2763,7 @@
        }
        double x = parseMetersValue(device.getRealtimeX());
        double y = parseMetersValue(device.getRealtimeY());
        return Double.isFinite(x) && Double.isFinite(y);
        return isFinite(x) && isFinite(y);
    }
    private boolean isDuplicateHandheldPoint(double x, double y) {
@@ -3560,7 +3571,7 @@
        double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
        double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
        if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
        if (!isFinite(lat) || !isFinite(lon)) {
            JOptionPane.showMessageDialog(this, "采集点坐标无效,请重新采集。", "提示", JOptionPane.WARNING_MESSAGE);
            return false;
        }
@@ -3717,7 +3728,7 @@
        }
        double baseLat = parseDMToDecimal(parts[0], parts[1]);
        double baseLon = parseDMToDecimal(parts[2], parts[3]);
        if (!Double.isFinite(baseLat) || !Double.isFinite(baseLon)) {
        if (!isFinite(baseLat) || !isFinite(baseLon)) {
            return null;
        }
        return new double[]{baseLat, baseLon};
@@ -3783,7 +3794,7 @@
        double centerX = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / d;
        double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / d;
        double radius = Math.hypot(centerX - x1, centerY - y1);
        if (!Double.isFinite(centerX) || !Double.isFinite(centerY) || !Double.isFinite(radius)) {
        if (!isFinite(centerX) || !isFinite(centerY) || !isFinite(radius)) {
            return null;
        }
        if (radius < 0.05) {