空间搜索如何求附近的人

首先声明下,这是一篇学习笔记,来自极客时间陈东老师《检索核心技术20讲》,讲的很不错,有些问题的处理算法常常让我感觉耳目一新,这次聊的空间搜索算法就是其中之一。

一 空间搜索的需求

我们在使用地图的时候,常常会使用地图搜索附近的酒店,附近的美食店,甚至附近的WC等功能。这些还好说,以为酒店这些的位置是固定的,只要拿搜索区域内酒店的经纬度和我们所在位置经纬度做计算,计算出距离,依次计算不同酒店的位置,然后再做个排序就得出了我们附近酒店排行TOP N。

我们在用微信的话,想必对附近的人的功能,并不陌生,那么我们如何计算我们附近的人,这里面和酒店的差别是,人员是流动的,计算起来很麻烦。如果拿在线的人和我们现在的人做一对一的位置计算,再进行排序,显然不够明知,虽然我们不知道附近到底是多远的距离叫附近,但是一般来说不同的城市不能算附近(当然边界上可能更近,后面再说),所以我们在计算距离的时候,只要计算同城的附近人就可以了,这样我们可以很大的减少计算量。

再深入一下,如果我们附近的范围更小点,我们可以减少到计算一个区的在线用户和我们位置的距离,其实像这种计算,我们只要取最近的top n就可以了,有时候没有必要严格排序。

有了缩小区域的思路,我们就来看下如何来求计算距离能更快速,更合适。

二 区域编码

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


区域编码

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

2.1 解决附近查询误差的问题

前文有说,如果我们计算附近的人的时候,按照一个区域内的人去搜索,减少了计算的量,但是还有个问题,就是区域内的人,也可能不是离我们近的人,相临区域的人有可能距离更近,如下图:


附近人问题

说明,在上图中,绿色的三角符号表示本人位置,绿色的圆圈和绿色三角符号属于同一个区域,但是距离更近的反而是相邻的区域。

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

2.2 Geohash 编码

刚才是我们按照自己的理解去进行区域编码,如果我们将地球看作一个大的二维空间,经度显然是水平方向,维度就是垂直方向,地球的经度范围是[-180,180] ,维度的区间[-90,90]; 假设以(经度:104.07 纬度:30.67)这个位置来进行编码:

  1. 经度方向上,104.07 经度在0-180之间,我们将空间右半部分编码为1,在180度的范围继续一分为二,104.07在90度到180之间,继续编码为1,所以在经度上编码为:11。
  2. 维度方向上,30.67维度在0-90范围编码为1,继续划分,30.67在0-45范围,继续编码为0,故此在维度上的编码为:10
  3. 综合来看上述位置的编码为:1110 (先经度后维度)。
    我们一共才用4位编码,所以显然粒度很粗,如果想精细化,用更多位数来表示,我们就通过这种编码的方式,将整个空间的二维的编码转成了一维的数字,计算操作起来更简单了。

如果我们将经度用15个bit来表示,维度位15个bit来表示,那么和起来就是30个bit,编码很长,我们最后使用用0-9、b-z(去掉a, i, l, o)这32个字母,对01串进行base32编码编码的方式,将5个bit转成一个字母和数字就形成类似:"w3qcef",这样30位编码就转成了6位字符串。这种用数字和字符来表示位置的编码方式叫GeoHash 编码。


base32编码 图片来自互联网

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

缺点:Geohash编码1个字符或数字就表示经维度,可能我们用的时候,比如我们需要精度为3米的时候,采用9位的话精度不够,采用10位的精度又太过精确用不到。这时候需要换编码方式或者直接采用原始的01串表示。

三 实践

常用的全文搜索引擎,比如Solr或ES都支持位置搜索,以Solr为例,solr支持多种距离字段定义,常用的配置如下:

<!-- solr4.0以上版本有默认的字段类型定义 可能有细微差别-->
<fieldType name="location" class="solr.LatLonPointSpatialField" docValues="true"/>
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType" geo="true"  />

新建些测试文档:

<add>
  <doc> 
      <field name = "id">001</field> 
      <field name = "city_s">成都</field> 
      <field name = "location_p">30.67,104.07</field> 
   </doc>  
   <doc> 
      <field name = "id">002</field> 
      <field name = "city_s">南京</field>       
      <field name = "location_p">32.07,118.78 </field> 
   </doc>  
   <doc> 
      <field name = "id">003</field> 
      <field name = "city_s">重庆</field> 
      <field name = "location_p">29.57,106.56</field> 
   </doc>  
    <doc> 
      <field name = "id">004</field> 
      <field name = "city_s">西安</field> 
      <field name = "location_p">34.27,108.93</field> 
   </doc> 
</add>

此处采用动态字段形式,定义city_s即为string类型,location_p为location类型。
查询下结果如下:


结果验证

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

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

geofit过滤器可以根据地理空间距离从给定点做一个圆形的过滤,查询5公里范围内的数据,命令如下:

fq={!geofilt sfield=location_p  pt=30.67,104.05 d=1000}

sfield: 保存位置的字段名。
pt :初始位置
d: 附近距离,单位千米
结果展示:


查询结果

Geofit采用两步求结果:

  1. 按照精度要求,创建一个正方形边框,正方形的边长为搜索的距离,通过这个正方形过滤文档。
  2. 计算边框内的位置和中心的距离,然后进行排序,且要过滤掉以距离为圆的圆外数据。
    但是如果数据量很大,那么精确的圆过滤没有必要,那就可以采用bbox查询器。


    Geofit计算

3.2 bbox查询分析器查询附近数据

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

fq= {!bbox sfield=location_p  pt=30.67,104.05 d=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
查询就且排序

就说到这里吧。

四 共享诗词

# 江城子·前瞻马耳九仙山

[宋] [苏轼]

前瞻马耳九仙山。
碧连天。晚云间。
城上高台,真个是超然。
莫使匆匆云雨散,今夜里,月婵娟。
小溪鸥鹭静联拳。去翩翩。点轻烟。
人事凄凉,回首便他年。
莫忘使君歌笑处,垂柳下,矮槐前。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270