张世豪
9 小时以前 5b685e9066ccfbc432c29739b5524f1d42a20891
src/zhuye/Shouye.java
@@ -11,26 +11,32 @@
import java.awt.event.*;
import chuankou.dellmessage;
import chuankou.sendmessage;
import chuankou.SerialPortService;
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
import dikuai.addzhangaiwu;
import gecaoji.Device;
import gecaoji.Gecaoji;
import gecaoji.MowerBoundaryChecker;
import publicway.buttonset;
import set.Sets;
import set.debug;
import udpdell.UDPServer;
import zhangaiwu.AddDikuai;
import yaokong.Control04;
import yaokong.RemoteControlDialog;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.awt.geom.Point2D;
import publicway.Gpstoxuzuobiao;
import publicway.Fanhuibutton;
/**
 * 首页界面 - 适配6.5寸竖屏,使用独立的MapRenderer进行绘制
 */
@@ -64,7 +70,7 @@
   private JLabel mowerSpeedValueLabel;
   private JLabel mowerSpeedUnitLabel;
   private JLabel mowingProgressLabel;
   private FixQualityIndicator fixQualityIndicator;
   private gpszhuangtai fixQualityIndicator;
   // 导航按钮
   private JButton homeNavBtn;
@@ -75,6 +81,17 @@
   private JLabel statusLabel;
   private JLabel speedLabel;  // 速度显示标签
   private JLabel areaNameLabel;
   private JLabel drawingBoundaryLabel;  // 正在绘制边界状态标签
   private JLabel navigationPreviewLabel;  // 导航预览模式标签
   // 边界警告相关
   private Timer boundaryWarningTimer;  // 边界警告检查定时器
   private Timer warningBlinkTimer;  // 警告图标闪烁定时器
   private boolean isMowerOutsideBoundary = false;  // 割草机是否在边界外
   private boolean warningIconVisible = true;  // 警告图标显示状态(用于闪烁)
   // 以割草机为中心视图模式
   private boolean centerOnMowerMode = false;  // 是否处于以割草机为中心的模式
   // 当前选中的导航按钮
   private JButton currentNavButton;
@@ -88,10 +105,22 @@
   private Sets settingsDialog;
   private BaseStation baseStation;
   private final Consumer<String> serialLineListener = line -> SwingUtilities.invokeLater(this::updateDataPacketCountLabel);
   // 地图渲染器
   private MapRenderer mapRenderer;
   private boolean pathPreviewActive;
   private final Consumer<String> serialLineListener = line -> {
      SwingUtilities.invokeLater(() -> {
         updateDataPacketCountLabel();
         // 如果收到$GNGGA数据,立即更新拖尾
         if (line != null && line.trim().startsWith("$GNGGA")) {
            if (mapRenderer != null && !pathPreviewActive) {
               mapRenderer.forceUpdateIdleMowerTrail();
            }
         }
      });
   };
   private static final int FLOAT_ICON_SIZE = 32;
   private JButton endDrawingButton;
   private JButton drawingPauseButton;
@@ -99,10 +128,12 @@
   private JPanel floatingButtonColumn;
   private Runnable endDrawingCallback;
   private JButton pathPreviewReturnButton;
   private boolean pathPreviewActive;
   private Runnable pathPreviewReturnAction;
   private JButton settingsReturnButton;  // 返回系统设置页面的悬浮按钮
   private JButton saveManualBoundaryButton;  // 保存手动绘制边界的按钮
   private String previewRestoreLandNumber;
   private String previewRestoreLandName;
   private Dikuai currentBoundaryPreviewDikuai;  // 当前边界预览的地块引用
   private boolean drawingPaused;
   private ImageIcon pauseIcon;
   private ImageIcon pauseActiveIcon;
@@ -142,6 +173,7 @@
   private boolean storedStopButtonActive;
   private String storedStatusBeforeDrawing;
   private boolean handheldCaptureInlineUiActive;
   private WangfanDraw returnPathDrawer;  // 往返路径绘制管理器
   private Timer handheldCaptureStatusTimer;
   private String handheldCaptureStoredStatusText;
   private Color handheldStartButtonOriginalBackground;
@@ -181,6 +213,54 @@
      // 初始化地图渲染器
      mapRenderer = new MapRenderer(visualizationPanel);
      applyIdleTrailDurationFromSettings();
      // 初始化往返路径绘制管理器
      returnPathDrawer = new WangfanDraw(this, mapRenderer, new WangfanDraw.DrawingHelper() {
         @Override
         public double[] resolveBaseLatLon() {
            return resolveCircleBaseLatLon();
         }
         @Override
         public Coordinate getLatestCoordinate() {
            return Shouye.this.getLatestCoordinate();
         }
         @Override
         public double parseDMToDecimal(String dmm, String direction) {
            return Shouye.this.parseDMToDecimal(dmm, direction);
         }
         @Override
         public double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
            return Shouye.this.convertLatLonToLocal(lat, lon, baseLat, baseLon);
         }
         @Override
         public boolean arePointsClose(Point2D.Double a, Point2D.Double b) {
            return Shouye.this.arePointsClose(a, b);
         }
         @Override
         public void enterDrawingControlMode() {
            Shouye.this.enterDrawingControlMode();
         }
         @Override
         public void exitDrawingControlMode() {
            Shouye.this.exitDrawingControlMode();
         }
         @Override
         public boolean isDrawingPaused() {
            return drawingPaused;
         }
      });
      // 设置 MapRenderer 的往返路径绘制管理器
      if (mapRenderer != null) {
         mapRenderer.setReturnPathDrawer(returnPathDrawer);
      }
      // 初始化对话框引用为null,延迟创建
      legendDialog = null;
