From 13d032241e1a2938a8be4f64c9171e1240e9ea1e Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期一, 22 十二月 2025 18:50:42 +0800
Subject: [PATCH] 新增了边界管理页面和首页边界虚线功能
---
src/lujing/AoxinglujingNoObstacle.java | 206 ++++++++++++++++++++++++++++++++-------------------
1 files changed, 130 insertions(+), 76 deletions(-)
diff --git a/src/lujing/AoxinglujingNoObstacle.java b/src/lujing/AoxinglujingNoObstacle.java
index 75717ba..1b13c98 100644
--- a/src/lujing/AoxinglujingNoObstacle.java
+++ b/src/lujing/AoxinglujingNoObstacle.java
@@ -1,14 +1,19 @@
package lujing;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * 鏃犻殰纰嶇墿鍑稿舰鑽夊湴璺緞瑙勫垝绫� (浼樺寲鐗�)
+ * 鏃犻殰纰嶇墿鍑稿舰鑽夊湴璺緞瑙勫垝绫� (缁堟瀬浼樺寲鐗�)
+ * 鐗规�э細
+ * 1. 鏈�灏忔姇褰卞搴︽柟鍚戦�夋嫨 (鏁堢巼鏈�楂橈紝杞集鏈�灏�)
+ * 2. 杈圭紭杞粨浼樺厛鍒囧壊 (鏃犳瑙掕鐩�)
+ * 3. 鏀寔澶栭儴浼犲叆宸插寘鍚噸鍙犵巼鐨勫搴﹀弬鏁�
*/
public class AoxinglujingNoObstacle {
- // 浼樺寲锛氬紩鍏ユ瀬灏忓�肩敤浜庢诞鐐规暟姣旇緝锛屽鐞嗗嚑浣曠簿搴﹁宸�
+ // 寮曞叆鏋佸皬鍊肩敤浜庢诞鐐规暟姣旇緝锛屽鐞嗗嚑浣曠簿搴﹁宸�
private static final double EPSILON = 1e-6;
/**
@@ -49,15 +54,15 @@
}
/**
- * 瀵瑰鍏紑鐨勯潤鎬佽皟鐢ㄦ柟娉� (淇濈暀瀛楃涓插叆鍙傛牸寮�)
+ * 瀵瑰鍏紑鐨勯潤鎬佽皟鐢ㄦ柟娉�
*
* @param boundaryCoordsStr 鍦板潡杈圭晫鍧愭爣瀛楃涓� "x1,y1;x2,y2;..."
- * @param mowingWidthStr 鍓茶崏瀹藉害瀛楃涓诧紝濡� "0.34"
+ * @param mowingWidthStr 鏈夋晥鍓茶崏瀹藉害瀛楃涓� (宸插寘鍚噸鍙犵巼)锛屽 "0.30"
* @param safetyMarginStr 瀹夊叏杈硅窛瀛楃涓诧紝濡� "0.2"
* @return 璺緞娈靛垪琛�
*/
public static List<PathSegment> planPath(String boundaryCoordsStr, String mowingWidthStr, String safetyMarginStr) {
- // 1. 瑙f瀽鍙傛暟 (浼樺寲锛氬崟鐙彁鍙栬В鏋愰�昏緫)
+ // 1. 瑙f瀽鍙傛暟
List<Point> originalPolygon = parseCoords(boundaryCoordsStr);
double mowingWidth;
double safetyMargin;
@@ -74,85 +79,119 @@
}
/**
- * 鏍稿績绠楁硶閫昏緫 (寮虹被鍨嬪叆鍙傦紝渚夸簬娴嬭瘯鍜屽唴閮ㄨ皟鐢�)
+ * 鏍稿績绠楁硶閫昏緫
*/
- private static List<PathSegment> planPathCore(List<Point> originalPolygon, double mowingWidth, double safetyMargin) {
- // 浼樺寲锛氫笁瑙掑舰涔熸槸鍚堟硶鐨勫嚫澶氳竟褰紝闄愬埗鏀逛负灏忎簬3
+ private static List<PathSegment> planPathCore(List<Point> originalPolygon, double width, double safetyMargin) {
if (originalPolygon == null || originalPolygon.size() < 3) {
- throw new IllegalArgumentException("澶氳竟褰㈠潗鏍囩偣涓嶈冻3涓紝鏃犳硶鏋勬垚鏈夋晥鍖哄煙");
+ return new ArrayList<>(); // 鎴栨姏鍑哄紓甯革紝瑙嗕笟鍔¢渶姹傝�屽畾
}
// 纭繚澶氳竟褰㈡槸閫嗘椂閽堟柟鍚�
ensureCCW(originalPolygon);
- // 1. 鏍规嵁瀹夊叏杈硅窛鍐呯缉
- List<Point> shrunkPolygon = shrinkPolygon(originalPolygon, safetyMargin);
+ // 1. 鏍规嵁瀹夊叏杈硅窛鍐呯缉锛屽緱鍒板疄闄呬綔涓氬尯鍩�
+ List<Point> workAreaPolygon = shrinkPolygon(originalPolygon, safetyMargin);
- // 浼樺寲锛氬唴缂╁悗濡傛灉澶氳竟褰㈠け鏁堬紙渚嬪鍦板潡澶獎锛屽唴缂╁悗娑堝け锛夛紝鐩存帴杩斿洖绌鸿矾寰勶紝閬垮厤鍚庣画鎶ラ敊
- if (shrunkPolygon.size() < 3) {
- // 杩欓噷鍙互璁板綍鏃ュ織锛氬湴鍧楄繃灏忥紝鏃犳硶婊¤冻瀹夊叏璺濈浣滀笟
+ // 濡傛灉鍐呯缉鍚庡尯鍩熷け鏁堬紙濡傚湴鍧楀お灏忥級锛岃繑鍥炵┖璺緞
+ if (workAreaPolygon.size() < 3) {
return new ArrayList<>();
}
- // 2. 璁$畻鏈�闀胯竟瑙掑害 (浣滀负鎵弿鏂瑰悜)
- double angle = calculateLongestEdgeAngle(originalPolygon);
+ List<PathSegment> finalPath = new ArrayList<>();
- // 3. & 4. 鏃嬭浆鎵弿骞跺壀瑁�
- List<PathSegment> mowingLines = generateClippedMowingLines(shrunkPolygon, angle, mowingWidth);
+ // 2. [浼樺寲] 浼樺厛鐢熸垚杞粨璺緞 (Contour Pass)
+ // 娌夸綔涓氳竟鐣岃蛋涓�鍦堬紝纭繚杈圭紭鏁撮綈涓旀棤閬楁紡
+ addContourPath(workAreaPolygon, finalPath);
- // 5. 寮撳瓧褰㈣繛鎺�
- return connectPathSegments(mowingLines);
- }
+ // 3. [浼樺寲] 璁$畻鏈�浣虫壂鎻忚搴�
+ // 瀵绘壘璁╁杈瑰舰鎶曞奖楂樺害鏈�灏忕殑瑙掑害锛屼粠鑰屾渶灏忓寲杞集娆℃暟
+ double bestAngle = findOptimalScanAngle(workAreaPolygon);
- // ================= 鏍稿績绠楁硶杈呭姪鏂规硶 =================
+ // 4. 鐢熸垚鍐呴儴寮撳瓧褰㈣矾寰�
+ // 鐩存帴浣跨敤浼犲叆鐨� width (宸插寘鍚噸鍙犵巼)
+ List<PathSegment> zigZagPaths = generateClippedMowingLines(workAreaPolygon, bestAngle, width);
- 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);
+ // 5. 杩炴帴杞粨璺緞鍜屽紦瀛楀舰璺緞
+ if (!finalPath.isEmpty() && !zigZagPaths.isEmpty()) {
+ Point contourEnd = finalPath.get(finalPath.size() - 1).end;
+ Point zigzagStart = zigZagPaths.get(0).start;
- // 浼樺寲锛氬鍔犻潪绌哄垽鏂紝涓斿鏋滀氦鐐瑰紓甯歌繙锛堝皷瑙掓晥搴旓級锛屽疄闄呭伐绋嬩腑鍙兘闇�瑕佸垏瑙掑鐞�
- // 杩欓噷鏆備繚鐣欏熀纭�閫昏緫锛屼絾鍦ㄥ嚫澶氳竟褰腑閫氬父娌¢棶棰�
- if (intersection != null) {
- newPoints.add(intersection);
+ // 濡傛灉杞粨缁堢偣涓庡紦瀛楀舰璧风偣涓嶉噸鍚堬紝娣诲姞杩囨浮娈�
+ if (distanceSq(contourEnd, zigzagStart) > EPSILON) {
+ finalPath.add(new PathSegment(contourEnd, zigzagStart, false));
}
}
- return newPoints;
+
+ // 6. 鍚堝苟寮撳瓧褰㈣矾寰�
+ finalPath.addAll(connectPathSegments(zigZagPaths));
+
+ return finalPath;
}
- private static double calculateLongestEdgeAngle(List<Point> polygon) {
- double maxDistSq = -1;
- double angle = 0;
- int n = polygon.size();
+ // ================= 鏍稿績閫昏緫杈呭姪鏂规硶 =================
+ /**
+ * 娣诲姞杞粨璺緞 (鍥寸潃澶氳竟褰㈣蛋涓�鍦�)
+ */
+ private static void addContourPath(List<Point> polygon, List<PathSegment> path) {
+ 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;
+ path.add(new PathSegment(p1, p2, true));
+ }
+ }
- if (distSq > maxDistSq) {
- maxDistSq = distSq;
- angle = Math.atan2(dy, dx);
+ /**
+ * 瀵绘壘鏈�浼樻壂鎻忚搴� (鏈�灏忔姇褰遍珮搴︽硶)
+ */
+ private static double findOptimalScanAngle(List<Point> polygon) {
+ double minHeight = Double.MAX_VALUE;
+ double bestAngle = 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 currentAngle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
+
+ // 璁$畻鍦ㄨ繖涓搴︿笅鐨勬姇褰遍珮搴�
+ double height = calculatePolygonHeightAtAngle(polygon, currentAngle);
+
+ if (height < minHeight) {
+ minHeight = height;
+ bestAngle = currentAngle;
}
}
- return angle;
+ return bestAngle;
+ }
+
+ /**
+ * 璁$畻澶氳竟褰㈠湪鐗瑰畾鏃嬭浆瑙掑害涓嬬殑Y杞存姇褰遍珮搴�
+ */
+ private static double calculatePolygonHeightAtAngle(List<Point> poly, double angle) {
+ double minY = Double.MAX_VALUE;
+ double maxY = -Double.MAX_VALUE;
+
+ double cos = Math.cos(-angle);
+ double sin = Math.sin(-angle);
+
+ for (Point p : poly) {
+ // 鍙渶璁$畻鏃嬭浆鍚庣殑Y鍧愭爣
+ double rotatedY = p.x * sin + p.y * cos;
+ if (rotatedY < minY) minY = rotatedY;
+ if (rotatedY > maxY) maxY = rotatedY;
+ }
+ return maxY - minY;
}
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;
@@ -162,28 +201,27 @@
if (p.y > maxY) maxY = p.y;
}
- // 浼樺寲锛氳捣濮嬫壂鎻忕嚎澧炲姞涓�涓井灏忕殑鍋忕Щ閲� EPSILON
- // 閬垮厤鎵弿绾挎濂借惤鍦ㄩ《鐐逛笂锛屽鑷翠氦鐐瑰垽鏂�昏緫鍑虹幇浜屼箟鎬�
+ // 璧峰鎵弿绾夸綅缃細
+ // 浠� minY + width/2 寮�濮嬶紝鍥犱负涔嬪墠宸茬粡璧颁簡杞粨绾�(Contour Pass)銆�
+ // 杞粨绾胯礋璐f竻鐞嗚竟缂樺尯鍩燂紝鍐呴儴濉厖绾夸繚鎸� width 鐨勯棿璺濆嵆鍙��
+ // 鍔犱笂 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);
}
@@ -191,20 +229,19 @@
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); // 杩欓噷鐨刢urrentY瀹為檯涓婂甫浜唀psilon锛岃繕鍘熸椂娌¢棶棰�
+ // 鍙嶅悜鏃嬭浆鍥炲師鍧愭爣绯�
+ Point rStart = rotatePoint(new Point(xStart, currentY), angle);
Point rEnd = rotatePoint(new Point(xEnd, currentY), angle);
segments.add(new PathSegment(rStart, rEnd, true));
}
}
-
+ // 姝ヨ繘
currentY += width;
}
@@ -231,7 +268,6 @@
// 娣诲姞杩囨浮娈�
if (i > 0) {
Point prevEnd = result.get(result.size() - 1).end;
- // 鍙湁褰撹窛绂荤‘瀹炲瓨鍦ㄦ椂鎵嶆坊鍔犺繃娓℃锛堥伩鍏嶉噸鍚堢偣锛�
if (distanceSq(prevEnd, actualStart) > EPSILON) {
result.add(new PathSegment(prevEnd, actualStart, false));
}
@@ -242,12 +278,12 @@
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(",");
@@ -257,14 +293,13 @@
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++) {
@@ -272,14 +307,33 @@
Point p2 = polygon.get((i + 1) % polygon.size());
sum += (p2.x - p1.x) * (p2.y + p1.y);
}
- // 鍋囪鏍囧噯绗涘崱灏斿潗鏍囩郴锛宻um > 0 涓洪『鏃堕拡锛岄渶瑕佸弽杞负閫嗘椂閽�
if (sum > 0) {
Collections.reverse(polygon);
}
}
+ 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 class Line {
- double a, b, c;
+ double a, b, c;
public Line(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
}
@@ -287,13 +341,13 @@
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;
@@ -305,7 +359,7 @@
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; // 骞宠
+ 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);
@@ -324,7 +378,7 @@
}
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);
}
--
Gitblit v1.10.0