DistanceUtil.java 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package com.ylx.common.utils;
  2. import cn.hutool.core.util.ObjectUtil;
  3. import java.math.BigDecimal;
  4. import java.math.RoundingMode;
  5. /**
  6. * Haversine公式计算两点经纬度球面直线距离工具类
  7. * 所有坐标入参统一使用 BigDecimal,保证数据库高精度存储兼容
  8. */
  9. public final class DistanceUtil {
  10. // 地球平均半径,单位:米
  11. private static final double EARTH_RADIUS_M = 6_371_000D;
  12. // 角度转弧度系数
  13. private static final double TO_RADIANS = Math.PI / 180.0D;
  14. // 米转公里换算值
  15. private static final BigDecimal METER_TO_KM = new BigDecimal("1000");
  16. // 经纬度极值常量
  17. private static final BigDecimal MAX_LAT = new BigDecimal("90");
  18. private static final BigDecimal MAX_LON = new BigDecimal("180");
  19. private static final BigDecimal ZERO = BigDecimal.ZERO;
  20. // 私有构造,禁止实例化工具类
  21. private DistanceUtil() {
  22. throw new AssertionError("工具类不可实例化");
  23. }
  24. // ===================== 对外业务方法 =====================
  25. /**
  26. * 获取距离(单位:米,四舍五入整数字符串)
  27. * @param userLat 用户纬度
  28. * @param userLon 用户经度
  29. * @param shopLat 门店纬度
  30. * @param shopLon 门店经度
  31. * @return 有效距离返回米数字,坐标为空/0/超出范围 返回 "未知"
  32. */
  33. public static String formatDistance(BigDecimal userLat, BigDecimal userLon,
  34. BigDecimal shopLat, BigDecimal shopLon) {
  35. if (!isAllCoordinateValid(userLat, userLon, shopLat, shopLon)) {
  36. return "未知";
  37. }
  38. double meter = getDistance(userLat.doubleValue(), userLon.doubleValue(),
  39. shopLat.doubleValue(), shopLon.doubleValue());
  40. return String.valueOf(Math.round(meter));
  41. }
  42. /**
  43. * 获取距离(单位:公里,保留2位小数)
  44. * @param userLat 用户纬度
  45. * @param userLon 用户经度
  46. * @param shopLat 门店纬度
  47. * @param shopLon 门店经度
  48. * @return 公里字符串,坐标非法返回 "未知"
  49. */
  50. public static BigDecimal formatDistanceInKilometers(BigDecimal userLat, BigDecimal userLon,
  51. BigDecimal shopLat, BigDecimal shopLon) {
  52. if (!isAllCoordinateValid(userLat, userLon, shopLat, shopLon)) {
  53. return null;
  54. }
  55. double meterVal = getDistance(userLat.doubleValue(), userLon.doubleValue(),
  56. shopLat.doubleValue(), shopLon.doubleValue());
  57. BigDecimal meter = new BigDecimal(meterVal);
  58. return meter.divide(METER_TO_KM, 2, RoundingMode.HALF_UP);
  59. }
  60. /**
  61. * 前端友好展示距离
  62. * 小于1000米:xx米;大于等于1000米:xx.xx公里;坐标异常:未知
  63. */
  64. public static String formatDisplay(BigDecimal userLat, BigDecimal userLon,
  65. BigDecimal shopLat, BigDecimal shopLon) {
  66. String meterStr = formatDistance(userLat, userLon, shopLat, shopLon);
  67. if ("未知".equals(meterStr)) {
  68. return "未知";
  69. }
  70. long meter = Long.parseLong(meterStr);
  71. if (meter < METER_TO_KM.longValue()) {
  72. return meter + "米";
  73. }
  74. BigDecimal meterNum = new BigDecimal(meter);
  75. BigDecimal km = meterNum.divide(METER_TO_KM, 2, RoundingMode.HALF_UP);
  76. return km + "公里";
  77. }
  78. /**
  79. * 底层计算:返回两点球面距离(单位米)
  80. */
  81. public static double getDistance(double lat1, double lon1, double lat2, double lon2) {
  82. double lat1Rad = lat1 * TO_RADIANS;
  83. double lon1Rad = lon1 * TO_RADIANS;
  84. double lat2Rad = lat2 * TO_RADIANS;
  85. double lon2Rad = lon2 * TO_RADIANS;
  86. double deltaLat = lat2Rad - lat1Rad;
  87. double deltaLon = lon2Rad - lon1Rad;
  88. double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)
  89. + Math.cos(lat1Rad) * Math.cos(lat2Rad)
  90. * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
  91. double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  92. return EARTH_RADIUS_M * c;
  93. }
  94. // ===================== 坐标校验私有方法 =====================
  95. /**
  96. * 批量校验四个坐标:非空、不等于0、经纬度范围合法
  97. */
  98. private static boolean isAllCoordinateValid(BigDecimal lat1, BigDecimal lon1,
  99. BigDecimal lat2, BigDecimal lon2) {
  100. // 任意坐标为null
  101. if (ObjectUtil.hasNull(lat1, lon1, lat2, lon2)) {
  102. return false;
  103. }
  104. // 任意坐标数值等于0
  105. if (lat1.compareTo(ZERO) == 0
  106. || lon1.compareTo(ZERO) == 0
  107. || lat2.compareTo(ZERO) == 0
  108. || lon2.compareTo(ZERO) == 0) {
  109. return false;
  110. }
  111. // 两组坐标分别校验范围
  112. return isSingleCoordinateValid(lat1, lon1) && isSingleCoordinateValid(lat2, lon2);
  113. }
  114. /**
  115. * 校验单组经纬度范围:纬度[-90,90] 经度[-180,180]
  116. */
  117. private static boolean isSingleCoordinateValid(BigDecimal lat, BigDecimal lon) {
  118. return lat.abs().compareTo(MAX_LAT) <= 0
  119. && lon.abs().compareTo(MAX_LON) <= 0;
  120. }
  121. }