张世豪
10 小时以前 13d032241e1a2938a8be4f64c9171e1240e9ea1e
src/dikuai/ObstacleManagementPage.java
@@ -14,10 +14,14 @@
import zhangaiwu.Obstacledge;
import zhuye.Shouye;
import zhuye.Coordinate;
import bianjie.bianjieguihua2;
/**
 * 障碍物管理页面 - UI优化版
 */
import publicway.Gpstoxuzuobiao;
public class ObstacleManagementPage extends JDialog {
    private static final long serialVersionUID = 1L;
    
@@ -297,7 +301,17 @@
        JTextArea xyArea = createDataTextArea(genCoords, 3); // 引用以便更新
        JScrollPane scrollXY = new JScrollPane(xyArea);
        scrollXY.setBorder(BorderFactory.createEmptyBorder()); // 外部由Panel提供边框
        // 设置滚动条策略:需要时显示垂直滚动条,不使用水平滚动条(因为启用了自动换行)
        scrollXY.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollXY.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        // 设置滚动条单位增量,使滚动更流畅
        scrollXY.getVerticalScrollBar().setUnitIncrement(16);
        // 设置滚动面板的首选大小,确保在内容超出时显示滚动条
        int lineHeight = xyArea.getFontMetrics(xyArea.getFont()).getHeight();
        int preferredHeight = 3 * lineHeight + 10; // 3行的高度
        scrollXY.setPreferredSize(new Dimension(Integer.MAX_VALUE, preferredHeight));
        scrollXY.setMaximumSize(new Dimension(Integer.MAX_VALUE, 200)); // 最大高度200像素
        JPanel xyWrapper = createWrapperPanel("生成坐标 (XY米)", scrollXY);
        card.add(xyWrapper);
        card.add(Box.createVerticalStrut(15));
@@ -307,15 +321,21 @@
        actionPanel.setLayout(new BoxLayout(actionPanel, BoxLayout.X_AXIS));
        actionPanel.setOpaque(false);
        
        JButton generateBtn = createStyledButton("重新生成坐标", PRIMARY_COLOR, true);
        generateBtn.addActionListener(e -> generateObstacleCoordinates(obstacle, xyArea));
        // 对于圆形障碍物,不显示"重新生成坐标"按钮,只显示预览按钮
        Obstacledge.ObstacleShape shape = obstacle.getShape();
        boolean isCircle = (shape == Obstacledge.ObstacleShape.CIRCLE);
        if (!isCircle) {
            // 只有非圆形障碍物才显示"重新生成坐标"按钮
            JButton generateBtn = createStyledButton("重新生成坐标", PRIMARY_COLOR, true);
            generateBtn.addActionListener(e -> generateObstacleCoordinates(obstacle, xyArea));
            actionPanel.add(generateBtn);
            actionPanel.add(Box.createHorizontalStrut(10));
        }
        
        JButton previewBtn = createStyledButton("预览", TEXT_SECONDARY, false);
        previewBtn.setPreferredSize(new Dimension(70, 36)); // 稍微窄一点
        previewBtn.addActionListener(e -> previewObstacle(obstacle));
        actionPanel.add(generateBtn);
        actionPanel.add(Box.createHorizontalStrut(10));
        actionPanel.add(previewBtn);
        
        card.add(actionPanel);
@@ -330,6 +350,16 @@
        JTextArea area = createDataTextArea(content, rows);
        JScrollPane scroll = new JScrollPane(area);
        scroll.setBorder(BorderFactory.createEmptyBorder());
        // 设置滚动条策略:需要时显示垂直滚动条,不使用水平滚动条(因为启用了自动换行)
        scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        // 设置滚动条单位增量,使滚动更流畅
        scroll.getVerticalScrollBar().setUnitIncrement(16);
        // 设置滚动面板的首选大小,确保在内容超出时显示滚动条
        int lineHeight = area.getFontMetrics(area.getFont()).getHeight();
        int preferredHeight = rows * lineHeight + 10; // 根据行数计算首选高度
        scroll.setPreferredSize(new Dimension(Integer.MAX_VALUE, preferredHeight));
        scroll.setMaximumSize(new Dimension(Integer.MAX_VALUE, 200)); // 最大高度200像素
        return createWrapperPanel(title, scroll);
    }
