前言
之前做過的很多項目中都有涉及到根據經緯度計算兩點之間的距離,然後做排序。這個場景在很多外賣App或者小程序上經常看到,例如距離2km,<100m等等。
工具類1
public class LocationUtil {
/**
* 地球半徑,單位 km
*/
private static final double EARTH_RADIUS = 6371;
private LocationUtil() {
}
/**
* 根據經緯度,計算兩點間的距離
*
* @param longitude1 第一個點的經度
* @param latitude1 第一個點的緯度
* @param longitude2 第二個點的經度
* @param latitude2 第二個點的緯度
* @return 返回距離 單位米
*/
public static int getDistance(double longitude1, double latitude1, double longitude2, double latitude2) {
// 緯度
double lat1 = Math.toRadians(latitude1);
double lat2 = Math.toRadians(latitude2);
// 經度
double lng1 = Math.toRadians(longitude1);
double lng2 = Math.toRadians(longitude2);
// 緯度之差
double a = lat1 - lat2;
// 經度之差
double b = lng1 - lng2;
// 計算兩點距離的公式
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(b / 2), 2)));
// 弧長乘地球半徑, 返回單位: 千米
s = s * EARTH_RADIUS;
//轉成米
return (int) Math.round(s * 1000);
}
public static void main(String[] args) {
int d = getDistance(116.353454, 39.996059, 116.308479, 39.983171);
System.out.println(d);
}
注意:最後計算的s返回的是km(千米),項目需要可以x1000轉成了m,需要轉成km可以在業務代碼中自己做處理或者交給前端同學去處理均可。
工具類2
public class DistanceUtil {
private static final double EARTH_RADIUS = 6371;
private static final String LESS = "<";
private static final String M = "m";
private static final String KM = "km";
private static final int ONE_HUNDRED = 100;
private static final int THOUSAND_AND_ONE_HUNDRED = 1100;
private static final int ONE_THOUSAND = 1000;
private static final BigDecimal BIG_DECIMAL_THOUSAND = new BigDecimal(1000);
public static final String DISTANCE_DEFAULT_STR = "—";
/**
* @param lat1 當前緯度
* @param lon1 當前經度
* @param lat2 目標緯度
* @param lon2 目標經度
* @return
*/
public static double distance(double lat1, double lon1, double lat2, double lon2) {
lat1 = convertDegreesToRadians(lat1);
lon1 = convertDegreesToRadians(lon1);
lat2 = convertDegreesToRadians(lat2);
lon2 = convertDegreesToRadians(lon2);
double vLat = Math.abs(lat1 - lat2);
double vLon = Math.abs(lon1 - lon2);
double h = haverSin(vLat) + Math.cos(lat1) * Math.cos(lat2) * haverSin(vLon);
return Math.round(2 * EARTH_RADIUS * Math.asin(Math.sqrt(h)) * 1000);
}
private static double haverSin(double theta) {
double v = Math.sin(theta / 2);
return v * v;
}
private static double convertDegreesToRadians(double degrees) {
return degrees * Math.PI / 180;
}
/**
* 格式化距離
* @param distance
* @return
*/
public static String formatDistance(double distance) {
String distanceStr = DISTANCE_DEFAULT_STR;
if (ObjectUtil.isNull(distance) || distance <= -1) {
return distanceStr;
}
if (distance < ONE_HUNDRED) {
distanceStr = LESS + ONE_HUNDRED + M;
} else if (distance >= ONE_HUNDRED && distance < ONE_THOUSAND) {
distanceStr = new BigDecimal(distance).setScale(0, BigDecimal.ROUND_DOWN).toString();
distanceStr = distanceStr + M;
} else if (distance >= ONE_THOUSAND) {
distanceStr = new BigDecimal(distance).divide(BIG_DECIMAL_THOUSAND).setScale(1, BigDecimal.ROUND_DOWN).toString();
distanceStr = distanceStr + KM;
}
return distanceStr;
}
}
注意:這和第一種的計算方式其實是一樣的,只是用的形式不一樣,可以自定義格式化一些前端需要的數據格式。
Demo
private List<AddressInfoModel> buildResultList(AddressListParam param, List<QrorderingCustomerAddressDO> list) {
Float longitude = null;
Float latitude = null;
this.fillParam(param, longitude, latitude);
List<AddressInfoModel> resultList = Lists.newArrayList();
AddressInfoModel addressInfoModel;
for (QrorderingCustomerAddressDO addressDO : list) {
addressInfoModel = FsBeanUtil.map(addressDO, AddressInfoModel.class);
int distance = LocationUtil.getDistance(Double.valueOf(longitude), Double.valueOf(latitude), Double.valueOf(addressDO.getLongitude()), Double.valueOf(addressDO.getLatitude()));
addressInfoModel.setDistance(distance);
if (distance > param.getDeliveryArea()) {
addressInfoModel.setDistributionScope(CommonConstant.CONSTANT_ONE);
} else {
addressInfoModel.setDistributionScope(CommonConstant.CONSTANT_TWO);
}
resultList.add(addressInfoModel);
}
//按照距離降序排序
resultList.sort(Comparator.comparingInt(o -> o.getDistance()));
return resultList;
}