张世豪
16 小时以前 1175f5fbe8fd832943880bfc37c0e2a451a0688a
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
package gecaoji; // 包声明
import java.awt.BasicStroke; // 引入基础描边类
import java.awt.Color; // 引入颜色类
import java.awt.Graphics2D; // 引入2D图形上下文
import java.awt.Stroke; // 引入描边接口
import java.awt.geom.Path2D; // 引入路径绘制类
import java.awt.geom.Point2D; // 引入二维点类
import java.util.ArrayList; // 引入动态数组
import java.util.List; // 引入列表接口
import org.locationtech.jts.geom.Coordinate; // 引入坐标类
import org.locationtech.jts.geom.GeometryFactory; // 引入几何工厂类
import org.locationtech.jts.geom.Polygon; // 引入多边形类
 
/** // 文档注释开头
 * Helper class to parse and render planned mowing paths. // 辅助类说明
 */ // 文档注释结尾
public final class lujingdraw { // 类定义,防止继承
    // 马尔斯绿色 (RGB: 102, 205, 170) - 用于内缩边界(围边)
    private static final Color INNER_BOUNDARY_COLOR = new Color(102, 205, 170);
    // 提香红透明度70% (RGB: 210, 4, 45, alpha: 178) - 用于割草作业路径
    private static final Color MOWING_PATH_COLOR = new Color(210, 4, 45, 178);
    // 蓝色 - 用于非作业移动路径
    private static final Color TRAVEL_PATH_COLOR = new Color(0, 0, 255);
    // 虚线样式 - 用于非作业移动路径
    private static final float[] DASH_PATTERN = {5.0f, 5.0f};
    private static final Color START_POINT_COLOR = new Color(0, 0, 0, 220); // 起点箭头颜色
    private static final Color END_POINT_COLOR = new Color(0, 0, 0, 220); // 终点箭头颜色
 
    private lujingdraw() { // 私有构造防止实例化
    } // 空实现
 
    /** // 文档注释开始
     * Parse the planned path string (semicolon-separated "x,y" pairs) into a list of points in meters. // 方法说明
     */ // 文档注释结束
    public static List<Point2D.Double> parsePlannedPath(String plannedPath) { // 解析路径字符串
        List<Point2D.Double> points = new ArrayList<>(); // 存放解析后的点集
        if (plannedPath == null) { // 判空
            return points; // 返回空集合
        } // if结束
 
        String normalized = plannedPath.trim(); // 去除首尾空格
        if (normalized.isEmpty() || "-1".equals(normalized)) { // 无效判断
            return points; // 返回空集合
        } // if结束
 
        int commentIndex = normalized.indexOf('#'); // 查找注释符号
        if (commentIndex >= 0) { // 找到注释
            normalized = normalized.substring(0, commentIndex).trim(); // 截取注释前内容
        } // if结束
 
        if (normalized.isEmpty()) { // 再次判空
            return points; // 返回空集合
        } // if结束
 
        String[] segments = normalized.split(";"); // 切分各段
        for (String segment : segments) { // 遍历每段
            if (segment == null) { // 判空
                continue; // 跳过
            } // if结束
            String trimmed = segment.trim(); // 去除空格
            if (trimmed.isEmpty()) { // 空段
                continue; // 跳过
            } // if结束
            String[] parts = trimmed.split(","); // 切分坐标
            if (parts.length < 2) { // 检查长度
                continue; // 跳过
            } // if结束
            try { // 数值解析
                double x = Double.parseDouble(parts[0].trim()); // 解析X坐标
                double y = Double.parseDouble(parts[1].trim()); // 解析Y坐标
                points.add(new Point2D.Double(x, y)); // 加入点集
            } catch (NumberFormatException ignored) { // 捕获格式异常
                // Ignore malformed coordinates // 忽略非法坐标
            } // try结束
        } // for结束
        return points; // 返回结果
    } // 方法结束
 
    /** // 文档注释开始
     * Draw the planned mowing path. // 绘制路径
     */ // 文档注释结束
    public static void drawPlannedPath(Graphics2D g2d, List<Point2D.Double> path, double scale, double arrowScale) { // 绘制主方法
        drawPlannedPath(g2d, path, scale, arrowScale, null, null, null, null, null); // 调用重载方法
    } // 方法结束
    
