package com.bonus.common.core.utils;

/**
 * 经纬度工具类
 */
public class HaversineUtil {


    /**
     *精度低
     * @param lat1
     * @param lon1
     * @param lat2
     * @param lon2
     * @return
     */
    public static double haversineDistance(double lat1, double lon1, double lat2, double lon2) {
        double earthRadius = 6371; // 地球半径（单位：千米）
        // 将经纬度转换为弧度
        double latRad1 = Math.toRadians(lat1);
        double lonRad1 = Math.toRadians(lon1);
        double latRad2 = Math.toRadians(lat2);
        double lonRad2 = Math.toRadians(lon2);
        // 计算经纬度差值
        double deltaLat = latRad2 - latRad1;
        double deltaLon = lonRad2 - lonRad1;
        // 应用Haversine公式计算距离
        double a = Math.pow(Math.sin(deltaLat / 2), 2)
                + Math.cos(latRad1) * Math.cos(latRad2)
                * Math.pow(Math.sin(deltaLon / 2), 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        // 计算距离
        double distance = earthRadius * c;
        return distance;
    }

    /**
     * 计算两个经纬度的距离
     * @param lat1
     * @param lon1
     * @param lat2
     * @param lon2
     * @return
     */
    public static double vincentyDistance(double lat1, double lon1, double lat2, double lon2) {
        double a = 6378137; // 赤道半径（单位：米）
        double b = 6356752.3142; // 极半径（单位：米）
        double f = 1 / 298.257223563; // 椭球扁率
        // 将经纬度转换为弧度
        double latRad1 = Math.toRadians(lat1);
        double lonRad1 = Math.toRadians(lon1);
        double latRad2 = Math.toRadians(lat2);
        double lonRad2 = Math.toRadians(lon2);
        // 计算经纬度差值
        double L = lonRad2 - lonRad1;
        // 计算U1和U2
        double tanU1 = (1 - f) * Math.tan(latRad1);
        double cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1);
        double sinU1 = tanU1 * cosU1;
        double tanU2 = (1 - f) * Math.tan(latRad2);
        double cosU2 = 1 / Math.sqrt(1 + tanU2 * tanU2);
        double sinU2 = tanU2 * cosU2;

        // 初始化λ和λ'
        double lambda = L;
        double lambdaPrime;
        int iterationLimit = 100; // 迭代次数限制
        double cosSigma, sinSigma, sigma, sinAlpha, cosSqAlpha, cos2SigmaM;
        do {
            double sinLambda = Math.sin(lambda);
            double cosLambda = Math.cos(lambda);
            sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda)
                    + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)
                    * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
            if (sinSigma == 0) {
                // 两点重合，距离为0
                return 0;
            }
            cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
            sigma = Math.atan2(sinSigma, cosSigma);
            sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
            cosSqAlpha = 1 - sinAlpha * sinAlpha;
            cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
            if (Double.isNaN(cos2SigmaM)) {
                cos2SigmaM = 0;
            }
            double C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
            lambdaPrime = lambda;
            lambda = L + (1 - C) * f * sinAlpha
                    * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma
                    * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
            iterationLimit--;
        } while (Math.abs(lambda - lambdaPrime) > 1e-12 && iterationLimit > 0);

        if (iterationLimit == 0) {
            // 未能收敛，返回NaN
            return Double.NaN;
        }

        // 计算椭球面之间的距离
        double uSq = cosSqAlpha * (a * a - b * b) / (b * b);
        double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
        double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
        double deltaSigma = B
                * sinSigma
                * (cos2SigmaM + B
                / 4
                * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B
                / 6
                * cos2SigmaM
                * (-3 + 4 * sinSigma * sinSigma)
                * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
        double distance = b * A * (sigma - deltaSigma);
        return distance;
    }

}
