张世豪
7 天以前 68b1f4e85c29164d5de189262282454f9a0b1cc0
以割草机视图为中心显示功能
已修改2个文件
238 ■■■■ 文件已修改
set.properties 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/zhuye/Shouye.java 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
set.properties
@@ -1,16 +1,16 @@
#Mower Configuration Properties - Updated
#Tue Dec 16 15:12:34 CST 2025
#Tue Dec 16 16:27:10 CST 2025
appVersion=-1
currentWorkLandNumber=LAND1
cuttingWidth=200
firmwareVersion=-1
handheldMarkerId=
idleTrailDurationSeconds=60
mapScale=34.71
mapScale=20.09
mowerId=1234
serialAutoConnect=true
serialBaudRate=115200
serialPortName=COM15
simCardNumber=-1
viewCenterX=-3.49
viewCenterY=6.93
viewCenterX=0.55
viewCenterY=12.53
src/zhuye/Shouye.java
@@ -78,6 +78,15 @@
    private JLabel statusLabel;
    private JLabel speedLabel;  // 速度显示标签
    private JLabel areaNameLabel;
    // 边界警告相关
    private Timer boundaryWarningTimer;  // 边界警告检查定时器
    private Timer warningBlinkTimer;  // 警告图标闪烁定时器
    private boolean isMowerOutsideBoundary = false;  // 割草机是否在边界外
    private boolean warningIconVisible = true;  // 警告图标显示状态(用于闪烁)
    // 以割草机为中心视图模式
    private boolean centerOnMowerMode = false;  // 是否处于以割草机为中心的模式
    // 当前选中的导航按钮
    private JButton currentNavButton;
@@ -207,6 +216,9 @@
        initializeDefaultAreaSelection();
        refreshMapForSelectedArea();
        // 启动边界警告检查定时器
        startBoundaryWarningTimer();
    }
    private void scheduleIdentifierCheck() {
@@ -239,6 +251,14 @@
                public void windowClosing(WindowEvent e) {
                    // 保存当前缩放比例
                    saveCurrentScale();
                    // 停止边界警告定时器
                    if (boundaryWarningTimer != null && boundaryWarningTimer.isRunning()) {
                        boundaryWarningTimer.stop();
                    }
                    // 停止闪烁定时器
                    if (warningBlinkTimer != null && warningBlinkTimer.isRunning()) {
                        warningBlinkTimer.stop();
                    }
                }
            });
        }
