| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- package com.ylx.common.utils;
- import cn.hutool.core.util.ObjectUtil;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- /**
- * Haversine公式计算两点经纬度球面直线距离工具类
- * 所有坐标入参统一使用 BigDecimal,保证数据库高精度存储兼容
- */
- public final class DistanceUtil {
- // 地球平均半径,单位:米
- private static final double EARTH_RADIUS_M = 6_371_000D;
- // 角度转弧度系数
- private static final double TO_RADIANS = Math.PI / 180.0D;
- // 米转公里换算值
- private static final BigDecimal METER_TO_KM = new BigDecimal("1000");
- // 经纬度极值常量
- private static final BigDecimal MAX_LAT = new BigDecimal("90");
- private static final BigDecimal MAX_LON = new BigDecimal("180");
- private static final BigDecimal ZERO = BigDecimal.ZERO;
- // 私有构造,禁止实例化工具类
- private DistanceUtil() {
- throw new AssertionError("工具类不可实例化");
- }
- // ===================== 对外业务方法 =====================
- /**
- * 获取距离(单位:米,四舍五入整数字符串)
- * @param userLat 用户纬度
- * @param userLon 用户经度
- * @param shopLat 门店纬度
- * @param shopLon 门店经度
- * @return 有效距离返回米数字,坐标为空/0/超出范围 返回 "未知"
- */
- public static String formatDistance(BigDecimal userLat, BigDecimal userLon,
- BigDecimal shopLat, BigDecimal shopLon) {
- if (!isAllCoordinateValid(userLat, userLon, shopLat, shopLon)) {
- return "未知";
- }
- double meter = getDistance(userLat.doubleValue(), userLon.doubleValue(),
- shopLat.doubleValue(), shopLon.doubleValue());
- return String.valueOf(Math.round(meter));
- }
- /**
- * 获取距离(单位:公里,保留2位小数)
- * @param userLat 用户纬度
- * @param userLon 用户经度
- * @param shopLat 门店纬度
- * @param shopLon 门店经度
- * @return 公里字符串,坐标非法返回 "未知"
- */
- public static BigDecimal formatDistanceInKilometers(BigDecimal userLat, BigDecimal userLon,
- BigDecimal shopLat, BigDecimal shopLon) {
- if (!isAllCoordinateValid(userLat, userLon, shopLat, shopLon)) {
- return null;
- }
- double meterVal = getDistance(userLat.doubleValue(), userLon.doubleValue(),
- shopLat.doubleValue(), shopLon.doubleValue());
- BigDecimal meter = new BigDecimal(meterVal);
- return meter.divide(METER_TO_KM, 2, RoundingMode.HALF_UP);
- }
- /**
- * 前端友好展示距离
- * 小于1000米:xx米;大于等于1000米:xx.xx公里;坐标异常:未知
- */
- public static String formatDisplay(BigDecimal userLat, BigDecimal userLon,
- BigDecimal shopLat, BigDecimal shopLon) {
- String meterStr = formatDistance(userLat, userLon, shopLat, shopLon);
- if ("未知".equals(meterStr)) {
- return "未知";
- }
- long meter = Long.parseLong(meterStr);
- if (meter < METER_TO_KM.longValue()) {
- return meter + "米";
- }
- BigDecimal meterNum = new BigDecimal(meter);
- BigDecimal km = meterNum.divide(METER_TO_KM, 2, RoundingMode.HALF_UP);
- return km + "公里";
- }
- /**
- * 底层计算:返回两点球面距离(单位米)
- */
- public static double getDistance(double lat1, double lon1, double lat2, double lon2) {
- double lat1Rad = lat1 * TO_RADIANS;
- double lon1Rad = lon1 * TO_RADIANS;
- double lat2Rad = lat2 * TO_RADIANS;
- double lon2Rad = lon2 * TO_RADIANS;
- double deltaLat = lat2Rad - lat1Rad;
- double deltaLon = lon2Rad - lon1Rad;
- double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)
- + Math.cos(lat1Rad) * Math.cos(lat2Rad)
- * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
- double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
- return EARTH_RADIUS_M * c;
- }
- // ===================== 坐标校验私有方法 =====================
- /**
- * 批量校验四个坐标:非空、不等于0、经纬度范围合法
- */
- private static boolean isAllCoordinateValid(BigDecimal lat1, BigDecimal lon1,
- BigDecimal lat2, BigDecimal lon2) {
- // 任意坐标为null
- if (ObjectUtil.hasNull(lat1, lon1, lat2, lon2)) {
- return false;
- }
- // 任意坐标数值等于0
- if (lat1.compareTo(ZERO) == 0
- || lon1.compareTo(ZERO) == 0
- || lat2.compareTo(ZERO) == 0
- || lon2.compareTo(ZERO) == 0) {
- return false;
- }
- // 两组坐标分别校验范围
- return isSingleCoordinateValid(lat1, lon1) && isSingleCoordinateValid(lat2, lon2);
- }
- /**
- * 校验单组经纬度范围:纬度[-90,90] 经度[-180,180]
- */
- private static boolean isSingleCoordinateValid(BigDecimal lat, BigDecimal lon) {
- return lat.abs().compareTo(MAX_LAT) <= 0
- && lon.abs().compareTo(MAX_LON) <= 0;
- }
- }
|