geohash算法原理及实现方式

时间:2023-03-09 00:31:49
geohash算法原理及实现方式

1、geohash特点

2、geohash原理

3、geohash的php 、python、java、C#实现代码

4、观点讨论

w微博:http://weibo.com/dxl0321

geohash有以下几个特点:

首先,geohash用一个字符串表示经度和纬度两个坐标。某些情况下无法在两列上同时应用索引 (例如MySQL 4之前的版本,Google App Engine的数据层等),利用geohash,只需在一列上应用索引即可。

其次,geohash表示的并不是一个点,而是一个矩形区域。比如编码wx4g0ec19,它表示的是一个矩形区域。 使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。

第三,编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。首先根据用户当前坐标计算geohash(例如wx4g0ec1)然后取其前缀进行查询 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),即可查询附近的所有地点。

Geohash比直接用经纬度的高效很多。

Geohash的原理

Geohash的最简单的解释就是:将一个经纬度信息,转换成一个可以排序,可以比较的字符串编码

首先将纬度范围(-90, 90)平分成两个区间(-90,0)、(0, 90),如果目标纬度位于前一个区间,则编码为0,否则编码为1。

由于39.92324属于(0, 90),所以取编码为1。

然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0。

以此类推,直到精度符合要求为止,得到纬度编码为1011 1000 1100 0111 1001。

纬度范围

划分区间0

划分区间1

39.92324所属区间

(-90, 90)

(-90, 0.0)

(0.0, 90)

1

(0.0, 90)

(0.0, 45.0)

(45.0, 90)

0

(0.0, 45.0)

(0.0, 22.5)

(22.5, 45.0)

1

(22.5, 45.0)

(22.5, 33.75)

(33.75, 45.0)

1

(33.75, 45.0)

(33.75, 39.375)

(39.375, 45.0)

1

(39.375, 45.0)

(39.375, 42.1875)

(42.1875, 45.0)

0

(39.375, 42.1875)

(39.375, 40.7812)

(40.7812, 42.1875)

0

(39.375, 40.7812)

(39.375, 40.0781)

(40.0781, 40.7812)

0

(39.375, 40.0781)

(39.375, 39.7265)

(39.7265, 40.0781)

1

(39.7265, 40.0781)

(39.7265, 39.9023)

(39.9023, 40.0781)

1

(39.9023, 40.0781)

(39.9023, 39.9902)

(39.9902, 40.0781)

0

(39.9023, 39.9902)

(39.9023, 39.9462)

(39.9462, 39.9902)

0

(39.9023, 39.9462)

(39.9023, 39.9243)

(39.9243, 39.9462)

0

(39.9023, 39.9243)

(39.9023, 39.9133)

(39.9133, 39.9243)

1

(39.9133, 39.9243)

(39.9133, 39.9188)

(39.9188, 39.9243)

1

(39.9188, 39.9243)

(39.9188, 39.9215)

(39.9215, 39.9243)

1

经度也用同样的算法,对(-180, 180)依次细分,得到116.3906的编码为1101 0010 1100 0100 0100。

经度范围

划分区间0

划分区间1

116.3906所属区间

(-180, 180)

(-180, 0.0)

(0.0, 180)

1

(0.0, 180)

(0.0, 90.0)

(90.0, 180)

1

(90.0, 180)

(90.0, 135.0)

(135.0, 180)

0

(90.0, 135.0)

(90.0, 112.5)

(112.5, 135.0)

1

(112.5, 135.0)

(112.5, 123.75)

(123.75, 135.0)

0

(112.5, 123.75)

(112.5, 118.125)

(118.125, 123.75)

0

(112.5, 118.125)

(112.5, 115.312)

(115.312, 118.125)

1

(115.312, 118.125)

(115.312, 116.718)

(116.718, 118.125)

0

(115.312, 116.718)

(115.312, 116.015)

(116.015, 116.718)

1

(116.015, 116.718)

(116.015, 116.367)

(116.367, 116.718)

1

(116.367, 116.718)

(116.367, 116.542)

(116.542, 116.718)

0

(116.367, 116.542)

(116.367, 116.455)

(116.455, 116.542)

0

(116.367, 116.455)

(116.367, 116.411)

(116.411, 116.455)

0

