地位位置索引查询介绍——Geohash

时间:2024-04-01 18:04:25

Geohash

本文介绍一种高维(二维以上)坐标对的搜索算法——Geohash。本文主要在二维层面进行分析Geohash算法的使用方法和优缺点。

Geohash 介绍

在日常生活中,我们对某一坐标的定位,一般都是使用经纬度来进行标记的。比如:中国科学技术大学(经度:117.26139,纬度:31.83819。我们获取一个区域的位置,是使用一个二维数组对其进行标记的,它表示的不是一个具体的点,而是泛指一片区域,区域的范围与经纬度的取值精度直接相关。

Geohash是 Gustavo Niemeyer 和GM Morton(于1966年进行的类似工作)于2008年发明的公共领域地理编码系统,将地理位置编码为一小段字母和数字。它是一种分层的空间数据结构,可将空间细分为网格状的桶,这是所谓的Z阶曲线(通常是空间填充曲线)的许多应用之一。关于Z曲线的详细内容本文不会提及,可以理解为一种将二维投影到一维的方式。

GeoHash将二维的经纬度转换成字符串,比如下图展示了北京9个区域的GeoHash字符串,分别是WX4ER,WX4G2、WX4G3等等(编码含义参见 下一节:Geohash 区域划分原理 ),每一个字符串代表了某一矩形区域。也就是说,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存,比如左上角这个区域内的用户不断发送位置信息请求餐馆数据,由于这些用户的GeoHash字符串都是WX4ER,所以可以把WX4ER当作key,把该区域的餐馆信息当作value来进行缓存,而如果不使用GeoHash的话,由于区域内的用户传来的经纬度是各不相同的,很难做缓存。

地位位置索引查询介绍——Geohash

字符串越长,表示的范围越精确。如图所示,5位的编码能表示10平方千米范围的矩形区域,而6位编码能表示更精细的区域(约0.34平方千米)

地位位置索引查询介绍——Geohash

也就是说,GeoHash就是一种将经纬度转换成字符串的方法,并且使得在大部分情况下,字符串前缀匹配越多的距离越近。回到我们的案例,根据所在位置查询来查询附近餐馆时,只需要将所在位置经纬度转换成GeoHash字符串,并与各个餐馆的GeoHash字符串进行前缀匹配,匹配越多的距离越近。

Geohash 区域划分原理

Geohash其实就是将整个地图或者某个分割所得的区域进行一次划分,由于采用的是base32编码方式,即Geohash中的每一个字母或者数字(如wx4g0e中的w)都是由5bits组成(2^5 = 32,base32),这5bits可以有32中不同的组合(0~31),这样我们可以将整个地图区域分为32个区域,通过00000 ~ 11111来标识这32个区域。第一次对地图划分后的情况如下图所示(每个区域中的编号对应于该区域所对应的编码)

地位位置索引查询介绍——Geohash

Geohash的0、1串序列是经度0、1序列和纬度0、1序列中的数字交替进行排列的,偶数位对应的序列为经度序列,奇数位对应的序列为纬度序列,在进行第一次划分时,Geohash0、1序列中的前5个bits(11100),那么这5bits中有3bits是表示经度,2bits表示纬度,所以第一次划分时,是将经度划分成8个区段(2^3 = 8),将纬度划分为4个区段(2^2 = 4),这样就形成了32个区域(对应Base32)

地位位置索引查询介绍——Geohash

Geohash 精度

数字的精度(单位:km)
地位位置索引查询介绍——Geohash

Geohash 使用注意点(缺点)

1)边界情况

由于GeoHash是将区域划分为一个个规则矩形,并对每个矩形进行编码,这样在查询附近POI信息时会导致以下问题,比如红色的点是我们的位置,绿色的两个点分别是附近的两个餐馆,但是在查询的时候会发现距离较远餐馆的GeoHash编码与我们一样(因为在同一个GeoHash区域块上),而较近餐馆的GeoHash编码与我们不一致。这个问题往往产生在边界处。

地位位置索引查询介绍——Geohash

解决的思路很简单,我们查询时,除了使用定位点的GeoHash编码进行匹配外,还使用周围8个区域的GeoHash编码(在查找的时候每一级都需要查询相邻区块,额外开销不小),这样可以“宁可错杀也不放过”的方式虽然可以避免出现错误,但是损失了部分效率。

2)非线性

非线性特性是指相邻两个区块的编码可能会有很大的不同,原因是Geohash采用的是Z曲线来填充平面,这种曲线会产生突变,造成了编码虽然相似但距离可能相差很大的问题,因此在查询附近餐馆时候,首先筛选GeoHash编码相似的POI点,然后进行实际距离计算。

geohash 只是空间索引的一种方式,特别适合点数据,而对线、面数据采用R树索引更有优势(可参考:深入浅出空间索引:为什么需要空间索引)。

3)存储空间

最精确的geohash需要12个字节的存储空间。相比之下如S2可以用8个字节达到相同的效果。并且Geohash不能实现不同大小的区块混合使用。Geohash只 有12级,从5000km 到7cm。中间每一级的变化比较大。有时候可能选择上一级会大很多,选择下一级又会小一些。这种情况选择多长的 Geohash 字符串就比较难选。选择不好,每次判断可能就还需要取出周围的8个格子再次进行判断。例如当查找水平为19.5km x 19.5km的情况偏小时,下一个水平就是156km x 156km!过于陡峭的梯度将导致部分时候搜索性能的急剧降低。

总结

目前MongoDB采用的是Google-s2的搜索算法,个人认为这种方法在各方面都比Geohash来得更加高效、合理,并且s2算法规避了许多Geohash的缺点。可以参考:高效的多维空间点索引算法 — Geohash 和 Google S2,此文也详细解释了Geohash中Z曲线的相关知识点。如果希望进一步了解S2算法中的cell,可以参考这篇:Google S2 中的 CellID 是如何生成的 ?。关于S2算法中用到的1D to 2D 的希尔伯特曲线,可以参考这篇文档:Hilbert Curves