## geohash算法原理及实现方式

1、geohash特点

2、geohash原理

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

4、观点讨论

geohash有以下几个特点：

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

Geohash的原理

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

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

 纬度范围 划分区间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

 经度范围 划分区间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

 十进制 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

php：

geohash.class.php

View Code
  1 <?php
2 /**
3  * Geohash generation class
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 ?>

python：

python版本的geohash：python-geohash

java：

View Code
  1 import java.io.File;
2 import java.io.FileInputStream;
3 import java.util.BitSet;
4 import java.util.HashMap;
5
6
7 public class Geohash {
8
9     private static int numbits = 6 * 5;
10     final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
11             '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
12             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
13
14     final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
15     static {
16         int i = 0;
17         for (char c : digits)
18             lookup.put(c, i++);
19     }
20
21     public static void main(String[] args)  throws Exception{
22
23         System.out.println(new Geohash().encode(45, 125));
24
25     }
26
27     public double[] decode(String geohash) {
28         StringBuilder buffer = new StringBuilder();
29         for (char c : geohash.toCharArray()) {
30
31             int i = lookup.get(c) + 32;
32             buffer.append( Integer.toString(i, 2).substring(1) );
33         }
34
35         BitSet lonset = new BitSet();
36         BitSet latset = new BitSet();
37
38         //even bits
39         int j =0;
40         for (int i=0; i< numbits*2;i+=2) {
41             boolean isSet = false;
42             if ( i < buffer.length() )
43               isSet = buffer.charAt(i) == '1';
44             lonset.set(j++, isSet);
45         }
46
47         //odd bits
48         j=0;
49         for (int i=1; i< numbits*2;i+=2) {
50             boolean isSet = false;
51             if ( i < buffer.length() )
52               isSet = buffer.charAt(i) == '1';
53             latset.set(j++, isSet);
54         }
55
56         double lon = decode(lonset, -180, 180);
57         double lat = decode(latset, -90, 90);
58
59         return new double[] {lat, lon};
60     }
61
62     private double decode(BitSet bs, double floor, double ceiling) {
63         double mid = 0;
64         for (int i=0; i<bs.length(); i++) {
65             mid = (floor + ceiling) / 2;
66             if (bs.get(i))
67                 floor = mid;
68             else
69                 ceiling = mid;
70         }
71         return mid;
72     }
73
74
75     public String encode(double lat, double lon) {
76         BitSet latbits = getBits(lat, -90, 90);
77         BitSet lonbits = getBits(lon, -180, 180);
78         StringBuilder buffer = new StringBuilder();
79         for (int i = 0; i < numbits; i++) {
80             buffer.append( (lonbits.get(i))?'1':'0');
81             buffer.append( (latbits.get(i))?'1':'0');
82         }
83         return base32(Long.parseLong(buffer.toString(), 2));
84     }
85
86     private BitSet getBits(double lat, double floor, double ceiling) {
87         BitSet buffer = new BitSet(numbits);
88         for (int i = 0; i < numbits; i++) {
89             double mid = (floor + ceiling) / 2;
90             if (lat >= mid) {
91                 buffer.set(i);
92                 floor = mid;
93             } else {
94                 ceiling = mid;
95             }
96         }
97         return buffer;
98     }
99
100     public static String base32(long i) {
101         char[] buf = new char[65];
102         int charPos = 64;
103         boolean negative = (i < 0);
104         if (!negative)
105             i = -i;
106         while (i <= -32) {
107             buf[charPos--] = digits[(int) (-(i % 32))];
108             i /= 32;
109         }
110         buf[charPos] = digits[(int) (-i)];
111
112         if (negative)
113             buf[--charPos] = '-';
114         return new String(buf, charPos, (65 - charPos));
115     }
116
117 }  

C#：

C#版本的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                 {
83                     if (even)
84                     {
86                     }
87                     else
88                     {
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 }

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

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

1.两个离的越近，geohash的结果相同的位数越多，对么？

2.既然不能做到将相近的点hash值也相近，那么geohash的意义何在呢？

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

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

*在纬度相等的情况下：

*经度每隔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

posted on 2012-12-14 11:45  划风  阅读(49170)  评论(1编辑  收藏  举报