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<Point2D.Double> 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<Point2D.Double> 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<Point2D.Double> getPointsSnapshot() {
|
synchronized (returnPathPoints) {
|
return new ArrayList<>(returnPathPoints);
|
}
|
}
|
|
/**
|
* 绘制往返路径
|
*/
|
public void draw(Graphics2D g2d, double scale) {
|
List<Point2D.Double> 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<Point2D.Double> 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);
|
}
|
}
|