| | |
| | | import java.awt.event.*; |
| | | import java.awt.datatransfer.*; |
| | | import java.awt.datatransfer.StringSelection; |
| | | import java.io.File; |
| | | import java.math.BigDecimal; |
| | | import ui.UIConfig; |
| | | import ui.UIUtils; |
| | |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | |
| | | import zhangaiwu.AddDikuai; |
| | | import zhangaiwu.Obstacledge; |
| | | import zhuye.MapRenderer; |
| | | import zhuye.Shouye; |
| | | import zhuye.Coordinate; |
| | | |
| | | /** |
| | | * 地块管理面板 - 卡片式布局设计 |
| | |
| | | private ImageIcon boundaryVisibleIcon; |
| | | private ImageIcon boundaryHiddenIcon; |
| | | private static final int BOUNDARY_TOGGLE_ICON_SIZE = 48; |
| | | private Map<String, ObstacleSummary> obstacleSummaryCache = Collections.emptyMap(); |
| | | |
| | | public Dikuaiguanli(String landNumber) { |
| | | latestInstance = this; |
| | |
| | | cardsPanel.removeAll(); |
| | | |
| | | Map<String, Dikuai> allDikuai = Dikuai.getAllDikuai(); |
| | | |
| | | |
| | | if (allDikuai.isEmpty()) { |
| | | obstacleSummaryCache = Collections.emptyMap(); |
| | | // 显示空状态 |
| | | JPanel emptyPanel = createEmptyStatePanel(); |
| | | cardsPanel.add(emptyPanel); |
| | | setCurrentWorkLand(null, null); |
| | | } else { |
| | | obstacleSummaryCache = loadObstacleSummaries(); |
| | | if (allDikuai.size() == 1) { |
| | | Dikuai onlyDikuai = allDikuai.values().iterator().next(); |
| | | setCurrentWorklandIfNeeded(onlyDikuai); |
| | |
| | | contentPanel.add(boundaryPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | // 障碍物坐标(带新增和查看按钮) |
| | | JPanel obstaclePanel = createCardInfoItemWithButton("障碍物坐标:", |
| | | getTruncatedValue(dikuai.getObstacleCoordinates(), 12, "未设置"), |
| | | ObstacleSummary obstacleSummary = getObstacleSummaryFromCache(dikuai.getLandNumber()); |
| | | JPanel obstaclePanel = createCardInfoItemWithButton("障碍物:", |
| | | obstacleSummary.buildDisplayValue(), |
| | | "新增", |
| | | e -> addNewObstacle(dikuai)); |
| | | setInfoItemTooltip(obstaclePanel, dikuai.getObstacleCoordinates()); |
| | | setInfoItemTooltip(obstaclePanel, obstacleSummary.buildTooltip()); |
| | | contentPanel.add(obstaclePanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | |
| | | if (renderer != null) { |
| | | String boundary = (dikuai != null) ? dikuai.getBoundaryCoordinates() : null; |
| | | String plannedPath = (dikuai != null) ? dikuai.getPlannedPath() : null; |
| | | String obstacles = (dikuai != null) ? dikuai.getObstacleCoordinates() : null; |
| | | List<Obstacledge.Obstacle> configuredObstacles = (landNumber != null) ? loadObstaclesFromConfig(landNumber) : null; |
| | | renderer.setCurrentBoundary(boundary, landNumber, landNumber == null ? null : landName); |
| | | renderer.setCurrentPlannedPath(plannedPath); |
| | | if (configuredObstacles != null) { |
| | | renderer.setCurrentObstacles(configuredObstacles, landNumber); |
| | | } else { |
| | | renderer.setCurrentObstacles(obstacles, landNumber); |
| | | } |
| | | boolean showBoundaryPoints = landNumber != null && boundaryPointVisibility.getOrDefault(landNumber, false); |
| | | renderer.setBoundaryPointsVisible(showBoundaryPoints); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public static String getCurrentWorkLandNumber() { |
| | | return currentWorkLandNumber; |
| | | } |
| | | |
| | | private ImageIcon loadIcon(String path, int width, int height) { |
| | | try { |
| | | ImageIcon rawIcon = new ImageIcon(path); |
| | |
| | | if (ownerWindow instanceof JDialog) { |
| | | ownerWindow.dispose(); |
| | | } |
| | | Coordinate.coordinates.clear(); |
| | | latestInstance = null; |
| | | SwingUtilities.invokeLater(() -> AddDikuai.showAddDikuaiDialog(parentComponent)); |
| | | }); |
| | |
| | | } |
| | | |
| | | private void addNewObstacle(Dikuai dikuai) { |
| | | String obstacleCoords = JOptionPane.showInputDialog(this, |
| | | "请输入障碍物坐标(格式: 纬度1,经度1;纬度2,经度2;...):"); |
| | | |
| | | if (obstacleCoords != null && !obstacleCoords.trim().isEmpty()) { |
| | | // 更新障碍物坐标 |
| | | Dikuai.updateField(dikuai.getLandNumber(), "obstacleCoordinates", obstacleCoords.trim()); |
| | | Dikuai.updateField(dikuai.getLandNumber(), "updateTime", getCurrentTime()); |
| | | |
| | | // 保存到文件 |
| | | Dikuai.saveToProperties(); |
| | | |
| | | // 刷新显示 |
| | | loadDikuaiData(); |
| | | |
| | | JOptionPane.showMessageDialog(this, "障碍物坐标已添加!", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | if (dikuai == null) { |
| | | JOptionPane.showMessageDialog(this, "未找到当前地块,无法新增障碍物", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | Window windowAncestor = SwingUtilities.getWindowAncestor(this); |
| | | if (windowAncestor instanceof JDialog) { |
| | | windowAncestor.dispose(); |
| | | } |
| | | Component parent = windowAncestor != null ? windowAncestor.getOwner() instanceof Component ? (Component) windowAncestor.getOwner() : windowAncestor : null; |
| | | List<String> existingObstacleNames = loadObstacleNamesForLand(dikuai.getLandNumber()); |
| | | addzhangaiwu.showDialog(parent, dikuai, existingObstacleNames); |
| | | loadDikuaiData(); |
| | | } |
| | | |
| | | private Map<String, ObstacleSummary> loadObstacleSummaries() { |
| | | Map<String, ObstacleSummary> summaries = new HashMap<>(); |
| | | try { |
| | | File configFile = new File("Obstacledge.properties"); |
| | | if (!configFile.exists()) { |
| | | return summaries; |
| | | } |
| | | Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); |
| | | if (!manager.loadFromFile(configFile.getAbsolutePath())) { |
| | | return summaries; |
| | | } |
| | | for (Obstacledge.Plot plot : manager.getPlots()) { |
| | | if (plot == null) { |
| | | continue; |
| | | } |
| | | String plotId = plot.getPlotId(); |
| | | if (plotId == null || plotId.trim().isEmpty()) { |
| | | continue; |
| | | } |
| | | List<String> names = new ArrayList<>(); |
| | | for (Obstacledge.Obstacle obstacle : plot.getObstacles()) { |
| | | if (obstacle == null) { |
| | | continue; |
| | | } |
| | | String name = obstacle.getObstacleName(); |
| | | if (name == null) { |
| | | continue; |
| | | } |
| | | String trimmed = name.trim(); |
| | | if (!trimmed.isEmpty()) { |
| | | names.add(trimmed); |
| | | } |
| | | } |
| | | summaries.put(plotId.trim(), ObstacleSummary.of(names)); |
| | | } |
| | | } catch (Exception ex) { |
| | | System.err.println("读取障碍物配置失败: " + ex.getMessage()); |
| | | } |
| | | return summaries; |
| | | } |
| | | |
| | | private static List<Obstacledge.Obstacle> loadObstaclesFromConfig(String landNumber) { |
| | | if (landNumber == null || landNumber.trim().isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | try { |
| | | File configFile = new File("Obstacledge.properties"); |
| | | if (!configFile.exists()) { |
| | | return null; |
| | | } |
| | | Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager(); |
| | | if (!manager.loadFromFile(configFile.getAbsolutePath())) { |
| | | return null; |
| | | } |
| | | Obstacledge.Plot plot = manager.getPlotById(landNumber.trim()); |
| | | if (plot == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<Obstacledge.Obstacle> obstacles = plot.getObstacles(); |
| | | if (obstacles == null || obstacles.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return new ArrayList<>(obstacles); |
| | | } catch (Exception ex) { |
| | | System.err.println("读取障碍物配置失败: " + ex.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private ObstacleSummary getObstacleSummaryFromCache(String landNumber) { |
| | | if (landNumber == null || landNumber.trim().isEmpty()) { |
| | | return ObstacleSummary.empty(); |
| | | } |
| | | if (obstacleSummaryCache == null || obstacleSummaryCache.isEmpty()) { |
| | | return ObstacleSummary.empty(); |
| | | } |
| | | ObstacleSummary summary = obstacleSummaryCache.get(landNumber.trim()); |
| | | return summary != null ? summary : ObstacleSummary.empty(); |
| | | } |
| | | |
| | | private List<String> loadObstacleNamesForLand(String landNumber) { |
| | | List<String> names = new ArrayList<>(); |
| | | if (landNumber == null || landNumber.trim().isEmpty()) { |
| | | return names; |
| | | } |
| | | ObstacleSummary cached = getObstacleSummaryFromCache(landNumber); |
| | | if (!cached.isEmpty()) { |
| | | names.addAll(cached.copyNames()); |
| | | return names; |
| | | } |
| | | Map<String, ObstacleSummary> latest = loadObstacleSummaries(); |
| | | if (!latest.isEmpty()) { |
| | | obstacleSummaryCache = latest; |
| | | } |
| | | ObstacleSummary refreshed = getObstacleSummaryFromCache(landNumber); |
| | | if (!refreshed.isEmpty()) { |
| | | names.addAll(refreshed.copyNames()); |
| | | } |
| | | return names; |
| | | } |
| | | |
| | | private void editMowingWidth(Dikuai dikuai) { |
| | |
| | | } |
| | | boundaryPointVisibility.put(landNumber, visible); |
| | | } |
| | | |
| | | private static final class ObstacleSummary { |
| | | private static final ObstacleSummary EMPTY = new ObstacleSummary(Collections.emptyList()); |
| | | private final List<String> names; |
| | | |
| | | private ObstacleSummary(List<String> names) { |
| | | this.names = names; |
| | | } |
| | | |
| | | static ObstacleSummary of(List<String> originalNames) { |
| | | if (originalNames == null || originalNames.isEmpty()) { |
| | | return empty(); |
| | | } |
| | | List<String> cleaned = new ArrayList<>(); |
| | | for (String name : originalNames) { |
| | | if (name == null) { |
| | | continue; |
| | | } |
| | | String trimmed = name.trim(); |
| | | if (trimmed.isEmpty()) { |
| | | continue; |
| | | } |
| | | boolean duplicated = false; |
| | | for (String existing : cleaned) { |
| | | if (existing.equalsIgnoreCase(trimmed)) { |
| | | duplicated = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!duplicated) { |
| | | cleaned.add(trimmed); |
| | | } |
| | | } |
| | | if (cleaned.isEmpty()) { |
| | | return empty(); |
| | | } |
| | | cleaned.sort(String::compareToIgnoreCase); |
| | | return new ObstacleSummary(Collections.unmodifiableList(cleaned)); |
| | | } |
| | | |
| | | static ObstacleSummary empty() { |
| | | return EMPTY; |
| | | } |
| | | |
| | | boolean isEmpty() { |
| | | return names.isEmpty(); |
| | | } |
| | | |
| | | int count() { |
| | | return names.size(); |
| | | } |
| | | |
| | | String buildDisplayValue() { |
| | | return count() > 0 ? String.format("障碍物%d个", count()) : "暂无障碍物"; |
| | | } |
| | | |
| | | String buildTooltip() { |
| | | return count() > 0 ? String.join(",", names) : "暂无障碍物"; |
| | | } |
| | | |
| | | List<String> copyNames() { |
| | | return new ArrayList<>(names); |
| | | } |
| | | } |
| | | } |