Geohash 基本知识及 .NET 下计算相邻8个区域编码

目录

最近项目中需要搜索周边的 POI 信息,查找的过程中了解到了 Geohash ,这这里记录下以便自己牢记也和大家分享下。

一、简介

GeoHash是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串。GeoHash具有以下特点:
1、GeoHash用一个字符串表示经度和纬度两个坐标。在数据库中可以实现在一列上应用索引
2、GeoHash表示的并不是一个点,而是一个区域;
3、GeoHash编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索

二、计算方法

GeoHash的计算过程分为三步:
1、将经纬度转换成二进制:
比如这样一个点(39.923201, 116.390705)
纬度的范围是(-90,90),其中间值为0。对于纬度39.923201,在区间(0,90)中,因此得到一个1;(0,90)区间的中间值为45度,纬度39.923201小于45,因此得到一个0,依次计算下去,即可得到纬度的二进制表示,如下表:



最后得到纬度的二进制表示为:
10111000110001111001
同理可以得到经度116.390705的二进制表示为:
11010010110001000100
2、合并纬度、经度的二进制:
合并方法是将经度、纬度二进制按照奇偶位合并:
11100 11101 00100 01111 00000 01101 01011 00001
3、按照Base32进行编码:
Base32编码表(其中一种):

 

将上述合并后二进制编码后结果为:
wx4g0ec1

三、GeoHash的精度

下面是 GeoHash 精度对应的经纬度和误差

 

编码越长,表示的范围越小,位置也越精确。因此我们就可以通过比较GeoHash匹配的位数来判断两个点之间的大概距离。

四、查找相邻8个区域的Geohash编码(.NET)

为什么会有这样的算法,原因是Geohash是有缺点的,如下:

边缘附近的点,黄色的点要比黑色的点更加靠近红点,但是由于黑点跟红点的GeoHash前缀匹配数目更多,因此得到黑点更加靠近红点的结果(如下图)

这个问题的解决办法就是:筛选周围8个区域内的所有点,然后计算距离得到满足条件结果