    /** // 文档注释开始
     * Draw the planned mowing path with segment information. // 绘制路径(带段信息)
     * @param boundaryCoords 地块边界坐标字符串,用于绘制内缩边界
     * @param mowingWidth 割草宽度(米),字符串格式
     * @param safetyDistance 安全距离(米),字符串格式
     * @param obstaclesCoords 障碍物坐标字符串
     * @param mowingPattern 割草模式("parallel"或"spiral")
     */ // 文档注释结束
    public static void drawPlannedPath(Graphics2D g2d, List<Point2D.Double> path, double scale, double arrowScale,
                                      String boundaryCoords, String mowingWidth, String safetyDistance, String obstaclesCoords, String mowingPattern) { // 绘制主方法(重载)
        if (path == null || path.size() < 2) { // 判定点数
            return; // 数据不足直接返回
        } // if结束
 
        Stroke previous = g2d.getStroke(); // 保存原描边
        float strokeWidth = (float) (2.5 / Math.max(0.5, scale)); // 根据缩放计算线宽
        
        // 1. 绘制内缩边界(围边)- 马尔斯绿色
        if (boundaryCoords != null && !boundaryCoords.trim().isEmpty() && !"-1".equals(boundaryCoords.trim())) {
            drawInnerBoundary(g2d, boundaryCoords, safetyDistance, scale);
        }
        
        // 2. 直接绘制路径(不再重新生成)
        Path2D polyline = new Path2D.Double(); // 创建折线
        boolean move = true; // 首段标记
        for (Point2D.Double point : path) { // 遍历点集
            if (move) { // 第一段
                polyline.moveTo(point.x, point.y); // 移动到首点
                move = false; // 更新标记
            } else { // 后续段
                polyline.lineTo(point.x, point.y); // 连线到下一点
            } // if结束
        } // for结束
        
        g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // 设置圆头圆角描边
        g2d.setColor(MOWING_PATH_COLOR); // 设置作业路径颜色(提香红70%透明度)
        g2d.draw(polyline); // 绘制折线
        
        Point2D.Double start = path.get(0); // 起点
        Point2D.Double second = path.get(1); // 第二个点
        Point2D.Double end = path.get(path.size() - 1); // 终点
        Point2D.Double prev = path.get(path.size() - 2); // 倒数第二个点
 
        drawArrowMarker(g2d, start, second, START_POINT_COLOR, scale, arrowScale); // 绘制起点箭头
        drawArrowMarker(g2d, prev, end, END_POINT_COLOR, scale, arrowScale); // 绘制终点箭头
 
        g2d.setStroke(previous); // 恢复原描边
    } // 方法结束
    
    /** // 文档注释开始
     * 绘制内缩边界(围边)- 马尔斯绿色
     */ // 文档注释结束
    private static void drawInnerBoundary(Graphics2D g2d, String boundaryCoords, String safetyDistanceStr, double scale) {
        try {
            List<Coordinate> boundary = parseCoordinates(boundaryCoords);
            if (boundary.size() < 4) {
                return; // 边界点不足
            }
            
            // 解析安全距离
            double safetyDistance = 0.2; // 默认0.2米
            if (safetyDistanceStr != null && !safetyDistanceStr.trim().isEmpty()) {
                try {
                    safetyDistance = Double.parseDouble(safetyDistanceStr.trim());
                } catch (NumberFormatException e) {
                    // 使用默认值
                }
            }
            
            // 创建多边形并内缩
            GeometryFactory gf = new GeometryFactory();
            Polygon poly = gf.createPolygon(gf.createLinearRing(boundary.toArray(new Coordinate[0])));
            if (!poly.isValid()) {
                poly = (Polygon) poly.buffer(0);
            }
            
            // 内缩生成安全边界
            org.locationtech.jts.geom.Geometry innerBoundary = poly.buffer(-safetyDistance);
            if (innerBoundary == null || innerBoundary.isEmpty()) {
                return;
            }
            
            // 绘制内缩边界
            g2d.setColor(INNER_BOUNDARY_COLOR); // 马尔斯绿色
            float strokeWidth = (float) (3.0 / Math.max(0.5, scale));
            g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            
            // 将JTS几何体转换为Path2D并绘制
            Path2D.Double innerPath = geometryToPath2D(innerBoundary);
            if (innerPath != null) {
                g2d.draw(innerPath);
            }
        } catch (Exception e) {
            // 绘制失败时忽略,不影响其他路径绘制
        }
    } // 方法结束
    
    /** // 文档注释开始
     * 将JTS几何体转换为Path2D
     */ // 文档注释结束
    private static Path2D.Double geometryToPath2D(org.locationtech.jts.geom.Geometry geom) {
        if (geom == null) {
            return null;
        }
        Path2D.Double path = new Path2D.Double();
        if (geom instanceof Polygon) {
            Polygon poly = (Polygon) geom;
            addRingToPath(path, poly.getExteriorRing().getCoordinates(), true);
            for (int i = 0; i < poly.getNumInteriorRing(); i++) {
                addRingToPath(path, poly.getInteriorRingN(i).getCoordinates(), true);
            }
        } else if (geom instanceof org.locationtech.jts.geom.MultiPolygon) {
            org.locationtech.jts.geom.MultiPolygon mp = (org.locationtech.jts.geom.MultiPolygon) geom;
            for (int i = 0; i < mp.getNumGeometries(); i++) {
                Polygon poly = (Polygon) mp.getGeometryN(i);
                addRingToPath(path, poly.getExteriorRing().getCoordinates(), true);
                for (int j = 0; j < poly.getNumInteriorRing(); j++) {
                    addRingToPath(path, poly.getInteriorRingN(j).getCoordinates(), true);
                }
            }
        }
        return path;
    } // 方法结束
    
