這兩天需要在公眾號上面做一個關於根據地圖當前定位與目標地址直線距離遠近推薦的查詢,一開始摸不着頭腦,現已解決,mark一下
現有的材料:當前用戶手機端的通過微信定位的經緯度坐標、數據表中保存有場地的定位坐標、微信端傳過來的一個距離范圍值
一開始的思路:
1、程序代碼通過當前定位和范圍計算出符合條件的經緯度區間
2、sql語句里面直接計算記錄里面的直線距離作為篩選條件
3、查出當前所有場地的記錄,然后一條條匹配計算直線距離(最粗暴的方法)
本來是最傾向第一種方案的,但是仔細想過之后,覺得這是個圓形范圍,直接計算出來經緯度區間然后去數據庫匹配的話也不太可能(算法渣渣)
第二種的話貌似也不容易,這個sql語句也太難搞了,而且查了網上的資料有類似的,但是可讀性太差了
本來想着上面兩種方案都不行的話,只能第三種暴力處理
但是在網上查資料的過程中又看到一種類似第一種方案的的方法:
1、先根據當前定位和距離范圍值計算出這個圓形范圍的最大經緯度,這樣就得出了這個圓的切線正方形;
2、再根據這個經緯度區間將符合條件的記錄查詢出來(包含有小部分直線距離不符合要求的記錄);
3、將查詢出來的結果與定位坐標逐一計算直線距離值,剔除超出距離范圍的記錄
4、將結果集排序,完成!
代碼實現:
計算經緯度區間:
//進行范圍搜索,先按照圓切先正方形查找大概范圍,再逐一計算排序 if (space.getLongitude()!=null && space.getLatitude() != null && space.getDistance()!=null){ String[] split = MapCountUtil.getNearbyByLongitudeAndLatitudeAndDistance( BigDecimal.valueOf(space.getLongitude()), BigDecimal.valueOf(space.getLatitude()), space.getDistance().intValue() ).split("-"); BigDecimal minlng = new BigDecimal(split[0]); BigDecimal maxlng = new BigDecimal(split[1]); BigDecimal minlat = new BigDecimal(split[2]); BigDecimal maxlat = new BigDecimal(split[3]); queryWrapper.ge("s.latitude",minlat) .ge("s.longitude",minlng) .le("s.latitude",maxlat) .le("s.longitude",maxlng); }
結果集篩選:
//查詢出圓切線正方形的結果集 List<SpaceVo> listTemp = spaceMapper.selectListInstall(queryWrapper); //計算並排序 List<SpaceVo> list = new ArrayList<>(); if (space.getLatitude()!=null && space.getLongitude()!=null){ for (SpaceVo item : listTemp){ SpaceVo vo = new SpaceVo(); BeanUtils.copyProperties(item, vo); Double distance = MapCountUtil.getDistance( BigDecimal.valueOf(space.getLongitude()), BigDecimal.valueOf(space.getLatitude()), BigDecimal.valueOf(item.getLongitude()), BigDecimal.valueOf(item.getLatitude())); vo.setDistance(distance); if (space.getDistance()!=null){ if (space.getDistance() > (vo.getDistance()/1000)){ list.add(vo); } }else { list.add(vo); } } Collections.sort(list, Comparator.comparing(SpaceVo::getDistance)); }else { list = listTemp; }
地圖工具類:
import java.math.BigDecimal;
/**
* 地圖計算工具類
*/
public class MapCountUtil {
/**
*
* @Description 計算給定經緯度附近相應公里數的經緯度范圍
* @param longitude 經度
* @param latitude 緯度
* @param distance 距離(千米)
* @return String 格式:經度最小值-經度最大值-緯度最小值-緯度最大值
* @Data 2020.06.19
**/
public static String getNearbyByLongitudeAndLatitudeAndDistance(BigDecimal longitude, BigDecimal latitude, Integer distance) {
// 地球半徑千米
double r = 6371.393;
double lng = longitude.doubleValue();
double lat = latitude.doubleValue();
double dlng = 2 * Math.asin(Math.sin(distance / (2 * r)) / Math.cos(lat * Math.PI / 180));
// 角度轉為弧度
dlng = dlng * 180 / Math.PI;
double dlat = distance / r;
dlat = dlat * 180 / Math.PI;
double minlat = lat - dlat;
double maxlat = lat + dlat;
double minlng = lng - dlng;
double maxlng = lng + dlng;
return minlng + "-" + maxlng + "-" + minlat + "-" + maxlat;
}
/**
* @Description 根據經緯度獲取兩點之間的距離
* @param longitude1 地點1經度
* @param latitude1 地點1緯度
* @param longitude2 地點2經度
* @param latitude2 地點2緯度
* @return 距離:單位 米
*/
public static Double getDistance(BigDecimal longitude1, BigDecimal latitude1, BigDecimal longitude2, BigDecimal latitude2) {
// 地球半徑千米
double r = 6371.393;
double lat1 = latitude1.doubleValue();
double lng1 = longitude1.doubleValue();
double lat2 = latitude2.doubleValue();
double lng2 = longitude2.doubleValue();
double radLat1 = rad(lat1);
double radLat2 = rad(lat2);
double a = radLat1 - radLat2;
double b = rad(lng1) - rad(lng2);
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) +
Math.cos(radLat1)*Math.cos(radLat2)*Math.pow(Math.sin(b/2),2)));
s = s * r;
s = Math.round(s * 1000);
return s;
}
private static Double rad(double d) {
return d * Math.PI / 180.0;
}
}