下面是用C#写的在 .NET 平台下的寻找给定区域相邻的8个区域的代码

  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         /// <summary>
 43         /// 计算相邻
 44         /// </summary>
 45         /// <param name="hash"></param>
 46         /// <param name="direction"></param>
 47         /// <returns></returns>
 48         public static String CalculateAdjacent(String hash, Direction direction)
 49         {
 50             hash = hash.ToLower();
 51 
 52             char lastChr = hash[hash.Length - 1];
 53             int type = hash.Length%2;
 54             var dir = (int) direction;
 55             string nHash = hash.Substring(0, hash.Length - 1);
 56 
 57             if (Borders[type][dir].IndexOf(lastChr) != -1)
 58             {
 59                 nHash = CalculateAdjacent(nHash, (Direction) dir);
 60             }
 61             return nHash + Base32[Neighbors[type][dir].IndexOf(lastChr)];
 62         }
 63         /// <summary>
 64         /// 细化间隔
 65         /// </summary>
 66         /// <param name="interval"></param>
 67         /// <param name="cd"></param>
 68         /// <param name="mask"></param>
 69         public static void RefineInterval(ref double[] interval, int cd, int mask)
 70         {
 71             if ((cd & mask) != 0)
 72             {
 73                 interval[0] = (interval[0] + interval[1])/2;
 74             }
 75             else
 76             {
 77                 interval[1] = (interval[0] + interval[1])/2;
 78             }
 79         }
 80 
 81         /// <summary>
 82         /// 解码
 83         /// </summary>
 84         /// <param name="geohash"></param>
 85         /// <returns></returns>
 86         public static double[] Decode(String geohash)
 87         {
 88             bool even = true;
 89             double[] lat = {-90.0, 90.0};
 90             double[] lon = {-180.0, 180.0};
 91 
 92             foreach (char c in geohash)
 93             {
 94                 int cd = Base32.IndexOf(c);
 95                 for (int j = 0; j < 5; j++)
 96                 {
 97                     int mask = Bits[j];
 98                     if (even)
 99                     {
100                         RefineInterval(ref lon, cd, mask);
101                     }
102                     else
103                     {
104                         RefineInterval(ref lat, cd, mask);
105                     }
106                     even = !even;
107                 }
108             }
109 
110             return new[] {(lat[0] + lat[1])/2, (lon[0] + lon[1])/2};
111         }
112         /// <summary>
113         /// 编码
114         /// </summary>
115         /// <param name="latitude">纬度</param>
116         /// <param name="longitude">经度</param>
117         /// <param name="precision">精度</param>
118         /// <returns></returns>
119         public static String Encode(double latitude, double longitude, int precision = 12)
120         {
121             bool even = true;
122             int bit = 0;
123             int ch = 0;
124             string geohash = "";
125 
126             double[] lat = {-90.0, 90.0};
127             double[] lon = {-180.0, 180.0};
128 
129             if (precision < 1 || precision > 20) precision = 12;
130 
131             while (geohash.Length < precision)
132             {
133                 double mid;
134 
135                 if (even)
136                 {
137                     mid = (lon[0] + lon[1])/2;
138                     if (longitude > mid)
139                     {
140                         ch |= Bits[bit];
141                         lon[0] = mid;
142                     }
143                     else
144                         lon[1] = mid;
145                 }
146                 else
147                 {
148                     mid = (lat[0] + lat[1])/2;
149                     if (latitude > mid)
150                     {
151                         ch |= Bits[bit];
152                         lat[0] = mid;
153                     }
154                     else
155                         lat[1] = mid;
156                 }
157 
158                 even = !even;
159                 if (bit < 4)
160                     bit++;
161                 else
162                 {
163                     geohash += Base32[ch];
164                     bit = 0;
165                     ch = 0;
166                 }
167             }
168             return geohash;
169         }
170 
171         /// <summary>
172         /// 获取九个格子 顺序 本身 上、下、左、右、 左上、 右上、 左下、右下
173         /// </summary>
174         /// <param name="geohash"></param>
175         /// <returns></returns>
176         public static String[] getGeoHashExpand(String geohash)
177         { 
178         
179         try {
180             String geohashTop = CalculateAdjacent(geohash, Direction.Top);//
181 
182             String geohashBottom = CalculateAdjacent(geohash, Direction.Bottom);//
183 
184             String geohashLeft = CalculateAdjacent(geohash, Direction.Left);//
185 
186             String geohashRight = CalculateAdjacent(geohash, Direction.Right);//
187 
188 
189             String geohashTopLeft = CalculateAdjacent(geohashLeft, Direction.Top);//左上
190 
191             String geohashTopRight = CalculateAdjacent(geohashRight, Direction.Top);//右上
192 
193             String geohashBottomLeft = CalculateAdjacent(geohashLeft, Direction.Bottom);//左下
194 
195             String geohashBottomRight = CalculateAdjacent(geohashRight, Direction.Bottom);//右下
196 
197             String[] expand = { geohash, geohashTop, geohashBottom, geohashLeft, geohashRight, geohashTopLeft, geohashTopRight,  geohashBottomLeft, geohashBottomRight};
198             return expand;
199         } catch (Exception e) {
200             return null;
201         }
202         }
203 
204 
205         ///// <summary>
206         ///// test 
207         ///// </summary>
208         ///// <param name="args"></param>
209         //public  void main()
210         //{
211         //    double lat = 39.90403;
212         //    double lon = 116.407526; //需要查询经纬度,目前指向的是BeiJing
213         //    string hash = Geohash.Encode(lat, lon);
214         //    int geohashLen = 6;
215         //    /*获取中心点的geohash*/
216         //    String geohash = hash.Substring(0, geohashLen);
217         //    /*获取所有的矩形geohash, 一共是九个 ,包含中心点,打印顺序请参考参数*/
218         //    String[] result = Geohash.getGeoHashExpand(geohash);
219         //}
220     }
221 }
View Code

五、MySQL 中使用 GeoHash 

在MySQL 5.7 以后,对GIS空间数据的更好的支持,加上虚拟列可以很方便的使用GeoHash。

选择存储 GeoHash 的列,设置为“虚拟列”,在“表达式”里填入“st_geohash(`point`,6)”,其中 point 是要编码的点字段

参考:

https://blog.csdn.net/youhongaa/article/details/78816700

https://www.cnblogs.com/lucoo/p/5085986.html

posted @ 2018-10-31 21:22  漠里  阅读(1923)  评论(0编辑  收藏  举报