@@ -194,6 +274,9 @@
      initializeDefaultAreaSelection();
      refreshMapForSelectedArea();
      // 启动边界警告检查定时器
      startBoundaryWarningTimer();
   }
   private void scheduleIdentifierCheck() {
@@ -205,12 +288,200 @@
               SwingUtilities.invokeLater(() -> {
                  Shouye.this.checkIdentifiersAndPromptIfNeeded();
                  Shouye.this.showInitialMowerSelfCheckDialogIfNeeded();
                  // 设置窗口关闭监听器,在关闭时保存缩放比例
                  setupWindowCloseListener();
               });
            }
         }
      };
      addHierarchyListener(listener);
   }
   /**
    * 设置窗口关闭监听器,在窗口关闭时保存当前缩放比例
    */
   private void setupWindowCloseListener() {
      Window window = SwingUtilities.getWindowAncestor(this);
      if (window != null && window instanceof JFrame) {
         JFrame frame = (JFrame) window;
         frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
               // 保存当前缩放比例
               saveCurrentScale();
               // 停止边界警告定时器
               if (boundaryWarningTimer != null && boundaryWarningTimer.isRunning()) {
                  boundaryWarningTimer.stop();
               }
               // 停止闪烁定时器
               if (warningBlinkTimer != null && warningBlinkTimer.isRunning()) {
                  warningBlinkTimer.stop();
               }
            }
         });
      }
   }
   /**
    * 保存当前地图缩放比例和视图中心坐标到配置文件
    */
   public void saveCurrentScale() {
      if (mapRenderer != null) {
         double currentScale = mapRenderer.getScale();
         double translateX = mapRenderer.getTranslateX();
         double translateY = mapRenderer.getTranslateY();
         Setsys setsys = new Setsys();
         // 保留2位小数
         setsys.updateProperty("mapScale", String.format("%.2f", currentScale));
         setsys.updateProperty("viewCenterX", String.format("%.2f", translateX));
         setsys.updateProperty("viewCenterY", String.format("%.2f", translateY));
      }
   }
   /**
    * 启动边界警告检查定时器
    */
   private void startBoundaryWarningTimer() {
      // 边界检查定时器:每500ms检查一次割草机是否在边界内
      boundaryWarningTimer = new Timer(500, e -> {
         checkMowerBoundaryStatus();
         // 同时更新蓝牙图标状态
         updateBluetoothButtonIcon();
      });
      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() {
      // 已移除进入主页时的自检提示(按用户要求删除)
@@ -237,6 +508,251 @@
         }
      }
      mapRenderer.setIdleTrailDurationSeconds(durationSeconds);
      // 应用边界距离显示设置和测量模式设置
      Setsys setsys = new Setsys();
      setsys.initializeFromProperties();
      mapRenderer.setBoundaryLengthVisible(setsys.isBoundaryLengthVisible());
      // 初始化测量模式
      boolean measurementEnabled = setsys.isMeasurementModeEnabled();
      mapRenderer.setMeasurementMode(measurementEnabled);
      if (measurementEnabled) {
         celiangmoshi.start();
      } else {
         celiangmoshi.stop();
      }
      // 初始化手动绘制边界模式
      boolean manualBoundaryDrawingEnabled = setsys.isManualBoundaryDrawingMode();
      if (mapRenderer != null) {
         mapRenderer.setManualBoundaryDrawingMode(manualBoundaryDrawingEnabled);
      }
      // 更新返回设置按钮的显示状态
      updateSettingsReturnButtonVisibility();
   }
   /**
    * 更新返回系统设置按钮的显示状态
    * 当手动绘制边界模式、显示边界距离或开启测量模式任一开启时显示
    */
   public void updateSettingsReturnButtonVisibility() {
      Setsys setsys = new Setsys();
      setsys.initializeFromProperties();
      boolean manualBoundaryDrawingEnabled = setsys.isManualBoundaryDrawingMode();
      boolean shouldShow = manualBoundaryDrawingEnabled
         || setsys.isBoundaryLengthVisible()
         || setsys.isMeasurementModeEnabled();
      if (shouldShow) {
         showSettingsReturnButton();
         // 如果手动绘制边界模式开启,显示保存按钮
         if (manualBoundaryDrawingEnabled) {
            showSaveManualBoundaryButton();
         } else {
            hideSaveManualBoundaryButton();
         }
      } else {
         hideSettingsReturnButton();
         hideSaveManualBoundaryButton();
      }
   }
   /**
    * 显示返回系统设置按钮
    */
   private void showSettingsReturnButton() {
      ensureFloatingButtonInfrastructure();
      if (settingsReturnButton == null) {
         settingsReturnButton = Fanhuibutton.createReturnButton(null);
         settingsReturnButton.setToolTipText("返回系统设置");
         settingsReturnButton.addActionListener(e -> {
            // 关闭所有相关模式
            Setsys setsys = new Setsys();
            setsys.initializeFromProperties();
            boolean modeChanged = false;
            // 关闭手动绘制边界模式
            if (setsys.isManualBoundaryDrawingMode()) {
               setsys.setManualBoundaryDrawingMode(false);
               setsys.updateProperty("manualBoundaryDrawingMode", "false");
               // 清空手动绘制的边界点
               if (mapRenderer != null) {
                  mapRenderer.clearManualBoundaryPoints();
               }
               modeChanged = true;
            }
            // 关闭显示边界距离
            if (setsys.isBoundaryLengthVisible()) {
               setsys.setBoundaryLengthVisible(false);
               setsys.updateProperty("boundaryLengthVisible", "false");
               if (mapRenderer != null) {
                  mapRenderer.setBoundaryLengthVisible(false);
               }
               modeChanged = true;
            }
            // 关闭测量模式
            if (setsys.isMeasurementModeEnabled()) {
               setsys.setMeasurementModeEnabled(false);
               setsys.updateProperty("measurementModeEnabled", "false");
               if (mapRenderer != null) {
                  mapRenderer.setMeasurementMode(false);
               }
               celiangmoshi.stop();
               modeChanged = true;
            }
            // 如果关闭了任何模式,立即隐藏返回按钮并刷新界面
            if (modeChanged) {
               // 立即隐藏返回按钮
               if (settingsReturnButton != null) {
                  settingsReturnButton.setVisible(false);
               }
               // 更新按钮列(移除返回按钮)
               rebuildFloatingButtonColumn();
               // 如果所有按钮都隐藏了,隐藏悬浮按钮面板
               if (floatingButtonPanel != null && floatingButtonColumn != null
                     && floatingButtonColumn.getComponentCount() == 0) {
                  floatingButtonPanel.setVisible(false);
               }
               // 刷新界面
               if (visualizationPanel != null) {
                  visualizationPanel.revalidate();
                  visualizationPanel.repaint();
               }
            }
            // 更新返回按钮显示状态(确保状态同步)
            updateSettingsReturnButtonVisibility();
            // 打开系统设置页面
            showSettingsDialog();
         });
      }
      settingsReturnButton.setVisible(true);
      // 隐藏绘制相关的按钮(暂停、结束绘制)
      if (drawingPauseButton != null) {
         drawingPauseButton.setVisible(false);
      }
      if (endDrawingButton != null) {
         endDrawingButton.setVisible(false);
      }
      if (floatingButtonPanel != null) {
         floatingButtonPanel.setVisible(true);
         if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
         }
      }
      rebuildFloatingButtonColumn();
   }
   /**
    * 隐藏返回系统设置按钮
    */
   private void hideSettingsReturnButton() {
      if (settingsReturnButton != null) {
         settingsReturnButton.setVisible(false);
      }
      rebuildFloatingButtonColumn();
      if (floatingButtonPanel != null && floatingButtonColumn != null
            && floatingButtonColumn.getComponentCount() == 0) {
         floatingButtonPanel.setVisible(false);
      }
   }
   /**
    * 显示保存手动绘制边界按钮
    */
   private void showSaveManualBoundaryButton() {
      ensureFloatingButtonInfrastructure();
      if (saveManualBoundaryButton == null) {
         saveManualBoundaryButton = createFloatingTextButton("保存");
         saveManualBoundaryButton.setToolTipText("保存手动绘制的边界");
         saveManualBoundaryButton.addActionListener(e -> saveManualBoundary());
      }
      saveManualBoundaryButton.setVisible(true);
      if (floatingButtonPanel != null) {
         floatingButtonPanel.setVisible(true);
         if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
         }
      }
      rebuildFloatingButtonColumn();
   }
   /**
    * 隐藏保存手动绘制边界按钮
    */
   private void hideSaveManualBoundaryButton() {
      if (saveManualBoundaryButton != null) {
         saveManualBoundaryButton.setVisible(false);
      }
      rebuildFloatingButtonColumn();
      if (floatingButtonPanel != null && floatingButtonColumn != null
            && floatingButtonColumn.getComponentCount() == 0) {
         floatingButtonPanel.setVisible(false);
      }
   }
   /**
    * 保存手动绘制的边界到文件
    */
   private void saveManualBoundary() {
      if (mapRenderer == null) {
         JOptionPane.showMessageDialog(this, "地图渲染器未初始化", "错误", JOptionPane.ERROR_MESSAGE);
         return;
      }
      List<Point2D.Double> points = mapRenderer.getManualBoundaryPoints();
      if (points == null || points.isEmpty()) {
         JOptionPane.showMessageDialog(this, "没有可保存的边界点,请先在地图上点击绘制边界", "提示", JOptionPane.WARNING_MESSAGE);
         return;
      }
      // 构建坐标字符串:x1,y1;x2,y2;...;xn,yn(单位:米,精确到小数点后2位)
      StringBuilder coordinates = new StringBuilder();
      for (int i = 0; i < points.size(); i++) {
         Point2D.Double point = points.get(i);
         if (i > 0) {
            coordinates.append(";");
         }
         coordinates.append(String.format(Locale.US, "%.2f,%.2f", point.x, point.y));
      }
      // 保存到 properties 文件
      try {
         java.util.Properties props = new java.util.Properties();
         java.io.File file = new java.io.File("shoudongbianjie.properties");
         // 如果文件存在,先加载现有内容
         if (file.exists()) {
            try (java.io.FileInputStream input = new java.io.FileInputStream(file)) {
               props.load(input);
            }
         }
         // 保存坐标
         props.setProperty("boundaryCoordinates", coordinates.toString());
         props.setProperty("pointCount", String.valueOf(points.size()));
         // 写回文件
         try (java.io.FileOutputStream output = new java.io.FileOutputStream(file)) {
            props.store(output, "手动绘制边界坐标 - 格式: x1,y1;x2,y2;...;xn,yn (单位:米,精确到小数点后2位)");
         }
         JOptionPane.showMessageDialog(this,
            String.format("边界已保存成功!\n共 %d 个点", points.size()),
            "保存成功",
            JOptionPane.INFORMATION_MESSAGE);
      } catch (Exception ex) {
         ex.printStackTrace();
         JOptionPane.showMessageDialog(this,
            "保存失败: " + ex.getMessage(),
            "错误",
            JOptionPane.ERROR_MESSAGE);
      }
   }
   private void createHeaderPanel() {
@@ -283,14 +799,28 @@
      // 添加速度显示标签
      speedLabel = new JLabel("");
      speedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
      speedLabel.setForeground(Color.GRAY);
      speedLabel.setVisible(false);  // 默认隐藏
   speedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
   speedLabel.setForeground(Color.GRAY);
   speedLabel.setVisible(false);  // 默认隐藏
   // 正在绘制边界状态标签
   drawingBoundaryLabel = new JLabel("正在绘制边界");
   drawingBoundaryLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
   drawingBoundaryLabel.setForeground(new Color(46, 139, 87));
   drawingBoundaryLabel.setVisible(false);  // 默认隐藏
   // 导航预览模式标签
   navigationPreviewLabel = new JLabel("当前导航预览模式");
   navigationPreviewLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
   navigationPreviewLabel.setForeground(new Color(46, 139, 87));
   navigationPreviewLabel.setVisible(false);  // 默认隐藏
   // 将状态与速度放在同一行,显示在地块名称下面一行
   JPanel statusRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
   statusRow.setOpaque(false);
   statusRow.add(statusLabel);
   statusRow.add(drawingBoundaryLabel);
   statusRow.add(navigationPreviewLabel);
   statusRow.add(speedLabel);
   // 左对齐标签与状态行,确保它们在 BoxLayout 中靠左显示
@@ -349,6 +879,39 @@
      // 可视化区域 - 使用MapRenderer进行绘制
      visualizationPanel = new JPanel() {
         private ImageIcon gecaojiIcon1 = null;  // 默认图标
         private ImageIcon gecaojiIcon2 = null;  // 以割草机为中心模式图标
         private static final int GECAOJI_ICON_X = 37;
         private static final int GECAOJI_ICON_Y = 10;
         private static final int GECAOJI_ICON_SIZE = 20;
         {
            // 加载割草机图标,大小20x20像素
            gecaojiIcon1 = loadScaledIcon("image/gecaojishijiao1.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
            gecaojiIcon2 = loadScaledIcon("image/gecaojishijiao2.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
         }
         /**
          * 检查鼠标位置是否在割草机图标区域内
          */
         private boolean isMouseOnGecaojiIcon(Point mousePoint) {
            return mousePoint.x >= GECAOJI_ICON_X &&
                   mousePoint.x <= GECAOJI_ICON_X + GECAOJI_ICON_SIZE &&
                   mousePoint.y >= GECAOJI_ICON_Y &&
                   mousePoint.y <= GECAOJI_ICON_Y + GECAOJI_ICON_SIZE;
         }
         @Override
         public String getToolTipText(MouseEvent event) {
            // 如果鼠标在割草机图标区域内,显示提示文字
            if (isMouseOnGecaojiIcon(event.getPoint())) {
               // 根据当前模式显示不同的提示文字
               return centerOnMowerMode ? "取消以割草机为中心" : "以割草机为中心";
            }
            // 不在图标上时返回null,不显示工具提示框
            return null;
         }
         @Override
         protected void paintComponent(Graphics g) {
            super.paintComponent(g);
@@ -356,14 +919,75 @@
            if (mapRenderer != null) {
               mapRenderer.renderMap(g);
            }
            // 检查是否需要显示警告图标
            if (isMowerOutsideBoundary && warningIconVisible) {
               // 绘制红色三角形警告图标(带叹号)
               drawWarningIcon(g, GECAOJI_ICON_X, GECAOJI_ICON_Y, GECAOJI_ICON_SIZE);
            } else {
               // 根据模式选择不同的图标
               ImageIcon iconToDraw = centerOnMowerMode ? gecaojiIcon2 : gecaojiIcon1;
               if (iconToDraw != null) {
                  // 绘制割草机图标
                  // 水平方向与速度指示器对齐(x=37)
                  // 垂直方向与卫星状态图标对齐(y=10,速度指示器面板顶部边距10像素,使图标中心对齐)
                  g.drawImage(iconToDraw.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());
      // 添加鼠标点击监听器,检测是否点击了割草机图标
      visualizationPanel.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseClicked(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
               Point clickPoint = e.getPoint();
               // 检查是否点击了割草机图标区域(37, 10, 20, 20)
               if (clickPoint.x >= 37 && clickPoint.x <= 57 &&
                   clickPoint.y >= 10 && clickPoint.y <= 30) {
                  // 切换以割草机为中心的模式
                  toggleCenterOnMowerMode();
               }
            }
         }
      });
      JPanel speedIndicatorPanel = createSpeedIndicatorPanel();
      visualizationPanel.add(speedIndicatorPanel, BorderLayout.NORTH);
      // 创建功能按钮面板(放在左上角)
      // 创建功能按钮面板
      JPanel functionButtonsPanel = new JPanel();
      functionButtonsPanel.setLayout(new BoxLayout(functionButtonsPanel, BoxLayout.Y_AXIS));
      functionButtonsPanel.setOpaque(false);
@@ -658,12 +1282,14 @@
         }
      });
      ensureBluetoothIconsLoaded();
      bluetoothConnected = Bluelink.isConnected();
      ImageIcon initialIcon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
      // 根据串口连接状态显示图标
      SerialPortService service = sendmessage.getActiveService();
      boolean serialConnected = (service != null && service.isOpen());
      ImageIcon initialIcon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
      if (initialIcon != null) {
         button.setIcon(initialIcon);
      } else {
         button.setText(bluetoothConnected ? "已连" : "蓝牙");
         button.setText(serialConnected ? "已连" : "蓝牙");
      }
      return button;
   }
