首先,这是一篇学习笔记,来源于极客时代陈东先生的《检索核心技术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)的位置进行编码:
如果我们用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过滤器与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
查询和排序
我们到此为止吧。