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.SwingUtilities;
|
|
import zhuye.Coordinate;
|
import zhuye.Shouye;
|
|
/**
|
* 在地图上实时预览正在绘制的障碍物。
|
*/
|
public final class yulanzhangaiwu {
|
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<double[]> 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;
|
}
|
|
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);
|
|
if ("circle".equals(snapshot.shape) && snapshot.circle != null) {
|
drawCirclePreview(g2d, snapshot, dashedStroke, scale);
|
} else {
|
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<double[]> 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<double[]> 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<Coordinate> copy;
|
synchronized (Coordinate.coordinates) {
|
copy = new ArrayList<>(Coordinate.coordinates);
|
}
|
if (copy.isEmpty()) {
|
localPoints = Collections.emptyList();
|
circleState = null;
|
return;
|
}
|
|
List<double[]> 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) {
|
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;
|
}
|
}
|
|
private static 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 eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
|
double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
|
return new double[]{eastMeters, northMeters};
|
}
|
|
private static CircleState fitCircle(List<double[]> 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<double[]> points;
|
final CircleState circle;
|
|
private PreviewSnapshot(boolean active, String shape, List<double[]> 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<double[]> pts, CircleState circle) {
|
List<double[]> 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);
|
}
|
}
|
}
|