@@ -787,6 +1413,8 @@
      remoteBtn.addActionListener(e -> showRemoteControlDialog());
      areaSelectBtn.addActionListener(e -> {
         // 点击“地块”直接打开地块管理对话框(若需要可传入特定地块编号)
//       Dikuaiguanli.showDikuaiManagement(this, null);
         // 直接进入地块管理界面
         Dikuaiguanli.showDikuaiManagement(this, null);
      });
      baseStationBtn.addActionListener(e -> showBaseStationDialog());
@@ -830,7 +1458,7 @@
         if (parentWindow != null) {
            // 使用 yaokong 包中的 RemoteControlDialog 实现
            remoteDialog = new yaokong.RemoteControlDialog(this, THEME_COLOR, speedLabel);
         } else {
         } else {/*  */
            remoteDialog = new yaokong.RemoteControlDialog((JFrame) null, THEME_COLOR, speedLabel);
         }
      }
@@ -1132,26 +1760,115 @@
      }
      startButtonShowingPause = !startButtonShowingPause;
      if (!startButtonShowingPause) {
         statusLabel.setText("作业中");
         if (stopButtonActive) {
            stopButtonActive = false;
            updateStopButtonIcon();
         }
         if (!beginMowingSession()) {
         // 检查割草机是否在作业地块边界范围内
         if (!checkMowerInBoundary()) {
            startButtonShowingPause = true;
            statusLabel.setText("待机");
            updateStartButtonAppearance();
            return;
         }
         Control04.sendStartCommandIfDebugSerialOpen();
         statusLabel.setText("作业中");
         if (stopButtonActive) {
            stopButtonActive = false;
            updateStopButtonIcon();
         }
      if (!beginMowingSession()) {
         startButtonShowingPause = true;
         statusLabel.setText("待机");
         updateStartButtonAppearance();
         return;
      }
      } else {
         statusLabel.setText("暂停中");
         pauseMowingSession();
         Control04.sendPauseCommandIfDebugSerialOpen();
      }
      updateStartButtonAppearance();
   }
   /**
    * 检查割草机是否在当前选中的作业地块边界范围内
    * @return 如果割草机在边界内返回true,否则返回false并显示提示
    */
   private boolean checkMowerInBoundary() {
      if (mapRenderer == null) {
         return true; // 如果没有地图渲染器,跳过检查
      }
      // 获取当前边界
      List<Point2D.Double> boundary = mapRenderer.getCurrentBoundary();
      if (boundary == null || boundary.size() < 3) {
         return true; // 如果没有边界或边界点不足,跳过检查
      }
      // 获取割草机位置
      Gecaoji mower = mapRenderer.getMower();
      if (mower == null || !mower.hasValidPosition()) {
         showCustomMessageDialog("无法获取割草机位置,请检查设备连接", "提示");
         return false;
      }
      Point2D.Double mowerPosition = mower.getPosition();
      if (mowerPosition == null) {
         showCustomMessageDialog("无法获取割草机位置,请检查设备连接", "提示");
         return false;
      }
      // 使用 MowerBoundaryChecker 检查是否在边界内
      boolean isInside = MowerBoundaryChecker.isInsideBoundaryPoints(
         boundary,
         mowerPosition.x,
         mowerPosition.y
      );
      if (!isInside) {
         showCustomMessageDialog("请将割草机开到作业地块内然后点击开始作业", "提示");
         return false;
      }
      return true;
   }
   /**
    * 显示自定义消息对话框,使用 buttonset 创建确定按钮
    * @param message 消息内容
    * @param title 对话框标题
    */
   private void showCustomMessageDialog(String message, String title) {
      Window parentWindow = SwingUtilities.getWindowAncestor(this);
      JDialog dialog = new JDialog(parentWindow, title, Dialog.ModalityType.APPLICATION_MODAL);
      dialog.setLayout(new BorderLayout(20, 20));
      dialog.setResizable(false);
      // 内容面板
      JPanel contentPanel = new JPanel(new BorderLayout(0, 15));
      contentPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 10, 20));
      contentPanel.setBackground(Color.WHITE);
      // 消息标签
      JLabel messageLabel = new JLabel("<html><div style='text-align: center;'>" + message + "</div></html>");
      messageLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
      messageLabel.setHorizontalAlignment(SwingConstants.CENTER);
      contentPanel.add(messageLabel, BorderLayout.CENTER);
      // 按钮面板
      JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
      buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
      buttonPanel.setOpaque(false);
      // 使用 buttonset 创建确定按钮
      JButton okButton = buttonset.createStyledButton("确定", THEME_COLOR);
      okButton.addActionListener(e -> dialog.dispose());
      buttonPanel.add(okButton);
      contentPanel.add(buttonPanel, BorderLayout.SOUTH);
      dialog.add(contentPanel, BorderLayout.CENTER);
      dialog.pack();
      dialog.setLocationRelativeTo(this);
      dialog.setVisible(true);
   }
   private void handleStopAction() {
      if (handheldCaptureInlineUiActive) {
         handleHandheldFinishAction();
@@ -1167,7 +1884,6 @@
         statusLabel.setText("已结束");
         startButtonShowingPause = false;
         stopMowingSession();
         Control04.sendStopCommandIfDebugSerialOpen();
      } else {
         statusLabel.setText("待机");
         startButtonShowingPause = true;
@@ -1177,7 +1893,11 @@
   }
   private void handleDrawingStopFromControlPanel() {
      if (endDrawingCallback != null) {
      // 如果是往返路径绘制模式,调用完成绘制回调
      if (returnPathDrawer != null && returnPathDrawer.isActive()) {
         returnPathDrawer.stop();
         returnPathDrawer.executeFinishCallback();
      } else if (endDrawingCallback != null) {
         endDrawingCallback.run();
      } else {
         addzhangaiwu.finishDrawingSession();
@@ -1409,7 +2129,12 @@
         updateStopButtonIcon();
      }
      if (statusLabel != null) {
         statusLabel.setText(storedStatusBeforeDrawing != null ? storedStatusBeforeDrawing : "待机");
         // 如果是往返路径绘制,退出时恢复为"待机"
         if (returnPathDrawer != null && returnPathDrawer.isActive()) {
            statusLabel.setText("待机");
         } else {
            statusLabel.setText(storedStatusBeforeDrawing != null ? storedStatusBeforeDrawing : "待机");
         }
      }
      storedStatusBeforeDrawing = null;
   }
@@ -1424,7 +2149,8 @@
         startBtn.setText(drawingPaused ? "开始绘制" : "暂停绘制");
      }
      if (stopBtn != null) {
         stopBtn.setText("结束绘制");
         // 如果是往返路径绘制模式,显示"完成绘制",否则显示"结束绘制"
         stopBtn.setText((returnPathDrawer != null && returnPathDrawer.isActive()) ? "完成绘制" : "结束绘制");
      }
   }
