您的位置:首页 > 其它

地理位置距离计算方法之geohash算法

2017-09-20 16:59 288 查看
网上无意中查到了一篇介绍GeoHash算法,看了下发现好神奇,就跟感知Hash当初给我的感觉一样惊艳。所以立马记录下来,下面是具体内容

转载自原文链接



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,格式不太好(PS:我格式化了下),大家自己整理一下即可

#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;
}
==================================分割线=========================================

下面的几个链接是geohash的GitHub代码以及介绍

https://github.com/CloudSide/geohash

https://github.com/CloudSide/geohash/wiki
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: