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

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

Geohash 算法学习

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

lng:0111110000000

lat:101111001001

合并后:01101 11111 11000 00100 00010

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

Geohash 算法学习

01101 11111 11000 00100 00010  

13       31      24       4         2

e         z         s       4          2

最后的Geohash 编码为:ezs42 

应用场景:

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

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

Geohash 算法学习

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

Geohash 算法学习

  当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

 

你可能感兴趣的:(hash)