| | |
| | | # å²èæºå°åéç¢ç©é
ç½®æä»¶ |
| | | # çææ¶é´ï¼2025-12-18T19:44:44.126173900 |
| | | # çææ¶é´ï¼2025-12-19T19:40:57.347052 |
| | | # åæ ç³»ï¼WGS84ï¼åº¦åæ ¼å¼ï¼ |
| | | |
| | | # ============ å°ååºåç«é
ç½® ============ |
| | |
| | | #Updated device properties |
| | | #Fri Dec 19 12:08:39 CST 2025 |
| | | #Fri Dec 19 19:43:54 CST 2025 |
| | | BupdateTime=-1 |
| | | GupdateTime=-1 |
| | | baseStationCardNumber=-1 |
| | |
| | | differentialAge=-1 |
| | | heading=-1 |
| | | mowerBladeHeight=-1 |
| | | mowerLength=3.00 |
| | | mowerLength=0.40 |
| | | mowerLightStatus=-1 |
| | | mowerModel=-1 |
| | | mowerName=-1 |
| | | mowerNumber=-1 |
| | | mowerStartStatus=-1 |
| | | mowerWidth=2.00 |
| | | mowerWidth=0.20 |
| | | mowingHeight=-1 |
| | | mowingSafetyDistance=1.86 |
| | | mowingSafetyDistance=0.28 |
| | | mowingWidth=-1 |
| | | pitch=-1 |
| | | positioningStatus=-1 |
| | |
| | | #Dikuai Properties |
| | | #Thu Dec 18 19:44:44 CST 2025 |
| | | #Fri Dec 19 19:44:17 CST 2025 |
| | | LAND1.angleThreshold=-1 |
| | | LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E |
| | | LAND1.boundaryCoordinates=2.87,-0.19;6.73,-1.23;22.76,1.06;27.88,3.65;35.84,9.96;38.40,10.35;41.91,8.49;44.09,4.86;43.49,2.92;33.76,0.59;30.22,-1.26;30.09,-3.80;34.19,-28.28;32.68,-29.99;30.12,-30.19;28.89,-28.59;25.58,-6.77;24.09,-4.26;21.10,-3.94;-10.36,-8.92;-11.69,-8.57;-12.75,-4.68;-12.23,-3.28;-6.21,-2.34;0.00,0.00;1.44,-0.09;2.87,-0.19 |
| | | LAND1.boundaryOriginalCoordinates=39.831524,116.279912,49.30;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;39.831256,116.280261,49.30;39.831253,116.280245,49.20;39.831254,116.280231,49.22;39.831258,116.280220,49.10;39.831268,116.280216,49.17;39.831281,116.280214,49.14;39.831295,116.280212,49.14;39.831308,116.280209,49.17;39.831321,116.280206,49.14;39.831334,116.280204,49.17;39.831348,116.280202,49.21;39.831362,116.280198,49.21;39.831375,116.280195,49.23;39.831390,116.280193,49.25;39.831405,116.280189,49.20;39.831420,116.280187,49.19;39.831436,116.280185,49.21;39.831450,116.280182,49.19;39.831464,116.280177,49.17;39.831478,116.280171,49.14;39.831487,116.280160,49.25;39.831491,116.280143,49.21;39.831490,116.280125,49.18;39.831487,116.280107,49.23;39.831485,116.280089,49.27;39.831483,116.280072,49.24;39.831482,116.280054,49.25;39.831480,116.280037,49.25;39.831477,116.280020,49.30;39.831475,116.280004,49.31;39.831473,116.279989,49.30;39.831472,116.279973,49.23;39.831470,116.279958,49.26;39.831468,116.279941,49.29;39.831465,116.279925,49.33;39.831463,116.279908,49.34;39.831462,116.279891,49.35;39.831461,116.279874,49.33;39.831458,116.279859,49.34;39.831456,116.279843,49.32;39.831454,116.279827,49.32;39.831452,116.279813,49.42;39.831450,116.279798,49.41;39.831448,116.279783,49.36;39.831447,116.279769,49.26;39.831445,116.279757,49.24;39.831445,116.279747,49.38;39.831448,116.279741,49.27;39.831455,116.279736,49.26;39.831463,116.279733,49.26;39.831473,116.279731,49.26;39.831483,116.279729,49.25;39.831491,116.279730,49.26;39.831496,116.279735,49.22;39.831497,116.279748,49.27;39.831498,116.279762,49.27;39.831500,116.279776,49.34;39.831502,116.279791,49.32;39.831504,116.279805,49.27;39.831507,116.279820,49.28;39.831510,116.279835,49.20;39.831513,116.279849,49.25;39.831517,116.279860,49.30;39.831520,116.279864,49.32;39.831520,116.279865,49.34;39.831522,116.279873,49.25;39.831524,116.279878,49.25;39.831525,116.279878,49.24 |
| | | LAND1.boundaryCoordinates=-10.36,-8.92;-12.10,-7.80;-12.66,-3.77;-4.96,-1.98;-6.04,-3.37;-7.12,-4.76;-8.20,-6.14;-9.28,-7.53;-10.36,-8.92 |
| | | LAND1.boundaryOriginalCoordinates=39.831445,116.279757,49.24;39.831445,116.279747,49.38;39.831448,116.279741,49.27;39.831455,116.279736,49.26;39.831463,116.279733,49.26;39.831473,116.279731,49.26;39.831483,116.279729,49.25;39.831491,116.279730,49.26;39.831496,116.279735,49.22;39.831497,116.279748,49.27;39.831498,116.279762,49.27;39.831500,116.279776,49.34;39.831502,116.279791,49.32;39.831504,116.279805,49.27;39.831507,116.279820,49.28 |
| | | LAND1.boundaryPointInterval=-1 |
| | | LAND1.createTime=2025-12-18 17\:12\:20 |
| | | LAND1.createTime=2025-12-19 18\:31\:53 |
| | | LAND1.intelligentSceneAnalysis=-1 |
| | | LAND1.landArea=483.34 |
| | | LAND1.landName=123 |
| | | LAND1.landArea=26.75 |
| | | LAND1.landName=21233 |
| | | LAND1.landNumber=LAND1 |
| | | LAND1.mowingBladeWidth=0.40 |
| | | LAND1.mowingOverlapDistance=0.06 |
| | | LAND1.mowingPattern=å¹³è¡çº¿ |
| | | LAND1.mowingTrack=-1 |
| | | LAND1.mowingWidth=34 |
| | | LAND1.plannedPath=35.722,9.394;38.565,9.844;39.065,9.579;35.179,8.964;34.636,8.534;39.565,9.314;40.065,9.049;34.094,8.104;33.551,7.673;40.565,8.784;41.066,8.519;33.009,7.243;32.466,6.813;41.566,8.254;41.804,7.947;31.923,6.383;31.381,5.953;41.993,7.633;42.182,7.319;30.838,5.523;30.296,5.093;42.371,7.004;42.560,6.690;29.753,4.663;29.210,4.232;42.748,6.375;0.018,-0.389;0.094,-0.377;1.645,-0.475;-1.557,-0.982;42.937,6.061;28.668,3.802;28.125,3.372;43.126,5.747;-3.133,-1.576;2.993,-0.606;3.797,-0.823;-4.708,-2.169;43.315,5.432;27.172,2.877;26.181,2.376;43.503,5.118;-11.978,-3.664;4.602,-1.040;5.407,-1.257;-12.114,-4.030;43.685,4.803;25.191,1.875;24.200,1.374;43.573,4.441;-12.250,-4.396;6.212,-1.474;15.278,-0.383;-12.346,-4.755;43.461,4.079;23.210,0.873;-12.256,-5.085;43.349,3.717;43.237,3.355;-12.166,-5.415;-12.076,-5.745;40.411,2.563;36.170,1.548;-11.986,-6.075;-11.896,-6.405;33.250,0.741;32.306,0.247;-11.806,-6.735;-11.717,-7.065;31.361,-0.246;30.416,-0.740;-11.627,-7.395;-11.537,-7.725;29.861,-1.172;29.836,-1.520;-11.447,-8.055;-11.095,-8.344;29.818,-1.867;29.801,-2.215;21.190,-3.578;22.488,-3.716;29.783,-2.562;29.765,-2.909;23.785,-3.855;24.424,-4.098;29.747,-3.256;29.730,-3.603;24.611,-4.413;24.798,-4.728;29.739,-3.945;29.795,-4.281;24.985,-5.042;25.171,-5.357;29.852,-4.616;29.908,-4.951;25.358,-5.672;25.545,-5.986;29.964,-5.287;30.020,-5.622;25.732,-6.301;25.915,-6.616;30.076,-5.957;30.132,-6.293;25.982,-6.950;26.033,-7.286;30.189,-6.628;30.245,-6.964;26.084,-7.622;26.135,-7.958;30.301,-7.299;30.357,-7.634;26.185,-8.295;26.236,-8.631;30.413,-7.970;30.469,-8.305;26.287,-8.967;26.338,-9.303;30.526,-8.640;30.582,-8.976;26.389,-9.639;26.440,-9.975;30.638,-9.311;30.694,-9.646;26.491,-10.312;26.542,-10.648;30.750,-9.982;30.806,-10.317;26.593,-10.984;26.644,-11.320;30.862,-10.652;30.919,-10.988;26.695,-11.656;26.746,-11.992;30.975,-11.323;31.031,-11.658;26.797,-12.328;26.848,-12.665;31.087,-11.994;31.143,-12.329;26.899,-13.001;26.950,-13.337;31.199,-12.664;31.256,-13.000;27.001,-13.673;27.052,-14.009;31.312,-13.335;31.368,-13.670;27.103,-14.345;27.154,-14.682;31.424,-14.006;31.480,-14.341;27.205,-15.018;27.256,-15.354;31.536,-14.676;31.593,-15.012;27.307,-15.690;27.358,-16.026;31.649,-15.347;31.705,-15.682;27.409,-16.362;27.460,-16.699;31.761,-16.018;31.817,-16.353;27.511,-17.035;27.562,-17.371;31.873,-16.688;31.930,-17.024;27.613,-17.707;27.664,-18.043;31.986,-17.359;32.042,-17.694;27.715,-18.379;27.766,-18.716;32.098,-18.030;32.154,-18.365;27.817,-19.052;27.868,-19.388;32.210,-18.701;32.267,-19.036;27.919,-19.724;27.970,-20.060;32.323,-19.371;32.379,-19.707;28.021,-20.396;28.072,-20.733;32.435,-20.042;32.491,-20.377;28.123,-21.069;28.174,-21.405;32.547,-20.713;32.604,-21.048;28.225,-21.741;28.276,-22.077;32.660,-21.383;32.716,-21.719;28.327,-22.413;28.378,-22.749;32.772,-22.054;32.828,-22.389;28.429,-23.086;28.480,-23.422;32.884,-22.725;32.941,-23.060;28.531,-23.758;28.582,-24.094;32.997,-23.395;33.053,-23.731;28.633,-24.430;28.684,-24.766;33.109,-24.066;33.165,-24.401;28.735,-25.103;28.786,-25.439;33.221,-24.737;33.278,-25.072;28.837,-25.775;28.888,-26.111;33.334,-25.407;33.390,-25.743;28.939,-26.447;28.990,-26.783;33.446,-26.078;33.502,-26.413;29.041,-27.120;29.092,-27.456;33.558,-26.749;33.615,-27.084;29.143,-27.792;29.194,-28.128;33.671,-27.419;33.727,-27.755;29.258,-28.462;29.494,-28.769;33.783,-28.090;33.524,-28.475;29.730,-29.076;29.966,-29.383;33.170,-28.876;32.817,-29.276;30.202,-29.690 |
| | | LAND1.plannedPath=-5.830841,-2.644443;-12.323966,-4.153883;-12.276978,-4.492026;-6.161845,-3.070456;-6.492848,-3.496470;-12.229991,-4.830169;-12.183003,-5.168312;-6.823852,-3.922484;-7.154855,-4.348497;-12.136016,-5.506455;-12.089028,-5.844598;-7.487144,-4.774810;-7.821079,-5.201505;-12.042040,-6.182741;-11.995053,-6.520885;-8.155014,-5.628200;-8.700347,-6.104039;-11.948065,-6.859028;-11.901077,-7.197171;-10.070186,-6.771548;-11.440025,-7.439057;-11.854090,-7.535314 |
| | | LAND1.returnPointCoordinates=-1 |
| | | LAND1.updateTime=2025-12-18 19\:44\:44 |
| | | LAND1.updateTime=2025-12-19 19\:44\:17 |
| | | LAND1.userId=-1 |
| | |
| | | #Mower Configuration Properties - Updated |
| | | #Fri Dec 19 17:40:36 CST 2025 |
| | | #Fri Dec 19 19:44:56 CST 2025 |
| | | appVersion=-1 |
| | | boundaryLengthVisible=false |
| | | currentWorkLandNumber=LAND1 |
| | |
| | | handheldMarkerId=1872 |
| | | idleTrailDurationSeconds=60 |
| | | manualBoundaryDrawingMode=false |
| | | mapScale=5.63 |
| | | mapScale=41.56 |
| | | measurementModeEnabled=false |
| | | mowerId=860 |
| | | serialAutoConnect=true |
| | | serialBaudRate=115200 |
| | | serialPortName=COM15 |
| | | simCardNumber=-1 |
| | | viewCenterX=-15.67 |
| | | viewCenterY=9.92 |
| | | viewCenterX=8.76 |
| | | viewCenterY=4.44 |
| | |
| | | #\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 19 16:55:22 CST 2025 |
| | | boundaryCoordinates=2.64,9.22;1.40,10.29;9.39,20.06;19.16,22.54;19.69,12.60;13.48,8.34;6.20,6.74 |
| | | #Fri Dec 19 19:18:05 CST 2025 |
| | | boundaryCoordinates=-7.66,-10.73;-9.27,-9.14;-5.87,-8.78 |
| | | email=789 |
| | | language=zh |
| | | lastLoginTime=-1 |
| | | password=123 |
| | | pointCount=7 |
| | | pointCount=3 |
| | | registrationTime=-1 |
| | | status=-1 |
| | | userId=-1 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package lujing; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æéç¢ç©å¸å½¢å°åè·¯å¾è§åç±» |
| | | */ |
| | | public class AoxinglujingHaveObstacel { |
| | | |
| | | /** |
| | | * çæè·¯å¾ |
| | | * @param boundaryCoordsStr å°åè¾¹çåæ å符串 "x1,y1;x2,y2;..." |
| | | * @param obstacleCoordsStr éç¢ç©åæ å符串 |
| | | * @param mowingWidthStr å²è宽度å符串ï¼å¦ "0.34" |
| | | * @param safetyMarginStr å®å
¨è¾¹è·å符串ï¼å¦ "0.2" |
| | | * @return è·¯å¾åæ åç¬¦ä¸²ï¼æ ¼å¼ "x1,y1;x2,y2;..." |
| | | */ |
| | | public static String planPath(String boundaryCoordsStr, String obstacleCoordsStr, String mowingWidthStr, String safetyMarginStr) { |
| | | // TODO: å®ç°å¸å½¢å°åæéç¢ç©è·¯å¾è§åç®æ³ |
| | | // ç®å使ç¨é»è®¤æ¹æ³ä½ä¸ºä¸´æ¶å®ç° |
| | | throw new UnsupportedOperationException("AoxinglujingHaveObstacel.planPath å°æªå®ç°"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package lujing; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ éç¢ç©å¸å½¢èå°è·¯å¾è§åç±» (ä¼åç) |
| | | */ |
| | | public class AoxinglujingNoObstacle { |
| | | |
| | | // ä¼åï¼å¼å
¥æå°å¼ç¨äºæµ®ç¹æ°æ¯è¾ï¼å¤çå ä½ç²¾åº¦è¯¯å·® |
| | | private static final double EPSILON = 1e-6; |
| | | |
| | | /** |
| | | * è·¯å¾æ®µç±» |
| | | */ |
| | | public static class PathSegment { |
| | | public Point start; |
| | | public Point end; |
| | | public boolean isMowing; // true为å²è工使®µï¼falseä¸ºè¿æ¸¡æ®µ |
| | | |
| | | public PathSegment(Point start, Point end, boolean isMowing) { |
| | | this.start = start; |
| | | this.end = end; |
| | | this.isMowing = isMowing; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return String.format("[%s -> %s, isMowing=%b]", start, end, isMowing); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åæ ç¹ç±» |
| | | */ |
| | | public static class Point { |
| | | double x, y; |
| | | |
| | | public Point(double x, double y) { |
| | | this.x = x; |
| | | this.y = y; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return String.format("(%.4f, %.4f)", x, y); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对å¤å
¬å¼çéæè°ç¨æ¹æ³ (ä¿çå符串å
¥åæ ¼å¼) |
| | | * |
| | | * @param boundaryCoordsStr å°åè¾¹çåæ å符串 "x1,y1;x2,y2;..." |
| | | * @param mowingWidthStr å²è宽度å符串ï¼å¦ "0.34" |
| | | * @param safetyMarginStr å®å
¨è¾¹è·å符串ï¼å¦ "0.2" |
| | | * @return è·¯å¾æ®µå表 |
| | | */ |
| | | public static List<PathSegment> planPath(String boundaryCoordsStr, String mowingWidthStr, String safetyMarginStr) { |
| | | // 1. è§£æåæ° (ä¼åï¼åç¬æåè§£æé»è¾) |
| | | List<Point> originalPolygon = parseCoords(boundaryCoordsStr); |
| | | double mowingWidth; |
| | | double safetyMargin; |
| | | |
| | | try { |
| | | mowingWidth = Double.parseDouble(mowingWidthStr); |
| | | safetyMargin = Double.parseDouble(safetyMarginStr); |
| | | } catch (NumberFormatException e) { |
| | | throw new IllegalArgumentException("å²è宽度æå®å
¨è¾¹è·æ ¼å¼é误"); |
| | | } |
| | | |
| | | // 2. è°ç¨æ ¸å¿ç®æ³ |
| | | return planPathCore(originalPolygon, mowingWidth, safetyMargin); |
| | | } |
| | | |
| | | /** |
| | | * æ ¸å¿ç®æ³é»è¾ (强类åå
¥åï¼ä¾¿äºæµè¯åå
é¨è°ç¨) |
| | | */ |
| | | private static List<PathSegment> planPathCore(List<Point> originalPolygon, double mowingWidth, double safetyMargin) { |
| | | // ä¼åï¼ä¸è§å½¢ä¹æ¯åæ³çå¸å¤è¾¹å½¢ï¼éå¶æ¹ä¸ºå°äº3 |
| | | if (originalPolygon == null || originalPolygon.size() < 3) { |
| | | throw new IllegalArgumentException("å¤è¾¹å½¢åæ ç¹ä¸è¶³3ä¸ªï¼æ æ³ææææåºå"); |
| | | } |
| | | |
| | | // ç¡®ä¿å¤è¾¹å½¢æ¯éæ¶éæ¹å |
| | | ensureCCW(originalPolygon); |
| | | |
| | | // 1. æ ¹æ®å®å
¨è¾¹è·å
缩 |
| | | List<Point> shrunkPolygon = shrinkPolygon(originalPolygon, safetyMargin); |
| | | |
| | | // ä¼åï¼å
缩å妿å¤è¾¹å½¢å¤±æï¼ä¾å¦å°å太çªï¼å
ç¼©åæ¶å¤±ï¼ï¼ç´æ¥è¿å空路å¾ï¼é¿å
åç»æ¥é |
| | | if (shrunkPolygon.size() < 3) { |
| | | // è¿éå¯ä»¥è®°å½æ¥å¿ï¼å°åè¿å°ï¼æ æ³æ»¡è¶³å®å
¨è·ç¦»ä½ä¸ |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | // 2. è®¡ç®æé¿è¾¹è§åº¦ (ä½ä¸ºæ«ææ¹å) |
| | | double angle = calculateLongestEdgeAngle(originalPolygon); |
| | | |
| | | // 3. & 4. æè½¬æ«æå¹¶åªè£ |
| | | List<PathSegment> mowingLines = generateClippedMowingLines(shrunkPolygon, angle, mowingWidth); |
| | | |
| | | // 5. å¼åå½¢è¿æ¥ |
| | | return connectPathSegments(mowingLines); |
| | | } |
| | | |
| | | // ================= æ ¸å¿ç®æ³è¾
婿¹æ³ ================= |
| | | |
| | | private static List<Point> shrinkPolygon(List<Point> polygon, double margin) { |
| | | List<Point> newPoints = 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); |
| | | Point p0 = polygon.get((i - 1 + n) % n); |
| | | |
| | | Line line1 = offsetLine(p1, p2, margin); |
| | | Line line0 = offsetLine(p0, p1, margin); |
| | | |
| | | Point intersection = getIntersection(line0, line1); |
| | | |
| | | // ä¼åï¼å¢å éç©ºå¤æï¼ä¸å¦æäº¤ç¹å¼å¸¸è¿ï¼å°è§æåºï¼ï¼å®é
å·¥ç¨ä¸å¯è½éè¦åè§å¤ç |
| | | // è¿éæä¿çåºç¡é»è¾ï¼ä½å¨å¸å¤è¾¹å½¢ä¸é常没é®é¢ |
| | | if (intersection != null) { |
| | | newPoints.add(intersection); |
| | | } |
| | | } |
| | | return newPoints; |
| | | } |
| | | |
| | | private static double calculateLongestEdgeAngle(List<Point> polygon) { |
| | | double maxDistSq = -1; |
| | | double angle = 0; |
| | | int n = polygon.size(); |
| | | |
| | | for (int i = 0; i < n; i++) { |
| | | Point p1 = polygon.get(i); |
| | | Point p2 = polygon.get((i + 1) % n); |
| | | double dx = p2.x - p1.x; |
| | | double dy = p2.y - p1.y; |
| | | double distSq = dx * dx + dy * dy; |
| | | |
| | | if (distSq > maxDistSq) { |
| | | maxDistSq = distSq; |
| | | angle = Math.atan2(dy, dx); |
| | | } |
| | | } |
| | | return angle; |
| | | } |
| | | |
| | | private static List<PathSegment> generateClippedMowingLines(List<Point> polygon, double angle, double width) { |
| | | List<PathSegment> segments = new ArrayList<>(); |
| | | |
| | | // æè½¬è³æ°´å¹³ |
| | | List<Point> rotatedPoly = rotatePolygon(polygon, -angle); |
| | | |
| | | double minY = Double.MAX_VALUE; |
| | | double maxY = -Double.MAX_VALUE; |
| | | for (Point p : rotatedPoly) { |
| | | if (p.y < minY) minY = p.y; |
| | | if (p.y > maxY) maxY = p.y; |
| | | } |
| | | |
| | | // ä¼åï¼èµ·å§æ«æçº¿å¢å ä¸ä¸ªå¾®å°çåç§»é EPSILON |
| | | // é¿å
æ«æçº¿æ£å¥½è½å¨é¡¶ç¹ä¸ï¼å¯¼è´äº¤ç¹å¤æé»è¾åºç°äºä¹æ§ |
| | | double currentY = minY + width / 2.0 + EPSILON; |
| | | |
| | | while (currentY < maxY) { |
| | | List<Double> xIntersections = new ArrayList<>(); |
| | | int n = rotatedPoly.size(); |
| | | |
| | | for (int i = 0; i < n; i++) { |
| | | Point p1 = rotatedPoly.get(i); |
| | | Point p2 = rotatedPoly.get((i + 1) % n); |
| | | |
| | | // ä¼åï¼å¿½ç¥æ°´å¹³çº¿æ®µ (p1.y == p2.y)ï¼é¿å
é¤é¶éè¯¯ï¼æ°´å¹³çº¿æ®µä¸åä¸åç´æ«æçº¿æ±äº¤ |
| | | if (Math.abs(p1.y - p2.y) < EPSILON) continue; |
| | | |
| | | // å¤æçº¿æ®µæ¯å¦è·¨è¶ currentY |
| | | // 使ç¨ä¸¥æ ¼çä¸çå¼é»è¾é
åèå´å¤æ |
| | | double minP = Math.min(p1.y, p2.y); |
| | | double maxP = Math.max(p1.y, p2.y); |
| | | |
| | | if (currentY >= minP && currentY < maxP) { |
| | | // çº¿æ§æå¼æ±X |
| | | double x = p1.x + (currentY - p1.y) * (p2.x - p1.x) / (p2.y - p1.y); |
| | | xIntersections.add(x); |
| | | } |
| | | } |
| | | |
| | | Collections.sort(xIntersections); |
| | | |
| | | // æå线段ï¼éå¸¸æ¯æå¯¹åºç° |
| | | if (xIntersections.size() >= 2) { |
| | | // åæå·¦åæå³ç¹è¿æ¥ï¼åºå¯¹å¯è½åºç°çå¾®å°è®¡ç®è¯¯å·®å¯¼è´çå¤ä¸ªäº¤ç¹ï¼ |
| | | double xStart = xIntersections.get(0); |
| | | double xEnd = xIntersections.get(xIntersections.size() - 1); |
| | | |
| | | // åªæå½çº¿æ®µé¿åº¦å¤§äºæå°å¼æ¶ææ·»å ï¼é¿å
çæåªç¹è·¯å¾ |
| | | if (xEnd - xStart > EPSILON) { |
| | | Point rStart = rotatePoint(new Point(xStart, currentY), angle); // è¿éçcurrentYå®é
ä¸å¸¦äºepsilonï¼è¿åæ¶æ²¡é®é¢ |
| | | Point rEnd = rotatePoint(new Point(xEnd, currentY), angle); |
| | | segments.add(new PathSegment(rStart, rEnd, true)); |
| | | } |
| | | } |
| | | |
| | | currentY += width; |
| | | } |
| | | |
| | | return segments; |
| | | } |
| | | |
| | | private static List<PathSegment> connectPathSegments(List<PathSegment> lines) { |
| | | List<PathSegment> result = new ArrayList<>(); |
| | | if (lines.isEmpty()) return result; |
| | | |
| | | for (int i = 0; i < lines.size(); i++) { |
| | | PathSegment currentLine = lines.get(i); |
| | | Point actualStart, actualEnd; |
| | | |
| | | // å¼åå½¢è§åï¼å¶æ°è¡æ£åï¼å¥æ°è¡åå |
| | | if (i % 2 == 0) { |
| | | actualStart = currentLine.start; |
| | | actualEnd = currentLine.end; |
| | | } else { |
| | | actualStart = currentLine.end; |
| | | actualEnd = currentLine.start; |
| | | } |
| | | |
| | | // æ·»å è¿æ¸¡æ®µ |
| | | if (i > 0) { |
| | | Point prevEnd = result.get(result.size() - 1).end; |
| | | // åªæå½è·ç¦»ç¡®å®å卿¶ææ·»å è¿æ¸¡æ®µï¼é¿å
éåç¹ï¼ |
| | | if (distanceSq(prevEnd, actualStart) > EPSILON) { |
| | | result.add(new PathSegment(prevEnd, actualStart, false)); |
| | | } |
| | | } |
| | | |
| | | result.add(new PathSegment(actualStart, actualEnd, true)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | // ================= åºç¡å ä½å·¥å
· (ä¼åç) ================= |
| | | |
| | | private static List<Point> parseCoords(String s) { |
| | | List<Point> list = new ArrayList<>(); |
| | | if (s == null || s.trim().isEmpty()) return list; |
| | | |
| | | String[] parts = s.split(";"); |
| | | for (String part : parts) { |
| | | String[] xy = part.split(","); |
| | | if (xy.length >= 2) { |
| | | try { |
| | | double x = Double.parseDouble(xy[0].trim()); |
| | | double y = Double.parseDouble(xy[1].trim()); |
| | | list.add(new Point(x, y)); |
| | | } catch (NumberFormatException e) { |
| | | // å¿½ç¥æ ¼å¼é误çåä¸ªç¹ |
| | | } |
| | | } |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | // Shoelaceå
¬å¼å¤ææ¹åå¹¶è°æ´ |
| | | private static void ensureCCW(List<Point> polygon) { |
| | | double sum = 0; |
| | | for (int i = 0; i < polygon.size(); i++) { |
| | | Point p1 = polygon.get(i); |
| | | Point p2 = polygon.get((i + 1) % polygon.size()); |
| | | sum += (p2.x - p1.x) * (p2.y + p1.y); |
| | | } |
| | | // å设æ åç¬å¡å°åæ ç³»ï¼sum > 0 为顺æ¶éï¼éè¦åè½¬ä¸ºéæ¶é |
| | | if (sum > 0) { |
| | | Collections.reverse(polygon); |
| | | } |
| | | } |
| | | |
| | | private static class Line { |
| | | double a, b, c; |
| | | public Line(double a, double b, double c) { this.a = a; this.b = b; this.c = c; } |
| | | } |
| | | |
| | | private static Line offsetLine(Point p1, Point p2, double dist) { |
| | | double dx = p2.x - p1.x; |
| | | double dy = p2.y - p1.y; |
| | | double len = Math.sqrt(dx * dx + dy * dy); |
| | | |
| | | // 鲿¢é¤ä»¥0 |
| | | if (len < EPSILON) return new Line(0, 0, 0); |
| | | |
| | | double nx = -dy / len; |
| | | double ny = dx / len; |
| | | |
| | | double newX = p1.x + nx * dist; |
| | | double newY = p1.y + ny * dist; |
| | | |
| | | double a = -dy; |
| | | double b = dx; |
| | | double c = -a * newX - b * newY; |
| | | return new Line(a, b, c); |
| | | } |
| | | |
| | | private static Point getIntersection(Line l1, Line l2) { |
| | | double det = l1.a * l2.b - l2.a * l1.b; |
| | | if (Math.abs(det) < EPSILON) return null; // å¹³è¡ |
| | | double x = (l1.b * l2.c - l2.b * l1.c) / det; |
| | | double y = (l2.a * l1.c - l1.a * l2.c) / det; |
| | | return new Point(x, y); |
| | | } |
| | | |
| | | 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> poly, double angle) { |
| | | List<Point> res = new ArrayList<>(); |
| | | for (Point p : poly) { |
| | | res.add(rotatePoint(p, angle)); |
| | | } |
| | | return res; |
| | | } |
| | | |
| | | private static double distanceSq(Point p1, Point p2) { |
| | | return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y); |
| | | } |
| | | } |
| | |
| | | throw new IllegalStateException("Utility class"); |
| | | } |
| | | |
| | | // 5åæ°æ ¸å¿çææ¹æ³ |
| | | /** |
| | | * çæå²èè·¯å¾æ®µå表ï¼5åæ°çæ¬ï¼ |
| | | * æ ¹æ®ç»å®çå¤è¾¹å½¢è¾¹çãéç¢ç©ãå²è宽度ãå®å
¨è·ç¦»å模å¼ï¼çæå®æ´çå²èè·¯å¾è§å |
| | | * |
| | | * @param polygonCoords å¤è¾¹å½¢è¾¹çåæ åç¬¦ä¸²ï¼æ ¼å¼ï¼"x1,y1;x2,y2;x3,y3;..." æ "(x1,y1);(x2,y2);..." |
| | | * æ¯æåå·ãç©ºæ ¼ãéå·çå¤ç§åé符ï¼ä¼èªå¨éåå¤è¾¹å½¢ |
| | | * @param obstaclesCoords éç¢ç©åæ åç¬¦ä¸²ï¼æ ¼å¼ï¼"(x1,y1;x2,y2;...)" å¤ä¸ªéç¢ç©ç¨æ¬å·åé |
| | | * å¦æä¸ºç©ºænullï¼åè§ä¸ºæ éç¢ç© |
| | | * @param mowingWidth å²è宽度ï¼ç±³ï¼ï¼åç¬¦ä¸²æ ¼å¼ï¼å¦ "0.34" 表示34åç±³ |
| | | * å¦æä¸ºç©ºææ æ³è§£æï¼é»è®¤ä½¿ç¨0.34ç±³ |
| | | * @param safetyDistStr å®å
¨è¾¹è·ï¼ç±³ï¼ï¼åç¬¦ä¸²æ ¼å¼ï¼ç¨äºå
缩边çåéç¢ç©å¤æ© |
| | | * å¦æä¸ºç©ºænullï¼å°èªå¨è®¡ç®ä¸º width/2 + 0.2ç±³ï¼ç¡®ä¿å²èæºå®ä½å®å
¨å¨çå
|
| | | * @param modeStr è·¯å¾æ¨¡å¼å符串ï¼"1" æ "spiral" è¡¨ç¤ºèºææ¨¡å¼ï¼å
¶ä»å¼è¡¨ç¤ºå¹³è¡æ¨¡å¼ï¼é»è®¤ï¼ |
| | | * @return è·¯å¾æ®µåè¡¨ï¼æ¯ä¸ªPathSegmentå
å«èµ·ç¹ãç»ç¹åæ¯å¦ä¸ºå²èæ®µçæ å¿ |
| | | * 妿å¤è¾¹å½¢åæ ä¸è¶³4个ç¹ï¼å°æåºIllegalArgumentExceptionå¼å¸¸ |
| | | */ |
| | | public static List<PathSegment> generatePathSegments(String polygonCoords, |
| | | String obstaclesCoords, |
| | | String mowingWidth, |
| | |
| | | return planner.generate(); |
| | | } |
| | | |
| | | // 4åæ°éè½½ï¼éé
AddDikuai.java |
| | | /** |
| | | * çæå²èè·¯å¾æ®µå表ï¼4åæ°çæ¬ï¼ |
| | | * è¿æ¯5åæ°çæ¬çéè½½æ¹æ³ï¼å®å
¨è·ç¦»åæ°èªå¨è®¾ä¸ºnullï¼ç³»ç»å°ä½¿ç¨é»è®¤è®¡ç®å¼ |
| | | * 主è¦ç¨äºéé
AddDikuai.java çä¸éè¦æå®å®å
¨è·ç¦»çåºæ¯ |
| | | * |
| | | * @param polygonCoords å¤è¾¹å½¢è¾¹çåæ åç¬¦ä¸²ï¼æ ¼å¼ï¼"x1,y1;x2,y2;x3,y3;..." |
| | | * @param obstaclesCoords éç¢ç©åæ åç¬¦ä¸²ï¼æ ¼å¼ï¼"(x1,y1;x2,y2;...)"ï¼å¯ä¸ºç©º |
| | | * @param mowingWidth å²è宽度ï¼ç±³ï¼ï¼åç¬¦ä¸²æ ¼å¼ï¼å¦ "0.34" |
| | | * @param modeStr è·¯å¾æ¨¡å¼å符串ï¼"1" æ "spiral" è¡¨ç¤ºèºææ¨¡å¼ï¼å
¶ä»å¼è¡¨ç¤ºå¹³è¡æ¨¡å¼ |
| | | * @return è·¯å¾æ®µåè¡¨ï¼æ¯ä¸ªPathSegmentå
å«èµ·ç¹ãç»ç¹åæ¯å¦ä¸ºå²èæ®µçæ å¿ |
| | | * å®å
¨è·ç¦»å°èªå¨è®¡ç®ä¸º width/2 + 0.2ç±³ |
| | | */ |
| | | public static List<PathSegment> generatePathSegments(String polygonCoords, |
| | | String obstaclesCoords, |
| | | String mowingWidth, |
| | |
| | | import dikuai.Dikuai; |
| | | import lujing.Lunjingguihua; |
| | | import lujing.ObstaclePathPlanner; |
| | | import lujing.Qufenxingzhuang; |
| | | import lujing.AoxinglujingNoObstacle; |
| | | import lujing.YixinglujingNoObstacle; |
| | | import lujing.AoxinglujingHaveObstacel; |
| | | import lujing.YixinglujingHaveObstacel; |
| | | import org.locationtech.jts.geom.Coordinate; |
| | | import gecaoji.Device; |
| | | import java.util.Locale; |
| | | |
| | | /** |
| | | * çæå²èè·¯å¾é¡µé¢ |
| | |
| | | obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' '); |
| | | } |
| | | |
| | | // è·åå®å
¨è·ç¦» |
| | | String safetyMarginStr = getSafetyDistanceString(); |
| | | if (safetyMarginStr == null) { |
| | | // å¦ææ²¡æè®¾ç½®å®å
¨è·ç¦»ï¼ä½¿ç¨é»è®¤å¼ï¼å²è宽度çä¸å + 0.2ç±³ |
| | | double defaultSafetyDistance = widthMeters / 2.0 + 0.2; |
| | | safetyMarginStr = BigDecimal.valueOf(defaultSafetyDistance) |
| | | .setScale(3, RoundingMode.HALF_UP) |
| | | .stripTrailingZeros() |
| | | .toPlainString(); |
| | | } |
| | | |
| | | String mode = normalizeExistingMowingPattern(modeInput); |
| | | try { |
| | | // 1. é¦å
夿å°åç±»åï¼å¸å½¢è¿æ¯å¼å½¢ï¼ |
| | | Qufenxingzhuang shapeJudger = new Qufenxingzhuang(); |
| | | int grassType = shapeJudger.judgeGrassType(boundary); |
| | | // grassType: 0=æ æ³å¤æ, 1=å¸å½¢, 2=å¼å½¢ |
| | | |
| | | // è§£æéç¢ç©å表 |
| | | List<List<Coordinate>> obstacleList = Lunjingguihua.parseObstacles(obstacles); |
| | | if (obstacleList == null) { |
| | |
| | | // 夿æ¯å¦æææçéç¢ç©ï¼åªæå½è§£ææåä¸å表ä¸ä¸ºç©ºæ¶ï¼æè®¤ä¸ºæéç¢ç© |
| | | boolean hasValidObstacles = !obstacleList.isEmpty(); |
| | | |
| | | String generated; |
| | | String generated = null; |
| | | |
| | | // 2. æ ¹æ®å°åç±»å忝妿éç¢ç©ï¼è°ç¨ä¸åçè·¯å¾çæç±» |
| | | if (!hasValidObstacles) { |
| | | // éç¢ç©åæ ä¸åå¨æä¸ºç©ºæ¶ï¼ä½¿ç¨Lunjingguihuaç±»çæ¹æ³çæè·¯å¾ |
| | | generated = Lunjingguihua.generatePathFromStrings( |
| | | boundary, |
| | | obstacles != null ? obstacles : "", |
| | | plannerWidth, |
| | | null, // safetyDistStrï¼ä½¿ç¨é»è®¤å¼ |
| | | mode |
| | | ); |
| | | } else { |
| | | // æææéç¢ç©æ¶ï¼ä½¿ç¨ObstaclePathPlannerå¤çè·¯å¾çæ |
| | | List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary); |
| | | if (polygon.size() < 4) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "å¤è¾¹å½¢åæ æ°éä¸è¶³ï¼è³å°éè¦ä¸ä¸ªç¹", |
| | | "é误", JOptionPane.ERROR_MESSAGE); |
| | | // æ éç¢ç©çæ
åµ |
| | | if (grassType == 1) { |
| | | // å¸å½¢å°åï¼æ éç¢ç© -> è°ç¨ AoxinglujingNoObstacle |
| | | List<AoxinglujingNoObstacle.PathSegment> segments = |
| | | AoxinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr); |
| | | generated = formatAoxingPathSegments(segments); |
| | | } else if (grassType == 2) { |
| | | // å¼å½¢å°åï¼æ éç¢ç© -> è°ç¨ YixinglujingNoObstacle |
| | | // 注æï¼å¦æè¯¥ç±»è¿æ²¡æå®ç°ï¼è¿é伿åºå¼å¸¸æè¿ånull |
| | | try { |
| | | // å设 YixinglujingNoObstacle æç±»ä¼¼çæ¹æ³ç¾å |
| | | // å¦æç±»è¿æ²¡æå®ç°ï¼å¯è½éè¦ä½¿ç¨åæ¥çæ¹æ³ä½ä¸ºåå¤ |
| | | generated = YixinglujingNoObstacle.planPath(boundary, plannerWidth, safetyMarginStr); |
| | | } catch (Exception e) { |
| | | // å¦æç±»è¿æ²¡æå®ç°ï¼ä½¿ç¨åæ¥çæ¹æ³ä½ä¸ºåå¤ |
| | | if (showMessages) { |
| | | System.err.println("YixinglujingNoObstacle å°æªå®ç°ï¼ä½¿ç¨é»è®¤æ¹æ³: " + e.getMessage()); |
| | | } |
| | | generated = Lunjingguihua.generatePathFromStrings( |
| | | boundary, obstacles != null ? obstacles : "", plannerWidth, safetyMarginStr, mode); |
| | | } |
| | | return null; |
| | | } else { |
| | | // æ æ³å¤æå°åç±»åï¼ä½¿ç¨åæ¥çæ¹æ³ä½ä¸ºåå¤ |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "æ æ³å¤æå°åç±»åï¼ä½¿ç¨é»è®¤è·¯å¾çææ¹æ³", |
| | | "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | generated = Lunjingguihua.generatePathFromStrings( |
| | | boundary, obstacles != null ? obstacles : "", plannerWidth, safetyMarginStr, mode); |
| | | } |
| | | |
| | | // æéç¢ç©æ¶ä½¿ç¨å²è宽度çä¸å + 0.05ç±³é¢å¤å®å
¨è·ç¦» |
| | | double safetyDistance = widthMeters / 2.0 + 0.05; |
| | | |
| | | ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner( |
| | | polygon, widthMeters, mode, obstacleList, safetyDistance); |
| | | List<Lunjingguihua.PathSegment> segments = pathPlanner.generate(); |
| | | generated = Lunjingguihua.formatPathSegments(segments); |
| | | } else { |
| | | // æéç¢ç©çæ
åµ |
| | | if (grassType == 1) { |
| | | // å¸å½¢å°åï¼æéç¢ç© -> è°ç¨ AoxinglujingHaveObstacel |
| | | try { |
| | | // å设 AoxinglujingHaveObstacel æç±»ä¼¼çæ¹æ³ç¾å |
| | | generated = AoxinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | } catch (Exception e) { |
| | | // å¦æç±»è¿æ²¡æå®ç°ï¼ä½¿ç¨åæ¥çæ¹æ³ä½ä¸ºåå¤ |
| | | if (showMessages) { |
| | | System.err.println("AoxinglujingHaveObstacel å°æªå®ç°ï¼ä½¿ç¨é»è®¤æ¹æ³: " + e.getMessage()); |
| | | } |
| | | List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary); |
| | | if (polygon.size() < 4) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "å¤è¾¹å½¢åæ æ°éä¸è¶³ï¼è³å°éè¦ä¸ä¸ªç¹", |
| | | "é误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | double safetyDistance = Double.parseDouble(safetyMarginStr); |
| | | ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner( |
| | | polygon, widthMeters, mode, obstacleList, safetyDistance); |
| | | List<Lunjingguihua.PathSegment> segments = pathPlanner.generate(); |
| | | generated = Lunjingguihua.formatPathSegments(segments); |
| | | } |
| | | } else if (grassType == 2) { |
| | | // å¼å½¢å°åï¼æéç¢ç© -> è°ç¨ YixinglujingHaveObstacel |
| | | try { |
| | | // å设 YixinglujingHaveObstacel æç±»ä¼¼çæ¹æ³ç¾å |
| | | generated = YixinglujingHaveObstacel.planPath(boundary, obstacles, plannerWidth, safetyMarginStr); |
| | | } catch (Exception e) { |
| | | // å¦æç±»è¿æ²¡æå®ç°ï¼ä½¿ç¨åæ¥çæ¹æ³ä½ä¸ºåå¤ |
| | | if (showMessages) { |
| | | System.err.println("YixinglujingHaveObstacel å°æªå®ç°ï¼ä½¿ç¨é»è®¤æ¹æ³: " + e.getMessage()); |
| | | } |
| | | List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary); |
| | | if (polygon.size() < 4) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "å¤è¾¹å½¢åæ æ°éä¸è¶³ï¼è³å°éè¦ä¸ä¸ªç¹", |
| | | "é误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | double safetyDistance = Double.parseDouble(safetyMarginStr); |
| | | ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner( |
| | | polygon, widthMeters, mode, obstacleList, safetyDistance); |
| | | List<Lunjingguihua.PathSegment> segments = pathPlanner.generate(); |
| | | generated = Lunjingguihua.formatPathSegments(segments); |
| | | } |
| | | } else { |
| | | // æ æ³å¤æå°åç±»åï¼ä½¿ç¨åæ¥çæ¹æ³ä½ä¸ºåå¤ |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "æ æ³å¤æå°åç±»åï¼ä½¿ç¨é»è®¤è·¯å¾çææ¹æ³", |
| | | "æç¤º", JOptionPane.WARNING_MESSAGE); |
| | | } |
| | | List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary); |
| | | if (polygon.size() < 4) { |
| | | if (showMessages) { |
| | | JOptionPane.showMessageDialog(parentComponent, "å¤è¾¹å½¢åæ æ°éä¸è¶³ï¼è³å°éè¦ä¸ä¸ªç¹", |
| | | "é误", JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | return null; |
| | | } |
| | | double safetyDistance = Double.parseDouble(safetyMarginStr); |
| | | ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner( |
| | | polygon, widthMeters, mode, obstacleList, safetyDistance); |
| | | List<Lunjingguihua.PathSegment> segments = pathPlanner.generate(); |
| | | generated = Lunjingguihua.formatPathSegments(segments); |
| | | } |
| | | } |
| | | |
| | | String trimmed = generated != null ? generated.trim() : ""; |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * è·åå®å
¨è·ç¦»å符串ï¼ç±³ï¼ |
| | | */ |
| | | private String getSafetyDistanceString() { |
| | | Device device = Device.getActiveDevice(); |
| | | if (device != null) { |
| | | String safetyDistanceValue = device.getMowingSafetyDistance(); |
| | | if (safetyDistanceValue != null && !"-1".equals(safetyDistanceValue) && !safetyDistanceValue.trim().isEmpty()) { |
| | | try { |
| | | double distanceMeters = Double.parseDouble(safetyDistanceValue.trim()); |
| | | // 妿å¼å¤§äº100ï¼è®¤ä¸ºæ¯åç±³ï¼éè¦è½¬æ¢ä¸ºç±³ |
| | | if (distanceMeters > 100) { |
| | | distanceMeters = distanceMeters / 100.0; |
| | | } |
| | | return BigDecimal.valueOf(distanceMeters) |
| | | .setScale(3, RoundingMode.HALF_UP) |
| | | .stripTrailingZeros() |
| | | .toPlainString(); |
| | | } catch (NumberFormatException e) { |
| | | // è§£æå¤±è´¥ï¼è¿ånullï¼ä½¿ç¨é»è®¤å¼ |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * æ ¼å¼å AoxinglujingNoObstacle.PathSegment åè¡¨ä¸ºåæ å符串 |
| | | */ |
| | | private String formatAoxingPathSegments(List<AoxinglujingNoObstacle.PathSegment> segments) { |
| | | if (segments == null || segments.isEmpty()) { |
| | | return ""; |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | AoxinglujingNoObstacle.Point last = null; |
| | | for (AoxinglujingNoObstacle.PathSegment segment : segments) { |
| | | // åªæ·»å å²è工使®µï¼è·³è¿è¿æ¸¡æ®µ |
| | | if (segment.isMowing) { |
| | | // å¦æèµ·ç¹ä¸ä¸ä¸ä¸ªç»ç¹ä¸åï¼æ·»å èµ·ç¹ |
| | | if (last == null || !equals2D(last, segment.start)) { |
| | | appendPoint(sb, segment.start); |
| | | } |
| | | // æ·»å ç»ç¹ |
| | | appendPoint(sb, segment.end); |
| | | last = segment.end; |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * æ¯è¾ä¸¤ä¸ªç¹æ¯å¦ç¸åï¼ä½¿ç¨å°çå®¹å·®ï¼ |
| | | */ |
| | | private boolean equals2D(AoxinglujingNoObstacle.Point p1, AoxinglujingNoObstacle.Point p2) { |
| | | if (p1 == null || p2 == null) { |
| | | return p1 == p2; |
| | | } |
| | | double tolerance = 1e-6; |
| | | return Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.y - p2.y) < tolerance; |
| | | } |
| | | |
| | | /** |
| | | * æ·»å ç¹å°å符串æå»ºå¨ |
| | | */ |
| | | private void appendPoint(StringBuilder sb, AoxinglujingNoObstacle.Point point) { |
| | | if (sb.length() > 0) { |
| | | sb.append(";"); |
| | | } |
| | | sb.append(String.format(Locale.US, "%.6f,%.6f", point.x, point.y)); |
| | | } |
| | | |
| | | // ========== UIè¾
婿¹æ³ ========== |
| | | |
| | | private JTextArea createInfoTextArea(String text, boolean editable, int rows) { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package lujing; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | public class Qufenxingzhuang { |
| | | |
| | | // 容差éå¼ï¼sin(è§åº¦)ã |
| | | // 0.05 å¤§çº¦å¯¹åº 2.8度ãæå³çå°äº3度çå¾®å°å¹é·æå¸èµ·ä¼è¢«å¿½ç¥ï¼è§ä¸ºç´çº¿ã |
| | | // 妿æ¨è§å¾å¤æä¾ç¶å¤ªä¸¥ï¼å¯ä»¥å°æ¤å¼è°å¤§å° 0.1 (约5.7度)ã |
| | | private static final double ANGLE_TOLERANCE = 0.1; |
| | | |
| | | // 夿èå°ç±»åçä¸»æ¹æ³ |
| | | public int judgeGrassType(String coordinates) { |
| | | try { |
| | | // 1. è§£æåæ å符串 |
| | | List<Point> points = parseCoordinates(coordinates); |
| | | |
| | | // 2. æ°æ®é¢å¤çï¼å 餿åä¸ä¸ªéåç¹ |
| | | // åå ï¼è¾å
¥æ°æ®é常é¦å°¾ç¸åï¼å 餿åä¸ä¸ªç¹é¿å
éå¤è®¡ç® |
| | | if (!points.isEmpty()) { |
| | | points.remove(points.size() - 1); |
| | | } |
| | | |
| | | // 3. æ£æ¥ç¹æ°ï¼ç§»é¤åè³å°éè¦3个ç¹ï¼ |
| | | if (points.size() < 3) { |
| | | return 0; // æ æ³ææå¤è¾¹å½¢ |
| | | } |
| | | |
| | | // 4. å¤æå½¢ç¶ |
| | | if (isConvexPolygon(points)) { |
| | | return 1; // å¸å½¢èå° |
| | | } else { |
| | | return 2; // å¼å½¢èå°ï¼ææ¾çå¹å¤è¾¹å½¢ï¼ |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | // è§£æå¤±è´¥æè®¡ç®å¼å¸¸ |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | // è§£æåæ å符串 |
| | | private List<Point> parseCoordinates(String coordinates) { |
| | | List<Point> points = new ArrayList<>(); |
| | | String cleanStr = coordinates.replaceAll("[()\\[\\]{}]", "").trim(); |
| | | String[] pointStrings = cleanStr.split(";"); |
| | | |
| | | for (String pointStr : pointStrings) { |
| | | pointStr = pointStr.trim(); |
| | | if (pointStr.isEmpty()) continue; |
| | | |
| | | String[] xy = pointStr.split(","); |
| | | if (xy.length != 2) throw new IllegalArgumentException("æ ¼å¼é误"); |
| | | |
| | | points.add(new Point(Double.parseDouble(xy[0].trim()), Double.parseDouble(xy[1].trim()))); |
| | | } |
| | | return points; |
| | | } |
| | | |
| | | // ãæ ¸å¿ä¿®æ¹ã夿å¤è¾¹å½¢æ¯å¦ä¸ºå¸å¤è¾¹å½¢ï¼å¸¦å®¹å·®ï¼ |
| | | private boolean isConvexPolygon(List<Point> points) { |
| | | // å
æ¸
çæåº¦æ¥è¿çç¹ï¼é¿å
é¤ä»¥é¶é误 |
| | | List<Point> cleanedPoints = cleanDuplicatePoints(points); |
| | | int n = cleanedPoints.size(); |
| | | if (n < 3) return false; |
| | | |
| | | boolean hasPositive = false; |
| | | boolean hasNegative = false; |
| | | |
| | | for (int i = 0; i < n; i++) { |
| | | Point p0 = cleanedPoints.get(i); |
| | | Point p1 = cleanedPoints.get((i + 1) % n); |
| | | Point p2 = cleanedPoints.get((i + 2) % n); |
| | | |
| | | // 计ç®åé p0->p1 å p1->p2 |
| | | double dx1 = p1.x - p0.x; |
| | | double dy1 = p1.y - p0.y; |
| | | double dx2 = p2.x - p1.x; |
| | | double dy2 = p2.y - p1.y; |
| | | |
| | | // 计ç®å积 |
| | | double cross = dx1 * dy2 - dx2 * dy1; |
| | | |
| | | // 计ç®è¾¹é¿ |
| | | double len1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); |
| | | double len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); |
| | | |
| | | // 鲿¢æç边导è´ç计ç®é误 |
| | | if (len1 < 1e-6 || len2 < 1e-6) continue; |
| | | |
| | | // ãå
³é®ä¿®æ¹ã计ç®å½ä¸åçåç§¯å¼ (ç¸å½äº sinθ) |
| | | // è¿ä»£è¡¨äºå¼¯æ²çç¨åº¦ï¼æé¤äºè¾¹é¿å¯¹å积æ°å¼å¤§å°çå¹²æ° |
| | | double sinTheta = cross / (len1 * len2); |
| | | |
| | | // åºç¨å®¹å·®é»è¾ï¼ |
| | | // åªæå½å¼¯æ²ç¨åº¦è¶
è¿é弿¶ï¼æè®°å½æ¹å |
| | | if (sinTheta > ANGLE_TOLERANCE) { |
| | | hasPositive = true; // ææ¾ç左转ï¼å¸ï¼ |
| | | } else if (sinTheta < -ANGLE_TOLERANCE) { |
| | | hasNegative = true; // ææ¾çå³è½¬ï¼å¹ï¼ |
| | | } |
| | | // 妿 sinTheta å¨ -0.05 å° 0.05 ä¹é´ï¼è®¤ä¸ºæ¯ç´çº¿æå¨ï¼å¿½ç¥ä¸è®¡ |
| | | |
| | | // å¦ææ¢æææ¾çå左转ï¼åæææ¾çåå³è½¬ï¼é£å°±æ¯å¼å½¢ï¼å¹å¤è¾¹å½¢ï¼ |
| | | if (hasPositive && hasNegative) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // å¦ææ²¡æäº§çå²çªç转åï¼æè
æ¯å纯çç´çº¿ï¼ææç¹å
±çº¿ï¼ï¼è§ä¸ºå¸å½¢ |
| | | return true; |
| | | } |
| | | |
| | | // è¾
å©ï¼ä»
å»é¤éå¤ç¹ï¼ä¸åè¿åº¦æ¸
çå
±çº¿ç¹ï¼å 为主é»è¾å·²ç»è½å¤çå
±çº¿æå¨ï¼ |
| | | private List<Point> cleanDuplicatePoints(List<Point> points) { |
| | | List<Point> cleaned = new ArrayList<>(); |
| | | double tolerance = 1e-6; |
| | | |
| | | for (Point p : points) { |
| | | if (!cleaned.isEmpty()) { |
| | | Point last = cleaned.get(cleaned.size() - 1); |
| | | if (Math.abs(p.x - last.x) < tolerance && Math.abs(p.y - last.y) < tolerance) { |
| | | continue; // è·³è¿éå¤ç¹ |
| | | } |
| | | } |
| | | cleaned.add(p); |
| | | } |
| | | |
| | | // æ£æ¥é¦å°¾æ¯å¦å 为循ç¯å¯¼è´éå¤ï¼è½ç¶ä¸»æ¹æ³å é¤äºæåä¸ä¸ªç¹ï¼ä¸ºäºå¥å£®æ§åæ¥ä¸æ¬¡ï¼ |
| | | if (cleaned.size() > 1) { |
| | | Point first = cleaned.get(0); |
| | | Point last = cleaned.get(cleaned.size() - 1); |
| | | if (Math.abs(first.x - last.x) < tolerance && Math.abs(first.y - last.y) < tolerance) { |
| | | cleaned.remove(cleaned.size() - 1); |
| | | } |
| | | } |
| | | return cleaned; |
| | | } |
| | | |
| | | // ç¹ç±» |
| | | private static class Point { |
| | | double x, y; |
| | | Point(double x, double y) { this.x = x; this.y = y; } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package lujing; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æéç¢ç©å¼å½¢å°åè·¯å¾è§åç±» |
| | | */ |
| | | public class YixinglujingHaveObstacel { |
| | | |
| | | /** |
| | | * çæè·¯å¾ |
| | | * @param boundaryCoordsStr å°åè¾¹çåæ å符串 "x1,y1;x2,y2;..." |
| | | * @param obstacleCoordsStr éç¢ç©åæ å符串 |
| | | * @param mowingWidthStr å²è宽度å符串ï¼å¦ "0.34" |
| | | * @param safetyMarginStr å®å
¨è¾¹è·å符串ï¼å¦ "0.2" |
| | | * @return è·¯å¾åæ åç¬¦ä¸²ï¼æ ¼å¼ "x1,y1;x2,y2;..." |
| | | */ |
| | | public static String planPath(String boundaryCoordsStr, String obstacleCoordsStr, String mowingWidthStr, String safetyMarginStr) { |
| | | // TODO: å®ç°å¼å½¢å°åæéç¢ç©è·¯å¾è§åç®æ³ |
| | | // ç®å使ç¨é»è®¤æ¹æ³ä½ä¸ºä¸´æ¶å®ç° |
| | | throw new UnsupportedOperationException("YixinglujingHaveObstacel.planPath å°æªå®ç°"); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package lujing; |
| | | |
| | | import java.util.List; |
| | | import java.util.ArrayList; |
| | | |
| | | /** |
| | | * æ éç¢ç©å¼å½¢å°åè·¯å¾è§åç±» |
| | | */ |
| | | public class YixinglujingNoObstacle { |
| | | |
| | | /** |
| | | * çæè·¯å¾ |
| | | * @param boundaryCoordsStr å°åè¾¹çåæ å符串 "x1,y1;x2,y2;..." |
| | | * @param mowingWidthStr å²è宽度å符串ï¼å¦ "0.34" |
| | | * @param safetyMarginStr å®å
¨è¾¹è·å符串ï¼å¦ "0.2" |
| | | * @return è·¯å¾åæ åç¬¦ä¸²ï¼æ ¼å¼ "x1,y1;x2,y2;..." |
| | | */ |
| | | public static String planPath(String boundaryCoordsStr, String mowingWidthStr, String safetyMarginStr) { |
| | | // TODO: å®ç°å¼å½¢å°åæ éç¢ç©è·¯å¾è§åç®æ³ |
| | | // ç®å使ç¨é»è®¤æ¹æ³ä½ä¸ºä¸´æ¶å®ç° |
| | | throw new UnsupportedOperationException("YixinglujingNoObstacle.planPath å°æªå®ç°"); |
| | | } |
| | | } |
| | |
| | | import zhuye.Shouye; |
| | | import zhuye.Coordinate; |
| | | import zhuye.buttonset; |
| | | import gecaoji.Device; |
| | | |
| | | /** |
| | | * æ°å¢å°åå¯¹è¯æ¡ - 夿¥éª¤è¡¨å设计 |
| | |
| | | private JComboBox<String> mowingPatternCombo; |
| | | private JTextField mowingWidthField; // å²èæºå²å宽度 |
| | | private JTextField overlapDistanceField; // ç¸é»è¡éå è·ç¦» |
| | | private JTextField mowingSafetyDistanceField; // å²èå®å
¨è·ç¦» |
| | | private JLabel calculatedMowingWidthLabel; // 计ç®åçå²è宽度æ¾ç¤º |
| | | private JPanel previewPanel; |
| | | private Map<String, JPanel> drawingOptionPanels = new HashMap<>(); |
| | |
| | | |
| | | calculatedWidthPanel.add(calculatedWidthDisplayPanel); |
| | | settingsPanel.add(calculatedWidthPanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // å²èå®å
¨è·ç¦» |
| | | JPanel safetyDistancePanel = createFormGroupWithoutHint("å²èå®å
¨è·ç¦»"); |
| | | JPanel safetyDistanceInputPanel = new JPanel(new BorderLayout()); |
| | | safetyDistanceInputPanel.setBackground(WHITE); |
| | | safetyDistanceInputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 48)); |
| | | safetyDistanceInputPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | |
| | | // è·åé»è®¤å¼ |
| | | String defaultSafetyDistance = "0.5"; // é»è®¤å¼ |
| | | try { |
| | | Device device = Device.getGecaoji(); |
| | | if (device != null) { |
| | | String safetyDistance = device.getMowingSafetyDistance(); |
| | | if (safetyDistance != null && !safetyDistance.trim().isEmpty() && !"-1".equals(safetyDistance.trim())) { |
| | | defaultSafetyDistance = safetyDistance.trim(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | // 妿è·å失败ï¼ä½¿ç¨é»è®¤å¼ |
| | | } |
| | | |
| | | mowingSafetyDistanceField = new JTextField(defaultSafetyDistance); |
| | | mowingSafetyDistanceField.setFont(new Font("微软é
é»", Font.PLAIN, 16)); |
| | | mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | |
| | | // æ·»å ææ¬æ¡ç¦ç¹ææ |
| | | mowingSafetyDistanceField.addFocusListener(new FocusAdapter() { |
| | | @Override |
| | | public void focusGained(FocusEvent e) { |
| | | mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(PRIMARY_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | |
| | | @Override |
| | | public void focusLost(FocusEvent e) { |
| | | mowingSafetyDistanceField.setBorder(BorderFactory.createCompoundBorder( |
| | | BorderFactory.createLineBorder(BORDER_COLOR, 2), |
| | | BorderFactory.createEmptyBorder(10, 12, 10, 12) |
| | | )); |
| | | } |
| | | }); |
| | | |
| | | JLabel safetyDistanceUnitLabel = new JLabel("ç±³"); |
| | | safetyDistanceUnitLabel.setFont(new Font("微软é
é»", Font.PLAIN, 14)); |
| | | safetyDistanceUnitLabel.setForeground(LIGHT_TEXT); |
| | | safetyDistanceUnitLabel.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 0)); |
| | | |
| | | safetyDistanceInputPanel.add(mowingSafetyDistanceField, BorderLayout.CENTER); |
| | | safetyDistanceInputPanel.add(safetyDistanceUnitLabel, BorderLayout.EAST); |
| | | |
| | | safetyDistancePanel.add(safetyDistanceInputPanel); |
| | | settingsPanel.add(safetyDistancePanel); |
| | | settingsPanel.add(Box.createRigidArea(new Dimension(0, 25))); |
| | | |
| | | stepPanel.add(settingsPanel); |