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匹配的位數來判斷兩個點之間的大概距離。

四、查找相鄰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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM