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

 


免責聲明!

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



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