| | |
| | | import javax.swing.*; |
| | | import java.awt.*; |
| | | import java.awt.event.*; |
| | | import java.awt.FontMetrics; |
| | | import java.awt.geom.AffineTransform; |
| | | import java.awt.geom.Ellipse2D; |
| | | import java.awt.geom.Point2D; |
| | | import java.awt.geom.Rectangle2D; |
| | | import java.math.BigDecimal; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Set; |
| | | import gecaoji.Device; |
| | | import gecaoji.Gecaoji; |
| | | import dikuai.Dikuaiguanli; |
| | | import dikuai.Dikuai; |
| | | import zhangaiwu.Obstacledraw; |
| | | import zhangaiwu.Obstacledge; |
| | | import zhangaiwu.yulanzhangaiwu; |
| | | |
| | | /** |
| | | * 地图渲染器 - 负责坐标系绘制、视图变换等功能 |
| | |
| | | private static final Color GRASS_BORDER_COLOR = new Color(60, 179, 113); |
| | | private static final Color BOUNDARY_POINT_COLOR = new Color(128, 0, 128); |
| | | private static final Color BOUNDARY_LABEL_COLOR = Color.BLACK; |
| | | private static final Color CIRCLE_SAMPLE_COLOR = new Color(220, 20, 60, 230); |
| | | private static final double CIRCLE_SAMPLE_SIZE = 0.54d; |
| | | private static final double BOUNDARY_POINT_MERGE_THRESHOLD = 0.05; |
| | | private static final SimpleDateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| | | |
| | |
| | | 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 currentObstacleLandNumber; |
| | | private String boundaryName; |
| | | private boolean boundaryPointsVisible; |
| | | private String currentBoundaryLandNumber; |
| | |
| | | private JLabel realtimeSpeedValueLabel; |
| | | private JLabel headingValueLabel; |
| | | private JLabel updateTimeValueLabel; |
| | | private CircleCaptureOverlay circleCaptureOverlay; |
| | | private final List<double[]> circleSampleMarkers = new ArrayList<>(); |
| | | |
| | | public MapRenderer(JPanel visualizationPanel) { |
| | | this.visualizationPanel = visualizationPanel; |
| | |
| | | |
| | | 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); |
| | | } |
| | | |
| | | yulanzhangaiwu.renderPreview(g2d, scale); |
| | | |
| | | if (!circleSampleMarkers.isEmpty()) { |
| | | drawCircleSampleMarkers(g2d, circleSampleMarkers, scale); |
| | | } |
| | | |
| | | if (circleCaptureOverlay != null) { |
| | | drawCircleCaptureOverlay(g2d, circleCaptureOverlay, scale); |
| | | } |
| | | |
| | | if (hasPlannedPath) { |
| | | drawCurrentPlannedPath(g2d); |
| | | } |
| | |
| | | lujingdraw.drawPlannedPath(g2d, currentPlannedPath, scale); |
| | | } |
| | | |
| | | private void drawCircleSampleMarkers(Graphics2D g2d, List<double[]> markers, double scale) { |
| | | if (markers == null || markers.isEmpty()) { |
| | | return; |
| | | } |
| | | Shape markerShape; |
| | | double half = CIRCLE_SAMPLE_SIZE / 2.0; |
| | | g2d.setColor(CIRCLE_SAMPLE_COLOR); |
| | | g2d.setStroke(new BasicStroke((float) (1.2f / scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | | Font originalFont = g2d.getFont(); |
| | | float baseSize = (float) Math.max(12f / scale, 9f); |
| | | float reducedSize = Math.max(baseSize / 3f, 3f); |
| | | Font labelFont = originalFont.deriveFont(reducedSize); |
| | | g2d.setFont(labelFont); |
| | | FontMetrics metrics = g2d.getFontMetrics(); |
| | | for (double[] pt : markers) { |
| | | if (pt == null || pt.length < 2 || !Double.isFinite(pt[0]) || !Double.isFinite(pt[1])) { |
| | | continue; |
| | | } |
| | | double x = pt[0]; |
| | | double y = pt[1]; |
| | | markerShape = new Ellipse2D.Double(x - half, y - half, CIRCLE_SAMPLE_SIZE, CIRCLE_SAMPLE_SIZE); |
| | | g2d.fill(markerShape); |
| | | String label = String.format(Locale.US, "%.2f,%.2f", x, y); |
| | | int textWidth = metrics.stringWidth(label); |
| | | float textX = (float) (x - textWidth / 2.0); |
| | | float textY = (float) (y - half - 0.2d) - metrics.getDescent(); |
| | | g2d.setColor(new Color(33, 37, 41, 220)); |
| | | g2d.drawString(label, textX, textY); |
| | | g2d.setColor(CIRCLE_SAMPLE_COLOR); |
| | | } |
| | | g2d.setFont(originalFont); |
| | | } |
| | | |
| | | private void drawCircleCaptureOverlay(Graphics2D g2d, CircleCaptureOverlay overlay, double scale) { |
| | | double diameter = overlay.radius * 2.0; |
| | | Ellipse2D outline = new Ellipse2D.Double( |
| | | overlay.centerX - overlay.radius, |
| | | overlay.centerY - overlay.radius, |
| | | diameter, |
| | | diameter); |
| | | |
| | | Color fillColor = new Color(255, 152, 0, 80); |
| | | Color borderColor = new Color(255, 87, 34, 230); |
| | | Color centerColor = new Color(46, 139, 87, 230); |
| | | |
| | | g2d.setColor(fillColor); |
| | | g2d.fill(outline); |
| | | |
| | | g2d.setColor(borderColor); |
| | | g2d.setStroke(new BasicStroke((float) (1.8f / scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | | g2d.draw(outline); |
| | | |
| | | double markerSize = 0.18d; |
| | | double centerMarkerSize = 0.22d; |
| | | |
| | | Ellipse2D centerMarker = new Ellipse2D.Double( |
| | | overlay.centerX - centerMarkerSize / 2.0, |
| | | overlay.centerY - centerMarkerSize / 2.0, |
| | | centerMarkerSize, |
| | | centerMarkerSize); |
| | | g2d.setColor(centerColor); |
| | | g2d.fill(centerMarker); |
| | | } |
| | | |
| | | public void showCircleCaptureOverlay(double centerX, double centerY, double radius, List<double[]> samplePoints) { |
| | | List<double[]> copies = new ArrayList<>(); |
| | | if (samplePoints != null) { |
| | | for (double[] pt : samplePoints) { |
| | | if (pt == null || pt.length < 2) { |
| | | continue; |
| | | } |
| | | copies.add(new double[]{pt[0], pt[1]}); |
| | | } |
| | | } |
| | | circleCaptureOverlay = new CircleCaptureOverlay(centerX, centerY, radius, copies); |
| | | updateCircleSampleMarkers(samplePoints); |
| | | if (visualizationPanel != null) { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | } |
| | | |
| | | public void clearCircleCaptureOverlay() { |
| | | circleCaptureOverlay = null; |
| | | if (visualizationPanel != null) { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | } |
| | | |
| | | public void updateCircleSampleMarkers(List<double[]> samplePoints) { |
| | | circleSampleMarkers.clear(); |
| | | if (samplePoints != null) { |
| | | for (double[] pt : samplePoints) { |
| | | if (pt == null || pt.length < 2) { |
| | | continue; |
| | | } |
| | | double x = pt[0]; |
| | | double y = pt[1]; |
| | | if (!Double.isFinite(x) || !Double.isFinite(y)) { |
| | | continue; |
| | | } |
| | | circleSampleMarkers.add(new double[]{x, y}); |
| | | } |
| | | } |
| | | if (visualizationPanel != null) { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | } |
| | | |
| | | public void clearCircleSampleMarkers() { |
| | | if (!circleSampleMarkers.isEmpty()) { |
| | | circleSampleMarkers.clear(); |
| | | if (visualizationPanel != null) { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static final class CircleCaptureOverlay { |
| | | final double centerX; |
| | | final double centerY; |
| | | final double radius; |
| | | final List<double[]> samplePoints; |
| | | |
| | | CircleCaptureOverlay(double centerX, double centerY, double radius, List<double[]> samplePoints) { |
| | | this.centerX = centerX; |
| | | this.centerY = centerY; |
| | | this.radius = radius; |
| | | this.samplePoints = samplePoints; |
| | | } |
| | | } |
| | | |
| | | private void showMowerInfo() { |
| | | Device device = Device.getGecaoji(); |
| | | if (device == null) { |
| | |
| | | currentBoundaryLandNumber = null; |
| | | } |
| | | |
| | | public void setCurrentObstacles(String obstaclesData, String landNumber) { |
| | | List<Obstacledge.Obstacle> parsed = parseObstacles(obstaclesData, landNumber); |
| | | applyObstaclesToRenderer(parsed, landNumber); |
| | | } |
| | | |
| | | public void setCurrentObstacles(List<Obstacledge.Obstacle> obstacles, String landNumber) { |
| | | List<Obstacledge.Obstacle> cloned = cloneObstacles(obstacles, landNumber); |
| | | applyObstaclesToRenderer(cloned, landNumber); |
| | | } |
| | | |
| | | private void applyObstaclesToRenderer(List<Obstacledge.Obstacle> obstacles, String landNumber) { |
| | | List<Obstacledge.Obstacle> safeList = obstacles != null ? obstacles : Collections.emptyList(); |
| | | String normalizedLand = (landNumber != null) ? landNumber.trim() : null; |
| | | |
| | | if (normalizedLand != null && !normalizedLand.isEmpty()) { |
| | | safeList = filterObstaclesForLand(safeList, normalizedLand); |
| | | } |
| | | |
| | | if (normalizedLand == null || normalizedLand.isEmpty()) { |
| | | clearObstacleData(); |
| | | if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) { |
| | | resetView(); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (safeList.isEmpty()) { |
| | | clearObstacleData(); |
| | | if (!hasRenderableBoundary() && !hasRenderablePlannedPath()) { |
| | | resetView(); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | currentObstacles = Collections.unmodifiableList(new ArrayList<>(safeList)); |
| | | currentObstacleLandNumber = normalizedLand; |
| | | obstacleBounds = convertObstacleBounds(Obstacledraw.getAllObstaclesBounds(currentObstacles)); |
| | | selectedObstacleName = null; |
| | | |
| | | if (!hasRenderableBoundary() && !hasRenderablePlannedPath() && obstacleBounds != null) { |
| | | Rectangle2D.Double bounds = obstacleBounds; |
| | | SwingUtilities.invokeLater(() -> { |
| | | fitBoundsToView(bounds); |
| | | visualizationPanel.repaint(); |
| | | }); |
| | | } else { |
| | | visualizationPanel.repaint(); |
| | | } |
| | | } |
| | | |
| | | private List<Obstacledge.Obstacle> cloneObstacles(List<Obstacledge.Obstacle> obstacles, String landNumber) { |
| | | List<Obstacledge.Obstacle> result = new ArrayList<>(); |
| | | if (obstacles == null || obstacles.isEmpty()) { |
| | | return result; |
| | | } |
| | | |
| | | String normalizedLandNumber = landNumber != null ? landNumber.trim() : null; |
| | | Set<String> usedNames = new LinkedHashSet<>(); |
| | | int fallbackIndex = 1; |
| | | |
| | | for (Obstacledge.Obstacle source : obstacles) { |
| | | if (source == null) { |
| | | continue; |
| | | } |
| | | |
| | | if (normalizedLandNumber != null && !normalizedLandNumber.isEmpty()) { |
| | | String sourcePlotId = source.getPlotId(); |
| | | if (sourcePlotId != null && !sourcePlotId.trim().isEmpty() |
| | | && !normalizedLandNumber.equalsIgnoreCase(sourcePlotId.trim())) { |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | Obstacledge.ObstacleShape shape = source.getShape(); |
| | | List<Obstacledge.XYCoordinate> xySource = source.getXyCoordinates(); |
| | | if (shape == null || xySource == null) { |
| | | continue; |
| | | } |
| | | |
| | | List<Obstacledge.XYCoordinate> xyCopy = copyXYCoordinates(xySource); |
| | | if (shape == Obstacledge.ObstacleShape.CIRCLE && xyCopy.size() < 2) { |
| | | continue; |
| | | } |
| | | if (shape == Obstacledge.ObstacleShape.POLYGON && xyCopy.size() < 3) { |
| | | continue; |
| | | } |
| | | |
| | | String desiredName = source.getObstacleName(); |
| | | if (desiredName == null || desiredName.trim().isEmpty()) { |
| | | desiredName = "障碍物" + fallbackIndex++; |
| | | } |
| | | String uniqueName = ensureUniqueName(usedNames, desiredName.trim()); |
| | | |
| | | Obstacledge.Obstacle copy = new Obstacledge.Obstacle( |
| | | normalizedLandNumber != null ? normalizedLandNumber : source.getPlotId(), |
| | | uniqueName, |
| | | shape |
| | | ); |
| | | |
| | | copy.setXyCoordinates(xyCopy); |
| | | |
| | | List<Obstacledge.DMCoordinate> dmCopy = copyDMCoordinates(source.getOriginalCoordinates()); |
| | | if (dmCopy.isEmpty()) { |
| | | populateDummyOriginalCoordinates(copy, xyCopy.size()); |
| | | } else { |
| | | copy.setOriginalCoordinates(dmCopy); |
| | | } |
| | | |
| | | result.add(copy); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | private List<Obstacledge.Obstacle> filterObstaclesForLand(List<Obstacledge.Obstacle> obstacles, String landNumber) { |
| | | if (obstacles == null || obstacles.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | if (landNumber == null || landNumber.trim().isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | String normalized = landNumber.trim(); |
| | | List<Obstacledge.Obstacle> filtered = new ArrayList<>(); |
| | | for (Obstacledge.Obstacle obstacle : obstacles) { |
| | | if (obstacle == null) { |
| | | continue; |
| | | } |
| | | String plotId = obstacle.getPlotId(); |
| | | if (plotId == null || plotId.trim().isEmpty()) { |
| | | filtered.add(obstacle); |
| | | continue; |
| | | } |
| | | if (normalized.equalsIgnoreCase(plotId.trim())) { |
| | | filtered.add(obstacle); |
| | | } |
| | | } |
| | | return filtered; |
| | | } |
| | | |
| | | private String ensureUniqueName(Set<String> usedNames, String preferredName) { |
| | | String base = (preferredName == null || preferredName.trim().isEmpty()) ? "障碍物" : preferredName.trim(); |
| | | String normalized = base.toLowerCase(Locale.ROOT); |
| | | if (usedNames.add(normalized)) { |
| | | return base; |
| | | } |
| | | int suffix = 2; |
| | | while (true) { |
| | | String attempt = base + suffix; |
| | | String attemptKey = attempt.toLowerCase(Locale.ROOT); |
| | | if (usedNames.add(attemptKey)) { |
| | | return attempt; |
| | | } |
| | | suffix++; |
| | | } |
| | | } |
| | | |
| | | private List<Obstacledge.XYCoordinate> copyXYCoordinates(List<Obstacledge.XYCoordinate> source) { |
| | | List<Obstacledge.XYCoordinate> copy = new ArrayList<>(); |
| | | if (source == null) { |
| | | return copy; |
| | | } |
| | | for (Obstacledge.XYCoordinate coord : source) { |
| | | if (coord == null) { |
| | | continue; |
| | | } |
| | | double x = coord.getX(); |
| | | double y = coord.getY(); |
| | | if (!Double.isFinite(x) || !Double.isFinite(y)) { |
| | | continue; |
| | | } |
| | | copy.add(new Obstacledge.XYCoordinate(x, y)); |
| | | } |
| | | return copy; |
| | | } |
| | | |
| | | private List<Obstacledge.DMCoordinate> copyDMCoordinates(List<Obstacledge.DMCoordinate> source) { |
| | | List<Obstacledge.DMCoordinate> copy = new ArrayList<>(); |
| | | if (source == null) { |
| | | return copy; |
| | | } |
| | | for (Obstacledge.DMCoordinate coord : source) { |
| | | if (coord == null) { |
| | | continue; |
| | | } |
| | | copy.add(new Obstacledge.DMCoordinate(coord.getDegreeMinute(), coord.getDirection())); |
| | | } |
| | | return copy; |
| | | } |
| | | |
| | | private void clearObstacleData() { |
| | | currentObstacles = null; |
| | | obstacleBounds = null; |
| | | selectedObstacleName = null; |
| | | currentObstacleLandNumber = 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(); |