(116.367, 116.411)

(116.367, 116.389)

(116.389, 116.411)

1

(116.389, 116.411)

(116.389, 116.400)

(116.400, 116.411)

0

(116.389, 116.400)

(116.389, 116.394)

(116.394, 116.400)

0

接下来将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,得到编码 11100 11101 00100 01111 00000 01101 01011 00001。

最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(39.92324, 116.3906)的编码为wx4g0ec1。

十进制

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

base32

0

1

2

3

4

5

6

7

8

9

b

c

d

e

f

g

十进制

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

base32

h

j

k

m

n

p

q

r

s

t

u

v

w

x

y

z

解码算法与编码算法相反,先进行base32解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可,这里不再赘述。

实现代码:

php版本的实现方式:http://blog.dixo.net/downloads/geohash-php-class/  我下载了一个上传的

 php:

geohash.class.php

geohash算法原理及实现方式
  1 <?php
2 /**
3 * Geohash generation class
4 * http://blog.dixo.net/downloads/
5 *
6 * This file copyright (C) 2008 Paul Dixon (paul@elphin.com)
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 3
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23
24
25 /**
26 * Encode and decode geohashes
27 *
28 */
29 class Geohash
30 {
31 private $coding="0123456789bcdefghjkmnpqrstuvwxyz";
32 private $codingMap=array();
33
34 public function Geohash()
35 {
36 //build map from encoding char to 0 padded bitfield
37 for($i=0; $i<32; $i++)
38 {
39 $this->codingMap[substr($this->coding,$i,1)]=str_pad(decbin($i), 5, "0", STR_PAD_LEFT);
40 }
41
42 }
43
44 /**
45 * Decode a geohash and return an array with decimal lat,long in it
46 */
47 public function decode($hash)
48 {
49 //decode hash into binary string
50 $binary="";
51 $hl=strlen($hash);
52 for($i=0; $i<$hl; $i++)
53 {
54 $binary.=$this->codingMap[substr($hash,$i,1)];
55 }
56
57 //split the binary into lat and log binary strings
58 $bl=strlen($binary);
59 $blat="";
60 $blong="";
61 for ($i=0; $i<$bl; $i++)
62 {
63 if ($i%2)
64 $blat=$blat.substr($binary,$i,1);
65 else
66 $blong=$blong.substr($binary,$i,1);
67
68 }
69
70 //now concert to decimal
71 $lat=$this->binDecode($blat,-90,90);
72 $long=$this->binDecode($blong,-180,180);
73
74 //figure out how precise the bit count makes this calculation
75 $latErr=$this->calcError(strlen($blat),-90,90);
76 $longErr=$this->calcError(strlen($blong),-180,180);
77
78 //how many decimal places should we use? There's a little art to
79 //this to ensure I get the same roundings as geohash.org
80 $latPlaces=max(1, -round(log10($latErr))) - 1;
81 $longPlaces=max(1, -round(log10($longErr))) - 1;
82
83 //round it
84 $lat=round($lat, $latPlaces);
85 $long=round($long, $longPlaces);
86
87 return array($lat,$long);
88 }
89
90
91 /**
92 * Encode a hash from given lat and long
93 */
94 public function encode($lat,$long)
95 {
96 //how many bits does latitude need?
97 $plat=$this->precision($lat);
98 $latbits=1;
99 $err=45;
100 while($err>$plat)
101 {
102 $latbits++;
103 $err/=2;
104 }
105
106 //how many bits does longitude need?
107 $plong=$this->precision($long);
108 $longbits=1;
109 $err=90;
110 while($err>$plong)
111 {
112 $longbits++;
113 $err/=2;
114 }
115
116 //bit counts need to be equal
117 $bits=max($latbits,$longbits);
118
119 //as the hash create bits in groups of 5, lets not
120 //waste any bits - lets bulk it up to a multiple of 5
121 //and favour the longitude for any odd bits
122 $longbits=$bits;
123 $latbits=$bits;
124 $addlong=1;
125 while (($longbits+$latbits)%5 != 0)
126 {
127 $longbits+=$addlong;
128 $latbits+=!$addlong;
129 $addlong=!$addlong;
130 }
131
132
133 //encode each as binary string
134 $blat=$this->binEncode($lat,-90,90, $latbits);
135 $blong=$this->binEncode($long,-180,180,$longbits);
136
137 //merge lat and long together
138 $binary="";
139 $uselong=1;
140 while (strlen($blat)+strlen($blong))
141 {
142 if ($uselong)
143 {
144 $binary=$binary.substr($blong,0,1);
145 $blong=substr($blong,1);
146 }
147 else
148 {
149 $binary=$binary.substr($blat,0,1);
150 $blat=substr($blat,1);
151 }
152 $uselong=!$uselong;
153 }
154
155 //convert binary string to hash
156 $hash="";
157 for ($i=0; $i<strlen($binary); $i+=5)
158 {
159 $n=bindec(substr($binary,$i,5));
160 $hash=$hash.$this->coding[$n];
161 }
162
163
164 return $hash;
165 }
166
167 /**
168 * What's the maximum error for $bits bits covering a range $min to $max
169 */
170 private function calcError($bits,$min,$max)
171 {
172 $err=($max-$min)/2;
173 while ($bits--)
174 $err/=2;
175 return $err;
176 }
177
178 /*
179 * returns precision of number
180 * precision of 42 is 0.5
181 * precision of 42.4 is 0.05
182 * precision of 42.41 is 0.005 etc
183 */
184 private function precision($number)
185 {
186 $precision=0;
187 $pt=strpos($number,'.');
188 if ($pt!==false)
189 {
190 $precision=-(strlen($number)-$pt-1);
191 }
192
193 return pow(10,$precision)/2;
194 }
195
196
197 /**
198 * create binary encoding of number as detailed in http://en.wikipedia.org/wiki/Geohash#Example
199 * removing the tail recursion is left an exercise for the reader
200 */
201 private function binEncode($number, $min, $max, $bitcount)
202 {
203 if ($bitcount==0)
204 return "";
205
206 #echo "$bitcount: $min $max<br>";
207
208 //this is our mid point - we will produce a bit to say
209 //whether $number is above or below this mid point
210 $mid=($min+$max)/2;
211 if ($number>$mid)
212 return "1".$this->binEncode($number, $mid, $max,$bitcount-1);
213 else
214 return "0".$this->binEncode($number, $min, $mid,$bitcount-1);
215 }
216
217
218 /**
219 * decodes binary encoding of number as detailed in http://en.wikipedia.org/wiki/Geohash#Example
220 * removing the tail recursion is left an exercise for the reader
221 */
222 private function binDecode($binary, $min, $max)
223 {
224 $mid=($min+$max)/2;
225
226 if (strlen($binary)==0)
227 return $mid;
228
229 $bit=substr($binary,0,1);
230 $binary=substr($binary,1);
231
232 if ($bit==1)
233 return $this->binDecode($binary, $mid, $max);
234 else
235 return $this->binDecode($binary, $min, $mid);
236 }
237 }
238
239
240
241
242
243
244 ?>
geohash算法原理及实现方式

