package zhuye;
|
|
import javax.swing.*;
|
import javax.swing.Timer;
|
|
import baseStation.BaseStation;
|
import set.Setsys;
|
import baseStation.BaseStationDialog;
|
|
import java.awt.*;
|
import java.awt.event.*;
|
|
import chuankou.dellmessage;
|
import dikuai.Dikuai;
|
import dikuai.Dikuaiguanli;
|
import dikuai.addzhangaiwu;
|
import gecaoji.Device;
|
import set.Sets;
|
import udpdell.UDPServer;
|
import zhangaiwu.AddDikuai;
|
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.function.Consumer;
|
import java.awt.geom.Point2D;
|
|
/**
|
* 首页界面 - 适配6.5寸竖屏,使用独立的MapRenderer进行绘制
|
*/
|
public class Shouye extends JPanel {
|
private static final long serialVersionUID = 1L;
|
private static Shouye instance;
|
|
// 主题颜色
|
private final Color THEME_COLOR = new Color(46, 139, 87);
|
private final Color THEME_HOVER_COLOR = new Color(30, 107, 69);
|
private final Color BACKGROUND_COLOR = new Color(250, 250, 250);
|
private final Color PANEL_BACKGROUND = new Color(255, 255, 255);
|
private final Color STATUS_PAUSE_COLOR = new Color(255, 165, 0);
|
|
// 组件
|
private JPanel headerPanel;
|
private JPanel mainContentPanel;
|
private JPanel controlPanel;
|
private JPanel navigationPanel;
|
private JPanel visualizationPanel;
|
|
// 按钮
|
private JButton startBtn;
|
private JButton stopBtn;
|
private JButton legendBtn;
|
private JButton remoteBtn;
|
private JButton areaSelectBtn;
|
private JButton baseStationBtn;
|
private JButton bluetoothBtn;
|
private JLabel dataPacketCountLabel;
|
private JLabel mowerSpeedValueLabel;
|
private JLabel mowerSpeedUnitLabel;
|
private JLabel mowingProgressLabel;
|
private FixQualityIndicator fixQualityIndicator;
|
|
// 导航按钮
|
private JButton homeNavBtn;
|
private JButton areasNavBtn;
|
private JButton settingsNavBtn;
|
|
// 状态显示
|
private JLabel statusLabel;
|
private JLabel areaNameLabel;
|
|
// 当前选中的导航按钮
|
private JButton currentNavButton;
|
|
// 对话框引用
|
private LegendDialog legendDialog;
|
private RemoteControlDialog remoteDialog;
|
private AreaSelectionDialog areaDialog;
|
private BaseStationDialog baseStationDialog;
|
private Sets settingsDialog;
|
private BaseStation baseStation;
|
|
private final Consumer<String> serialLineListener = line -> SwingUtilities.invokeLater(this::updateDataPacketCountLabel);
|
|
// 地图渲染器
|
private MapRenderer mapRenderer;
|
private static final int FLOAT_ICON_SIZE = 32;
|
private JButton endDrawingButton;
|
private JButton drawingPauseButton;
|
private JPanel floatingButtonPanel;
|
private JPanel floatingButtonColumn;
|
private Runnable endDrawingCallback;
|
private JButton pathPreviewReturnButton;
|
private boolean pathPreviewActive;
|
private Runnable pathPreviewReturnAction;
|
private String previewRestoreLandNumber;
|
private String previewRestoreLandName;
|
private boolean drawingPaused;
|
private ImageIcon pauseIcon;
|
private ImageIcon pauseActiveIcon;
|
private ImageIcon endIcon;
|
private ImageIcon bluetoothIcon;
|
private ImageIcon bluetoothLinkedIcon;
|
private JPanel circleGuidancePanel;
|
private JLabel circleGuidanceLabel;
|
private JButton circleGuidancePrimaryButton;
|
private JButton circleGuidanceSecondaryButton;
|
private int circleGuidanceStep;
|
private JDialog circleGuidanceDialog;
|
private boolean circleDialogMode;
|
private ComponentAdapter circleDialogOwnerAdapter;
|
private static final double METERS_PER_DEGREE_LAT = 111320.0d;
|
private static final double HANDHELD_DUPLICATE_THRESHOLD_METERS = 0.01d;
|
private final List<double[]> circleCapturedPoints = new ArrayList<>();
|
private double[] circleBaseLatLon;
|
private Timer circleDataMonitor;
|
private Coordinate lastCapturedCoordinate;
|
private boolean handheldCaptureActive;
|
private int handheldCapturedPoints;
|
private final List<Point2D.Double> handheldTemporaryPoints = new ArrayList<>();
|
private final List<Point2D.Double> mowerTemporaryPoints = new ArrayList<>();
|
private enum BoundaryCaptureMode { NONE, HANDHELD, MOWER }
|
private BoundaryCaptureMode activeBoundaryMode = BoundaryCaptureMode.NONE;
|
private boolean mowerBoundaryCaptureActive;
|
private Timer mowerBoundaryMonitor;
|
private Coordinate lastMowerCoordinate;
|
private double[] mowerBaseLatLon;
|
private boolean startButtonShowingPause = true;
|
private boolean stopButtonActive = false;
|
private boolean bluetoothConnected = false;
|
private Timer mowerSpeedRefreshTimer;
|
private boolean drawingControlModeActive;
|
private boolean storedStartButtonShowingPause;
|
private boolean storedStopButtonActive;
|
private String storedStatusBeforeDrawing;
|
private boolean handheldCaptureInlineUiActive;
|
private Timer handheldCaptureStatusTimer;
|
private String handheldCaptureStoredStatusText;
|
private Color handheldStartButtonOriginalBackground;
|
private Color handheldStartButtonOriginalForeground;
|
private Color handheldStopButtonOriginalBackground;
|
private Color handheldStopButtonOriginalForeground;
|
|
public Shouye() {
|
instance = this;
|
baseStation = new BaseStation();
|
baseStation.load();
|
dellmessage.registerLineListener(serialLineListener);
|
initializeUI();
|
setupEventHandlers();
|
scheduleIdentifierCheck();
|
}
|
|
public static Shouye getInstance() {
|
return instance;
|
}
|
|
private void initializeUI() {
|
setLayout(new BorderLayout());
|
setBackground(BACKGROUND_COLOR);
|
|
// 创建各个面板
|
createHeaderPanel();
|
createMainContentPanel();
|
createControlPanel();
|
createNavigationPanel();
|
|
// 添加到主面板
|
add(headerPanel, BorderLayout.NORTH);
|
add(mainContentPanel, BorderLayout.CENTER);
|
add(controlPanel, BorderLayout.SOUTH);
|
|
// 初始化地图渲染器
|
mapRenderer = new MapRenderer(visualizationPanel);
|
applyIdleTrailDurationFromSettings();
|
|
// 初始化对话框引用为null,延迟创建
|
legendDialog = null;
|
remoteDialog = null;
|
areaDialog = null;
|
baseStationDialog = null;
|
settingsDialog = null;
|
|
// 设置默认状态
|
setNavigationActive(homeNavBtn);
|
|
initializeDefaultAreaSelection();
|
refreshMapForSelectedArea();
|
}
|
|
private void scheduleIdentifierCheck() {
|
HierarchyListener listener = new HierarchyListener() {
|
@Override
|
public void hierarchyChanged(HierarchyEvent e) {
|
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && Shouye.this.isShowing()) {
|
Shouye.this.removeHierarchyListener(this);
|
SwingUtilities.invokeLater(() -> {
|
Shouye.this.checkIdentifiersAndPromptIfNeeded();
|
Shouye.this.showInitialMowerSelfCheckDialogIfNeeded();
|
});
|
}
|
}
|
};
|
addHierarchyListener(listener);
|
}
|
|
private void showInitialMowerSelfCheckDialogIfNeeded() {
|
zijian.showInitialPromptIfNeeded(this, this::showRemoteControlDialog);
|
}
|
|
private void applyIdleTrailDurationFromSettings() {
|
if (mapRenderer == null) {
|
return;
|
}
|
int durationSeconds = MapRenderer.DEFAULT_IDLE_TRAIL_DURATION_SECONDS;
|
String configuredValue = Setsys.getPropertyValue("idleTrailDurationSeconds");
|
if (configuredValue != null) {
|
String trimmed = configuredValue.trim();
|
if (!trimmed.isEmpty()) {
|
try {
|
int parsed = Integer.parseInt(trimmed);
|
if (parsed >= 5 && parsed <= 600) {
|
durationSeconds = parsed;
|
}
|
} catch (NumberFormatException ignored) {
|
durationSeconds = MapRenderer.DEFAULT_IDLE_TRAIL_DURATION_SECONDS;
|
}
|
}
|
}
|
mapRenderer.setIdleTrailDurationSeconds(durationSeconds);
|
}
|
|
private void createHeaderPanel() {
|
headerPanel = new JPanel(new BorderLayout());
|
headerPanel.setBackground(PANEL_BACKGROUND);
|
headerPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
|
headerPanel.setPreferredSize(new Dimension(0, 80));
|
|
// 左侧信息区域
|
JPanel leftInfoPanel = new JPanel(new GridLayout(2, 1));
|
leftInfoPanel.setBackground(PANEL_BACKGROUND);
|
|
areaNameLabel = new JLabel("未选择地块");
|
areaNameLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
|
areaNameLabel.setForeground(Color.BLACK);
|
areaNameLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
areaNameLabel.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
Dikuaiguanli.showDikuaiManagement(Shouye.this, null);
|
}
|
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
areaNameLabel.setForeground(THEME_COLOR);
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
areaNameLabel.setForeground(Color.BLACK);
|
}
|
});
|
|
statusLabel = new JLabel("待机");
|
statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
statusLabel.setForeground(Color.GRAY);
|
statusLabel.addPropertyChangeListener("text", evt -> {
|
Object newValue = evt.getNewValue();
|
applyStatusLabelColor(newValue instanceof String ? (String) newValue : null);
|
});
|
applyStatusLabelColor(statusLabel.getText());
|
|
leftInfoPanel.add(areaNameLabel);
|
leftInfoPanel.add(statusLabel);
|
|
// 右侧操作区域
|
JPanel rightActionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
rightActionPanel.setBackground(PANEL_BACKGROUND);
|
|
bluetoothBtn = createBluetoothButton();
|
|
// 修改设置按钮:使用图片替代Unicode图标
|
JButton settingsBtn = new JButton();
|
try {
|
ImageIcon settingsIcon = new ImageIcon("image/sets.png");
|
// 调整图片大小以适应按钮
|
Image scaledImage = settingsIcon.getImage().getScaledInstance(30, 30, Image.SCALE_SMOOTH);
|
settingsBtn.setIcon(new ImageIcon(scaledImage));
|
} catch (Exception e) {
|
// 如果图片加载失败,使用默认文本
|
settingsBtn.setText("设置");
|
System.err.println("无法加载设置图标: " + e.getMessage());
|
}
|
settingsBtn.setPreferredSize(new Dimension(40, 40));
|
settingsBtn.setBackground(PANEL_BACKGROUND);
|
settingsBtn.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
settingsBtn.setFocusPainted(false);
|
|
// 添加悬停效果
|
settingsBtn.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
settingsBtn.setBackground(new Color(240, 240, 240));
|
}
|
public void mouseExited(MouseEvent e) {
|
settingsBtn.setBackground(PANEL_BACKGROUND);
|
}
|
});
|
|
// 添加设置按钮事件
|
settingsBtn.addActionListener(e -> showSettingsDialog());
|
|
rightActionPanel.add(bluetoothBtn);
|
rightActionPanel.add(settingsBtn);
|
|
headerPanel.add(leftInfoPanel, BorderLayout.WEST);
|
headerPanel.add(rightActionPanel, BorderLayout.EAST);
|
}
|
|
private void createMainContentPanel() {
|
mainContentPanel = new JPanel(new BorderLayout());
|
mainContentPanel.setBackground(BACKGROUND_COLOR);
|
|
// 可视化区域 - 使用MapRenderer进行绘制
|
visualizationPanel = new JPanel() {
|
@Override
|
protected void paintComponent(Graphics g) {
|
super.paintComponent(g);
|
// 委托给MapRenderer进行绘制
|
if (mapRenderer != null) {
|
mapRenderer.renderMap(g);
|
}
|
}
|
};
|
visualizationPanel.setLayout(new BorderLayout());
|
|
JPanel speedIndicatorPanel = createSpeedIndicatorPanel();
|
visualizationPanel.add(speedIndicatorPanel, BorderLayout.NORTH);
|
|
// 创建功能按钮面板(放在左上角)
|
JPanel functionButtonsPanel = new JPanel();
|
functionButtonsPanel.setLayout(new BoxLayout(functionButtonsPanel, BoxLayout.Y_AXIS));
|
functionButtonsPanel.setOpaque(false);
|
functionButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20, 5, 0, 0)); // 左边距改为5像素
|
|
legendBtn = createFunctionButton("图例", "📖");
|
baseStationBtn = createFunctionButton("基准站", "📡"); // 调整到第二位
|
areaSelectBtn = createFunctionButton("地块", "🌿"); // 调整到第三位
|
remoteBtn = createFunctionButton("遥控", "🎮"); // 调整到最后
|
|
functionButtonsPanel.add(legendBtn);
|
functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
functionButtonsPanel.add(baseStationBtn);
|
functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
functionButtonsPanel.add(areaSelectBtn);
|
functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
functionButtonsPanel.add(remoteBtn);
|
|
visualizationPanel.add(functionButtonsPanel, BorderLayout.WEST);
|
|
mainContentPanel.add(visualizationPanel, BorderLayout.CENTER);
|
|
startMowerSpeedUpdates();
|
}
|
|
private void createControlPanel() {
|
controlPanel = new JPanel(new BorderLayout());
|
controlPanel.setBackground(PANEL_BACKGROUND);
|
controlPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
|
controlPanel.setPreferredSize(new Dimension(0, 100));
|
|
JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 20, 0));
|
buttonPanel.setBackground(PANEL_BACKGROUND);
|
|
startBtn = createControlButton("暂停", THEME_COLOR);
|
updateStartButtonAppearance();
|
|
stopBtn = createControlButton("结束", Color.ORANGE);
|
updateStopButtonIcon();
|
|
buttonPanel.add(startBtn);
|
buttonPanel.add(stopBtn);
|
|
controlPanel.add(buttonPanel, BorderLayout.CENTER);
|
}
|
|
private void createNavigationPanel() {
|
navigationPanel = new JPanel(new GridLayout(1, 3));
|
navigationPanel.setBackground(PANEL_BACKGROUND);
|
navigationPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
navigationPanel.setPreferredSize(new Dimension(0, 70));
|
|
homeNavBtn = createNavButton("首页", "🏠");
|
areasNavBtn = createNavButton("地块", "🌿");
|
settingsNavBtn = createNavButton("设置", "⚙️");
|
|
navigationPanel.add(homeNavBtn);
|
navigationPanel.add(areasNavBtn);
|
navigationPanel.add(settingsNavBtn);
|
|
// 添加到主界面底部
|
add(navigationPanel, BorderLayout.SOUTH);
|
}
|
|
private JButton createIconButton(String icon, int size) {
|
JButton button = new JButton(icon);
|
button.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 20));
|
button.setPreferredSize(new Dimension(size, size));
|
button.setBackground(PANEL_BACKGROUND);
|
button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
button.setFocusPainted(false);
|
|
// 悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(new Color(240, 240, 240));
|
}
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(PANEL_BACKGROUND);
|
}
|
});
|
|
return button;
|
}
|
|
private JButton createBluetoothButton() {
|
JButton button = new JButton();
|
button.setPreferredSize(new Dimension(40, 40));
|
button.setBackground(PANEL_BACKGROUND);
|
button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
button.setFocusPainted(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(new Color(240, 240, 240));
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(PANEL_BACKGROUND);
|
}
|
});
|
ensureBluetoothIconsLoaded();
|
bluetoothConnected = Bluelink.isConnected();
|
ImageIcon initialIcon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
|
if (initialIcon != null) {
|
button.setIcon(initialIcon);
|
} else {
|
button.setText(bluetoothConnected ? "已连" : "蓝牙");
|
}
|
return button;
|
}
|
|
private JButton createFunctionButton(String text, String icon) {
|
JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
|
button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
button.setPreferredSize(new Dimension(80, 70));
|
button.setBackground(new Color(0, 0, 0, 0)); // 完全透明背景
|
button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 只保留内边距
|
button.setFocusPainted(false);
|
button.setOpaque(false); // 设置为不透明,确保背景透明
|
|
// 去掉悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
// 悬停时不改变任何样式
|
}
|
public void mouseExited(MouseEvent e) {
|
// 悬停时不改变任何样式
|
}
|
});
|
|
return button;
|
}
|
|
private JButton createControlButton(String text, Color color) {
|
JButton button = new JButton(text);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
button.setBackground(color);
|
button.setForeground(Color.WHITE);
|
button.setBorder(BorderFactory.createEmptyBorder(15, 0, 15, 0));
|
button.setFocusPainted(false);
|
|
// 悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
if (button.isEnabled()) {
|
if (color == THEME_COLOR) {
|
button.setBackground(THEME_HOVER_COLOR);
|
} else {
|
button.setBackground(new Color(255, 165, 0));
|
}
|
}
|
}
|
public void mouseExited(MouseEvent e) {
|
if (button.isEnabled()) {
|
button.setBackground(color);
|
}
|
}
|
});
|
|
return button;
|
}
|
|
private void applyButtonIcon(JButton button, String imagePath) {
|
try {
|
ImageIcon icon = new ImageIcon(imagePath);
|
Image scaledImage = icon.getImage().getScaledInstance(28, 28, Image.SCALE_SMOOTH);
|
button.setIcon(new ImageIcon(scaledImage));
|
button.setHorizontalAlignment(SwingConstants.CENTER);
|
button.setIconTextGap(10);
|
button.setHorizontalTextPosition(SwingConstants.RIGHT);
|
button.setVerticalTextPosition(SwingConstants.CENTER);
|
} catch (Exception e) {
|
System.err.println("无法加载按钮图标: " + imagePath + " -> " + e.getMessage());
|
}
|
}
|
|
private JButton createNavButton(String text, String icon) {
|
JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
|
button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
button.setBackground(PANEL_BACKGROUND);
|
button.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
|
button.setFocusPainted(false);
|
|
// 悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
if (button != currentNavButton) {
|
button.setBackground(new Color(240, 240, 240));
|
}
|
}
|
public void mouseExited(MouseEvent e) {
|
if (button != currentNavButton) {
|
button.setBackground(PANEL_BACKGROUND);
|
}
|
}
|
});
|
|
return button;
|
}
|
|
private void setNavigationActive(JButton navButton) {
|
// 重置所有导航按钮样式
|
for (Component comp : navigationPanel.getComponents()) {
|
if (comp instanceof JButton) {
|
JButton btn = (JButton) comp;
|
btn.setBackground(PANEL_BACKGROUND);
|
btn.setForeground(Color.BLACK);
|
}
|
}
|
|
// 设置当前选中按钮样式
|
navButton.setBackground(THEME_COLOR);
|
navButton.setForeground(Color.WHITE);
|
currentNavButton = navButton;
|
}
|
|
private void setupEventHandlers() {
|
// 导航按钮事件
|
homeNavBtn.addActionListener(e -> setNavigationActive(homeNavBtn));
|
areasNavBtn.addActionListener(e -> setNavigationActive(areasNavBtn));
|
settingsNavBtn.addActionListener(e -> setNavigationActive(settingsNavBtn));
|
|
// 功能按钮事件
|
legendBtn.addActionListener(e -> showLegendDialog());
|
if (bluetoothBtn != null) {
|
bluetoothBtn.addActionListener(e -> toggleBluetoothConnection());
|
}
|
remoteBtn.addActionListener(e -> showRemoteControlDialog());
|
areaSelectBtn.addActionListener(e -> {
|
// 点击“地块”直接打开地块管理对话框(若需要可传入特定地块编号)
|
Dikuaiguanli.showDikuaiManagement(this, null);
|
});
|
baseStationBtn.addActionListener(e -> showBaseStationDialog());
|
|
// 控制按钮事件
|
startBtn.addActionListener(e -> toggleStartPause());
|
stopBtn.addActionListener(e -> handleStopAction());
|
}
|
|
private void showSettingsDialog() {
|
if (settingsDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow instanceof JFrame) {
|
settingsDialog = new Sets((JFrame) parentWindow, THEME_COLOR);
|
} else if (parentWindow instanceof JDialog) {
|
settingsDialog = new Sets((JDialog) parentWindow, THEME_COLOR);
|
} else {
|
// Fallback to a frameless dialog when no parent is available
|
settingsDialog = new Sets((JFrame) null, THEME_COLOR);
|
}
|
}
|
settingsDialog.setVisible(true);
|
}
|
|
private void showLegendDialog() {
|
if (legendDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow != null) {
|
legendDialog = new LegendDialog(this, THEME_COLOR);
|
} else {
|
// 如果没有父窗口,创建无父窗口的对话框
|
legendDialog = new LegendDialog((JFrame) null, THEME_COLOR);
|
}
|
}
|
legendDialog.setVisible(true);
|
}
|
|
private void showRemoteControlDialog() {
|
if (remoteDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow != null) {
|
remoteDialog = new RemoteControlDialog(this, THEME_COLOR);
|
} else {
|
remoteDialog = new RemoteControlDialog((JFrame) null, THEME_COLOR);
|
}
|
}
|
if (remoteDialog != null) {
|
positionRemoteDialogBottomCenter(remoteDialog);
|
zijian.markSelfCheckCompleted();
|
remoteDialog.setVisible(true);
|
}
|
}
|
|
private void positionRemoteDialogBottomCenter(RemoteControlDialog dialog) {
|
if (dialog == null) {
|
return;
|
}
|
Rectangle targetBounds = computeVisualizationBoundsOnScreen();
|
Dimension dialogSize = dialog.getSize();
|
|
int x;
|
int y;
|
if (targetBounds != null) {
|
x = targetBounds.x + (targetBounds.width - dialogSize.width) / 2;
|
y = targetBounds.y + targetBounds.height - dialogSize.height;
|
} else {
|
Component parentComponent = this;
|
Point parentOnScreen = parentComponent.getLocationOnScreen();
|
int parentWidth = parentComponent.getWidth();
|
int parentHeight = parentComponent.getHeight();
|
x = parentOnScreen.x + (parentWidth - dialogSize.width) / 2;
|
y = parentOnScreen.y + parentHeight - dialogSize.height;
|
}
|
|
dialog.setLocation(Math.max(x, 0), Math.max(y, 0));
|
}
|
|
private Rectangle computeVisualizationBoundsOnScreen() {
|
if (visualizationPanel == null || !visualizationPanel.isShowing()) {
|
return null;
|
}
|
Point location = visualizationPanel.getLocationOnScreen();
|
Dimension size = visualizationPanel.getSize();
|
return new Rectangle(location.x, location.y, size.width, size.height);
|
}
|
|
private void showAreaSelectionDialog() {
|
if (areaDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow != null) {
|
areaDialog = new AreaSelectionDialog(this, THEME_COLOR, areaNameLabel, statusLabel);
|
} else {
|
areaDialog = new AreaSelectionDialog((JFrame) null, THEME_COLOR, areaNameLabel, statusLabel);
|
}
|
}
|
areaDialog.setVisible(true);
|
}
|
|
private void showBaseStationDialog() {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
baseStation.load();
|
|
Component dialogParent = this;
|
|
if (!hasValidBaseStationId()) {
|
boolean recorded = promptForBaseStationId(dialogParent);
|
if (!recorded) {
|
return;
|
}
|
}
|
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
device = new Device();
|
device.initFromProperties();
|
Device.setGecaoji(device);
|
}
|
|
if (baseStationDialog == null) {
|
baseStationDialog = new BaseStationDialog(dialogParent, THEME_COLOR, device, baseStation);
|
} else {
|
baseStationDialog.refreshData();
|
}
|
baseStationDialog.setVisible(true);
|
}
|
|
private void checkIdentifiersAndPromptIfNeeded() {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
baseStation.load();
|
|
String currentMowerId = Setsys.getPropertyValue("mowerId");
|
String currentBaseStationId = baseStation.getDeviceId();
|
|
if (!isIdentifierMissing(currentMowerId) && !isIdentifierMissing(currentBaseStationId)) {
|
return;
|
}
|
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
promptForMissingIdentifiers(owner, currentMowerId, currentBaseStationId);
|
}
|
|
private void promptForMissingIdentifiers(Window owner, String currentMowerId, String currentBaseStationId) {
|
while (true) {
|
JTextField mowerField = new JTextField(10);
|
JTextField baseField = new JTextField(10);
|
|
if (!isIdentifierMissing(currentMowerId)) {
|
mowerField.setText(currentMowerId.trim());
|
}
|
if (!isIdentifierMissing(currentBaseStationId)) {
|
baseField.setText(currentBaseStationId.trim());
|
}
|
|
JPanel panel = new JPanel();
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
|
|
JLabel mowerLabel = new JLabel("割草机编号");
|
JLabel baseLabel = new JLabel("差分基准站编号");
|
|
mowerField.setMaximumSize(new Dimension(Integer.MAX_VALUE, mowerField.getPreferredSize().height));
|
baseField.setMaximumSize(new Dimension(Integer.MAX_VALUE, baseField.getPreferredSize().height));
|
|
panel.add(mowerLabel);
|
panel.add(Box.createVerticalStrut(4));
|
panel.add(mowerField);
|
panel.add(Box.createVerticalStrut(10));
|
panel.add(baseLabel);
|
panel.add(Box.createVerticalStrut(4));
|
panel.add(baseField);
|
|
Object[] options = {"保存", "取消"};
|
int result = JOptionPane.showOptionDialog(owner, panel, "完善设备信息",
|
JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
|
|
if (result != 0) {
|
break;
|
}
|
|
String mowerInput = mowerField.getText().trim();
|
String baseInput = baseField.getText().trim();
|
|
if (mowerInput.isEmpty()) {
|
JOptionPane.showMessageDialog(owner, "割草机编号不能为空。", "提示", JOptionPane.WARNING_MESSAGE);
|
continue;
|
}
|
|
if (baseInput.isEmpty()) {
|
JOptionPane.showMessageDialog(owner, "差分基准站编号不能为空。", "提示", JOptionPane.WARNING_MESSAGE);
|
continue;
|
}
|
|
boolean mowerSaved = persistMowerIdentifier(mowerInput);
|
boolean baseSaved = persistBaseStationIdentifier(baseInput);
|
|
if (mowerSaved && baseSaved) {
|
JOptionPane.showMessageDialog(owner, "编号已保存。", "成功", JOptionPane.INFORMATION_MESSAGE);
|
break;
|
}
|
|
StringBuilder errorBuilder = new StringBuilder();
|
if (!mowerSaved) {
|
errorBuilder.append("割草机编号保存失败。");
|
}
|
if (!baseSaved) {
|
if (errorBuilder.length() > 0) {
|
errorBuilder.append('\n');
|
}
|
errorBuilder.append("差分基准站编号保存失败。");
|
}
|
|
JOptionPane.showMessageDialog(owner, errorBuilder.toString(), "保存失败", JOptionPane.ERROR_MESSAGE);
|
|
currentMowerId = Setsys.getPropertyValue("mowerId");
|
baseStation.load();
|
currentBaseStationId = baseStation.getDeviceId();
|
}
|
}
|
|
private boolean isIdentifierMissing(String value) {
|
if (value == null) {
|
return true;
|
}
|
String trimmed = value.trim();
|
return trimmed.isEmpty() || "-1".equals(trimmed);
|
}
|
|
private boolean persistMowerIdentifier(String mowerId) {
|
try {
|
Setsys setsys = new Setsys();
|
setsys.initializeFromProperties();
|
boolean updated = setsys.updateProperty("mowerId", mowerId);
|
if (updated) {
|
Device.initializeActiveDevice(mowerId);
|
}
|
return updated;
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
return false;
|
}
|
}
|
|
private boolean persistBaseStationIdentifier(String baseStationId) {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
try {
|
baseStation.updateByDeviceId(baseStationId,
|
baseStation.getInstallationCoordinates(),
|
baseStation.getIotSimCardNumber(),
|
baseStation.getDeviceActivationTime(),
|
baseStation.getDataUpdateTime());
|
baseStation.load();
|
return true;
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
return false;
|
}
|
}
|
|
private boolean hasValidBaseStationId() {
|
if (baseStation == null) {
|
return false;
|
}
|
String deviceId = baseStation.getDeviceId();
|
if (deviceId == null) {
|
return false;
|
}
|
String trimmed = deviceId.trim();
|
return !trimmed.isEmpty() && !"-1".equals(trimmed);
|
}
|
|
private boolean promptForBaseStationId(Component parentComponent) {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
|
while (true) {
|
String input = JOptionPane.showInputDialog(parentComponent,
|
"请输入基准站编号", "录入基准站编号", JOptionPane.PLAIN_MESSAGE);
|
|
if (input == null) {
|
return false;
|
}
|
|
String trimmed = input.trim();
|
if (trimmed.isEmpty()) {
|
JOptionPane.showMessageDialog(parentComponent,
|
"基准站编号不能为空,请重新输入。", "提示", JOptionPane.WARNING_MESSAGE);
|
continue;
|
}
|
|
try {
|
baseStation.updateByDeviceId(trimmed,
|
baseStation.getInstallationCoordinates(),
|
baseStation.getIotSimCardNumber(),
|
baseStation.getDeviceActivationTime(),
|
baseStation.getDataUpdateTime());
|
baseStation.load();
|
JOptionPane.showMessageDialog(parentComponent,
|
"基准站编号已保存。", "操作成功", JOptionPane.INFORMATION_MESSAGE);
|
return true;
|
} catch (IllegalArgumentException ex) {
|
JOptionPane.showMessageDialog(parentComponent,
|
ex.getMessage(), "输入错误", JOptionPane.ERROR_MESSAGE);
|
}
|
}
|
}
|
|
private void toggleStartPause() {
|
if (handheldCaptureInlineUiActive) {
|
handleHandheldConfirmAction();
|
return;
|
}
|
if (drawingControlModeActive) {
|
toggleDrawingPause();
|
return;
|
}
|
if (startBtn == null) {
|
return;
|
}
|
if (startButtonShowingPause) {
|
if (!zijian.ensureBeforeMowing(this, this::showRemoteControlDialog)) {
|
return;
|
}
|
}
|
startButtonShowingPause = !startButtonShowingPause;
|
if (!startButtonShowingPause) {
|
statusLabel.setText("作业中");
|
if (stopButtonActive) {
|
stopButtonActive = false;
|
updateStopButtonIcon();
|
}
|
if (!beginMowingSession()) {
|
startButtonShowingPause = true;
|
statusLabel.setText("待机");
|
updateStartButtonAppearance();
|
return;
|
}
|
} else {
|
statusLabel.setText("暂停中");
|
pauseMowingSession();
|
}
|
updateStartButtonAppearance();
|
}
|
|
private void handleStopAction() {
|
if (handheldCaptureInlineUiActive) {
|
handleHandheldFinishAction();
|
return;
|
}
|
if (drawingControlModeActive) {
|
handleDrawingStopFromControlPanel();
|
return;
|
}
|
stopButtonActive = !stopButtonActive;
|
updateStopButtonIcon();
|
if (stopButtonActive) {
|
statusLabel.setText("已结束");
|
startButtonShowingPause = false;
|
stopMowingSession();
|
} else {
|
statusLabel.setText("待机");
|
startButtonShowingPause = true;
|
pauseMowingSession();
|
}
|
updateStartButtonAppearance();
|
}
|
|
private void handleDrawingStopFromControlPanel() {
|
if (endDrawingCallback != null) {
|
endDrawingCallback.run();
|
} else {
|
addzhangaiwu.finishDrawingSession();
|
}
|
}
|
|
private void handleHandheldConfirmAction() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
if (!canConfirmHandheldPoint()) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
int count = captureHandheldBoundaryPoint();
|
if (count <= 0) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
refreshHandheldCaptureUiState();
|
}
|
|
private void handleHandheldFinishAction() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
if (stopBtn != null && !stopBtn.isEnabled()) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
if (!finishHandheldBoundaryCapture()) {
|
refreshHandheldCaptureUiState();
|
}
|
}
|
|
private void enterHandheldCaptureInlineUi() {
|
if (handheldCaptureInlineUiActive) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
handheldCaptureInlineUiActive = true;
|
handheldCaptureStoredStatusText = statusLabel != null ? statusLabel.getText() : null;
|
if (statusLabel != null) {
|
statusLabel.setText("手持采集中");
|
}
|
if (startBtn != null) {
|
handheldStartButtonOriginalBackground = startBtn.getBackground();
|
handheldStartButtonOriginalForeground = startBtn.getForeground();
|
startBtn.setIcon(null);
|
startBtn.setIconTextGap(0);
|
startBtn.setHorizontalAlignment(SwingConstants.CENTER);
|
startBtn.setHorizontalTextPosition(SwingConstants.CENTER);
|
startBtn.setVerticalTextPosition(SwingConstants.CENTER);
|
}
|
if (stopBtn != null) {
|
handheldStopButtonOriginalBackground = stopBtn.getBackground();
|
handheldStopButtonOriginalForeground = stopBtn.getForeground();
|
stopBtn.setIcon(null);
|
stopBtn.setIconTextGap(0);
|
stopBtn.setHorizontalAlignment(SwingConstants.CENTER);
|
stopBtn.setHorizontalTextPosition(SwingConstants.CENTER);
|
stopBtn.setVerticalTextPosition(SwingConstants.CENTER);
|
stopBtn.setText("结束");
|
}
|
startHandheldCaptureStatusTimer();
|
refreshHandheldCaptureUiState();
|
}
|
|
private void exitHandheldCaptureInlineUi() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
handheldCaptureInlineUiActive = false;
|
stopHandheldCaptureStatusTimer();
|
if (statusLabel != null) {
|
statusLabel.setText(handheldCaptureStoredStatusText != null ? handheldCaptureStoredStatusText : "待机");
|
}
|
if (startBtn != null) {
|
startBtn.setToolTipText(null);
|
if (handheldStartButtonOriginalBackground != null) {
|
startBtn.setBackground(handheldStartButtonOriginalBackground);
|
}
|
if (handheldStartButtonOriginalForeground != null) {
|
startBtn.setForeground(handheldStartButtonOriginalForeground);
|
}
|
startBtn.setEnabled(true);
|
updateStartButtonAppearance();
|
}
|
if (stopBtn != null) {
|
stopBtn.setToolTipText(null);
|
if (handheldStopButtonOriginalBackground != null) {
|
stopBtn.setBackground(handheldStopButtonOriginalBackground);
|
}
|
if (handheldStopButtonOriginalForeground != null) {
|
stopBtn.setForeground(handheldStopButtonOriginalForeground);
|
}
|
stopBtn.setEnabled(true);
|
stopBtn.setText("结束");
|
updateStopButtonIcon();
|
}
|
handheldCaptureStoredStatusText = null;
|
handheldStartButtonOriginalBackground = null;
|
handheldStartButtonOriginalForeground = null;
|
handheldStopButtonOriginalBackground = null;
|
handheldStopButtonOriginalForeground = null;
|
}
|
|
private void startHandheldCaptureStatusTimer() {
|
if (handheldCaptureStatusTimer == null) {
|
handheldCaptureStatusTimer = new Timer(400, e -> refreshHandheldCaptureUiState());
|
handheldCaptureStatusTimer.setRepeats(true);
|
}
|
if (!handheldCaptureStatusTimer.isRunning()) {
|
handheldCaptureStatusTimer.start();
|
}
|
}
|
|
private void stopHandheldCaptureStatusTimer() {
|
if (handheldCaptureStatusTimer != null && handheldCaptureStatusTimer.isRunning()) {
|
handheldCaptureStatusTimer.stop();
|
}
|
}
|
|
// Update inline handheld capture buttons based on the current device reading.
|
private void refreshHandheldCaptureUiState() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
int nextIndex = handheldCapturedPoints + 1;
|
boolean hasFix = hasHighPrecisionFix();
|
boolean hasValid = hasValidRealtimeHandheldPosition();
|
boolean duplicate = hasValid && isCurrentHandheldPointDuplicate();
|
boolean canConfirm = handheldCaptureActive && hasFix && hasValid && !duplicate;
|
|
if (startBtn != null) {
|
String prompt = "<html><center>采集点" + nextIndex + "<br>确定</center></html>";
|
startBtn.setText(prompt);
|
startBtn.setEnabled(canConfirm);
|
if (canConfirm) {
|
if (handheldStartButtonOriginalBackground != null) {
|
startBtn.setBackground(handheldStartButtonOriginalBackground);
|
}
|
if (handheldStartButtonOriginalForeground != null) {
|
startBtn.setForeground(handheldStartButtonOriginalForeground);
|
}
|
startBtn.setToolTipText(null);
|
} else {
|
startBtn.setBackground(new Color(200, 200, 200));
|
startBtn.setForeground(new Color(130, 130, 130));
|
startBtn.setToolTipText(resolveHandheldConfirmTooltip(hasFix, hasValid, duplicate));
|
}
|
}
|
|
if (stopBtn != null) {
|
boolean canFinish = handheldCapturedPoints >= 3;
|
stopBtn.setText("结束");
|
stopBtn.setEnabled(canFinish);
|
if (canFinish) {
|
if (handheldStopButtonOriginalBackground != null) {
|
stopBtn.setBackground(handheldStopButtonOriginalBackground);
|
}
|
if (handheldStopButtonOriginalForeground != null) {
|
stopBtn.setForeground(handheldStopButtonOriginalForeground);
|
}
|
stopBtn.setToolTipText("结束采集并返回新增地块");
|
} else {
|
stopBtn.setBackground(new Color(220, 220, 220));
|
stopBtn.setForeground(new Color(130, 130, 130));
|
stopBtn.setToolTipText("至少采集三个点才能结束");
|
}
|
}
|
}
|
|
private String resolveHandheldConfirmTooltip(boolean hasFix, boolean hasValidPosition, boolean duplicate) {
|
if (!hasFix) {
|
return "当前定位质量不足,无法采集";
|
}
|
if (!hasValidPosition) {
|
return "当前定位数据无效,请稍后再试";
|
}
|
if (duplicate) {
|
return "当前坐标已采集,请移动到新的位置";
|
}
|
return null;
|
}
|
|
private boolean hasHighPrecisionFix() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return false;
|
}
|
String status = device.getPositioningStatus();
|
return status != null && "4".equals(status.trim());
|
}
|
|
private boolean canConfirmHandheldPoint() {
|
return handheldCaptureActive
|
&& hasHighPrecisionFix()
|
&& hasValidRealtimeHandheldPosition()
|
&& !isCurrentHandheldPointDuplicate();
|
}
|
|
private void enterDrawingControlMode() {
|
if (drawingControlModeActive) {
|
return;
|
}
|
storedStartButtonShowingPause = startButtonShowingPause;
|
storedStopButtonActive = stopButtonActive;
|
storedStatusBeforeDrawing = statusLabel != null ? statusLabel.getText() : null;
|
drawingControlModeActive = true;
|
applyDrawingPauseState(false, false);
|
updateDrawingControlButtonLabels();
|
}
|
|
private void exitDrawingControlMode() {
|
if (!drawingControlModeActive) {
|
return;
|
}
|
drawingControlModeActive = false;
|
applyDrawingPauseState(false, false);
|
drawingPaused = false;
|
stopButtonActive = storedStopButtonActive;
|
startButtonShowingPause = storedStartButtonShowingPause;
|
if (startBtn != null) {
|
updateStartButtonAppearance();
|
}
|
if (stopBtn != null) {
|
stopBtn.setText("结束");
|
updateStopButtonIcon();
|
}
|
if (statusLabel != null) {
|
statusLabel.setText(storedStatusBeforeDrawing != null ? storedStatusBeforeDrawing : "待机");
|
}
|
storedStatusBeforeDrawing = null;
|
}
|
|
private void updateDrawingControlButtonLabels() {
|
if (!drawingControlModeActive) {
|
return;
|
}
|
configureButtonForDrawingMode(startBtn);
|
configureButtonForDrawingMode(stopBtn);
|
if (startBtn != null) {
|
startBtn.setText(drawingPaused ? "开始绘制" : "暂停绘制");
|
}
|
if (stopBtn != null) {
|
stopBtn.setText("结束绘制");
|
}
|
}
|
|
private void configureButtonForDrawingMode(JButton button) {
|
if (button == null) {
|
return;
|
}
|
button.setIcon(null);
|
button.setIconTextGap(0);
|
button.setHorizontalAlignment(SwingConstants.CENTER);
|
button.setHorizontalTextPosition(SwingConstants.CENTER);
|
}
|
|
private void updateStartButtonAppearance() {
|
if (startBtn == null) {
|
return;
|
}
|
String iconPath = startButtonShowingPause ? "image/start0.png" : "image/start1.png";
|
startBtn.setText(startButtonShowingPause ? "暂停" : "开始");
|
applyButtonIcon(startBtn, iconPath);
|
}
|
|
private void updateStopButtonIcon() {
|
if (stopBtn == null) {
|
return;
|
}
|
String iconPath = stopButtonActive ? "image/stop1.png" : "image/stop0.png";
|
applyButtonIcon(stopBtn, iconPath);
|
}
|
|
private void toggleBluetoothConnection() {
|
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();
|
}
|
|
private void updateBluetoothButtonIcon() {
|
if (bluetoothBtn == null) {
|
return;
|
}
|
ensureBluetoothIconsLoaded();
|
bluetoothConnected = Bluelink.isConnected();
|
ImageIcon icon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
|
if (icon != null) {
|
bluetoothBtn.setIcon(icon);
|
bluetoothBtn.setText(null);
|
} else {
|
bluetoothBtn.setText(bluetoothConnected ? "已连" : "蓝牙");
|
}
|
}
|
|
private JPanel createSpeedIndicatorPanel() {
|
JPanel panel = new JPanel(new BorderLayout());
|
panel.setOpaque(false);
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 20, 5, 20));
|
|
JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
rightPanel.setOpaque(false);
|
|
fixQualityIndicator = new FixQualityIndicator();
|
fixQualityIndicator.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
fixQualityIndicator.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
if (SwingUtilities.isLeftMouseButton(e) && mapRenderer != null) {
|
mapRenderer.showMowerInfo();
|
}
|
}
|
});
|
mowingProgressLabel = new JLabel("--%");
|
mowingProgressLabel.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
mowingProgressLabel.setForeground(THEME_COLOR);
|
|
mowerSpeedValueLabel = new JLabel("--");
|
mowerSpeedValueLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
mowerSpeedValueLabel.setForeground(THEME_COLOR);
|
|
mowerSpeedUnitLabel = new JLabel("km/h");
|
mowerSpeedUnitLabel.setFont(new Font("微软雅黑", Font.BOLD, 9));
|
mowerSpeedUnitLabel.setForeground(THEME_COLOR);
|
|
dataPacketCountLabel = new JLabel("--");
|
dataPacketCountLabel.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
dataPacketCountLabel.setForeground(THEME_COLOR);
|
|
rightPanel.add(fixQualityIndicator);
|
|
JSeparator areaSeparator = new JSeparator(SwingConstants.VERTICAL);
|
areaSeparator.setPreferredSize(new Dimension(1, 16));
|
rightPanel.add(areaSeparator);
|
|
rightPanel.add(mowingProgressLabel);
|
JSeparator speedSeparator = new JSeparator(SwingConstants.VERTICAL);
|
speedSeparator.setPreferredSize(new Dimension(1, 16));
|
rightPanel.add(speedSeparator);
|
rightPanel.add(mowerSpeedValueLabel);
|
rightPanel.add(mowerSpeedUnitLabel);
|
|
JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
|
separator.setPreferredSize(new Dimension(1, 16));
|
rightPanel.add(separator);
|
|
rightPanel.add(dataPacketCountLabel);
|
|
panel.add(rightPanel, BorderLayout.EAST);
|
updateFixQualityIndicator();
|
updateDataPacketCountLabel();
|
return panel;
|
}
|
|
private void startMowerSpeedUpdates() {
|
if (mowerSpeedRefreshTimer == null) {
|
mowerSpeedRefreshTimer = new Timer(1000, e -> refreshMowerSpeedLabel());
|
mowerSpeedRefreshTimer.setRepeats(true);
|
}
|
if (!mowerSpeedRefreshTimer.isRunning()) {
|
mowerSpeedRefreshTimer.start();
|
}
|
refreshMowerSpeedLabel();
|
}
|
|
private void refreshMowerSpeedLabel() {
|
if (mowerSpeedValueLabel == null) {
|
return;
|
}
|
String display = "--";
|
Device device = Device.getGecaoji();
|
if (device != null) {
|
String sanitized = sanitizeSpeedValue(device.getRealtimeSpeed());
|
if (sanitized != null) {
|
display = sanitized;
|
}
|
}
|
mowerSpeedValueLabel.setText(display);
|
if (mowerSpeedUnitLabel != null) {
|
mowerSpeedUnitLabel.setText("km/h");
|
}
|
updateMowingProgressLabel();
|
updateFixQualityIndicator();
|
updateDataPacketCountLabel();
|
}
|
|
private void updateDataPacketCountLabel() {
|
if (dataPacketCountLabel == null) {
|
return;
|
}
|
int udpCount = UDPServer.getReceivedPacketCount();
|
int serialCount = dellmessage.getProcessedLineCount();
|
int displayCount = Math.max(udpCount, serialCount);
|
|
if (displayCount <= 0) {
|
dataPacketCountLabel.setText("--");
|
dataPacketCountLabel.setToolTipText(null);
|
} else {
|
dataPacketCountLabel.setText(String.valueOf(displayCount));
|
dataPacketCountLabel.setToolTipText(String.format("串口: %d UDP: %d", serialCount, udpCount));
|
}
|
}
|
|
private void updateFixQualityIndicator() {
|
if (fixQualityIndicator == null) {
|
return;
|
}
|
Device device = Device.getGecaoji();
|
String code = null;
|
if (device != null) {
|
code = sanitizeDeviceValue(device.getPositioningStatus());
|
}
|
fixQualityIndicator.setQuality(code);
|
}
|
|
private Color resolveFixQualityColor(String code) {
|
if (code == null) {
|
return new Color(160, 160, 160);
|
}
|
switch (code) {
|
case "0":
|
return new Color(160, 160, 160);
|
case "1":
|
return new Color(52, 152, 219);
|
case "2":
|
return new Color(26, 188, 156);
|
case "3":
|
return new Color(155, 89, 182);
|
case "4":
|
return THEME_COLOR;
|
case "5":
|
return new Color(241, 196, 15);
|
case "6":
|
return new Color(231, 76, 60);
|
case "7":
|
return new Color(230, 126, 34);
|
default:
|
return new Color(95, 95, 95);
|
}
|
}
|
|
private String resolveFixQualityDescription(String code) {
|
if (code == null) {
|
return "未知";
|
}
|
switch (code) {
|
case "0":
|
return "未定位";
|
case "1":
|
return "单点定位";
|
case "2":
|
return "码差分";
|
case "3":
|
return "无效PPS";
|
case "4":
|
return "固定解";
|
case "5":
|
return "浮点解";
|
case "6":
|
return "正在估算";
|
case "7":
|
return "人工输入固定值";
|
default:
|
return "其他";
|
}
|
}
|
|
private String sanitizeSpeedValue(String raw) {
|
if (raw == null) {
|
return null;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
if (trimmed.toLowerCase().endsWith("km/h")) {
|
trimmed = trimmed.substring(0, trimmed.length() - 4).trim();
|
}
|
return trimmed;
|
}
|
|
private String sanitizeDeviceValue(String raw) {
|
if (raw == null) {
|
return null;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed) || "null".equalsIgnoreCase(trimmed)) {
|
return null;
|
}
|
return trimmed;
|
}
|
|
private void updateMowingProgressLabel() {
|
if (mowingProgressLabel == null) {
|
return;
|
}
|
if (mapRenderer == null) {
|
mowingProgressLabel.setText("--%");
|
mowingProgressLabel.setToolTipText(null);
|
return;
|
}
|
|
double totalArea = mapRenderer.getTotalLandAreaSqMeters();
|
double completedArea = mapRenderer.getCompletedMowingAreaSqMeters();
|
double ratio = mapRenderer.getMowingCompletionRatio();
|
|
if (totalArea <= 0) {
|
mowingProgressLabel.setText("--%");
|
mowingProgressLabel.setToolTipText("暂无地块面积数据");
|
return;
|
}
|
|
double percent = Math.max(0.0, Math.min(1.0, ratio)) * 100.0;
|
mowingProgressLabel.setText(String.format(Locale.US, "%.1f%%", percent));
|
mowingProgressLabel.setToolTipText(String.format(Locale.US, "%.1f㎡ / %.1f㎡", completedArea, totalArea));
|
}
|
|
public void refreshMowingIndicators() {
|
refreshMowerSpeedLabel();
|
}
|
|
public void setHandheldMowerIconActive(boolean active) {
|
if (mapRenderer == null) {
|
return;
|
}
|
mapRenderer.setHandheldMowerIconActive(active);
|
}
|
|
public boolean startMowerBoundaryCapture() {
|
if (mapRenderer == null) {
|
return false;
|
}
|
double[] baseLatLonCandidate = resolveCircleBaseLatLon();
|
if (baseLatLonCandidate == null) {
|
return false;
|
}
|
|
mapRenderer.clearIdleTrail();
|
|
activeBoundaryMode = BoundaryCaptureMode.MOWER;
|
mowerBoundaryCaptureActive = true;
|
mowerBaseLatLon = baseLatLonCandidate;
|
lastMowerCoordinate = null;
|
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.clear();
|
}
|
synchronized (mowerTemporaryPoints) {
|
mowerTemporaryPoints.clear();
|
}
|
|
AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList());
|
Coordinate.setStartSaveGngga(true);
|
|
if (mapRenderer != null) {
|
mapRenderer.setBoundaryPreviewMarkerScale(2.0d);
|
mapRenderer.beginHandheldBoundaryPreview();
|
}
|
|
setHandheldMowerIconActive(false);
|
|
startMowerBoundaryMonitor();
|
return true;
|
}
|
|
public boolean startHandheldBoundaryCapture() {
|
if (mapRenderer == null) {
|
return false;
|
}
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
stopMowerBoundaryCapture();
|
}
|
|
mapRenderer.clearIdleTrail();
|
|
activeBoundaryMode = BoundaryCaptureMode.HANDHELD;
|
handheldCaptureActive = true;
|
handheldCapturedPoints = 0;
|
Coordinate.setStartSaveGngga(false);
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.clear();
|
}
|
synchronized (handheldTemporaryPoints) {
|
handheldTemporaryPoints.clear();
|
}
|
AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList());
|
mapRenderer.setBoundaryPreviewMarkerScale(1.0d);
|
mapRenderer.beginHandheldBoundaryPreview();
|
setHandheldMowerIconActive(true);
|
enterHandheldCaptureInlineUi();
|
return true;
|
}
|
|
private void startMowerBoundaryMonitor() {
|
if (mowerBoundaryMonitor == null) {
|
mowerBoundaryMonitor = new Timer(600, e -> pollMowerBoundaryCoordinate());
|
mowerBoundaryMonitor.setRepeats(true);
|
}
|
if (!mowerBoundaryMonitor.isRunning()) {
|
mowerBoundaryMonitor.start();
|
}
|
pollMowerBoundaryCoordinate();
|
}
|
|
private void stopMowerBoundaryMonitor() {
|
if (mowerBoundaryMonitor != null && mowerBoundaryMonitor.isRunning()) {
|
mowerBoundaryMonitor.stop();
|
}
|
}
|
|
private void pollMowerBoundaryCoordinate() {
|
if (!mowerBoundaryCaptureActive) {
|
return;
|
}
|
|
Coordinate latest = getLatestCoordinate();
|
if (latest == null || latest == lastMowerCoordinate) {
|
return;
|
}
|
|
double[] base = mowerBaseLatLon;
|
if (base == null || base.length < 2) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
|
double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
|
double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
|
if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
|
double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
|
Point2D.Double candidate = new Point2D.Double(local[0], local[1]);
|
if (!Double.isFinite(candidate.x) || !Double.isFinite(candidate.y)) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
|
List<Point2D.Double> snapshot;
|
synchronized (mowerTemporaryPoints) {
|
for (Point2D.Double existing : mowerTemporaryPoints) {
|
if (existing != null && arePointsClose(existing, candidate)) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
}
|
mowerTemporaryPoints.add(candidate);
|
snapshot = new ArrayList<>(mowerTemporaryPoints.size() + 1);
|
for (Point2D.Double point : mowerTemporaryPoints) {
|
if (point != null) {
|
snapshot.add(new Point2D.Double(point.x, point.y));
|
}
|
}
|
}
|
|
ensureClosed(snapshot);
|
AddDikuai.recordTemporaryBoundaryPoints(snapshot);
|
if (mapRenderer != null) {
|
mapRenderer.addHandheldBoundaryPoint(candidate.x, candidate.y);
|
}
|
lastMowerCoordinate = latest;
|
}
|
|
private void stopMowerBoundaryCapture() {
|
stopMowerBoundaryMonitor();
|
mowerBoundaryCaptureActive = false;
|
lastMowerCoordinate = null;
|
mowerBaseLatLon = null;
|
if (mapRenderer != null) {
|
mapRenderer.clearHandheldBoundaryPreview();
|
}
|
Coordinate.setStartSaveGngga(false);
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
activeBoundaryMode = BoundaryCaptureMode.NONE;
|
}
|
setHandheldMowerIconActive(false);
|
}
|
|
private void discardLatestCoordinate(Coordinate coordinate) {
|
if (coordinate == null) {
|
return;
|
}
|
synchronized (Coordinate.coordinates) {
|
int size = Coordinate.coordinates.size();
|
if (size == 0) {
|
return;
|
}
|
int lastIndex = size - 1;
|
if (Coordinate.coordinates.get(lastIndex) == coordinate) {
|
Coordinate.coordinates.remove(lastIndex);
|
} else {
|
Coordinate.coordinates.remove(coordinate);
|
}
|
}
|
}
|
|
private void ensureClosed(List<Point2D.Double> points) {
|
if (points == null || points.size() < 3) {
|
return;
|
}
|
Point2D.Double first = points.get(0);
|
Point2D.Double last = points.get(points.size() - 1);
|
if (first == null || last == null) {
|
return;
|
}
|
if (!arePointsClose(first, last)) {
|
points.add(new Point2D.Double(first.x, first.y));
|
}
|
}
|
|
int captureHandheldBoundaryPoint() {
|
if (!handheldCaptureActive) {
|
return -1;
|
}
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
JOptionPane.showMessageDialog(this, "未检测到采集设备,请检查连接。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
|
String[] latParts = splitCoordinateComponents(device.getRealtimeLatitude(), true);
|
String[] lonParts = splitCoordinateComponents(device.getRealtimeLongitude(), false);
|
if (latParts == null || lonParts == null) {
|
JOptionPane.showMessageDialog(this, "当前定位无效,请在定位稳定后再试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
|
double x = parseMetersValue(device.getRealtimeX());
|
double y = parseMetersValue(device.getRealtimeY());
|
if (!Double.isFinite(x) || !Double.isFinite(y)) {
|
JOptionPane.showMessageDialog(this, "当前定位数据无效,请稍后再试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
if (isDuplicateHandheldPoint(x, y)) {
|
JOptionPane.showMessageDialog(this, "当前坐标已采集,请移动到新的位置后再试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
|
double altitude = parseAltitudeValue(device.getRealtimeAltitude());
|
Coordinate coordinate = new Coordinate(latParts[0], latParts[1], lonParts[0], lonParts[1], altitude);
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.add(coordinate);
|
}
|
|
if (mapRenderer != null) {
|
mapRenderer.addHandheldBoundaryPoint(x, y);
|
}
|
|
List<Point2D.Double> snapshot;
|
synchronized (handheldTemporaryPoints) {
|
handheldTemporaryPoints.add(new Point2D.Double(x, y));
|
snapshot = new ArrayList<>(handheldTemporaryPoints);
|
}
|
AddDikuai.recordTemporaryBoundaryPoints(snapshot);
|
|
handheldCapturedPoints++;
|
return handheldCapturedPoints;
|
}
|
|
boolean finishHandheldBoundaryCapture() {
|
if (!handheldCaptureActive) {
|
return false;
|
}
|
if (handheldCapturedPoints < 3) {
|
JOptionPane.showMessageDialog(this, "至少采集三个点才能生成边界。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
List<Point2D.Double> closedSnapshot = createClosedHandheldPointSnapshot();
|
handheldCaptureActive = false;
|
activeBoundaryMode = BoundaryCaptureMode.NONE;
|
Coordinate.setStartSaveGngga(false);
|
if (mapRenderer != null) {
|
mapRenderer.clearHandheldBoundaryPreview();
|
}
|
|
AddDikuai.recordTemporaryBoundaryPoints(closedSnapshot);
|
|
exitHandheldCaptureInlineUi();
|
|
SwingUtilities.invokeLater(AddDikuai::finishDrawingSession);
|
return true;
|
}
|
|
int getHandheldCapturedPointCount() {
|
return handheldCapturedPoints;
|
}
|
|
public List<Point2D.Double> getHandheldTemporaryPointsSnapshot() {
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
return createClosedMowerPointSnapshot();
|
}
|
if (!handheldCaptureActive) {
|
return createClosedHandheldPointSnapshot();
|
}
|
synchronized (handheldTemporaryPoints) {
|
return new ArrayList<>(handheldTemporaryPoints);
|
}
|
}
|
|
public boolean isCurrentHandheldPointDuplicate() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return false;
|
}
|
double x = parseMetersValue(device.getRealtimeX());
|
double y = parseMetersValue(device.getRealtimeY());
|
if (!Double.isFinite(x) || !Double.isFinite(y)) {
|
return false;
|
}
|
return isDuplicateHandheldPoint(x, y);
|
}
|
|
public boolean hasValidRealtimeHandheldPosition() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return false;
|
}
|
double x = parseMetersValue(device.getRealtimeX());
|
double y = parseMetersValue(device.getRealtimeY());
|
return Double.isFinite(x) && Double.isFinite(y);
|
}
|
|
private boolean isDuplicateHandheldPoint(double x, double y) {
|
Point2D.Double candidate = new Point2D.Double(x, y);
|
synchronized (handheldTemporaryPoints) {
|
for (Point2D.Double existing : handheldTemporaryPoints) {
|
if (existing == null) {
|
continue;
|
}
|
if (arePointsClose(existing, candidate)) {
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
private boolean arePointsClose(Point2D.Double a, Point2D.Double b) {
|
if (a == null || b == null) {
|
return false;
|
}
|
double dx = a.x - b.x;
|
double dy = a.y - b.y;
|
return Math.hypot(dx, dy) < HANDHELD_DUPLICATE_THRESHOLD_METERS;
|
}
|
|
private List<Point2D.Double> createClosedHandheldPointSnapshot() {
|
List<Point2D.Double> copy = new ArrayList<>();
|
synchronized (handheldTemporaryPoints) {
|
for (Point2D.Double point : handheldTemporaryPoints) {
|
if (point != null) {
|
copy.add(new Point2D.Double(point.x, point.y));
|
}
|
}
|
}
|
ensureClosed(copy);
|
return copy;
|
}
|
|
private List<Point2D.Double> createClosedMowerPointSnapshot() {
|
List<Point2D.Double> copy = new ArrayList<>();
|
synchronized (mowerTemporaryPoints) {
|
for (Point2D.Double point : mowerTemporaryPoints) {
|
if (point != null) {
|
copy.add(new Point2D.Double(point.x, point.y));
|
}
|
}
|
}
|
ensureClosed(copy);
|
return copy;
|
}
|
|
private String[] splitCoordinateComponents(String combined, boolean latitude) {
|
if (combined == null) {
|
return null;
|
}
|
String trimmed = combined.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
|
String valuePart;
|
String directionPart = null;
|
String[] parts = trimmed.split(",");
|
if (parts.length >= 2) {
|
valuePart = parts[0].trim();
|
directionPart = parts[1].trim();
|
} else {
|
valuePart = trimmed;
|
}
|
|
if (valuePart.isEmpty()) {
|
return null;
|
}
|
|
if (directionPart == null || directionPart.isEmpty()) {
|
char lastChar = valuePart.charAt(valuePart.length() - 1);
|
if (Character.isLetter(lastChar)) {
|
directionPart = String.valueOf(lastChar);
|
valuePart = valuePart.substring(0, valuePart.length() - 1).trim();
|
}
|
}
|
|
if (valuePart.isEmpty()) {
|
return null;
|
}
|
|
directionPart = normalizeHemisphere(directionPart, latitude);
|
return new String[]{valuePart, directionPart};
|
}
|
|
private String normalizeHemisphere(String direction, boolean latitude) {
|
if (direction == null || direction.trim().isEmpty()) {
|
return latitude ? "N" : "E";
|
}
|
String normalized = direction.trim().toUpperCase(Locale.ROOT);
|
if (latitude) {
|
if (!"N".equals(normalized) && !"S".equals(normalized)) {
|
return "N";
|
}
|
} else {
|
if (!"E".equals(normalized) && !"W".equals(normalized)) {
|
return "E";
|
}
|
}
|
return normalized;
|
}
|
|
private double parseMetersValue(String raw) {
|
if (raw == null) {
|
return Double.NaN;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return Double.NaN;
|
}
|
try {
|
return Double.parseDouble(trimmed);
|
} catch (NumberFormatException ex) {
|
return Double.NaN;
|
}
|
}
|
|
private double parseAltitudeValue(String raw) {
|
if (raw == null) {
|
return 0.0;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return 0.0;
|
}
|
try {
|
return Double.parseDouble(trimmed);
|
} catch (NumberFormatException ex) {
|
return 0.0;
|
}
|
}
|
|
private boolean beginMowingSession() {
|
if (mapRenderer == null) {
|
return false;
|
}
|
String landNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
if (!isMeaningfulValue(landNumber)) {
|
JOptionPane.showMessageDialog(this, "请先选择地块后再开始作业", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double widthMeters = resolveMowerWidthMeters(landNumber);
|
if (widthMeters <= 0) {
|
JOptionPane.showMessageDialog(this, "未配置割草宽度,将无法计算作业面积", "提示", JOptionPane.WARNING_MESSAGE);
|
}
|
|
mapRenderer.startRealtimeTrackRecording(landNumber, widthMeters);
|
refreshMowerSpeedLabel();
|
return true;
|
}
|
|
private void pauseMowingSession() {
|
if (mapRenderer == null) {
|
return;
|
}
|
mapRenderer.pauseRealtimeTrackRecording();
|
refreshMowerSpeedLabel();
|
}
|
|
private void stopMowingSession() {
|
if (mapRenderer == null) {
|
return;
|
}
|
mapRenderer.stopRealtimeTrackRecording();
|
refreshMowerSpeedLabel();
|
}
|
|
private double resolveMowerWidthMeters(String landNumber) {
|
double width = 0.0;
|
if (isMeaningfulValue(landNumber)) {
|
Dikuai current = Dikuai.getDikuai(landNumber);
|
if (current != null) {
|
width = parseMowerWidthMeters(current.getMowingWidth());
|
}
|
}
|
if (width > 0) {
|
return width;
|
}
|
return parseMowerWidthFromDevice();
|
}
|
|
private double parseMowerWidthFromDevice() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return 0.0;
|
}
|
return parseMowerWidthMeters(device.getMowingWidth());
|
}
|
|
private double parseMowerWidthMeters(String raw) {
|
if (raw == null) {
|
return 0.0;
|
}
|
String sanitized = raw.trim().toLowerCase(Locale.ROOT);
|
if (sanitized.isEmpty() || "-1".equals(sanitized)) {
|
return 0.0;
|
}
|
sanitized = sanitized.replace("厘米", "cm");
|
sanitized = sanitized.replace("公分", "cm");
|
sanitized = sanitized.replace("米", "m");
|
sanitized = sanitized.replace("cm", "");
|
sanitized = sanitized.replace("m", "");
|
sanitized = sanitized.trim();
|
if (sanitized.isEmpty()) {
|
return 0.0;
|
}
|
try {
|
double value = Double.parseDouble(sanitized);
|
if (value <= 0) {
|
return 0.0;
|
}
|
if (value > 10) {
|
return value / 100.0;
|
}
|
return value;
|
} catch (NumberFormatException ex) {
|
return 0.0;
|
}
|
}
|
|
private void applyStatusLabelColor(String statusText) {
|
if (statusLabel == null) {
|
return;
|
}
|
if ("作业中".equals(statusText) || "绘制中".equals(statusText)) {
|
statusLabel.setForeground(THEME_COLOR);
|
} else if ("暂停中".equals(statusText) || "绘制暂停".equals(statusText)) {
|
statusLabel.setForeground(STATUS_PAUSE_COLOR);
|
} else {
|
statusLabel.setForeground(Color.GRAY);
|
}
|
}
|
|
private void ensureBluetoothIconsLoaded() {
|
if (bluetoothIcon == null) {
|
bluetoothIcon = loadScaledIcon("image/blue.png", 28, 28);
|
}
|
if (bluetoothLinkedIcon == null) {
|
bluetoothLinkedIcon = loadScaledIcon("image/bluelink.png", 28, 28);
|
}
|
}
|
|
private JButton createFloatingIconButton() {
|
JButton button = new JButton();
|
button.setContentAreaFilled(false);
|
button.setBorder(BorderFactory.createEmptyBorder());
|
button.setFocusPainted(false);
|
button.setOpaque(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
int size = FLOAT_ICON_SIZE + 8;
|
button.setPreferredSize(new Dimension(size, size));
|
return button;
|
}
|
|
private JButton createFloatingTextButton(String text) {
|
JButton button = new JButton(text);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 15));
|
button.setForeground(Color.WHITE);
|
button.setBackground(THEME_COLOR);
|
button.setBorder(BorderFactory.createEmptyBorder(10, 18, 10, 18));
|
button.setFocusPainted(false);
|
button.setOpaque(true);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(THEME_HOVER_COLOR);
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(THEME_COLOR);
|
}
|
});
|
return button;
|
}
|
|
private ImageIcon loadScaledIcon(String path, int width, int height) {
|
try {
|
ImageIcon icon = new ImageIcon(path);
|
if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) {
|
return null;
|
}
|
Image scaled = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
|
return new ImageIcon(scaled);
|
} catch (Exception ex) {
|
System.err.println("加载图标失败: " + path + " - " + ex.getMessage());
|
return null;
|
}
|
}
|
|
private void ensureFloatingIconsLoaded() {
|
if (pauseIcon == null) {
|
pauseIcon = loadScaledIcon("image/zanting.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
|
}
|
if (pauseActiveIcon == null) {
|
pauseActiveIcon = loadScaledIcon("image/zantingzhong.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
|
}
|
if (endIcon == null) {
|
endIcon = loadScaledIcon("image/end.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
|
}
|
}
|
|
private void updatePauseButtonVisual() {
|
if (drawingPauseButton == null) {
|
return;
|
}
|
if (drawingPaused) {
|
if (pauseActiveIcon != null) {
|
drawingPauseButton.setIcon(pauseActiveIcon);
|
drawingPauseButton.setText(null);
|
} else {
|
drawingPauseButton.setText("暂停中");
|
drawingPauseButton.setIcon(null);
|
}
|
drawingPauseButton.setToolTipText("点击恢复绘制");
|
} else {
|
if (pauseIcon != null) {
|
drawingPauseButton.setIcon(pauseIcon);
|
drawingPauseButton.setText(null);
|
} else {
|
drawingPauseButton.setText("暂停");
|
drawingPauseButton.setIcon(null);
|
}
|
drawingPauseButton.setToolTipText("点击暂停绘制");
|
}
|
}
|
|
private void applyDrawingPauseState(boolean paused, boolean notifyCoordinate) {
|
drawingPaused = paused;
|
updatePauseButtonVisual();
|
if (notifyCoordinate) {
|
Coordinate.setStartSaveGngga(!paused);
|
}
|
if (drawingControlModeActive) {
|
updateDrawingControlButtonLabels();
|
if (statusLabel != null) {
|
statusLabel.setText(paused ? "绘制暂停" : "绘制中");
|
}
|
}
|
}
|
|
private void toggleDrawingPause() {
|
applyDrawingPauseState(!drawingPaused, true);
|
}
|
|
public void showEndDrawingButton(Runnable callback) {
|
showEndDrawingButton(callback, null);
|
}
|
|
public void showEndDrawingButton(Runnable callback, String drawingShape) {
|
endDrawingCallback = callback;
|
circleDialogMode = false;
|
hideCircleGuidancePanel();
|
enterDrawingControlMode();
|
|
boolean enableCircleGuidance = drawingShape != null
|
&& "circle".equalsIgnoreCase(drawingShape.trim());
|
if (enableCircleGuidance) {
|
ensureFloatingIconsLoaded();
|
ensureFloatingButtonInfrastructure();
|
if (drawingPauseButton != null) {
|
drawingPauseButton.setVisible(false);
|
}
|
if (endDrawingButton != null) {
|
endDrawingButton.setVisible(false);
|
}
|
prepareCircleGuidanceState();
|
showCircleGuidanceStep(1);
|
floatingButtonPanel.setVisible(true);
|
if (floatingButtonPanel.getParent() != visualizationPanel) {
|
visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
|
}
|
rebuildFloatingButtonColumn();
|
} else {
|
clearCircleGuidanceArtifacts();
|
hideFloatingDrawingControls();
|
}
|
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
}
|
|
private void ensureFloatingButtonInfrastructure() {
|
if (endDrawingButton == null) {
|
endDrawingButton = createFloatingIconButton();
|
endDrawingButton.addActionListener(e -> {
|
if (endDrawingCallback != null) {
|
endDrawingCallback.run();
|
}
|
});
|
}
|
if (endIcon != null) {
|
endDrawingButton.setIcon(endIcon);
|
endDrawingButton.setText(null);
|
} else {
|
endDrawingButton.setText("结束绘制");
|
}
|
endDrawingButton.setToolTipText("结束绘制");
|
|
if (drawingPauseButton == null) {
|
drawingPauseButton = createFloatingIconButton();
|
drawingPauseButton.addActionListener(e -> toggleDrawingPause());
|
}
|
updatePauseButtonVisual();
|
|
if (floatingButtonPanel == null) {
|
floatingButtonPanel = new JPanel(new BorderLayout());
|
floatingButtonPanel.setOpaque(false);
|
floatingButtonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 20));
|
|
floatingButtonColumn = new JPanel();
|
floatingButtonColumn.setOpaque(false);
|
floatingButtonColumn.setLayout(new BoxLayout(floatingButtonColumn, BoxLayout.Y_AXIS));
|
floatingButtonPanel.add(floatingButtonColumn, BorderLayout.EAST);
|
}
|
}
|
|
private void hideFloatingDrawingControls() {
|
if (drawingPauseButton != null) {
|
drawingPauseButton.setVisible(false);
|
}
|
if (endDrawingButton != null) {
|
endDrawingButton.setVisible(false);
|
}
|
if (floatingButtonPanel != null) {
|
floatingButtonPanel.setVisible(false);
|
}
|
if (!circleDialogMode) {
|
rebuildFloatingButtonColumn();
|
}
|
}
|
|
private void rebuildFloatingButtonColumn() {
|
if (floatingButtonColumn == null) {
|
return;
|
}
|
floatingButtonColumn.removeAll();
|
boolean added = false;
|
if (!circleDialogMode && circleGuidancePanel != null && circleGuidancePanel.isVisible()) {
|
floatingButtonColumn.add(circleGuidancePanel);
|
added = true;
|
}
|
if (!circleDialogMode && drawingPauseButton != null && drawingPauseButton.isVisible()) {
|
if (added) {
|
floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
|
}
|
floatingButtonColumn.add(drawingPauseButton);
|
added = true;
|
}
|
if (!circleDialogMode && endDrawingButton != null && endDrawingButton.isVisible()) {
|
if (added) {
|
floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
|
}
|
floatingButtonColumn.add(endDrawingButton);
|
added = true;
|
}
|
if (pathPreviewReturnButton != null && pathPreviewReturnButton.isVisible()) {
|
if (added) {
|
floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
|
}
|
floatingButtonColumn.add(pathPreviewReturnButton);
|
added = true;
|
}
|
floatingButtonColumn.revalidate();
|
floatingButtonColumn.repaint();
|
}
|
|
private void showCircleGuidanceStep(int step) {
|
ensureCircleGuidancePanel();
|
if (circleGuidancePanel == null) {
|
return;
|
}
|
circleGuidanceStep = step;
|
|
if (step == 1) {
|
circleGuidanceLabel.setText("采集第1个点");
|
circleGuidancePrimaryButton.setText("确认第1点");
|
circleGuidanceSecondaryButton.setText("返回");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else if (step == 2) {
|
circleGuidanceLabel.setText("采集第2个点");
|
circleGuidancePrimaryButton.setText("确认第2点");
|
circleGuidanceSecondaryButton.setText("返回");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else if (step == 3) {
|
circleGuidanceLabel.setText("采集第3个点");
|
circleGuidancePrimaryButton.setText("确认第3点");
|
circleGuidanceSecondaryButton.setText("返回");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else if (step == 4) {
|
circleGuidanceLabel.setText("已采集三个点");
|
circleGuidancePrimaryButton.setText("结束绘制");
|
circleGuidanceSecondaryButton.setText("重新采集");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else {
|
hideCircleGuidancePanel();
|
return;
|
}
|
circleGuidancePanel.setVisible(true);
|
|
refreshCircleGuidanceButtonAvailability();
|
|
if (circleDialogMode) {
|
ensureCircleGuidanceDialog();
|
if (circleGuidanceDialog != null) {
|
circleGuidanceDialog.pack();
|
positionCircleGuidanceDialog();
|
circleGuidanceDialog.setVisible(true);
|
circleGuidanceDialog.toFront();
|
}
|
} else {
|
rebuildFloatingButtonColumn();
|
}
|
}
|
|
private void ensureCircleGuidancePanel() {
|
if (circleGuidancePanel != null) {
|
return;
|
}
|
circleGuidancePanel = new JPanel();
|
circleGuidancePanel.setLayout(new BoxLayout(circleGuidancePanel, BoxLayout.Y_AXIS));
|
circleGuidancePanel.setOpaque(true);
|
circleGuidancePanel.setBackground(new Color(255, 255, 255, 235));
|
circleGuidancePanel.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(THEME_COLOR, 1),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)));
|
circleGuidancePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
circleGuidanceLabel = new JLabel();
|
circleGuidanceLabel.setFont(new Font("微软雅黑", Font.BOLD, 13));
|
circleGuidanceLabel.setForeground(new Color(33, 37, 41));
|
circleGuidanceLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JPanel buttonRow = new JPanel();
|
buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.X_AXIS));
|
buttonRow.setOpaque(false);
|
buttonRow.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
circleGuidancePrimaryButton = createGuidanceButton("是", THEME_COLOR, Color.WHITE, true);
|
circleGuidancePrimaryButton.addActionListener(e -> onCircleGuidancePrimary());
|
|
circleGuidanceSecondaryButton = createGuidanceButton("否", Color.WHITE, THEME_COLOR, false);
|
circleGuidanceSecondaryButton.addActionListener(e -> onCircleGuidanceSecondary());
|
|
buttonRow.add(circleGuidancePrimaryButton);
|
buttonRow.add(Box.createRigidArea(new Dimension(8, 0)));
|
buttonRow.add(circleGuidanceSecondaryButton);
|
|
circleGuidancePanel.add(circleGuidanceLabel);
|
circleGuidancePanel.add(Box.createRigidArea(new Dimension(0, 8)));
|
circleGuidancePanel.add(buttonRow);
|
circleGuidancePanel.setVisible(false);
|
}
|
|
private void ensureCircleGuidanceDialog() {
|
if (circleGuidancePanel == null) {
|
return;
|
}
|
if (circleGuidanceDialog != null) {
|
if (circleGuidancePanel.getParent() != circleGuidanceDialog.getContentPane()) {
|
detachCircleGuidancePanel();
|
circleGuidanceDialog.getContentPane().removeAll();
|
circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
|
circleGuidanceDialog.pack();
|
}
|
return;
|
}
|
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
circleGuidanceDialog = new JDialog(owner, "绘制提示", Dialog.ModalityType.MODELESS);
|
circleGuidanceDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
circleGuidanceDialog.setResizable(false);
|
circleGuidanceDialog.setAlwaysOnTop(true);
|
|
if (owner != null && circleDialogOwnerAdapter == null) {
|
circleDialogOwnerAdapter = new ComponentAdapter() {
|
@Override
|
public void componentMoved(ComponentEvent e) {
|
positionCircleGuidanceDialog();
|
}
|
|
@Override
|
public void componentResized(ComponentEvent e) {
|
positionCircleGuidanceDialog();
|
}
|
};
|
owner.addComponentListener(circleDialogOwnerAdapter);
|
}
|
|
detachCircleGuidancePanel();
|
circleGuidanceDialog.getContentPane().setLayout(new BorderLayout());
|
circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
|
circleGuidanceDialog.pack();
|
}
|
|
private void detachCircleGuidancePanel() {
|
if (circleGuidancePanel == null) {
|
return;
|
}
|
Container parent = circleGuidancePanel.getParent();
|
if (parent != null) {
|
parent.remove(circleGuidancePanel);
|
parent.revalidate();
|
parent.repaint();
|
}
|
}
|
|
private void positionCircleGuidanceDialog() {
|
if (circleGuidanceDialog == null) {
|
return;
|
}
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
if (owner == null || !owner.isShowing()) {
|
return;
|
}
|
try {
|
Point ownerLocation = owner.getLocationOnScreen();
|
int x = ownerLocation.x + owner.getWidth() - circleGuidanceDialog.getWidth() - 30;
|
int y = ownerLocation.y + owner.getHeight() - circleGuidanceDialog.getHeight() - 40;
|
x = Math.max(ownerLocation.x, x);
|
y = Math.max(ownerLocation.y, y);
|
circleGuidanceDialog.setLocation(x, y);
|
} catch (IllegalComponentStateException ex) {
|
// Owner not yet displayable; skip positioning
|
}
|
}
|
|
private JButton createGuidanceButton(String text, Color bg, Color fg, boolean filled) {
|
JButton button = new JButton(text);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
button.setForeground(fg);
|
button.setBackground(bg);
|
button.setOpaque(true);
|
button.setFocusPainted(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.setAlignmentX(Component.LEFT_ALIGNMENT);
|
if (filled) {
|
button.setBorder(BorderFactory.createEmptyBorder(6, 14, 6, 14));
|
} else {
|
button.setBackground(Color.WHITE);
|
button.setOpaque(true);
|
button.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(THEME_COLOR, 1),
|
BorderFactory.createEmptyBorder(5, 12, 5, 12)));
|
}
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (!button.isEnabled()) {
|
return;
|
}
|
if (filled) {
|
button.setBackground(THEME_HOVER_COLOR);
|
} else {
|
button.setBackground(new Color(245, 245, 245));
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (!button.isEnabled()) {
|
return;
|
}
|
if (filled) {
|
button.setBackground(bg);
|
} else {
|
button.setBackground(Color.WHITE);
|
}
|
}
|
});
|
return button;
|
}
|
|
private void onCircleGuidancePrimary() {
|
if (circleGuidanceStep >= 1 && circleGuidanceStep <= 3) {
|
if (!recordCircleSamplePoint()) {
|
return;
|
}
|
if (circleGuidanceStep == 3) {
|
showCircleGuidanceStep(4);
|
} else {
|
showCircleGuidanceStep(circleGuidanceStep + 1);
|
}
|
} else if (circleGuidanceStep == 4) {
|
handleCircleCompletion();
|
}
|
}
|
|
private void onCircleGuidanceSecondary() {
|
if (circleGuidanceStep == 1 || circleGuidanceStep == 2 || circleGuidanceStep == 3) {
|
handleCircleAbort(null);
|
} else if (circleGuidanceStep == 4) {
|
restartCircleCapture();
|
}
|
}
|
|
private void hideCircleGuidancePanel() {
|
circleGuidanceStep = 0;
|
if (circleGuidancePanel != null) {
|
circleGuidancePanel.setVisible(false);
|
}
|
if (circleGuidanceDialog != null) {
|
circleGuidanceDialog.setVisible(false);
|
}
|
if (!circleDialogMode) {
|
rebuildFloatingButtonColumn();
|
}
|
}
|
|
private void handleCircleAbort(String message) {
|
// Return the wizard to step 2 without committing any captured points
|
hideEndDrawingButton();
|
addzhangaiwu.abortCircleDrawingAndReturn(message);
|
}
|
|
private void handleCircleCompletion() {
|
hideCircleGuidancePanel();
|
clearCircleGuidanceArtifacts();
|
if (endDrawingCallback != null) {
|
endDrawingCallback.run();
|
} else {
|
addzhangaiwu.finishDrawingSession();
|
}
|
}
|
|
private void prepareCircleGuidanceState() {
|
clearCircleGuidanceArtifacts();
|
circleBaseLatLon = resolveCircleBaseLatLon();
|
startCircleDataMonitor();
|
}
|
|
private void clearCircleGuidanceArtifacts() {
|
stopCircleDataMonitor();
|
circleCapturedPoints.clear();
|
circleBaseLatLon = null;
|
lastCapturedCoordinate = null;
|
if (mapRenderer != null) {
|
mapRenderer.clearCircleCaptureOverlay();
|
mapRenderer.clearCircleSampleMarkers();
|
}
|
}
|
|
private void restartCircleCapture() {
|
clearCircleGuidanceArtifacts();
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.clear();
|
}
|
Coordinate.setStartSaveGngga(true);
|
circleBaseLatLon = resolveCircleBaseLatLon();
|
showCircleGuidanceStep(1);
|
startCircleDataMonitor();
|
}
|
|
private boolean recordCircleSamplePoint() {
|
Coordinate latest = getLatestCoordinate();
|
if (latest == null) {
|
JOptionPane.showMessageDialog(this, "未获取到当前位置坐标,请稍后重试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double[] base = ensureCircleBaseLatLon();
|
if (base == null) {
|
JOptionPane.showMessageDialog(this, "基准站坐标无效,请先在基准站管理中完成设置。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
|
double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
|
if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
|
JOptionPane.showMessageDialog(this, "采集点坐标无效,请重新采集。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
|
circleCapturedPoints.add(local);
|
if (mapRenderer != null) {
|
mapRenderer.updateCircleSampleMarkers(circleCapturedPoints);
|
}
|
lastCapturedCoordinate = latest;
|
refreshCircleGuidanceButtonAvailability();
|
|
if (circleCapturedPoints.size() >= 3) {
|
CircleSolution solution = fitCircleFromPoints(circleCapturedPoints);
|
if (solution == null) {
|
circleCapturedPoints.remove(circleCapturedPoints.size() - 1);
|
JOptionPane.showMessageDialog(this, "无法根据当前三个点生成圆,请重新采集点。", "提示", JOptionPane.WARNING_MESSAGE);
|
restartCircleCapture();
|
return false;
|
}
|
if (mapRenderer != null) {
|
mapRenderer.showCircleCaptureOverlay(solution.centerX, solution.centerY, solution.radius, circleCapturedPoints);
|
}
|
}
|
|
return true;
|
}
|
|
private void startCircleDataMonitor() {
|
if (circleDataMonitor == null) {
|
circleDataMonitor = new Timer(600, e -> refreshCircleGuidanceButtonAvailability());
|
circleDataMonitor.setRepeats(true);
|
}
|
if (!circleDataMonitor.isRunning()) {
|
circleDataMonitor.start();
|
}
|
refreshCircleGuidanceButtonAvailability();
|
}
|
|
private void stopCircleDataMonitor() {
|
if (circleDataMonitor != null) {
|
circleDataMonitor.stop();
|
}
|
}
|
|
private void refreshCircleGuidanceButtonAvailability() {
|
if (circleGuidancePrimaryButton == null) {
|
return;
|
}
|
boolean shouldEnable = circleGuidanceStep >= 1 && circleGuidanceStep <= 3
|
? isCircleDataAvailable()
|
: true;
|
applyCirclePrimaryButtonState(shouldEnable);
|
}
|
|
private boolean isCircleDataAvailable() {
|
Coordinate latest = getLatestCoordinate();
|
if (latest == null) {
|
return false;
|
}
|
return lastCapturedCoordinate == null || latest != lastCapturedCoordinate;
|
}
|
|
private void applyCirclePrimaryButtonState(boolean enabled) {
|
if (circleGuidancePrimaryButton == null) {
|
return;
|
}
|
if (circleGuidanceStep >= 4) {
|
enabled = true;
|
}
|
circleGuidancePrimaryButton.setEnabled(enabled);
|
if (enabled) {
|
circleGuidancePrimaryButton.setBackground(THEME_COLOR);
|
circleGuidancePrimaryButton.setForeground(Color.WHITE);
|
circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
} else {
|
circleGuidancePrimaryButton.setBackground(new Color(200, 200, 200));
|
circleGuidancePrimaryButton.setForeground(new Color(120, 120, 120));
|
circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
}
|
}
|
|
private Coordinate getLatestCoordinate() {
|
synchronized (Coordinate.coordinates) {
|
int size = Coordinate.coordinates.size();
|
if (size == 0) {
|
return null;
|
}
|
return Coordinate.coordinates.get(size - 1);
|
}
|
}
|
|
private double[] ensureCircleBaseLatLon() {
|
if (circleBaseLatLon == null) {
|
circleBaseLatLon = resolveCircleBaseLatLon();
|
}
|
return circleBaseLatLon;
|
}
|
|
private double[] resolveCircleBaseLatLon() {
|
String coords = null;
|
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
baseStation.load();
|
coords = baseStation.getInstallationCoordinates();
|
|
if (!isMeaningfulValue(coords)) {
|
String landNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
if (isMeaningfulValue(landNumber)) {
|
Dikuai current = Dikuai.getDikuai(landNumber);
|
if (current != null) {
|
coords = current.getBaseStationCoordinates();
|
}
|
}
|
if (!isMeaningfulValue(coords)) {
|
coords = addzhangaiwu.getActiveSessionBaseStation();
|
}
|
if (!isMeaningfulValue(coords)) {
|
return null;
|
}
|
}
|
String[] parts = coords.split(",");
|
if (parts.length < 4) {
|
return null;
|
}
|
double baseLat = parseDMToDecimal(parts[0], parts[1]);
|
double baseLon = parseDMToDecimal(parts[2], parts[3]);
|
if (!Double.isFinite(baseLat) || !Double.isFinite(baseLon)) {
|
return null;
|
}
|
return new double[]{baseLat, baseLon};
|
}
|
|
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;
|
}
|
}
|
|
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};
|
}
|
|
private CircleSolution fitCircleFromPoints(List<double[]> points) {
|
if (points == null || points.size() < 3) {
|
return null;
|
}
|
CircleSolution best = null;
|
double bestScore = 0.0;
|
int n = points.size();
|
for (int i = 0; i < n - 2; i++) {
|
double[] p1 = points.get(i);
|
for (int j = i + 1; j < n - 1; j++) {
|
double[] p2 = points.get(j);
|
for (int k = j + 1; k < n; k++) {
|
double[] p3 = points.get(k);
|
CircleSolution candidate = circleFromThreePoints(p1, p2, p3);
|
if (candidate == null || candidate.radius <= 0) {
|
continue;
|
}
|
double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3)));
|
if (minEdge > bestScore) {
|
bestScore = minEdge;
|
best = candidate;
|
}
|
}
|
}
|
}
|
return best;
|
}
|
|
private CircleSolution circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
|
if (p1 == null || p2 == null || p3 == null) {
|
return null;
|
}
|
double x1 = p1[0];
|
double y1 = p1[1];
|
double x2 = p2[0];
|
double y2 = p2[1];
|
double x3 = p3[0];
|
double y3 = p3[1];
|
|
double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
|
double d = 2.0 * a;
|
if (Math.abs(d) < 1e-6) {
|
return null;
|
}
|
|
double x1Sq = x1 * x1 + y1 * y1;
|
double x2Sq = x2 * x2 + y2 * y2;
|
double x3Sq = x3 * x3 + y3 * y3;
|
|
double centerX = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / d;
|
double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / d;
|
double radius = Math.hypot(centerX - x1, centerY - y1);
|
if (!Double.isFinite(centerX) || !Double.isFinite(centerY) || !Double.isFinite(radius)) {
|
return null;
|
}
|
if (radius < 0.05) {
|
return null;
|
}
|
return new CircleSolution(centerX, centerY, radius);
|
}
|
|
private double distance(double[] a, double[] b) {
|
if (a == null || b == null) {
|
return 0.0;
|
}
|
double dx = a[0] - b[0];
|
double dy = a[1] - b[1];
|
return Math.hypot(dx, dy);
|
}
|
|
private static final class CircleSolution {
|
final double centerX;
|
final double centerY;
|
final double radius;
|
|
CircleSolution(double centerX, double centerY, double radius) {
|
this.centerX = centerX;
|
this.centerY = centerY;
|
this.radius = radius;
|
}
|
}
|
|
public void hideEndDrawingButton() {
|
hideCircleGuidancePanel();
|
clearCircleGuidanceArtifacts();
|
hideFloatingDrawingControls();
|
circleDialogMode = false;
|
exitHandheldCaptureInlineUi();
|
handheldCaptureActive = false;
|
exitDrawingControlMode();
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
stopMowerBoundaryCapture();
|
} else if (activeBoundaryMode == BoundaryCaptureMode.HANDHELD && !handheldCaptureActive) {
|
activeBoundaryMode = BoundaryCaptureMode.NONE;
|
}
|
endDrawingCallback = null;
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
setHandheldMowerIconActive(false);
|
}
|
|
private void showPathPreviewReturnControls() {
|
ensureFloatingButtonInfrastructure();
|
if (drawingPauseButton != null) {
|
drawingPauseButton.setVisible(false);
|
}
|
if (endDrawingButton != null) {
|
endDrawingButton.setVisible(false);
|
}
|
if (pathPreviewReturnButton == null) {
|
pathPreviewReturnButton = createFloatingTextButton("返回");
|
pathPreviewReturnButton.setToolTipText("返回新增地块步骤");
|
pathPreviewReturnButton.addActionListener(e -> handlePathPreviewReturn());
|
}
|
pathPreviewReturnButton.setVisible(true);
|
if (floatingButtonPanel != null) {
|
floatingButtonPanel.setVisible(true);
|
if (floatingButtonPanel.getParent() != visualizationPanel) {
|
visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
|
}
|
}
|
rebuildFloatingButtonColumn();
|
}
|
|
private void hidePathPreviewReturnControls() {
|
if (pathPreviewReturnButton != null) {
|
pathPreviewReturnButton.setVisible(false);
|
}
|
rebuildFloatingButtonColumn();
|
if (floatingButtonPanel != null && floatingButtonColumn != null
|
&& floatingButtonColumn.getComponentCount() == 0) {
|
floatingButtonPanel.setVisible(false);
|
}
|
}
|
|
private void handlePathPreviewReturn() {
|
Runnable callback = pathPreviewReturnAction;
|
exitMowingPathPreview();
|
if (callback != null) {
|
callback.run();
|
}
|
}
|
|
public boolean startMowingPathPreview(String landNumber,
|
String landName,
|
String boundary,
|
String obstacles,
|
String plannedPath,
|
Runnable returnAction) {
|
if (mapRenderer == null || !isMeaningfulValue(plannedPath)) {
|
return false;
|
}
|
|
if (pathPreviewActive) {
|
exitMowingPathPreview();
|
}
|
|
exitDrawingControlMode();
|
hideCircleGuidancePanel();
|
clearCircleGuidanceArtifacts();
|
|
pathPreviewReturnAction = returnAction;
|
pathPreviewActive = true;
|
mapRenderer.setPathPreviewSizingEnabled(true);
|
|
previewRestoreLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
previewRestoreLandName = null;
|
if (isMeaningfulValue(previewRestoreLandNumber)) {
|
Dikuai existing = Dikuai.getDikuai(previewRestoreLandNumber);
|
if (existing != null) {
|
previewRestoreLandName = existing.getLandName();
|
}
|
}
|
|
mapRenderer.setCurrentBoundary(boundary, landNumber, landName);
|
mapRenderer.setCurrentObstacles(obstacles, landNumber);
|
mapRenderer.setCurrentPlannedPath(plannedPath);
|
mapRenderer.clearHandheldBoundaryPreview();
|
mapRenderer.setBoundaryPointSizeScale(1.0d);
|
mapRenderer.setBoundaryPointsVisible(isMeaningfulValue(boundary));
|
|
String displayName = isMeaningfulValue(landName) ? landName : landNumber;
|
updateCurrentAreaName(displayName);
|
|
showPathPreviewReturnControls();
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
return true;
|
}
|
|
public void exitMowingPathPreview() {
|
if (!pathPreviewActive) {
|
return;
|
}
|
pathPreviewActive = false;
|
if (mapRenderer != null) {
|
mapRenderer.setPathPreviewSizingEnabled(false);
|
}
|
hidePathPreviewReturnControls();
|
|
String restoreNumber = previewRestoreLandNumber;
|
String restoreName = previewRestoreLandName;
|
previewRestoreLandNumber = null;
|
previewRestoreLandName = null;
|
pathPreviewReturnAction = null;
|
|
if (restoreNumber != null) {
|
Dikuaiguanli.setCurrentWorkLand(restoreNumber, restoreName);
|
} else if (mapRenderer != null) {
|
mapRenderer.setCurrentBoundary(null, null, null);
|
mapRenderer.setCurrentObstacles((String) null, null);
|
mapRenderer.setCurrentPlannedPath(null);
|
mapRenderer.setBoundaryPointsVisible(false);
|
mapRenderer.setBoundaryPointSizeScale(1.0d);
|
mapRenderer.clearHandheldBoundaryPreview();
|
mapRenderer.resetView();
|
updateCurrentAreaName(null);
|
}
|
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
}
|
|
/**
|
* 获取地图渲染器实例
|
*/
|
public MapRenderer getMapRenderer() {
|
return mapRenderer;
|
}
|
|
public void updateCurrentAreaName(String areaName) {
|
if (areaNameLabel == null) {
|
return;
|
}
|
if (areaName == null || areaName.trim().isEmpty()) {
|
areaNameLabel.setText("未选择地块");
|
} else {
|
areaNameLabel.setText(areaName);
|
}
|
}
|
|
/**
|
* 重置地图视图
|
*/
|
public void resetMapView() {
|
if (mapRenderer != null) {
|
mapRenderer.resetView();
|
}
|
}
|
|
private void initializeDefaultAreaSelection() {
|
Dikuai.initFromProperties();
|
String persistedLandNumber = Dikuaiguanli.getPersistedWorkLandNumber();
|
if (persistedLandNumber != null) {
|
Dikuai stored = Dikuai.getDikuai(persistedLandNumber);
|
if (stored != null) {
|
Dikuaiguanli.setCurrentWorkLand(persistedLandNumber, stored.getLandName());
|
return;
|
}
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
}
|
|
Map<String, Dikuai> all = Dikuai.getAllDikuai();
|
if (all.isEmpty()) {
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
} else if (all.size() == 1) {
|
Dikuai only = all.values().iterator().next();
|
if (only != null) {
|
Dikuaiguanli.setCurrentWorkLand(only.getLandNumber(), only.getLandName());
|
}
|
}
|
}
|
|
private void refreshMapForSelectedArea() {
|
if (mapRenderer == null) {
|
return;
|
}
|
|
String currentLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
if (isMeaningfulValue(currentLandNumber)) {
|
Dikuai current = Dikuai.getDikuai(currentLandNumber);
|
String landName = current != null ? current.getLandName() : null;
|
Dikuaiguanli.setCurrentWorkLand(currentLandNumber, landName);
|
return;
|
}
|
|
String labelName = areaNameLabel != null ? areaNameLabel.getText() : null;
|
if (!isMeaningfulAreaName(labelName)) {
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
return;
|
}
|
|
Map<String, Dikuai> all = Dikuai.getAllDikuai();
|
for (Dikuai dikuai : all.values()) {
|
if (dikuai == null) {
|
continue;
|
}
|
String candidateName = dikuai.getLandName();
|
if (candidateName != null && candidateName.trim().equals(labelName.trim())) {
|
Dikuaiguanli.setCurrentWorkLand(dikuai.getLandNumber(), candidateName);
|
return;
|
}
|
}
|
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
}
|
|
private boolean isMeaningfulValue(String value) {
|
if (value == null) {
|
return false;
|
}
|
String trimmed = value.trim();
|
return !trimmed.isEmpty() && !"-1".equals(trimmed);
|
}
|
|
private boolean isMeaningfulAreaName(String value) {
|
if (value == null) {
|
return false;
|
}
|
String trimmed = value.trim();
|
if (trimmed.isEmpty()) {
|
return false;
|
}
|
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();
|
}
|
}
|
}
|
|
// 测试方法
|
public static void main(String[] args) {
|
JFrame frame = new JFrame("AutoMow - 首页");
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
frame.setSize(400, 800);
|
frame.setLocationRelativeTo(null);
|
|
Shouye shouye = new Shouye();
|
frame.add(shouye);
|
|
frame.setVisible(true);
|
UDPServer.startAsync();//启动数据接收线程
|
}
|
}
|