@@ -1459,19 +2185,15 @@
      if (bluetoothBtn == null) {
         return;
      }
      if (Bluelink.isConnected()) {
         Bluelink.disconnect();
         bluetoothConnected = false;
      } else {
         boolean success = Bluelink.connect();
         if (success) {
            bluetoothConnected = true;
         } else {
            bluetoothConnected = false;
            JOptionPane.showMessageDialog(this, "蓝牙连接失败,请重试", "提示", JOptionPane.WARNING_MESSAGE);
         }
      }
      updateBluetoothButtonIcon();
      // 弹出系统调试页面
      showDebugDialog();
   }
   private void showDebugDialog() {
      Window parentWindow = SwingUtilities.getWindowAncestor(this);
      debug debugDialog = new debug(parentWindow, THEME_COLOR);
      debugDialog.setLocationRelativeTo(this); // 居中显示在首页
      debugDialog.setVisible(true);
   }
   private void updateBluetoothButtonIcon() {
@@ -1479,13 +2201,15 @@
         return;
      }
      ensureBluetoothIconsLoaded();
      bluetoothConnected = Bluelink.isConnected();
      ImageIcon icon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
      // 根据串口连接状态显示图标
      SerialPortService service = sendmessage.getActiveService();
      boolean serialConnected = (service != null && service.isOpen());
      ImageIcon icon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
      if (icon != null) {
         bluetoothBtn.setIcon(icon);
         bluetoothBtn.setText(null);
      } else {
         bluetoothBtn.setText(bluetoothConnected ? "已连" : "蓝牙");
         bluetoothBtn.setText(serialConnected ? "已连" : "蓝牙");
      }
   }
