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>
来源:https://www.cnblogs.com/zrhai/p/3832070.html