package dikuai; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.plaf.basic.BasicScrollBarUI; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.RoundRectangle2D; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import zhangaiwu.Obstacledge; import zhuye.Shouye; import zhuye.Coordinate; import bianjie.bianjieguihua2; /** * 障碍物管理页面 - UI优化版 */ public class ObstacleManagementPage extends JDialog { private static final long serialVersionUID = 1L; // --- 尺寸常量 (保持不变) --- private static final int SCREEN_WIDTH = 400; private static final int SCREEN_HEIGHT = 800; // --- 配色方案 (更现代的莫兰迪/扁平风格) --- private static final Color PRIMARY_COLOR = new Color(46, 139, 87); // 海藻绿 (主色) private static final Color PRIMARY_HOVER = new Color(60, 179, 113); // 悬停绿 private static final Color DANGER_COLOR = new Color(220, 53, 69); // 警告红 private static final Color DANGER_HOVER = new Color(200, 35, 51); private static final Color TEXT_PRIMARY = new Color(33, 37, 41); // 主要文字 private static final Color TEXT_SECONDARY = new Color(108, 117, 125); // 次要文字 private static final Color BG_MAIN = new Color(248, 249, 250); // 整体背景 (灰白) private static final Color BG_CARD = Color.WHITE; // 卡片背景 private static final Color BG_INPUT = new Color(241, 243, 245); // 输入框背景 private static final Color BORDER_LIGHT = new Color(233, 236, 239); // 浅边框 private final Dikuai dikuai; private JPanel cardsPanel; private JScrollPane scrollPane; public ObstacleManagementPage(Window owner, Dikuai dikuai) { super(owner, "障碍物管理", Dialog.ModalityType.APPLICATION_MODAL); this.dikuai = dikuai; initializeWindow(); initializeUI(); // 确保布局完成后再显示数据 SwingUtilities.invokeLater(this::loadAndDisplayObstacles); } private void initializeWindow() { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setLayout(new BorderLayout()); getContentPane().setBackground(BG_MAIN); setSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); setResizable(false); setLocationRelativeTo(getOwner()); } private void initializeUI() { // 1. 顶部导航栏 add(createHeader(), BorderLayout.NORTH); // 2. 中间滚动区域 cardsPanel = new JPanel(); cardsPanel.setLayout(new BoxLayout(cardsPanel, BoxLayout.Y_AXIS)); cardsPanel.setBackground(BG_MAIN); cardsPanel.setBorder(new EmptyBorder(15, 15, 15, 15)); // 外边距 scrollPane = new JScrollPane(cardsPanel); scrollPane.setBorder(null); // 无边框 scrollPane.setBackground(BG_MAIN); scrollPane.getVerticalScrollBar().setUnitIncrement(20); // 自定义滚动条样式 customizeScrollBar(scrollPane); add(scrollPane, BorderLayout.CENTER); } /** * 创建顶部标题栏 */ private JPanel createHeader() { JPanel header = new JPanel(new BorderLayout()); header.setBackground(Color.WHITE); header.setPreferredSize(new Dimension(SCREEN_WIDTH, 60)); header.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, BORDER_LIGHT)); // 底部细线 // 左侧信息 JPanel infoPanel = new JPanel(new GridLayout(2, 1)); infoPanel.setBackground(Color.WHITE); infoPanel.setBorder(new EmptyBorder(8, 20, 8, 0)); String landName = getDisplayValue(dikuai.getLandName(), "未知地块"); String landNumber = getDisplayValue(dikuai.getLandNumber(), "--"); JLabel titleLabel = new JLabel(landName); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); titleLabel.setForeground(TEXT_PRIMARY); JLabel subTitleLabel = new JLabel("编号: " + landNumber); subTitleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); subTitleLabel.setForeground(TEXT_SECONDARY); infoPanel.add(titleLabel); infoPanel.add(subTitleLabel); header.add(infoPanel, BorderLayout.CENTER); // 右侧关闭按钮 JButton closeBtn = new JButton("✕"); closeBtn.setFont(new Font("Arial", Font.BOLD, 18)); closeBtn.setForeground(TEXT_SECONDARY); closeBtn.setBorder(new EmptyBorder(0, 15, 0, 20)); closeBtn.setContentAreaFilled(false); closeBtn.setFocusPainted(false); closeBtn.setCursor(new Cursor(Cursor.HAND_CURSOR)); closeBtn.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { closeBtn.setForeground(TEXT_PRIMARY); } public void mouseExited(MouseEvent e) { closeBtn.setForeground(TEXT_SECONDARY); } }); closeBtn.addActionListener(e -> dispose()); header.add(closeBtn, BorderLayout.EAST); return header; } /** * 自定义滚动条样式(扁平化、细条) */ private void customizeScrollBar(JScrollPane scrollPane) { scrollPane.getVerticalScrollBar().setUI(new BasicScrollBarUI() { @Override protected void configureScrollBarColors() { this.thumbColor = new Color(200, 200, 200); this.trackColor = BG_MAIN; } @Override protected JButton createDecreaseButton(int orientation) { return createZeroButton(); } @Override protected JButton createIncreaseButton(int orientation) { return createZeroButton(); } private JButton createZeroButton() { JButton jbutton = new JButton(); jbutton.setPreferredSize(new Dimension(0, 0)); jbutton.setMinimumSize(new Dimension(0, 0)); jbutton.setMaximumSize(new Dimension(0, 0)); return jbutton; } @Override protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(thumbColor); g2.fillRoundRect(thumbBounds.x + 2, thumbBounds.y, thumbBounds.width - 4, thumbBounds.height, 6, 6); g2.dispose(); } }); scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(8, 0)); } /** * 加载数据 */ private void loadAndDisplayObstacles() { cardsPanel.removeAll(); List obstacles = loadObstacles(); if (obstacles.isEmpty()) { showEmptyState(); } else { for (Obstacledge.Obstacle obstacle : obstacles) { cardsPanel.add(createObstacleCard(obstacle)); cardsPanel.add(Box.createVerticalStrut(15)); // 卡片间距 } } cardsPanel.revalidate(); cardsPanel.repaint(); } private void showEmptyState() { JPanel emptyPanel = new JPanel(); emptyPanel.setLayout(new BoxLayout(emptyPanel, BoxLayout.Y_AXIS)); emptyPanel.setBackground(BG_MAIN); emptyPanel.setAlignmentX(Component.CENTER_ALIGNMENT); JLabel iconLabel = new JLabel("🔲"); // 简单的占位符,如果有图片更好 iconLabel.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 48)); iconLabel.setAlignmentX(Component.CENTER_ALIGNMENT); JLabel textLabel = new JLabel("暂无障碍物数据"); textLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); textLabel.setForeground(TEXT_SECONDARY); textLabel.setAlignmentX(Component.CENTER_ALIGNMENT); emptyPanel.add(Box.createVerticalStrut(50)); emptyPanel.add(iconLabel); emptyPanel.add(Box.createVerticalStrut(10)); emptyPanel.add(textLabel); cardsPanel.add(emptyPanel); } /** * 创建单个障碍物卡片 */ private JPanel createObstacleCard(Obstacledge.Obstacle obstacle) { // 卡片容器 - 使用圆角矩形背景 JPanel card = new JPanel() { @Override protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(BG_CARD); // 绘制圆角矩形背景 g2.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12); // 绘制淡淡的边框 g2.setColor(BORDER_LIGHT); g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12); g2.dispose(); } }; card.setLayout(new BoxLayout(card, BoxLayout.Y_AXIS)); card.setOpaque(false); // 透明以显示 paintComponent 效果 card.setBorder(new EmptyBorder(15, 15, 15, 15)); // 卡片内部内边距 card.setMaximumSize(new Dimension(Integer.MAX_VALUE, 380)); // 限制高度,防止拉伸 // 1. 卡片标题行 (名称 + 形状 + 删除) JPanel titleRow = new JPanel(new BorderLayout()); titleRow.setOpaque(false); String name = (obstacle.getObstacleName() == null || obstacle.getObstacleName().isEmpty()) ? "未命名" : obstacle.getObstacleName(); JLabel nameLabel = new JLabel(name); nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 15)); nameLabel.setForeground(TEXT_PRIMARY); // 添加一个小图标装饰 nameLabel.setIcon(new Icon() { public void paintIcon(Component c, Graphics g, int x, int y) { g.setColor(PRIMARY_COLOR); g.fillOval(x, y + 4, 8, 8); } public int getIconWidth() { return 12; } public int getIconHeight() { return 16; } }); // 添加形状显示(在名称右边) JLabel shapeLabel = createShapeLabel(obstacle.getShape()); // 左侧面板:名称 + 形状 JPanel leftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); leftPanel.setOpaque(false); leftPanel.add(nameLabel); leftPanel.add(shapeLabel); JButton deleteBtn = createIconButton("🗑", DANGER_COLOR); // 使用Unicode垃圾桶 deleteBtn.addActionListener(e -> deleteObstacle(obstacle)); titleRow.add(leftPanel, BorderLayout.CENTER); titleRow.add(deleteBtn, BorderLayout.EAST); card.add(titleRow); card.add(Box.createVerticalStrut(12)); // 分割线 JSeparator sep = new JSeparator(); sep.setForeground(BORDER_LIGHT); sep.setMaximumSize(new Dimension(Integer.MAX_VALUE, 1)); card.add(sep); card.add(Box.createVerticalStrut(12)); // 2. 原始坐标区域 String originalCoords = extractOriginalCoordinates(obstacle); card.add(createDataField("原始经纬度数据", originalCoords, 2)); card.add(Box.createVerticalStrut(10)); // 3. 生成坐标区域 String genCoords = extractObstacleCoordinates(obstacle); 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)); // 4. 操作按钮行 JPanel actionPanel = new JPanel(); actionPanel.setLayout(new BoxLayout(actionPanel, BoxLayout.X_AXIS)); actionPanel.setOpaque(false); // 对于圆形障碍物,不显示"重新生成坐标"按钮,只显示预览按钮 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(previewBtn); card.add(actionPanel); return card; } /** * 辅助方法:创建带标题的数据展示块 */ private JPanel createDataField(String title, String content, int rows) { 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); } private JPanel createWrapperPanel(String title, JComponent content) { JPanel panel = new JPanel(new BorderLayout(0, 5)); panel.setOpaque(false); JLabel label = new JLabel(title); label.setFont(new Font("微软雅黑", Font.PLAIN, 12)); label.setForeground(TEXT_SECONDARY); JPanel contentBox = new JPanel(new BorderLayout()); contentBox.setBackground(BG_INPUT); contentBox.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(new Color(230, 230, 230), 1), BorderFactory.createEmptyBorder(5, 5, 5, 5) )); contentBox.add(content, BorderLayout.CENTER); panel.add(label, BorderLayout.NORTH); panel.add(contentBox, BorderLayout.CENTER); return panel; } private JTextArea createDataTextArea(String text, int rows) { 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)); return area; } /** * 创建现代风格按钮 (实心或轮廓) */ private JButton createStyledButton(String text, Color baseColor, boolean filled) { JButton btn = new JButton(text) { @Override protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); boolean isPressed = getModel().isPressed(); boolean isRollover = getModel().isRollover(); if (filled) { if (isPressed) g2.setColor(baseColor.darker()); else if (isRollover) g2.setColor(new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), 230)); // 微透明 else g2.setColor(baseColor); g2.fill(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 8, 8)); g2.setColor(Color.WHITE); } else { g2.setColor(BG_CARD); // 背景白 g2.fill(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 8, 8)); if (isPressed) g2.setColor(baseColor.darker()); else if (isRollover) g2.setColor(baseColor); else g2.setColor(new Color(200, 200, 200)); g2.setStroke(new BasicStroke(1.2f)); g2.draw(new RoundRectangle2D.Double(0, 0, getWidth()-1, getHeight()-1, 8, 8)); g2.setColor(isRollover ? baseColor : TEXT_SECONDARY); } FontMetrics fm = g2.getFontMetrics(); int x = (getWidth() - fm.stringWidth(getText())) / 2; int y = (getHeight() - fm.getHeight()) / 2 + fm.getAscent(); g2.drawString(getText(), x, y); g2.dispose(); } }; btn.setFont(new Font("微软雅黑", Font.BOLD, 12)); btn.setFocusPainted(false); btn.setContentAreaFilled(false); btn.setBorderPainted(false); btn.setCursor(new Cursor(Cursor.HAND_CURSOR)); btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36)); return btn; } /** * 创建形状标签(显示圆形或多边形图标) */ private JLabel createShapeLabel(Obstacledge.ObstacleShape shape) { JLabel shapeLabel = new JLabel(); shapeLabel.setPreferredSize(new Dimension(24, 24)); if (shape == null) { return shapeLabel; } // 根据形状创建不同的图标 Icon shapeIcon; if (shape == Obstacledge.ObstacleShape.CIRCLE) { // 圆形图标 shapeIcon = new Icon() { @Override public void paintIcon(Component c, Graphics g, int x, int y) { Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(PRIMARY_COLOR); g2.setStroke(new BasicStroke(2.0f)); g2.drawOval(x + 2, y + 2, getIconWidth() - 4, getIconHeight() - 4); g2.dispose(); } @Override public int getIconWidth() { return 20; } @Override public int getIconHeight() { return 20; } }; shapeLabel.setToolTipText("圆形"); } else if (shape == Obstacledge.ObstacleShape.POLYGON) { // 多边形图标(六边形) shapeIcon = new Icon() { @Override public void paintIcon(Component c, Graphics g, int x, int y) { Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(PRIMARY_COLOR); g2.setStroke(new BasicStroke(2.0f)); // 绘制六边形 int centerX = x + getIconWidth() / 2; int centerY = y + getIconHeight() / 2; int radius = 8; int[] xPoints = new int[6]; int[] yPoints = new int[6]; for (int i = 0; i < 6; i++) { double angle = Math.PI / 3.0 * i - Math.PI / 6.0; // 从顶部开始 xPoints[i] = centerX + (int) (radius * Math.cos(angle)); yPoints[i] = centerY + (int) (radius * Math.sin(angle)); } g2.drawPolygon(xPoints, yPoints, 6); g2.dispose(); } @Override public int getIconWidth() { return 20; } @Override public int getIconHeight() { return 20; } }; shapeLabel.setToolTipText("多边形"); } else { return shapeLabel; // 未知形状,返回空标签 } shapeLabel.setIcon(shapeIcon); return shapeLabel; } /** * 创建纯图标按钮(用于删除) */ private JButton createIconButton(String iconText, Color hoverColor) { JButton btn = new JButton(iconText); btn.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 16)); btn.setForeground(TEXT_SECONDARY); btn.setBorder(null); btn.setContentAreaFilled(false); btn.setFocusPainted(false); btn.setCursor(new Cursor(Cursor.HAND_CURSOR)); btn.setPreferredSize(new Dimension(30, 30)); btn.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { btn.setForeground(hoverColor); } public void mouseExited(MouseEvent e) { btn.setForeground(TEXT_SECONDARY); } }); return btn; } // ================================================================================= // 以下为原有业务逻辑代码,保持不变,仅调整部分空值判断以适配新UI // ================================================================================= private void previewObstacle(Obstacledge.Obstacle obstacle) { if (obstacle == null) return; String landNumber = dikuai.getLandNumber(); String landName = dikuai.getLandName(); String boundary = dikuai.getBoundaryCoordinates(); String obstacleCoords = extractObstacleCoordinates(obstacle); if (obstacleCoords == null || obstacleCoords.trim().isEmpty()) { JOptionPane.showMessageDialog(this, "该障碍物没有坐标数据", "提示", JOptionPane.INFORMATION_MESSAGE); return; } // 计算障碍物的中心点坐标 double[] centerCoords = calculateObstacleCenter(obstacle); List allObstacles = loadObstacles(); String allObstaclesCoords = buildAllObstaclesCoordinates(allObstacles); // 关闭障碍物管理页面 dispose(); SwingUtilities.invokeLater(() -> { Shouye shouye = Shouye.getInstance(); if (shouye != null) { // 传递回调以重新打开障碍物管理页面 shouye.startMowingPathPreview( landNumber, landName, boundary, allObstaclesCoords, null, () -> 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); } }); } /** * 计算障碍物的中心点坐标 * @param obstacle 障碍物 * @return 中心点坐标 [centerX, centerY],如果无法计算则返回null */ private double[] calculateObstacleCenter(Obstacledge.Obstacle obstacle) { if (obstacle == null) { return null; } List 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 obstacles) { if (obstacles == null || obstacles.isEmpty()) return null; List coordStrings = new ArrayList<>(); for (Obstacledge.Obstacle obstacle : obstacles) { String coords = extractObstacleCoordinates(obstacle); if (coords != null && !coords.trim().isEmpty()) { coordStrings.add(coords.trim()); } } return coordStrings.isEmpty() ? null : String.join(" ", coordStrings); } private String extractObstacleCoordinates(Obstacledge.Obstacle obstacle) { if (obstacle == null) return ""; String xy = obstacle.getXyCoordsString(); return isMeaningfulValue(xy) ? xy.trim() : ""; } private String extractOriginalCoordinates(Obstacledge.Obstacle obstacle) { if (obstacle == null) return ""; String original = obstacle.getOriginalCoordsString(); return isMeaningfulValue(original) ? original.trim() : ""; } private void generateObstacleCoordinates(Obstacledge.Obstacle obstacle, JTextArea coordArea) { if (obstacle == null || coordArea == null) return; String originalCoords = extractOriginalCoordinates(obstacle); if (!isMeaningfulValue(originalCoords)) { JOptionPane.showMessageDialog(this, "无原始坐标数据,无法生成", "提示", JOptionPane.INFORMATION_MESSAGE); return; } String baseStation = dikuai.getBaseStationCoordinates(); if (!isMeaningfulValue(baseStation)) { JOptionPane.showMessageDialog(this, "地块未设置基站坐标", "提示", JOptionPane.WARNING_MESSAGE); return; } try { List originalCoordsList = obstacle.getOriginalCoordinates(); if (originalCoordsList == null || originalCoordsList.isEmpty()) { JOptionPane.showMessageDialog(this, "原始坐标数据无效", "提示", JOptionPane.INFORMATION_MESSAGE); return; } String[] baseParts = baseStation.split(","); if (baseParts.length < 4) { JOptionPane.showMessageDialog(this, "基站坐标格式不正确", "错误", JOptionPane.ERROR_MESSAGE); return; } Obstacledge.ObstacleShape shape = obstacle.getShape(); List xyCoords; // 根据障碍物形状调用不同的算法 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 == null || xyCoords.isEmpty()) { JOptionPane.showMessageDialog(this, "坐标生成失败", "错误", JOptionPane.ERROR_MESSAGE); return; } obstacle.setXyCoordinates(xyCoords); saveObstacleUpdate(obstacle, coordArea); } catch (Exception ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, "错误: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } } /** * 使用 bianjieguihua2 算法生成多边形坐标 */ private List generatePolygonCoordinates( List originalCoordsList, String baseStation) { // 保存当前的 Coordinate.coordinates List savedCoordinates = new ArrayList<>(Coordinate.coordinates); try { // 将障碍物的原始坐标转换为 Coordinate 对象列表 List 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 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 generateCircleCoordinates( List 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 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 { File configFile = new File("Obstacledge.properties"); Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); if (configFile.exists()) manager.loadFromFile(configFile.getAbsolutePath()); Obstacledge.Plot plot = manager.getPlotById(landNumber.trim()); if (plot != null) { plot.removeObstacleByName(obstacle.getObstacleName()); plot.addObstacle(obstacle); manager.saveToFile(configFile.getAbsolutePath()); Dikuai.updateField(landNumber.trim(), "updateTime", getCurrentTime()); Dikuai.saveToProperties(); coordArea.setText(obstacle.getXyCoordsString()); JOptionPane.showMessageDialog(this, "坐标已生成并保存", "成功", JOptionPane.INFORMATION_MESSAGE); } } catch (Exception ex) { ex.printStackTrace(); } } 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 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}; } private void deleteObstacle(Obstacledge.Obstacle obstacle) { if (obstacle == null) return; String name = obstacle.getObstacleName(); int choice = JOptionPane.showConfirmDialog(this, "确定要删除障碍物 \"" + name + "\" 吗?", "删除确认", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (choice != JOptionPane.YES_OPTION) return; String landNumber = dikuai.getLandNumber(); try { File configFile = new File("Obstacledge.properties"); Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); if (configFile.exists()) manager.loadFromFile(configFile.getAbsolutePath()); Obstacledge.Plot plot = manager.getPlotById(landNumber.trim()); if (plot != null && plot.removeObstacleByName(name)) { manager.saveToFile(configFile.getAbsolutePath()); Dikuai.updateField(landNumber.trim(), "updateTime", getCurrentTime()); Dikuai.saveToProperties(); Dikuaiguanli.notifyExternalCreation(landNumber.trim()); JOptionPane.showMessageDialog(this, "删除成功"); loadAndDisplayObstacles(); } else { JOptionPane.showMessageDialog(this, "删除失败:未找到记录", "错误", JOptionPane.ERROR_MESSAGE); } } catch (Exception ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, "异常: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } } private String getCurrentTime() { return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()); } private List loadObstacles() { if (dikuai == null) return Collections.emptyList(); String landNumber = dikuai.getLandNumber(); if (landNumber == null || landNumber.trim().isEmpty()) return Collections.emptyList(); try { File configFile = new File("Obstacledge.properties"); if (!configFile.exists()) return Collections.emptyList(); Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); if (!manager.loadFromFile(configFile.getAbsolutePath())) return Collections.emptyList(); Obstacledge.Plot plot = manager.getPlotById(landNumber.trim()); return plot != null ? new ArrayList<>(plot.getObstacles()) : Collections.emptyList(); } catch (Exception ex) { ex.printStackTrace(); return Collections.emptyList(); } } private String getDisplayValue(String value, String defaultValue) { return (value != null && !value.equals("-1") && !value.trim().isEmpty()) ? value : defaultValue; } private boolean isMeaningfulValue(String value) { return value != null && !value.trim().isEmpty() && !"-1".equals(value.trim()); } }