@@ -1497,7 +2221,7 @@
      JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
      rightPanel.setOpaque(false);
      fixQualityIndicator = new FixQualityIndicator();
   fixQualityIndicator = new gpszhuangtai(THEME_COLOR);
      fixQualityIndicator.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      fixQualityIndicator.addMouseListener(new MouseAdapter() {
         @Override
@@ -1722,6 +2446,12 @@
      mapRenderer.setHandheldMowerIconActive(active);
   }
   public void setStatusLabelText(String text) {
      if (statusLabel != null) {
         statusLabel.setText(text);
      }
   }
   public boolean startMowerBoundaryCapture() {
      if (mapRenderer == null) {
         return false;
@@ -2365,7 +3095,11 @@
      if (drawingControlModeActive) {
         updateDrawingControlButtonLabels();
         if (statusLabel != null) {
            statusLabel.setText(paused ? "绘制暂停" : "绘制中");
            if (returnPathDrawer != null && returnPathDrawer.isActive()) {
               statusLabel.setText("正在绘制往返路径");
            } else {
               statusLabel.setText(paused ? "绘制暂停" : "绘制中");
            }
         }
      }
   }
@@ -2383,6 +3117,19 @@
      circleDialogMode = false;
      hideCircleGuidancePanel();
      enterDrawingControlMode();
      // 隐藏返回设置按钮(如果显示绘制按钮,则不应该显示返回按钮)
//    if (settingsReturnButton != null) {
//       settingsReturnButton.setVisible(false);
//    }
      // 显示"正在绘制边界"提示
      if (drawingBoundaryLabel != null) {
         // 如果是往返路径绘制,不显示此标签(状态栏已显示"正在绘制往返路径")
//       boolean isReturnPathDrawing = returnPathDrawer != null && returnPathDrawer.isActive();
//       drawingBoundaryLabel.setVisible(!isReturnPathDrawing);
         drawingBoundaryLabel.setVisible(true);
      }
      boolean enableCircleGuidance = drawingShape != null
            && "circle".equalsIgnoreCase(drawingShape.trim());
@@ -2492,6 +3239,20 @@
         floatingButtonColumn.add(pathPreviewReturnButton);
         added = true;
      }
      if (saveManualBoundaryButton != null && saveManualBoundaryButton.isVisible()) {
         if (added) {
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
         }
         floatingButtonColumn.add(saveManualBoundaryButton);
         added = true;
      }
      if (settingsReturnButton != null && settingsReturnButton.isVisible()) {
         if (added) {
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
         }
         floatingButtonColumn.add(settingsReturnButton);
         added = true;
      }
      floatingButtonColumn.revalidate();
      floatingButtonColumn.repaint();
   }