@@ -259,6 +279,149 @@
            setsys.updateProperty("viewCenterY", String.format("%.2f", translateY));
        }
    }
    /**
     * 启动边界警告检查定时器
     */
    private void startBoundaryWarningTimer() {
        // 边界检查定时器:每500ms检查一次割草机是否在边界内
        boundaryWarningTimer = new Timer(500, e -> {
            checkMowerBoundaryStatus();
        });
        boundaryWarningTimer.setInitialDelay(0);
        boundaryWarningTimer.start();
        // 闪烁定时器:每1秒切换一次警告图标显示状态
        warningBlinkTimer = new Timer(1000, e -> {
            if (isMowerOutsideBoundary) {
                warningIconVisible = !warningIconVisible;
                if (visualizationPanel != null) {
                    visualizationPanel.repaint();
                }
            }
        });
        warningBlinkTimer.setInitialDelay(0);
        warningBlinkTimer.start();
    }
    /**
     * 切换以割草机为中心的模式
     */
    private void toggleCenterOnMowerMode() {
        centerOnMowerMode = !centerOnMowerMode;
        if (centerOnMowerMode) {
            // 开启模式:立即将视图中心移动到割草机位置
            updateViewToCenterOnMower();
        }
        // 关闭模式时不需要做任何操作,用户已经可以自由移动地图
        // 更新工具提示
        if (visualizationPanel != null) {
            visualizationPanel.repaint();
        }
    }
    /**
     * 更新视图中心到割草机位置
     */
    private void updateViewToCenterOnMower() {
        if (mapRenderer == null) {
            return;
        }
        Gecaoji mower = mapRenderer.getMower();
        if (mower != null && mower.hasValidPosition()) {
            Point2D.Double mowerPosition = mower.getPosition();
            if (mowerPosition != null) {
                // 获取当前缩放比例
                double currentScale = mapRenderer.getScale();
                // 设置视图变换,使割草机位置对应到屏幕中心
                // translateX = -mowerX, translateY = -mowerY 可以让割草机在屏幕中心
                mapRenderer.setViewTransform(currentScale, -mowerPosition.x, -mowerPosition.y);
            }
        }
    }
    /**
     * 检查割草机边界状态
     */
    private void checkMowerBoundaryStatus() {
        // 如果处于以割草机为中心的模式,实时更新视图中心
        if (centerOnMowerMode) {
            updateViewToCenterOnMower();
        }
        // 检查是否在作业中
        if (statusLabel == null || !"作业中".equals(statusLabel.getText())) {
            // 不在作业中,重置状态
            if (isMowerOutsideBoundary) {
                isMowerOutsideBoundary = false;
                warningIconVisible = true;
                if (visualizationPanel != null) {
                    visualizationPanel.repaint();
                }
            }
            return;
        }
        // 在作业中,检查是否在边界内
        if (mapRenderer == null) {
            return;
        }
        // 获取当前边界
        List<Point2D.Double> boundary = mapRenderer.getCurrentBoundary();
        if (boundary == null || boundary.size() < 3) {
            // 没有边界,重置状态
            if (isMowerOutsideBoundary) {
                isMowerOutsideBoundary = false;
                warningIconVisible = true;
                if (visualizationPanel != null) {
                    visualizationPanel.repaint();
                }
            }
            return;
        }
        // 获取割草机位置
        Gecaoji mower = mapRenderer.getMower();
        if (mower == null || !mower.hasValidPosition()) {
            // 无法获取位置,重置状态
            if (isMowerOutsideBoundary) {
                isMowerOutsideBoundary = false;
                warningIconVisible = true;
                if (visualizationPanel != null) {
                    visualizationPanel.repaint();
                }
            }
            return;
        }
        Point2D.Double mowerPosition = mower.getPosition();
        if (mowerPosition == null) {
            return;
        }
        // 使用 MowerBoundaryChecker 检查是否在边界内
        boolean isInside = MowerBoundaryChecker.isInsideBoundaryPoints(
            boundary,
            mowerPosition.x,
            mowerPosition.y
        );
        // 更新状态
        boolean wasOutside = isMowerOutsideBoundary;
        isMowerOutsideBoundary = !isInside;
        // 如果状态改变,立即重绘
        if (wasOutside != isMowerOutsideBoundary) {
            warningIconVisible = true;
            if (visualizationPanel != null) {
                visualizationPanel.repaint();
            }
        }
    }
    private void showInitialMowerSelfCheckDialogIfNeeded() {
        // 已移除进入主页时的自检提示(按用户要求删除)
@@ -405,8 +568,6 @@
            {
                // 加载割草机图标,大小20x20像素
                gecaojiIcon = loadScaledIcon("image/gecaoji.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
                // 启用工具提示
                setToolTipText("");
            }
            
            /**
@@ -423,9 +584,11 @@
            public String getToolTipText(MouseEvent event) {
                // 如果鼠标在割草机图标区域内,显示提示文字
                if (isMouseOnGecaojiIcon(event.getPoint())) {
                    return "以割草机为中心";
                    // 根据当前模式显示不同的提示文字
                    return centerOnMowerMode ? "取消以割草机为中心" : "以割草机为中心";
                }
                return super.getToolTipText(event);
                // 不在图标上时返回null,不显示工具提示框
                return null;
            }
            
            @Override
@@ -435,13 +598,48 @@
                if (mapRenderer != null) {
                    mapRenderer.renderMap(g);
                }
                // 在地图左上角绘制割草机图标
                // 水平方向与速度指示器对齐(x=37)
                // 垂直方向与卫星状态图标对齐(y=10,速度指示器面板顶部边距10像素,使图标中心对齐)
                if (gecaojiIcon != null) {
                // 检查是否需要显示警告图标
                if (isMowerOutsideBoundary && warningIconVisible) {
                    // 绘制红色三角形警告图标(带叹号)
                    drawWarningIcon(g, GECAOJI_ICON_X, GECAOJI_ICON_Y, GECAOJI_ICON_SIZE);
                } else if (gecaojiIcon != null) {
                    // 绘制正常的割草机图标
                    // 水平方向与速度指示器对齐(x=37)
                    // 垂直方向与卫星状态图标对齐(y=10,速度指示器面板顶部边距10像素,使图标中心对齐)
                    g.drawImage(gecaojiIcon.getImage(), GECAOJI_ICON_X, GECAOJI_ICON_Y, null);
                }
            }
            /**
             * 绘制红色三角形警告图标(带叹号)
             */
            private void drawWarningIcon(Graphics g, int x, int y, int size) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                // 绘制红色三角形
                int[] xPoints = {x + size / 2, x, x + size};
                int[] yPoints = {y, y + size, y + size};
                g2d.setColor(Color.RED);
                g2d.fillPolygon(xPoints, yPoints, 3);
                // 绘制白色边框
                g2d.setColor(Color.WHITE);
                g2d.setStroke(new BasicStroke(1.5f));
                g2d.drawPolygon(xPoints, yPoints, 3);
                // 绘制白色叹号
                g2d.setColor(Color.WHITE);
                g2d.setFont(new Font("Arial", Font.BOLD, size * 3 / 4));
                FontMetrics fm = g2d.getFontMetrics();
                String exclamation = "!";
                int textWidth = fm.stringWidth(exclamation);
                int textHeight = fm.getAscent();
                g2d.drawString(exclamation, x + (size - textWidth) / 2, y + (size + textHeight) / 2 - 2);
                g2d.dispose();
            }
        };
        visualizationPanel.setLayout(new BorderLayout());
        
@@ -454,20 +652,8 @@
                    // 检查是否点击了割草机图标区域(37, 10, 20, 20)
                    if (clickPoint.x >= 37 && clickPoint.x <= 57 && 
                        clickPoint.y >= 10 && clickPoint.y <= 30) {
                        // 点击了割草机图标,将地图视图中心移动到割草机位置
                        if (mapRenderer != null) {
                            Gecaoji mower = mapRenderer.getMower();
                            if (mower != null && mower.hasValidPosition()) {
                                Point2D.Double mowerPosition = mower.getPosition();
                                if (mowerPosition != null) {
                                    // 获取当前缩放比例
                                    double currentScale = mapRenderer.getScale();
                                    // 设置视图变换,使割草机位置对应到屏幕中心
                                    // translateX = -mowerX, translateY = -mowerY 可以让割草机在屏幕中心
                                    mapRenderer.setViewTransform(currentScale, -mowerPosition.x, -mowerPosition.y);
                                }
                            }
                        }
                        // 切换以割草机为中心的模式
                        toggleCenterOnMowerMode();
                    }
                }
            }