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,格式不太好,大家自己整理一下即可
- #define BASE32 "0123456789bcdefghjkmnpqrstuvwxyz"
- static void encode_geohash(double latitude, double longitude, int precision, char *geohash) {
- int is_even=1, i=0;
- double lat[2], lon[2], mid;
- char bits[] = {16,8,4,2,1};
- int bit=0, ch=0;
- lat[0] = -90.0; lat[1] = 90.0;
- lon[0] = -180.0; lon[1] = 180.0;
- while (i < precision) {
- if (is_even) {
- mid = (lon[0] + lon[1]) / 2;
- if (longitude > mid) {
- ch |= bits[bit];
- lon[0] = mid;
- } else
- lon[1] = mid;
- } else {
- mid = (lat[0] + lat[1]) / 2;
- if (latitude > mid) {
- ch |= bits[bit];
- lat[0] = mid;
- } else
- lat[1] = mid;
- }
- is_even = !is_even;
- if (bit < 4)
- bit++;
- else {
- geohash[i++] = BASE32[ch];
- bit = 0;
- ch = 0;
- }
- }
- geohash[i] = 0;
- }