package dikuai;
|
|
import javax.swing.*;
|
import java.awt.*;
|
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionListener;
|
import java.awt.geom.Point2D;
|
import java.util.List;
|
import java.util.ArrayList;
|
import zhuye.Shouye;
|
import zhuye.MapRenderer;
|
import zhuye.buttonset;
|
import gecaoji.Gecaoji;
|
import gecaoji.lujingdraw;
|
|
/**
|
* 导航预览功能类
|
* 实现割草机沿路径模拟导航
|
*/
|
public class daohangyulan {
|
private static daohangyulan instance;
|
private Shouye shouye;
|
private MapRenderer mapRenderer;
|
private Gecaoji mower;
|
private Dikuai currentDikuai;
|
|
// 路径相关
|
private List<Point2D.Double> pathPoints;
|
private int currentPathIndex;
|
private double currentSpeed; // 米/秒
|
private static final double DEFAULT_SPEED = 1.0; // 默认1米/秒
|
private static final double SPEED_MULTIPLIER = 1.0; // 每次加速/减速的倍数
|
|
// 定时器
|
private Timer navigationTimer;
|
private static final int TIMER_INTERVAL_MS = 50; // 50毫秒更新一次
|
|
// 导航预览按钮
|
private JButton speedUpBtn;
|
private JButton speedDownBtn;
|
private JButton exitBtn;
|
|
// 保存原始按钮和面板的引用
|
private JButton originalStartBtn;
|
private JButton originalStopBtn;
|
private JPanel originalButtonPanel;
|
private LayoutManager originalButtonPanelLayout;
|
|
// 导航预览轨迹
|
private List<Point2D.Double> navigationTrack;
|
|
// 是否正在导航预览
|
private boolean isNavigating;
|
|
private daohangyulan() {
|
this.currentSpeed = DEFAULT_SPEED;
|
this.navigationTrack = new ArrayList<>();
|
this.isNavigating = false;
|
}
|
|
public static daohangyulan getInstance() {
|
if (instance == null) {
|
instance = new daohangyulan();
|
}
|
return instance;
|
}
|
|
/**
|
* 启动导航预览
|
* @param dikuai 地块对象
|
*/
|
public void startNavigationPreview(Dikuai dikuai) {
|
if (dikuai == null) {
|
JOptionPane.showMessageDialog(null, "地块数据无效", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
|
// 获取地块的割草路径坐标
|
String plannedPath = dikuai.getPlannedPath();
|
if (plannedPath == null || plannedPath.trim().isEmpty() || "-1".equals(plannedPath.trim())) {
|
JOptionPane.showMessageDialog(null, "当前地块没有规划路径,无法进行导航预览", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
|
// 解析路径坐标
|
pathPoints = lujingdraw.parsePlannedPath(plannedPath);
|
if (pathPoints == null || pathPoints.size() < 2) {
|
JOptionPane.showMessageDialog(null, "路径坐标解析失败,无法进行导航预览", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
|
this.currentDikuai = dikuai;
|
this.currentPathIndex = 0;
|
this.currentSpeed = DEFAULT_SPEED;
|
this.navigationTrack.clear();
|
|
// 获取首页和地图渲染器
|
shouye = Shouye.getInstance();
|
if (shouye == null) {
|
JOptionPane.showMessageDialog(null, "无法获取首页实例", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
|
mapRenderer = shouye.getMapRenderer();
|
if (mapRenderer == null) {
|
JOptionPane.showMessageDialog(null, "无法获取地图渲染器", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
|
mower = mapRenderer.getMower();
|
if (mower == null) {
|
JOptionPane.showMessageDialog(null, "无法获取割草机实例", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
|
// 设置割草机初始位置为路径起点
|
Point2D.Double startPoint = pathPoints.get(0);
|
mower.setPosition(startPoint.x, startPoint.y);
|
|
// 计算初始方向(朝向第一个目标点)
|
if (pathPoints.size() > 1) {
|
Point2D.Double nextPoint = pathPoints.get(1);
|
double heading = calculateHeading(startPoint, nextPoint);
|
setMowerHeading(heading);
|
} else {
|
// 如果只有一个点,设置默认方向(0度,向右)
|
setMowerHeading(0.0);
|
}
|
|
// 初始化导航预览轨迹
|
navigationTrack.clear();
|
navigationTrack.add(new Point2D.Double(startPoint.x, startPoint.y));
|
mapRenderer.clearNavigationPreviewTrack();
|
mapRenderer.addNavigationPreviewTrackPoint(startPoint);
|
|
// 获取割草机割刀宽度
|
String bladeWidthStr = dikuai.getMowingBladeWidth();
|
double bladeWidthMeters = 0.5; // 默认0.5米
|
if (bladeWidthStr != null && !bladeWidthStr.trim().isEmpty() && !"-1".equals(bladeWidthStr.trim())) {
|
try {
|
bladeWidthMeters = Double.parseDouble(bladeWidthStr.trim());
|
} catch (NumberFormatException e) {
|
// 使用默认值
|
}
|
}
|
mapRenderer.setNavigationPreviewWidth(bladeWidthMeters);
|
|
// 设置初始速度显示
|
mapRenderer.setNavigationPreviewSpeed(currentSpeed);
|
|
// 创建并显示导航预览按钮(替换底部按钮)
|
createNavigationButtons();
|
|
// 显示导航预览模式标签
|
if (shouye != null) {
|
shouye.setNavigationPreviewLabelVisible(true);
|
}
|
|
// 启动导航定时器
|
startNavigationTimer();
|
|
isNavigating = true;
|
|
// 刷新地图显示
|
mapRenderer.repaint();
|
}
|
|
/**
|
* 创建导航预览按钮(替换底部的暂停、结束按钮)
|
*/
|
private void createNavigationButtons() {
|
// 移除旧的按钮(如果存在)
|
removeNavigationButtons();
|
|
if (shouye == null) {
|
return;
|
}
|
|
// 获取原始按钮
|
originalStartBtn = shouye.getStartButton();
|
originalStopBtn = shouye.getStopButton();
|
|
if (originalStartBtn == null || originalStopBtn == null) {
|
return;
|
}
|
|
// 获取控制面板
|
JPanel controlPanel = shouye.getControlPanel();
|
if (controlPanel == null) {
|
return;
|
}
|
|
// 查找按钮面板(包含 startBtn 和 stopBtn 的面板)
|
JPanel buttonPanel = null;
|
for (Component comp : controlPanel.getComponents()) {
|
if (comp instanceof JPanel) {
|
JPanel panel = (JPanel) comp;
|
// 检查是否是按钮面板(包含 startBtn 和 stopBtn)
|
boolean hasStartBtn = false;
|
boolean hasStopBtn = false;
|
for (Component child : panel.getComponents()) {
|
if (child == originalStartBtn) {
|
hasStartBtn = true;
|
}
|
if (child == originalStopBtn) {
|
hasStopBtn = true;
|
}
|
}
|
if (hasStartBtn && hasStopBtn) {
|
buttonPanel = panel;
|
break;
|
}
|
}
|
}
|
|
if (buttonPanel == null) {
|
return;
|
}
|
|
// 保存原始按钮面板和布局
|
originalButtonPanel = buttonPanel;
|
originalButtonPanelLayout = buttonPanel.getLayout();
|
|
// 隐藏原始按钮
|
if (originalStartBtn != null) {
|
originalStartBtn.setVisible(false);
|
}
|
if (originalStopBtn != null) {
|
originalStopBtn.setVisible(false);
|
}
|
|
// 修改按钮面板布局为3列(加速、减速、退出)
|
// 减少按钮间距,给按钮更多空间显示文字
|
buttonPanel.setLayout(new GridLayout(1, 3, 10, 0));
|
|
// 创建加速按钮
|
speedUpBtn = createControlButton("加速", new Color(46, 139, 87));
|
speedUpBtn.addActionListener(e -> speedUp());
|
|
// 创建减速按钮
|
speedDownBtn = createControlButton("减速", new Color(255, 140, 0));
|
speedDownBtn.addActionListener(e -> speedDown());
|
|
// 创建退出按钮
|
exitBtn = createControlButton("退出", new Color(255, 107, 107));
|
exitBtn.addActionListener(e -> exitNavigationPreview());
|
|
// 添加新按钮到按钮面板
|
buttonPanel.add(speedUpBtn);
|
buttonPanel.add(speedDownBtn);
|
buttonPanel.add(exitBtn);
|
|
// 刷新显示
|
buttonPanel.revalidate();
|
buttonPanel.repaint();
|
controlPanel.revalidate();
|
controlPanel.repaint();
|
}
|
|
/**
|
* 创建控制按钮(使用buttonset风格,尺寸40x80)
|
*/
|
private JButton createControlButton(String text, Color color) {
|
// 使用buttonset创建按钮
|
JButton button = buttonset.createStyledButton(text, color);
|
|
// 设置按钮尺寸:宽度40像素,高度80像素
|
Dimension buttonSize = new Dimension(80, 40);
|
button.setPreferredSize(buttonSize);
|
button.setMinimumSize(buttonSize);
|
button.setMaximumSize(buttonSize);
|
|
return button;
|
}
|
|
/**
|
* 移除导航预览按钮(恢复原始按钮)
|
*/
|
private void removeNavigationButtons() {
|
if (originalButtonPanel == null) {
|
return;
|
}
|
|
// 移除导航预览按钮
|
if (speedUpBtn != null && speedUpBtn.getParent() == originalButtonPanel) {
|
originalButtonPanel.remove(speedUpBtn);
|
}
|
if (speedDownBtn != null && speedDownBtn.getParent() == originalButtonPanel) {
|
originalButtonPanel.remove(speedDownBtn);
|
}
|
if (exitBtn != null && exitBtn.getParent() == originalButtonPanel) {
|
originalButtonPanel.remove(exitBtn);
|
}
|
|
// 恢复原始布局
|
if (originalButtonPanelLayout != null) {
|
originalButtonPanel.setLayout(originalButtonPanelLayout);
|
}
|
|
// 恢复原始按钮显示
|
if (originalStartBtn != null) {
|
originalStartBtn.setVisible(true);
|
if (originalStartBtn.getParent() != originalButtonPanel) {
|
originalButtonPanel.add(originalStartBtn);
|
}
|
}
|
if (originalStopBtn != null) {
|
originalStopBtn.setVisible(true);
|
if (originalStopBtn.getParent() != originalButtonPanel) {
|
originalButtonPanel.add(originalStopBtn);
|
}
|
}
|
|
// 刷新显示
|
originalButtonPanel.revalidate();
|
originalButtonPanel.repaint();
|
|
if (shouye != null && shouye.getControlPanel() != null) {
|
shouye.getControlPanel().revalidate();
|
shouye.getControlPanel().repaint();
|
}
|
|
// 清空引用
|
speedUpBtn = null;
|
speedDownBtn = null;
|
exitBtn = null;
|
originalButtonPanel = null;
|
originalButtonPanelLayout = null;
|
originalStartBtn = null;
|
originalStopBtn = null;
|
}
|
|
/**
|
* 启动导航定时器
|
*/
|
private void startNavigationTimer() {
|
if (navigationTimer != null) {
|
navigationTimer.stop();
|
}
|
|
navigationTimer = new Timer(TIMER_INTERVAL_MS, new ActionListener() {
|
@Override
|
public void actionPerformed(ActionEvent e) {
|
updateNavigation();
|
}
|
});
|
navigationTimer.start();
|
}
|
|
/**
|
* 更新导航状态
|
*/
|
private void updateNavigation() {
|
if (pathPoints == null || pathPoints.size() < 2 || currentPathIndex >= pathPoints.size() - 1) {
|
// 路径完成
|
stopNavigation();
|
JOptionPane.showMessageDialog(null, "导航预览完成", "提示", JOptionPane.INFORMATION_MESSAGE);
|
return;
|
}
|
|
Point2D.Double currentPos = mower.getPosition();
|
if (currentPos == null) {
|
return;
|
}
|
|
Point2D.Double targetPoint = pathPoints.get(currentPathIndex + 1);
|
|
// 计算到目标点的距离
|
double dx = targetPoint.x - currentPos.x;
|
double dy = targetPoint.y - currentPos.y;
|
double distance = Math.hypot(dx, dy);
|
|
// 计算每帧移动的距离(米)
|
double moveDistance = currentSpeed * (TIMER_INTERVAL_MS / 1000.0);
|
|
if (distance <= moveDistance) {
|
// 到达目标点,移动到下一个点
|
mower.setPosition(targetPoint.x, targetPoint.y);
|
navigationTrack.add(new Point2D.Double(targetPoint.x, targetPoint.y));
|
mapRenderer.addNavigationPreviewTrackPoint(targetPoint);
|
|
currentPathIndex++;
|
|
// 如果还有下一个点,计算方向
|
if (currentPathIndex < pathPoints.size() - 1) {
|
Point2D.Double nextPoint = pathPoints.get(currentPathIndex + 1);
|
double heading = calculateHeading(targetPoint, nextPoint);
|
setMowerHeading(heading);
|
}
|
} else {
|
// 向目标点移动
|
// 先更新方向,确保车头朝向目标点
|
double heading = calculateHeading(currentPos, targetPoint);
|
setMowerHeading(heading);
|
|
double ratio = moveDistance / distance;
|
double newX = currentPos.x + dx * ratio;
|
double newY = currentPos.y + dy * ratio;
|
|
mower.setPosition(newX, newY);
|
navigationTrack.add(new Point2D.Double(newX, newY));
|
mapRenderer.addNavigationPreviewTrackPoint(new Point2D.Double(newX, newY));
|
}
|
|
// 更新速度显示到地图渲染器
|
if (mapRenderer != null) {
|
mapRenderer.setNavigationPreviewSpeed(currentSpeed);
|
}
|
|
// 更新地图显示
|
mapRenderer.repaint();
|
|
// 更新速度显示(如果需要)
|
updateSpeedDisplay();
|
}
|
|
/**
|
* 计算两点之间的方向角(度)
|
* 图标默认朝上,向右旋转90度车头朝右
|
* atan2返回的角度:向右是0度,向上是90度
|
* 需要转换为图标旋转角度:向右需要90度,向上需要0度
|
*/
|
private double calculateHeading(Point2D.Double from, Point2D.Double to) {
|
double dx = to.x - from.x;
|
double dy = to.y - from.y;
|
// atan2返回的角度:向右是0度,向上是90度,向左是180度,向下是-90度(270度)
|
double atan2Angle = Math.toDegrees(Math.atan2(dy, dx));
|
|
// 转换为0-360度范围
|
if (atan2Angle < 0) {
|
atan2Angle += 360;
|
}
|
|
// 图标默认朝上(0度),向右旋转90度车头朝右
|
// 所以:运动方向向右(0度)→ 需要旋转90度
|
// 运动方向向上(90度)→ 需要旋转0度
|
// 运动方向向左(180度)→ 需要旋转270度
|
// 运动方向向下(270度)→ 需要旋转180度
|
// 公式:heading = (90 - atan2Angle + 360) % 360
|
double heading = (90.0 - atan2Angle + 360.0) % 360.0;
|
|
return heading;
|
}
|
|
/**
|
* 设置割草机方向
|
*/
|
private void setMowerHeading(double headingDegrees) {
|
if (mower != null) {
|
mower.setHeading(headingDegrees);
|
}
|
}
|
|
/**
|
* 加速
|
*/
|
private void speedUp() {
|
currentSpeed += SPEED_MULTIPLIER;
|
updateSpeedDisplay();
|
}
|
|
/**
|
* 减速
|
*/
|
private void speedDown() {
|
if (currentSpeed > 0.1) { // 最小速度0.1米/秒
|
currentSpeed -= SPEED_MULTIPLIER;
|
if (currentSpeed < 0.1) {
|
currentSpeed = 0.1;
|
}
|
}
|
updateSpeedDisplay();
|
}
|
|
/**
|
* 更新速度显示
|
*/
|
private void updateSpeedDisplay() {
|
// 可以在地图上显示当前速度
|
// 这里暂时不实现,如果需要可以在MapRenderer中添加速度显示
|
}
|
|
/**
|
* 停止导航
|
*/
|
private void stopNavigation() {
|
if (navigationTimer != null) {
|
navigationTimer.stop();
|
navigationTimer = null;
|
}
|
isNavigating = false;
|
}
|
|
/**
|
* 退出导航预览
|
*/
|
public void exitNavigationPreview() {
|
stopNavigation();
|
removeNavigationButtons();
|
|
// 隐藏导航预览模式标签
|
if (shouye != null) {
|
shouye.setNavigationPreviewLabelVisible(false);
|
}
|
|
// 清除导航预览轨迹
|
if (mapRenderer != null) {
|
mapRenderer.clearNavigationPreviewTrack();
|
mapRenderer.setNavigationPreviewSpeed(0.0); // 清除速度显示
|
mapRenderer.repaint();
|
}
|
|
// 恢复地块管理页面
|
// 在清空currentDikuai之前保存地块编号,使用final变量以便在lambda中使用
|
final String landNumber = (currentDikuai != null) ? currentDikuai.getLandNumber() : null;
|
|
isNavigating = false;
|
currentDikuai = null;
|
|
// 如果有地块编号,显示地块管理页面
|
if (landNumber != null && !landNumber.trim().isEmpty()) {
|
SwingUtilities.invokeLater(() -> {
|
try {
|
Component parent = null;
|
if (shouye != null) {
|
Window owner = SwingUtilities.getWindowAncestor(shouye);
|
if (owner instanceof Component) {
|
parent = (Component) owner;
|
} else {
|
parent = shouye;
|
}
|
}
|
Dikuaiguanli.showDikuaiManagement(parent, landNumber);
|
} catch (Exception e) {
|
System.err.println("显示地块管理页面失败: " + e.getMessage());
|
e.printStackTrace();
|
}
|
});
|
}
|
}
|
|
/**
|
* 检查是否正在导航预览
|
*/
|
public boolean isNavigating() {
|
return isNavigating;
|
}
|
}
|