python:

python版本的geohash:python-geohash

java:

java版本的geohash,实现:http://code.google.com/p/geospatialweb/source/browse/#svn/trunk/geohash/src

C#:

geohash算法原理及实现方式

C#版本的geohash代

geohash算法原理及实现方式
  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
43 public static String CalculateAdjacent(String hash, Direction direction)
44 {
45 hash = hash.ToLower();
46
47 char lastChr = hash[hash.Length - 1];
48 int type = hash.Length%2;
49 var dir = (int) direction;
50 string nHash = hash.Substring(0, hash.Length - 1);
51
52 if (Borders[type][dir].IndexOf(lastChr) != -1)
53 {
54 nHash = CalculateAdjacent(nHash, (Direction) dir);
55 }
56 return nHash + Base32[Neighbors[type][dir].IndexOf(lastChr)];
57 }
58
59 public static void RefineInterval(ref double[] interval, int cd, int mask)
60 {
61 if ((cd & mask) != 0)
62 {
63 interval[0] = (interval[0] + interval[1])/2;
64 }
65 else
66 {
67 interval[1] = (interval[0] + interval[1])/2;
68 }
69 }
70
71 public static double[] Decode(String geohash)
72 {
73 bool even = true;
74 double[] lat = {-90.0, 90.0};
75 double[] lon = {-180.0, 180.0};
76
77 foreach (char c in geohash)
78 {
79 int cd = Base32.IndexOf(c);
80 for (int j = 0; j < 5; j++)
81 {
82 int mask = Bits[j];
83 if (even)
84 {
85 RefineInterval(ref lon, cd, mask);
86 }
87 else
88 {
89 RefineInterval(ref lat, cd, mask);
90 }
91 even = !even;
92 }
93 }
94
95 return new[] {(lat[0] + lat[1])/2, (lon[0] + lon[1])/2};
96 }
97
98 public static String Encode(double latitude, double longitude, int precision = 12)
99 {
100 bool even = true;
101 int bit = 0;
102 int ch = 0;
103 string geohash = "";
104
105 double[] lat = {-90.0, 90.0};
106 double[] lon = {-180.0, 180.0};
107
108 if (precision < 1 || precision > 20) precision = 12;
109
110 while (geohash.Length < precision)
111 {
112 double mid;
113
114 if (even)
115 {
116 mid = (lon[0] + lon[1])/2;
117 if (longitude > mid)
118 {
119 ch |= Bits[bit];
120 lon[0] = mid;
121 }
122 else
123 lon[1] = mid;
124 }
125 else
126 {
127 mid = (lat[0] + lat[1])/2;
128 if (latitude > mid)
129 {
130 ch |= Bits[bit];
131 lat[0] = mid;
132 }
133 else
134 lat[1] = mid;
135 }
136
137 even = !even;
138 if (bit < 4)
139 bit++;
140 else
141 {
142 geohash += Base32[ch];
143 bit = 0;
144 ch = 0;
145 }
146 }
147 return geohash;
148 }
149 }
150 }
geohash算法原理及实现方式

