最近、近くにいる人を見つけるというビジネスシナリオがあったので、関連情報を確認し、PHP の使用方法を検討しました。関連する機能を実装します。さまざまなメソッドと具体的な実装に関する技術的な概要です。コメントや修正は大歓迎です。さあ、本題に入りましょう:
#LBS (位置情報サービス)
近くにいる人を見つけることは、より広い意味でLBS(location-based service)と呼ばれます.LBSとは、通信事業者の無線通信網や外部測位手段を通じて携帯端末利用者の位置情報を取得することを指します.GISプラットフォームのサポートにより、これは、ユーザーに対応するサービスを提供する付加価値サービスです。したがって、ユーザーの位置を最初に取得する必要があります。ユーザーの位置は、GPS、オペレーター基地局、WIFI などを通じて取得できます。通常、クライアントはユーザーの位置の経度と緯度の座標を取得し、アプリケーション サーバーにアップロードします。アプリケーション サーバーはユーザーの座標を保存し、クライアントは近くの人々のデータを取得すると、データベースにアクセスして、要求者の地理的位置と特定の条件 (距離、性別、活動時間など) に基づいてフィルター処理および並べ替えを行います。
経度と緯度に基づいて 2 点間の距離を見つけるにはどうすればよいですか?
平面座標上の 2 点の座標は平面座標距離公式を使用して計算できることは誰もが知っていますが、経度と緯度は 3 次元空間の球面を使用した球座標系です。地球上の空間を定義します。地球が正球であると仮定します。球面距離を計算する式は次のとおりです。
具体的なことに興味がある場合は、推論プロセスについては、この記事をお勧めします。 [数式と導出]経度と緯度に基づいて地面と地面の間の距離を計算します。点間の距離は
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 地理位置機能を提供します。これは 2 つの位置間の距離を取得するだけでなく、指定された位置範囲内の地理情報位置コレクションも取得できます。 Redis コマンド ドキュメント
1. 地理的位置の追加
GEOADD key longitude latitude member [longitude latitude member ...]
2. 地理的位置の取得
GEOPOS key member [member ...]
3. 2 つの地理的位置間の距離の取得
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 コマンドを使用する場合の検索クエリのページング関連の制限とスキップ パラメーターをサポートしていません
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 = 'INSERT INTO `markers`(`uin`, `lon`, `lat`) VALUES (?, ?, ?)'; $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[':lat'] = $lat; $input[':lon'] = $lon; if ($where) { $sqlWhere = ' WHERE '; foreach ($where as $key => $value) { $sqlWhere .= "`{$key}` = :{$key} ,"; $input[":{$key}"] = $value; } $sql .= rtrim($sqlWhere, ','); } if ($maxDistance) { $sqlHaving = " HAVING distance < :maxDistance"; $sql .= $sqlHaving; $input[':maxDistance'] = $maxDistance; } $sql .= ' ORDER BY distance'; 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 拡張機能をインストールするか、コンポーザーを介して predis クラス ライブラリをインストールできます。この記事では、redis 拡張機能を使用して実装します。
メンバー追加方法:
public function geoAdd($uin, $lon, $lat) { $redis = $this->getRedis(); $redis->geoAdd('markers', $lon, $lat, $uin); return true; }
近くの人にクエリ (クエリ条件とページングはサポートされていません):
public function geoNearFind($uin, $maxDistance = 0, $unit = 'km') { $redis = $this->getRedis(); $options = ['WITHDIST']; //显示距离 $list = $redis->geoRadiusByMember('markers', $uin, $maxDistance, $unit, $options); return $list; }
3. MongoDB に基づく
PHP は次のように使用します。 MongoDB 拡張機能には mongo(Documentation) と mongodb(Documentation) があり、この 2 つの記述方法は大きく異なります。適切な拡張機能には対応する拡張機能が必要です ドキュメントを参照 mongodb 拡張機能は新しいバージョンであるため、この記事では mongodb 拡張機能を選択します。
db ライブラリと場所のコレクションを作成するとします。
インデックスの設定:
db.getCollection('location').ensureIndex({"uin":1},{"unique":true}) db.getCollection('location').ensureIndex({loc:"2d"}) #若查询位置附带查询,可以将常查询条件添加至组合索引 #db.getCollection('location').ensureIndex({loc:"2d",uin:1})
メンバー追加メソッド:
public function geoAdd($uin, $lon, $lat) { $document = array( 'uin' => $uin, 'loc' => array( 'lon' => $lon, 'lat' => $lat, ), ); $bulk = new MongoDB\Driver\BulkWrite; $bulk->update( ['uin' => $uin], $document, [ 'upsert' => 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('db.location', $bulk, $writeConcern); if ($result->getWriteErrors()) { return false; } return true; }
近くの人々にクエリを実行します (何もせずに結果を返します)距離、クエリ条件をサポート、ページングをサポート)
public function geoNearFind($lon, $lat, $maxDistance = 0, $where = array(), $page = 0) { $filter = array( 'loc' => array( '$near' => array($lon, $lat), ), ); if ($maxDistance) { $filter['loc']['$maxDistance'] = $maxDistance; } if ($where) { $filter = array_merge($filter, $where); } $options = array(); if ($page) { $page > 1 ? $skip = ($page - 1) * $this->pageCount : $skip = 0; $options = [ 'limit' => $this->pageCount, 'skip' => $skip ]; } $query = new MongoDB\Driver\Query($filter, $options); $manager = $this->getMongoManager(); $cursor = $manager->executeQuery('db.location', $query); $list = $cursor->toArray(); return $list; }
近くの人々をクエリ (距離を含む結果を返します、クエリ条件、支払い返品数量をサポート、ページングはサポートしていません):
public function geoNearFindReturnDistance($lon, $lat, $maxDistance = 0, $where = array(), $num = 0) { $params = array( 'geoNear' => "location", 'near' => array($lon, $lat), 'spherical' => true, // spherical设为false(默认),dis的单位与坐标的单位保持一致,spherical设为true,dis的单位是弧度 'distanceMultiplier' => 6371, // 计算成公里,坐标单位distanceMultiplier: 111。 弧度单位 distanceMultiplier: 6371 ); if ($maxDistance) { $params['maxDistance'] = $maxDistance; } if ($num) { $params['num'] = $num; } if ($where) { $params['query'] = $where; } $command = new MongoDB\Driver\Command($params); $manager = $this->getMongoManager(); $cursor = $manager->executeCommand('db', $command); $response = (array) $cursor->toArray()[0]; $list = $response['results']; return $list; }
注:
1. 適切な拡張機能を選択する mongo 拡張機能と mongodb 拡張機能の記述方法は大きく異なります
2. データ書き込み時に noreply が表示される場合は、書き込み確認レベルを確認してください
3. find を使用してクエリされたデータは距離を自分で計算する必要があり、geoNear を使用してクエリされたデータはページングをサポートしていません
4. geoNear によってクエリされた距離は、spherical パラメーターと distanceMultiplier パラメーターを使用して km に変換する必要があります
上記のデモはここをクリックできます: demo
上記の3種類を紹介します 近くにいる人にクエリを実行する機能を実装するメソッドです 各メソッドには適用可能なシナリオがあります たとえばデータ行が比較的少ない場合 たとえばクエリは Mysql で十分ですユーザーと複数の都市の間の距離をリアルタイムで迅速に応答する必要がある場合、一般的に範囲内の距離を求めるには Redis を使用できますが、データ量が多く、複数の属性フィルター条件がある場合は、Redis を使用できます。上記はあくまで提案であり、具体的な導入計画は具体的な業務に応じて検討する必要があります。
以上がPHP を使用して近くにいる探している人を見つける方法を教えますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。