张世豪
4 天以前 dc9dce0555beb85d1262893fd5d56747d6a83855
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
package lujing;
 
import org.locationtech.jts.algorithm.Angle;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.operation.distance.DistanceOp;
import org.locationtech.jts.operation.union.CascadedPolygonUnion;
 
import java.util.*;
import java.util.stream.Collectors;
 
/**
 * 障碍物路径规划器
 * * 优化方案:
 * 1. 预规划:先不考虑障碍物,规划出覆盖全图的平行线路径(弓字形)。
 * 2. 几何切割:根据障碍物的外扩(安全距离)几何体,将路径切断,移除落在障碍物内的部分。
 * 3. 区域重组:将剩余的路径段按连通性聚类为若干个“连续区域”,每个区域内部通过弓字形连接。
 * 4. 全局连接:使用避障算法(A*可视图)将这些孤立的区域串联起来。
 * * * 修复问题:
 * 1. 修复路径穿越障碍物的问题(通过更严格的 Interior Intersection 检查)。
 * 2. 修复路径超出地块边界的问题(通过加入 Boundary Covers 约束)。
 * 3. [本次修复] 修复 IntersectionMatrix 类型报错,使用正确的矩阵单元格检查替代不存在的方法。
 * * * 二次优化(针对用户反馈):
 * 1. 引入 Tolerance Buffer (内缩) 用于相交检测,解决“接触即相交”导致的合法边缘路径被误判问题。
 * 2. 增强 isLineSafe 的边界约束,确保路径严格在 Boundary 内部。
 * 3. 优化 findSafePath,增加点位校验与吸附(Snap),防止因浮点误差导致的寻路失败而回退到直线。
 */
public class ObstaclePathPlanner {
    private final List<Coordinate> polygon;           // 割草区域边界点集
    private final double width;                      // 割草宽度
    private final String mode;                       // 模式
    private final List<List<Coordinate>> obstacles;   // 障碍物列表
    private final double safetyDistance;             // 安全距离
    private final GeometryFactory gf = new GeometryFactory();
    
    // 保存地块的几何形状,用于边界约束检查
    private Geometry boundaryGeom;
    // 用于检测的障碍物几何体(可能经过微调)
    private Geometry checkObstacleGeom;
 
    public ObstaclePathPlanner(List<Coordinate> polygon, double width, String mode,
                               List<List<Coordinate>> obstacles, double safetyDistance) {
        this.polygon = polygon;
        this.width = width;
        this.mode = mode;
        this.obstacles = obstacles != null ? obstacles : new ArrayList<>();
        this.safetyDistance = safetyDistance;
        
        initBoundaryGeom();
    }
    
    /**
     * 初始化边界几何体,用于后续的约束检查
     */
    private void initBoundaryGeom() {
        if (polygon == null || polygon.size() < 3) {
            this.boundaryGeom = gf.createPolygon();
            return;
        }
        List<Coordinate> closed = new ArrayList<>(polygon);
        if (!closed.get(0).equals2D(closed.get(closed.size() - 1))) {
            closed.add(new Coordinate(closed.get(0)));
        }
        LinearRing ring = gf.createLinearRing(closed.toArray(new Coordinate[0]));
        Polygon poly = gf.createPolygon(ring);
        // 确保几何有效性
        this.boundaryGeom = poly.isValid() ? poly : poly.buffer(0);
    }
 
    /**
     * 生成路径的主入口
     */
    public List<Lunjingguihua.PathSegment> generate() {
        // 1. 生成初始的完整平行线路径(忽略障碍物)
        List<Lunjingguihua.PathSegment> fullPath = generateFullPathWithoutObstacles();
        if (fullPath.isEmpty()) return fullPath;
 
        // 2. 构建障碍物的外扩安全区域 (Buffer)
        Geometry obstacleBuffer = createObstacleBuffer();
        
        // 初始化用于检测的几何体(内缩一点点,允许路径贴边)
        if (!obstacleBuffer.isEmpty()) {
            this.checkObstacleGeom = obstacleBuffer.buffer(-0.01); // 内缩1cm,容忍浮点误差
        } else {
            this.checkObstacleGeom = obstacleBuffer;
        }
 
        if (obstacleBuffer.isEmpty()) return fullPath;
 
        // 3. 切割路径:移除与障碍物重叠的部分
        List<Lunjingguihua.PathSegment> clippedSegments = clipPathWithObstacles(fullPath, obstacleBuffer);
        if (clippedSegments.isEmpty()) return new ArrayList<>();
 
        // 4. 重新规划:将碎片化的线段重组成连续的弓字形路径,并进行避障连接
        List<Lunjingguihua.PathSegment> finalPath = reorganizeAndConnectPath(clippedSegments, obstacleBuffer);
 
        // 5. 后处理(标记起终点等)
        postProcessPath(finalPath);
 
        return finalPath;
    }
 
