引言
在地理信息中,我们经常要做一个范围搜索,这个往往也是性能的瓶颈,今天我们就来介绍基于geohash的临近搜索。
geohash
首先我们来介绍什么是geohash,geohash简单来说是一种地理编码,就是你给我输入一个经纬度,我给你一个字符串编码。
我们可以简单思考一下,如果让你做范围搜索,你怎么来做索引呢?如果用树的思路,一定是不断的切分现有的空间域,从而加快搜索速度,geohash也是同样的道理。基于的实事就是我们的纬度的范围是(-90,90),同理经度也是指定范围内的数字而已,所以我们基于这个固定的范围不断做切分就好了。下面我们来看看过程
编码过程
我们来编码点(39.923201, 116.390705)
经度范围 | 划分区域(1) | 划分区域(0) | 39.923201 |
(-90,90) | (-90,0) | (0,90) | 1 |
(0,90) | (0,45) | (45,90) | 0 |
... | ... | ... | ... |
最后得到纬度的二进制表示为:
10111000110001111001
经度的编码
11010010110001000100
然后让我们将经纬度合并,经度占偶数位,纬度占奇数位,注意,0也是偶数位。
11100 11101 00100 01111 00000 01101 01011 00001
按照Base32进行编码
wx4g0ec1
这样我们将一个经纬度表达,变成一个字符串表达啦,这个时候你可以还是有疑问,这个和范围搜索有啥问题。
可以设想一下,我们将地图上所有的地标经纬度,全部编码成指定位数的geohash,那么使用geohash我们是不是就能很快找到,这个分块内的地标呢?
geohash的位数
从上面的geohash的计算过程,我知道,其实我们能把这个geohash编码成任意精度,那么这个位数和精度的关系是是什么呢?
这个图有比较好的说明效果,假如我们编码geohash8位,其实我们是能搜索附近19m的对象。
geohash的缺点
你可能已经感受到了,geohash是一个固定的编码,也就是这个编码是一个矩形,我如果编码8位geohash,其实我是搜索我所在的矩形的附近19m的对象,并不是真正的19m对象。对的,这个也是geohash的缺点,边界bad case频发,那么如何来解决这个问题呢?
其实我们一般是通过目标节点的geohash,获取周边8个块内的对象一起搜索。
这张图就是我们说的搜索范围,怎样保证你搜索的对象都不会少。
项目落地
说了这么多这个geohash在业务中我们怎么使用呢?
场景1
微信的附近的人是一个比较好场景,我们可以根据精度搜索周边上线的用户,开始聊天。
MM匹配
本博客上篇文章讲到MM(map match)算法,有一个过程就是我们要搜索目标点周边的link数据,这个就是很好的geohash的场景。
python使用
python中我们一般使用python-geohash的第三方包来解决geohash的算法。
import geohash
code = geohash.encode(longitude=116.390705,latitude=39.923201, precision=8)
neighbors = geohash.expand(code) # 获取包括自己周边9个块
总结
geohash在工程是比较常用的基于范围的索引方式,实际应用我们可以把点编码好放到redis中,从而搜索也十分高效。从另一个角度来讲,geohash也是一个典型的二分思想,类似于构建一个树,然后在树上搜索。