Geohash原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Geohash原理相关的知识,希望对你有一定的参考价值。

参考技术A         GeoHash本质上是空间索引的一种方式,其基本原理是将地球理解为一个二维平面,将平面递归分解成更小的子块,每个子块在一定经纬度范围内拥有相同的编码。以GeoHash方式建立空间索引,可以提高对空间poi数据进行经纬度检索的效率。

        GeoHash将二维的经纬度转换成字符串,比如下图展示了北京9个区域的GeoHash字符串,分别是WX4ER,WX4G2、WX4G3等等,每一个字符串代表了某一矩形区域。也就是说,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存。

        Geohash编码中,字符串相似的表示距离相近(特殊情况后文阐述),这样可以利用字符串的前缀匹配来查询附近的POI信息。如下两个图所示,一个在城区,一个在郊区,城区的GeoHash字符串之间比较相似,郊区的字符串之间也比较相似,而城区和郊区的GeoHash字符串相似程度要低些。此外,不同的编码长度,表示不同的范围区间,字符串越长,表示的范围越精确。

        以经纬度值:(116.389550, 39.928167)进行算法说明,对纬度39.928167进行逼近编码 (地球纬度区间是[-90,90]

    a. 区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.928167属于右区间[0,90],给标记为1

    b. 接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定39.928167属于左区间 [0,45),给标记为0

    c. 递归上述过程39.928167总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近39.928167

    d. 如果给定的纬度x(39.928167)属于左区间,则记录0,如果属于右区间则记录1,序列的长度跟给定的区间划分次数有关,如下图

        e. 同理,地球经度区间是[-180,180],可以对经度116.389550进行编码。通过上述计算, 纬度产生的编码为1 1 0 1 0 0 1 0 1 1 0 0 0 1 0,经度产生的编码为1 0 1 1 1 0 0 0 1 1 0 0 0  1 1

        f. 合并:偶数位放经度,奇数位放纬度,把2串编码组合生成新串如下图

        g. 首先将11100 11101 00100 01111 0000  01101转成十进制,对应着28、29、4、15,0,13 十进制对应的base32编码就是wx4g0e,如下图.

         h. 同理,将编码转换成经纬度的解码算法与之相反

        Geohash其实就是将整个地图或者某个分割所得的区域进行一次划分,由于采用的是base32编码方式,即Geohash中的每一个字母或者数字(如wx4g0e中的w)都是由5bits组成(2^5 = 32,base32),这5bits可以有32中不同的组合(0~31),这样我们可以将整个地图区域分为32个区域,通过00000 ~ 11111来标识这32个区域。第一次对地图划分后的情况如下图所示(每个区域中的编号对应于该区域所对应的编码)。

        Geohash的0、1串序列是经度0、1序列和纬度0、1序列中的数字交替进行排列的,偶数位对应的序列为经度序列,奇数位对应的序列为纬度序列,在进行第一次划分时,Geohash0、1序列中的前5个bits(11100),那么这5bits中有3bits是表示经度,2bits表示纬度,所以第一次划分时,是将经度划分成8个区段(2^3 = 8),将纬度划分为4个区段(2^2 = 4),这样就形成了32个区域。如下图

        同理,可以按照第一次划分所采用的方式对第一次划分所得的32个区域各自再次划分。

        上文讲了GeoHash的计算步骤,仅仅说明是什么而没有说明为什么?为什么分别给经度和维度编码?为什么需要将经纬度两串编码交叉组合成一串编码?本节试图回答这一问题。

        如图所示,我们将二进制编码的结果填写到空间中,当将空间划分为四块时候,编码的顺序分别是左下角00,左上角01,右下脚10,右上角11,也就是类似于Z的曲线,当我们递归的将各个块分解成更小的子块时,编码的顺序是自相似的(分形),每一个子快也形成Z曲线,这种类型的曲线被称为Peano空间填充曲线。

        这种类型的空间填充曲线的优点是将二维空间转换成一维曲线(事实上是分形维),对大部分而言,编码相似的距离也相近,但Peano空间填充曲线最大的缺点就是突变性,有些编码相邻但距离却相差很远,比如0111与1000,编码是相邻的,但距离相差很大。

        除Peano空间填充曲线外,还有很多空间填充曲线,如图所示,其中效果公认较好是Hilbert空间填充曲线,相较于Peano曲线而言,Hilbert曲线没有较大的突变。但是由于Peano曲线实现更加简单,在使用的时候配合一定的解决手段,可以很好的满足大部分需求,因此TD内部Geohash算法采用的是Peano空间填充曲线。

    a. 由于GeoHash是将区域划分为一个个规则矩形,并对每个矩形进行编码,这样在查询附近POI信息时会导致以下问题,比如红色的点是我们的位置,绿色的两个点分别是附近的两个餐馆,但是在查询的时候会发现距离较远餐馆的GeoHash编码与我们一样(因为在同一个GeoHash区域块上),而较近餐馆的GeoHash编码与我们不一致。这个问题往往产生在边界处。

        解决的思路很简单,我们查询时,除了使用定位点的GeoHash编码进行匹配外,还使用周围8个区域的GeoHash编码,这样可以避免这个问题。

    b. 我们已经知道现有的GeoHash算法使用的是Peano空间填充曲线,这种曲线会产生突变,造成了编码虽然相似但距离可能相差很大的问题,因此在查询附近餐馆时候,首先筛选GeoHash编码相似的POI点,然后进行实际距离计算。

    c. GeoHash Base32编码长度与精度。可以看出,当geohash base32编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右,编码长度需要根据数据情况进行选择。

        理解了geohash算法的基本原理之后,本节将介绍一个实际应用中常见的场景:计算围栏范围内所有的Geohash编码。该场景封装为函数可以表示如下:输入组成围栏的点经纬度坐标集合和指定的geohash长度,输出一组geohash编码。

        public static Set getHashByFence(List points, int length)

        具体算法步骤如下:

1. 输入围栏点坐标集合List points和指定的geohash长度length

2. 计算围栏的外包矩形的左上角和右下角坐标lat_min、lat_max、lng_min、lng_max

3. 根据lat_min、lat_max、lng_min、lng_max,计算外包矩形对角定点的距离d

4. 以外包矩形中心点为圆心,以d/2为半径做一个圆,计算圆覆盖范围内的geohash

    4.1 获取圆的外包矩形左上角和右下角定点坐标经纬度,存储到double[] locs

    4.2 根据geohash字符长度计算该长度geohash编码对应的经纬度间隔(latA,lngA)

    4.3 根据latA和lngA,计算出locs组成的矩形的左上角和右下角定点的经纬度,在geohash划分的网格的索引(也就是第几个),分别记为lat_min,lat_max,lng_min,lng_max

    4.4 计算lat_min,lat_max,lng_min,lng_max对应范围内左右geohash的二进制编码,然后将经纬度二进制编码uncode为geohash字符编码,保存为Set sets

5. 剔除sets中geohash编码对应矩形的中心点不在points围栏范围内的geohash,得到最终的geohash结果集

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

View Code
复制代码
  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 ?>
复制代码

 

python:

python版本的geohash:python-geohash

java:

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

View Code

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                 {
 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 }
复制代码

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

复制代码

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

以上是关于Geohash原理的主要内容,如果未能解决你的问题,请参考以下文章

geohash基本原理

GeoHash原理和可视化显示

地理空间索引:GeoHash原理

地理空间索引:GeoHash原理

Geohash精度和原理

GeoHash算法学习讲解解析及原理分析