地理空间索引:GeoHash原理

1. 基于空间位置的服务

基于位置的服务型电商席卷而来,搭乘网约车去到目的地、搜索附近的餐馆酒旅,无不让人们感觉到便捷。比如打开滴滴APP,我们看到附近的车辆如下
地理空间索引:GeoHash原理_第1张图片

那么问题来了,滴滴是怎么快速的匹配出乘客附近2公里的车辆的呢?

先讲最暴力的解法:首先获取乘客的经纬度坐标,然后和当前所有车辆的经纬度坐标计算距离,最后筛选留下距离小于两公里的车辆。这种解法计算量随着车辆规模线性增长,因此非常耗时。一种优化思路是按照经纬度坐标范围粗略筛选掉距离过远的车辆,然后在剩余的车辆里继续计算距离来进行精确筛选,但这同样需要遍历所有的车辆,仍然无法避免大量的时间开销。有没有不用遍历的解法?

上面的思考容易让我们联想到哈希散列的思路:遍历列表查找时间复杂度高,而创建哈希值散列后查找效率可以获得极大提升。尽管我们要考虑的场景是二维的,但是我们可以将经度和纬度分开来处理,也就是先后在经度和纬度上对空间进行编码,相当于在地球上打上网格,并且该网格具有层次(见下图),层次由高到低,代表的空间范围由大到小,描述的地理位置由粗到细;为了查找一个经纬度所在的网格,只需要按照网格层次由高到低进行匹配就可以了。

回到之前的问题,为了匹配乘客附近2公里的车辆,我们事先给乘客和所有车辆的经纬度坐标打上网格(期望网格恰巧就是2公里,实际不能保证这么精确),然后查找和乘客所在的网格相同的车辆即可。由于网格具有层次,于是这里的查找就可以避免遍历,而采取B树等一系列高效的算法进行实现了。

不知不觉中,我们已经在使用了GeoHash的思想开始解决问题了。

2. GeoHash基本原理

下面来正式介绍GeoHash算法。

GeoHash是一种通用的地理编码算法,是由Gustavo Niemeyer发明的,简言之,它可以将地理经纬度坐标编码为由字母和数字所构成的短字符串。它具有如下特性:

  1. 层级空间数据结构,将地理位置用矩形网格划分,同一网格内地理编码相同;
  2. 可以表示任意精度的地理位置坐标,只要编码长度足够长;
  3. 编码前缀匹配的越长,地理位置越邻近。

例如下图对北京中关村软件园附近进行6位的GeoHash编码结果,9个网格相互邻近且具有相同的前缀wx4ey。
地理空间索引:GeoHash原理_第2张图片
那么GeoHash算法是怎么对经纬度坐标进行编码的呢?总的来说,它采用的是二分法不断缩小经度和纬度的区间来进行二进制编码,最后将经纬度分别产生的编码奇偶位交叉合并,再用字母数字表示。举例来说,对于一个坐标116.29513,40.04920的经度执行算法:

  1. 将地球经度区间[-180,180]二分为[-180,0]和[0,180],116.29513在右区间,记1;
  2. 将[0,180]二分为[0,90]和[90,180],116.29513在右区间,记1;
  3. 将[90,180]二分为[90,135]和[135,180],116.29513在左区间,记0;
  4. 递归上述过程(左区间记0,右区间记1)直到所需要的精度,得到一串二进制编码11010 01010 11001

同理将地球纬度区间[-90,90]根据纬度40.04920进行递归二分得到二进制编码10111 00011 11010,接着生成新的二进制数,它的偶数位放经度,奇数位放纬度,得到11100 11101 00100 01101 11110 00110,最后使用32个数字和字母(字母去掉a、i、l、o这4个)进行32进制编码,即先将二进制数每5位转化为十进制28 29 4 13 30 6,然后对应着编码表进行映射得到wy4ey6

对这样的GeoHash编码大小排序后,是按照Z形曲线来填充空间的,后来又衍生出多种填充曲线且具有多种特性,由于没有Z形曲线简单通用,这里就不赘述了。下表是GeoHash的编码长度与网格大小以及距离精度的关系,对于我们第一节讨论的匹配附近2公里的车辆,使用编码长度为5就可以了,如果需要更精细的匹配,可以在GeoHash匹配的结果上进行进一步的距离筛选。

3. 代码实现

目前比较高效实现的代码参考python-geohash,它支持Python和C两种语言环境,其设计思路主要是用移位操作来代替二分区间,实现效率非常高。其Python代码主要接口如下:

encode(latitude, longitude, precision=12)  # 编码
decode(hashcode, delta=False)  # 解码
bbox(hashcode)  # 边界经纬度
neighbors(hashcode)  # 8个近邻编码
expand(hashcode)  # 拓展编码

4. GeoHash的应用与注意事项

GeoHash的主要价值在于将二维的经纬度坐标信息编码到了一维的字符串中,在做地理位置索引时只需要匹配字符串即可,便于缓存、信息压缩。在使用大数据工具(例如Spark)进行数据挖掘聚类时,GeoHash显得更加便捷和高效。

但是使用GeoHash还有一些注意事项:

  1. 由于GeoHash使用Z形曲线来顺序填充空间的,而Z形曲线在拐角处会有突变,这表现在有些相邻的网格的编码前缀比其他网格相差较多,因此利用前缀匹配可以找到一部分邻近的区域,但同时也会漏掉一些。
  2. 一个网格内部所有点会共用一个GeoHash值,在网格的边缘点会匹配到可能较远但是GeoHash值相同的点,而本来距离较近的点却没有匹配到。这种问题可以这样解决:适当增加GeoHash编码长度,并使用周围的8个近邻编码来参与,因为往往只使用一个GeoHash编码可能会有严重风险!

你可能感兴趣的:(策略算法)