附近地點搜索解決方案之基於球面距離公式的算法

求球面上任意兩點之間的距離

球面上任意兩點之間的距離計算,我們採用Haversine公式。

distance-haversin-distance.png

其中

 

  • 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);
    }
}

附近地點搜索

查找附近地點算法的難點在於,數據庫中保存的是地點的座標信息,搜索條件是地點座標和給定座標的距離,兩者無法直接建立聯繫,但是如果我們能把搜索的條件轉換成座標範圍的話,就可以直接搜索了。
一般我們需要搜索一定範圍內的地點,這個範圍實際上是一個圓,但是我們可以把搜索的範圍擴大到這個圓的外接正方形,求出這個正方形對應的經緯度範圍,這樣就可以在數據庫中進行查找了。比如下圖:

distance-map.png

根據上面提到的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 &gt;= #{minlng} and longitude &lt;= #{maxlng} and latitude &gt;= #{minlat} and latitude &lt;= #{maxlat}
  </select>

參考:https://cn.charlee.li/location-search.html

https://www.cnblogs.com/toutou/p/9771386.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章