@@ -2858,7 +3619,34 @@
      if (latest == null) {
         return false;
      }
      return lastCapturedCoordinate == null || latest != lastCapturedCoordinate;
      // 检查是否有新的坐标(与上次采集的不同)
      if (lastCapturedCoordinate != null && latest == lastCapturedCoordinate) {
         return false;
      }
      // 检查定位状态是否为4(固定解)
      // 当选择割草机绘制圆形障碍物时,需要检查设备编号和定位状态
      Device device = Device.getGecaoji();
      if (device == null) {
         return false;
      }
      String positioningStatus = device.getPositioningStatus();
      if (positioningStatus == null || !"4".equals(positioningStatus.trim())) {
         return false;
      }
      // 检查设备编号是否匹配割草机编号
      String mowerId = Setsys.getPropertyValue("mowerId");
      String deviceId = device.getMowerNumber();
      if (mowerId != null && !mowerId.trim().isEmpty()) {
         if (deviceId == null || !mowerId.trim().equals(deviceId.trim())) {
            return false;
         }
      }
      return true;
   }
   private void applyCirclePrimaryButtonState(boolean enabled) {
@@ -2934,34 +3722,11 @@
   }
   private double parseDMToDecimal(String dmm, String direction) {
      if (dmm == null || dmm.trim().isEmpty()) {
         return Double.NaN;
      }
      try {
         String trimmed = dmm.trim();
         int dotIndex = trimmed.indexOf('.');
         if (dotIndex < 2) {
            return Double.NaN;
         }
         int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
         double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
         double decimal = degrees + minutes / 60.0;
         if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
            decimal = -decimal;
         }
         return decimal;
      } catch (NumberFormatException ex) {
         return Double.NaN;
      }
      return Gpstoxuzuobiao.parseDMToDecimal(dmm, direction);
   }
   private double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
      double deltaLat = lat - baseLat;
      double deltaLon = lon - baseLon;
      double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
      double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
      double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
      return new double[]{eastMeters, northMeters};
      return Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon);
   }
   private CircleSolution fitCircleFromPoints(List<double[]> points) {
@@ -3060,6 +3825,12 @@
         activeBoundaryMode = BoundaryCaptureMode.NONE;
      }
      endDrawingCallback = null;
      // 隐藏"正在绘制边界"提示
      if (drawingBoundaryLabel != null) {
         drawingBoundaryLabel.setVisible(false);
      }
      visualizationPanel.revalidate();
      visualizationPanel.repaint();
      setHandheldMowerIconActive(false);
