PHP實現搜尋附近的人

攻城獅丶發表於2017-11-10

最近的一個專案要求根據使用者當前位置的經緯度來查詢該使用者方圓十公里以內的人,這個功能並不是什麼很有技術含量的實現(當然,我們僅僅指的是該功能本身和資料量較小的時候,並不包括長期以來其派生出來的其他問題 ),但由於種種考慮,我還是決定將其記錄下來。
以後不管我們從事APP服務端開發,還是做WEB開發,可能經常會有客戶要求實現這樣的功能,所以我們還是要掌握其原理。

【問題的提出】

我們該如何思考這個問題 ? 可能有的小夥伴要說了:“ 既然我們已經知道了當前使用者的經緯度,我們從資料庫裡面查詢使用者的經緯度,然後一一計算,篩選出所有符合條件的使用者。”

我也曾想當然的這樣認為,並沒有考慮到資料庫的感受,當你操作的是一個十萬,百萬級別的資料庫時,這樣的做法帶來的問題將是災難性的,那麼我們到底該如何做呢 ?

【實現思路】

首先,我們應該這樣想: 既然我們知道了使用者當前位置的經緯度,又知道我們將要搜尋的範圍,我們可不可以計算出一個範圍 ?也就是說,根據一箇中心點和半徑,計算出符合條件的經緯度的最大值和最小值 。

【具體實現】

那麼到此,想要獨立思考完成的小夥伴可以不要繼續往下看了。

上面我們提到該功能的一個實現原理,接下來我們就講解一下具體的實現步驟。

我們先宣告一個函式,用作計算經緯度的範圍:

/**
 * 根據經緯度和半徑計算出範圍
 * @param string $lat 緯度
 * @param String $lng 經度
 * @param float $radius 半徑
 * @return Array 範圍陣列
 */
private function calcScope($lat, $lng, $radius) {
    $degree = (24901*1609)/360.0;
    $dpmLat = 1/$degree;

    $radiusLat = $dpmLat*$radius;
    $minLat = $lat - $radiusLat;       // 最小緯度
    $maxLat = $lat + $radiusLat;       // 最大緯度

    $mpdLng = $degree*cos($lat * (PI/180));
    $dpmLng = 1 / $mpdLng;
    $radiusLng = $dpmLng*$radius;
    $minLng = $lng - $radiusLng;      // 最小經度
    $maxLng = $lng + $radiusLng;      // 最大經度

    /** 返回範圍陣列 */
    $scope = array(
        `minLat`    =>  $minLat,
        `maxLat`    =>  $maxLat,
        `minLng`    =>  $minLng,
        `maxLng`    =>  $maxLng
        );
    return $scope;
}

返回的陣列中包含了在 $radius 範圍內,符合條件的最大最小經緯度。

既然我們已經獲取到了範圍,那麼我們就可以開始從資料庫中查詢所有在這個經緯度範圍內符合條件的記錄:

/**
 * 根據經緯度和半徑查詢在此範圍內的所有的
 * @param  String $lat    緯度
 * @param  String $lng    經度
 * @param  float $radius 半徑
 * @return Array         計算出來的結果
 */
public function searchByLatAndLng($lat, $lng, $radius) {
    $scope = $this->calcScope($lat, $lng, $radius);     // 呼叫範圍計算函式,獲取最大最小經緯度
    /** 查詢經緯度在 $radius 範圍內的電站的詳細地址 */
    $sql = `SELECT `欄位` FROM `表名` WHERE `Latitude` < `.$scope[`maxLat`].` and `Latitude` > `.$scope[`minLat`].` and `Longitude` < `.$scope[`maxLng`].` and `Longitude` > `.$scope[`minLng`];

    $stmt = self::$db->query($sql);
    $res = $stmt->fetchAll(PDO::FETCH_ASSOC);       // 獲取查詢結果並返回
    return $res;
}

【擴充套件】

直到現在,我們已經知道了如何計算出附近的人,但在實際需求中,我們往往需要計算出每一個人與當前中心點的實際距離。

接著,我們再來看一個方法:


/**
 * 獲取兩個經緯度之間的距離
 * @param  string $lat1 緯一
 * @param  String $lng1 經一
 * @param  String $lat2 緯二
 * @param  String $lng2 經二
 * @return float  返回兩點之間的距離
 */
public function calcDistance($lat1, $lng1, $lat2, $lng2) {
    /** 轉換資料型別為 double */
    $lat1 = doubleval($lat1);
    $lng1 = doubleval($lng1);
    $lat2 = doubleval($lat2);
    $lng2 = doubleval($lng2);
    /** 以下演算法是 Google 出來的,與大多數經緯度計算工具結果一致 */
    $theta = $lng1 - $lng2;
    $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) +  cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
    $dist = acos($dist);
    $dist = rad2deg($dist);
    $miles = $dist * 60 * 1.1515;
    return ($miles * 1.609344);
}

我們在之前已經計算出了附近符合條件的所有人的經緯度,那麼我們可以將每一個人的經緯度和當前中心點的經緯度作為引數傳入該函式,最終計算出每個人符合條件的人與該中心點的距離。

為了保證程式的嚴謹性,我必須做出以下說明:

經緯度範圍的計算方法是在百度上查詢的,目前尚沒有找到標準的計算工具,因此該演算法無法求證,但大多數帖子基本上採用的這種計算方式。
經緯度距離的計算方式是在 Google 上查詢的,計算結果與大多數計算工具結果相近。之所以在 Google 中查詢,是因為我曾在百度上查詢過多種計算方式,結果都不盡人意。
通過半徑和經緯度計算範圍時,半徑的單位是m;計算距離時,得到的結果是km。因此,要注意單位變化。
我依然在努力求證,尋求有關專業的幫助,獲得更精確的計算結果。

作者:Elvis_Li
連結:http://www.jianshu.com/p/d2080a9b87eb
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。


相關文章