package com.ard.sample.annotation.util; import lombok.extern.slf4j.Slf4j; import java.util.List; /** * 地理坐标计算工具类(WGS84坐标系) * 支持: * 1. 线:水平距离(累加)、直线距离(起点→终点) * 2. 面:水平面积、水平周长 * 3. 圆:水平面积(πr²)、水平周长(2πr) */ @Slf4j public class GeoCalculator { // WGS84椭球参数 private static final double WGS84_A = 6378137.0; // 地球长半轴(米) private static final double RAD = Math.PI / 180.0; // 角度转弧度 private static final double PI = Math.PI; // 圆周率 // ====================== 通用工具方法 ====================== /** * 保留两位小数(四舍五入) */ public static double keepTwoDecimal(double value) { return Math.round(value * 100.0) / 100.0; } /** * 计算两点间的水平距离(米)- 经纬度投影到水平面 */ private static double calculateTwoPointHorizontalDistance(Coordinate p1, Coordinate p2) { if (p1 == null || p2 == null) return 0.0; // 转换为弧度 double lon1 = p1.getLongitude() * RAD; double lat1 = p1.getLatitude() * RAD; double lon2 = p2.getLongitude() * RAD; double lat2 = p2.getLatitude() * RAD; // 计算纬度对应的地球切面半径 double R = WGS84_A * Math.cos(lat1); // 经度差对应的水平距离 double dx = R * (lon2 - lon1); // 纬度差对应的水平距离 double dy = WGS84_A * (lat2 - lat1); // 水平直线距离(勾股定理) return Math.sqrt(dx * dx + dy * dy); } /** * 计算两点间的直线地理距离(米)- Haversine公式 */ private static double calculateTwoPointStraightDistance(Coordinate p1, Coordinate p2) { if (p1 == null || p2 == null) return 0.0; double lon1 = p1.getLongitude() * RAD; double lat1 = p1.getLatitude() * RAD; double lon2 = p2.getLongitude() * RAD; double lat2 = p2.getLatitude() * RAD; double deltaLat = lat2 - lat1; double deltaLon = lon2 - lon1; double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return WGS84_A * c; } // ====================== 线(折线)计算 ====================== /** * 计算折线的水平距离(累加,米) */ public static double calculatePolylineHorizontalDistance(List points) { if (points == null || points.size() < 2) { log.warn("折线水平距离计算失败:顶点数不足2个"); return 0.0; } double totalHorizontal = 0.0; for (int i = 0; i < points.size() - 1; i++) { totalHorizontal += calculateTwoPointHorizontalDistance(points.get(i), points.get(i + 1)); } return keepTwoDecimal(totalHorizontal); } /** * 计算折线的直线距离(起点→终点,米) */ public static double calculatePolylineStraightDistance(List points) { if (points == null || points.size() < 2) { log.warn("折线直线距离计算失败:顶点数不足2个"); return 0.0; } Coordinate start = points.get(0); Coordinate end = points.get(points.size() - 1); return keepTwoDecimal(calculateTwoPointStraightDistance(start, end)); } // ====================== 面(多边形)计算 ====================== /** * 计算多边形的水平面积(平方米) */ public static double calculatePolygonHorizontalArea(List points) { if (points == null || points.size() < 3) { log.warn("多边形水平面积计算失败:顶点数不足3个"); return 0.0; } // 闭合多边形 List closedPoints = new java.util.ArrayList<>(points); Coordinate first = closedPoints.get(0); Coordinate last = closedPoints.get(closedPoints.size() - 1); if (!isCoordinateEqual(first, last)) { closedPoints.add(first); } double area = 0.0; int n = closedPoints.size() - 1; for (int i = 0; i < n; i++) { Coordinate p1 = closedPoints.get(i); Coordinate p2 = closedPoints.get(i + 1); // 水平投影面积公式(高斯平面投影) area += (p1.getLongitude() * p2.getLatitude() - p2.getLongitude() * p1.getLatitude()); } // 转换为实际面积(修正系数) area = Math.abs(area) * 0.5 * WGS84_A * WGS84_A * RAD * RAD; return keepTwoDecimal(area); } /** * 计算多边形的水平周长(米) */ public static double calculatePolygonHorizontalPerimeter(List points) { if (points == null || points.size() < 3) { log.warn("多边形水平周长计算失败:顶点数不足3个"); return 0.0; } // 闭合多边形 List closedPoints = new java.util.ArrayList<>(points); Coordinate first = closedPoints.get(0); Coordinate last = closedPoints.get(closedPoints.size() - 1); if (!isCoordinateEqual(first, last)) { closedPoints.add(first); } double perimeter = 0.0; int n = closedPoints.size() - 1; for (int i = 0; i < n; i++) { perimeter += calculateTwoPointHorizontalDistance(closedPoints.get(i), closedPoints.get(i + 1)); } return keepTwoDecimal(perimeter); } // ====================== 圆计算 ====================== /** * 计算圆的水平面积(平方米)- π×r² */ public static double calculateCircleHorizontalArea(double radius) { if (radius <= 0) { log.warn("圆形水平面积计算失败:半径非法({}米)", radius); return 0.0; } return keepTwoDecimal(PI * radius * radius); } /** * 计算圆的水平周长(米)- 2πr */ public static double calculateCircleHorizontalPerimeter(double radius) { if (radius <= 0) { log.warn("圆形水平周长计算失败:半径非法({}米)", radius); return 0.0; } return keepTwoDecimal(2 * PI * radius); } // ====================== 辅助方法 ====================== /** * 判断两个坐标是否相等(容错1e-6) */ private static boolean isCoordinateEqual(Coordinate p1, Coordinate p2) { if (p1 == null || p2 == null) return false; double lonDiff = Math.abs(p1.getLongitude() - p2.getLongitude()); double latDiff = Math.abs(p1.getLatitude() - p2.getLatitude()); return lonDiff < 1e-6 && latDiff < 1e-6; } // ====================== 坐标实体类 ====================== public static class Coordinate { private double longitude; // 经度 private double latitude; // 纬度 public Coordinate() {} public Coordinate(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } // Getter & Setter public double getLongitude() { return longitude; } public void setLongitude(double longitude) { this.longitude = longitude; } public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } } }