From 6799351be12deb2f713f2c0a2b4c467a6d1098c3 Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期二, 02 十二月 2025 19:51:00 +0800
Subject: [PATCH] 2025122
---
src/zhuye/Shouye.java | 550 ++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 503 insertions(+), 47 deletions(-)
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index 14565cb..4f20092 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -1,6 +1,7 @@
package zhuye;
import javax.swing.*;
+import javax.swing.Timer;
import baseStation.BaseStation;
import baseStation.BaseStationDialog;
@@ -14,6 +15,8 @@
import gecaoji.Device;
import set.Sets;
import udpdell.UDPServer;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
@@ -28,6 +31,7 @@
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;
@@ -38,11 +42,12 @@
// 鎸夐挳
private JButton startBtn;
- private JButton pauseBtn;
+ private JButton stopBtn;
private JButton legendBtn;
private JButton remoteBtn;
private JButton areaSelectBtn;
private JButton baseStationBtn;
+ private JButton bluetoothBtn;
// 瀵艰埅鎸夐挳
private JButton homeNavBtn;
@@ -76,6 +81,8 @@
private ImageIcon pauseIcon;
private ImageIcon pauseActiveIcon;
private ImageIcon endIcon;
+ private ImageIcon bluetoothIcon;
+ private ImageIcon bluetoothLinkedIcon;
private JPanel circleGuidancePanel;
private JLabel circleGuidanceLabel;
private JButton circleGuidancePrimaryButton;
@@ -84,6 +91,14 @@
private JDialog circleGuidanceDialog;
private boolean circleDialogMode;
private ComponentAdapter circleDialogOwnerAdapter;
+ private static final double METERS_PER_DEGREE_LAT = 111320.0d;
+ private final List<double[]> circleCapturedPoints = new ArrayList<>();
+ private double[] circleBaseLatLon;
+ private Timer circleDataMonitor;
+ private Coordinate lastCapturedCoordinate;
+ private boolean startButtonShowingPause = true;
+ private boolean stopButtonActive = false;
+ private boolean bluetoothConnected = false;
public Shouye() {
instance = this;
@@ -163,6 +178,11 @@
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);
@@ -171,7 +191,7 @@
JPanel rightActionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
rightActionPanel.setBackground(PANEL_BACKGROUND);
- JButton notificationBtn = createIconButton("馃敂", 40);
+ bluetoothBtn = createBluetoothButton();
// 淇敼璁剧疆鎸夐挳锛氫娇鐢ㄥ浘鐗囨浛浠nicode鍥炬爣
JButton settingsBtn = new JButton();
@@ -203,7 +223,7 @@
// 娣诲姞璁剧疆鎸夐挳浜嬩欢
settingsBtn.addActionListener(e -> showSettingsDialog());
- rightActionPanel.add(notificationBtn);
+ rightActionPanel.add(bluetoothBtn);
rightActionPanel.add(settingsBtn);
headerPanel.add(leftInfoPanel, BorderLayout.WEST);
@@ -260,14 +280,14 @@
JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 20, 0));
buttonPanel.setBackground(PANEL_BACKGROUND);
- startBtn = createControlButton("寮�濮�", THEME_COLOR);
- applyButtonIcon(startBtn, "image/startzuoye.png");
- pauseBtn = createControlButton("鏆傚仠", Color.ORANGE);
- applyButtonIcon(pauseBtn, "image/zantingzuoye.png");
- pauseBtn.setEnabled(false);
+ startBtn = createControlButton("鏆傚仠", THEME_COLOR);
+ updateStartButtonAppearance();
+
+ stopBtn = createControlButton("缁撴潫", Color.ORANGE);
+ updateStopButtonIcon();
buttonPanel.add(startBtn);
- buttonPanel.add(pauseBtn);
+ buttonPanel.add(stopBtn);
controlPanel.add(buttonPanel, BorderLayout.CENTER);
}
@@ -310,6 +330,35 @@
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>");
@@ -424,6 +473,9 @@
// 鍔熻兘鎸夐挳浜嬩欢
legendBtn.addActionListener(e -> showLegendDialog());
+ if (bluetoothBtn != null) {
+ bluetoothBtn.addActionListener(e -> toggleBluetoothConnection());
+ }
remoteBtn.addActionListener(e -> showRemoteControlDialog());
areaSelectBtn.addActionListener(e -> {
// 鐐瑰嚮鈥滃湴鍧椻�濈洿鎺ユ墦寮�鍦板潡绠$悊瀵硅瘽妗嗭紙鑻ラ渶瑕佸彲浼犲叆鐗瑰畾鍦板潡缂栧彿锛�
@@ -432,8 +484,8 @@
baseStationBtn.addActionListener(e -> showBaseStationDialog());
// 鎺у埗鎸夐挳浜嬩欢
- startBtn.addActionListener(e -> startMowing());
- pauseBtn.addActionListener(e -> pauseMowing());
+ startBtn.addActionListener(e -> toggleStartPause());
+ stopBtn.addActionListener(e -> handleStopAction());
}
private void showSettingsDialog() {
@@ -563,16 +615,107 @@
}
}
- private void startMowing() {
- statusLabel.setText("浣滀笟涓�");
- startBtn.setEnabled(false);
- pauseBtn.setEnabled(true);
+ private void toggleStartPause() {
+ if (startBtn == null) {
+ return;
+ }
+ startButtonShowingPause = !startButtonShowingPause;
+ if (startButtonShowingPause) {
+ statusLabel.setText("浣滀笟涓�");
+ if (stopButtonActive) {
+ stopButtonActive = false;
+ updateStopButtonIcon();
+ }
+ } else {
+ statusLabel.setText("鏆傚仠涓�");
+ }
+ updateStartButtonAppearance();
}
-
- private void pauseMowing() {
- statusLabel.setText("鏆傚仠涓�");
- startBtn.setEnabled(true);
- pauseBtn.setEnabled(false);
+
+ private void handleStopAction() {
+ stopButtonActive = !stopButtonActive;
+ updateStopButtonIcon();
+ if (stopButtonActive) {
+ statusLabel.setText("宸茬粨鏉�");
+ startButtonShowingPause = false;
+ } else {
+ statusLabel.setText("寰呮満");
+ startButtonShowingPause = true;
+ }
+ updateStartButtonAppearance();
+ }
+
+ 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 void applyStatusLabelColor(String statusText) {
+ if (statusLabel == null) {
+ return;
+ }
+ if ("浣滀笟涓�".equals(statusText)) {
+ statusLabel.setForeground(THEME_COLOR);
+ } else if ("鏆傚仠涓�".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() {
@@ -657,26 +800,27 @@
public void showEndDrawingButton(Runnable callback, String drawingShape) {
endDrawingCallback = callback;
applyDrawingPauseState(false, false);
- circleDialogMode = drawingShape != null && "circle".equalsIgnoreCase(drawingShape);
-
- if (circleDialogMode) {
- hideFloatingDrawingControls();
- ensureCircleGuidancePanel();
- showCircleGuidanceStep(1);
- visualizationPanel.revalidate();
- visualizationPanel.repaint();
- return;
- }
-
circleDialogMode = false;
hideCircleGuidancePanel();
ensureFloatingIconsLoaded();
ensureFloatingButtonInfrastructure();
- endDrawingButton.setVisible(true);
- if (drawingPauseButton != null) {
- drawingPauseButton.setVisible(true);
+ boolean enableCircleGuidance = drawingShape != null
+ && "circle".equalsIgnoreCase(drawingShape.trim());
+ if (enableCircleGuidance) {
+ prepareCircleGuidanceState();
+ showCircleGuidanceStep(1);
+ endDrawingButton.setVisible(false);
+ if (drawingPauseButton != null) {
+ drawingPauseButton.setVisible(false);
+ }
+ } else {
+ clearCircleGuidanceArtifacts();
+ endDrawingButton.setVisible(true);
+ if (drawingPauseButton != null) {
+ drawingPauseButton.setVisible(true);
+ }
}
floatingButtonPanel.setVisible(true);
@@ -773,20 +917,33 @@
}
circleGuidanceStep = step;
if (step == 1) {
- circleGuidanceLabel.setText("鏄惁灏嗗綋鍓嶇偣璁剧疆涓哄渾蹇冿紵");
- circleGuidancePrimaryButton.setText("鏄�");
+ circleGuidanceLabel.setText("閲囬泦绗�1涓偣");
+ circleGuidancePrimaryButton.setText("纭绗�1鐐�");
circleGuidanceSecondaryButton.setText("杩斿洖");
circleGuidanceSecondaryButton.setVisible(true);
} else if (step == 2) {
- circleGuidanceLabel.setText("鏄惁灏嗗綋鍓嶇偣璁剧疆涓哄崐寰勭偣锛�");
- circleGuidancePrimaryButton.setText("鏄�");
- circleGuidanceSecondaryButton.setVisible(false);
+ 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) {
@@ -933,6 +1090,9 @@
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
+ if (!button.isEnabled()) {
+ return;
+ }
if (filled) {
button.setBackground(THEME_HOVER_COLOR);
} else {
@@ -942,6 +1102,9 @@
@Override
public void mouseExited(MouseEvent e) {
+ if (!button.isEnabled()) {
+ return;
+ }
if (filled) {
button.setBackground(bg);
} else {
@@ -953,20 +1116,25 @@
}
private void onCircleGuidancePrimary() {
- if (circleGuidanceStep == 1) {
- showCircleGuidanceStep(2);
- } else {
+ 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) {
+ if (circleGuidanceStep == 1 || circleGuidanceStep == 2 || circleGuidanceStep == 3) {
handleCircleAbort(null);
- } else if (circleGuidanceStep == 2) {
- handleCircleAbort(null);
- } else {
- hideCircleGuidancePanel();
+ } else if (circleGuidanceStep == 4) {
+ restartCircleCapture();
}
}
@@ -991,6 +1159,7 @@
private void handleCircleCompletion() {
hideCircleGuidancePanel();
+ clearCircleGuidanceArtifacts();
if (endDrawingCallback != null) {
endDrawingCallback.run();
} else {
@@ -998,8 +1167,295 @@
}
}
+ 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;
+ 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) && baseStation != null) {
+ coords = baseStation.getInstallationCoordinates();
+ }
+ 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;
applyDrawingPauseState(false, false);
--
Gitblit v1.10.0