| | |
| | | package gecaoji; // 包声明 |
| | | |
| | | import java.awt.BasicStroke; // 引入基础描边类 |
| | | import java.awt.Color; // 引入颜色类 |
| | | import java.awt.Graphics2D; // 引入2D图形上下文 |
| | |
| | | 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 { // 类定义,防止继承 |
| | | private static final Color PATH_COLOR = new Color(255, 0, 0, 220); // 路径颜色(红色半透明) |
| | | // 马尔斯绿色 (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); // 终点箭头颜色 |
| | | |
| | |
| | | * 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) { // 遍历点集 |
| | |
| | | polyline.lineTo(point.x, point.y); // 连线到下一点 |
| | | } // if结束 |
| | | } // for结束 |
| | | |
| | | Stroke previous = g2d.getStroke(); // 保存原描边 |
| | | float strokeWidth = (float) (2.5 / Math.max(0.5, scale)); // 根据缩放计算线宽 |
| | | |
| | | g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // 设置圆头圆角描边 |
| | | g2d.setColor(PATH_COLOR); // 设置线路颜色 |
| | | 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); // 绘制终点箭头 |
| | | 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) { // 判空 |
| | |
| | | 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; |
| | | } |
| | | } // 类结束 |