中学地理课上我们都学过地球坐标的表示方法:经纬度。在我国通用的坐标体系有两种:WGS-84和GCJ-02(又称火星)。其中,84即GPS标准,而火星则为我国政府出于种种安全目的考虑,在84的基础上加了几百米的偏置得到的。

我们知道,像百度地图、谷歌地图、高德地图等,都具备非常快的局部缩放能力,如果采用高精度经纬度来表示,会出现查询性能的障碍(如Mysql4之前的版本不支持同时在两列上建索引)。为此,便出现了geohash的表示方法。

geohash用一个字符串即可同时表示经度和纬度两个坐标,它将一个经纬度信息转换为一个可以排序和比较的字符串编码。在地图上它表示的是一个矩形区域,而不是一个点。经纬度表示时,小数点后4位精度大概为100米,小数点后5位精度大概为10米;用geohash base32表示,编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右。通过geohash,我们就可以只在一列上进行索引,加快局部缩放速度。

geohash的编码方式是这样的:

首先把地球简化为一个纬度-90~90、经度-180~180的矩形平面,然后在这个矩形平面上进行"切割"。每次都是横一刀、竖一刀。编码方式是:左下,左上,右下,右上。以西湖苏堤为例:它的经纬度为(30.232685,120.142135),对应的geohash值为wtm7vzsy,各自对经纬度进行处理:

这样,我们对经度和纬度分别解析得到了两个二进制编码,将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,然后进行base32编码(去掉a, i, l, o),即得wtm7vzsy。解码算法与编码算法相反,先进行base32解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可。

从geohash的生成过程可以看出,它有一个特性:wtm7vzsy必定包含在它的前缀串wtm7vzs中,即只要前缀相同,其中一个矩形必定是另一个的子矩形,这可以用来进行不同精度的查询,提高缓存命中率。

不过,从geohash的编码算法中可以看出它的一个缺点:位于矩形边界两侧的两点, 虽然十分接近,但编码会完全不同,因为它的编码方式从左上到右下突变时存在不连续的"跳跃":

这就会导致下面这种问题:下图中黑色六角形表示一个人,现在他要寻找离自己最近的饭馆。当他搜索周边的咖啡馆时,如果优先取同一个geohash的点,会将J推送给它,而实际上离他较近的是D,这就要求我们在实际操作时,同时搜索当前矩形周围的8个相邻矩形。