package zhuye; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; import javax.swing.Timer; import gecaoji.Device; /** * 往返路径绘制管理器 * 负责管理往返路径的绘制逻辑和状态 */ public class WangfanDraw { // 往返路径点列表 private final List returnPathPoints = new ArrayList<>(); // 绘制状态 private boolean drawingActive = false; private boolean paused = false; private boolean isHandheldMode = false; // 是否使用手持设备模式 // 基准坐标 private double[] baseLatLon; // 监控定时器 private Timer monitorTimer; // 回调接口 private Runnable finishCallback; // 依赖对象 private Shouye shouye; private MapRenderer mapRenderer; // 往返路径绘制接口 public interface DrawingHelper { double[] resolveBaseLatLon(); Coordinate getLatestCoordinate(); double parseDMToDecimal(String dmm, String direction); double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon); boolean arePointsClose(Point2D.Double a, Point2D.Double b); void enterDrawingControlMode(); void exitDrawingControlMode(); boolean isDrawingPaused(); } private DrawingHelper helper; /** * 构造函数 */ public WangfanDraw(Shouye shouye, MapRenderer mapRenderer, DrawingHelper helper) { this.shouye = shouye; this.mapRenderer = mapRenderer; this.helper = helper; } /** * 启动往返路径绘制 * @param finishCallback 完成绘制时的回调 * @param isHandheld 是否使用手持设备模式 * @return 是否成功启动 */ public boolean start(Runnable finishCallback, boolean isHandheld) { if (mapRenderer == null || helper == null) { return false; } double[] baseLatLonCandidate = helper.resolveBaseLatLon(); if (baseLatLonCandidate == null) { return false; } if (mapRenderer != null) { mapRenderer.clearIdleTrail(); } drawingActive = true; paused = false; this.finishCallback = finishCallback; this.baseLatLon = baseLatLonCandidate; this.isHandheldMode = isHandheld; // 如果是手持设备模式,切换图标 if (isHandheld && shouye != null) { shouye.setHandheldMowerIconActive(true); } // 清空路径点 synchronized (returnPathPoints) { returnPathPoints.clear(); } synchronized (Coordinate.coordinates) { Coordinate.coordinates.clear(); } if (mapRenderer != null) { mapRenderer.startReturnPathDrawing(); } Coordinate.setStartSaveGngga(true); if (helper != null) { helper.enterDrawingControlMode(); } // 设置状态提示为"正在绘制往返路径" if (shouye != null) { shouye.setStatusLabelText("正在绘制往返路径"); } startMonitor(); return true; } /** * 设置预览路径点 * @param points 路径点列表 */ public void setPoints(List points) { synchronized (returnPathPoints) { returnPathPoints.clear(); if (points != null) { returnPathPoints.addAll(points); } } if (mapRenderer != null) { mapRenderer.repaint(); } } /** * 清空路径点 */ public void clearPoints() { synchronized (returnPathPoints) { returnPathPoints.clear(); } if (mapRenderer != null) { mapRenderer.repaint(); } } /** * 启动监控定时器 */ private void startMonitor() { if (monitorTimer == null) { monitorTimer = new Timer(600, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { pollCoordinate(); } }); monitorTimer.setRepeats(true); } if (!monitorTimer.isRunning()) { monitorTimer.start(); } pollCoordinate(); } /** * 停止监控定时器 */ private void stopMonitor() { if (monitorTimer != null && monitorTimer.isRunning()) { monitorTimer.stop(); } } /** * 轮询往返路径坐标(只保存定位状态=4的有效坐标) */ private void pollCoordinate() { if (!drawingActive || (helper != null && helper.isDrawingPaused())) { return; } // 检查定位状态是否为4(固定解)- 统一来源为设备数据 Device device = Device.getGecaoji(); if (device == null) { return; } String positioningQuality = device.getPositioningStatus(); if (positioningQuality == null || !"4".equals(positioningQuality.trim())) { return; // 只保存定位状态=4的有效坐标 } if (helper == null) { return; } Coordinate latest = helper.getLatestCoordinate(); if (latest == null) { return; } if (baseLatLon == null || baseLatLon.length < 2) { return; } double lat = helper.parseDMToDecimal(latest.getLatitude(), latest.getLatDirection()); double lon = helper.parseDMToDecimal(latest.getLongitude(), latest.getLonDirection()); if (!Double.isFinite(lat) || !Double.isFinite(lon)) { return; } double[] local = helper.convertLatLonToLocal(lat, lon, baseLatLon[0], baseLatLon[1]); Point2D.Double candidate = new Point2D.Double(local[0], local[1]); if (!Double.isFinite(candidate.x) || !Double.isFinite(candidate.y)) { return; } // 检查是否与上一个点太近(避免重复点) synchronized (returnPathPoints) { if (!returnPathPoints.isEmpty()) { Point2D.Double lastPoint = returnPathPoints.get(returnPathPoints.size() - 1); if (helper.arePointsClose(lastPoint, candidate)) { return; // 点太近,跳过 } } } // 添加到路径点列表 synchronized (returnPathPoints) { returnPathPoints.add(candidate); } // 触发重绘 if (mapRenderer != null) { mapRenderer.repaint(); } // 同时保存到 Coordinate.coordinates 用于后续保存 synchronized (Coordinate.coordinates) { Coordinate.coordinates.add(latest); } } /** * 停止往返路径绘制 */ public void stop() { stopMonitor(); drawingActive = false; paused = false; baseLatLon = null; if (mapRenderer != null) { mapRenderer.stopReturnPathDrawing(); } Coordinate.setStartSaveGngga(false); if (helper != null) { helper.exitDrawingControlMode(); } // 恢复割草机图标 if (isHandheldMode && shouye != null) { shouye.setHandheldMowerIconActive(false); } isHandheldMode = false; } /** * 暂停/恢复绘制 */ public void setPaused(boolean paused) { this.paused = paused; } /** * 检查是否正在绘制 */ public boolean isActive() { return drawingActive; } /** * 检查是否暂停 */ public boolean isPaused() { return paused; } /** * 获取完成绘制回调 */ public Runnable getFinishCallback() { return finishCallback; } /** * 执行完成绘制回调 */ public void executeFinishCallback() { if (finishCallback != null) { finishCallback.run(); finishCallback = null; } } /** * 获取路径点列表的快照 */ public List getPointsSnapshot() { synchronized (returnPathPoints) { return new ArrayList<>(returnPathPoints); } } /** * 绘制往返路径 */ public void draw(Graphics2D g2d, double scale) { List points = getPointsSnapshot(); if (points.isEmpty()) { return; } // 保存原始变换 AffineTransform originalTransform = g2d.getTransform(); // 设置往返路径颜色 Color lineColor = Color.GREEN; // 绿色连线 Color pointColor = Color.RED; // 红色实心圆 // 设置线宽 float lineWidth = (float)(3.0 / Math.max(0.5, scale)); g2d.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); // 点的大小(在世界坐标系中,米),直径为线宽度的3倍 // 注意:lineWidth已经是世界坐标系下的宽度(3.0/scale),所以直接乘以倍数即可得到世界坐标系下的直径 // 不需要再除以scale,否则会导致放大地图时圆圈变小 double pointSizeInWorld = lineWidth * 3.0; double halfSize = pointSizeInWorld / 2.0; // 先绘制所有路径点(作为基础层) g2d.setColor(pointColor); for (Point2D.Double point : points) { Ellipse2D.Double pointShape = new Ellipse2D.Double( point.x - halfSize, point.y - halfSize, pointSizeInWorld, pointSizeInWorld ); g2d.fill(pointShape); } // 然后绘制连线 g2d.setColor(lineColor); if (points.size() >= 2) { Path2D.Double linePath = new Path2D.Double(); linePath.moveTo(points.get(0).x, points.get(0).y); for (int i = 1; i < points.size(); i++) { linePath.lineTo(points.get(i).x, points.get(i).y); } g2d.draw(linePath); } // 最后再次绘制路径点(在连线之上,确保点覆盖在线的端点上) // 准备字体:大小与圆圈一致(世界坐标系),黑色 float fontSizeInWorld = (float)pointSizeInWorld; // 使用0.8倍大小以确保数字在圆圈内不拥挤 Font font = new Font("Arial", Font.BOLD, 1).deriveFont(fontSizeInWorld * 0.8f); g2d.setFont(font); FontMetrics fm = g2d.getFontMetrics(); for (int i = 0; i < points.size(); i++) { Point2D.Double point = points.get(i); // 绘制点 g2d.setColor(pointColor); Ellipse2D.Double pointShape = new Ellipse2D.Double( point.x - halfSize, point.y - halfSize, pointSizeInWorld, pointSizeInWorld ); g2d.fill(pointShape); // 绘制编号 g2d.setColor(Color.BLACK); String number = String.valueOf(i + 1); java.awt.geom.Rectangle2D bounds = fm.getStringBounds(number, g2d); double textX = point.x - bounds.getWidth() / 2.0; double textY = point.y - bounds.getCenterY(); g2d.drawString(number, (float)textX, (float)textY); } } /** * 绘制实线线条中间加上虚线虚线白色,实线黑色的风格 * @param g2d Graphics2D对象 * @param points 路径点列表 * @param scale 当前缩放比例 */ public static void drawRailwayPath(Graphics2D g2d, List points, double scale) { if (points == null || points.size() < 2) { return; } // 1. 绘制底层黑色实线 float baseWidth = (float)(3.0 / Math.max(0.5, scale)); // 基础宽度 g2d.setColor(Color.BLACK); g2d.setStroke(new BasicStroke(baseWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); Path2D.Double path = new Path2D.Double(); path.moveTo(points.get(0).x, points.get(0).y); for (int i = 1; i < points.size(); i++) { path.lineTo(points.get(i).x, points.get(i).y); } g2d.draw(path); // 2. 绘制顶层白色虚线 float dashWidth = baseWidth * 0.4f; // 虚线宽度比实线细一些 float dashLen = baseWidth * 2.0f; // 虚线段长度 float dashSpace = baseWidth * 2.0f; // 虚线间隔 float[] dashPattern = {dashLen, dashSpace}; g2d.setColor(Color.WHITE); g2d.setStroke(new BasicStroke(dashWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 10.0f, dashPattern, 0.0f)); g2d.draw(path); } }