geohash: 一个实用的geocoding方法

geohash: 一个实用的geocoding方法

来自:http://blog.csdn.net/historyasamirror/archive/2010/06/01/5638248.aspx

这年头和 location 相关的应用越来越火。从 foursquare 的热闹程度就可见一般(什么,没听过 foursquare…. 哥们,你 out 了)。和 location 有关的应用一般都包括一些共同的操作,最常见的一个,就是找附近的东东(餐馆,商店 …. )。

所以,这里就抛出了一个问题,怎样才能知道两个物体离得近呢?

我之前转过一篇 blog ,是关于  cellid进行定位的 ,当然,这种方法是在不得已的情况下才使用,比如得不到 gps 。这里,我们假设可以拿到两个物体的 gps 数据,所以一个最直观的办法,计算两个 gps 点的直线距离。当然,这个计算不精确,不要忘了,地球是圆的,所以两个 gps 点之间的距离应该是一个弧线。上网搜一下,应该能找到一个复杂的公式,专门用来计算这个弧长。

对于两个点来说,上述的方法就够用了。当如果有很多个点呢,难道要我计算两两之间的距离么?

这个问题属于 spatial data indexing&management 的范畴,有很多关于 database 或者 GIS 的书都会讲到一些解决的算法和特殊的数据结构。我在这只介绍一个简单的方法,叫做 geohash 

geohash 其实是对 gps 数据进行了编码,使得上述问题更容易得到解决。(关于 geohash 的详细论述可以看 wiki ,介绍得很全面)

假设我们有一个 gps 数据,为 <42.6, -5.6> ,首先 (1) 我们会将经纬度分别编码成一个 binarycode ,比如纬度 42.6 被编码成“ 101111001001 ”,经度 -5.6 被翻译成“ 0111110000000 ”,然后 (2) 将两个 binarycode 连起来,经度的 binarycode 作为奇数位,纬度的 binarycode 作为偶数位,就变成了“ 01101 11111 11000 00100 00010 ”,最后,将这个 binarycode 转化为一个 32 进制的字符串,变成“ ezs42 ”。

需要说一下经纬度转化成 binarycode 的算法。举例来说,比如纬度的范围是 +90 ~ -90 ,我们将这个分为两个区间,分别是 (-90, 0)  (0,90) ,如果 gps  x( 纬度 ) 落在了第一个区间,那么它的第一位 binarycode 就是 0 ,如果落在第二个区间,那么它的第一位 binarycode 就是 1 ,显然 42.6 是在第二个区间,所以它的第一位 binarycode  1 ,然后再对 (0,90) 这个区间做二分,再计算下一步的 binarycode….

编码方式说完了,说说 geohash 的好处。当两个 gps 数据对应的 geohash 数据有一定长度的前缀是相同的,表示这两个数据在一定程度上距离接近,相同的前缀越长,那么两个点越离得近。( nearby places will often (but not always) present similar prefixes. 

注意,需要提及的是,两个 geohash 有相同前缀,表示这两个点离得近,但是!两个点离得近,不一定 geohash 有相同的前缀。 geohash 在这里存在一个缺陷,就是所谓的 edge case 。详情见 wiki 

再往深入琢磨一下, geohash 的本质是什么。其实它就是对一个二维平面进行了一个索引,首先对这个平面竖着切一刀,刀的左边标记为 0 ,刀的右边标记为 1 ,然后再横着切一刀,并且继续标记,然后再竖着切 …. 有很多 spatial data indexing 的方法都是这样的思路,它的作用就是把平面的这种二维数据改造成一维的数据。而一维数据有个好处,就是可以做 sorting 

到此,我们还没有回答之前提的问题,如果有很多点,该怎样从其中找出附近的点呢?答案貌似已经呼之欲出,俺就不多说了。

 

Reference 

http://en.wikipedia.org/wiki/Geohash

http://geohash.org/

 

Geohash算法 收藏

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,格式不太好,大家自己整理一下即可

 

C版

#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; }

 

C#版 public class GeoHashClass {

public const string BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"; //对经纬度进行GeoHash编码

public string encode_geohash(double latitude, double longitude, int precision) { char[] geohash = new char[precision]; bool is_even = true; //1 int i=0; double[] lat=new double[2]; double[] lon=new double[2]; double mid; //char[] bits =new char[]{16,8,4,2,1}; int[] bits = new int[] { 16, 8, 4, 2, 1 }; int bit=0, ch=0; lat[0] = -90.0; lat[1] = 90.0; //纬度(-90,90) lon[0] = -180.0; lon[1] = 180.0; //经度(-180,180) while (i < precision) { if (is_even==true) { 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++] = GeoHashClass.BASE32[ch]; bit = 0; ch = 0; } } geohash[i] = '0'; string rbc=""; for (int k = 0; k < geohash.Length; k++) { rbc += geohash[k].ToString(); } return rbc; } }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值