张世豪
2025-12-09 32524195d474b74e48916867b2a6c2f022a40d98
src/zhangaiwu/AddDikuai.java
@@ -14,16 +14,17 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Comparator;
import java.awt.geom.Point2D;
import baseStation.BaseStation;
import bianjie.jisuanmianjie;
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
import gecaoji.Device;
import bianjie.bianjieguihua2;
import lujing.Lunjingguihua;
import ui.UIConfig;
@@ -49,6 +50,9 @@
    private final Color LIGHT_TEXT = new Color(108, 117, 125);
    private final Color BORDER_COLOR = new Color(222, 226, 230);
    private final Color SUCCESS_COLOR = new Color(40, 167, 69);
    private final Color ERROR_COLOR = new Color(220, 53, 69);
    private static final String KEY_PATH_MESSAGE_TEXT = "__pathMessageText";
    private static final String KEY_PATH_MESSAGE_SUCCESS = "__pathMessageSuccess";
    
    // 步骤面板
    private JPanel mainPanel;
@@ -68,8 +72,12 @@
    private JButton prevButton;
    private JButton nextButton;
    private JButton createButton;
    private JButton previewButton;
    private Component previewButtonSpacer;
    private JLabel boundaryCountLabel;
    private JPanel obstacleListContainer;
    private JTextArea pathGenerationMessageArea;
    private JPanel pathMessageWrapper;
    
    // 地块数据
    private Map<String, String> dikuaiData = new HashMap<>();
@@ -967,6 +975,29 @@
        stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
        stepPanel.add(generatePathButton);
        stepPanel.add(Box.createRigidArea(new Dimension(0, 12)));
        pathMessageWrapper = new JPanel(new BorderLayout());
        pathMessageWrapper.setAlignmentX(Component.LEFT_ALIGNMENT);
        pathMessageWrapper.setBackground(PRIMARY_LIGHT);
        pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(PRIMARY_COLOR, 1),
            BorderFactory.createEmptyBorder(12, 12, 12, 12)
        ));
        pathMessageWrapper.setVisible(false);
        pathGenerationMessageArea = new JTextArea();
        pathGenerationMessageArea.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        pathGenerationMessageArea.setForeground(TEXT_COLOR);
        pathGenerationMessageArea.setOpaque(false);
        pathGenerationMessageArea.setEditable(false);
        pathGenerationMessageArea.setLineWrap(true);
        pathGenerationMessageArea.setWrapStyleWord(true);
        pathGenerationMessageArea.setFocusable(false);
        pathGenerationMessageArea.setBorder(null);
        pathMessageWrapper.add(pathGenerationMessageArea, BorderLayout.CENTER);
        stepPanel.add(pathMessageWrapper);
        stepPanel.add(Box.createVerticalGlue());
        return stepPanel;
@@ -1026,6 +1057,9 @@
    private void generateMowingPath() {
        if (!dikuaiData.containsKey("boundaryDrawn")) {
            JOptionPane.showMessageDialog(this, "请先完成边界绘制后再生成路径", "提示", JOptionPane.WARNING_MESSAGE);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("请先完成边界绘制后再生成路径。", false);
            setPathAvailability(false);
            showStep(2);
            return;
        }
@@ -1039,9 +1073,9 @@
        }
        if (boundaryCoords == null) {
            JOptionPane.showMessageDialog(this, "未找到有效的地块边界坐标,无法生成路径", "提示", JOptionPane.WARNING_MESSAGE);
            if (createButton != null) {
                createButton.setEnabled(false);
            }
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("未找到有效的地块边界坐标,无法生成路径。", false);
            setPathAvailability(false);
            return;
        }
@@ -1060,17 +1094,17 @@
        Object widthObj = mowingWidthSpinner.getValue();
        if (!(widthObj instanceof Number)) {
            JOptionPane.showMessageDialog(this, "割草宽度输入无效", "提示", JOptionPane.WARNING_MESSAGE);
            if (createButton != null) {
                createButton.setEnabled(false);
            }
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草宽度输入无效,请重新输入。", false);
            setPathAvailability(false);
            return;
        }
        double widthCm = ((Number) widthObj).doubleValue();
        if (widthCm <= 0) {
            JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
            if (createButton != null) {
                createButton.setEnabled(false);
            }
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("割草宽度必须大于0,请重新设置。", false);
            setPathAvailability(false);
            return;
        }
        dikuaiData.put("mowingWidth", widthObj.toString());
@@ -1088,31 +1122,141 @@
            String plannedPath = Lunjingguihua.formatPathSegments(segments);
            if (!isMeaningfulValue(plannedPath)) {
                JOptionPane.showMessageDialog(this, "生成割草路径失败: 生成结果为空", "错误", JOptionPane.ERROR_MESSAGE);
                if (createButton != null) {
                    createButton.setEnabled(false);
                }
                dikuaiData.remove("plannedPath");
                showPathGenerationMessage("生成割草路径失败:生成结果为空。", false);
                setPathAvailability(false);
                return;
            }
            dikuaiData.put("plannedPath", plannedPath);
            if (createButton != null) {
                createButton.setEnabled(true);
            if (isMeaningfulValue(boundaryCoords)) {
                dikuaiData.put("boundaryCoordinates", boundaryCoords);
            }
            JOptionPane.showMessageDialog(this,
                "已根据当前设置生成割草路径,共生成 " + segments.size() + " 段。",
                "成功",
                JOptionPane.INFORMATION_MESSAGE);
            if (isMeaningfulValue(obstacleCoords)) {
                dikuaiData.put("obstacleCoordinates", obstacleCoords);
            }
            dikuaiData.put("plannedPath", plannedPath);
            setPathAvailability(true);
            showPathGenerationMessage(
                "已根据当前设置生成割草路径,共生成 " + segments.size() + " 段。\n点击“预览”按钮可在主页面查看效果。",
                true);
        } catch (IllegalArgumentException ex) {
            JOptionPane.showMessageDialog(this, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            if (createButton != null) {
                createButton.setEnabled(false);
            }
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("生成割草路径失败:" + ex.getMessage(), false);
            setPathAvailability(false);
        } catch (Exception ex) {
            ex.printStackTrace();
            JOptionPane.showMessageDialog(this, "生成割草路径时发生异常: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            if (createButton != null) {
                createButton.setEnabled(false);
            dikuaiData.remove("plannedPath");
            showPathGenerationMessage("生成割草路径时发生异常:" + ex.getMessage(), false);
            setPathAvailability(false);
        }
    }
    private void previewMowingPath() {
        if (!hasGeneratedPath()) {
            showPathGenerationMessage("请先生成割草路径后再预览。", false);
            setPathAvailability(false);
            return;
        }
        persistStep3Inputs();
        String landNumber = getPendingLandNumber();
        String trimmedAreaName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
        String displayAreaName = isMeaningfulValue(trimmedAreaName) ? trimmedAreaName : landNumber;
        String plannedPath = dikuaiData.get("plannedPath");
        if (!isMeaningfulValue(plannedPath)) {
            showPathGenerationMessage("请先生成割草路径后再预览。", false);
            setPathAvailability(false);
            return;
        }
        String boundary = null;
        Dikuai pending = getOrCreatePendingDikuai();
        if (pending != null) {
            boundary = normalizeCoordinateValue(pending.getBoundaryCoordinates());
        }
        if (boundary == null) {
            boundary = normalizeCoordinateValue(dikuaiData.get("boundaryCoordinates"));
        }
        String obstacles = normalizeCoordinateValue(dikuaiData.get("obstacleCoordinates"));
        if (!isMeaningfulValue(obstacles)) {
            obstacles = resolveObstaclePayloadFromConfig(landNumber);
            if (isMeaningfulValue(obstacles)) {
                dikuaiData.put("obstacleCoordinates", obstacles);
            }
        }
        Shouye shouye = Shouye.getInstance();
        if (shouye == null) {
            JOptionPane.showMessageDialog(this, "无法打开主页面,请稍后重试", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        dikuaiData.put("areaName", trimmedAreaName);
        if (isMeaningfulValue(boundary)) {
            dikuaiData.put("boundaryCoordinates", boundary);
        }
        pendingLandNumber = landNumber;
        captureSessionSnapshot();
        resumeRequested = true;
        boolean started = shouye.startMowingPathPreview(
            landNumber,
            displayAreaName,
            boundary,
            obstacles,
            plannedPath,
            AddDikuai::resumeFromPreview
        );
        if (!started) {
            resumeRequested = false;
            JOptionPane.showMessageDialog(this, "无法启动预览,请稍后再试", "提示", JOptionPane.WARNING_MESSAGE);
            return;
        }
        closePreviewAndDispose();
    }
    private void persistStep3Inputs() {
        String trimmedName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
        dikuaiData.put("areaName", trimmedName);
        if (mowingPatternCombo != null) {
            Object selection = mowingPatternCombo.getSelectedItem();
            if (selection != null) {
                dikuaiData.put("mowingPattern", selection.toString());
            }
        }
        if (mowingWidthSpinner != null) {
            Object widthValue = mowingWidthSpinner.getValue();
            if (widthValue instanceof Number) {
                int widthInt = ((Number) widthValue).intValue();
                dikuaiData.put("mowingWidth", Integer.toString(widthInt));
            } else if (widthValue != null) {
                dikuaiData.put("mowingWidth", widthValue.toString());
            }
        }
    }
    private void captureSessionSnapshot() {
        if (activeSession == null) {
            activeSession = new DrawingSession();
        }
        String landNumber = getPendingLandNumber();
        activeSession.landNumber = landNumber;
        activeSession.areaName = areaNameField.getText() != null ? areaNameField.getText().trim() : "";
        activeSession.drawingCompleted = true;
        activeSession.data = new HashMap<>(dikuaiData);
    }
    private void closePreviewAndDispose() {
        setVisible(false);
        dispose();
    }
    
    private JButton createPrimaryButton(String text, int fontSize) {
@@ -1146,6 +1290,44 @@
        
        return button;
    }
    private void showPathGenerationMessage(String message, boolean success) {
        if (pathGenerationMessageArea == null || pathMessageWrapper == null) {
            return;
        }
        String display = message == null ? "" : message.trim();
        if (display.isEmpty()) {
            dikuaiData.remove(KEY_PATH_MESSAGE_TEXT);
            dikuaiData.remove(KEY_PATH_MESSAGE_SUCCESS);
        } else {
            dikuaiData.put(KEY_PATH_MESSAGE_TEXT, display);
            dikuaiData.put(KEY_PATH_MESSAGE_SUCCESS, success ? "true" : "false");
        }
        pathGenerationMessageArea.setText(display);
        Color borderColor = success ? PRIMARY_COLOR : ERROR_COLOR;
        Color textColor = success ? PRIMARY_DARK : ERROR_COLOR;
        Color backgroundColor = success ? PRIMARY_LIGHT : new Color(255, 235, 238);
        pathGenerationMessageArea.setForeground(textColor);
        pathMessageWrapper.setBackground(backgroundColor);
        pathMessageWrapper.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(borderColor, 1),
            BorderFactory.createEmptyBorder(12, 12, 12, 12)
        ));
        pathMessageWrapper.setVisible(!display.isEmpty());
        pathMessageWrapper.revalidate();
        pathMessageWrapper.repaint();
    }
    private void setPathAvailability(boolean available) {
        boolean effective = available && currentStep == 3;
        if (createButton != null) {
            createButton.setEnabled(effective);
        }
        if (previewButton != null) {
            boolean visible = previewButton.isVisible();
            previewButton.setEnabled(effective && visible);
        }
    }
    
    private JPanel createButtonPanel() {
        JPanel buttonPanel = new JPanel();
@@ -1167,11 +1349,20 @@
        nextButton = createPrimaryButton("下一步", 16);
        createButton = createPrimaryButton("保存", 16);
        createButton.setVisible(false);
    createButton.setEnabled(false);
        createButton.setEnabled(false);
        previewButton = createPrimaryButton("预览", 16);
        previewButton.setVisible(false);
        previewButton.setEnabled(false);
        previewButtonSpacer = Box.createHorizontalStrut(15);
        previewButtonSpacer.setVisible(false);
        buttonPanel.add(prevButton);
        buttonPanel.add(Box.createHorizontalGlue());
        buttonPanel.add(nextButton);
        buttonPanel.add(previewButtonSpacer);
        buttonPanel.add(previewButton);
        buttonPanel.add(Box.createHorizontalStrut(15));
        buttonPanel.add(createButton);
@@ -1223,14 +1414,40 @@
        return true;
    }
    private static String buildOriginalBoundaryString() {
        if (Coordinate.coordinates == null || Coordinate.coordinates.isEmpty()) {
    private static List<Coordinate> sanitizeCoordinateList(List<Coordinate> source) {
        if (source == null || source.isEmpty()) {
            return Collections.emptyList();
        }
        List<Coordinate> snapshot = new ArrayList<>();
        for (Coordinate coordinate : source) {
            if (coordinate != null) {
                snapshot.add(coordinate);
            }
        }
        if (snapshot.isEmpty()) {
            return Collections.emptyList();
        }
        DecimalFormat latLonFormat = new DecimalFormat("0.000000");
        LinkedHashMap<String, Coordinate> unique = new LinkedHashMap<>();
        for (Coordinate coord : snapshot) {
            double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection());
            double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection());
            String key = latLonFormat.format(lat) + "," + latLonFormat.format(lon);
            unique.putIfAbsent(key, coord);
        }
        return new ArrayList<>(unique.values());
    }
    private static String buildOriginalBoundaryString(List<Coordinate> coordinates) {
        if (coordinates == null || coordinates.isEmpty()) {
            return "-1";
        }
        StringBuilder sb = new StringBuilder();
        DecimalFormat latLonFormat = new DecimalFormat("0.000000");
        DecimalFormat elevationFormat = new DecimalFormat("0.00");
        for (Coordinate coord : Coordinate.coordinates) {
        for (Coordinate coord : coordinates) {
            double lat = convertToDecimalDegree(coord.getLatitude(), coord.getLatDirection());
            double lon = convertToDecimalDegree(coord.getLongitude(), coord.getLonDirection());
            double elevation = coord.getElevation();
@@ -1334,8 +1551,14 @@
    }
    private static BoundarySnapshotResult computeBoundarySnapshot() {
        int count = Coordinate.coordinates != null ? Coordinate.coordinates.size() : 0;
        if (count < 3) {
        List<Coordinate> uniqueCoordinates;
        synchronized (Coordinate.coordinates) {
            uniqueCoordinates = sanitizeCoordinateList(Coordinate.coordinates);
            Coordinate.coordinates.clear();
            Coordinate.coordinates.addAll(uniqueCoordinates);
        }
        if (uniqueCoordinates.size() < 3) {
            return BoundarySnapshotResult.failure("采集的边界点不足,无法生成地块边界", JOptionPane.WARNING_MESSAGE);
        }
@@ -1344,9 +1567,9 @@
            return BoundarySnapshotResult.failure("当前地块面积为0,无法继续", JOptionPane.WARNING_MESSAGE);
        }
        Device device = new Device();
        device.initFromProperties();
        String baseStationCoordinates = normalizeCoordinateValue(device.getBaseStationCoordinates());
    BaseStation baseStation = new BaseStation();
    baseStation.load();
    String baseStationCoordinates = normalizeCoordinateValue(baseStation.getInstallationCoordinates());
        if (!isMeaningfulValue(baseStationCoordinates)) {
            return BoundarySnapshotResult.failure("未获取到有效的基准站坐标,请先在基准站管理中设置", JOptionPane.WARNING_MESSAGE);
        }
@@ -1359,7 +1582,7 @@
            return BoundarySnapshotResult.failure("生成地块边界失败: " + ex.getMessage(), JOptionPane.ERROR_MESSAGE);
        }
        String originalBoundary = buildOriginalBoundaryString();
    String originalBoundary = buildOriginalBoundaryString(uniqueCoordinates);
        DecimalFormat areaFormat = new DecimalFormat("0.00");
        String areaString = areaFormat.format(area);
@@ -1407,6 +1630,10 @@
        
        // 创建地块按钮
        createButton.addActionListener(e -> createDikuai());
        if (previewButton != null) {
            previewButton.addActionListener(e -> previewMowingPath());
        }
        
        // 关闭对话框
        addWindowListener(new WindowAdapter() {
@@ -1435,11 +1662,24 @@
        if (step < 3) {
            nextButton.setVisible(true);
            createButton.setVisible(false);
            createButton.setEnabled(false);
            setPathAvailability(false);
            if (previewButton != null) {
                previewButton.setVisible(false);
                previewButton.setEnabled(false);
            }
            if (previewButtonSpacer != null) {
                previewButtonSpacer.setVisible(false);
            }
        } else {
            nextButton.setVisible(false);
            createButton.setVisible(true);
            createButton.setEnabled(hasGeneratedPath());
            if (previewButton != null) {
                previewButton.setVisible(true);
            }
            if (previewButtonSpacer != null) {
                previewButtonSpacer.setVisible(true);
            }
            setPathAvailability(hasGeneratedPath());
        }
        Container parent = prevButton.getParent();
@@ -1736,6 +1976,71 @@
            showStep(1);
            hideBoundaryPointSummary();
        }
        restoreGeneratedPathState(session);
    }
    private void restoreGeneratedPathState(DrawingSession session) {
        if (session == null || session.data == null) {
            showPathGenerationMessage("", true);
            return;
        }
        Map<String, String> data = session.data;
        if (mowingPatternCombo != null) {
            String pattern = data.get("mowingPattern");
            if (pattern != null) {
                ComboBoxModel<String> model = mowingPatternCombo.getModel();
                for (int i = 0; i < model.getSize(); i++) {
                    String candidate = model.getElementAt(i);
                    if (pattern.equals(candidate)) {
                        mowingPatternCombo.setSelectedIndex(i);
                        break;
                    }
                }
            }
        }
        if (mowingWidthSpinner != null) {
            String width = data.get("mowingWidth");
            if (isMeaningfulValue(width)) {
                try {
                    double parsed = Double.parseDouble(width.trim());
                    SpinnerNumberModel model = (SpinnerNumberModel) mowingWidthSpinner.getModel();
                    int min = ((Number) model.getMinimum()).intValue();
                    int max = ((Number) model.getMaximum()).intValue();
                    int rounded = (int) Math.round(parsed);
                    if (rounded < min) {
                        rounded = min;
                    } else if (rounded > max) {
                        rounded = max;
                    }
                    mowingWidthSpinner.setValue(rounded);
                } catch (NumberFormatException ignored) {
                    // 保持当前值
                }
            }
        }
        boolean hasPath = isMeaningfulValue(data.get("plannedPath"));
        if (!hasPath) {
            showPathGenerationMessage("", true);
            if (currentStep == 3) {
                setPathAvailability(false);
            }
            return;
        }
        String message = data.get(KEY_PATH_MESSAGE_TEXT);
        boolean success = !"false".equalsIgnoreCase(data.get(KEY_PATH_MESSAGE_SUCCESS));
        showStep(3);
        if (isMeaningfulValue(message)) {
            showPathGenerationMessage(message, success);
        } else {
            showPathGenerationMessage("已生成割草路径,可点击“预览”按钮查看效果。", true);
        }
        setPathAvailability(true);
    }
    public static void finishDrawingSession() {
@@ -1769,6 +2074,19 @@
        Component parent = shouye != null ? shouye : null;
        showAddDikuaiDialog(parent);
    }
    public static void resumeFromPreview() {
        Shouye shouye = Shouye.getInstance();
        if (shouye != null) {
            shouye.exitMowingPathPreview();
        }
        if (activeSession == null) {
            return;
        }
        resumeRequested = true;
        Component parent = shouye != null ? shouye : null;
        SwingUtilities.invokeLater(() -> showAddDikuaiDialog(parent));
    }
    
    private void createDikuai() {
        if (!validateCurrentStep()) {