赞
踩
后续文章参见
地理空间索引:线段与多边形的GeoHash编码
地理空间索引:线段的GeoHash编码优化
基于位置的服务型电商席卷而来,搭乘网约车去到目的地、搜索附近的餐馆酒旅,无不让人们感觉到便捷。比如打开滴滴APP,我们看到附近的车辆如下
那么问题来了,滴滴是怎么快速的匹配出乘客附近2公里的车辆的呢?
先讲最暴力的解法:首先获取乘客的经纬度坐标,然后和当前所有车辆的经纬度坐标计算距离,最后筛选留下距离小于两公里的车辆。这种解法计算量随着车辆规模线性增长,因此非常耗时。一种优化思路是按照经纬度坐标范围粗略筛选掉距离过远的车辆,然后在剩余的车辆里继续计算距离来进行精确筛选,但这同样需要遍历所有的车辆,仍然无法避免大量的时间开销。有没有不用遍历的解法?
上面的思考容易让我们联想到哈希散列的思路:遍历列表查找时间复杂度高,而创建哈希值散列后查找效率可以获得极大提升。尽管我们要考虑的场景是二维的,但是我们可以将经度和纬度分开来处理,也就是先后在经度和纬度上对空间进行编码,相当于在地球上打上网格,并且该网格具有层次(见下图),层次由高到低,代表的空间范围由大到小,描述的地理位置由粗到细;为了查找一个经纬度所在的网格,只需要按照网格层次由高到低进行匹配就可以了。
回到之前的问题,为了匹配乘客附近2公里的车辆,我们事先给乘客和所有车辆的经纬度坐标打上网格(期望网格恰巧就是2公里,实际不能保证这么精确),然后查找和乘客所在的网格相同的车辆即可。由于网格具有层次,于是这里的查找就可以避免遍历,而采取B树等一系列高效的算法进行实现了。
不知不觉中,我们已经在使用了GeoHash的思想开始解决问题了。
下面来正式介绍GeoHash算法。
GeoHash是一种通用的地理编码算法,是由Gustavo Niemeyer发明的,简言之,它可以将地理经纬度坐标编码为由字母和数字所构成的短字符串。它具有如下特性:
例如下图对北京中关村软件园附近进行6位的GeoHash编码结果,9个网格相互邻近且具有相同的前缀wx4ey。
那么GeoHash算法是怎么对经纬度坐标进行编码的呢?总的来说,它采用的是二分法不断缩小经度和纬度的区间来进行二进制编码,最后将经纬度分别产生的编码奇偶位交叉合并,再用字母数字表示。举例来说,对于一个坐标116.29513,40.04920的经度执行算法:
116.29513
在右区间,记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匹配的结果上进行进一步的距离筛选。
目前比较高效实现的代码参考python-geohash,它支持Python和C两种语言环境,其设计思路主要是用移位操作来代替二分区间,实现效率非常高。其Python代码主要接口如下:
encode(latitude, longitude, precision=12) # 编码
decode(hashcode, delta=False) # 解码
bbox(hashcode) # 边界经纬度
neighbors(hashcode) # 8个近邻编码
expand(hashcode) # 拓展编码
GeoHash的主要价值在于将二维的经纬度坐标信息编码到了一维的字符串中,在做地理位置索引时只需要匹配字符串即可,便于缓存、信息压缩。在使用大数据工具(例如Spark)进行数据挖掘聚类时,GeoHash显得更加便捷和高效。
但是使用GeoHash还有一些注意事项:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。