目錄
總結
首頁 後端開發 php教程 教你使用PHP實作來找出你想要的附近人

教你使用PHP實作來找出你想要的附近人

Oct 22, 2020 am 11:49 AM
php

最近有個業務場景使用到了查找附近的人,於是查閱了相關資料,並對使用PHP實現相關功能的多種方式和具體實現做一篇技術總結,歡迎各位看官提出意見和糾錯,以下開始進入正題:

LBS(基於位置的服務)

查找附近的人有個更大的專有名詞叫做LBS(基於位置的服務),LBS是指是指通過電信移動運營商的無線電通信網絡或外部定位方式,獲取移動終端用戶的位置信息,在GIS平台的支援下,為使用者提供相應服務的一種增值業務。因此首先得獲取用戶的位置,獲取用戶的位置有基於GPS、基於運營商基地台、WIFI等方式,一般由客戶端獲取用戶位置的經緯度坐標上傳至應用服務器,應用服務器對用戶坐標進行保存,客戶端取得附近的人資料的時候,應用程式伺服器是基於請求人的地理位置配合一定的條件(距離,性別,活躍時間等)去資料庫進行篩選排序。

根據經緯度如何得出兩點之間的距離?

我們都知道平面座標內的兩點座標可以使用平面座標距離公式來計算,但經緯度是利用三度空間的球面來定義地球上的空間的球面座標系統,假定地球是正球體,關於球面距離計算公式如下:

教你使用PHP實作來找出你想要的附近人

具體推斷過程有興趣的推薦這篇文章:【數學公式及推導】根據經緯度計算地面兩點間的距離

PHP函數程式碼如下:

/**
     * 根据两点间的经纬度计算距离
     * @param $lat1
     * @param $lng1
     * @param $lat2
     * @param $lng2
     * @return float
     */
    public static function getDistance($lat1, $lng1, $lat2, $lng2){
        $earthRadius = 6367000; //approximate radius of earth in meters
        $lat1 = ($lat1 * pi() ) / 180;
        $lng1 = ($lng1 * pi() ) / 180;
        $lat2 = ($lat2 * pi() ) / 180;
        $lng2 = ($lng2 * pi() ) / 180;
        $calcLongitude = $lng2 - $lng1;
        $calcLatitude = $lat2 - $lat1;
        $stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2);
        $stepTwo = 2 * asin(min(1, sqrt($stepOne)));
        $calculatedDistance = $earthRadius * $stepTwo;
        return round($calculatedDistance);
    }
登入後複製

MySQL程式碼如下:

SELECT  
  id, (  
    3959 * acos (  
      cos ( radians(78.3232) )  
      * cos( radians( lat ) )  
      * cos( radians( lng ) - radians(65.3234) )  
      + sin ( radians(78.3232) )  
      * sin( radians( lat ) )  
    )  
  ) AS distance  
FROM markers  
HAVING distance < 30  
ORDER BY distance  
LIMIT 0 , 20;
登入後複製

除了上面透過計算球面距離公式來取得,我們可以使用某有些資料庫服務得到,例如Redis和MongoDB:

Redis 3.2提供GEO地理位置功能,不僅可以取得兩個位置之間的距離,取得指定位置範圍內的地理資訊位置集合也很簡單。 Redis指令文件

1.增加地理位置

GEOADD key longitude latitude member [longitude latitude member ...]
登入後複製

2.取得地理位置

GEOPOS key member [member ...]
登入後複製

3.取得兩個地理位置的距離

GEODIST key member1 member2 [unit]
登入後複製

4.取得指定經緯度的地理資訊位置集合

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
登入後複製

5.取得指定成員的地理資訊位置集合

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
登入後複製

MongoDB專門針對這種查詢建立了地理空間索引。 2d和2dsphere索引,分別是針對平面和球面。 MongoDB文件

1.新增資料

db.location.insert( {uin : 1 , loc : { lon : 50 , lat : 50 } } )
登入後複製

2.建立索引

db.location.ensureIndex( { loc : "2d" } )
登入後複製

3.尋找附近的點

