附近的人实现原理详细剖析!

附近的人实现原理详细剖析!

在微信的最早的版本里,有“附近的人”,“摇一摇”,“漂流瓶”等功能,因为在那个时候,微信还倾向于陌生人社交,是微信当时的招牌功能。后来微信逐渐发展为熟人社交的软件,很多人根本不加陌生人,这些功能大家就不太用了,朋友圈大家再也不敢乱发内容了,晒东西给熟人看其实是没有多少动力的。大家更加倾向于去那些为特定群体服务的软件中聊天交友,晒心得,晒心情,晒照片,晒视频,大家更喜欢和有着同样性格爱好追求的朋友在一个空间内去交流,这样比较聊的来,和陌生人聊天也比较有意思,比较新奇,比较刺激,那些功能没有没落,只是到别的软件中发扬光大去了。尤其是“附近的人”这个功能,很多小众App现在都有这个功能,例如约会,宠物交流,跑步健身,同性交友,养生保健,像这些内容,现实对人的吸引力会更大,交流从线上到线下,从网络到现实,会带来更好的体验感受。

要实现附近的人这个功能,我们要经历以下几个环节:

  1. 用户定时上传自己的定位信息,并存到服务端的数据库中;
  2. 用户发起查找请求,服务端根据用户提供的定位信息去数据库中查找与他的经度纬度海拔最接近的其他用户的定位信息。
  3. 服务端通过两个定位信息就能算出距离,按照远近排好序后,连同对应的用户账户,昵称,头像等信息一同发给查找的户。

用户的手机在连上GPS后,每隔一定时间就会收到一定格式的数据,数据格式为:$信息类型,x,x,x,x,x,x,x,x,x,x,x,x,x每行开头的字符都是$,接着是信息类型,后面是数据,以逗号分隔开。 一行完整的数据如下:
$GPRMC,080655.00,A,4546.40891,N,12639.65641,E,1.045,328.42,170809,A*60
信息类型分为以下几种:GPGSV(可见卫星信息),GPGLL(地理定位信息),GPRMC(推荐最小定位信息),GPVTG(地面速度信息),GPGGA(GPS定位信息),GPGSA(当前卫星信息),下边是对几个主要信息的描述。
我们最常用的是GPRMC,也就是最小定位信息,数据格式为:

$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh
<1> UTC 时间,hhmmss(时分秒)格式,这个是格林威治时间,是世界时间(UTC),我们需要把它转换成北京时间(BTC),BTC和UTC差了8个小时,要在这个时间基础上加8个小时;
<2> 定位状态,A=有效定位,V=无效定位,在接收到有效数据前,这个位是‘V’,后面的数据都为空,接到有效数据后,这个位是‘A’,后面才开始有数据;
<3> 纬度ddmm.mmmm(度分)格式(前面的0也将被传输) ,我们需要把它转换成度分秒的格式,计算方法:如接收到的纬度是:4546.40891 ,4546.40891/100=45.4640891可以直接读出45度, 4546.40891–45*100=46.40891, 可以直接读出46分,46.40891–46 =0.40891*60=24.5346读出24秒, 所以纬度是:45度46分24秒;
<4> 纬度半球N(北半球)或S(南半球) ,这个位有两种值‘N’(北纬)和‘S’(南纬)。
<5> 经度dddmm.mmmm(度分)格式(前面的0也将被传输) ,经度的计算方法和纬度的计算方法一样;
<6> 经度半球E(东经)或W(西经) ,这个位有两种值‘E’(东经)和‘W’(西经);
<7> 地面速率(000.0~999.9节,前面的0也将被传输) ,这个速率值是海里/时,单位是节,要把它转换成千米/时,根据:1海里=1.85公里,把得到的速率乘以1.85;
<8> 地面航向(000.0~359.9度,以真北为参考基准,前面的0也将被传输) ,指的是偏离正北的角度;
<9> UTC 日期,ddmmyy(日月年)格式 ,这个日期是准确的,不需要转换;
<10> 磁偏角(000.0~180.0度,前面的0也将被传输) ;
<11> 磁偏角方向,E(东)或W(西) ;
<12>模式指示(仅NMEA01833.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效) 。

GPS定位数据GPGGA,数据格式为:

$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*xx<CR><LF>
<1> UTC 时间,格式为hhmmss.sss; 
<2> 纬度,格式为ddmm.mmmm(第一位是零也将传送); 
<3> 纬度半球,N 或S(北纬或南纬);
<4> 经度,格式为dddmm.mmmm(第一位零也将传送); 
<5> 经度半球,E 或W(东经或西经);
<6> 定位质量指示,0=定位无效,1=定位有效; 
<7> 使用卫星数量,从00到12(第一个零也将传送);
<8> 水平精确度,0.5到99.9;
<9> 天线离海平面的高度,-9999.9到9999.9米M指单位米;
<10> 大地水准面高度,-9999.9到9999.9米M指单位米; 
<11> 差分GPS数据期限(RTCMSC-104),最后设立RTCM传送的秒数量;
<12> 差分参考基站标号,从0000到1023(首位0也将传送)。

地面速度信息GPVTG,数据格式为:

$GPVTG,<1>,T,<2>,M,<3>,N,<4>,K,<5>*hh
<1> 以正北为参考基准的地面航向(000~359度,前面的0也将被传输);
<2> 以磁北为参考基准的地面航向(000~359度,前面的0也将被传输);
<3> 地面速率(000.0~999.9节,前面的0也将被传输);
<4> 地面速率(0000.0~1851.8公里/小时,前面的0也将被传输);
<5> 模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效。

有了这些定位信息,我们就能精确的找到每个用户现在的位置。但是由于用户的数据量可能很大,例如有1000万,1个亿,如果拿你的定位信息同其他所有用户的定位信息整个对比一次,会耗费很长的时间和计算资源,无法支持大量用户同时使用这项功能,在这里我们推荐使用比较容易搭建的而且可以快速上手的矩形算法方案。

矩形算法方案

就是以用户输入的经度和维度为中心和查询距离构建一个正方形,然后从数据库直接筛选掉不在这个正方形中的经纬度点,然后计算剩下的点与中心点的距离,把超出距离的四个角的点抛弃掉,这个算法的速度非常快。
附近的人实现原理详细剖析!
附近的人实现原理详细剖析!
当用户开始查找时,会先输入一个较小的距离,例如100米,300米,当用户列表向下滑动时,就会把这个距离不断放大,从1千米,2千米到10千米,这样就能搜索到更多附近的人了。

GeoHash方法

GeoHash是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串,GeoHash用一个字符串表示经度和纬度两个坐标。在数据库中可以实现在一列上应用索引。GeoHash表示的并不是一个点,而是一个矩形区域。GeoHash编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。
1、将经纬度转换成二进制:比如这样一个点(39.928167, 116.389550),地球纬度区 间是(-90,90),其中间值为0。对于纬度39.928167,在区间(0,90)中,因此得 到一个1;(0,90)区间的中间值为45度,纬度39.923201小于45,因此得到一个0, 依次计算下去,即可得到纬度的二进制表示。如下图:
根据纬度算编码

bit min mid max
1 -90.000 0.000 90.000
0 0.000 45.000 90.000
1 0.000 22.500 45.000
1 22.500 33.750 45.000
1 33.7500 39.375 45.000
0 39.375 42.188 45.000
0 39.375 40.7815 42.188
0 39.375 40.07825 40.7815
1 39.375 39.726625 40.07825
1 39.726625 39.9024375 40.07825
2、合并纬度、经度的二进制:通过上述计算,纬度产生的编码为10111 00011,同理 经度产生的编码为11010 01011。偶数位放经度,奇数位放纬度,把2串编码组合生成 新串: 11100 11101 00100 01111。
3、按照Base32进行编码:将上述合并后二进制编码后结果为 “wx4g”。
4、然后就可以根据类似SELECT * FROM table WHERE geohash LIKE ‘wx4g%’;这样的sql语句去数据库进行模糊查询。
5、将查询到的字符串用base32进行解码,重新获取到经度纬度点。

Base32编码长度与距离
GeoHash distance
Length km
1 ±2500
2 ±630
3 ±78
4 ±20
5 ±2.4
6 ±0.61
7 ±0.076
8 ±0.019
9 ±0.00478
10 ±0.0005971
11 ±0.0001492
12 ±0.0000186

用GeoHash算法的优点:1.可以保护用户的隐私,由于GeoHash的每一个字符串代表了某一个矩形区域,就是指这个矩形中的所有的点都公用一个GeoHash字符串,一个GeoHash字符串只会表示大概区域;2.可以做缓存,如果用户不断发送查询附近的人的请求,那么我们就可以把这个字符串当key,把附近的人的信息当做value存储起来。