package zhangaiwu; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import javax.swing.JDialog; import javax.swing.SwingUtilities; import zhuye.Coordinate; import zhuye.Shouye; /** * 在地图上实时预览正在绘制的障碍物。 */ import publicway.Gpstoxuzuobiao; public class yulanzhangaiwu extends JDialog { private static final Object LOCK = new Object(); private static final double METERS_PER_DEGREE_LAT = 111320.0d; private static final Color PREVIEW_LINE_COLOR = new Color(0, 123, 255, 200); private static final Color PREVIEW_FILL_COLOR = new Color(0, 123, 255, 70); private static final Color PREVIEW_POINT_COLOR = new Color(255, 77, 0, 210); private static final double PREVIEW_POINT_SIZE = 0.18d; private static boolean active; private static String shapeKey; private static String baseStation; private static List localPoints = Collections.emptyList(); private static CircleState circleState; private yulanzhangaiwu() { } public static void startPreview(String shape, String baseStationCoordinates) { synchronized (LOCK) { active = true; shapeKey = shape != null ? shape.toLowerCase(Locale.ROOT) : null; baseStation = baseStationCoordinates; localPoints = Collections.emptyList(); circleState = null; } requestRepaint(); } public static void stopPreview() { synchronized (LOCK) { active = false; shapeKey = null; baseStation = null; localPoints = Collections.emptyList(); circleState = null; } requestRepaint(); } public static void renderPreview(Graphics2D g2d, double scale) { PreviewSnapshot snapshot = snapshot(); if (!snapshot.active) { return; } // 去掉圆形障碍物的实时预览功能 if ("circle".equals(snapshot.shape)) { return; } Color originalColor = g2d.getColor(); Stroke originalStroke = g2d.getStroke(); try { Stroke dashedStroke = new BasicStroke( (float) (1.6f / scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f, new float[]{(float) (6f / scale), (float) (4f / scale)}, 0f ); Stroke solidStroke = new BasicStroke((float) (1.4f / scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); drawPolygonPreview(g2d, snapshot, dashedStroke, solidStroke, scale); } finally { g2d.setColor(originalColor); g2d.setStroke(originalStroke); } } private static void drawCirclePreview(Graphics2D g2d, PreviewSnapshot snapshot, Stroke dashedStroke, double scale) { CircleState circle = snapshot.circle; if (circle.radius <= 0) { drawSamplePoints(g2d, snapshot.points, scale); return; } double diameter = circle.radius * 2.0; double x = circle.centerX - circle.radius; double y = circle.centerY - circle.radius; Shape outline = new Ellipse2D.Double(x, y, diameter, diameter); g2d.setColor(PREVIEW_FILL_COLOR); g2d.fill(outline); g2d.setColor(PREVIEW_LINE_COLOR); g2d.setStroke(dashedStroke); g2d.draw(outline); drawPoint(g2d, circle.centerX, circle.centerY, scale, PREVIEW_POINT_COLOR); if (circle.referencePoint != null) { drawPoint(g2d, circle.referencePoint[0], circle.referencePoint[1], scale, PREVIEW_LINE_COLOR.darker()); } drawSamplePoints(g2d, snapshot.points, scale); } private static void drawPolygonPreview(Graphics2D g2d, PreviewSnapshot snapshot, Stroke dashedStroke, Stroke solidStroke, double scale) { List pts = snapshot.points; if (pts.isEmpty()) { return; } Path2D.Double path = new Path2D.Double(); double[] first = pts.get(0); path.moveTo(first[0], first[1]); for (int i = 1; i < pts.size(); i++) { double[] pt = pts.get(i); path.lineTo(pt[0], pt[1]); } if (pts.size() >= 3) { path.closePath(); g2d.setColor(PREVIEW_FILL_COLOR); g2d.fill(path); } g2d.setColor(PREVIEW_LINE_COLOR); g2d.setStroke(pts.size() >= 3 ? dashedStroke : solidStroke); g2d.draw(path); drawSamplePoints(g2d, pts, scale); } private static void drawSamplePoints(Graphics2D g2d, List points, double scale) { if (points.isEmpty()) { return; } for (double[] pt : points) { drawPoint(g2d, pt[0], pt[1], scale, PREVIEW_POINT_COLOR); } } private static void drawPoint(Graphics2D g2d, double x, double y, double scale, Color color) { double size = PREVIEW_POINT_SIZE; double half = size / 2.0; Shape dot = new Ellipse2D.Double(x - half, y - half, size, size); g2d.setColor(color); g2d.fill(dot); } private static PreviewSnapshot snapshot() { synchronized (LOCK) { if (!active || shapeKey == null || baseStation == null) { return PreviewSnapshot.inactive(); } refreshLocked(); return PreviewSnapshot.of(shapeKey, localPoints, circleState); } } private static void refreshLocked() { String base = baseStation; if (base == null) { localPoints = Collections.emptyList(); circleState = null; return; } double[] baseLatLon = parseBaseStation(base); if (baseLatLon == null) { localPoints = Collections.emptyList(); circleState = null; return; } List copy; synchronized (Coordinate.coordinates) { copy = new ArrayList<>(Coordinate.coordinates); } if (copy.isEmpty()) { localPoints = Collections.emptyList(); circleState = null; return; } List converted = new ArrayList<>(copy.size()); for (Coordinate coord : copy) { double lat = parseDMToDecimal(coord.getLatitude(), coord.getLatDirection()); double lon = parseDMToDecimal(coord.getLongitude(), coord.getLonDirection()); if (!Double.isFinite(lat) || !Double.isFinite(lon)) { continue; } converted.add(convertLatLonToLocal(lat, lon, baseLatLon[0], baseLatLon[1])); } localPoints = converted.isEmpty() ? Collections.emptyList() : converted; if ("circle".equals(shapeKey)) { circleState = fitCircle(localPoints); } else { circleState = null; } } private static double[] parseBaseStation(String baseStationCoordinates) { String[] parts = baseStationCoordinates.split(","); if (parts.length < 4) { return null; } double lat = parseDMToDecimal(parts[0], parts[1]); double lon = parseDMToDecimal(parts[2], parts[3]); if (!Double.isFinite(lat) || !Double.isFinite(lon)) { return null; } return new double[]{lat, lon}; } private static double parseDMToDecimal(String dmm, String direction) { return Gpstoxuzuobiao.parseDMToDecimal(dmm, direction); } private static double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) { return Gpstoxuzuobiao.convertLatLonToLocal(lat, lon, baseLat, baseLon); } private static CircleState fitCircle(List points) { if (points == null || points.size() < 3) { return null; } CircleState best = null; double bestScore = 0.0; int n = points.size(); for (int i = 0; i < n - 2; i++) { double[] p1 = points.get(i); for (int j = i + 1; j < n - 1; j++) { double[] p2 = points.get(j); for (int k = j + 1; k < n; k++) { double[] p3 = points.get(k); CircleState candidate = circleFromThreePoints(p1, p2, p3); if (candidate == null || candidate.radius <= 0) { continue; } double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3))); if (minEdge > bestScore) { bestScore = minEdge; best = candidate; } } } } return best; } private static CircleState circleFromThreePoints(double[] p1, double[] p2, double[] p3) { double x1 = p1[0]; double y1 = p1[1]; double x2 = p2[0]; double y2 = p2[1]; double x3 = p3[0]; double y3 = p3[1]; double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2; double d = 2.0 * a; if (Math.abs(d) < 1e-6) { return null; } double x1Sq = x1 * x1 + y1 * y1; double x2Sq = x2 * x2 + y2 * y2; double x3Sq = x3 * x3 + y3 * y3; double ux = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / d; double uy = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / d; double radius = Math.hypot(ux - x1, uy - y1); if (!Double.isFinite(ux) || !Double.isFinite(uy) || !Double.isFinite(radius)) { return null; } if (radius < 0.05) { return null; } double[] reference = new double[]{x1, y1}; return new CircleState(ux, uy, radius, reference); } private static double distance(double[] a, double[] b) { double dx = a[0] - b[0]; double dy = a[1] - b[1]; return Math.hypot(dx, dy); } private static void requestRepaint() { SwingUtilities.invokeLater(() -> { Shouye shouye = Shouye.getInstance(); if (shouye != null) { shouye.repaint(); } }); } private static final class CircleState { final double centerX; final double centerY; final double radius; final double[] referencePoint; CircleState(double centerX, double centerY, double radius, double[] referencePoint) { this.centerX = centerX; this.centerY = centerY; this.radius = radius; this.referencePoint = referencePoint; } } private static final class PreviewSnapshot { final boolean active; final String shape; final List points; final CircleState circle; private PreviewSnapshot(boolean active, String shape, List points, CircleState circle) { this.active = active; this.shape = shape; this.points = points; this.circle = circle; } static PreviewSnapshot inactive() { return new PreviewSnapshot(false, null, Collections.emptyList(), null); } static PreviewSnapshot of(String shape, List pts, CircleState circle) { List safePoints = new ArrayList<>(pts.size()); for (double[] pt : pts) { safePoints.add(new double[]{pt[0], pt[1]}); } CircleState safeCircle = null; if (circle != null) { double[] refCopy = circle.referencePoint != null ? new double[]{circle.referencePoint[0], circle.referencePoint[1]} : null; safeCircle = new CircleState(circle.centerX, circle.centerY, circle.radius, refCopy); } return new PreviewSnapshot(true, shape, safePoints, safeCircle); } } }