| | |
| | | /** |
| | | * 障碍物管理页面 - UI优化版 |
| | | */ |
| | | import publicway.Gpstoxuzuobiao; |
| | | |
| | | public class ObstacleManagementPage extends JDialog { |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | |
| | | 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)); |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | // 计算障碍物的中心点坐标 |
| | | double[] centerCoords = calculateObstacleCenter(obstacle); |
| | | |
| | | List<Obstacledge.Obstacle> allObstacles = loadObstacles(); |
| | | String allObstaclesCoords = buildAllObstaclesCoordinates(allObstacles); |
| | | |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | return value != null && !value.trim().isEmpty() && !"-1".equals(value.trim()); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |