geohash算法原理及实现方式

约定不等于承诺〃 2022-08-09 06:49 245阅读 0赞

geohash是一种地址编码,它能把二维的经纬度编码成一维的字符串。
一、geohash特点
首先,geohash用一个字符串表示经度和纬度两个坐标。某些情况下无法在两列上同时应用索引 (例如MySQL 4之前的版本,Google App Engine的数据层等),利用geohash,只需在一列上应用索引即可。
其次,geohash表示的并不是一个点,而是一个矩形区域。比如编码wx4g0ec19,它表示的是一个矩形区域。 使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。
第三,编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。首先根据用户当前坐标计算geohash(例如wx4g0ec1)然后取其前缀进行查询 (SELECT * FROM place WHERE geohash LIKE ‘wx4g0e%’),即可查询附近的所有地点。
Geohash比直接用经纬度的高效很多。
二、Geohash的原理
Geohash的最简单的解释就是:将一个经纬度信息,转换成一个可以排序,可以比较的字符串编码。
首先将纬度范围(-90, 90)平分成两个区间(-90,0)、(0, 90),如果目标纬度位于前一个区间,则编码为0,否则编码为1。
由于39.92324属于(0, 90),所以取编码为1。
然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0。

以此类推,直到精度符合要求为止,得到纬度编码为1011 1000 1100 0111 1001。










































































































纬度范围

划分区间0

划分区间1

39.92324所属区间

(-90, 90)

(-90, 0.0)

(0.0, 90)

1

(0.0, 90)

(0.0, 45.0)

(45.0, 90)

0

(0.0, 45.0)

(0.0, 22.5)

(22.5, 45.0)

1

(22.5, 45.0)

(22.5, 33.75)

(33.75, 45.0)

1

(33.75, 45.0)

(33.75, 39.375)

(39.375, 45.0)

1

(39.375, 45.0)

(39.375, 42.1875)

(42.1875, 45.0)

0

(39.375, 42.1875)

(39.375, 40.7812)

(40.7812, 42.1875)

0

(39.375, 40.7812)

(39.375, 40.0781)

(40.0781, 40.7812)

0

(39.375, 40.0781)

(39.375, 39.7265)

(39.7265, 40.0781)

1

(39.7265, 40.0781)

(39.7265, 39.9023)

(39.9023, 40.0781)

1

(39.9023, 40.0781)

(39.9023, 39.9902)

(39.9902, 40.0781)

0

(39.9023, 39.9902)

(39.9023, 39.9462)

(39.9462, 39.9902)

0

(39.9023, 39.9462)

(39.9023, 39.9243)

(39.9243, 39.9462)

0

(39.9023, 39.9243)

(39.9023, 39.9133)

(39.9133, 39.9243)

1

(39.9133, 39.9243)

(39.9133, 39.9188)

(39.9188, 39.9243)

1

(39.9188, 39.9243)

(39.9188, 39.9215)

(39.9215, 39.9243)

1

经度也用同样的算法,对(-180, 180)依次细分,得到116.3906的编码为1101 0010 1100 0100 0100。










































































































经度范围

划分区间0

划分区间1

116.3906所属区间

(-180, 180)

(-180, 0.0)

(0.0, 180)

1

(0.0, 180)

(0.0, 90.0)

(90.0, 180)

1

(90.0, 180)

(90.0, 135.0)

(135.0, 180)

0

(90.0, 135.0)

(90.0, 112.5)

(112.5, 135.0)

1

(112.5, 135.0)

(112.5, 123.75)

(123.75, 135.0)

0

(112.5, 123.75)

(112.5, 118.125)

(118.125, 123.75)

0

(112.5, 118.125)

(112.5, 115.312)

(115.312, 118.125)

1

(115.312, 118.125)

(115.312, 116.718)

(116.718, 118.125)

0

(115.312, 116.718)

(115.312, 116.015)

(116.015, 116.718)

1

(116.015, 116.718)

(116.015, 116.367)

(116.367, 116.718)

1

(116.367, 116.718)

(116.367, 116.542)

(116.542, 116.718)

0

(116.367, 116.542)

(116.367, 116.455)

(116.455, 116.542)

0

(116.367, 116.455)

(116.367, 116.411)

(116.411, 116.455)

0

(116.367, 116.411)

(116.367, 116.389)

(116.389, 116.411)

1

(116.389, 116.411)

(116.389, 116.400)

(116.400, 116.411)

0

(116.389, 116.400)

(116.389, 116.394)

(116.394, 116.400)

0

接下来将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,得到编码 11100 11101 00100 01111 00000 01101 01011 00001。

最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(39.92324, 116.3906)的编码为wx4g0ec1。
















































































十进制

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

base32

0

1

2

3

4

5

6

7

8

9

b

c

d

e

f

g

十进制

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

base32

h

j

k

m

n

p

q

r

s

t

u

v

w

x

y

z

解码算法与编码算法相反,先进行base32解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可,这里不再赘述。

