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 }
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>