GeoHash算法学习讲解、解析及原理分析

GeoHash算法学习讲解、解析及原理分析

在日常生活中,我们经常会有很多和地理相关的一些应用,比如:地图导航,附近的人,查找某些地标等等,以前我们会选择问路,而随着智能设备的更新和升级,现在我们仅仅只需要一个手机,打开一个地图APP,即可快速查找自己想要的路线或者信息了。

那么,附近的一个建筑或者商店,是通过怎样的形式,让其可以在手机上展示出来的呢?

比如:周末了,躺在床上,使用手机下单,寻找外卖美食...这些美食店信息是怎么在手机上进行展示的呢,它和我所在的距离是怎么计算出来的呢? 这时候,可能就需要用到GeoHash这种算法了。前段时间一个朋友问到过我关于GeoHash的相关知识,但是对这种算法,只是有所了解,并没有深入去探索内部的实现,在此,给它进行自我解析与记录。

先简单介绍一下GeoHash算法,在日常生活中,我们对某一坐标的定位,一半都是使用经纬度来进行标记的。比如:常营地铁站(lat:39.9257460000,lng:116.5998310000),我们获取一个区域的位置,是使用一个二维数组对其进行标记的,它表示的不是一个具体的点,而是泛指一片区域,区域的范围与经纬度的取值精度直接相关。

当我拿到常营地铁站的经纬度后,通过GeoHash这种算法进行计算后,获取到一个可比较的字符串,具体计算过程如下:

同样,对纬度也进行相对应的算法进行计算得到一个二进制值(对经纬度取值范围越小,精度越高,所表示区域范围越小),在此省去计算过程。

https://www.cnblogs.com/tgzhu/p/6204173.html

将经纬度的二进制数进行组合,以奇数为纬度,偶数为经度组合,过程如上图。

然户将获取到的经纬度二进制数以每5个数为一组,将每一组都进行转换成十进制数字。

然后采用Base32对应编码进行转换可得到编码 wx4g0e这样的可比较的字符串,比如我们的经纬度都分了10次,那么最后生成的字符串的长度就是4,范围是20km,如果我们经纬度都分20次,那么最后生成的字符串的长度就是8,范围可以精确到19m。为什么是可比较字符串,后面会详细讲解到。

在此对Base32编码进行一番简单介绍: Base32,是将数字 0~9 ,加上26个字母(去除a,i,l,o 四个)进行组合而成的32个字符编码形式。如代码:

Base32对应编码参考图:(上面二进制转换对应的十进制数值)

维基百科上Base32位值信息表

下面是从网上在线解析的常营地铁站GeoHash值(wx4gjk32kfrx)

网址: http://geohash.co/

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

https://images2015.cnblogs.com/blog/1004194/201612/1004194-20161220214008089-542894090.jpg

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将每一个区域画成一块块矩形块,每个矩形块使用一个字符串表示,当我们需要查询附近的点时,通过自己的坐标计算出一个字符串,根据这个字符串定位到我们所在的矩形块,然后返回这个矩形块中的点。例如 wx4e就包含wx4e0e,也就是说wx4e0ewx4e范围内。

按照这种思路进行,思路逐渐清晰,但是,这种方式会不会有什么问题呢? 或者说,它有什么弊端。

按照单个区域情况考虑,就会出现如上所示的情况。所以就得想办法解决这种情况,就需要将范围进一步扩大。

目前比较通行的做法就是我们不仅获取当前我们所在的矩形区域,还获取周围8个矩形块中的点。那么怎样定位周围8个点呢?关键就是需要获取周围8个点的经纬度,那我们已经知道自己的经纬度,只需要用自己的经纬度减去最小划分单位的经纬度就行。因为我们知道经纬度的范围,有知道需要划分的次数,所以很容易就能计算出最小划分单位的经纬度。

通过上面这张图,我们就能很容易的计算出周围8个点的经纬度了。有了经纬度就能定位到周围8个矩形块了。这样我们就能获取包括自己所在矩形块的9个矩形块中的所有的点。最后分别计算这些点和自己的距离(由于范围很小,点的数量就也很少,计算量就很少)过滤掉不满足条件的点就行了。

试想一下,我们打开百度地图或者是百度地图,在页面中,我们可以看到一个以自我为中心单位范围的一个,之前我一直有疑惑,觉得GeoHash应该是已圆为范围,半径为单位进行搜索附近的标志。那么,在实际开发中,会不会地图上显示的圆,仅仅是一种参考,以表示自己周围范围内的标志,而在代码计算中,依旧是已矩形形式来计算的呢? 这个问题,希望我可以在后面进一步的学习和深入了解相关地图API后得到解答。


附上GeoHash字符串对应地图精度参考图

对了,Redis支持Geo,可查看Redis操作文档API(redisdoc.com/geo/geohas)

在Redis3.2之后引入的。


本文参考资料:

链接:cnblogs.com/LBSer/p/331

链接:zhuanlan.zhihu.com/p/27

链接:cnblogs.com/tgzhu/p/620

链接:cnblogs.com/zhenbianshu


2018年4月21日下午地铁上续更:

前面提到的常营地铁站(lat:39.9257460000,lng:116.5998310000)

将其按geohash拆分规律拆分后的二进制值应该为

11100 11101 00100 01111 10001 10010 00011 00010 01110 10111 11101

转成10进制得到28 29 4 15 17 18 3 2 18 14 23 29

转换成Base32值为wx4gjk32kfrx

下面是转换过程

28(w)
16+8+4+0+0
1*2^4+1*2^3+1*2^2+0*2^1+0*2^0
11100 --> Base32 -->w

29(x)
16+8+4+0+1
1*2^4+1*2^3+1*2^2+0*2^1+1*2^0
11101 --> Base32 -->x

4(4)
0+0+4+0+0
0*2^4+0*2^3+1*2^2+0*2^1+0^2^0
00100 --> Base32 -->4

15(g)
0+8+4+2+1
0*2^4+1*2^3+1*2^2+1*2^1+1*2^0
01111 --> Base32 -->g

17(j)
16+0+0+0+1
1*2^4+0*2^3+0*2^2+0*2^1+1*2^0
10001 --> Base32 -->j

18(k)
16+0+0+2+0
1*2^4+0*2^3+0*2^2+1*2^1+0*2^0
10010 --> Base32 -->k

3(3)
0+0+0+2+1
0*2^4+0*2^3+0*2^2+1*2^1+1*2^0
00011 --> Base32 -->3

2(2)
0+0+0+2+0
0*2^4+0*2^3+0*2^2+1*2^1+0*2^0
00010 --> Base32 -->2

18(k)
16+0+0+2+0
1*2^4+0*2^3+0*2^2+1*2^1+0*2^0
10010 --> Base32 -->k

14(f)
0+8+4+2+0
0*2^4+1*2^3+1*2^2+1*2^1+0*2^0
01110 --> Base32 -->f

23(r)
16+0+4+2+1
...
10111 --> Base32 -->r

29(x)
16+8+4+0+1
1*2^4+1*2^3+1*2^2+0*2^1+1*2^0
11101 --> Base32 -->x

得到值wx4gjk32kfrx(常营地铁站)




编辑于 2018-04-21