三、java代码实现

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.util.BitSet;
  4. import java.util.HashMap;
  5. public class Geohash {
  6. private static int numbits = 6 * 5;
  7. final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
  8. '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
  9. 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
  10. final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
  11. static {
  12. int i = 0;
  13. for (char c : digits)
  14. lookup.put(c, i++);
  15. }
  16. public static void main(String[] args) throws Exception{
  17. System.out.println(new Geohash().encode(45, 125));
  18. }
  19. public double[] decode(String geohash) {
  20. StringBuilder buffer = new StringBuilder();
  21. for (char c : geohash.toCharArray()) {
  22. int i = lookup.get(c) + 32;
  23. buffer.append( Integer.toString(i, 2).substring(1) );
  24. }
  25. BitSet lonset = new BitSet();
  26. BitSet latset = new BitSet();
  27. //even bits
  28. int j =0;
  29. for (int i=0; i< numbits*2;i+=2) {
  30. boolean isSet = false;
  31. if ( i < buffer.length() )
  32. isSet = buffer.charAt(i) == '1';
  33. lonset.set(j++, isSet);
  34. }
  35. //odd bits
  36. j=0;
  37. for (int i=1; i< numbits*2;i+=2) {
  38. boolean isSet = false;
  39. if ( i < buffer.length() )
  40. isSet = buffer.charAt(i) == '1';
  41. latset.set(j++, isSet);
  42. }
  43. double lon = decode(lonset, -180, 180);
  44. double lat = decode(latset, -90, 90);
  45. return new double[] {lat, lon};
  46. }
  47. private double decode(BitSet bs, double floor, double ceiling) {
  48. double mid = 0;
  49. for (int i=0; i<bs.length(); i++) {
  50. mid = (floor + ceiling) / 2;
  51. if (bs.get(i))
  52. floor = mid;
  53. else
  54. ceiling = mid;
  55. }
  56. return mid;
  57. }
  58. public String encode(double lat, double lon) {
  59. BitSet latbits = getBits(lat, -90, 90);
  60. BitSet lonbits = getBits(lon, -180, 180);
  61. StringBuilder buffer = new StringBuilder();
  62. for (int i = 0; i < numbits; i++) {
  63. buffer.append( (lonbits.get(i))?'1':'0');
  64. buffer.append( (latbits.get(i))?'1':'0');
  65. }
  66. return base32(Long.parseLong(buffer.toString(), 2));
  67. }
  68. private BitSet getBits(double lat, double floor, double ceiling) {
  69. BitSet buffer = new BitSet(numbits);
  70. for (int i = 0; i < numbits; i++) {
  71. double mid = (floor + ceiling) / 2;
  72. if (lat >= mid) {
  73. buffer.set(i);
  74. floor = mid;
  75. } else {
  76. ceiling = mid;
  77. }
  78. }
  79. return buffer;
  80. }
  81. public static String base32(long i) {
  82. char[] buf = new char[65];
  83. int charPos = 64;
  84. boolean negative = (i < 0);
  85. if (!negative)
  86. i = -i;
  87. while (i <= -32) {
  88. buf[charPos--] = digits[(int) (-(i % 32))];
  89. i /= 32;
  90. }
  91. buf[charPos] = digits[(int) (-i)];
  92. if (negative)
  93. buf[--charPos] = '-';
  94. return new String(buf, charPos, (65 - charPos));
  95. }
  96. }

四、观点讨论

引用阿里云以为技术专家的博客上的讨论:

1.两个离的越近,geohash的结果相同的位数越多,对么?
这一点是有些用户对geohash的误解,虽然geo确实尽可能的将位置相近的点hash到了一起,可是这并不是严格意义上的(实际上也并不可能,因为毕竟多一维坐标),
例如在方格4的左下部分的点和大方格1的右下部分的点离的很近,可是它们的geohash值一定是相差的相当远,因为头一次的分块就相差太大了,很多时候我们对geohash的值进行简单的排序比较,结果貌似真的能够找出相近的点,并且似乎还是按照距离的远近排列的,可是实际上会有一些点被漏掉了。
上述这个问题,可以通过搜索一个格子,周围八个格子的数据,统一获取后再进行过滤。这样就在编码层次解决了这个问题。
2.既然不能做到将相近的点hash值也相近,那么geohash的意义何在呢?
我觉得geohash还是相当有用的一个算法,毕竟这个算法通过无穷的细分,能确保将每一个小块的geohash值确保在一定的范围之内,这样就为灵活的周边查找和范围查找提供了可能。

常见的一些应用场景

A、如果想查询附近的点?如何操作

查出改点的gehash值,然后到数据库里面进行前缀匹配就可以了。

B、如果想查询附近点,特定范围内,例如一个点周围500米的点,如何搞?

可以查询结果,在结果中进行赛选,将geohash进行解码为经纬度,然后进行比较

*在纬度相等的情况下:

*经度每隔0.00001度,距离相差约1米;

*每隔0.0001度,距离相差约10米;

*每隔0.001度,距离相差约100米;

*每隔0.01度,距离相差约1000米;

*每隔0.1度,距离相差约10000米。

*在经度相等的情况下:

*纬度每隔0.00001度,距离相差约1.1米;

*每隔0.0001度,距离相差约11米;

*每隔0.001度,距离相差约111米;

*每隔0.01度,距离相差约1113米;

*每隔0.1度,距离相差约11132米。

Geohash,如果geohash的位数是6位数的时候,大概为附近1千米…

参考资料:

http://iamzhongyong.iteye.com/blog/1399333

http://tech.idv2.com/2011/06/17/location-search/

http://blog.sina.com.cn/s/blog_62ba0fdd0100tul4.html

转载: http://www.cnblogs.com/dengxinglin/archive/2012/12/14/2817761.html
相关文章:

1.基于LBS功能应用的Geohash方案

2.GeoHash核心原理解析

3.Mysql or Mongodb LBS快速实现方案

发表评论

表情:
评论列表 (有 0 条评论,245人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Svm算法原理实现

    Svm(support Vector Mac)又称为支持向量机,是一种二分类的模型。当然如果进行修改之后也是可以用于多类别问题的分类。支持向量机可以分为线性核非线性两大类。其主

    相关 geohash算法原理

    1.geohash算法实现的功能: 在很多应用中会用到查询附近的人的功能,具体实现就是根据自己所在地点的经纬度与别人的经纬度做计算。如果在某个范围内则取出显示,但是经纬度是