    /**
     * 步骤1:调用核心库生成无障碍的平行线路径
     */
    private List<Lunjingguihua.PathSegment> generateFullPathWithoutObstacles() {
        Lunjingguihua.PlannerCore tempPlanner = new Lunjingguihua.PlannerCore(
                polygon, width, mode, new ArrayList<>());
        return tempPlanner.generateParallelPath();
    }
 
    /**
     * 步骤2:创建所有障碍物的并集 + 安全距离外扩
     */
    private Geometry createObstacleBuffer() {
        if (obstacles.isEmpty()) return gf.createPolygon();
 
        List<Geometry> geoms = new ArrayList<>();
        for (List<Coordinate> obs : obstacles) {
            if (obs == null || obs.size() < 3) continue;
            List<Coordinate> closed = new ArrayList<>(obs);
            if (!closed.get(0).equals2D(closed.get(closed.size() - 1))) {
                closed.add(new Coordinate(closed.get(0)));
            }
            LinearRing ring = gf.createLinearRing(closed.toArray(new Coordinate[0]));
            Polygon poly = gf.createPolygon(ring);
            if (!poly.isValid()) poly = (Polygon) poly.buffer(0);
            geoms.add(poly);
        }
 
        if (geoms.isEmpty()) return gf.createPolygon();
 
        Geometry union = CascadedPolygonUnion.union(geoms);
        // 对合并后的障碍物进行外扩(Buffer)
        Geometry buffered = union.buffer(safetyDistance, 8); 
        return buffered.isValid() ? buffered : buffered.buffer(0);
    }
 
    /**
     * 步骤3:用障碍物几何体切割路径
     */
    private List<Lunjingguihua.PathSegment> clipPathWithObstacles(List<Lunjingguihua.PathSegment> fullPath, Geometry obstacleBuffer) {
        List<Lunjingguihua.PathSegment> validSegments = new ArrayList<>();
 
        for (Lunjingguihua.PathSegment seg : fullPath) {
            if (!seg.isMowing) continue;
 
            LineString line = gf.createLineString(new Coordinate[]{seg.start, seg.end});
            Geometry diff;
            try {
                diff = line.difference(obstacleBuffer);
            } catch (Exception e) {
                continue; 
            }
 
            if (diff.isEmpty()) continue;
 
            for (int i = 0; i < diff.getNumGeometries(); i++) {
                Geometry g = diff.getGeometryN(i);
                if (g instanceof LineString) {
                    Coordinate[] coords = g.getCoordinates();
                    for (int k = 0; k < coords.length - 1; k++) {
                        Coordinate p1 = coords[k];
                        Coordinate p2 = coords[k+1];
                        if (p1.distance(p2) > 0.1) {
                            validSegments.add(new Lunjingguihua.PathSegment(p1, p2, true));
                        }
                    }
                }
            }
        }
        return validSegments;
    }
 
