geohash算法原理及实现方式

来源:互联网 发布:学java还是学php 编辑:程序博客网 时间:2024/05/16 18:04

geohash算法原理及实现方式

1、geohash特点

2、geohash原理

3、geohash的php 、python、java、C#实现代码

4、观点讨论

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解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可,这里不再赘述。

实现代码:

PHP版本的实现方式:http://blog.dixo.net/downloads/geohash-php-class/  我下载了一个上传的

 php:

geohash.class.php

View Code

 

Python:

python版本的geohash:python-geohash

Java:

java版本的geohash,实现:http://code.google.com/p/geospatialweb/source/browse/#svn/trunk/geohash/src

View Code

C#:

复制代码
 C#版本的geohash代
复制代码
  1 using System;  2   3 namespace sharonjl.utils  4 {  5     public static class Geohash  6     {  7         #region Direction enum  8   9         public enum Direction 10         { 11             Top = 0, 12             Right = 1, 13             Bottom = 2, 14             Left = 3  15         } 16  17         #endregion 18  19         private const string Base32 = "0123456789bcdefghjkmnpqrstuvwxyz"; 20         private static readonly int[] Bits = new[] {16, 8, 4, 2, 1}; 21  22         private static readonly string[][] Neighbors = { 23                                                            new[] 24                                                                { 25                                                                    "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Top 26                                                                    "bc01fg45238967deuvhjyznpkmstqrwx", // Right 27                                                                    "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Bottom 28                                                                    "238967debc01fg45kmstqrwxuvhjyznp", // Left 29                                                                }, new[] 30                                                                       { 31                                                                           "bc01fg45238967deuvhjyznpkmstqrwx", // Top 32                                                                           "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Right 33                                                                           "238967debc01fg45kmstqrwxuvhjyznp", // Bottom 34                                                                           "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Left 35                                                                       } 36                                                        }; 37  38         private static readonly string[][] Borders = { 39                                                          new[] {"prxz", "bcfguvyz", "028b", "0145hjnp"}, 40                                                          new[] {"bcfguvyz", "prxz", "0145hjnp", "028b"} 41                                                      }; 42  43         public static String CalculateAdjacent(String hash, Direction direction) 44         { 45             hash = hash.ToLower(); 46  47             char lastChr = hash[hash.Length - 1]; 48             int type = hash.Length%2; 49             var dir = (int) direction; 50             string nHash = hash.Substring(0, hash.Length - 1); 51  52             if (Borders[type][dir].IndexOf(lastChr) != -1) 53             { 54                 nHash = CalculateAdjacent(nHash, (Direction) dir); 55             } 56             return nHash + Base32[Neighbors[type][dir].IndexOf(lastChr)]; 57         } 58  59         public static void RefineInterval(ref double[] interval, int cd, int mask) 60         { 61             if ((cd & mask) != 0) 62             { 63                 interval[0] = (interval[0] + interval[1])/2; 64             } 65             else 66             { 67                 interval[1] = (interval[0] + interval[1])/2; 68             } 69         } 70  71         public static double[] Decode(String geohash) 72         { 73             bool even = true; 74             double[] lat = {-90.0, 90.0}; 75             double[] lon = {-180.0, 180.0}; 76  77             foreach (char c in geohash) 78             { 79                 int cd = Base32.IndexOf(c); 80                 for (int j = 0; j < 5; j++) 81                 { 82                     int mask = Bits[j]; 83                     if (even) 84                     { 85                         RefineInterval(ref lon, cd, mask); 86                     } 87                     else 88                     { 89                         RefineInterval(ref lat, cd, mask); 90                     } 91                     even = !even; 92                 } 93             } 94  95             return new[] {(lat[0] + lat[1])/2, (lon[0] + lon[1])/2}; 96         } 97  98         public static String Encode(double latitude, double longitude, int precision = 12) 99         {100             bool even = true;101             int bit = 0;102             int ch = 0;103             string geohash = "";104 105             double[] lat = {-90.0, 90.0};106             double[] lon = {-180.0, 180.0};107 108             if (precision < 1 || precision > 20) precision = 12;109 110             while (geohash.Length < precision)111             {112                 double mid;113 114                 if (even)115                 {116                     mid = (lon[0] + lon[1])/2;117                     if (longitude > mid)118                     {119                         ch |= Bits[bit];120                         lon[0] = mid;121                     }122                     else123                         lon[1] = mid;124                 }125                 else126                 {127                     mid = (lat[0] + lat[1])/2;128                     if (latitude > mid)129                     {130                         ch |= Bits[bit];131                         lat[0] = mid;132                     }133                     else134                         lat[1] = mid;135                 }136 137                 even = !even;138                 if (bit < 4)139                     bit++;140                 else141                 {142                     geohash += Base32[ch];143                     bit = 0;144                     ch = 0;145                 }146             }147             return geohash;148         }149     }150 }
复制代码

C#代码来自:https://github.com/sharonjl/geohash-net

复制代码

geohash演示:http://openlocation.org/geohash/geohash-js/

 

各种版本下载:打包下载

观点讨论

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

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/step1/archive/2009/04/22/1441689.html

http://tech.idv2.com/2011/07/05/geohash-intro/

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

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

 

geohash的java代码实现:

http://code.google.com/p/geospatialweb/source/browse/#svn/trunk/geohash/src

原文来自:

http://www.cnblogs.com/dengxinglin/archive/2012/12/14/2817761.html

0 0
原创粉丝点击