
1前言
在微信轻触【发现】->【附近的人】,便可以查看距离自己较近的人,这个神奇的功能让陌生人能成为朋友、饭友、聊友、约友等。但是这个功能的技术实现原理又是什么呢?
最简单的想法是计算自己和每个好友间的距离,然后按距离排序并输出。这种想法对于用户数较少的应用尚可,但对于微信这种有上亿用户数的应用,显然是不可取的。事实上,不管用户数多少,只要点击【附近的人】基本上几秒钟之内,就能把附近的用户筛选出来。这么快的速度是用了什么奇淫巧技呢?本文我们一起来探究下。
2原理
首先需要获取自己和好友的位置信息,这涉及到LBS(Location Based Service,基于位置的服务),就是通过移动终端获取到用户或者物体的经纬度坐标,通过这些位置信息来提供服务。
其次需要对这些位置信息进行网格化处理。把整个地球想象成经纬度构成的网络,每一个网格包含一定的区域,只要这个网格划分足够精细,便可以用来逼近我们所处的位置了。当某个用户都被划分在某个网格中时,通过查询自己所处网格或周边网格内的用户,便可以找到自己附近的人。
所以需要记录下网格的名称,以便于查询用户附近的人。记录网格名称的过程涉及到GeoHash算法。
当网格划分结束(即每个网格都确定了名称),那么接下来便是根据网格名称来查询用户自己的附近的人。本文用到了字典树来实现这个查询功能。
3实现流程
本文主要介绍如何在Python中实现模拟微信《附近的人》这个功能。实现流程如下所示。

4 Geohash法
利用GeoHash算法,可以将经纬度数据转化为字符串格式,这样便将二维的数据转化为了一维,存储就方便了,搜索效率也会高很多。GeoHash算法分为编码和组码。
第一步是编码。经纬度的编码通过折半比较法实现,当大于中值时编码为1,下次新的区间为中值到最大值;当小于中值时编码为0,下次新的区间为最小值到中值。这样一直比较下去,直到到达要求的精度,经度和纬度的方法是一样的,只是纬度的原始区间是[-90,90],经度的原始区间[-180,180]。如下表所示对纬度42.61233和经度-5.61234进行编码:

这样便可将经纬度(42.61233, -5.61234)转换成二进制码(10111 10010, 01111 10000),二进制码的位数即为编码精度。
第二步是组码(或者合并)。将第一步产生的二进制码组合起来产生Base32的字符串。组码的方式是奇数位放经度、偶数位放纬度(也可互换),如下图所示:

合并后的二进制码为:01101, 11111, 11000, 00100
将二进制码转换成十进制数,得到:13, 31, 24, 4
再按照如下图所示的Base32编码转换关系(用0-9、b-z(去掉a, i, l, o)这32个字母表示),将十进制数转换为Base32的字符串编码,得到ezs4

本例中编码精度设置为10位,当继续增大精度位数时,可得到更为精确的Base32字符串。
给定如下图所示的8个用户位置信息,

可以利用Geohash算法将这些好友的经纬度信息转化为Base32的字符串编码。如下表所示:

其中,转换后的后4位为_ID,是为了区别同一位置的不同用户。
用Python实现经纬度的编码和组码,代码见附录一。
5 字典树查询法
计算出这些经纬度的字符串编码值,那么如何存放到数据库中,能够快速检索一个用户附近的好友呢?
本节使用字典树来完成字符串编码值的存储与查询。
字典树(也叫Trie树),是一种 N 叉树,也是一种特殊的前缀树结构。通常来说,一个前缀树是用来存储字符串的。前缀树的每一个节点代表一个字符。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符是由节点本身的原始字符,以及通往该子节点路径上所有的字符组成的。
前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。
将上述各个用户转换后的字符串编码值,存入到字典树中,如下图所示:

图中,每一条从根节点(root)到叶子结点的路径都表示一个用户的位置信息(经纬度)。
当构建完成字典树后,便可以查询离自己距离较近的用户是哪些。如当自己(ID记为109)的位置信息表示为:(42.61236, -5.61234)时,通过Geohash算法可以计算出字符串编码值为 ‘ezs42m34yzx_109’。
通过字典树查询(深度优先搜索),可以获取到离该用户最近的好友有100和107,他们的信息分别如下:

用Python实现字典树的插入与查询,代码见附录二。
6 距离计算法
根据2个经纬度点,可以利用Haversine公式计算这2个经纬度点之间的距离,计算公式如下:
其中
其中R为地球半径,可取平均值 6371km;
可以使用字典dict来存放用户自己到每个附近邻居的距离。每个用户都可以通过ID检索,于是可以把ID作为字典的键,把距离作为字典的值。
然后通过快速排序方法对字典排序(如对上述ID=100和ID=107的用户按距离排序),便可以按照距离筛选出离自己较近的好友了。。。
7 参考
[1] https://blog.csdn.net/wzw212/article/details/79308798
[2] https://blog.csdn.net/qfire/article/details/83189482
[3] https://www.cnblogs.com/zhoug2020/p/8993750.html
附录一:用Python实现经纬度的编码、组码和解码
class
附录二:用Python实现字典树的插入与查询
class
感谢阅读。。。
少侠,留个印记再走呗。。。