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优化版
|
*/
|
import publicway.Gpstoxuzuobiao;
|
|
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<Obstacledge.Obstacle> 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<Obstacledge.Obstacle> 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<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;
|
List<String> 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<Obstacledge.DMCoordinate> 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<Obstacledge.XYCoordinate> 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<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 {
|
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) {
|
return Gpstoxuzuobiao.parseDMToDecimal(dmm, direction);
|
}
|
|
private double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
|
return Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon);
|
}
|
|
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<Obstacledge.Obstacle> 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());
|
}
|
}
|