问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

如何实现想微信 查找附近的人的功能,精确到几百米都有的,主要问算法之类的?

发布网友 发布时间:2022-04-06 00:07

我来回答

2个回答

懂视网 时间:2022-04-06 04:28

最近有个业务场景使用到了查找附近的人,于是查阅了相关资料,并对使用PHP实现相关功能的多种方式和具体实现做一篇技术总结,欢迎各位看官提出意见和纠错,下面开始进入正题:

LBS(基于位置的服务)

查找附近的人有个更大的专有名词叫做LBS(基于位置的服务),LBS是指是指通过电信移动运营商的无线电通讯网络或外部定位方式,获取移动终端用户的位置信息,在GIS平台的支持下,为用户提供相应服务的一种增值业务。因此首先得获取用户的位置,获取用户的位置有基于GPS、基于运营商基站、WIFI等方式,一般由客户端获取用户位置的经纬度坐标上传至应用服务器,应用服务器对用户坐标进行保存,客户端获取附近的人数据的时候,应用服务器基于请求人的地理位置配合一定的条件(距离,性别,活跃时间等)去数据库进行筛选和排序。

根据经纬度如何得出两点之间的距离?

我们都知道平面坐标内的两点坐标可以使用平面坐标距离公式来计算,但经纬度是利用三度空间的球面来定义地球上的空间的球面坐标系统,假定地球是正球体,关于球面距离计算公式如下:

ca23df6f88dbe0b3de7461ae28ac6bd.png

具体推断过程有兴趣的推荐这篇文章:【数学公式及推导】根据经纬度计算地面两点间的距离

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 = '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扩展或者通过composer安装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(文档)和mongodb(文档),两者写法差别很大,选择好扩展需要对应相应的文档查看,由于mongodb扩展是新版,本文选择mongodb扩展。

假设我们创建db库和location集合

设置索引:

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 MongoDBDriverBulkWrite;
 $bulk->update(
 ['uin' => $uin],
 $document,
 [ 'upsert' => true]
 );
 //出现noreply 可以改成确认式写入
 $manager = $this->getMongoManager();
 $writeConcern = new MongoDBDriverWriteConcern(1, 100);
 //$writeConcern = new MongoDBDriverWriteConcern(MongoDBDriverWriteConcern::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 MongoDBDriverQuery($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 MongoDBDriverCommand($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查询的距离需要转化成km使用spherical和distanceMultiplier参数

上述demo可以戳这里:demo

总结

以上介绍了三种方式去实现查询附近的人的功能,各种方式都有各自的适用场景,比如数据行比较少,例如查询用户和几座城市之间的距离使用Mysql就足够了,如果需要实时快速响应并且普通查找范围内的距离,可以使用Redis,但如果数据量大并且多种属性筛选条件,使用mongo会更方便,以上只是建议,具体实现方案还要视具体业务去进行方案评审。

热心网友 时间:2022-04-06 01:36

很简单。利用基站定位快速得到经纬度的大概值,上传服务器,保存,记录你在这里出现过,并寻找服务器里已有的回显
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
初一语文同步练习册68页第3题的作文! 是什么? 高一语文68页续写作文, 很久以前,在很远的地方,有个老头儿………_百度... 语文必修一68页写作练习的作文!800字、急求!。 涉嫌强奸罪但证据不足最长拘留多久 嗓子疼可以喝柠檬吗 喝柠檬水上火么?网上都说不上火,可为什么喝完柠檬水一觉起来嗓子... 三清茶功效原理 瑶寨三清茶 三清茶如何 如何判断三清茶的品质? 如何实现类似微信中查找附近的人功能 如何实现查找附近的人 php怎么做附近的人功能? python输出一个九九乘法表,输出结果,怎么才能相同乘数在一行。实现的代码和结果如下 python做九九乘法表的思路是什么 Python用for循环打印反向九九乘法表 怎么用python代码输出乘法表? python输出九九乘法表倒序? python中for循环前面换行最后不换行 如何获取目录下的文件数(一行Python) 如何使用python flask遍历一个路径中的所有文件 python怎么遍历一个目录下的所有目录 Wegame更新游戏网速固定在900K到1M之间,正常吗?不能再快了吗? 如何用PHP给上传的文件改名 用php批量修改并替换文件名如何实现? php如何修改文件夹的名称 php如何批量修改文件权限,文件后缀,文件名 php 如何批量下载文件并给每个文件重命名的写法? php如何批量修改某个文件夹下所有文件名的方法 谷歌浏览器所有页面都崩溃,设置都崩溃 求一个电脑版微信查找附近的人的办法? 电脑u盘怎么使用教程 请问在电脑上要怎样使用U盘?[详细步骤]?谢谢 USB接口在电脑的哪个位子 电脑USB2.0的接口在哪里 win10怎么开vt Win10系统怎样开启VT虚拟化技术 怎么开启VT虚拟化功能? 惠普15q-aj006TX系统是WIN10的怎么开VT? macbook电脑win10打开虚拟化 为什么chrome一打开,蓝牙耳机就没声音,win10 google 浏览器突然没声音了,怎么回事 MAC上 google chrome浏览器没有声音了怎么办 谷歌浏览器没声音了,怎么解决? 谷歌浏览器没声音怎么解决 google浏览器没有声音以前都正常啊,最近突然放视频突然没有了声音,用其他浏览器都行,就是谷歌不行 只有浏览器GOOGLE CHROME看视频没声音,其他浏览器都正常,只想用这个浏览器。 为什么用浏览器Google Chrome看视频没声音? 谷歌浏览器看电影没有声音怎么调 Chrome浏览器播放视频/音频没有声音怎么解决?