    /** // 文档注释开始
     * 将坐标环添加到Path2D
     */ // 文档注释结束
    private static void addRingToPath(Path2D.Double path, Coordinate[] coords, boolean close) {
        if (coords == null || coords.length < 2) {
            return;
        }
        path.moveTo(coords[0].x, coords[0].y);
        for (int i = 1; i < coords.length; i++) {
            path.lineTo(coords[i].x, coords[i].y);
        }
        if (close) {
            path.closePath();
        }
    } // 方法结束
    
 
    
    /** // 文档注释开始
     * 比较两个坐标是否相同(容差)
     */ // 文档注释结束
    private static boolean equals2D(Coordinate a, Coordinate b) {
        if (a == b) return true;
        if (a == null || b == null) return false;
        return a.distance(b) < 1e-4;
    } // 方法结束
 
    private static void drawArrowMarker(Graphics2D g2d, Point2D.Double from, Point2D.Double to, Color color, double scale, double sizeScale) { // 绘制箭头辅助
        if (from == null || to == null) { // 判空
            return; // 数据不足返回
        } // if结束
        double dx = to.x - from.x; // 计算X增量
        double dy = to.y - from.y; // 计算Y增量
        double length = Math.hypot(dx, dy); // 计算向量长度
        if (length == 0) { // 防止零长度
            return; // 无方向返回
        } // if结束
 
    double arrowLength = Math.max(2.5, 5.5 / Math.max(0.5, scale)); // 计算箭头长度
    double clampedScale = sizeScale > 0 ? sizeScale : 1.0; // 防止非法缩放
    arrowLength *= 0.25 * clampedScale; // 缩小箭头至原来的一半
        double arrowWidth = arrowLength * 0.45; // 计算箭头宽度
 
        double ux = dx / length; // 单位向量X
        double uy = dy / length; // 单位向量Y
 
        double tipX = to.x; // 箭头尖端X
        double tipY = to.y; // 箭头尖端Y
        double baseX = tipX - ux * arrowLength; // 箭头底部X
        double baseY = tipY - uy * arrowLength; // 箭头底部Y
 
        double perpX = -uy; // 垂直向量X
        double perpY = ux; // 垂直向量Y
 
        double leftX = baseX + perpX * arrowWidth * 0.5; // 左侧点X
        double leftY = baseY + perpY * arrowWidth * 0.5; // 左侧点Y
        double rightX = baseX - perpX * arrowWidth * 0.5; // 右侧点X
        double rightY = baseY - perpY * arrowWidth * 0.5; // 右侧点Y
 
        Path2D arrow = new Path2D.Double(); // 创建箭头路径
        arrow.moveTo(tipX, tipY); // 起点移动到尖端
        arrow.lineTo(leftX, leftY); // 连到左侧
        arrow.lineTo(rightX, rightY); // 连到右侧
        arrow.closePath(); // 闭合形成三角形
 
        g2d.setColor(color); // 设置颜色
        g2d.fill(arrow); // 填充箭头
    } // 方法结束
 
    /**
     * 解析坐标字符串
     */
    private static List<Coordinate> parseCoordinates(String s) {
        List<Coordinate> list = new ArrayList<>();
        if (s == null || s.trim().isEmpty()) return list;
        // 增强正则:处理可能存在的多种分隔符
        String[] pts = s.split("[;\\s]+");
        for (String p : pts) {
            String trimmed = p.trim().replace("(", "").replace(")", "");
            if (trimmed.isEmpty()) continue;
            String[] xy = trimmed.split("[,,\\s]+");
            if (xy.length >= 2) {
                try {
                    double x = Double.parseDouble(xy[0].trim());
                    double y = Double.parseDouble(xy[1].trim());
                    // 过滤无效坐标
                    if (!Double.isNaN(x) && !Double.isNaN(y) && !Double.isInfinite(x) && !Double.isInfinite(y)) {
                        list.add(new Coordinate(x, y));
                    }
                } catch (NumberFormatException ex) {
                    // 忽略解析错误的点
                }
            }
        }
        // 确保多边形闭合
        if (list.size() > 2 && !list.get(0).equals2D(list.get(list.size() - 1))) {
            list.add(new Coordinate(list.get(0)));
        }
        return list;
    }
} // 类结束