求球面上任意兩點之間的距離
球面上任意兩點之間的距離計算,我們採用Haversine公式。
其中
- R爲地球半徑,取6371km;
- φ1, φ2 表示兩點的緯度;
- Δλ 表示兩點經度的差值。
- d就是我們要求的距離
用python實現下計算球面兩點之間距離的函數:
# coding:utf8
from math import sin, asin, cos, radians, fabs, sqrt
EARTH_RADIUS = 6371 # 地球平均半徑,6371km
def hav(theta):
s = sin(theta / 2)
return s * s
def get_distance_hav(lat0, lng0, lat1, lng1):
# 用haversine公式計算球面兩點間的距離。
# 經緯度轉換成弧度
lat0 = radians(lat0)
lat1 = radians(lat1)
lng0 = radians(lng0)
lng1 = radians(lng1)
dlng = fabs(lng0 - lng1)
dlat = fabs(lat0 - lat1)
h = hav(dlat) + cos(lat0) * cos(lat1) * hav(dlng)
distance = 2 * EARTH_RADIUS * asin(sqrt(h))
return distance
print(get_distance_hav(116.403933, 39.914147, 120.403237, 49.927919))
java實現:
public class HarvenSin {
public final static double AVERAGE_RADIUS_OF_EARTH_KM = 6371;
public static double hav(double theta){
double s = Math.sin(theta / 2);
return s * s;
}
public static int getDistanceHav(double lat0, double lng0,
double lat1, double lng1) {
lat0 = Math.toRadians(lat0);
lat1 = Math.toRadians(lat1);
lng0 = Math.toRadians(lng0);
lng1 = Math.toRadians(lng1);
double dlat = Math.abs(lat0 - lat1);
double dlng = Math.abs(lng0 - lng1);
double h = hav(dlat) + Math.cos(lat0) * Math.cos(lat1) * hav(dlng);
double c = 2 * AVERAGE_RADIUS_OF_EARTH_KM * Math.asin(Math.sqrt(h));
return (int) (Math.round(c));
}
public static void main(String[] args) {
double dist = getDistanceHav(116.403933,39.914147, 120.403237,49.927919);
System.out.println(dist);
}
}
附近地點搜索
查找附近地點算法的難點在於,數據庫中保存的是地點的座標信息,搜索條件是地點座標和給定座標的距離,兩者無法直接建立聯繫,但是如果我們能把搜索的條件轉換成座標範圍的話,就可以直接搜索了。
一般我們需要搜索一定範圍內的地點,這個範圍實際上是一個圓,但是我們可以把搜索的範圍擴大到這個圓的外接正方形,求出這個正方形對應的經緯度範圍,這樣就可以在數據庫中進行查找了。比如下圖:
根據上面提到的Haversine公式,我們還可以反推出計算經緯度搜索範圍的計算。
首先求東西兩側的的範圍邊界。在haversin公式中令φ1 = φ2,可得:
寫成python
dlng = 2 * asin(sin(distance / (2 * EARTH_RADIUS)) / cos(lat))
dlng = degrees(dlng) # 弧度轉換成角度
寫成java
double dlng = 2*Math.asin(Math.sin(dis/(2*r))/Math.cos(lat*Math.PI/180));
dlng = Math.toRadians(dlng);//角度轉爲弧度
然後求南北兩側的範圍邊界,在haversin公式中令 Δλ = 0,可得
寫成python代碼就是
dlat = distance / EARTH_RADIUS
dlng = degrees(dlat) # 弧度轉換成角度
寫成java:
double dlat = dis/r;
dlat = Math.toRadians(dlat);
這樣,根據當前點座標,我們可以得出搜索範圍爲:
left-top : (lat + dlat, lng - dlng)
right-top : (lat + dlat, lng + dlng)
left-bottom : (lat - dlat, lng - dlng)
right-bottom: (lat - dlat, lng + dlng)
java中表示:
double minlat =lat - dlat;
double maxlat =lat + dlat;
double minlng =lng - dlng;
double maxlng =lon + dlng;
然後利用這個範圍構造SQL語句,即可實現範圍查詢:
SELECT * FROM place WHERE lat > (lat-dlat) AND lat < (lat+dlat) AND lng > (lng-dlng) AND lng < (lng+dlng);
java使用mybatis,那麼mapper的配置可能爲:
<select id="getvicinity" resultMap="BaseResultMap">
select
place,longitude,latitude
from userposition
where longitude >= #{minlng} and longitude <= #{maxlng} and latitude >= #{minlat} and latitude <= #{maxlat}
</select>