package lujing; import java.util.*; /** * 异形草地路径规划 - 含障碍物版 * 功能:在地块内部避开障碍物,生成连续弓字形割草路径 */ public class YixinglujingHaveObstacel { public static List planPath(String coordinates, String obstaclesStr, String widthStr, String marginStr) { // 1. 解析参数 List rawPoints = parseCoordinates(coordinates); if (rawPoints.size() < 3) return new ArrayList<>(); double mowWidth = Double.parseDouble(widthStr); double safeMargin = Double.parseDouble(marginStr); // 解析障碍物 List obstacles = parseObstacles(obstaclesStr); // 2. 预处理:确保边界逆时针 ensureCounterClockwise(rawPoints); // 3. 生成内缩多边形(安全边界) List boundary = getInsetPolygon(rawPoints, safeMargin); if (boundary.size() < 3) return new ArrayList<>(); // 4. 外扩障碍物(安全边距) List expandedObstacles = expandObstacles(obstacles, safeMargin); // 5. 确定最优作业角度 double bestAngle = findOptimalAngle(boundary); // 6. 获取首个作业点,用于对齐围边起点 Point firstScanStart = getFirstScanPoint(boundary, mowWidth, bestAngle); // 7. 对齐围边 List alignedBoundary = alignBoundaryStart(boundary, firstScanStart); // 8. 第一阶段:围边路径 List finalPath = new ArrayList<>(); for (int i = 0; i < alignedBoundary.size(); i++) { Point pStart = alignedBoundary.get(i); Point pEnd = alignedBoundary.get((i + 1) % alignedBoundary.size()); finalPath.add(new PathSegment(pStart, pEnd, true)); } // 9. 第二阶段:生成内部扫描路径(考虑障碍物) Point lastEdgePos = alignedBoundary.get(0); List scanPath = generateGlobalScanPathWithObstacles( boundary, expandedObstacles, mowWidth, bestAngle, lastEdgePos); finalPath.addAll(scanPath); // 10. 格式化坐标:保留两位小数 for (PathSegment segment : finalPath) { segment.start.x = Math.round(segment.start.x * 100.0) / 100.0; segment.start.y = Math.round(segment.start.y * 100.0) / 100.0; segment.end.x = Math.round(segment.end.x * 100.0) / 100.0; segment.end.y = Math.round(segment.end.y * 100.0) / 100.0; } // 11. 打印输出路径坐标 printPathCoordinates(finalPath); return finalPath; } /** * 生成带障碍物的扫描路径 */ private static List generateGlobalScanPathWithObstacles( List polygon, List obstacles, double width, double angle, Point startPos) { // 1. 生成原始扫描线(无障碍物) List originalSegments = generateGlobalScanPath(polygon, width, angle, startPos); // 2. 移除在障碍物内部的线段 List remainingSegments = new ArrayList<>(); for (PathSegment seg : originalSegments) { if (!seg.isMowing) { // 空走段直接保留 remainingSegments.add(seg); continue; } // 将割草段与所有障碍物进行裁剪 List clippedSegments = new ArrayList<>(); clippedSegments.add(seg); for (Obstacle obs : obstacles) { List newSegments = new ArrayList<>(); for (PathSegment s : clippedSegments) { newSegments.addAll(clipSegmentWithObstacle(s, obs)); } clippedSegments = newSegments; } remainingSegments.addAll(clippedSegments); } // 3. 重新连接路径段(弓字形连接,智能处理边界穿越) return reconnectSegments(remainingSegments, polygon); } /** * 将线段与障碍物进行裁剪 * 返回不在障碍物内部的子线段 */ private static List clipSegmentWithObstacle(PathSegment segment, Obstacle obstacle) { List result = new ArrayList<>(); // 检查线段是否完全在障碍物外部 boolean startInside = obstacle.contains(segment.start); boolean endInside = obstacle.contains(segment.end); if (!startInside && !endInside) { // 线段两端都在外部,检查是否穿过障碍物 List intersections = obstacle.getIntersections(segment); if (intersections.isEmpty()) { // 完全在外部 result.add(segment); } else { // 穿过障碍物,分割线段 intersections.sort(Comparator.comparingDouble(p -> distance(segment.start, p))); Point prevPoint = segment.start; for (Point inter : intersections) { result.add(new PathSegment(prevPoint, inter, true)); prevPoint = inter; } result.add(new PathSegment(prevPoint, segment.end, true)); // 移除在障碍物内部的段(奇数索引的段) List filtered = new ArrayList<>(); for (int i = 0; i < result.size(); i++) { PathSegment s = result.get(i); Point midPoint = new Point( (s.start.x + s.end.x) / 2, (s.start.y + s.end.y) / 2 ); if (!obstacle.contains(midPoint)) { filtered.add(s); } } return filtered; } } else if (startInside && endInside) { // 完全在内部,丢弃 return result; } else { // 一端在内部,一端在外部 Point outsidePoint = startInside ? segment.end : segment.start; List intersections = obstacle.getIntersections(segment); if (!intersections.isEmpty()) { // 取离外部点最近的交点 intersections.sort(Comparator.comparingDouble(p -> distance(outsidePoint, p))); Point inter = intersections.get(0); // 只保留外部部分 if (startInside) { result.add(new PathSegment(inter, outsidePoint, true)); } else { result.add(new PathSegment(outsidePoint, inter, true)); } } } return result; } /** * 重新连接路径段,形成连续弓字形路径 * 优化:智能处理边界穿越,当换行路径穿越边界时,沿边界行走 */ private static List reconnectSegments(List segments, List boundary) { if (segments.isEmpty()) return new ArrayList<>(); List reconnected = new ArrayList<>(); Point currentPos = segments.get(0).start; for (PathSegment seg : segments) { if (seg.isMowing) { // 割草段:检查是否需要添加空走段 if (distance(currentPos, seg.start) > 0.01) { // 使用智能连接方法生成换行路径 List connectionPath = buildSmartConnection(currentPos, seg.start, boundary); reconnected.addAll(connectionPath); } reconnected.add(seg); currentPos = seg.end; } else { // 空走段直接添加 reconnected.add(seg); currentPos = seg.end; } } return reconnected; } /** * 智能连接两点:如果直线不穿越边界则直接连接,否则使用直线+边界混合路径 * 优化逻辑: * 1. 如果AB线不穿越边界C,直接使用AB作为换行路线 * 2. 如果AB线穿越了边界C,找到所有交点,将AB分成多个段 * - 对于在边界内部的段(如DF段、GH段),沿边界行走 * - 对于在边界外部的段,沿AB直线行走 * 路径示例:A → D(直线) → F(沿边界) → G(直线) → H(沿边界) → B(直线) * * @param pointA 起点(上一段结束的终点) * @param pointB 终点(下一段需要割草路径的起始点) * @param boundary 安全内缩边界C * @return 连接路径段列表(全部为isMowing=false的空走段) */ private static List buildSmartConnection(Point pointA, Point pointB, List boundary) { List result = new ArrayList<>(); // 1. 检查AB直线是否穿越边界C if (!segmentIntersectsBoundary(pointA, pointB, boundary)) { // 不穿越边界,直接使用AB作为换行路线 result.add(new PathSegment(pointA, pointB, false)); return result; } // 2. AB线穿越了边界C,需要找到所有交点 List intersections = getAllBoundaryIntersections(pointA, pointB, boundary); if (intersections.isEmpty()) { // 没有交点(不应该发生,但安全处理),使用直线 result.add(new PathSegment(pointA, pointB, false)); return result; } // 3. 按距离起点A的距离排序交点 intersections.sort(Comparator.comparingDouble(inter -> distance(pointA, inter.point))); // 4. 构建完整的点序列:A, I1, I2, ..., In, B(I为交点) List pointSequence = new ArrayList<>(); pointSequence.add(pointA); for (IntersectionInfo inter : intersections) { pointSequence.add(inter.point); } pointSequence.add(pointB); // 5. 处理每两个相邻点之间的段 Point currentPos = pointA; for (int i = 0; i < pointSequence.size() - 1; i++) { Point p1 = pointSequence.get(i); Point p2 = pointSequence.get(i + 1); // 判断p1到p2的段(AB线段的一部分)是否在边界C内部(检查中点) Point midPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); boolean segmentInsideBoundary = isPointInPolygon(midPoint, boundary); if (segmentInsideBoundary) { // 段在边界内部(如DF段、GH段),需要沿边界行走 if (i == 0) { // 第一个段:从A到第一个交点D // 如果段在边界内部,说明A在边界内部,需要先从A沿边界走到第一个交点 IntersectionInfo firstInter = intersections.get(0); SnapResult snapA = snapToBoundary(currentPos, boundary); List boundaryPath = getBoundaryPathBetweenPoints( snapA.onEdge, snapA.edgeIndex, firstInter.point, firstInter.edgeIndex, boundary); result.addAll(boundaryPath); currentPos = firstInter.point; } else if (i == pointSequence.size() - 2) { // 最后一个段:从最后一个交点H到B // 需要沿边界从当前点(应该是最后一个交点H)到B在边界上的投影 IntersectionInfo lastInter = intersections.get(intersections.size() - 1); SnapResult snapB = snapToBoundary(pointB, boundary); List boundaryPath = getBoundaryPathBetweenPoints( currentPos, lastInter.edgeIndex, snapB.onEdge, snapB.edgeIndex, boundary); result.addAll(boundaryPath); // 如果B不在边界上,从边界投影直线到B if (distance(snapB.onEdge, pointB) > 1e-6) { result.add(new PathSegment(snapB.onEdge, pointB, false)); } currentPos = pointB; } else { // 中间段:两个交点之间的段(都在边界上),沿边界行走 IntersectionInfo inter1 = intersections.get(i - 1); IntersectionInfo inter2 = intersections.get(i); List boundaryPath = getBoundaryPathBetweenPoints( inter1.point, inter1.edgeIndex, inter2.point, inter2.edgeIndex, boundary); result.addAll(boundaryPath); currentPos = inter2.point; } } else { // 段在边界外部,可以直接沿AB直线连接(如A到D,F到G,H到B) if (distance(p1, p2) > 1e-6) { // 如果当前点不在p1,先连接到p1 if (distance(currentPos, p1) > 1e-6) { result.add(new PathSegment(currentPos, p1, false)); } // 从p1直线到p2 result.add(new PathSegment(p1, p2, false)); currentPos = p2; } } } return result; } /** * 检查线段是否穿越边界(与边界边相交,不包括端点) */ private static boolean segmentIntersectsBoundary(Point a, Point b, List boundary) { for (int i = 0; i < boundary.size(); i++) { Point c = boundary.get(i); Point d = boundary.get((i + 1) % boundary.size()); // 忽略共享端点的相交 if (isSamePoint(a, c) || isSamePoint(a, d) || isSamePoint(b, c) || isSamePoint(b, d)) { continue; } if (segmentsIntersect(a, b, c, d)) { return true; } } return false; } /** * 获取线段与边界的所有交点信息(包括点和对应边索引) */ private static List getAllBoundaryIntersections(Point a, Point b, List boundary) { List intersections = new ArrayList<>(); for (int i = 0; i < boundary.size(); i++) { Point c = boundary.get(i); Point d = boundary.get((i + 1) % boundary.size()); // 忽略共享端点 if (isSamePoint(a, c) || isSamePoint(a, d) || isSamePoint(b, c) || isSamePoint(b, d)) { continue; } Point intersection = getLineIntersection(a, b, c, d); if (intersection != null) { intersections.add(new IntersectionInfo(intersection, i)); } } return intersections; } /** * 获取边界上两点之间的路径(沿边界行走) * @param start 起点(必须在边界上) * @param startEdgeIndex 起点所在的边索引 * @param end 终点(必须在边界上) * @param endEdgeIndex 终点所在的边索引 * @param boundary 边界点列表 * @return 沿边界的路径段列表 */ private static List getBoundaryPathBetweenPoints( Point start, int startEdgeIndex, Point end, int endEdgeIndex, List boundary) { List result = new ArrayList<>(); if (startEdgeIndex == endEdgeIndex) { // 在同一条边上,直接连接 if (distance(start, end) > 1e-6) { result.add(new PathSegment(start, end, false)); } return result; } int n = boundary.size(); // 计算顺时针路径 List pathClockwise = new ArrayList<>(); pathClockwise.add(start); int curr = startEdgeIndex; while (curr != endEdgeIndex) { pathClockwise.add(boundary.get((curr + 1) % n)); curr = (curr + 1) % n; } pathClockwise.add(end); // 计算逆时针路径 List pathCounterClockwise = new ArrayList<>(); pathCounterClockwise.add(start); curr = startEdgeIndex; while (curr != endEdgeIndex) { pathCounterClockwise.add(boundary.get(curr)); curr = (curr - 1 + n) % n; } pathCounterClockwise.add(end); // 选择较短的路径 List chosenPath = getPathLength(pathClockwise) < getPathLength(pathCounterClockwise) ? pathClockwise : pathCounterClockwise; // 转换为路径段 for (int i = 0; i < chosenPath.size() - 1; i++) { if (distance(chosenPath.get(i), chosenPath.get(i + 1)) > 1e-6) { result.add(new PathSegment(chosenPath.get(i), chosenPath.get(i + 1), false)); } } return result; } /** * 计算路径总长度 */ private static double getPathLength(List path) { double len = 0; for (int i = 0; i < path.size() - 1; i++) { len += distance(path.get(i), path.get(i + 1)); } return len; } /** * 判断两个点是否相同(考虑浮点误差) */ private static boolean isSamePoint(Point a, Point b) { return Math.abs(a.x - b.x) < 1e-6 && Math.abs(a.y - b.y) < 1e-6; } /** * 判断两条线段是否相交(不包括端点) */ private static boolean segmentsIntersect(Point a, Point b, Point c, Point d) { return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d); } /** * 判断三点是否逆时针排列 */ private static boolean ccw(Point a, Point b, Point c) { return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x); } /** * 交点信息内部类 */ private static class IntersectionInfo { Point point; // 交点坐标 int edgeIndex; // 交点所在的边界边索引 IntersectionInfo(Point point, int edgeIndex) { this.point = point; this.edgeIndex = edgeIndex; } } /** * 边界吸附结果内部类 */ private static class SnapResult { Point onEdge; // 在边界上的投影点 int edgeIndex; // 所在的边界边索引 SnapResult(Point p, int idx) { this.onEdge = p; this.edgeIndex = idx; } } /** * 计算点到边界最近的投影点以及所在边索引 * @param p 要吸附的点 * @param poly 边界多边形 * @return 吸附结果 */ private static SnapResult snapToBoundary(Point p, List poly) { double minD = Double.MAX_VALUE; Point bestProj = p; int bestIdx = -1; for (int i = 0; i < poly.size(); i++) { Point s = poly.get(i); Point e = poly.get((i + 1) % poly.size()); double l2 = (s.x - e.x) * (s.x - e.x) + (s.y - e.y) * (s.y - e.y); if (l2 < 1e-10) { double d = Math.hypot(p.x - s.x, p.y - s.y); if (d < minD) { minD = d; bestProj = s; bestIdx = i; } continue; } double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2; t = Math.max(0, Math.min(1, t)); Point proj = new Point(s.x + t * (e.x - s.x), s.y + t * (e.y - s.y)); double d = Math.hypot(p.x - proj.x, p.y - proj.y); if (d < minD) { minD = d; bestProj = proj; bestIdx = i; } } return new SnapResult(bestProj, bestIdx == -1 ? 0 : bestIdx); } /** * 判断点是否在边界上(距离边界很近) * @param p 要检查的点 * @param boundary 边界多边形 * @return 是否在边界上 */ @SuppressWarnings("unused") private static boolean isPointOnBoundary(Point p, List boundary) { double threshold = 1e-4; // 阈值,考虑浮点误差 for (int i = 0; i < boundary.size(); i++) { Point s = boundary.get(i); Point e = boundary.get((i + 1) % boundary.size()); double dist = distToSegment(p, s, e); if (dist < threshold) { return true; } } return false; } /** * 计算点到线段的距离 * @param p 点 * @param s 线段起点 * @param e 线段终点 * @return 距离 */ private static double distToSegment(Point p, Point s, Point e) { double l2 = (s.x - e.x) * (s.x - e.x) + (s.y - e.y) * (s.y - e.y); if (l2 < 1e-10) { return Math.hypot(p.x - s.x, p.y - s.y); } double t = ((p.x - s.x) * (e.x - s.x) + (p.y - s.y) * (e.y - s.y)) / l2; t = Math.max(0, Math.min(1, t)); return Math.hypot(p.x - (s.x + t * (e.x - s.x)), p.y - (s.y + t * (e.y - s.y))); } /** * 生成原始扫描路径(无障碍物版本) */ private static List generateGlobalScanPath( List polygon, double width, double angle, Point currentPos) { List segments = new ArrayList<>(); List rotatedPoly = new ArrayList<>(); for (Point p : polygon) rotatedPoly.add(rotatePoint(p, -angle)); double minY = Double.MAX_VALUE, maxY = -Double.MAX_VALUE; for (Point p : rotatedPoly) { minY = Math.min(minY, p.y); maxY = Math.max(maxY, p.y); } boolean leftToRight = true; for (double y = minY + width/2; y <= maxY - width/2; y += width) { List xIntersections = getXIntersections(rotatedPoly, y); if (xIntersections.size() < 2) continue; Collections.sort(xIntersections); List lineSegmentsInRow = new ArrayList<>(); for (int i = 0; i < xIntersections.size() - 1; i += 2) { Point pS = rotatePoint(new Point(xIntersections.get(i), y), angle); Point pE = rotatePoint(new Point(xIntersections.get(i + 1), y), angle); lineSegmentsInRow.add(new PathSegment(pS, pE, true)); } if (!leftToRight) { Collections.reverse(lineSegmentsInRow); for (PathSegment s : lineSegmentsInRow) { Point temp = s.start; s.start = s.end; s.end = temp; } } for (PathSegment s : lineSegmentsInRow) { if (distance(currentPos, s.start) > 0.01) { segments.add(new PathSegment(currentPos, s.start, false)); } segments.add(s); currentPos = s.end; } leftToRight = !leftToRight; } return segments; } /** * 解析障碍物字符串 * 格式:"(x1,y1;x2,y2)(x1,y1;x2,y2;x3,y3)" */ private static List parseObstacles(String obstaclesStr) { List obstacles = new ArrayList<>(); if (obstaclesStr == null || obstaclesStr.trim().isEmpty()) { return obstacles; } String trimmed = obstaclesStr.trim(); List obstacleStrs = new ArrayList<>(); // 分割每个障碍物(用括号分隔) int start = trimmed.indexOf('('); while (start != -1) { int end = trimmed.indexOf(')', start); if (end == -1) break; String obsStr = trimmed.substring(start + 1, end); obstacleStrs.add(obsStr); start = trimmed.indexOf('(', end); } // 解析每个障碍物 for (String obsStr : obstacleStrs) { List points = new ArrayList<>(); String[] pairs = obsStr.split(";"); for (String pair : pairs) { String[] xy = pair.split(","); if (xy.length == 2) { points.add(new Point( Double.parseDouble(xy[0].trim()), Double.parseDouble(xy[1].trim()) )); } } if (points.size() == 2) { // 圆形障碍物:第一个点为圆心,第二个点为圆上一点 Point center = points.get(0); Point onCircle = points.get(1); double radius = distance(center, onCircle); obstacles.add(new Obstacle(center, radius)); } else if (points.size() > 2) { // 多边形障碍物 obstacles.add(new Obstacle(points)); } } return obstacles; } /** * 外扩障碍物(增加安全边距) */ private static List expandObstacles(List obstacles, double margin) { List expanded = new ArrayList<>(); for (Obstacle obs : obstacles) { if (obs.isCircle()) { // 圆形:半径增加安全边距 expanded.add(new Obstacle(obs.center, obs.radius + margin)); } else { // 多边形:向外偏移(与边界内缩方向相反) List expandedPoints = getOutsetPolygon(obs.points, margin); expanded.add(new Obstacle(expandedPoints)); } } return expanded; } /** * 多边形外扩(与内缩方向相反) */ private static List getOutsetPolygon(List points, double margin) { // 这里使用简化的外扩方法:沿法线向外移动 List outset = new ArrayList<>(); int n = points.size(); for (int i = 0; i < n; i++) { Point pPrev = points.get((i - 1 + n) % n); Point pCurr = points.get(i); Point pNext = points.get((i + 1) % n); // 计算两个边的向量 double v1x = pCurr.x - pPrev.x, v1y = pCurr.y - pPrev.y; double v2x = pNext.x - pCurr.x, v2y = pNext.y - pCurr.y; // 计算法线(确保向外) double nx1 = -v1y, ny1 = v1x; double nx2 = -v2y, ny2 = v2x; // 归一化 double len1 = Math.hypot(nx1, ny1); double len2 = Math.hypot(nx2, ny2); if (len1 > 1e-6) { nx1 /= len1; ny1 /= len1; } if (len2 > 1e-6) { nx2 /= len2; ny2 /= len2; } // 计算平均法线方向 double nx = (nx1 + nx2) / 2; double ny = (ny1 + ny2) / 2; double len = Math.hypot(nx, ny); if (len > 1e-6) { nx /= len; ny /= len; } // 向外移动 outset.add(new Point( pCurr.x + nx * margin, pCurr.y + ny * margin )); } return outset; } /** * 障碍物类 */ private static class Obstacle { List points; // 多边形顶点(对圆形为空) Point center; // 圆心(仅对圆形有效) double radius; // 半径(仅对圆形有效) boolean isCircle; // 多边形构造函数 Obstacle(List points) { this.points = new ArrayList<>(points); this.isCircle = false; ensureCounterClockwise(this.points); // 确保顺时针(对障碍物是内部区域) } // 圆形构造函数 Obstacle(Point center, double radius) { this.center = new Point(center.x, center.y); this.radius = radius; this.isCircle = true; this.points = new ArrayList<>(); } // 判断点是否在障碍物内部 boolean contains(Point p) { if (isCircle) { return distance(p, center) <= radius; } else { return isPointInPolygon(p, points); } } // 获取线段与障碍物的交点 List getIntersections(PathSegment segment) { List intersections = new ArrayList<>(); if (isCircle) { // 线段与圆的交点 double dx = segment.end.x - segment.start.x; double dy = segment.end.y - segment.start.y; double a = dx * dx + dy * dy; double b = 2 * (dx * (segment.start.x - center.x) + dy * (segment.start.y - center.y)); double c = (segment.start.x - center.x) * (segment.start.x - center.x) + (segment.start.y - center.y) * (segment.start.y - center.y) - radius * radius; double discriminant = b * b - 4 * a * c; if (discriminant >= 0) { discriminant = Math.sqrt(discriminant); for (int sign = -1; sign <= 1; sign += 2) { double t = (-b + sign * discriminant) / (2 * a); if (t >= 0 && t <= 1) { intersections.add(new Point( segment.start.x + t * dx, segment.start.y + t * dy )); } } } } else { // 线段与多边形的交点 for (int i = 0; i < points.size(); i++) { Point p1 = points.get(i); Point p2 = points.get((i + 1) % points.size()); Point inter = getLineIntersection( segment.start, segment.end, p1, p2); if (inter != null) { intersections.add(inter); } } } return intersections; } boolean isCircle() { return isCircle; } } /** * 判断点是否在多边形内部(射线法) */ private static boolean isPointInPolygon(Point p, List polygon) { boolean inside = false; for (int i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) { Point pi = polygon.get(i); Point pj = polygon.get(j); if (((pi.y > p.y) != (pj.y > p.y)) && (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x)) { inside = !inside; } } return inside; } /** * 计算两条线段的交点 */ private static Point getLineIntersection(Point p1, Point p2, Point p3, Point p4) { double denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x); if (Math.abs(denom) < 1e-6) return null; // 平行 double t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / denom; double u = -((p1.x - p2.x) * (p1.y - p3.y) - (p1.y - p2.y) * (p1.x - p3.x)) / denom; if (t >= 0 && t <= 1 && u >= 0 && u <= 1) { return new Point( p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y) ); } return null; } /** * 计算两点距离 */ private static double distance(Point p1, Point p2) { return Math.hypot(p1.x - p2.x, p1.y - p2.y); } // ============ 以下是从A代码复用的方法 ============ private static Point getFirstScanPoint(List polygon, double width, double angle) { List 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 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 alignBoundaryStart(List 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 aligned = new ArrayList<>(); for (int i = 0; i < boundary.size(); i++) { aligned.add(boundary.get((bestIdx + i) % boundary.size())); } return aligned; } private static List getXIntersections(List rotatedPoly, double y) { List xIntersections = new ArrayList<>(); for (int i = 0; i < rotatedPoly.size(); i++) { Point p1 = rotatedPoly.get(i); Point p2 = rotatedPoly.get((i + 1) % rotatedPoly.size()); if ((p1.y <= y && p2.y > y) || (p2.y <= y && p1.y > y)) { double x = p1.x + (y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y); xIntersections.add(x); } } return xIntersections; } private static double findOptimalAngle(List polygon) { double bestAngle = 0; double minHeight = Double.MAX_VALUE; for (int i = 0; i < polygon.size(); i++) { Point p1 = polygon.get(i), p2 = polygon.get((i + 1) % polygon.size()); double angle = Math.atan2(p2.y - p1.y, p2.x - p1.x); double h = calculateHeightAtAngle(polygon, angle); if (h < minHeight) { minHeight = h; bestAngle = angle; } } return bestAngle; } private static double calculateHeightAtAngle(List poly, double angle) { double minY = Double.MAX_VALUE, maxY = -Double.MAX_VALUE; for (Point p : poly) { Point rp = rotatePoint(p, -angle); minY = Math.min(minY, rp.y); maxY = Math.max(maxY, rp.y); } return maxY - minY; } private static List getInsetPolygon(List points, double margin) { List result = new ArrayList<>(); int n = points.size(); for (int i = 0; i < n; i++) { Point pPrev = points.get((i - 1 + n) % n); Point pCurr = points.get(i); Point pNext = points.get((i + 1) % n); double d1x = pCurr.x - pPrev.x, d1y = pCurr.y - pPrev.y; double l1 = Math.hypot(d1x, d1y); double d2x = pNext.x - pCurr.x, d2y = pNext.y - pCurr.y; double l2 = Math.hypot(d2x, d2y); if (l1 < 1e-6 || l2 < 1e-6) continue; double n1x = -d1y / l1, n1y = d1x / l1; double n2x = -d2y / l2, n2y = d2x / l2; double bisectorX = n1x + n2x, bisectorY = n1y + n2y; double bLen = Math.hypot(bisectorX, bisectorY); if (bLen < 1e-6) { bisectorX = n1x; bisectorY = n1y; } else { bisectorX /= bLen; bisectorY /= bLen; } double cosHalfAngle = n1x * bisectorX + n1y * bisectorY; double dist = margin / Math.max(cosHalfAngle, 0.1); dist = Math.min(dist, margin * 5); result.add(new Point(pCurr.x + bisectorX * dist, pCurr.y + bisectorY * dist)); } return result; } private static Point rotatePoint(Point p, double angle) { double cos = Math.cos(angle), sin = Math.sin(angle); return new Point(p.x * cos - p.y * sin, p.x * sin + p.y * cos); } private static void ensureCounterClockwise(List points) { double sum = 0; for (int i = 0; i < points.size(); i++) { Point p1 = points.get(i), p2 = points.get((i + 1) % points.size()); sum += (p2.x - p1.x) * (p2.y + p1.y); } if (sum > 0) Collections.reverse(points); } private static List parseCoordinates(String coordinates) { List points = new ArrayList<>(); String[] pairs = coordinates.split(";"); for (String pair : pairs) { String[] xy = pair.split(","); if (xy.length == 2) points.add(new Point(Double.parseDouble(xy[0]), Double.parseDouble(xy[1]))); } if (points.size() > 1 && points.get(0).equals(points.get(points.size()-1))) points.remove(points.size()-1); return points; } /** * 打印输出路径坐标到控制台 */ private static void printPathCoordinates(List path) { if (path == null || path.isEmpty()) { System.out.println("路径为空"); return; } System.out.println("========== 路径坐标输出 =========="); System.out.println("总路径段数: " + path.size()); System.out.println(); System.out.println("路径坐标序列 (格式: x,y;x,y;...):"); StringBuilder sb = new StringBuilder(); for (int i = 0; i < path.size(); i++) { PathSegment segment = path.get(i); if (i == 0) { // 第一个段的起点 sb.append(String.format("%.2f,%.2f", segment.start.x, segment.start.y)); } // 每个段的终点 sb.append(";"); sb.append(String.format("%.2f,%.2f", segment.end.x, segment.end.y)); } System.out.println(sb.toString()); System.out.println(); System.out.println("详细路径信息:"); for (int i = 0; i < path.size(); i++) { PathSegment segment = path.get(i); String type = segment.isMowing ? "割草" : "空走"; System.out.println(String.format("段 %d [%s]: (%.2f,%.2f) -> (%.2f,%.2f)", i + 1, type, segment.start.x, segment.start.y, segment.end.x, segment.end.y)); } System.out.println("=================================="); } public static class Point { public double x, y; public Point(double x, double y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return Math.abs(x - p.x) < 1e-4 && Math.abs(y - p.y) < 1e-4; } } public static class PathSegment { public Point start, end; public boolean isMowing; public PathSegment(Point s, Point e, boolean m) { this.start = s; this.end = e; this.isMowing = m; } } }