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 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 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; } }