826220679@qq.com
2 天以前 48ee74129bb09a817a0bbbabe860c4007b74c66b
src/zhuye/Shouye.java
@@ -19,23 +19,23 @@
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;
/**
 * 首页界面 - 适配6.5寸竖屏,使用独立的MapRenderer进行绘制
 */
@@ -107,12 +107,14 @@
   // 地图渲染器
   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) {
            if (mapRenderer != null && !pathPreviewActive) {
               mapRenderer.forceUpdateIdleMowerTrail();
            }
         }
@@ -125,8 +127,9 @@
   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 boolean drawingPaused;
@@ -168,6 +171,7 @@
   private boolean storedStopButtonActive;
   private String storedStatusBeforeDrawing;
   private boolean handheldCaptureInlineUiActive;
   private WangfanDraw returnPathDrawer;  // 往返路径绘制管理器
   private Timer handheldCaptureStatusTimer;
   private String handheldCaptureStoredStatusText;
   private Color handheldStartButtonOriginalBackground;
@@ -207,6 +211,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;
@@ -467,6 +519,238 @@
      } 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 = createFloatingTextButton("返回");
         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() {
@@ -1605,7 +1889,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();
@@ -1852,7 +2140,8 @@
         startBtn.setText(drawingPaused ? "开始绘制" : "暂停绘制");
      }
      if (stopBtn != null) {
         stopBtn.setText("结束绘制");
         // 如果是往返路径绘制模式,显示"完成绘制",否则显示"结束绘制"
         stopBtn.setText((returnPathDrawer != null && returnPathDrawer.isActive()) ? "完成绘制" : "结束绘制");
      }
   }
@@ -2810,6 +3099,11 @@
      hideCircleGuidancePanel();
      enterDrawingControlMode();
      
      // 隐藏返回设置按钮(如果显示绘制按钮,则不应该显示返回按钮)
      if (settingsReturnButton != null) {
         settingsReturnButton.setVisible(false);
      }
      // 显示"正在绘制边界"提示
      if (drawingBoundaryLabel != null) {
         drawingBoundaryLabel.setVisible(true);
@@ -2923,6 +3217,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();
   }
@@ -3392,34 +3700,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) {
@@ -3704,6 +3989,52 @@
   }
   /**
    * 更新导航预览状态显示
    * @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() {
@@ -3815,6 +4146,135 @@
   }
    
    /**
     * 启动往返路径绘制
     * @param finishCallback 完成绘制时的回调
     * @return 是否成功启动
     */
    public boolean startReturnPathDrawing(Runnable finishCallback) {
        if (returnPathDrawer == null) {
            return false;
        }
        return returnPathDrawer.start(finishCallback);
    }
    /**
     * 停止往返路径绘制
     */
    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) {
            pathPreviewReturnButton = publicway.buttonset.createStyledButton("返回", null);
            pathPreviewReturnButton.setToolTipText("返回绘制页面");
            pathPreviewReturnButton.addActionListener(e -> {
                // 停止预览
                stopReturnPathPreview();
            });
        }
        // 隐藏其他悬浮按钮
        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();
        }
    }
   // 测试方法
    public static void main(String[] args) {