| | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import gecaoji.Device; |
| | | import gecaoji.Gecaoji; |
| | | import dikuai.Dikuaiguanli; |
| | | import dikuai.Dikuai; |
| | | import zhangaiwu.Obstacledraw; |
| | | import zhangaiwu.Obstacledge; |
| | | |
| | | /** |
| | | * 地图渲染器 - 负责坐标系绘制、视图变换等功能 |
| | |
| | | private Rectangle2D.Double boundaryBounds; |
| | | private List<Point2D.Double> currentPlannedPath; |
| | | private Rectangle2D.Double plannedPathBounds; |
| | | private List<Obstacledge.Obstacle> currentObstacles; |
| | | private Rectangle2D.Double obstacleBounds; |
| | | private String selectedObstacleName; |
| | | private String boundaryName; |
| | | private boolean boundaryPointsVisible; |
| | | private String currentBoundaryLandNumber; |
| | |
| | | |
| | | boolean hasBoundary = currentBoundary != null && currentBoundary.size() >= 2; |
| | | boolean hasPlannedPath = currentPlannedPath != null && currentPlannedPath.size() >= 2; |
| | | boolean hasObstacles = currentObstacles != null && !currentObstacles.isEmpty(); |
| | | |
| | | if (hasBoundary) { |
| | | drawCurrentBoundary(g2d); |
| | | } |
| | | |
| | | if (hasObstacles) { |
| | | Obstacledraw.drawObstacles(g2d, currentObstacles, scale, selectedObstacleName); |
| | | } |
| | | |
| | | if (hasPlannedPath) { |
| | | drawCurrentPlannedPath(g2d); |
| | | } |
| | |
| | | currentBoundaryLandNumber = null; |
| | | } |
| | | |
| | | public void setCurrentObstacles(String obstaclesData, String landNumber) { |
| | | if (landNumber == null) { |
| | | clearObstacleData(); |
| | | if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) { |
| | | resetView(); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | List<Obstacledge.Obstacle> parsed = parseObstacles(obstaclesData, landNumber); |
| | | if (parsed.isEmpty()) { |
| | | clearObstacleData(); |
| | | if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) { |
| | | resetView(); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | currentObstacles = parsed; |
| | | obstacleBounds = convertObstacleBounds(Obstacledraw.getAllObstaclesBounds(parsed)); |
| | | selectedObstacleName = null; |
| | | |
| | | if (!hasRenderableBoundary() && !hasRenderablePlannedPath() && obstacleBounds != null) { |
| | | Rectangle2D.Double bounds = obstacleBounds; |
| | | SwingUtilities.invokeLater(() -> { |
| | | fitBoundsToView(bounds); |
| | | visualizationPanel.repaint(); |
| | | }); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | } |
| | | |
| | | private void clearObstacleData() { |
| | | currentObstacles = null; |
| | | obstacleBounds = null; |
| | | selectedObstacleName = null; |
| | | } |
| | | |
| | | private List<Obstacledge.Obstacle> parseObstacles(String obstaclesData, String landNumber) { |
| | | List<Obstacledge.Obstacle> obstacles = new ArrayList<>(); |
| | | if (obstaclesData == null) { |
| | | return obstacles; |
| | | } |
| | | |
| | | String normalized = stripInlineComment(obstaclesData.trim()); |
| | | if (normalized.isEmpty() || "-1".equals(normalized)) { |
| | | return obstacles; |
| | | } |
| | | |
| | | List<String> entries = splitObstacleEntries(normalized); |
| | | int defaultIndex = 1; |
| | | |
| | | for (String entry : entries) { |
| | | String trimmedEntry = stripInlineComment(entry); |
| | | if (trimmedEntry.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | String nameToken = null; |
| | | String shapeToken = null; |
| | | String coordsSection = trimmedEntry; |
| | | |
| | | if (trimmedEntry.contains("::")) { |
| | | String[] parts = trimmedEntry.split("::", 3); |
| | | if (parts.length == 3) { |
| | | nameToken = parts[0].trim(); |
| | | shapeToken = parts[1].trim(); |
| | | coordsSection = parts[2].trim(); |
| | | } |
| | | } else if (trimmedEntry.contains("@")) { |
| | | String[] parts = trimmedEntry.split("@", 3); |
| | | if (parts.length == 3) { |
| | | nameToken = parts[0].trim(); |
| | | shapeToken = parts[1].trim(); |
| | | coordsSection = parts[2].trim(); |
| | | } else if (parts.length == 2) { |
| | | shapeToken = parts[0].trim(); |
| | | coordsSection = parts[1].trim(); |
| | | } |
| | | } else if (trimmedEntry.contains(":")) { |
| | | String[] parts = trimmedEntry.split(":", 3); |
| | | if (parts.length == 3) { |
| | | nameToken = parts[0].trim(); |
| | | shapeToken = parts[1].trim(); |
| | | coordsSection = parts[2].trim(); |
| | | } else if (parts.length == 2) { |
| | | if (looksLikeShapeToken(parts[0])) { |
| | | shapeToken = parts[0].trim(); |
| | | coordsSection = parts[1].trim(); |
| | | } else { |
| | | nameToken = parts[0].trim(); |
| | | coordsSection = parts[1].trim(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | List<Obstacledge.XYCoordinate> xyCoordinates = parseObstacleCoordinates(coordsSection); |
| | | if (xyCoordinates.size() < 2) { |
| | | continue; |
| | | } |
| | | |
| | | Obstacledge.ObstacleShape shape = resolveObstacleShape(shapeToken, xyCoordinates.size()); |
| | | if (shape == null) { |
| | | continue; |
| | | } |
| | | |
| | | String obstacleName = (nameToken != null && !nameToken.isEmpty()) |
| | | ? nameToken |
| | | : "障碍物" + defaultIndex++; |
| | | |
| | | Obstacledge.Obstacle obstacle = new Obstacledge.Obstacle(landNumber, obstacleName, shape); |
| | | obstacle.setXyCoordinates(new ArrayList<>(xyCoordinates)); |
| | | populateDummyOriginalCoordinates(obstacle, xyCoordinates.size()); |
| | | |
| | | if (obstacle.isValid()) { |
| | | obstacles.add(obstacle); |
| | | } |
| | | } |
| | | |
| | | return obstacles; |
| | | } |
| | | |
| | | private boolean looksLikeShapeToken(String token) { |
| | | if (token == null) { |
| | | return false; |
| | | } |
| | | String normalized = token.trim().toLowerCase(Locale.ROOT); |
| | | return "circle".equals(normalized) |
| | | || "polygon".equals(normalized) |
| | | || "圆形".equals(normalized) |
| | | || "多边形".equals(normalized) |
| | | || "0".equals(normalized) |
| | | || "1".equals(normalized); |
| | | } |
| | | |
| | | private List<Obstacledge.XYCoordinate> parseObstacleCoordinates(String coordsSection) { |
| | | List<Obstacledge.XYCoordinate> coords = new ArrayList<>(); |
| | | if (coordsSection == null) { |
| | | return coords; |
| | | } |
| | | |
| | | String sanitized = stripInlineComment(coordsSection.trim()); |
| | | if (sanitized.isEmpty() || "-1".equals(sanitized)) { |
| | | return coords; |
| | | } |
| | | |
| | | String[] pairs = sanitized.split(";"); |
| | | for (String pair : pairs) { |
| | | if (pair == null) { |
| | | continue; |
| | | } |
| | | String trimmed = stripInlineComment(pair.trim()); |
| | | if (trimmed.isEmpty()) { |
| | | continue; |
| | | } |
| | | String[] parts = trimmed.split(","); |
| | | if (parts.length < 2) { |
| | | continue; |
| | | } |
| | | try { |
| | | double x = Double.parseDouble(parts[0].trim()); |
| | | double y = Double.parseDouble(parts[1].trim()); |
| | | coords.add(new Obstacledge.XYCoordinate(x, y)); |
| | | } catch (NumberFormatException ignored) { |
| | | // Skip malformed coordinate pair |
| | | } |
| | | } |
| | | |
| | | return coords; |
| | | } |
| | | |
| | | private Obstacledge.ObstacleShape resolveObstacleShape(String shapeToken, int coordinateCount) { |
| | | if (shapeToken != null && !shapeToken.trim().isEmpty()) { |
| | | String normalized = shapeToken.trim().toLowerCase(Locale.ROOT); |
| | | if ("circle".equals(normalized) || "圆形".equals(normalized) || "0".equals(normalized)) { |
| | | return Obstacledge.ObstacleShape.CIRCLE; |
| | | } |
| | | if ("polygon".equals(normalized) || "多边形".equals(normalized) || "1".equals(normalized)) { |
| | | return Obstacledge.ObstacleShape.POLYGON; |
| | | } |
| | | } |
| | | |
| | | if (coordinateCount == 2) { |
| | | return Obstacledge.ObstacleShape.CIRCLE; |
| | | } |
| | | if (coordinateCount >= 3) { |
| | | return Obstacledge.ObstacleShape.POLYGON; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void populateDummyOriginalCoordinates(Obstacledge.Obstacle obstacle, int xyCount) { |
| | | List<Obstacledge.DMCoordinate> dmCoordinates = new ArrayList<>(); |
| | | int points = Math.max(1, xyCount); |
| | | for (int i = 0; i < points; i++) { |
| | | dmCoordinates.add(new Obstacledge.DMCoordinate(0.0, 'N')); |
| | | dmCoordinates.add(new Obstacledge.DMCoordinate(0.0, 'E')); |
| | | } |
| | | obstacle.setOriginalCoordinates(dmCoordinates); |
| | | } |
| | | |
| | | private List<String> splitObstacleEntries(String data) { |
| | | List<String> entries = new ArrayList<>(); |
| | | if (data.indexOf('|') >= 0) { |
| | | String[] parts = data.split("\\|"); |
| | | for (String part : parts) { |
| | | if (part != null && !part.trim().isEmpty()) { |
| | | entries.add(part.trim()); |
| | | } |
| | | } |
| | | } else if (data.contains("\n")) { |
| | | String[] lines = data.split("\r?\n"); |
| | | for (String line : lines) { |
| | | if (line != null && !line.trim().isEmpty()) { |
| | | entries.add(line.trim()); |
| | | } |
| | | } |
| | | } else { |
| | | entries.add(data); |
| | | } |
| | | return entries; |
| | | } |
| | | |
| | | private String stripInlineComment(String text) { |
| | | if (text == null) { |
| | | return ""; |
| | | } |
| | | int hashIndex = text.indexOf('#'); |
| | | if (hashIndex >= 0) { |
| | | return text.substring(0, hashIndex).trim(); |
| | | } |
| | | return text.trim(); |
| | | } |
| | | |
| | | private Rectangle2D.Double convertObstacleBounds(double[] bounds) { |
| | | if (bounds == null || bounds.length < 4) { |
| | | return null; |
| | | } |
| | | double minX = bounds[0]; |
| | | double minY = bounds[1]; |
| | | double maxX = bounds[2]; |
| | | double maxY = bounds[3]; |
| | | return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); |
| | | } |
| | | |
| | | private boolean hasRenderableBoundary() { |
| | | return currentBoundary != null && currentBoundary.size() >= 2; |
| | | } |
| | | |
| | | private boolean hasRenderablePlannedPath() { |
| | | return currentPlannedPath != null && currentPlannedPath.size() >= 2; |
| | | } |
| | | |
| | | private void adjustViewAfterBoundaryReset() { |
| | | if (plannedPathBounds != null) { |
| | | Rectangle2D.Double bounds = plannedPathBounds; |
| | |
| | | fitBoundsToView(bounds); |
| | | visualizationPanel.repaint(); |
| | | }); |
| | | } else { |
| | | resetView(); |
| | | return; |
| | | } |
| | | |
| | | if (obstacleBounds != null) { |
| | | Rectangle2D.Double bounds = obstacleBounds; |
| | | SwingUtilities.invokeLater(() -> { |
| | | fitBoundsToView(bounds); |
| | | visualizationPanel.repaint(); |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | resetView(); |
| | | } |
| | | |
| | | public void setCurrentPlannedPath(String plannedPath) { |
| | | if (plannedPath == null) { |
| | | currentPlannedPath = null; |
| | | plannedPathBounds = null; |
| | | if (currentBoundary == null || currentBoundary.size() < 2) { |
| | | if (!hasRenderableBoundary()) { |
| | | resetView(); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | |
| | | if (parsed.size() < 2) { |
| | | currentPlannedPath = null; |
| | | plannedPathBounds = null; |
| | | if (currentBoundary == null || currentBoundary.size() < 2) { |
| | | if (!hasRenderableBoundary()) { |
| | | resetView(); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | |
| | | |
| | | Rectangle2D.Double bounds = plannedPathBounds; |
| | | SwingUtilities.invokeLater(() -> { |
| | | if (currentBoundary == null || currentBoundary.size() < 2) { |
| | | if (!hasRenderableBoundary()) { |
| | | fitBoundsToView(bounds); |
| | | } |
| | | visualizationPanel.repaint(); |