微信附近的人在哪里找不到(空间搜索如何求附近的人)

首先,这是一篇学习笔记,来源于极客时代陈东先生的《检索核心技术20讲》。这很好。处理一些问题的算法经常让我觉得耳目一新。本次聊天中的空 inter-search算法就是其中之一。

一 空间搜索的需求

当我们使用地图时,我们经常使用地图来搜索附近的酒店、附近的食品店甚至附近的WC。也就是说,我认为酒店的位置是固定的。只需取搜索区域内酒店的经纬度和我们所在位置的经纬度计算距离,依次计算不同酒店的位置,然后进行排序,得到我们附近酒店的前N名。

如果我们在用微信,一定要熟悉附近的人的功能,那么我们怎么计算附近的人呢?这个和酒店的区别是人是流动的,计算起来很麻烦。如果我们在网上的人和我们现在的人之间做一对一的位置计算,然后进行排序,显然不够清晰。虽然我们不知道到底多远的附近才叫做附近,但是一般来说,不同的城市是不能算附近的(当然,边界可能更近,后面再说),所以我们在计算距离的时候,只需要计算同一城市的附近的人,这样就可以大大减少计算量。

再往深了说,如果我们周围的区域更小,就可以减少一个区的在线用户与我们所在位置的距离。其实像这样的计算,我们只需要取最近的前n位,有时候不需要严格排序。

带着缩小面积的想法,我们来看看如何更快更合适的计算距离。

二 区域编码

为了计算距离,我们通常用经纬度来表示位置。我们需要把计算出来的空分成不同的区域,每个区域用一个代码来标识。比如我们把一个城市按照经纬度分为四个区域,分别用代码00,01,11,10表示。如果我们继续将它们划分如下:



如果需要更精确的距离,可以进一步划分,编码位数变成4,更精确。用这种编码方式划分,相同区域的前缀码是一样的,有利于我们做区域查询,比如在一个区域找不到的时候,可以再扩大范围。

2.1就近解决查询错误问题

前面说了,如果计算附近的人,我们按照一个区域的人来搜索,减少了计算量,但是还有一个问题,就是这个区域的人可能和我们不近,邻近区域的人可能更近,如下图所示:



描述:上图中绿色三角符号表示我的位置,绿色圆圈和绿色三角符号属于同一区域,但相邻区域更近。

对于这个问题,我们假设我们认为的附近区域是10km。如果我们只在这个区域搜索,显然我们可能会错过更近的距离。我们的区域扩大了一倍,从搜索一个区域到搜索九个区域,增加了八个附近的区域,这样就不会有遗漏。

2.2 Geohash 编码

刚才我们按照自己的理解对区域进行了编码。如果我们把地球看成一个大的二维空间空,经度明显是水平的,维度是垂直的,地球的经度范围是[-180,180],维度的区间是[-90,90];我们假设(经度:104.07纬度:30.67)的位置进行编码:

  • 在经度方向,104.07经度在0到180之间,我们将空的右半部分编码为1,在180度范围内继续一分为二,104.07经度在90度到180度之间,继续编码为1,所以在经度上编码为11。
  • 维度方向,30.67维度在0-90范围内编码为1,继续划分,30.67维度在0-45范围内编码为0,所以维度编码为10。
  • 综合来看,上述位置的代码为:1110(先经度,再维度)。我们总共只用4位编码,显然粒度很粗。如果要细化,用更多的位数来表示,我们就用这种编码方式把整个空二维码转换成一维数,这样计算和操作就更容易了。
  • 如果我们用15位表示经度,用15位表示维度,那么和就是30位,编码很长。最后我们用0-9和b-z这32个字母(不包括a,I,l,o)对01串进行base32编码,将5位转换成一个字母和一个数字,形成一个相似度:& # 34;w3qcef & # 34,以便将30位代码转换为6位字符串。这种用数字和字符来表示位置的编码方式被称为GeoHash编码。



    9位Geohash码是45位,也就是可以精确到4.8m,10位geohash码可以精确到1m左右。网上有对应的表格,可以根据我们的精度要求选择Geohash编码的位数。

    * *缺点:* *由**Geohash编码的一个字符或数字表示维度。可能我们在使用的时候,比如需要3米精度的时候,9位数的精度不够,10位数的精度太精确了,用不上。这时候就需要改变编码方式或者直接使用原来的01字符串。

    三 实践

    常见的全文搜索引擎,如Solr或es,都支持位置搜索。以solr为例,Solr支持多种距离场定义,常见配置如下:

    & lt!- solr4.0版本4.0或更高版本有一个默认字段类型定义,可能略有不同-& gt; & lt;fieldType name = & # 34位置& # 34;class = & # 34索尔。LatLonPointSpatialField & # 34docValues = & # 34真& # 34;/& gt; & lt;fieldType name = & # 34location _ rpt & # 34class = & # 34索尔。spatialrecursiveprefixtreefield type & # 34;geo = & # 34真& # 34;/& gt; 创建一些新的测试文档:

    & lt添加& gt & lt;doc & gt & lt;字段名= & # 34;id & # 34& gt001 & lt/field & gt; & lt;字段名= & # 34;city _ s & # 34& gt成都



    Solr支持两种查询分析器,其实也差不多:geofit查询分析器和bbox查询分析器。

    3.1 Geofit查询分析器查询附近数据

    Geofit filter可以根据geography 空之间的距离,从给定点做一个环形过滤,查询5km以内的数据。该命令如下所示:

    fq={!geofiltsfield = location _ p pt = 30.67,104.05d = 1000} sfield:存储位置的字段名称。Pt:初始位置d:附近距离,单位公里结果显示:



    Geofit使用两个步骤来获得结果:

  • 根据精度要求,创建一个正方形边框,正方形的边长就是搜索距离,通过这个正方形过滤文档。
  • 计算位置与边框内中心的距离,然后排序,以距离为圆筛选出圆外的数据。但是如果数据量很大,那么就不需要精确的循环过滤,可以使用bbox inquirer。
  • 3.2 bbox查询分析器查询附近数据

    Bbox过滤器与geofit非常相似,只要它使用要计算的圆的边界框,它就采用与geofit相同的参数:

    微信附近的人在哪里找

    fq= {!Oxfield = location _ p pt = 30.67,104.05d = 1000} 因为没有精准过滤,所以速度比较快,就不贴了。

    3.3 距离排序

    根据刚才的查询结果,最近的排在第一位,那么有没有办法计算这个距离呢?solr中有相关函数,支持排序:

    fl = id,city_s,distance:geodist(location_p,30.67,104.05) & sort=geodist(location_p,30.67,104.05)asc,score desc


    fl = id,city_s,distance:geodist(location_p,30.67,104.05)& sort = geodist(location _ p,30.67,104.05)asc,score desc

    查询和排序

    我们到此为止吧。

    您可以还会对下面的文章感兴趣

    使用微信扫描二维码后

    点击右上角发送给好友