Geohash算法简介
1. Geohash
算法介绍
Geohash
是一种用于公共领域的地理编码系统,其将地理位置编码为字母和数字的短字符串。Geohash
允许任意精度的属性,可以通过增长或缩短字符串来改变精度。当两个区域的公共前缀越长,说明他们的联系更加紧密。但是反过来,具有短公共前缀或者没有公共前缀并不一定代表着联系很小。
Geohash
算法可以将一个二维的经纬度坐标转换成一个可以比较的字符串,也就是降维。使用三十二进制,全球被划分为 $32$ 个大块,再在每个大块内继续划分出 $32$个小块,因此对于越长的geohash
字符串,其精度越大,代表的范围也就越小。通过Base32
算法,geohash
使用字母和数字表示值,如下所示:
数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
32进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | b | c | d | e | f | g | h | j | k | m | n | p | q | r | s | t | u | v | w | x | y | z |
即使用了所有 $10$ 以内的数字,以及除 $a$, $i$, $l$ 和 $o$ 之外的所有小写字母。在计算的时候只需要在十进制和三十二进制之间转换即可,举例:
正如之前所说的,geohash
算法将经纬度转换成一个字符串,自然也能由字符串转回经纬度。再以 $ezs42$ 为例,将其转换为二进制:
结果得到的二进制数字为:$(0110111111110000010000010)_2$ 。假设从左到右读,则奇数位代表纬度代码:$(101111001001)_2$,偶数位代表经度代码:$(0111110000000)_2$。
接下来再通过二分法对经纬度代码进行分别处理。纬度的范围为 $-90 \sim 90$ ,我们将其划分为两块:$-90 \sim 0$ 和 $0 \sim 90$ 。查看纬度代码,第一位为 $1$,因此选择 $0 \sim 90$。如果为 $0$ 的话,就要选择左边的区间。即为 $0$ 选择左边,为 $1$ 选择右边。如果没有更多的位数,那么可以认为纬度为 $45$,这样误差范围就为 $\pm45$ 。但是在这里我们有更多的位数,因此可以继续下去。再对 $0 \sim 90$ 进行二分分为 $0 \sim 45$ 和 $45 \sim 90$ ,可以看到纬度代码第二位为 $0$ ,因此选择 $0 \sim 45$ 。如此往复,可以确认纬度值。再使用同样的方法,可以确认经度值,不过要注意经度的范围是 $-180 \sim 180$ 。
纬度确定:
位数 | 位值 | 最小值 | 中间值 | 最大值 |
---|---|---|---|---|
0 | 1 | -90.000 | 0.000 | 90.000 |
1 | 0 | 0.000 | 45.000 | 90.000 |
2 | 1 | 0.000 | 22.500 | 45.000 |
3 | 1 | 22.500 | 33.750 | 45.000 |
4 | 1 | 33.750 | 39.375 | 45.000 |
5 | 1 | 39.375 | 42.188 | 45.000 |
6 | 0 | 42.188 | 43.594 | 45.000 |
7 | 0 | 42.188 | 42.891 | 43.594 |
8 | 1 | 42.188 | 42.539 | 42.891 |
9 | 0 | 42.539 | 42.715 | 42.891 |
10 | 0 | 42.539 | 42.627 | 42.715 |
11 | 1 | 42.539 | 42.583 | 42.627 |
可得纬度为 $42.583$ ,同理可得经度为 $-5.581$ ,经纬度误差范围都在 $\pm0.022$ 之间。根据经纬度与千米的比例表可得误差距离。
Geohash 长度 |
纬度位数 | 经度位数 | 纬度误差 | 经度误差 | 千米误差 |
---|---|---|---|---|---|
1 | 2 | 3 | $\pm23$ | $\pm23$ | $\pm2500$ |
2 | 5 | 5 | $\pm2.8$ | $\pm5.6$ | $\pm630$ |
3 | 7 | 8 | $\pm0.70$ | $\pm0.70$ | $\pm78$ |
4 | 10 | 10 | $\pm0.087$ | $\pm0.18$ | $\pm20$ |
5 | 12 | 13 | $\pm0.022$ | $\pm0.022$ | $\pm2.4$ |
6 | 15 | 15 | $\pm0.0027$ | $\pm0.0055$ | $\pm0.61$ |
7 | 17 | 18 | $\pm0.00068$ | $\pm0.00068$ | $\pm0.076$ |
8 | 20 | 20 | $\pm0.000085$ | $\pm0.00017$ | $\pm0.019$ |
对于一个给定的经纬度,要想求得经纬度的二进制代码值,只需要将上述步骤倒过来即可。再以 $(42.583, -5.581)$ 为例。
纬度代码确定:
位数 | 最小值 | 中间值 | 最大值 | 位值 |
---|---|---|---|---|
0 | -90.000 | 0.000 | 90.000 | 1 |
1 | 0.000 | 45.000 | 90.000 | 0 |
2 | 0.000 | 22.500 | 45.000 | 1 |
3 | 22.500 | 33.750 | 45.000 | 1 |
4 | 33.750 | 39.375 | 45.000 | 1 |
5 | 39.375 | 42.188 | 45.000 | 1 |
6 | 42.188 | 43.594 | 45.000 | 0 |
7 | 42.188 | 42.891 | 43.594 | 0 |
8 | 42.188 | 42.539 | 42.891 | 1 |
9 | 42.539 | 42.715 | 42.891 | 0 |
10 | 42.539 | 42.627 | 42.715 | 0 |
虽然通过geohash
可以通过公共前缀来查找一些彼此相邻的点,但是在一些边缘情况无法通过这种方式进行查询。例如在本初子午线左右两端的geohash
值并没有公共前缀,以及靠近南北极的区域的geohash
值差异很大。因为geohash
依赖经纬度得到数值,因此在这些经纬度变化迅速的区域,它们的geohash
值的变化也很迅速。
2. Redis
中使用geo
数据类型
继续以 $(42.583, -5.581)$ 为例,演示在Redis
中操作geo
数据类型。
geoadd location -5.581 42.583 gw01
将 $(42.583, -5.581)$ 存入 $location$ 并命名为 $gw01$。$location$ 本质为 $zset$ 数据类型。再对其获取得
geopos location gw01
计算其geohash
值
geohash location gw01
此外,通过
geodist
georadius
georadiusbymember
这些指令可以计算范围内符合条件的坐标。