    /**
     * 步骤4:核心重组逻辑
     */
    private List<Lunjingguihua.PathSegment> reorganizeAndConnectPath(List<Lunjingguihua.PathSegment> segments, Geometry obstacleBuffer) {
        if (segments.isEmpty()) return new ArrayList<>();
 
        // --- A. 分析扫描线方向 ---
        Lunjingguihua.PathSegment firstSeg = segments.get(0);
        double angle = Math.atan2(firstSeg.end.y - firstSeg.start.y, firstSeg.end.x - firstSeg.start.x);
        double sin = Math.sin(angle);
        double cos = Math.cos(angle);
 
        // --- B. 给每个线段分配扫描线索引 (Grid Index) ---
        double gridStep = width; 
        
        class IndexedSegment {
            final Lunjingguihua.PathSegment segment;
            final int gridIndex;
            final double projectVal;
            
            IndexedSegment(Lunjingguihua.PathSegment s) {
                this.segment = s;
                double cx = (s.start.x + s.end.x) / 2;
                double cy = (s.start.y + s.end.y) / 2;
                double perpDist = -cx * sin + cy * cos;
                this.gridIndex = (int) Math.floor(perpDist / gridStep);
                this.projectVal = cx * cos + cy * sin;
            }
        }
 
        List<IndexedSegment> indexedSegments = segments.stream()
                .map(IndexedSegment::new)
                .sorted(Comparator.comparingInt((IndexedSegment s) -> s.gridIndex)
                        .thenComparingDouble(s -> s.projectVal))
                .collect(Collectors.toList());
 
        // --- C. 构建“区域链” (Zones) ---
        List<List<Lunjingguihua.PathSegment>> zones = new ArrayList<>();
        Set<IndexedSegment> visited = new HashSet<>();
        
        while (visited.size() < indexedSegments.size()) {
            IndexedSegment startNode = null;
            for (IndexedSegment is : indexedSegments) {
                if (!visited.contains(is)) {
                    startNode = is;
                    break;
                }
            }
            if (startNode == null) break;
 
            List<Lunjingguihua.PathSegment> zone = new ArrayList<>();
            zone.add(startNode.segment);
            visited.add(startNode);
            
            IndexedSegment current = startNode;
            boolean lookingForNext = true;
 
            while (lookingForNext) {
                IndexedSegment bestNext = null;
                double minDistance = Double.MAX_VALUE;
 
                // 搜索最佳后续线段
                for (int i = 0; i < indexedSegments.size(); i++) {
                    IndexedSegment candidate = indexedSegments.get(i);
                    if (visited.contains(candidate)) continue;
                    
                    if (Math.abs(candidate.gridIndex - current.gridIndex) > 1) continue;
                    
                    double d = current.segment.end.distance(candidate.segment.start);
                    
                    if (d > width * 3.0) continue; 
                    
                    if (d < minDistance) {
                        // 使用 checkObstacleGeom 进行检测
                        if (isLineSafe(current.segment.end, candidate.segment.start)) {
                            minDistance = d;
                            bestNext = candidate;
                        }
                    }
                }
 
                if (bestNext != null) {
                    zone.add(bestNext.segment);
                    visited.add(bestNext);
                    current = bestNext;
                } else {
                    lookingForNext = false; 
                }
            }
            zones.add(zone);
        }
 
        // --- D. 连接所有 Zones ---
        List<Lunjingguihua.PathSegment> resultPath = new ArrayList<>();
        
        List<List<Lunjingguihua.PathSegment>> remainingZones = new ArrayList<>(zones);
        List<Lunjingguihua.PathSegment> currentProcessingZone = remainingZones.remove(0);
        
        addZoneToPath(resultPath, currentProcessingZone, obstacleBuffer, false);
 
        while (!remainingZones.isEmpty()) {
            Lunjingguihua.PathSegment lastSeg = resultPath.get(resultPath.size() - 1);
            Coordinate currentPos = lastSeg.end;
 
            int bestZoneIdx = -1;
            double minDist = Double.MAX_VALUE;
 
            for (int i = 0; i < remainingZones.size(); i++) {
                List<Lunjingguihua.PathSegment> z = remainingZones.get(i);
                if (z.isEmpty()) continue;
                double d = currentPos.distance(z.get(0).start);
                if (d < minDist) {
                    minDist = d;
                    bestZoneIdx = i;
                }
            }
 
            if (bestZoneIdx != -1) {
                List<Lunjingguihua.PathSegment> nextZone = remainingZones.remove(bestZoneIdx);
                addZoneToPath(resultPath, nextZone, obstacleBuffer, true);
            } else {
                break;
            }
        }
 
        return resultPath;
    }
 