@@ -358,12 +388,13 @@
        JTextArea area = new JTextArea(text);
        area.setRows(rows);
        area.setEditable(false);
        // 启用自动换行
        area.setLineWrap(true);
        area.setWrapStyleWord(true);
        area.setBackground(BG_INPUT);
        area.setForeground(new Color(50, 50, 50));
        // 使用等宽字体显示数据,看起来更专业
        area.setFont(new Font("Monospaced", Font.PLAIN, 12));
        area.setFont(new Font("Monospaced", Font.PLAIN, 12));
        return area;
    }
@@ -537,28 +568,111 @@
            return;
        }
        
        // 计算障碍物的中心点坐标
        double[] centerCoords = calculateObstacleCenter(obstacle);
        List<Obstacledge.Obstacle> allObstacles = loadObstacles();
        String allObstaclesCoords = buildAllObstaclesCoordinates(allObstacles);
        
        setVisible(false);
        // 关闭障碍物管理页面
        dispose();
        
        SwingUtilities.invokeLater(() -> {
            Shouye shouye = Shouye.getInstance();
            if (shouye != null) {
                // 传递回调以重新打开障碍物管理页面
                shouye.startMowingPathPreview(
                    landNumber,
                    landName,
                    boundary,
                    allObstaclesCoords,
                    null,
                    () -> SwingUtilities.invokeLater(() -> setVisible(true))
                    () -> SwingUtilities.invokeLater(() -> {
                        // 重新打开障碍物管理页面
                        Window owner = SwingUtilities.getWindowAncestor(shouye);
                        ObstacleManagementPage newPage = new ObstacleManagementPage(owner, dikuai);
                        newPage.setVisible(true);
                    })
                );
                // 将地图视图中心设置为障碍物的中心位置
                if (centerCoords != null && shouye.getMapRenderer() != null) {
                    double currentScale = shouye.getMapRenderer().getScale();
                    // 将视图中心设置为障碍物中心(使用负值,因为translate是相对于原点的偏移)
                    shouye.getMapRenderer().setViewTransform(currentScale, -centerCoords[0], -centerCoords[1]);
                }
            } else {
                JOptionPane.showMessageDialog(null, "无法打开主页面进行预览", "提示", JOptionPane.WARNING_MESSAGE);
                setVisible(true);
            }
        });
    }
    /**
     * 计算障碍物的中心点坐标
     * @param obstacle 障碍物
     * @return 中心点坐标 [centerX, centerY],如果无法计算则返回null
     */
    private double[] calculateObstacleCenter(Obstacledge.Obstacle obstacle) {
        if (obstacle == null) {
            return null;
        }
        List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
        if (xyCoords == null || xyCoords.isEmpty()) {
            return null;
        }
        Obstacledge.ObstacleShape shape = obstacle.getShape();
        double centerX, centerY;
        if (shape == Obstacledge.ObstacleShape.CIRCLE) {
            // 圆形障碍物:第一个坐标点就是圆心
            if (xyCoords.size() < 1) {
                return null;
            }
            Obstacledge.XYCoordinate centerCoord = xyCoords.get(0);
            centerX = centerCoord.getX();
            centerY = centerCoord.getY();
        } else if (shape == Obstacledge.ObstacleShape.POLYGON) {
            // 多边形障碍物:计算重心
            centerX = 0.0;
            centerY = 0.0;
            double area = 0.0;
            int n = xyCoords.size();
            for (int i = 0; i < n; i++) {
                Obstacledge.XYCoordinate current = xyCoords.get(i);
                Obstacledge.XYCoordinate next = xyCoords.get((i + 1) % n);
                double x0 = current.getX();
                double y0 = current.getY();
                double x1 = next.getX();
                double y1 = next.getY();
                double cross = x0 * y1 - x1 * y0;
                area += cross;
                centerX += (x0 + x1) * cross;
                centerY += (y0 + y1) * cross;
            }
            double areaFactor = area * 0.5;
            if (Math.abs(areaFactor) < 1e-9) {
                // 如果面积为0或接近0,使用简单平均
                for (Obstacledge.XYCoordinate coord : xyCoords) {
                    centerX += coord.getX();
                    centerY += coord.getY();
                }
                int size = Math.max(1, xyCoords.size());
                centerX /= size;
                centerY /= size;
            } else {
                centerX = centerX / (6.0 * areaFactor);
                centerY = centerY / (6.0 * areaFactor);
            }
        } else {
            return null;
        }
        return new double[]{centerX, centerY};
    }
    private String buildAllObstaclesCoordinates(List<Obstacledge.Obstacle> obstacles) {
        if (obstacles == null || obstacles.isEmpty()) return null;
@@ -612,23 +726,23 @@
                return;
            }
            
            double baseLat = parseDMToDecimal(baseParts[0].trim(), baseParts[1].trim());
            double baseLon = parseDMToDecimal(baseParts[2].trim(), baseParts[3].trim());
            Obstacledge.ObstacleShape shape = obstacle.getShape();
            List<Obstacledge.XYCoordinate> xyCoords;
            
            List<Obstacledge.XYCoordinate> xyCoords = new ArrayList<>();
            for (int i = 0; i < originalCoordsList.size(); i += 2) {
                if (i + 1 >= originalCoordsList.size()) break;
                double lat = originalCoordsList.get(i).toDecimalDegree();
                double lon = originalCoordsList.get(i + 1).toDecimalDegree();
                if (Double.isFinite(lat) && Double.isFinite(lon)) {
                    double[] localXY = convertLatLonToLocal(lat, lon, baseLat, baseLon);
                    xyCoords.add(new Obstacledge.XYCoordinate(localXY[0], localXY[1]));
                }
            // 根据障碍物形状调用不同的算法
            if (shape == Obstacledge.ObstacleShape.POLYGON) {
                // 多边形:使用 bianjieguihua2 算法
                xyCoords = generatePolygonCoordinates(originalCoordsList, baseStation);
            } else if (shape == Obstacledge.ObstacleShape.CIRCLE) {
                // 圆形:使用简单的坐标转换(保持原有逻辑)
                xyCoords = generateCircleCoordinates(originalCoordsList, baseStation);
            } else {
                JOptionPane.showMessageDialog(this, "未知的障碍物形状", "错误", JOptionPane.ERROR_MESSAGE);
                return;
            }
            
            if (xyCoords.isEmpty()) {
                JOptionPane.showMessageDialog(this, "坐标转换失败", "错误", JOptionPane.ERROR_MESSAGE);
            if (xyCoords == null || xyCoords.isEmpty()) {
                JOptionPane.showMessageDialog(this, "坐标生成失败", "错误", JOptionPane.ERROR_MESSAGE);
                return;
            }
            
@@ -641,6 +755,132 @@
        }
    }
    
    /**
     * 使用 bianjieguihua2 算法生成多边形坐标
     */
    private List<Obstacledge.XYCoordinate> generatePolygonCoordinates(
            List<Obstacledge.DMCoordinate> originalCoordsList, String baseStation) {
        // 保存当前的 Coordinate.coordinates
        List<Coordinate> savedCoordinates = new ArrayList<>(Coordinate.coordinates);
        try {
            // 将障碍物的原始坐标转换为 Coordinate 对象列表
            List<Coordinate> coordinateList = new ArrayList<>();
            for (int i = 0; i < originalCoordsList.size(); i += 2) {
                if (i + 1 >= originalCoordsList.size()) break;
                Obstacledge.DMCoordinate latCoord = originalCoordsList.get(i);
                Obstacledge.DMCoordinate lonCoord = originalCoordsList.get(i + 1);
                // 转换为 Coordinate 对象
                // DMCoordinate 的 degreeMinute 是度分格式(如 2324.200273 表示 23度24.200273分)
                // 需要格式化为字符串,保持度分格式
                double latDM = latCoord.getDegreeMinute();
                double lonDM = lonCoord.getDegreeMinute();
                // 格式化度分格式:确保整数部分至少2位(度),小数部分是分
                String latStr = formatDegreeMinute(latDM);
                String lonStr = formatDegreeMinute(lonDM);
                char latDir = latCoord.getDirection();
                char lonDir = lonCoord.getDirection();
                Coordinate coord = new Coordinate(
                    latStr,
                    String.valueOf(latDir),
                    lonStr,
                    String.valueOf(lonDir),
                    0.0 // 高程数据,障碍物可能没有,设为0
                );
                coordinateList.add(coord);
            }
            if (coordinateList.isEmpty()) {
                return null;
            }
            // 设置到全局坐标列表
            Coordinate.coordinates.clear();
            Coordinate.coordinates.addAll(coordinateList);
            // 调用 bianjieguihua2 算法生成优化后的多边形坐标
            String optimizedCoordsStr = bianjieguihua2.processCoordinateListAuto(baseStation);
            if (optimizedCoordsStr == null || optimizedCoordsStr.trim().isEmpty()) {
                return null;
            }
            // 解析返回的坐标字符串,格式:"X0,Y0;X1,Y1;X2,Y2;..."
            List<Obstacledge.XYCoordinate> xyCoords = new ArrayList<>();
            String[] pointStrings = optimizedCoordsStr.split(";");
            for (String pointStr : pointStrings) {
                pointStr = pointStr.trim();
                if (pointStr.isEmpty()) continue;
                String[] parts = pointStr.split(",");
                if (parts.length >= 2) {
                    try {
                        double x = Double.parseDouble(parts[0].trim());
                        double y = Double.parseDouble(parts[1].trim());
                        if (Double.isFinite(x) && Double.isFinite(y)) {
                            xyCoords.add(new Obstacledge.XYCoordinate(x, y));
                        }
                    } catch (NumberFormatException e) {
                        // 跳过无效的坐标点
                        continue;
                    }
                }
            }
            return xyCoords;
        } finally {
            // 恢复原来的坐标列表
            Coordinate.coordinates.clear();
            Coordinate.coordinates.addAll(savedCoordinates);
        }
    }
    /**
     * 格式化度分格式坐标
     * @param degreeMinute 度分值,如 2324.200273 表示 23度24.200273分
     * @return 格式化的字符串
     */
    private String formatDegreeMinute(double degreeMinute) {
        // 度分格式:整数部分是度,小数部分是分
        // 例如 2324.200273 -> "2324.200273"
        return String.format("%.6f", degreeMinute);
    }
    /**
     * 生成圆形坐标(保持原有简单转换逻辑)
     */
    private List<Obstacledge.XYCoordinate> generateCircleCoordinates(
            List<Obstacledge.DMCoordinate> originalCoordsList, String baseStation) {
        String[] baseParts = baseStation.split(",");
        if (baseParts.length < 4) {
            return null;
        }
        double baseLat = parseDMToDecimal(baseParts[0].trim(), baseParts[1].trim());
        double baseLon = parseDMToDecimal(baseParts[2].trim(), baseParts[3].trim());
        List<Obstacledge.XYCoordinate> xyCoords = new ArrayList<>();
        for (int i = 0; i < originalCoordsList.size(); i += 2) {
            if (i + 1 >= originalCoordsList.size()) break;
            double lat = originalCoordsList.get(i).toDecimalDegree();
            double lon = originalCoordsList.get(i + 1).toDecimalDegree();
            if (Double.isFinite(lat) && Double.isFinite(lon)) {
                double[] localXY = convertLatLonToLocal(lat, lon, baseLat, baseLon);
                xyCoords.add(new Obstacledge.XYCoordinate(localXY[0], localXY[1]));
            }
        }
        return xyCoords;
    }
    private void saveObstacleUpdate(Obstacledge.Obstacle obstacle, JTextArea coordArea) {
        String landNumber = dikuai.getLandNumber();
        try {
@@ -666,29 +906,11 @@
    }
    private double parseDMToDecimal(String dmm, String direction) {
        if (dmm == null || dmm.trim().isEmpty()) return Double.NaN;
        try {
            String trimmed = dmm.trim();
            int dotIndex = trimmed.indexOf('.');
            if (dotIndex < 2) return Double.NaN;
            int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
            double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
            double decimal = degrees + minutes / 60.0;
            if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) decimal = -decimal;
            return decimal;
        } catch (NumberFormatException ex) {
            return Double.NaN;
        }
        return Gpstoxuzuobiao.parseDMToDecimal(dmm, direction);
    }
    
    private double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
        double deltaLat = lat - baseLat;
        double deltaLon = lon - baseLon;
        double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
        double METERS_PER_DEGREE_LAT = 111320.0;
        double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
        double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
        return new double[]{eastMeters, northMeters};
        return Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon);
    }
    
    private void deleteObstacle(Obstacledge.Obstacle obstacle) {
@@ -757,3 +979,10 @@
        return value != null && !value.trim().isEmpty() && !"-1".equals(value.trim());
    }
}