解决了异形无障碍物边界路径bug,另外优化了首页显示效果
| | |
| | | # 割草机地块障碍物配置文件 |
| | | # 生成时间:2025-12-23T18:32:39.445241900 |
| | | # 生成时间:2025-12-23T18:40:37.614553600 |
| | | # 坐标系:WGS84(度分格式) |
| | | |
| | | # ============ 地块基准站配置 ============ |
| | |
| | | #Dikuai Properties |
| | | #Tue Dec 23 18:32:39 CST 2025 |
| | | #Tue Dec 23 18:41:03 CST 2025 |
| | | LAND1.angleThreshold=-1 |
| | | LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E |
| | | LAND1.boundaryCoordinates=-99.64,193.56;185.77,182.30;61.84,424.89;237.59,415.88;235.34,539.80;-26.03,544.31 |
| | |
| | | LAND1.mowingSafetyDistance=0.53 |
| | | LAND1.mowingTrack=-1 |
| | | LAND1.mowingWidth=200 |
| | | LAND1.plannedPath=-25.601252,543.772523;-98.992501,194.064867;184.886062,182.865285;60.950435,425.466300;237.049774,416.438391;234.819374,539.278905;-25.601252,543.772523;-23.565057,543.737388;-96.965713,193.984906;-94.938925,193.904945;-21.528862,543.702253;-19.492667,543.667118;-92.912138,193.824985;-90.885350,193.745024;-17.456472,543.631983;-15.420277,543.596848;-88.858562,193.665063;-86.831775,193.585102;-13.384082,543.561713;-11.347887,543.526578;-84.804987,193.505141;-82.778200,193.425180;-9.311692,543.491443;-7.275497,543.456308;-80.751412,193.345219;-78.724624,193.265259;-5.239302,543.421173;-3.203107,543.386038;-76.697837,193.185298;-74.671049,193.105337;-1.166912,543.350903;0.869283,543.315768;-72.644261,193.025376;-70.617474,192.945415;2.905478,543.280633;4.941673,543.245498;-68.590686,192.865454;-66.563898,192.785493;6.977868,543.210363;9.014063,543.175228;-64.537111,192.705533;-62.510323,192.625572;11.050258,543.140093;13.086453,543.104958;-60.483536,192.545611;-58.456748,192.465650;15.122648,543.069823;17.158843,543.034688;-56.429960,192.385689;-54.403173,192.305728;19.195038,542.999553;21.231233,542.964418;-52.376385,192.225767;-50.349597,192.145807;23.267428,542.929283;25.303623,542.894148;-48.322810,192.065846;-46.296022,191.985885;27.339818,542.859013;29.376013,542.823877;-44.269235,191.905924;-42.242447,191.825963;31.412208,542.788742;33.448403,542.753607;-40.215659,191.746002;-38.188872,191.666041;35.484598,542.718472;37.520793,542.683337;-36.162084,191.586081;-34.135296,191.506120;39.556988,542.648202;41.593183,542.613067;-32.108509,191.426159;-30.081721,191.346198;43.629378,542.577932;45.665573,542.542797;-28.054933,191.266237;-26.028146,191.186276;47.701768,542.507662;49.737963,542.472527;-24.001358,191.106315;-21.974571,191.026355;51.774158,542.437392;53.810353,542.402257;-19.947783,190.946394;-17.920995,190.866433;55.846548,542.367122;57.882743,542.331987;-15.894208,190.786472;-13.867420,190.706511;59.918938,542.296852;61.955133,542.261717;-11.840632,190.626550;-9.813845,190.546589;63.991328,542.226582;66.027523,542.191447;-7.787057,190.466628;-5.760269,190.386668;68.063718,542.156312;70.099913,542.121177;-3.733482,190.306707;-1.706694,190.226746;72.136108,542.086042;74.172303,542.050907;0.320093,190.146785;2.346881,190.066824;76.208498,542.015772;78.244693,541.980637;4.373669,189.986863;6.400456,189.906902;80.280888,541.945502;82.317083,541.910367;8.427244,189.826942;10.454032,189.746981;84.353278,541.875232;86.389473,541.840097;61.955917,425.414753;61.670804,424.056196;12.480819,189.667020;14.507607,189.587059;63.119317,421.220767;63.977733,425.311103;88.425668,541.804962;90.461863,541.769827;65.999549,425.207453;64.567829,418.385338;16.534394,189.507098;18.561182,189.427137;66.016342,415.549910;68.021365,425.103802;92.498058,541.734692;94.534253,541.699557;70.043181,425.000152;67.464854,412.714481;20.587970,189.347176;22.614757,189.267216;68.913367,409.879053;72.064997,424.896501;96.570448,541.664422;98.606643,541.629287;74.086814,424.792851;70.361880,407.043624;24.641545,189.187255;26.668333,189.107294;71.810392,404.208195;76.108630,424.689200;100.642838,541.594152;102.679033,541.559017;78.130446,424.585550;73.258905,401.372767;28.695120,189.027333;30.721908,188.947372;74.707417,398.537338;80.152262,424.481900;104.715228,541.523882;106.751423,541.488747;82.174078,424.378249;76.155930,395.701910;32.748696,188.867411;34.775483,188.787450;77.604442,392.866481;84.195894,424.274599;108.787618,541.453612;110.823813,541.418477;86.217710,424.170948;79.052955,390.031053;36.802271,188.707490;38.829058,188.627529;80.501468,387.195624;88.239526,424.067298;112.860008,541.383342;114.896203,541.348207;90.261342,423.963647;81.949980,384.360195;40.855846,188.547568;42.882634,188.467607;83.398493,381.524767;92.283158,423.859997;116.932398,541.313072;118.968593,541.277937;94.304974,423.756347;84.847005,378.689338;44.909421,188.387646;46.936209,188.307685;86.295518,375.853910;96.326790,423.652696;121.004788,541.242802;123.040983,541.207667;98.348606,423.549046;87.744030,373.018481;48.962997,188.227724;50.989784,188.147764;89.192543,370.183052;100.370422,423.445395;125.077177,541.172532;127.113372,541.137397;102.392238,423.341745;90.641056,367.347624;53.016572,188.067803;55.043360,187.987842;92.089568,364.512195;104.414054,423.238094;129.149567,541.102262;131.185762,541.067127;106.435870,423.134444;93.538081,361.676767;57.070147,187.907881;59.096935,187.827920;94.986593,358.841338;108.457686,423.030794;133.221957,541.031992;135.258152,540.996857;110.479502,422.927143;96.435106,356.005910;61.123722,187.747959;63.150510,187.667998;97.883618,353.170481;112.501318,422.823493;137.294347,540.961722;139.330542,540.926587;114.523134,422.719842;99.332131,350.335052;65.177298,187.588038;67.204085,187.508077;100.780644,347.499624;116.544950,422.616192;141.366737,540.891452;143.402932,540.856317;118.566766,422.512541;102.229156,344.664195;69.230873,187.428116;71.257661,187.348155;103.677669,341.828767;120.588582,422.408891;145.439127,540.821182;147.475322,540.786047;122.610398,422.305241;105.126181,338.993338;73.284448,187.268194;75.311236,187.188233;106.574694,336.157910;124.632214,422.201590;149.511517,540.750911;151.547712,540.715776;126.654030,422.097940;108.023206,333.322481;77.338023,187.108272;79.364811,187.028312;109.471719,330.487052;128.675846,421.994289;153.583907,540.680641;155.620102,540.645506;130.697662,421.890639;110.920231,327.651624;81.391599,186.948351;83.418386,186.868390;112.368744,324.816195;132.719478,421.786988;157.656297,540.610371;159.692492,540.575236;134.741294,421.683338;113.817257,321.980767;85.445174,186.788429;87.471962,186.708468;115.265769,319.145338;136.763111,421.579688;161.728687,540.540101;163.764882,540.504966;138.784927,421.476037;116.714282,316.309909;89.498749,186.628507;91.525537,186.548546;118.162794,313.474481;140.806743,421.372387;165.801077,540.469831;167.837272,540.434696;142.828559,421.268736;119.611307,310.639052;93.552325,186.468586;95.579112,186.388625;121.059819,307.803624;144.850375,421.165086;169.873467,540.399561;171.909662,540.364426;146.872191,421.061435;122.508332,304.968195;97.605900,186.308664;99.632687,186.228703;123.956845,302.132767;148.894007,420.957785;173.945857,540.329291;175.982052,540.294156;150.915823,420.854135;125.405357,299.297338;101.659475,186.148742;103.686263,186.068781;126.853870,296.461909;152.937639,420.750484;178.018247,540.259021;180.054442,540.223886;154.959455,420.646834;128.302382,293.626481;105.713050,185.988820;107.739838,185.908860;129.750895,290.791052;156.981271,420.543183;182.090637,540.188751;184.126832,540.153616;159.003087,420.439533;131.199407,287.955624;109.766626,185.828899;111.793413,185.748938;132.647920,285.120195;161.024903,420.335883;186.163027,540.118481;188.199222,540.083346;163.046719,420.232232;134.096433,282.284767;113.820201,185.668977;115.846989,185.589016;135.544945,279.449338;165.068535,420.128582;190.235417,540.048211;192.271612,540.013076;167.090351,420.024931;136.993458,276.613909;117.873776,185.509055;119.900564,185.429094;138.441970,273.778481;169.112167,419.921281;194.307807,539.977941;196.344002,539.942806;171.133983,419.817630;139.890483,270.943052;121.927351,185.349134;123.954139,185.269173;141.338995,268.107624;173.155799,419.713980;198.380197,539.907671;200.416392,539.872536;175.177615,419.610330;142.787508,265.272195;125.980927,185.189212;128.007714,185.109251;144.236021,262.436766;177.199431,419.506679;202.452587,539.837401;204.488782,539.802266;179.221247,419.403029;145.684533,259.601338;130.034502,185.029290;132.061290,184.949329;147.133046,256.765909;181.243063,419.299378;206.524977,539.767131;208.561172,539.731996;183.264879,419.195728;148.581558,253.930481;134.088077,184.869368;136.114865,184.789408;150.030071,251.095052;185.286695,419.092077;210.597367,539.696861;212.633562,539.661726;187.308511,418.988427;151.478583,248.259624;138.141652,184.709447;140.168440,184.629486;152.927096,245.424195;189.330327,418.884777;214.669757,539.626591;216.705952,539.591456;191.352143,418.781126;154.375609,242.588766;142.195228,184.549525;144.222015,184.469564;155.824121,239.753338;193.373959,418.677476;218.742147,539.556321;220.778342,539.521186;195.395775,418.573825;157.272634,236.917909;146.248803,184.389603;148.275591,184.309642;158.721146,234.082481;197.417591,418.470175;222.814537,539.486051;224.850732,539.450916;199.439408,418.366524;160.169659,231.247052;150.302378,184.229681;152.329166,184.149721;161.618171,228.411623;201.461224,418.262874;226.886927,539.415781;228.923122,539.380646;203.483040,418.159224;163.066684,225.576195;154.355954,184.069760;156.382741,183.989799;164.515197,222.740766;205.504856,418.055573;230.959317,539.345511;232.995512,539.310376;207.526672,417.951923;165.963709,219.905338;158.409529,183.909838;160.436316,183.829877;167.412222,217.069909;209.548488,417.848272;234.836343,538.344336;234.999068,529.382159;211.570304,417.744622;168.860734,214.234481;162.463104,183.749916;164.489892,183.669955;170.309247,211.399052;213.592120,417.640971;235.161793,520.419982;235.324518,511.457805;215.613936,417.537321;171.757759,208.563623;166.516679,183.589995;168.543467,183.510034;173.206272,205.728195;217.635752,417.433671;235.487243,502.495628;235.649969,493.533451;219.657568,417.330020;174.654784,202.892766;170.570255,183.430073;172.597042,183.350112;176.103297,200.057338;221.679384,417.226370;235.812694,484.571274;235.975419,475.609098;223.701200,417.122719;177.551810,197.221909;174.623830,183.270151;176.650618,183.190190;179.000322,194.386481;225.723016,417.019069;236.138144,466.646921;236.300869,457.684744;227.744832,416.915418;180.448835,191.551052;178.677405,183.110229;180.704193,183.030269;181.897347,188.715623;229.766648,416.811768;236.463594,448.722567;236.626319,439.760390;231.788464,416.708118;183.345860,185.880195;182.730980,182.950308;184.757768,182.870347;184.794372,183.044766;233.810280,416.604467;236.789045,430.798213;236.951770,421.836036;235.832096,416.500817 |
| | | LAND1.plannedPath=-25.601252,543.772523;-98.992501,194.064867;184.886062,182.865285;60.950435,425.466300;237.049774,416.438391;234.819374,539.278905;-25.601252,543.772523;-24.583154,543.754955;-97.979107,194.024887;-95.952319,193.944926;-22.546959,543.719820;-20.510764,543.684685;-93.925532,193.864965;-91.898744,193.785004;-18.474569,543.649550;-16.438374,543.614415;-89.871956,193.705043;-87.845169,193.625082;-14.402179,543.579280;-12.365984,543.544145;-85.818381,193.545122;-83.791593,193.465161;-10.329789,543.509010;-8.293594,543.473875;-81.764806,193.385200;-79.738018,193.305239;-6.257399,543.438740;-4.221204,543.403605;-77.711230,193.225278;-75.684443,193.145317;-2.185009,543.368470;-0.148814,543.333335;-73.657655,193.065356;-71.630868,192.985396;1.887381,543.298200;3.923576,543.263065;-69.604080,192.905435;-67.577292,192.825474;5.959771,543.227930;7.995966,543.192795;-65.550505,192.745513;-63.523717,192.665552;10.032161,543.157660;12.068356,543.122525;-61.496929,192.585591;-59.470142,192.505630;14.104551,543.087390;16.140746,543.052255;-57.443354,192.425670;-55.416566,192.345709;18.176941,543.017120;20.213136,542.981985;-53.389779,192.265748;-51.362991,192.185787;22.249331,542.946850;24.285526,542.911715;-49.336204,192.105826;-47.309416,192.025865;26.321721,542.876580;28.357916,542.841445;-45.282628,191.945904;-43.255841,191.865944;30.394110,542.806310;32.430305,542.771175;-41.229053,191.785983;-39.202265,191.706022;34.466500,542.736040;36.502695,542.700905;-37.175478,191.626061;-35.148690,191.546100;38.538890,542.665770;40.575085,542.630635;-33.121903,191.466139;-31.095115,191.386178;42.611280,542.595500;44.647475,542.560365;-29.068327,191.306218;-27.041540,191.226257;46.683670,542.525230;48.719865,542.490095;-25.014752,191.146296;-22.987964,191.066335;50.756060,542.454960;52.792255,542.419825;-20.961177,190.986374;-18.934389,190.906413;54.828450,542.384690;56.864645,542.349555;-16.907601,190.826452;-14.880814,190.746491;58.900840,542.314420;60.937035,542.279285;-12.854026,190.666531;-10.827239,190.586570;62.973230,542.244150;65.009425,542.209015;-8.800451,190.506609;-6.773663,190.426648;67.045620,542.173880;69.081815,542.138745;-4.746876,190.346687;-2.720088,190.266726;71.118010,542.103610;73.154205,542.068475;-0.693300,190.186765;1.333487,190.106805;75.190400,542.033340;77.226595,541.998205;3.360275,190.026844;5.387063,189.946883;79.262790,541.963070;81.298985,541.927935;7.413850,189.866922;9.440638,189.786961;83.335180,541.892800;85.371375,541.857665;11.467425,189.707000;13.494213,189.627039;62.395060,422.638481;62.966825,425.362928;87.407570,541.822530;89.443765,541.787394;64.988641,425.259278;63.843573,419.803053;15.521001,189.547079;17.547788,189.467118;65.292086,416.967624;67.010457,425.155627;91.479960,541.752259;93.516155,541.717124;69.032273,425.051977;66.740598,414.132195;19.574576,189.387157;21.601364,189.307196;68.189111,411.296767;71.054089,424.948326;95.552350,541.681989;97.588545,541.646854;73.075906,424.844676;69.637623,408.461338;23.628151,189.227235;25.654939,189.147274;71.086136,405.625910;75.097722,424.741026;99.624740,541.611719;101.660935,541.576584;77.119538,424.637375;72.534648,402.790481;27.681726,189.067313;29.708514,188.987353;73.983161,399.955053;79.141354,424.533725;103.697130,541.541449;105.733325,541.506314;81.163170,424.430074;75.431674,397.119624;31.735302,188.907392;33.762089,188.827431;76.880186,394.284195;83.184986,424.326424;107.769520,541.471179;109.805715,541.436044;85.206802,424.222773;78.328699,391.448767;35.788877,188.747470;37.815665,188.667509;79.777211,388.613338;87.228618,424.119123;111.841910,541.400909;113.878105,541.365774;89.250434,424.015473;81.225724,385.777910;39.842452,188.587548;41.869240,188.507587;82.674236,382.942481;91.272250,423.911822;115.914300,541.330639;117.950495,541.295504;93.294066,423.808172;84.122749,380.107053;43.896028,188.427627;45.922815,188.347666;85.571262,377.271624;95.315882,423.704521;119.986690,541.260369;122.022885,541.225234;97.337698,423.600871;87.019774,374.436195;47.949603,188.267705;49.976390,188.187744;88.468287,371.600767;99.359514,423.497221;124.059080,541.190099;126.095275,541.154964;101.381330,423.393570;89.916799,368.765338;52.003178,188.107783;54.029966,188.027822;91.365312,365.929910;103.403146,423.289920;128.131470,541.119829;130.167665,541.084694;105.424962,423.186269;92.813824,363.094481;56.056753,187.947861;58.083541,187.867901;94.262337,360.259052;107.446778,423.082619;132.203860,541.049559;134.240055,541.014424;109.468594,422.978968;95.710850,357.423624;60.110329,187.787940;62.137116,187.707979;97.159362,354.588195;111.490410,422.875318;136.276250,540.979289;138.312445,540.944154;113.512226,422.771668;98.607875,351.752767;64.163904,187.628018;66.190691,187.548057;100.056387,348.917338;115.534042,422.668017;140.348640,540.909019;142.384835,540.873884;117.555858,422.564367;101.504900,346.081910;68.217479,187.468096;70.244267,187.388135;102.953412,343.246481;119.577674,422.460716;144.421030,540.838749;146.457225,540.803614;121.599490,422.357066;104.401925,340.411052;72.271054,187.308175;74.297842,187.228214;105.850437,337.575624;123.621306,422.253415;148.493420,540.768479;150.529615,540.733344;125.643122,422.149765;107.298950,334.740195;76.324630,187.148253;78.351417,187.068292;108.747463,331.904767;127.664938,422.046115;152.565810,540.698209;154.602005,540.663074;129.686754,421.942464;110.195975,329.069338;80.378205,186.988331;82.404993,186.908370;111.644488,326.233909;131.708570,421.838814;156.638200,540.627939;158.674395,540.592804;133.730386,421.735163;113.093000,323.398481;84.431780,186.828409;86.458568,186.748449;114.541513,320.563052;135.752203,421.631513;160.710590,540.557669;162.746785,540.522534;137.774019,421.527862;115.990025,317.727624;88.485355,186.668488;90.512143,186.588527;117.438538,314.892195;139.795835,421.424212;164.782980,540.487399;166.819175,540.452264;141.817651,421.320562;118.887051,312.056767;92.538931,186.508566;94.565718,186.428605;120.335563,309.221338;143.839467,421.216911;168.855370,540.417129;170.891565,540.381994;145.861283,421.113261;121.784076,306.385909;96.592506,186.348644;98.619294,186.268683;123.232588,303.550481;147.883099,421.009610;172.927760,540.346859;174.963955,540.311724;149.904915,420.905960;124.681101,300.715052;100.646081,186.188723;102.672869,186.108762;126.129613,297.879624;151.926731,420.802309;177.000150,540.276589;179.036345,540.241454;153.948547,420.698659;127.578126,295.044195;104.699657,186.028801;106.726444,185.948840;129.026639,292.208767;155.970363,420.595009;181.072540,540.206319;183.108735,540.171184;157.992179,420.491358;130.475151,289.373338;108.753232,185.868879;110.780019,185.788918;131.923664,286.537909;160.013995,420.387708;185.144930,540.136049;187.181125,540.100914;162.035811,420.284057;133.372176,283.702481;112.806807,185.708957;114.833595,185.628997;134.820689,280.867052;164.057627,420.180407;189.217320,540.065779;191.253515,540.030644;166.079443,420.076756;136.269201,278.031624;116.860382,185.549036;118.887170,185.469075;137.717714,275.196195;168.101259,419.973106;193.289710,539.995509;195.325905,539.960374;170.123075,419.869456;139.166227,272.360766;120.913958,185.389114;122.940745,185.309153;140.614739,269.525338;172.144891,419.765805;197.362100,539.925239;199.398295,539.890104;174.166707,419.662155;142.063252,266.689909;124.967533,185.229192;126.994320,185.149231;143.511764,263.854481;176.188523,419.558504;201.434490,539.854969;203.470685,539.819834;178.210339,419.454854;144.960277,261.019052;129.021108,185.069271;131.047896,184.989310;146.408789,258.183624;180.232155,419.351203;205.506880,539.784699;207.543075,539.749564;182.253971,419.247553;147.857302,255.348195;133.074683,184.909349;135.101471,184.829388;149.305815,252.512766;184.275787,419.143903;209.579270,539.714428;211.615465,539.679293;186.297603,419.040252;150.754327,249.677338;137.128259,184.749427;139.155046,184.669466;152.202840,246.841909;188.319419,418.936602;213.651660,539.644158;215.687855,539.609023;190.341235,418.832951;153.651352,244.006481;141.181834,184.589505;143.208622,184.509545;155.099865,241.171052;192.363051,418.729301;217.724050,539.573888;219.760245,539.538753;194.384867,418.625650;156.548377,238.335624;145.235409,184.429584;147.262197,184.349623;157.996890,235.500195;196.406683,418.522000;221.796439,539.503618;223.832634,539.468483;198.428499,418.418350;159.445403,232.664766;149.288984,184.269662;151.315772,184.189701;160.893915,229.829338;200.450316,418.314699;225.868829,539.433348;227.905024,539.398213;202.472132,418.211049;162.342428,226.993909;153.342560,184.109740;155.369347,184.029779;163.790940,224.158481;204.493948,418.107398;229.941219,539.363078;231.977414,539.327943;206.515764,418.003748;165.239453,221.323052;157.396135,183.949818;159.422923,183.869858;166.687965,218.487623;208.537580,417.900097;234.013609,539.292808;234.917706,533.863247;210.559396,417.796447;168.136478,215.652195;161.449710,183.789897;163.476498,183.709936;169.584991,212.816766;212.581212,417.692797;235.080431,524.901070;235.243156,515.938894;214.603028,417.589146;171.033503,209.981338;165.503286,183.629975;167.530073,183.550014;172.482016,207.145909;216.624844,417.485496;235.405881,506.976717;235.568606,498.014540;218.646660,417.381845;173.930528,204.310481;169.556861,183.470053;171.583648,183.390092;175.379041,201.475052;220.668476,417.278195;235.731331,489.052363;235.894056,480.090186;222.690292,417.174544;176.827553,198.639623;173.610436,183.310132;175.637224,183.230171;178.276066,195.804195;224.712108,417.070894;236.056781,471.128009;236.219507,462.165832;226.733924,416.967244;179.724578,192.968766;177.664011,183.150210;179.690799,183.070249;181.173091,190.133338;228.755740,416.863593;236.382232,453.203655;236.544957,444.241478;230.777556,416.759943;182.621604,187.297909;181.717587,182.990288;183.744374,182.910327;184.070116,184.462480;232.799372,416.656292;236.707682,435.279301;236.870407,426.317125;234.821188,416.552642 |
| | | LAND1.returnPathCoordinates=-1 |
| | | LAND1.returnPathRawCoordinates=-1 |
| | | LAND1.returnPointCoordinates=-1 |
| | | LAND1.updateTime=2025-12-23 18\:32\:39 |
| | | LAND1.updateTime=2025-12-23 18\:41\:03 |
| | | LAND1.userId=-1 |
| | |
| | | #Mower Configuration Properties - Updated |
| | | #Tue Dec 23 18:35:51 CST 2025 |
| | | #Tue Dec 23 19:08:18 CST 2025 |
| | | appVersion=-1 |
| | | boundaryLengthVisible=false |
| | | currentWorkLandNumber=LAND1 |
| | |
| | | handheldMarkerId=1872 |
| | | idleTrailDurationSeconds=60 |
| | | manualBoundaryDrawingMode=false |
| | | mapScale=2.83 |
| | | mapScale=4.08 |
| | | measurementModeEnabled=false |
| | | mowerId=860 |
| | | serialAutoConnect=true |
| | | serialBaudRate=115200 |
| | | serialPortName=COM15 |
| | | simCardNumber=-1 |
| | | viewCenterX=-148.00 |
| | | viewCenterY=278.47 |
| | | viewCenterX=-14.44 |
| | | viewCenterY=-332.04 |
| | |
| | | import java.awt.geom.Point2D; // 引入二维点类 |
| | | import java.util.ArrayList; // 引入动态数组 |
| | | import java.util.List; // 引入列表接口 |
| | | import lujing.Lunjingguihua; // 引入路径规划类 |
| | | import org.locationtech.jts.geom.Coordinate; // 引入坐标类 |
| | | import org.locationtech.jts.geom.GeometryFactory; // 引入几何工厂类 |
| | | import org.locationtech.jts.geom.Polygon; // 引入多边形类 |
| | | |
| | | /** // 文档注释开头 |
| | | * Helper class to parse and render planned mowing paths. // 辅助类说明 |
| | | */ // 文档注释结尾 |
| | | public final class lujingdraw { // 类定义,防止继承 |
| | | private static final Color PATH_COLOR = new Color(255, 0, 0, 220); // 路径颜色(红色半透明) |
| | | // 马尔斯绿色 (RGB: 102, 205, 170) - 用于内缩边界(围边) |
| | | private static final Color INNER_BOUNDARY_COLOR = new Color(102, 205, 170); |
| | | // 提香红透明度70% (RGB: 210, 4, 45, alpha: 178) - 用于割草作业路径 |
| | | private static final Color MOWING_PATH_COLOR = new Color(210, 4, 45, 178); |
| | | // 蓝色 - 用于非作业移动路径 |
| | | private static final Color TRAVEL_PATH_COLOR = new Color(0, 0, 255); |
| | | // 虚线样式 - 用于非作业移动路径 |
| | | private static final float[] DASH_PATTERN = {10.0f, 5.0f}; |
| | | private static final Color START_POINT_COLOR = new Color(0, 0, 0, 220); // 起点箭头颜色 |
| | | private static final Color END_POINT_COLOR = new Color(0, 0, 0, 220); // 终点箭头颜色 |
| | | |
| | |
| | | * Draw the planned mowing path. // 绘制路径 |
| | | */ // 文档注释结束 |
| | | public static void drawPlannedPath(Graphics2D g2d, List<Point2D.Double> path, double scale, double arrowScale) { // 绘制主方法 |
| | | drawPlannedPath(g2d, path, scale, arrowScale, null, null, null, null, null); // 调用重载方法 |
| | | } // 方法结束 |
| | | |
| | | /** // 文档注释开始 |
| | | * Draw the planned mowing path with segment information. // 绘制路径(带段信息) |
| | | * @param boundaryCoords 地块边界坐标字符串,用于绘制内缩边界 |
| | | * @param mowingWidth 割草宽度(米),字符串格式 |
| | | * @param safetyDistance 安全距离(米),字符串格式 |
| | | * @param obstaclesCoords 障碍物坐标字符串 |
| | | * @param mowingPattern 割草模式("parallel"或"spiral") |
| | | */ // 文档注释结束 |
| | | public static void drawPlannedPath(Graphics2D g2d, List<Point2D.Double> path, double scale, double arrowScale, |
| | | String boundaryCoords, String mowingWidth, String safetyDistance, String obstaclesCoords, String mowingPattern) { // 绘制主方法(重载) |
| | | if (path == null || path.size() < 2) { // 判定点数 |
| | | return; // 数据不足直接返回 |
| | | } // if结束 |
| | | |
| | | Stroke previous = g2d.getStroke(); // 保存原描边 |
| | | float strokeWidth = (float) (2.5 / Math.max(0.5, scale)); // 根据缩放计算线宽 |
| | | |
| | | // 1. 绘制内缩边界(围边)- 马尔斯绿色 |
| | | if (boundaryCoords != null && !boundaryCoords.trim().isEmpty() && !"-1".equals(boundaryCoords.trim())) { |
| | | drawInnerBoundary(g2d, boundaryCoords, safetyDistance, scale); |
| | | } |
| | | |
| | | // 2. 尝试重新生成路径段以区分作业路径和移动路径 |
| | | List<Lunjingguihua.PathSegment> segments = null; |
| | | if (boundaryCoords != null && mowingWidth != null) { |
| | | try { |
| | | // 解析割草模式 |
| | | String mode = "parallel"; // 默认平行模式 |
| | | if (mowingPattern != null && !mowingPattern.trim().isEmpty()) { |
| | | String pattern = mowingPattern.trim().toLowerCase(); |
| | | if ("1".equals(pattern) || "spiral".equals(pattern) || "螺旋式".equals(pattern) || "螺旋".equals(pattern)) { |
| | | mode = "spiral"; |
| | | } else if ("parallel".equals(pattern) || "平行线".equals(pattern) || "平行".equals(pattern)) { |
| | | mode = "parallel"; |
| | | } |
| | | } |
| | | segments = Lunjingguihua.generatePathSegments( |
| | | boundaryCoords, |
| | | obstaclesCoords != null ? obstaclesCoords : "", |
| | | mowingWidth, |
| | | safetyDistance, |
| | | mode |
| | | ); |
| | | } catch (Exception e) { |
| | | // 如果重新生成失败,使用简单绘制方式 |
| | | segments = null; |
| | | } |
| | | } |
| | | |
| | | // 3. 根据是否有段信息选择不同的绘制方式 |
| | | if (segments != null && !segments.isEmpty()) { |
| | | // 有段信息:分别绘制作业路径和移动路径 |
| | | drawPathSegments(g2d, segments, scale, arrowScale); |
| | | } else { |
| | | // 无段信息:使用简单绘制方式(所有路径使用作业路径颜色) |
| | | Path2D polyline = new Path2D.Double(); // 创建折线 |
| | | boolean move = true; // 首段标记 |
| | | for (Point2D.Double point : path) { // 遍历点集 |
| | |
| | | } // if结束 |
| | | } // for结束 |
| | | |
| | | Stroke previous = g2d.getStroke(); // 保存原描边 |
| | | float strokeWidth = (float) (2.5 / Math.max(0.5, scale)); // 根据缩放计算线宽 |
| | | g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // 设置圆头圆角描边 |
| | | g2d.setColor(PATH_COLOR); // 设置线路颜色 |
| | | g2d.setColor(MOWING_PATH_COLOR); // 设置作业路径颜色(提香红70%透明度) |
| | | g2d.draw(polyline); // 绘制折线 |
| | | |
| | | Point2D.Double start = path.get(0); // 起点 |
| | |
| | | |
| | | drawArrowMarker(g2d, start, second, START_POINT_COLOR, scale, arrowScale); // 绘制起点箭头 |
| | | drawArrowMarker(g2d, prev, end, END_POINT_COLOR, scale, arrowScale); // 绘制终点箭头 |
| | | } |
| | | |
| | | g2d.setStroke(previous); // 恢复原描边 |
| | | } // 方法结束 |
| | | |
| | | /** // 文档注释开始 |
| | | * 绘制内缩边界(围边)- 马尔斯绿色 |
| | | */ // 文档注释结束 |
| | | private static void drawInnerBoundary(Graphics2D g2d, String boundaryCoords, String safetyDistanceStr, double scale) { |
| | | try { |
| | | List<Coordinate> boundary = Lunjingguihua.parseCoordinates(boundaryCoords); |
| | | if (boundary.size() < 4) { |
| | | return; // 边界点不足 |
| | | } |
| | | |
| | | // 解析安全距离 |
| | | double safetyDistance = 0.2; // 默认0.2米 |
| | | if (safetyDistanceStr != null && !safetyDistanceStr.trim().isEmpty()) { |
| | | try { |
| | | safetyDistance = Double.parseDouble(safetyDistanceStr.trim()); |
| | | } catch (NumberFormatException e) { |
| | | // 使用默认值 |
| | | } |
| | | } |
| | | |
| | | // 创建多边形并内缩 |
| | | GeometryFactory gf = new GeometryFactory(); |
| | | Polygon poly = gf.createPolygon(gf.createLinearRing(boundary.toArray(new Coordinate[0]))); |
| | | if (!poly.isValid()) { |
| | | poly = (Polygon) poly.buffer(0); |
| | | } |
| | | |
| | | // 内缩生成安全边界 |
| | | org.locationtech.jts.geom.Geometry innerBoundary = poly.buffer(-safetyDistance); |
| | | if (innerBoundary == null || innerBoundary.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | // 绘制内缩边界 |
| | | g2d.setColor(INNER_BOUNDARY_COLOR); // 马尔斯绿色 |
| | | float strokeWidth = (float) (3.0 / Math.max(0.5, scale)); |
| | | g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | | |
| | | // 将JTS几何体转换为Path2D并绘制 |
| | | Path2D.Double innerPath = geometryToPath2D(innerBoundary); |
| | | if (innerPath != null) { |
| | | g2d.draw(innerPath); |
| | | } |
| | | } catch (Exception e) { |
| | | // 绘制失败时忽略,不影响其他路径绘制 |
| | | } |
| | | } // 方法结束 |
| | | |
| | | /** // 文档注释开始 |
| | | * 将JTS几何体转换为Path2D |
| | | */ // 文档注释结束 |
| | | private static Path2D.Double geometryToPath2D(org.locationtech.jts.geom.Geometry geom) { |
| | | if (geom == null) { |
| | | return null; |
| | | } |
| | | Path2D.Double path = new Path2D.Double(); |
| | | if (geom instanceof Polygon) { |
| | | Polygon poly = (Polygon) geom; |
| | | addRingToPath(path, poly.getExteriorRing().getCoordinates(), true); |
| | | for (int i = 0; i < poly.getNumInteriorRing(); i++) { |
| | | addRingToPath(path, poly.getInteriorRingN(i).getCoordinates(), true); |
| | | } |
| | | } else if (geom instanceof org.locationtech.jts.geom.MultiPolygon) { |
| | | org.locationtech.jts.geom.MultiPolygon mp = (org.locationtech.jts.geom.MultiPolygon) geom; |
| | | for (int i = 0; i < mp.getNumGeometries(); i++) { |
| | | Polygon poly = (Polygon) mp.getGeometryN(i); |
| | | addRingToPath(path, poly.getExteriorRing().getCoordinates(), true); |
| | | for (int j = 0; j < poly.getNumInteriorRing(); j++) { |
| | | addRingToPath(path, poly.getInteriorRingN(j).getCoordinates(), true); |
| | | } |
| | | } |
| | | } |
| | | return path; |
| | | } // 方法结束 |
| | | |
| | | /** // 文档注释开始 |
| | | * 将坐标环添加到Path2D |
| | | */ // 文档注释结束 |
| | | private static void addRingToPath(Path2D.Double path, Coordinate[] coords, boolean close) { |
| | | if (coords == null || coords.length < 2) { |
| | | return; |
| | | } |
| | | path.moveTo(coords[0].x, coords[0].y); |
| | | for (int i = 1; i < coords.length; i++) { |
| | | path.lineTo(coords[i].x, coords[i].y); |
| | | } |
| | | if (close) { |
| | | path.closePath(); |
| | | } |
| | | } // 方法结束 |
| | | |
| | | /** // 文档注释开始 |
| | | * 绘制路径段(区分作业路径和移动路径) |
| | | */ // 文档注释结束 |
| | | private static void drawPathSegments(Graphics2D g2d, List<Lunjingguihua.PathSegment> segments, double scale, double arrowScale) { |
| | | if (segments == null || segments.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | float strokeWidth = (float) (2.5 / Math.max(0.5, scale)); |
| | | Stroke previous = g2d.getStroke(); |
| | | |
| | | // 分别绘制作业路径和移动路径 |
| | | Path2D.Double mowingPath = new Path2D.Double(); |
| | | Path2D.Double travelPath = new Path2D.Double(); |
| | | boolean mowingStarted = false; |
| | | boolean travelStarted = false; |
| | | |
| | | Coordinate lastMowingEnd = null; |
| | | Coordinate lastTravelEnd = null; |
| | | |
| | | for (Lunjingguihua.PathSegment seg : segments) { |
| | | if (seg == null || seg.start == null || seg.end == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (seg.isMowing) { |
| | | // 作业路径 - 提香红70%透明度 |
| | | if (!mowingStarted || lastMowingEnd == null || !equals2D(lastMowingEnd, seg.start)) { |
| | | mowingPath.moveTo(seg.start.x, seg.start.y); |
| | | mowingStarted = true; |
| | | } |
| | | mowingPath.lineTo(seg.end.x, seg.end.y); |
| | | lastMowingEnd = seg.end; |
| | | } else { |
| | | // 移动路径 - 蓝色虚线 |
| | | if (!travelStarted || lastTravelEnd == null || !equals2D(lastTravelEnd, seg.start)) { |
| | | travelPath.moveTo(seg.start.x, seg.start.y); |
| | | travelStarted = true; |
| | | } |
| | | travelPath.lineTo(seg.end.x, seg.end.y); |
| | | lastTravelEnd = seg.end; |
| | | } |
| | | } |
| | | |
| | | // 绘制作业路径 |
| | | if (mowingStarted) { |
| | | g2d.setColor(MOWING_PATH_COLOR); // 提香红70%透明度 |
| | | g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | | g2d.draw(mowingPath); |
| | | } |
| | | |
| | | // 绘制移动路径(虚线) |
| | | if (travelStarted) { |
| | | g2d.setColor(TRAVEL_PATH_COLOR); // 蓝色 |
| | | g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, DASH_PATTERN, 0)); |
| | | g2d.draw(travelPath); |
| | | } |
| | | |
| | | // 绘制起点和终点箭头 |
| | | if (!segments.isEmpty()) { |
| | | Lunjingguihua.PathSegment firstSeg = segments.get(0); |
| | | if (firstSeg != null && firstSeg.start != null && segments.size() > 1) { |
| | | Lunjingguihua.PathSegment secondSeg = segments.get(1); |
| | | if (secondSeg != null && secondSeg.start != null) { |
| | | Point2D.Double start = new Point2D.Double(firstSeg.start.x, firstSeg.start.y); |
| | | Point2D.Double second = new Point2D.Double(secondSeg.start.x, secondSeg.start.y); |
| | | drawArrowMarker(g2d, start, second, START_POINT_COLOR, scale, arrowScale); |
| | | } |
| | | } |
| | | |
| | | Lunjingguihua.PathSegment lastSeg = segments.get(segments.size() - 1); |
| | | if (lastSeg != null && lastSeg.end != null && segments.size() > 1) { |
| | | Lunjingguihua.PathSegment prevSeg = segments.get(segments.size() - 2); |
| | | if (prevSeg != null && prevSeg.end != null) { |
| | | Point2D.Double prev = new Point2D.Double(prevSeg.end.x, prevSeg.end.y); |
| | | Point2D.Double end = new Point2D.Double(lastSeg.end.x, lastSeg.end.y); |
| | | drawArrowMarker(g2d, prev, end, END_POINT_COLOR, scale, arrowScale); |
| | | } |
| | | } |
| | | } |
| | | |
| | | g2d.setStroke(previous); |
| | | } // 方法结束 |
| | | |
| | | /** // 文档注释开始 |
| | | * 比较两个坐标是否相同(容差) |
| | | */ // 文档注释结束 |
| | | private static boolean equals2D(Coordinate a, Coordinate b) { |
| | | if (a == b) return true; |
| | | if (a == null || b == null) return false; |
| | | return a.distance(b) < 1e-4; |
| | | } // 方法结束 |
| | | |
| | | private static void drawArrowMarker(Graphics2D g2d, Point2D.Double from, Point2D.Double to, Color color, double scale, double sizeScale) { // 绘制箭头辅助 |
| | | if (from == null || to == null) { // 判空 |
| | | return; // 数据不足返回 |
| | |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * 异形草地路径规划 - 围边+全局扫描版 V4.1 |
| | | * 优化:围边终点与弓字形起点自动对齐,实现无缝切换,确保路径不越界 |
| | | * 异形草地路径规划 - 凹多边形兼容优化版 V5.0 |
| | | * 修复:解决凹多边形扫描线跨越边界的问题,优化路径对齐 |
| | | */ |
| | | public class YixinglujingNoObstacle { |
| | | |
| | |
| | | double mowWidth = Double.parseDouble(widthStr); |
| | | double safeMargin = Double.parseDouble(marginStr); |
| | | |
| | | // 1. 预处理:逆时针化 |
| | | // 1. 预处理:确保逆时针顺序 |
| | | ensureCounterClockwise(rawPoints); |
| | | |
| | | // 2. 生成内缩多边形 |
| | | // 2. 生成内缩多边形(安全边界) |
| | | List<Point> boundary = getInsetPolygon(rawPoints, safeMargin); |
| | | if (boundary.size() < 3) return new ArrayList<>(); |
| | | |
| | | // 3. 确定最优扫描角度并找到弓字形路径的第一个作业起点 |
| | | // 3. 确定最优作业角度 |
| | | double bestAngle = findOptimalAngle(boundary); |
| | | |
| | | // 4. 获取首个作业点,用于对齐围边起点 |
| | | Point firstScanStart = getFirstScanPoint(boundary, mowWidth, bestAngle); |
| | | |
| | | // 4. 对齐围边起点:重新排列围边坐标,使最后一个点靠近(或等于)扫描起点 |
| | | // 5. 对齐围边:使围边最后结束于靠近扫描起点的位置 |
| | | List<Point> alignedBoundary = alignBoundaryStart(boundary, firstScanStart); |
| | | |
| | | List<PathSegment> finalPath = new ArrayList<>(); |
| | | |
| | | // 5. 【第一步】生成围边路径 |
| | | // 6. 第一阶段:围边路径 |
| | | 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)); |
| | | } |
| | | |
| | | // 6. 【第二步】从对齐后的终点开始生成内部扫描路径 |
| | | Point lastEdgePos = alignedBoundary.get(0); // 围边闭合回到起点 |
| | | // 7. 第二阶段:生成内部扫描路径(修复凹部空越问题) |
| | | Point lastEdgePos = alignedBoundary.get(0); |
| | | List<PathSegment> scanPath = generateGlobalScanPath(boundary, mowWidth, bestAngle, lastEdgePos); |
| | | |
| | | finalPath.addAll(scanPath); |
| | |
| | | return finalPath; |
| | | } |
| | | |
| | | /** |
| | | * 计算并获取扫描路径的第一行起点 |
| | | */ |
| | | 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; |
| | | List<Double> xIntersections = getXIntersections(rotatedPoly, firstY); |
| | | |
| | | if (xIntersections.isEmpty()) return polygon.get(0); |
| | | return rotatePoint(new Point(Collections.min(xIntersections), firstY), angle); |
| | | } |
| | | |
| | | /** |
| | | * 重新排列多边形顶点,使起始点与扫描起点对接 |
| | | */ |
| | | 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<PathSegment> generateGlobalScanPath(List<Point> polygon, double width, double angle, Point currentPos) { |
| | | List<PathSegment> segments = new ArrayList<>(); |
| | | List<Point> rotatedPoly = new ArrayList<>(); |
| | |
| | | } |
| | | |
| | | boolean leftToRight = true; |
| | | // 从 minY + width 开始,避开围边已割区域 |
| | | for (double y = minY + width; y <= maxY - width/2; y += width) { |
| | | // 步长 y 从最小到最大扫描 |
| | | 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> lineRows = new ArrayList<>(); |
| | | // 处理凹多边形:每两个点组成一个有效作业段 |
| | | 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); |
| | | lineRows.add(new PathSegment(pS, pE, true)); |
| | | lineSegmentsInRow.add(new PathSegment(pS, pE, true)); |
| | | } |
| | | |
| | | // 根据当前S型方向排序作业段 |
| | | if (!leftToRight) { |
| | | Collections.reverse(lineRows); |
| | | for (PathSegment s : lineRows) { |
| | | Point t = s.start; s.start = s.end; s.end = t; |
| | | Collections.reverse(lineSegmentsInRow); |
| | | for (PathSegment s : lineSegmentsInRow) { |
| | | Point temp = s.start; s.start = s.end; s.end = temp; |
| | | } |
| | | } |
| | | |
| | | for (PathSegment s : lineRows) { |
| | | // 如果间距极小,视为无缝衔接 |
| | | if (Math.hypot(currentPos.x - s.start.x, currentPos.y - s.start.y) > 0.05) { |
| | | // 将作业段连接到总路径 |
| | | for (PathSegment s : lineSegmentsInRow) { |
| | | if (Math.hypot(currentPos.x - s.start.x, currentPos.y - s.start.y) > 0.01) { |
| | | // 如果间距大于1cm,添加空走路径 |
| | | segments.add(new PathSegment(currentPos, s.start, false)); |
| | | } |
| | | segments.add(s); |
| | |
| | | return segments; |
| | | } |
| | | |
| | | 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 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 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; |
| | |
| | | |
| | | public static class PathSegment { |
| | | public Point start, end; |
| | | public boolean isMowing; |
| | | public boolean isMowing; // true: 割草中, false: 空载移动 |
| | | public PathSegment(Point s, Point e, boolean m) { this.start = s; this.end = e; this.isMowing = m; } |
| | | } |
| | | } |
| | |
| | | |
| | | private void drawCurrentPlannedPath(Graphics2D g2d) { |
| | | double arrowScale = previewSizingEnabled ? 0.5d : 1.0d; |
| | | lujingdraw.drawPlannedPath(g2d, currentPlannedPath, scale, arrowScale); |
| | | |
| | | // 尝试获取地块信息以支持区分作业路径和移动路径,以及绘制内缩边界 |
| | | String boundaryCoords = null; |
| | | String mowingWidth = null; |
| | | String safetyDistance = null; |
| | | String obstaclesCoords = null; |
| | | String mowingPattern = null; |
| | | |
| | | // 从当前地块编号获取地块信息 |
| | | if (currentBoundaryLandNumber != null) { |
| | | Dikuai landData = Dikuai.getDikuai(currentBoundaryLandNumber); |
| | | if (landData != null) { |
| | | boundaryCoords = landData.getBoundaryCoordinates(); |
| | | mowingWidth = landData.getMowingWidth(); |
| | | safetyDistance = landData.getMowingSafetyDistance(); |
| | | mowingPattern = landData.getMowingPattern(); |
| | | |
| | | // 获取障碍物坐标 |
| | | try { |
| | | java.io.File configFile = new java.io.File("Obstacledge.properties"); |
| | | if (configFile.exists()) { |
| | | Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); |
| | | if (manager.loadFromFile(configFile.getAbsolutePath())) { |
| | | Obstacledge.Plot plot = manager.getPlotById(currentBoundaryLandNumber.trim()); |
| | | if (plot != null && plot.getObstacles() != null && !plot.getObstacles().isEmpty()) { |
| | | obstaclesCoords = Obstacledge.buildPlannerPayload(plot.getObstacles()); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | // 忽略障碍物加载错误 |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 如果无法从地块获取边界,尝试使用当前显示的边界 |
| | | if (boundaryCoords == null || boundaryCoords.trim().isEmpty() || "-1".equals(boundaryCoords.trim())) { |
| | | if (currentBoundary != null && !currentBoundary.isEmpty()) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < currentBoundary.size(); i++) { |
| | | Point2D.Double pt = currentBoundary.get(i); |
| | | if (i > 0) sb.append(";"); |
| | | sb.append(String.format(java.util.Locale.US, "%.3f,%.3f", pt.x, pt.y)); |
| | | } |
| | | boundaryCoords = sb.toString(); |
| | | } |
| | | } |
| | | |
| | | // 转换割草宽度从厘米到米(如果存在) |
| | | if (mowingWidth != null && !mowingWidth.trim().isEmpty() && !"-1".equals(mowingWidth.trim())) { |
| | | try { |
| | | double widthCm = Double.parseDouble(mowingWidth.trim()); |
| | | double widthMeters = widthCm / 100.0; |
| | | mowingWidth = String.format(java.util.Locale.US, "%.3f", widthMeters); |
| | | } catch (NumberFormatException e) { |
| | | // 如果已经是米为单位,保持原值 |
| | | } |
| | | } |
| | | |
| | | // 转换安全距离从厘米到米(如果存在) |
| | | if (safetyDistance != null && !safetyDistance.trim().isEmpty() && !"-1".equals(safetyDistance.trim())) { |
| | | try { |
| | | double distCm = Double.parseDouble(safetyDistance.trim()); |
| | | // 如果值大于100,认为是厘米,需要转换为米 |
| | | if (distCm > 100) { |
| | | double distMeters = distCm / 100.0; |
| | | safetyDistance = String.format(java.util.Locale.US, "%.3f", distMeters); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 如果已经是米为单位,保持原值 |
| | | } |
| | | } |
| | | |
| | | // 调用带地块信息的绘制方法 |
| | | lujingdraw.drawPlannedPath(g2d, currentPlannedPath, scale, arrowScale, |
| | | boundaryCoords, mowingWidth, safetyDistance, obstaclesCoords, mowingPattern); |
| | | } |
| | | |
| | | private void drawCircleSampleMarkers(Graphics2D g2d, List<double[]> markers, double scale) { |
| | |
| | | private Runnable pathPreviewReturnAction; |
| | | private JButton settingsReturnButton; // 返回系统设置页面的悬浮按钮 |
| | | private JButton saveManualBoundaryButton; // 保存手动绘制边界的按钮 |
| | | private JButton toggleBoundaryDisplayButton; // 切换边界显示按钮(只显示原始边界/显示全部) |
| | | private String previewRestoreLandNumber; |
| | | private String previewRestoreLandName; |
| | | private Dikuai currentBoundaryPreviewDikuai; // 当前边界预览的地块引用 |
| | | private boolean drawingPaused; |
| | | private ImageIcon pauseIcon; |
| | | private ImageIcon pauseActiveIcon; |
| | |
| | | if (setsys.isManualBoundaryDrawingMode()) { |
| | | setsys.setManualBoundaryDrawingMode(false); |
| | | setsys.updateProperty("manualBoundaryDrawingMode", "false"); |
| | | // 关闭MapRenderer中的手动绘制边界模式(这会清除鼠标位置和边界点) |
| | | // 清空手动绘制的边界点 |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setManualBoundaryDrawingMode(false); |
| | | mapRenderer.clearManualBoundaryPoints(); |
| | | } |
| | | modeChanged = true; |
| | | } |
| | |
| | | floatingButtonColumn.add(pathPreviewReturnButton); |
| | | added = true; |
| | | } |
| | | if (toggleBoundaryDisplayButton != null && toggleBoundaryDisplayButton.isVisible()) { |
| | | if (added) { |
| | | floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | } |
| | | floatingButtonColumn.add(toggleBoundaryDisplayButton); |
| | | added = true; |
| | | } |
| | | if (saveManualBoundaryButton != null && saveManualBoundaryButton.isVisible()) { |
| | | if (added) { |
| | | floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10))); |
| | |
| | | return; |
| | | } |
| | | |
| | | // 保存当前地块引用 |
| | | shouye.currentBoundaryPreviewDikuai = dikuai; |
| | | |
| | | // 获取原始边界XY坐标 |
| | | String originalBoundaryXY = dikuai.getBoundaryOriginalXY(); |
| | | |
| | | // 设置边界预览 |
| | | shouye.mapRenderer.setBoundaryPreview(originalBoundaryXY, optimizedBoundary); |
| | | |
| | | // 设置边界预览更新回调,用于保存删除坐标点后的边界 |
| | | shouye.mapRenderer.setBoundaryPreviewUpdateCallback(updatedBoundary -> { |
| | | if (shouye.currentBoundaryPreviewDikuai != null && updatedBoundary != null) { |
| | | // 保存更新后的边界坐标 |
| | | Dikuai.updateField(shouye.currentBoundaryPreviewDikuai.getLandNumber(), "boundaryCoordinates", updatedBoundary); |
| | | java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| | | Dikuai.updateField(shouye.currentBoundaryPreviewDikuai.getLandNumber(), "updateTime", sdf.format(new java.util.Date())); |
| | | Dikuai.saveToProperties(); |
| | | |
| | | // 同步更新当前地块对象的内存值(确保返回时能获取到最新值) |
| | | shouye.currentBoundaryPreviewDikuai.setBoundaryCoordinates(updatedBoundary); |
| | | |
| | | // 更新预览边界(重新设置以刷新显示) |
| | | shouye.mapRenderer.setBoundaryPreview(originalBoundaryXY, updatedBoundary); |
| | | } |
| | | }); |
| | | |
| | | // 停止绘制割草机实时拖尾 |
| | | if (shouye.mapRenderer != null) { |
| | | shouye.mapRenderer.setIdleTrailSuppressed(true); |
| | |
| | | shouye.pathPreviewReturnButton.setToolTipText("返回边界编辑页面"); |
| | | } |
| | | |
| | | // 创建或显示切换边界显示按钮 |
| | | if (shouye.toggleBoundaryDisplayButton == null) { |
| | | shouye.toggleBoundaryDisplayButton = shouye.createToggleBoundaryDisplayButton(); |
| | | } |
| | | |
| | | // 重置切换按钮状态(默认显示全部边界) |
| | | if (shouye.mapRenderer != null) { |
| | | shouye.mapRenderer.setShowOnlyOriginalBoundary(false); |
| | | } |
| | | shouye.updateToggleBoundaryDisplayButtonText(); |
| | | |
| | | // 隐藏其他悬浮按钮 |
| | | shouye.hideFloatingDrawingControls(); |
| | | |
| | | // 显示返回按钮和切换按钮 |
| | | // 显示返回按钮 |
| | | shouye.pathPreviewReturnButton.setVisible(true); |
| | | shouye.toggleBoundaryDisplayButton.setVisible(true); |
| | | if (shouye.floatingButtonPanel != null) { |
| | | shouye.floatingButtonPanel.setVisible(true); |
| | | if (shouye.floatingButtonPanel.getParent() != shouye.visualizationPanel) { |
| | |
| | | private void exitBoundaryPreview() { |
| | | pathPreviewActive = false; |
| | | |
| | | // 清除当前地块引用 |
| | | currentBoundaryPreviewDikuai = null; |
| | | |
| | | // 恢复绘制割草机实时拖尾 |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setIdleTrailSuppressed(false); |
| | | } |
| | | |
| | | // 清除边界预览并重置显示状态 |
| | | // 清除边界预览 |
| | | if (mapRenderer != null) { |
| | | mapRenderer.clearBoundaryPreview(); |
| | | mapRenderer.setBoundaryPreviewUpdateCallback(null); |
| | | mapRenderer.setShowOnlyOriginalBoundary(false); // 重置为显示全部边界 |
| | | } |
| | | |
| | | // 隐藏返回按钮和切换按钮 |
| | | // 隐藏返回按钮 |
| | | if (pathPreviewReturnButton != null) { |
| | | pathPreviewReturnButton.setVisible(false); |
| | | } |
| | | if (toggleBoundaryDisplayButton != null) { |
| | | toggleBoundaryDisplayButton.setVisible(false); |
| | | } |
| | | |
| | | // 隐藏悬浮面板 |
| | | if (floatingButtonPanel != null) { |
| | |
| | | visualizationPanel.repaint(); |
| | | } |
| | | |
| | | /** |
| | | * 创建切换边界显示按钮 |
| | | */ |
| | | private JButton createToggleBoundaryDisplayButton() { |
| | | JButton button = new JButton("隐藏优化边界"); |
| | | button.setFont(new Font("微软雅黑", Font.PLAIN, 13)); |
| | | button.setBackground(new Color(46, 139, 87)); |
| | | button.setForeground(Color.WHITE); |
| | | button.setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16)); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | button.setToolTipText("切换显示:只显示原始边界/显示全部边界"); |
| | | |
| | | button.addActionListener(e -> { |
| | | if (mapRenderer == null) { |
| | | return; |
| | | } |
| | | // 切换显示状态 |
| | | boolean currentState = mapRenderer.isShowOnlyOriginalBoundary(); |
| | | mapRenderer.setShowOnlyOriginalBoundary(!currentState); |
| | | updateToggleBoundaryDisplayButtonText(); |
| | | }); |
| | | |
| | | button.addMouseListener(new java.awt.event.MouseAdapter() { |
| | | public void mouseEntered(java.awt.event.MouseEvent e) { |
| | | button.setBackground(new Color(30, 107, 69)); |
| | | } |
| | | |
| | | public void mouseExited(java.awt.event.MouseEvent e) { |
| | | button.setBackground(new Color(46, 139, 87)); |
| | | } |
| | | }); |
| | | |
| | | return button; |
| | | } |
| | | |
| | | /** |
| | | * 更新切换边界显示按钮的文本 |
| | | */ |
| | | private void updateToggleBoundaryDisplayButtonText() { |
| | | if (toggleBoundaryDisplayButton == null || mapRenderer == null) { |
| | | return; |
| | | } |
| | | if (mapRenderer.isShowOnlyOriginalBoundary()) { |
| | | toggleBoundaryDisplayButton.setText("显示全部边界"); |
| | | } else { |
| | | toggleBoundaryDisplayButton.setText("隐藏优化边界"); |
| | | } |
| | | } |
| | | |
| | | // 测试方法 |
| | | public static void main(String[] args) { |
| | | JFrame frame = new JFrame("AutoMow - 首页"); |