Geohash 算法学习

Geohash 算法:

    这是一套纬度/经度地理编码算法,把纬度/经度编码成base32位的字符串。这种编码和纬度/经度不是唯一对应,其实是一个纬度/经度区间。算法有一个精度概念,精度越高,字符串越长,所表示的区间越小。可以编码后的字符串想象成一个格子,里面存放一些纬度/经度值。格子趋近很小的时候,只能存放一纬度/经度值,那么编码和纬度/经度就是唯一对应的关系。但是这个不是重点,这套算法目的就是把纬度/经度编码成近似值,通过近似值搜索,就能很高效的缩小范围,然后再从小范围里查找精确值。

例如,坐标57.64911,10.40744(日德兰半岛的顶端附近,在丹麦)产生一个u4pruydqqvj字符串。参考Wikipedia:http://en.wikipedia.org/wiki/Geohash

算法原理:

以A[-170,42.6]  为例,纬度范围(-90, 90)平分成两个区间(-90, 0)、(0, 90),位于前一个区间,则编码为0,否则编码为1。由于42.6属于(0, 90),所以取编码为1。

再将(0, 90)分成 (0, 45), (45, 90)两个区间,而42.6位于(0, 45),所以编码为0,

再将(0, 45)分成 (0, 22.5), (22.5, 45)两个区间,而42.6位于(22.5, 45),所以编码为1,

再将(22.5, 45)分成 (22.5, 33.7.5), (33.7.5, 45)两个区间

最后划分四次后纬度编码为:1011

同理经度编码为:0000

如图绿色格子就是此编码代表的区间范围

算出经纬度编码后,从高到低,奇数为经度,偶数为纬度,合并经纬度编码。

lng:0111110000000

lat:101111001001

合并后:01101 11111 11000 00100 00010

然后再把二进制按每五个一组,按base 32 编码成字符串。

01101 11111 11000 00100 00010  

13       31      24       4         2

e         z         s       4          2

最后的Geohash 编码为:ezs42 

应用场景:

前面介绍了下编码的规则,现在来讨论下一些应用场景。我们知道,地球是一个近似球体。球面上两点相对球心的角度偏差,和两点的球面距离是一个等比关系。而Geohash 编码其实就是一个纬度/经度区间,区间的角度范围就决定了区间内的点之间的距离范围。通过这个原理,就可以通过一个坐标的经纬度,找出所在的区间和周边区间来搜索 该点周边的坐标。

Wikipedia上以纬度42.6 为例,统计出每次划分后的每个区间的纬度范围。

划分十二次后,每个区间的纬度范围 0.044 ,根据地球半径可心算出每个距离范围为4.8 公里

  当geohash length=5 时,通过搜索某点周边的8个相邻区间,可以大概找出周边5公里的坐标。

这个算法有一定限制,纬度越高,基于经度偏差和距离的比值越低,表格中的距离计算精度也随着降低,需要根据cos(纬度)的值进行调整。

下面是官方提供的代码,一个是根据经纬度计算HashCode ,另一个是根据HashCode 计算周边的8个HashCode 。在实际应用中就可以用这几个方法

构建地标的hashCode 并通过hashCode来检索。

C#代码:

 

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