    /**
     * 将一个 Zone 添加到结果路径中
     */
    private void addZoneToPath(List<Lunjingguihua.PathSegment> path, 
                               List<Lunjingguihua.PathSegment> zone, 
                               Geometry obstacleBuffer,
                               boolean needConnectToZoneStart) {
        if (zone.isEmpty()) return;
 
        // 1. 连接到 Zone 的起点
        if (needConnectToZoneStart && !path.isEmpty()) {
            Coordinate from = path.get(path.size() - 1).end;
            Coordinate to = zone.get(0).start;
            List<Coordinate> travel = findSafePath(from, to, obstacleBuffer);
            if (travel.size() > 1) {
                for (int i = 0; i < travel.size() - 1; i++) {
                    path.add(new Lunjingguihua.PathSegment(travel.get(i), travel.get(i+1), false));
                }
            }
        }
 
        // 2. 处理 Zone 内部
        for (int i = 0; i < zone.size(); i++) {
            Lunjingguihua.PathSegment seg = zone.get(i);
            
            if (i > 0) {
                Coordinate prevEnd = zone.get(i-1).end;
                Coordinate currStart = seg.start;
                if (!prevEnd.equals2D(currStart)) {
                    if (isLineSafe(prevEnd, currStart)) {
                         path.add(new Lunjingguihua.PathSegment(prevEnd, currStart, false));
                    } else {
                        List<Coordinate> detour = findSafePath(prevEnd, currStart, obstacleBuffer);
                        if (detour.size() > 1) {
                            for (int k = 0; k < detour.size() - 1; k++) {
                                path.add(new Lunjingguihua.PathSegment(detour.get(k), detour.get(k+1), false));
                            }
                        }
                    }
                }
            }
            path.add(seg);
        }
    }
 
    /**
     * 检查两点连线是否安全
     * 修改点:
     * 1. 严格检查 Boundary (Covers)
     * 2. 使用 checkObstacleGeom (内缩版) 检查障碍物,允许贴边
     * 3. [Fix] 使用 matrix.get() 代替不存在的 isIntersects(int)
     */
    private boolean isLineSafe(Coordinate p1, Coordinate p2) {
        if (p1.equals2D(p2)) return true;
        LineString line = gf.createLineString(new Coordinate[]{p1, p2});
        
        // 1. 边界约束:线段必须完全在地块内部
        if (boundaryGeom != null && !boundaryGeom.covers(line)) {
            return false;
        }
 
        // 2. 避障约束:使用内缩后的 buffer 检查
        // 如果 checkObstacleGeom 为空(无障碍),则安全
        if (checkObstacleGeom == null || checkObstacleGeom.isEmpty()) return true;
 
        IntersectionMatrix matrix = line.relate(checkObstacleGeom);
        
        // 我们要检查:线段的任何部分(Interior)是否穿过障碍物内部(Interior)
        // 或者 线段的端点(Boundary)是否在障碍物内部(Interior)
        // 如果两者都是 Dimension.FALSE (-1),则说明没有穿过内部
        
        boolean interiorIntersects = matrix.get(Location.INTERIOR, Location.INTERIOR) != Dimension.FALSE;
        boolean boundaryIntersects = matrix.get(Location.BOUNDARY, Location.INTERIOR) != Dimension.FALSE;
 
        return !interiorIntersects && !boundaryIntersects;
    }
 
    /**
     * 寻找两点间的安全路径
     * 修改点:
     * 1. 增加点位校验与吸附(Snap),确保起点终点有效
     * 2. 移除直线强制回退,若寻路失败则返回空(或保留起点),避免穿墙
     */
    private List<Coordinate> findSafePath(Coordinate start, Coordinate end, Geometry obstacleBuffer) {
        // 0. 数据清洗:吸附起点终点到合法区域
        Coordinate safeStart = snapPointToValid(start);
        Coordinate safeEnd = snapPointToValid(end);
 
        List<Coordinate> path = new ArrayList<>();
        
        // 1. 尝试直连
        if (isLineSafe(safeStart, safeEnd)) {
            path.add(safeStart);
            path.add(safeEnd);
            return path;
        }
 
        // 2. 构建可视图
        Set<Coordinate> nodes = new HashSet<>();
        nodes.add(safeStart);
        nodes.add(safeEnd);
        
        // 提取障碍物顶点
        addPolygonVertices(obstacleBuffer, nodes);
        // 提取边界顶点(关键:处理凹形边界)
        addPolygonVertices(boundaryGeom, nodes);
 
        List<Coordinate> nodeList = new ArrayList<>(nodes);
        
        // 构建邻接表
        Map<Coordinate, List<Coordinate>> graph = new HashMap<>();
        for (Coordinate c1 : nodeList) {
            for (Coordinate c2 : nodeList) {
                if (c1 == c2) continue;
                if (isLineSafe(c1, c2)) {
                    graph.computeIfAbsent(c1, k -> new ArrayList<>()).add(c2);
                }
            }
        }
 
        // Dijkstra 寻路
        Map<Coordinate, Double> dist = new HashMap<>();
        Map<Coordinate, Coordinate> prev = new HashMap<>();
        PriorityQueue<Coordinate> pq = new PriorityQueue<>(Comparator.comparingDouble(dist::get));
 
        for (Coordinate n : nodeList) dist.put(n, Double.MAX_VALUE);
        dist.put(safeStart, 0.0);
        pq.add(safeStart);
 
        while (!pq.isEmpty()) {
            Coordinate u = pq.poll();
            if (u.equals2D(safeEnd)) break;
            if (dist.get(u) == Double.MAX_VALUE) break;
 
            if (graph.containsKey(u)) {
                for (Coordinate v : graph.get(u)) {
                    double alt = dist.get(u) + u.distance(v);
                    if (alt < dist.get(v)) {
                        dist.put(v, alt);
                        prev.put(v, u);
                        pq.add(v); 
                    }
                }
            }
        }
 
        // 重构路径
        if (prev.containsKey(safeEnd)) {
            LinkedList<Coordinate> p = new LinkedList<>();
            Coordinate curr = safeEnd;
            while (curr != null) {
                p.addFirst(curr);
                curr = prev.get(curr);
            }
            return p;
        }
 
        // 寻路失败,返回空(避免错误的直线穿越)
        return path;
    }
 
    // 辅助:验证并吸附点到合法区域
    private Coordinate snapPointToValid(Coordinate p) {
        Point point = gf.createPoint(p);
        boolean inBoundary = (boundaryGeom == null) || boundaryGeom.covers(point);
        boolean outObstacle = (checkObstacleGeom == null) || !checkObstacleGeom.contains(point); // 使用 contains 而不是 intersects interior,稍微严格点
 
        if (inBoundary && outObstacle) return p;
 
        // 如果点无效,尝试找最近的有效点(边界或障碍物边缘)
        // 这里简化处理:如果在障碍物内,吸附到障碍物边界;如果在边界外,吸附到边界
        // 实际上 JTS DistanceOp.nearestPoints 可以做这个
        
        Geometry target = boundaryGeom;
        if (!outObstacle && checkObstacleGeom != null) {
            // 在障碍物内,优先吸附出障碍物
             Coordinate[] nearest = DistanceOp.nearestPoints(point, checkObstacleGeom.getBoundary());
             return nearest[1];
        }
        
        if (!inBoundary && boundaryGeom != null) {
             Coordinate[] nearest = DistanceOp.nearestPoints(point, boundaryGeom);
             return nearest[1];
        }
        
        return p;
    }
    
    private void addPolygonVertices(Geometry geom, Set<Coordinate> nodes) {
        if (geom == null) return;
        if (geom instanceof Polygon) {
            Collections.addAll(nodes, ((Polygon) geom).getExteriorRing().getCoordinates());
            for(int i=0; i<((Polygon)geom).getNumInteriorRing(); i++) {
                Collections.addAll(nodes, ((Polygon)geom).getInteriorRingN(i).getCoordinates());
            }
        } else if (geom instanceof MultiPolygon) {
            MultiPolygon mp = (MultiPolygon) geom;
            for (int i = 0; i < mp.getNumGeometries(); i++) {
                addPolygonVertices(mp.getGeometryN(i), nodes);
            }
        } else if (geom instanceof GeometryCollection) {
             GeometryCollection gc = (GeometryCollection) geom;
             for (int i = 0; i < gc.getNumGeometries(); i++) {
                 addPolygonVertices(gc.getGeometryN(i), nodes);
             }
        }
    }
 
    /**
     * 后处理:移除短线段,标记起终点
     */
    private void postProcessPath(List<Lunjingguihua.PathSegment> path) {
        if (path.isEmpty()) return;
        path.removeIf(seg -> seg.start.distance(seg.end) < 0.05);
        for (Lunjingguihua.PathSegment seg : path) {
            seg.isStartPoint = false;
            seg.isEndPoint = false;
        }
        boolean startFound = false;
        for (Lunjingguihua.PathSegment seg : path) {
            if (seg.isMowing) {
                seg.setAsStartPoint();
                startFound = true;
                break;
            }
        }
        for (int i = path.size() - 1; i >= 0; i--) {
            Lunjingguihua.PathSegment seg = path.get(i);
            if (seg.isMowing) {
                seg.setAsEndPoint();
                break;
            }
        }
    }
}