geohash 算法原理及實現方式

轉自:http://www.cnblogs.com/dengxinglin/archive/2012/12/14/2817761.htmlphp


geohash 算法原理及實現方式

一、geohash 特色html

二、geohash 原理java

三、geohash 的 php 、python、java、C# 實現代碼python

四、觀點討論git

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

geohash 有如下幾個特色:web

首先,geohash 用一個字符串表示經度和緯度兩個座標。某些狀況下沒法在兩列上同時應用索引 (例如 MySQL 4 以前的版本,Google App Engine 的數據層等),利用geohash,只需在一列上應用索引便可。算法

其次,geohash 表示的並非一個點,而是一個矩形區域。好比編碼 wx4g0ec19,它表示的是一個矩形區域。 使用者能夠發佈地址編碼,既能代表本身位於北海公園附近,又不至於暴露本身的精確座標,有助於隱私保護。數據庫

第三,編碼的前綴能夠表示更大的區域。例如wx4g0ec1,它的前綴wx4g0e表示包含編碼wx4g0ec1在內的更大範圍。 這個特性能夠用於附近地點搜索。首先根據用戶當前座標計算geohash(例如wx4g0ec1)而後取其前綴進行查詢 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),便可查詢附近的全部地點。svn

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-九、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

 

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

 

做者:劃風
郵箱:emaisi@hotmail.com
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利.
相關文章
相關標籤/搜索