db.location.find( { loc :{ $near : [50, 50] } )
登入後複製

4 .最大距離和限制條數

db.location.find( { loc : { $near : [50, 50] , $maxDistance : 5 } } ).limit(20)
登入後複製

5.使用geoNear在查詢結果中傳回每個點距離查詢點的距離

db.runCommand( { geoNear : "location" , near : [ 50 , 50 ], num : 10, query : { type : "museum" } } )
登入後複製

6.使用geoNear附帶查詢條件和傳回條數, geoNear使用runCommand指令不支援find查詢中分頁相關limit和skip參數的功能

db.runCommand( { geoNear : "location" , near : [ 50 , 50 ], num : 10, query : { uin : 1 } })
登入後複製

PHP多種方式和具體實作

1.基於MySql

成員新增方法:

public function geoAdd($uin, $lon, $lat)
{
    $pdo = $this->getPdo();
    $sql = &#39;INSERT INTO `markers`(`uin`, `lon`, `lat`) VALUES (?, ?, ?)&#39;;
    $stmt = $pdo->prepare($sql);
    return $stmt->execute(array($uin, $lon, $lat));
}
登入後複製

查詢附近的人(支援查詢條件與分頁):

public function geoNearFind($lon, $lat, $maxDistance = 0, $where = array(), $page = 0)
{
    $pdo = $this->getPdo();
    $sql = "SELECT  
              id, (  
                3959 * acos (  
                  cos ( radians(:lat) )  
                  * cos( radians( lat ) )  
                  * cos( radians( lon ) - radians(:lon) )  
                  + sin ( radians(:lat) )  
                  * sin( radians( lat ) )  
                )  
              ) AS distance  
            FROM markers";

    $input[&#39;:lat&#39;] = $lat;
    $input[&#39;:lon&#39;] = $lon;

    if ($where) {
        $sqlWhere = &#39; WHERE &#39;;
        foreach ($where as $key => $value) {
            $sqlWhere .= "`{$key}` = :{$key} ,";
            $input[":{$key}"] = $value;
        }
        $sql .= rtrim($sqlWhere, &#39;,&#39;);
    }

    if ($maxDistance) {
        $sqlHaving = " HAVING distance < :maxDistance";
        $sql .= $sqlHaving;
        $input[&#39;:maxDistance&#39;] = $maxDistance;
    }

    $sql .= &#39; ORDER BY distance&#39;;

    if ($page) {
        $page > 1 ? $offset = ($page - 1) * $this->pageCount : $offset = 0;
        $sqlLimit = " LIMIT {$offset} , {$this->pageCount}";
        $sql .= $sqlLimit;
    }

    $stmt = $pdo->prepare($sql);
    $stmt->execute($input);
    $list = $stmt->fetchAll(PDO::FETCH_ASSOC);

    return $list;
}
登入後複製

2.基於Redis(3.2以上)

PHP使用Redis可以安裝redis擴充或透過composer安裝predis類別庫,本文使用redis擴充功能來實作。

成員新增方法:

public function geoAdd($uin, $lon, $lat)
{
    $redis = $this->getRedis();
    $redis->geoAdd(&#39;markers&#39;, $lon, $lat, $uin);
    return true;
}
登入後複製

查詢附近的人(不支援查詢條件與分頁):

public function geoNearFind($uin, $maxDistance = 0, $unit = &#39;km&#39;)
{
    $redis = $this->getRedis();
    $options = [&#39;WITHDIST&#39;]; //显示距离
    $list = $redis->geoRadiusByMember(&#39;markers&#39;, $uin, $maxDistance, $unit, $options);
    return $list;
}
登入後複製

3.基於MongoDB

PHP使用MongoDB的擴充有mongo(文件)和mongodb(文件),兩者寫法差異很大,選擇好擴充需要對應的文檔查看,由於mongodb擴充是新版,本文選擇mongodb擴充。

假設我們建立db庫和location集合

設定索引:

db.getCollection(&#39;location&#39;).ensureIndex({"uin":1},{"unique":true}) 
db.getCollection(&#39;location&#39;).ensureIndex({loc:"2d"})
#若查询位置附带查询,可以将常查询条件添加至组合索引
#db.getCollection(&#39;location&#39;).ensureIndex({loc:"2d",uin:1})
登入後複製

成員新增方法:

public function geoAdd($uin, $lon, $lat)
{
    $document = array(
        &#39;uin&#39; => $uin,
        &#39;loc&#39; => array(
            &#39;lon&#39; =>  $lon,
            &#39;lat&#39; =>  $lat,
        ),
    );

    $bulk = new MongoDB\Driver\BulkWrite;
    $bulk->update(
        [&#39;uin&#39; => $uin],
        $document,
        [ &#39;upsert&#39; => true]
    );
    //出现noreply 可以改成确认式写入
    $manager = $this->getMongoManager();
    $writeConcern = new MongoDB\Driver\WriteConcern(1, 100);
    //$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 100);
    $result = $manager->executeBulkWrite(&#39;db.location&#39;, $bulk, $writeConcern);

    if ($result->getWriteErrors()) {
        return false;
    }
    return true;
}
登入後複製

查詢附近的人(回傳結果沒有距離,支援查詢條件,支援分頁)

public function geoNearFind($lon, $lat, $maxDistance = 0, $where = array(), $page = 0)
{
    $filter = array(
        &#39;loc&#39; => array(
            &#39;$near&#39; => array($lon, $lat),
        ),
    );
    if ($maxDistance) {
        $filter[&#39;loc&#39;][&#39;$maxDistance&#39;] = $maxDistance;
    }
    if ($where) {
        $filter = array_merge($filter, $where);
    }
    $options = array();
    if ($page) {
        $page > 1 ? $skip = ($page - 1) * $this->pageCount : $skip = 0;
        $options = [
            &#39;limit&#39; => $this->pageCount,
            &#39;skip&#39; => $skip
        ];
    }

    $query = new MongoDB\Driver\Query($filter, $options);
    $manager = $this->getMongoManager();
    $cursor = $manager->executeQuery(&#39;db.location&#39;, $query);
    $list = $cursor->toArray();
    return $list;
}
登入後複製

查詢附近的人(返回結果帶距離,支援查詢條件,支付返回數量,不支援分頁):

public function geoNearFindReturnDistance($lon, $lat, $maxDistance = 0, $where = array(), $num = 0)
{
    $params = array(
        &#39;geoNear&#39; => "location",
        &#39;near&#39; => array($lon, $lat),
        &#39;spherical&#39; => true, // spherical设为false(默认),dis的单位与坐标的单位保持一致,spherical设为true,dis的单位是弧度
        &#39;distanceMultiplier&#39; => 6371, // 计算成公里,坐标单位distanceMultiplier: 111。 弧度单位 distanceMultiplier: 6371
    );

    if ($maxDistance) {
        $params[&#39;maxDistance&#39;] = $maxDistance;
    }
    if ($num) {
        $params[&#39;num&#39;] = $num;
    }
    if ($where) {
        $params[&#39;query&#39;] = $where;
    }

    $command = new MongoDB\Driver\Command($params);
    $manager = $this->getMongoManager();
    $cursor = $manager->executeCommand(&#39;db&#39;, $command);
    $response = (array) $cursor->toArray()[0];
    $list = $response[&#39;results&#39;];
    return $list;
}
登入後複製

注意事項:

1.選擇好擴展,mongo和mongodb擴展寫法差異很大

2.寫資料時出現noreply請檢查寫入確認等級

3.使用find查詢的資料需要自行計算距離,使用geoNear查詢的不支援分頁

4.使用geoNear查詢的距離需要轉換成km使用spherical和distanceMultiplier參數

上述demo可以戳這裡:demo

總結

#以上介紹了三種方式去實現查詢附近的人的功能,各種方式都有各自的適用場景,例如資料行比較少,例如查詢使用者和幾座城市之間的距離使用Mysql就足夠了,如果需要即時快速回應並且普通找出範圍內的距離,可以使用Redis,但如果資料量大且多種屬性篩選條件,使用mongo會更方便,以上只是建議,具體實作方案還要視具體業務去進行方案評審。

#

以上是教你使用PHP實作來找出你想要的附近人的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1657
14
CakePHP 教程
1415
52
Laravel 教程
1309
25
PHP教程
1257
29
C# 教程
1229
24
適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 帶來了多項新功能、安全性改進和效能改進,同時棄用和刪除了大量功能。 本指南介紹如何在 Ubuntu、Debian 或其衍生版本上安裝 PHP 8.4 或升級到 PHP 8.4

您如何在PHP中解析和處理HTML/XML? 您如何在PHP中解析和處理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

php程序在字符串中計數元音 php程序在字符串中計數元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? 什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

PHP和Python:比較兩種流行的編程語言 PHP和Python:比較兩種流行的編程語言 Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

See all articles