| | |
| | | # 割草机地块障碍物配置文件 |
| | | # 生成时间:2025-12-27T22:24:22.132171500 |
| | | # 生成时间:2025-12-27T22:43:36.141786300 |
| | | # 坐标系:WGS84(度分格式) |
| | | |
| | | # ============ 地块基准站配置 ============ |
| | |
| | | #Dikuai Properties |
| | | #Sat Dec 27 22:24:22 GMT+08:00 2025 |
| | | #Sat Dec 27 22:43:36 GMT+08:00 2025 |
| | | LAND1.intelligentSceneAnalysis=-1 |
| | | LAND1.mowingSafetyDistance=0.53 |
| | | LAND1.landArea=577.12 |
| | |
| | | LAND1.returnPathRawCoordinates=-1 |
| | | LAND1.boundaryOriginalXY=-1 |
| | | LAND1.mowingWidth=1.00 |
| | | LAND1.plannedPath=73.87,49.87;50.78,50.04;41.97,22.44;49.61,19.99;51.85,34.26;66.29,36.70;66.29,21.40;78.09,24.75;73.87,49.87;73.96,49.37;50.62,49.54;50.30,48.54;74.12,48.37;74.29,47.37;49.98,47.54;49.66,46.55;74.46,46.37;74.63,45.37;49.34,45.55;49.02,44.55;74.80,44.37;74.96,43.37;48.71,43.55;48.39,42.55;75.13,42.36;75.30,41.36;48.07,41.56;47.75,40.56;75.47,40.36;75.64,39.36;47.43,39.56;47.11,38.56;75.80,38.36;75.97,37.36;46.79,37.57;46.48,36.57;64.75,36.44;66.29,36.43;76.14,36.36;76.31,35.36;66.29,35.43;59.07,35.48;46.16,35.57;45.84,34.57;53.38,34.52;66.29,34.43;76.48,34.35;76.64,33.35;66.29,33.43;51.73,33.53;45.52,33.57;45.20,32.58;51.57,32.53;66.29,32.43;76.81,32.35;76.98,31.35;66.29,31.43;51.42,31.53;44.88,31.58;44.56,30.58;51.26,30.53;66.29,30.43;77.15,30.35;77.32,29.35;66.29,29.43;51.11,29.53;44.25,29.58;43.93,28.59;50.95,28.54;66.29,28.43;77.48,28.35;77.65,27.35;66.29,27.43;50.79,27.54;43.61,27.59;43.29,26.59;50.64,26.54;66.29,26.43;77.82,26.34;77.99,25.34;66.29,25.43;50.48,25.54;42.97,25.59;42.65,24.59;50.32,24.54;66.29,24.43;76.69,24.35;73.25,23.38;66.29,23.43;50.17,23.54;42.33,23.60;42.02,22.60;50.01,22.54;66.29,22.43;69.81,22.40;66.38,21.43;66.29,21.43;49.85,21.54;44.66,21.58;47.85,20.56;49.70,20.54 |
| | | LAND1.updateTime=2025-12-27 22\:24\:22 |
| | | LAND1.plannedPath=73.87,49.87;50.78,50.04;41.97,22.44;49.61,19.99;51.85,34.26;66.29,36.70;66.29,21.40;78.09,24.75;73.87,49.87;73.96,49.37;50.62,49.54;50.30,48.54;74.12,48.37;74.29,47.37;49.98,47.54;49.66,46.55;74.46,46.37;74.63,45.37;49.34,45.55;49.02,44.55;74.80,44.37;74.96,43.37;48.71,43.55;48.39,42.55;75.13,42.36;75.30,41.36;48.07,41.56;47.75,40.56;75.47,40.36;75.64,39.36;47.43,39.56;47.11,38.56;75.80,38.36;75.97,37.36;46.79,37.57;66.29,36.43;76.14,36.36;76.31,35.36;66.29,35.43;66.29,34.43;76.48,34.35;76.64,33.35;66.29,33.43;66.29,32.43;76.81,32.35;76.98,31.35;66.29,31.43;66.29,30.43;77.15,30.35;77.32,29.35;66.29,29.43;66.29,28.43;77.48,28.35;77.65,27.35;66.29,27.43;66.29,26.43;77.82,26.34;77.99,25.34;66.29,25.43;66.29,24.43;76.69,24.35;73.25,23.38;66.29,23.43;66.29,22.43;69.81,22.40;66.38,21.43;66.29,21.43;47.85,20.56;49.70,20.54;46.48,36.57;64.75,36.44;59.07,35.48;46.16,35.57;45.84,34.57;53.38,34.52;51.73,33.53;45.52,33.57;45.20,32.58;51.57,32.53;51.42,31.53;44.88,31.58;44.56,30.58;51.26,30.53;51.11,29.53;44.25,29.58;43.93,28.59;50.95,28.54;50.79,27.54;43.61,27.59;43.29,26.59;50.64,26.54;50.48,25.54;42.97,25.59;42.65,24.59;50.32,24.54;50.17,23.54;42.33,23.60;42.02,22.60;50.01,22.54;49.85,21.54;44.66,21.58 |
| | | LAND1.updateTime=2025-12-27 22\:43\:36 |
| | | LAND1.baseStationCoordinates=3949.89151752,N,11616.79267501,E |
| | | LAND1.boundaryPointInterval=-1 |
| | | LAND1.createTime=2025-12-23 17\:08\:09 |
| | |
| | | #Mower Configuration Properties - Updated |
| | | #Sat Dec 27 22:24:35 GMT+08:00 2025 |
| | | #Sat Dec 27 22:43:28 GMT+08:00 2025 |
| | | appVersion=-1 |
| | | simCardNumber=-1 |
| | | currentWorkLandNumber=LAND1 |
| | |
| | | boundaryLengthVisible=false |
| | | idleTrailDurationSeconds=60 |
| | | handheldMarkerId=1872 |
| | | viewCenterX=-60.00 |
| | | viewCenterY=-34.94 |
| | | viewCenterX=-66.17 |
| | | viewCenterY=-34.20 |
| | | manualBoundaryDrawingMode=false |
| | | mowerId=6258 |
| | | serialPortName=COM15 |
| | | serialAutoConnect=true |
| | | mapScale=12.39 |
| | | mapScale=10.32 |
| | | measurementModeEnabled=false |
| | | firmwareVersion=-1 |
| | | cuttingWidth=200 |
| | |
| | | } |
| | | |
| | | private static List<PathSegment> generateGlobalScanPath(List<Point> polygon, double width, double angle, Point currentPos) { |
| | | // 先尝试将凹陷处视为两个独立区域,分两次扫描,避免跨区直线连接 |
| | | List<PathSegment> all = new ArrayList<>(); |
| | | // 第一次扫描:优先处理左侧区域(groupIndex=0) |
| | | List<PathSegment> leftScan = generateScanPathForSide(polygon, width, angle, currentPos, 0); |
| | | all.addAll(leftScan); |
| | | Point posAfterLeft = leftScan.isEmpty() ? currentPos : leftScan.get(leftScan.size() - 1).end; |
| | | // 第二次扫描:处理右侧区域(groupIndex=1),从左侧结束点沿边界到右侧首段 |
| | | List<PathSegment> rightScan = generateScanPathForSide(polygon, width, angle, posAfterLeft, 1); |
| | | all.addAll(rightScan); |
| | | return all; |
| | | } |
| | | |
| | | // 仅扫描指定侧(同一条扫描线的第 groupIndex 段),用于将“耳朵”视为独立区域 |
| | | private static List<PathSegment> generateScanPathForSide(List<Point> polygon, double width, double angle, Point currentPos, int sideIndex) { |
| | | List<PathSegment> segments = new ArrayList<>(); |
| | | List<Point> rotatedPoly = new ArrayList<>(); |
| | | for (Point p : polygon) rotatedPoly.add(rotatePoint(p, -angle)); |
| | |
| | | } |
| | | |
| | | boolean leftToRight = true; |
| | | // 步长 y 从最小到最大扫描 |
| | | boolean firstSegmentConnected = false; |
| | | |
| | | for (double y = minY + width/2; y <= maxY - width/2; y += width) { |
| | | List<Double> xIntersections = getXIntersections(rotatedPoly, y); |
| | | if (xIntersections.size() < 2) continue; |
| | | Collections.sort(xIntersections); |
| | | |
| | | // 处理凹多边形:每两个点组成一个有效作业段 |
| | | // 构建本行的作业段(左到右)和组索引 |
| | | List<PathSegment> lineSegmentsInRow = new ArrayList<>(); |
| | | for (int i = 0; i < xIntersections.size() - 1; i += 2) { |
| | | List<Integer> groupIndices = new ArrayList<>(); |
| | | for (int i = 0, g = 0; i < xIntersections.size() - 1; i += 2, g++) { |
| | | 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)); |
| | | groupIndices.add(g); |
| | | } |
| | | |
| | | // 根据当前S型方向排序作业段 |
| | | if (!leftToRight) { |
| | | Collections.reverse(lineSegmentsInRow); |
| | | Collections.reverse(groupIndices); |
| | | for (PathSegment s : lineSegmentsInRow) { |
| | | Point temp = s.start; s.start = s.end; s.end = temp; |
| | | } |
| | | } |
| | | |
| | | // 将作业段连接到总路径 |
| | | for (PathSegment s : lineSegmentsInRow) { |
| | | int idxInRow = groupIndices.indexOf(sideIndex); |
| | | if (idxInRow == -1) { |
| | | // 本行不包含该侧的作业段,跳过 |
| | | leftToRight = !leftToRight; |
| | | continue; |
| | | } |
| | | |
| | | PathSegment s = lineSegmentsInRow.get(idxInRow); |
| | | // 首次连接或跨区连接均强制沿边界,避免穿越凹陷区 |
| | | if (Math.hypot(currentPos.x - s.start.x, currentPos.y - s.start.y) > 0.01) { |
| | | // 如果间距大于1cm,添加空走路径 |
| | | addSafeConnection(segments, currentPos, s.start, polygon); |
| | | addBoundaryConnection(segments, currentPos, s.start, polygon); |
| | | firstSegmentConnected = true; |
| | | } |
| | | segments.add(s); |
| | | currentPos = s.end; |
| | | } |
| | | leftToRight = !leftToRight; |
| | | } |
| | | return segments; |
| | |
| | | } |
| | | } |
| | | |
| | | // 强制沿边界绕行的连接(不做直线安全判断),用来在同一扫描行的多个作业段之间跳转 |
| | | private static void addBoundaryConnection(List<PathSegment> segments, Point start, Point end, List<Point> polygon) { |
| | | List<Point> path = getBoundaryPath(start, end, polygon); |
| | | for (int i = 0; i < path.size() - 1; i++) { |
| | | segments.add(new PathSegment(path.get(i), path.get(i+1), false)); |
| | | } |
| | | } |
| | | |
| | | private static boolean isSegmentSafe(Point p1, Point p2, List<Point> polygon) { |
| | | Point mid = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); |
| | | if (!isPointInPolygon(mid, polygon)) return false; |