C#代码来自:https://github.com/sharonjl/geohash-net

geohash算法原理及实现方式

geohash演示:http://openlocation.org/geohash/geohash-js/

各种版本下载:打包下载

观点讨论

引用阿里云以为技术专家的博客上的讨论:

1.两个离的越近,geohash的结果相同的位数越多,对么?
这一点是有些用户对geohash的误解,虽然geo确实尽可能的将位置相近的点hash到了一起,可是这并不是严格意义上的(实际上也并不可能,因为毕竟多一维坐标),
例如在方格4的左下部分的点和大方格1的右下部分的点离的很近,可是它们的geohash值一定是相差的相当远,因为头一次的分块就相差太大了,很多时候我们对geohash的值进行简单的排序比较,结果貌似真的能够找出相近的点,并且似乎还是按照距离的远近排列的,可是实际上会有一些点被漏掉了。
上述这个问题,可以通过搜索一个格子,周围八个格子的数据,统一获取后再进行过滤。这样就在编码层次解决了这个问题。
2.既然不能做到将相近的点hash值也相近,那么geohash的意义何在呢?
我觉得geohash还是相当有用的一个算法,毕竟这个算法通过无穷的细分,能确保将每一个小块的geohash值确保在一定的范围之内,这样就为灵活的周边查找和范围查找提供了可能。

常见的一些应用场景

A、如果想查询附近的点?如何操作

查出改点的gehash值,然后到数据库里面进行前缀匹配就可以了。

B、如果想查询附近点,特定范围内,例如一个点周围500米的点,如何搞?

可以查询结果,在结果中进行赛选,将geohash进行解码为经纬度,然后进行比较

*在纬度相等的情况下:

*经度每隔0.00001度,距离相差约1米;

*每隔0.0001度,距离相差约10米;

*每隔0.001度,距离相差约100米;

*每隔0.01度,距离相差约1000米;

*每隔0.1度,距离相差约10000米。

*在经度相等的情况下:

*纬度每隔0.00001度,距离相差约1.1米;

*每隔0.0001度,距离相差约11米;

*每隔0.001度,距离相差约111米;

*每隔0.01度,距离相差约1113米;

*每隔0.1度,距离相差约11132米。

Geohash,如果geohash的位数是6位数的时候,大概为附近1千米…

参考资料:

http://iamzhongyong.iteye.com/blog/1399333

http://tech.idv2.com/2011/06/17/location-search/

http://blog.sina.com.cn/s/blog_62ba0fdd0100tul4.html