package zhuye;
|
import java.awt.Color;
|
import java.awt.Component;
|
import java.awt.Dimension;
|
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionListener;
|
import java.awt.event.ComponentAdapter;
|
import java.awt.event.ComponentEvent;
|
import java.awt.geom.Point2D;
|
import java.util.ArrayList;
|
import java.util.List;
|
import javax.swing.BorderFactory;
|
import javax.swing.Box;
|
import javax.swing.BoxLayout;
|
import javax.swing.JButton;
|
import javax.swing.JPanel;
|
import javax.swing.SwingUtilities;
|
import javax.swing.Timer;
|
import dikuai.Dikuai;
|
import gecaoji.Gecaoji;
|
import gecaoji.lujingdraw;
|
import publicway.buttonset;
|
import zhangaiwu.Obstacledge;
|
|
/**
|
* 导航预览 - 在地图上预览导航路径和已割区域
|
*/
|
public class daohangyulan {
|
private static boolean active = false;
|
private static Shouye shouyeInstance;
|
private static Dikuai currentDikuai;
|
private static Runnable returnCallback;
|
|
// 导航模拟相关
|
private static List<Point2D.Double> pathPoints = new ArrayList<>();
|
private static int currentPathIndex = 0;
|
private static double currentSpeed = 1.0; // 默认1米/秒
|
private static Timer navigationTimer;
|
private static double totalPathLength = 0.0;
|
private static double traveledDistance = 0.0;
|
private static List<Point2D.Double> mowedTrack = new ArrayList<>();
|
|
// UI组件
|
private static JButton exitButton;
|
private static JButton speedUpButton;
|
private static JButton speedDownButton;
|
private static JPanel navigationButtonPanel;
|
|
private daohangyulan() {
|
}
|
|
/**
|
* 启动导航预览
|
* @param shouye 主页面实例
|
* @param dikuai 地块数据
|
* @param callback 返回回调函数
|
*/
|
public static void startNavigationPreview(Shouye shouye, Dikuai dikuai, Runnable callback) {
|
if (shouye == null || dikuai == null) {
|
return;
|
}
|
|
active = true;
|
shouyeInstance = shouye;
|
currentDikuai = dikuai;
|
returnCallback = callback;
|
|
// 首先获取路径坐标
|
String plannedPath = dikuai.getPlannedPath();
|
if (plannedPath == null || plannedPath.trim().isEmpty() || "-1".equals(plannedPath.trim())) {
|
// 如果路径不存在,提示用户
|
SwingUtilities.invokeLater(() -> {
|
javax.swing.JOptionPane.showMessageDialog(shouye,
|
"无法获取有效的路径坐标,请先设置地块边界坐标和割草宽度,然后生成路径规划",
|
"提示", javax.swing.JOptionPane.WARNING_MESSAGE);
|
});
|
return;
|
}
|
|
// 解析路径坐标
|
pathPoints = parsePlannedPath(plannedPath);
|
if (pathPoints.isEmpty()) {
|
SwingUtilities.invokeLater(() -> {
|
javax.swing.JOptionPane.showMessageDialog(shouye,
|
"路径坐标解析失败,请检查路径数据",
|
"提示", javax.swing.JOptionPane.WARNING_MESSAGE);
|
});
|
return;
|
}
|
|
// 计算总路径长度
|
totalPathLength = calculatePathLength(pathPoints);
|
traveledDistance = 0.0;
|
currentPathIndex = 0;
|
currentSpeed = 1.0; // 默认1米/秒
|
mowedTrack.clear();
|
|
// 获取地块数据
|
String landNumber = dikuai.getLandNumber();
|
String landName = dikuai.getLandName();
|
String boundary = dikuai.getBoundaryCoordinates();
|
|
// 获取障碍物坐标
|
String obstacles = getObstacleCoordinates(landNumber);
|
|
// 获取割草宽度(转换为米)
|
double mowingWidthMeters = getMowingWidthInMeters(dikuai);
|
|
// 设置导航预览轨迹和宽度到MapRenderer
|
MapRenderer renderer = shouye.getMapRenderer();
|
if (renderer != null) {
|
renderer.setNavigationPreviewTrack(new ArrayList<>());
|
renderer.setNavigationPreviewWidth(mowingWidthMeters);
|
}
|
|
// 使用startMowingPathPreview来显示返回按钮和设置预览状态
|
Runnable returnAction = () -> {
|
handleReturn();
|
};
|
shouye.startMowingPathPreview(landNumber, landName, boundary, obstacles, plannedPath, returnAction);
|
|
// 更新导航预览状态显示(在作业状态左边,间隔10像素)
|
shouye.updateNavigationPreviewStatus(true);
|
|
// 创建并显示导航控制按钮
|
createNavigationButtons(shouye);
|
|
// 初始化割草机位置到路径起点
|
if (!pathPoints.isEmpty()) {
|
Point2D.Double startPoint = pathPoints.get(0);
|
Gecaoji mower = renderer != null ? renderer.getMower() : null;
|
if (mower != null) {
|
mower.setPosition(startPoint.x, startPoint.y);
|
}
|
// 添加起点到已割轨迹
|
mowedTrack.add(new Point2D.Double(startPoint.x, startPoint.y));
|
if (renderer != null) {
|
renderer.addNavigationPreviewTrackPoint(new Point2D.Double(startPoint.x, startPoint.y));
|
}
|
}
|
|
// 启动导航模拟定时器
|
startNavigationSimulation(shouye, renderer, mowingWidthMeters);
|
|
// 刷新地图显示
|
shouye.repaint();
|
}
|
|
/**
|
* 停止导航预览
|
*/
|
public static void stopNavigationPreview() {
|
if (!active) {
|
return;
|
}
|
|
active = false;
|
|
// 停止导航模拟定时器
|
stopNavigationSimulation();
|
|
// 隐藏导航控制按钮
|
hideNavigationButtons();
|
|
// 清除导航预览轨迹
|
if (shouyeInstance != null) {
|
MapRenderer renderer = shouyeInstance.getMapRenderer();
|
if (renderer != null) {
|
renderer.clearNavigationPreviewTrack();
|
}
|
|
// 更新导航预览状态显示
|
shouyeInstance.updateNavigationPreviewStatus(false);
|
|
// 刷新地图显示
|
shouyeInstance.repaint();
|
}
|
|
// 执行返回回调
|
if (returnCallback != null) {
|
Runnable callback = returnCallback;
|
returnCallback = null;
|
SwingUtilities.invokeLater(callback);
|
}
|
|
shouyeInstance = null;
|
currentDikuai = null;
|
pathPoints.clear();
|
mowedTrack.clear();
|
}
|
|
/**
|
* 检查是否处于导航预览模式
|
*/
|
public static boolean isActive() {
|
return active;
|
}
|
|
/**
|
* 处理返回按钮点击
|
*/
|
public static void handleReturn() {
|
stopNavigationPreview();
|
}
|
|
/**
|
* 创建导航控制按钮
|
*/
|
private static void createNavigationButtons(Shouye shouye) {
|
if (shouye == null) {
|
return;
|
}
|
|
JPanel visualizationPanel = shouye.getVisualizationPanel();
|
if (visualizationPanel == null) {
|
return;
|
}
|
|
// 创建按钮面板
|
navigationButtonPanel = new JPanel();
|
navigationButtonPanel.setLayout(new BoxLayout(navigationButtonPanel, BoxLayout.Y_AXIS));
|
navigationButtonPanel.setOpaque(false);
|
navigationButtonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
|
// 创建按钮
|
exitButton = createNavigationButton("退出");
|
exitButton.addActionListener(e -> handleReturn());
|
|
speedUpButton = createNavigationButton("加速");
|
speedUpButton.addActionListener(e -> {
|
currentSpeed *= 2.0;
|
updateSpeedDisplay(shouye);
|
});
|
|
speedDownButton = createNavigationButton("减速");
|
speedDownButton.addActionListener(e -> {
|
currentSpeed /= 2.0;
|
if (currentSpeed < 0.1) {
|
currentSpeed = 0.1;
|
}
|
updateSpeedDisplay(shouye);
|
});
|
|
// 垂直排列
|
navigationButtonPanel.add(speedUpButton);
|
navigationButtonPanel.add(Box.createRigidArea(new Dimension(0, 8)));
|
navigationButtonPanel.add(speedDownButton);
|
navigationButtonPanel.add(Box.createRigidArea(new Dimension(0, 8)));
|
navigationButtonPanel.add(exitButton);
|
|
// 设置面板大小
|
navigationButtonPanel.setPreferredSize(new Dimension(80, 120));
|
navigationButtonPanel.setSize(80, 120);
|
|
// 添加到容器
|
visualizationPanel.add(navigationButtonPanel);
|
visualizationPanel.setComponentZOrder(navigationButtonPanel, 0);
|
|
// 初始化位置
|
SwingUtilities.invokeLater(() -> {
|
updateButtonPositions(visualizationPanel);
|
});
|
|
// 监听缩放/调整大小
|
visualizationPanel.addComponentListener(new ComponentAdapter() {
|
@Override
|
public void componentResized(ComponentEvent e) {
|
updateButtonPositions(visualizationPanel);
|
}
|
});
|
}
|
|
private static JButton createNavigationButton(String text) {
|
return buttonset.createStyledButton(text, new Color(46, 139, 87));
|
}
|
|
/**
|
* 更新按钮位置 - 修改此处以匹配图中红框位置
|
*/
|
private static void updateButtonPositions(Component panel) {
|
if (navigationButtonPanel == null || panel == null) {
|
return;
|
}
|
|
int panelWidth = panel.getWidth();
|
int panelHeight = panel.getHeight();
|
if (panelWidth <= 0 || panelHeight <= 0) {
|
Component parent = panel.getParent();
|
if (parent != null) {
|
if (panelWidth <= 0) {
|
panelWidth = parent.getWidth();
|
}
|
if (panelHeight <= 0) {
|
panelHeight = parent.getHeight();
|
}
|
}
|
if (panelWidth <= 0) {
|
panelWidth = 800; // 默认宽度
|
}
|
if (panelHeight <= 0) {
|
panelHeight = 600; // 默认高度
|
}
|
}
|
|
// 按钮面板尺寸
|
int buttonPanelWidth = 80;
|
int buttonPanelHeight = 120;
|
|
// 右下角位置:距离右边20像素,距离底部20像素(在缩放文字上方)
|
// 缩放控制面板在右下角,有20像素的右边距和底部边距
|
// 按钮面板放在缩放控制面板左侧,避免重叠
|
int x = panelWidth - buttonPanelWidth - 20; // 距离右边20像素
|
|
// Y坐标计算说明:
|
// 按钮面板高度为 120。
|
// 为了避开底部的“缩放: 3.91x”文字(该文字通常在底部向上20-30像素处),
|
// 我们将按钮面板放在距离底部约 50-60 像素的位置。
|
// y = 总高度 - 按钮面板高度 - 底部预留空间
|
int y = panelHeight - buttonPanelHeight - 20; // 距离底部20像素
|
|
navigationButtonPanel.setBounds(x, y, buttonPanelWidth, buttonPanelHeight);
|
navigationButtonPanel.setVisible(true);
|
|
// 确保层级在最前
|
Component buttonParent = navigationButtonPanel.getParent();
|
if (buttonParent instanceof java.awt.Container) {
|
((java.awt.Container) buttonParent).setComponentZOrder(navigationButtonPanel, 0);
|
}
|
}
|
|
private static void hideNavigationButtons() {
|
if (navigationButtonPanel != null) {
|
navigationButtonPanel.setVisible(false);
|
if (navigationButtonPanel.getParent() != null) {
|
navigationButtonPanel.getParent().remove(navigationButtonPanel);
|
}
|
}
|
exitButton = null;
|
speedUpButton = null;
|
speedDownButton = null;
|
navigationButtonPanel = null;
|
}
|
|
private static void startNavigationSimulation(Shouye shouye, MapRenderer renderer, double mowingWidthMeters) {
|
if (pathPoints.isEmpty() || renderer == null) return;
|
|
if (navigationTimer != null && navigationTimer.isRunning()) {
|
navigationTimer.stop();
|
}
|
|
navigationTimer = new Timer(100, e -> {
|
if (!active) {
|
stopNavigationSimulation();
|
return;
|
}
|
if (currentPathIndex >= pathPoints.size() - 1) {
|
stopNavigationSimulation();
|
return;
|
}
|
updateNavigation(shouye, renderer, mowingWidthMeters);
|
});
|
navigationTimer.start();
|
}
|
|
private static void stopNavigationSimulation() {
|
if (navigationTimer != null) {
|
navigationTimer.stop();
|
navigationTimer = null;
|
}
|
}
|
|
private static void updateNavigation(Shouye shouye, MapRenderer renderer, double mowingWidthMeters) {
|
if (pathPoints.isEmpty() || currentPathIndex >= pathPoints.size() - 1) {
|
stopNavigationSimulation();
|
return;
|
}
|
|
double deltaTime = 0.1;
|
double moveDistance = currentSpeed * deltaTime;
|
|
Point2D.Double currentPos = pathPoints.get(currentPathIndex);
|
Point2D.Double nextPos = pathPoints.get(currentPathIndex + 1);
|
double segmentLength = Math.hypot(nextPos.x - currentPos.x, nextPos.y - currentPos.y);
|
|
if (segmentLength < 0.001) {
|
currentPathIndex++;
|
return;
|
}
|
|
Gecaoji mower = renderer.getMower();
|
if (moveDistance >= segmentLength) {
|
currentPathIndex++;
|
traveledDistance += segmentLength;
|
if (mower != null && currentPathIndex < pathPoints.size()) {
|
Point2D.Double newPos = pathPoints.get(currentPathIndex);
|
mower.setPosition(newPos.x, newPos.y);
|
mowedTrack.add(new Point2D.Double(newPos.x, newPos.y));
|
renderer.addNavigationPreviewTrackPoint(new Point2D.Double(newPos.x, newPos.y));
|
}
|
} else {
|
double ratio = moveDistance / segmentLength;
|
double newX = currentPos.x + (nextPos.x - currentPos.x) * ratio;
|
double newY = currentPos.y + (nextPos.y - currentPos.y) * ratio;
|
if (mower != null) {
|
mower.setPosition(newX, newY);
|
if (mowedTrack.isEmpty() || Math.hypot(newX - mowedTrack.get(mowedTrack.size() - 1).x, newY - mowedTrack.get(mowedTrack.size() - 1).y) > 0.05) {
|
mowedTrack.add(new Point2D.Double(newX, newY));
|
renderer.addNavigationPreviewTrackPoint(new Point2D.Double(newX, newY));
|
}
|
}
|
traveledDistance += moveDistance;
|
}
|
|
updateProgressAndSpeed(shouye, renderer, mowingWidthMeters);
|
shouye.repaint();
|
}
|
|
private static void updateProgressAndSpeed(Shouye shouye, MapRenderer renderer, double mowingWidthMeters) {
|
if (shouye == null || renderer == null) return;
|
double completedArea = traveledDistance * mowingWidthMeters;
|
double totalArea = renderer.getTotalLandAreaSqMeters();
|
if (totalArea <= 0 && currentDikuai != null) {
|
try {
|
String areaStr = currentDikuai.getLandArea();
|
if (areaStr != null) totalArea = Double.parseDouble(areaStr.trim());
|
} catch (Exception e) {}
|
}
|
if (totalArea > 0) {
|
double percentage = Math.max(0.0, Math.min(100.0, (completedArea / totalArea) * 100.0));
|
shouye.updateMowingProgress(percentage, completedArea, totalArea);
|
}
|
shouye.updateMowerSpeed(currentSpeed * 3.6);
|
}
|
|
private static void updateSpeedDisplay(Shouye shouye) {
|
if (shouye != null) shouye.updateMowerSpeed(currentSpeed * 3.6);
|
}
|
|
private static double calculatePathLength(List<Point2D.Double> points) {
|
double total = 0.0;
|
for (int i = 0; i < points.size() - 1; i++) {
|
total += Math.hypot(points.get(i+1).x - points.get(i).x, points.get(i+1).y - points.get(i).y);
|
}
|
return total;
|
}
|
|
private static List<Point2D.Double> parsePlannedPath(String plannedPath) {
|
List<Point2D.Double> points = lujingdraw.parsePlannedPath(plannedPath);
|
return points != null ? points : new ArrayList<>();
|
}
|
|
private static double getMowingWidthInMeters(Dikuai dikuai) {
|
if (dikuai == null) return 0.4;
|
try {
|
String bladeWidth = dikuai.getMowingBladeWidth();
|
if (bladeWidth != null && !"-1".equals(bladeWidth)) return Double.parseDouble(bladeWidth);
|
String mowWidth = dikuai.getMowingWidth();
|
if (mowWidth != null && !"-1".equals(mowWidth)) {
|
double val = Double.parseDouble(mowWidth);
|
return val > 10 ? val / 100.0 : val;
|
}
|
} catch (Exception e) {}
|
return 0.4;
|
}
|
|
private static String getObstacleCoordinates(String landNumber) {
|
if (landNumber == null || "-1".equals(landNumber)) return null;
|
try {
|
java.io.File configFile = new java.io.File("Obstacledge.properties");
|
if (!configFile.exists()) return null;
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) return null;
|
Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
|
if (plot == null) return null;
|
List<Obstacledge.Obstacle> obsList = plot.getObstacles();
|
if (obsList == null) return null;
|
StringBuilder sb = new StringBuilder();
|
for (Obstacledge.Obstacle obs : obsList) {
|
String coords = obs.getXyCoordsString();
|
if (coords != null && !"-1".equals(coords)) {
|
if (sb.length() > 0) sb.append(" ");
|
sb.append("(").append(coords).append(")");
|
}
|
}
|
return sb.length() > 0 ? sb.toString() : null;
|
} catch (Exception e) { return null; }
|
}
|
}
|