JS代码—引用 https://github.com/davetroy/geohash-js/blob/master/geohash.js

  1 <script type="text/javascript">
  2         BITS = [16, 8, 4, 2, 1];
  3 
  4         BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
  5         NEIGHBORS = { right: { even: "bc01fg45238967deuvhjyznpkmstqrwx" },
  6             left: { even: "238967debc01fg45kmstqrwxuvhjyznp" },
  7             top: { even: "p0r21436x8zb9dcf5h7kjnmqesgutwvy" },
  8             bottom: { even: "14365h7k9dcfesgujnmqp0r2twvyx8zb" }
  9         };
 10         BORDERS = { right: { even: "bcfguvyz" },
 11             left: { even: "0145hjnp" },
 12             top: { even: "prxz" },
 13             bottom: { even: "028b" }
 14         };
 15 
 16         NEIGHBORS.bottom.odd = NEIGHBORS.left.even;
 17         NEIGHBORS.top.odd = NEIGHBORS.right.even;
 18         NEIGHBORS.left.odd = NEIGHBORS.bottom.even;
 19         NEIGHBORS.right.odd = NEIGHBORS.top.even;
 20 
 21         BORDERS.bottom.odd = BORDERS.left.even;
 22         BORDERS.top.odd = BORDERS.right.even;
 23         BORDERS.left.odd = BORDERS.bottom.even;
 24         BORDERS.right.odd = BORDERS.top.even;
 25 
 26         function refine_interval(interval, cd, mask) {
 27             if (cd & mask)
 28                 interval[0] = (interval[0] + interval[1]) / 2;
 29             else
 30                 interval[1] = (interval[0] + interval[1]) / 2;
 31         }
 32 
 33         function calculateAdjacent(srcHash, dir) {
 34             srcHash = srcHash.toLowerCase();
 35             var lastChr = srcHash.charAt(srcHash.length - 1);
 36             var type = (srcHash.length % 2) ? 'odd' : 'even';
 37             var base = srcHash.substring(0, srcHash.length - 1);
 38             if (BORDERS[dir][type].indexOf(lastChr) != -1)
 39                 base = calculateAdjacent(base, dir);
 40             return base + BASE32[NEIGHBORS[dir][type].indexOf(lastChr)];
 41         }
 42 
 43         function decodeGeoHash(geohash) {
 44             var is_even = 1;
 45             var lat = []; var lon = [];
 46             lat[0] = -90.0; lat[1] = 90.0;
 47             lon[0] = -180.0; lon[1] = 180.0;
 48             lat_err = 90.0; lon_err = 180.0;
 49 
 50             for (i = 0; i < geohash.length; i++) {
 51                 c = geohash[i];
 52                 cd = BASE32.indexOf(c);
 53                 for (j = 0; j < 5; j++) {
 54                     mask = BITS[j];
 55                     if (is_even) {
 56                         lon_err /= 2;
 57                         refine_interval(lon, cd, mask);
 58                     } else {
 59                         lat_err /= 2;
 60                         refine_interval(lat, cd, mask);
 61                     }
 62                     is_even = !is_even;
 63                 }
 64             }
 65             lat[2] = (lat[0] + lat[1]) / 2;
 66             lon[2] = (lon[0] + lon[1]) / 2;
 67 
 68             return { latitude: lat, longitude: lon };
 69         }
 70 
 71         function encodeGeoHash(latitude, longitude) {
 72             var is_even = 1;
 73             var i = 0;
 74             var lat = []; var lon = [];
 75             var bit = 0;
 76             var ch = 0;
 77             var precision = 12;
 78             geohash = "";
 79 
 80             lat[0] = -90.0; lat[1] = 90.0;
 81             lon[0] = -180.0; lon[1] = 180.0;
 82 
 83             while (geohash.length < precision) {
 84                 if (is_even) {
 85                     mid = (lon[0] + lon[1]) / 2;
 86                     if (longitude > mid) {
 87                         ch |= BITS[bit];
 88                         lon[0] = mid;
 89                     } else
 90                         lon[1] = mid;
 91                 } else {
 92                     mid = (lat[0] + lat[1]) / 2;
 93                     if (latitude > mid) {
 94                         ch |= BITS[bit];
 95                         lat[0] = mid;
 96                     } else
 97                         lat[1] = mid;
 98                 }
 99 
100                 is_even = !is_even;
101                 if (bit < 4)
102                     bit++;
103                 else {
104                     geohash += BASE32[ch];
105                     bit = 0;
106                     ch = 0;
107                 }
108             }
109             return geohash;
110         }
111     </script>
View Code

 

作者:张日海

posted on 2014-07-09 14:07  张日海  阅读(5055)  评论(2编辑  收藏  举报

导航