@@ -3074,9 +3845,8 @@
         endDrawingButton.setVisible(false);
      }
      if (pathPreviewReturnButton == null) {
         pathPreviewReturnButton = createFloatingTextButton("返回");
         pathPreviewReturnButton = Fanhuibutton.createReturnButton(e -> handlePathPreviewReturn());
         pathPreviewReturnButton.setToolTipText("返回新增地块步骤");
         pathPreviewReturnButton.addActionListener(e -> handlePathPreviewReturn());
      }
      pathPreviewReturnButton.setVisible(true);
      if (floatingButtonPanel != null) {
@@ -3113,7 +3883,11 @@
         String obstacles,
         String plannedPath,
         Runnable returnAction) {
      if (mapRenderer == null || !isMeaningfulValue(plannedPath)) {
      if (mapRenderer == null) {
         return false;
      }
      // 允许没有路径的预览(例如障碍物预览),只要有返回回调即可
      if (!isMeaningfulValue(plannedPath) && returnAction == null) {
         return false;
      }
@@ -3140,10 +3914,17 @@
      mapRenderer.setCurrentBoundary(boundary, landNumber, landName);
      mapRenderer.setCurrentObstacles(obstacles, landNumber);
      mapRenderer.setCurrentPlannedPath(plannedPath);
      // 只有在有路径时才设置路径
      if (isMeaningfulValue(plannedPath)) {
         mapRenderer.setCurrentPlannedPath(plannedPath);
      } else {
         mapRenderer.setCurrentPlannedPath(null);
      }
      mapRenderer.clearHandheldBoundaryPreview();
      mapRenderer.setBoundaryPointSizeScale(1.0d);
      mapRenderer.setBoundaryPointsVisible(isMeaningfulValue(boundary));
      // 启用障碍物边界点显示
      mapRenderer.setObstaclePointsVisible(isMeaningfulValue(obstacles));
      String displayName = isMeaningfulValue(landName) ? landName : landNumber;
      updateCurrentAreaName(displayName);
@@ -3194,6 +3975,101 @@
      return mapRenderer;
   }
   /**
    * 获取控制面板(用于导航预览时替换按钮)
    * @return 控制面板
    */
   public JPanel getControlPanel() {
      return controlPanel;
   }
   /**
    * 获取开始按钮(用于导航预览时隐藏)
    * @return 开始按钮
    */
   public JButton getStartButton() {
      return startBtn;
   }
   /**
    * 获取结束按钮(用于导航预览时隐藏)
    * @return 结束按钮
    */
   public JButton getStopButton() {
      return stopBtn;
   }
   /**
    * 设置导航预览模式标签的显示状态
    * @param visible 是否显示
    */
   public void setNavigationPreviewLabelVisible(boolean visible) {
      if (navigationPreviewLabel != null) {
         navigationPreviewLabel.setVisible(visible);
      }
   }
   /**
    * 更新导航预览状态显示
    * @param active 是否处于导航预览模式
    */
   public void updateNavigationPreviewStatus(boolean active) {
      setNavigationPreviewLabelVisible(active);
   }
   /**
    * 更新割草进度显示
    * @param percentage 完成百分比
    * @param completedArea 已完成面积(平方米)
    * @param totalArea 总面积(平方米)
    */
   public void updateMowingProgress(double percentage, double completedArea, double totalArea) {
      if (mowingProgressLabel == null) {
         return;
      }
      if (totalArea <= 0) {
         mowingProgressLabel.setText("--%");
         mowingProgressLabel.setToolTipText("暂无地块面积数据");
         return;
      }
      double percent = Math.max(0.0, Math.min(100.0, percentage));
      mowingProgressLabel.setText(String.format(Locale.US, "%.1f%%", percent));
      mowingProgressLabel.setToolTipText(String.format(Locale.US, "%.1f㎡ / %.1f㎡", completedArea, totalArea));
   }
   /**
    * 更新割草机速度显示
    * @param speed 速度值(单位:km/h)
    */
   public void updateMowerSpeed(double speed) {
      if (mowerSpeedValueLabel == null) {
         return;
      }
      if (speed < 0) {
         mowerSpeedValueLabel.setText("--");
      } else {
         mowerSpeedValueLabel.setText(String.format(Locale.US, "%.1f", speed));
      }
      if (mowerSpeedUnitLabel != null) {
         mowerSpeedUnitLabel.setText("km/h");
      }
   }
   /**
    * 获取可视化面板实例
    */
   public JPanel getVisualizationPanel() {
      return visualizationPanel;
   }
   /**
    * 获取主内容面板实例(用于添加浮动按钮)
    */
   public JPanel getMainContentPanel() {
      return mainContentPanel;
   }
   public void updateCurrentAreaName(String areaName) {
      if (areaNameLabel == null) {
         return;
@@ -3290,47 +4166,255 @@
      return !"未选择地块".equals(trimmed);
   }
   private final class FixQualityIndicator extends JComponent {
      private static final long serialVersionUID = 1L;
      private static final int DIAMETER = 16;
      private String currentCode;
      private Color currentColor = new Color(160, 160, 160);
      private FixQualityIndicator() {
         setPreferredSize(new Dimension(DIAMETER, DIAMETER));
         setMinimumSize(new Dimension(DIAMETER, DIAMETER));
         setMaximumSize(new Dimension(DIAMETER, DIAMETER));
         setToolTipText("未知");
      }
      private void setQuality(String code) {
         if (Objects.equals(currentCode, code)) {
            return;
         }
         currentCode = code;
         currentColor = resolveFixQualityColor(code);
         setToolTipText(resolveFixQualityDescription(code));
         repaint();
      }
      @Override
      protected void paintComponent(Graphics g) {
         super.paintComponent(g);
         Graphics2D g2 = (Graphics2D) g.create();
         try {
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            int diameter = Math.min(getWidth(), getHeight()) - 2;
            int x = (getWidth() - diameter) / 2;
            int y = (getHeight() - diameter) / 2;
            g2.setColor(currentColor);
            g2.fillOval(x, y, diameter, diameter);
            g2.setColor(new Color(255, 255, 255, 128));
            g2.drawOval(x, y, diameter, diameter);
         } finally {
            g2.dispose();
         }
      }
   }
    /**
     * 启动往返路径绘制
     * @param finishCallback 完成绘制时的回调
     * @param isHandheld 是否使用手持设备模式
     * @return 是否成功启动
     */
    public boolean startReturnPathDrawing(Runnable finishCallback, boolean isHandheld) {
        if (returnPathDrawer == null) {
            return false;
        }
        return returnPathDrawer.start(finishCallback, isHandheld);
    }
    /**
     * 停止往返路径绘制
     */
    public void stopReturnPathDrawing() {
        if (returnPathDrawer != null) {
            returnPathDrawer.stop();
        }
    }
    /**
     * 获取往返路径绘制管理器
     */
    public WangfanDraw getReturnPathDrawer() {
        return returnPathDrawer;
    }
    /**
     * 启动往返路径预览
     * @param coordinatesStr 路径坐标字符串 (x,y;x,y)
     * @param returnCallback 返回回调
     */
    public void startReturnPathPreview(String coordinatesStr, Runnable returnCallback) {
        if (returnPathDrawer == null || coordinatesStr == null || coordinatesStr.isEmpty()) {
            return;
        }
        // 解析坐标
        List<Point2D.Double> points = new ArrayList<>();
        String[] pairs = coordinatesStr.split(";");
        for (String pair : pairs) {
            String[] xy = pair.split(",");
            if (xy.length == 2) {
                try {
                    double x = Double.parseDouble(xy[0]);
                    double y = Double.parseDouble(xy[1]);
                    points.add(new Point2D.Double(x, y));
                } catch (NumberFormatException e) {
                    // 忽略无效坐标
                }
            }
        }
        if (points.isEmpty()) {
            JOptionPane.showMessageDialog(this, "没有有效的路径点可预览", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        // 设置预览点
        returnPathDrawer.setPoints(points);
        if (mapRenderer != null) {
            mapRenderer.setPreviewReturnPath(points);
        }
        // 开启预览模式
        pathPreviewActive = true;
        if (mapRenderer != null) {
            mapRenderer.clearIdleTrail();
        }
        pathPreviewReturnAction = returnCallback;
        // 确保悬浮按钮基础设施已创建
        ensureFloatingButtonInfrastructure();
        // 创建或显示返回按钮
        if (pathPreviewReturnButton == null) {
            // 使用 Fanhuibutton 创建返回按钮
            pathPreviewReturnButton = publicway.Fanhuibutton.createReturnButton(e -> {
                // 停止预览
                stopReturnPathPreview();
            });
            pathPreviewReturnButton.setToolTipText("返回绘制页面");
        }
        // 隐藏其他悬浮按钮
        hideFloatingDrawingControls();
        // 显示返回按钮
        pathPreviewReturnButton.setVisible(true);
        floatingButtonPanel.setVisible(true);
        if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
        }
        rebuildFloatingButtonColumn();
        visualizationPanel.revalidate();
        visualizationPanel.repaint();
    }
    /**
     * 停止往返路径预览
     */
    private void stopReturnPathPreview() {
        pathPreviewActive = false;
        // 清空预览点
        if (returnPathDrawer != null) {
            returnPathDrawer.clearPoints();
        }
        if (mapRenderer != null) {
            mapRenderer.setPreviewReturnPath(null);
        }
        // 隐藏返回按钮
        if (pathPreviewReturnButton != null) {
            pathPreviewReturnButton.setVisible(false);
        }
        // 隐藏悬浮面板
        if (floatingButtonPanel != null) {
            floatingButtonPanel.setVisible(false);
        }
        // 执行返回回调
        if (pathPreviewReturnAction != null) {
            pathPreviewReturnAction.run();
        }
    }
    /**
     * 显示边界预览(原始边界-紫色,优化后边界-绿色)
     * @param dikuai 地块对象
     * @param optimizedBoundary 优化后的边界坐标字符串
     * @param returnCallback 返回回调
     */
    public static void showBoundaryPreview(dikuai.Dikuai dikuai, String optimizedBoundary, Runnable returnCallback) {
        Shouye shouye = getInstance();
        if (shouye == null || shouye.mapRenderer == null || dikuai == null) {
            return;
        }
        // 保存当前地块引用
        shouye.currentBoundaryPreviewDikuai = dikuai;
        // 获取原始边界XY坐标
        String originalBoundaryXY = dikuai.getBoundaryOriginalXY();
        // 设置边界预览
        shouye.mapRenderer.setBoundaryPreview(originalBoundaryXY, optimizedBoundary);
        // 设置边界预览更新回调,用于保存删除坐标点后的边界
        shouye.mapRenderer.setBoundaryPreviewUpdateCallback(updatedBoundary -> {
            if (shouye.currentBoundaryPreviewDikuai != null && updatedBoundary != null) {
                // 保存更新后的边界坐标
                Dikuai.updateField(shouye.currentBoundaryPreviewDikuai.getLandNumber(), "boundaryCoordinates", updatedBoundary);
                java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Dikuai.updateField(shouye.currentBoundaryPreviewDikuai.getLandNumber(), "updateTime", sdf.format(new java.util.Date()));
                Dikuai.saveToProperties();
                // 同步更新当前地块对象的内存值(确保返回时能获取到最新值)
                shouye.currentBoundaryPreviewDikuai.setBoundaryCoordinates(updatedBoundary);
                // 更新预览边界(重新设置以刷新显示)
                shouye.mapRenderer.setBoundaryPreview(originalBoundaryXY, updatedBoundary);
            }
        });
        // 停止绘制割草机实时拖尾
        if (shouye.mapRenderer != null) {
            shouye.mapRenderer.setIdleTrailSuppressed(true);
        }
        // 设置返回回调
        shouye.pathPreviewReturnAction = returnCallback;
        shouye.pathPreviewActive = true;
        // 确保悬浮按钮基础设施已创建
        shouye.ensureFloatingButtonInfrastructure();
        // 创建或显示返回按钮
        if (shouye.pathPreviewReturnButton == null) {
            shouye.pathPreviewReturnButton = publicway.Fanhuibutton.createReturnButton(e -> shouye.handleBoundaryPreviewReturn());
            shouye.pathPreviewReturnButton.setToolTipText("返回边界编辑页面");
        }
        // 隐藏其他悬浮按钮
        shouye.hideFloatingDrawingControls();
        // 显示返回按钮
        shouye.pathPreviewReturnButton.setVisible(true);
        if (shouye.floatingButtonPanel != null) {
            shouye.floatingButtonPanel.setVisible(true);
            if (shouye.floatingButtonPanel.getParent() != shouye.visualizationPanel) {
                shouye.visualizationPanel.add(shouye.floatingButtonPanel, BorderLayout.SOUTH);
            }
        }
        shouye.rebuildFloatingButtonColumn();
        shouye.visualizationPanel.revalidate();
        shouye.visualizationPanel.repaint();
    }
    /**
     * 处理边界预览返回
     */
    private void handleBoundaryPreviewReturn() {
        Runnable callback = pathPreviewReturnAction;
        exitBoundaryPreview();
        if (callback != null) {
            callback.run();
        }
    }
    /**
     * 退出边界预览
     */
    private void exitBoundaryPreview() {
        pathPreviewActive = false;
        // 清除当前地块引用
        currentBoundaryPreviewDikuai = null;
        // 恢复绘制割草机实时拖尾
        if (mapRenderer != null) {
            mapRenderer.setIdleTrailSuppressed(false);
        }
        // 清除边界预览
        if (mapRenderer != null) {
            mapRenderer.clearBoundaryPreview();
            mapRenderer.setBoundaryPreviewUpdateCallback(null);
        }
        // 隐藏返回按钮
        if (pathPreviewReturnButton != null) {
            pathPreviewReturnButton.setVisible(false);
        }
        // 隐藏悬浮面板
        if (floatingButtonPanel != null) {
            floatingButtonPanel.setVisible(false);
        }
        visualizationPanel.revalidate();
        visualizationPanel.repaint();
    }
   // 测试方法
    public static void main(String[] args) {