Geohash算法

来源:互联网 发布:centos7 网络配置文件 编辑:程序博客网 时间:2024/05/17 07:10

1.算法背景

  Geohash的初衷是如何用尽量短的URL来标志地图上的某个位置,而地图上的位置一般是用经纬度来表示,问题就转化为如何把经纬度转化为一个尽量短的URL。

 

Geohash的算法描述请参考:http://en.wikipedia.org/wiki/Geohash ,本文的主要目的是更加细致地解释该算法的原理及实用场景。

2.算法

   算法的主要思想是对某一数字通过二分法进行无限逼近,比如纬度的区间是[-90,90],假如给定一个纬度值:46.5,可以通过下面算法对46.5进行无限逼近:

 (1)把区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定46.5属于右区间[0,90]

 (2)递归上述过程46.5总是属于某个区间,无论第几次迭代46.5总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,根据极限可知[a,b]会收敛到46.5,用δ来描述就是,任意给定一个 ε,总存在一个N使得: δ=|x-a/2N |< ε,x为任意给定的纬度

 (3)上述分析过程保证了算法收敛性的同时,再记录一下收敛的过程:如果给定的纬度x(46.5)属于左区间,则记录0,如果属于右区间则记录1,这样随着算法的进行会产生一个序列1011100,序列的长度跟给定的收敛次数N相关。

      反过来,如果我们知道了序列1011100,我们就可以分别能确定纬度x(46.5)属于哪个更小的迭代区间,也就是说该算法是可逆的

 (4)算法的精度:显然的是,不可能让计算机执行无穷计算,加入执行N此计算,则x属于的区间长度为(b-a)/2N+1 ,以纬度计算为例,则为180/2N ,误差近似计算为:err= 180/2N+1 =90/2N ,如果N=20,则误差最大为:0.00009。但无论如何这样表明Geohash是一种近似算法。

3.编码

   在对纬度产生了序列1011100后,在对经度做相同的算法也会产生一个序列,比如0011101。根据偶数位放经度,奇数位放纬度(0被视为偶数),把2个串合二为一,产生一个新串:01001111110010,对该串进行Base32编码,则可获得一个ASIIC码的字符串,关于Base32编码,请参考:http://en.wikipedia.org/wiki/Base32

4.解码

  解码的过程相对比较简单

 (1)对拿到的字符串进行Base32解码

 (2)根据奇偶位取出纬度、经度

 (3)根据序列反向得到每个区间,并取中间值(0为左区间,1为右区间)

5.应用

  该算法目前主要用在地图的地址搜索,有了该算法可以为数据库中的地址建立索引,极大提高地图数据检索的速度。

  仔细观察,该算法还有另为一个特点,对相近的x,y,会得到相同前缀的序列,原因是相近的x,y,在递归的绝大多数时间会处在同一个区间,故而,逼近的轨迹是一致的,这又可以解决地图中“离我最近的搜索“的问题,同时,对进行hash的模糊检索也有一定的启发作用。

6.代码

  下面的实现代码很精美,引用自:http://bloggermap.org/rss/readblog/70907,格式不太好,大家自己整理一下即可

 

[cpp] view plaincopy
  1. #define BASE32 "0123456789bcdefghjkmnpqrstuvwxyz"  
  2. static void encode_geohash(double latitude, double longitude, int precision, char *geohash) {  
  3. int is_even=1, i=0;  
  4. double lat[2], lon[2], mid;  
  5. char bits[] = {16,8,4,2,1};  
  6. int bit=0, ch=0;  
  7. lat[0] = -90.0; lat[1] = 90.0;  
  8. lon[0] = -180.0; lon[1] = 180.0;  
  9. while (i < precision) {  
  10. if (is_even) {  
  11. mid = (lon[0] + lon[1]) / 2;  
  12. if (longitude > mid) {  
  13. ch |= bits[bit];  
  14. lon[0] = mid;  
  15. else  
  16. lon[1] = mid;  
  17. else {  
  18. mid = (lat[0] + lat[1]) / 2;  
  19. if (latitude > mid) {  
  20. ch |= bits[bit];  
  21. lat[0] = mid;  
  22. else  
  23. lat[1] = mid;  
  24. }  
  25. is_even = !is_even;  
  26. if (bit < 4)  
  27. bit++;  
  28. else {  
  29. geohash[i++] = BASE32[ch];  
  30. bit = 0;  
  31. ch = 0;  
  32. }  
  33. }  
  34. geohash[i] = 0;  
  35. }  

0 0