學習geohash算法

 基於地址進行數據的檢索,這個貌似有點難度,如果是小的應用的話,可以根據經緯度信息來直接進行查詢或者通過數據庫本身的空間數據檢索方案,但是如果數據量以及訪問請求變大時,這中方案就顯然不是很合適,往往會使請求變的很慢。

          經過一系列的溝通下來,可以通過geohash的方案來解決這個問題。

基本流程可以是這樣:

      (1)原始詳細地址數據--->經緯度數值--->geohash字符串編碼--->數據冗餘保存,主鍵換爲geohash,然後原始數據後置保存

      (2)請求接口,參數爲詳細地址,詳細地址進行轉換成geohash,然後基於geohash編碼來進行搜索和排序,返回結果

        詳細地址轉換爲經緯度這個可以直接調取成熟的geocoding服務來進行解決,地址規範的情況下,定位到街道應該不會很大,雖然有時候有一定的偏差,但是民用的話基本可以接受呵呵。

         所以目前的流程的話卡在了geohash算法這裏,所以寫這篇文章詳細的介紹一下。

geohash的最簡單解釋:將一個經緯度信息,轉換成一個可排序、可比較的字符串編碼。

         將經緯度的信息,按照(-90,90)(-180,180)來轉換成平面座標系。

         借用一篇文章中的例子來說明一下編碼生成的過程:

         首先將緯度範圍(-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。

                                

           經度也用同樣的算法,對(-180, 180)依次細分,得到116.3906的編碼爲1101 0010 1100 0100 0100。

                              

           接下來將經度和緯度的編碼合併,奇數位是緯度,偶數位是經度,得到編碼 11100 11101 00100 01111 00000 01101 01011 00001。

           最後,用0-9、b-z(去掉a, i, l, o)這32個字母進行base32編碼,得到(39.92324, 116.3906)的編碼爲wx4g0ec1。

                                

           解碼算法與編碼算法相反,先進行base32解碼,然後分離出經緯度,最後根據二進制編碼對經緯度範圍進行細分即可,這裏不再贅述。

           不過由於geohash表示的是區間,編碼越長越精確,但不可能解碼出完全一致的地址。

           引用阿里雲以爲技術專家的博客上的討論:

              

          常見的一些應用場景

          A、如果想查詢附近的點?如何操作

                 查出改點的gehash值,然後到數據庫裏面進行前綴匹配就可以了。

          B、如果想查詢附近點,特定範圍內,例如一個點周圍500米的點,如何搞?

                可以查詢結果,在結果中進行賽選,將geohash進行解碼爲經緯度,然後進行比較

         1、在緯度相等的情況下:

              經度每隔0.00001度,距離相差約1米

              經度每隔0.0001度,距離相差約10米

              經度每隔0.001度,距離相差約100米

              經度每隔0.01度,距離相差約1000米

              經度每隔0.1度,距離相差約10000米

          2、在經度相等的情況下:

              緯度每隔0.00001度,距離相差約1.1米

              緯度每隔0.0001度,距離相差約11米

              緯度每隔0.001度,距離相差約111米

              緯度每隔0.01度,距離相差約1113米

              緯度每隔0.1度,距離相差約11132米

        代碼直接貼出來,感興趣的直接運行一下吧呵呵。

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. import java.text.DecimalFormat;  
  2. import java.util.BitSet;  
  3. import java.util.HashMap;  
  4.   
  5. public class Geohash {  
  6.   
  7.     private static int numbits = 6 * 5;  
  8.     private static String data = "y8dcb88bgcqs#KP2#wx4g0ebcgcnw#KP2#wx4g0ec9er26#KP2#wx4g0ec9g30q";  
  9.     final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',  
  10.             '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',  
  11.             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };  
  12.   
  13.     final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();  
  14.     static {  
  15.         int i = 0;  
  16.         for (char c : digits)  
  17.             lookup.put(c, i++);  
  18.     }  
  19.   
  20.     public static void main(String[] args) throws Exception {  
  21.         double lon = 116.39036, lat = 39.92324;  
  22.         System.out.println(new Geohash().encode(39.92325, 116.39136));  
  23.         System.out.println(getFinalResult(data, 100, lon, lat));  
  24.     }  
  25.   
  26.     public static double[] decode(String geohash) {  
  27.         StringBuilder buffer = new StringBuilder();  
  28.         for (char c : geohash.toCharArray()) {  
  29.   
  30.             int i = lookup.get(c) + 32;  
  31.             buffer.append(Integer.toString(i, 2).substring(1));  
  32.         }  
  33.   
  34.         BitSet lonset = new BitSet();  
  35.         BitSet latset = new BitSet();  
  36.   
  37.         // even bits  
  38.         int j = 0;  
  39.         for (int i = 0; i < numbits * 2; i += 2) {  
  40.             boolean isSet = false;  
  41.             if (i < buffer.length())  
  42.                 isSet = buffer.charAt(i) == '1';  
  43.             lonset.set(j++, isSet);  
  44.         }  
  45.   
  46.         // odd bits  
  47.         j = 0;  
  48.         for (int i = 1; i < numbits * 2; i += 2) {  
  49.             boolean isSet = false;  
  50.             if (i < buffer.length())  
  51.                 isSet = buffer.charAt(i) == '1';  
  52.             latset.set(j++, isSet);  
  53.         }  
  54.   
  55.         double lon = decode(lonset, -180, 180);  
  56.         double lat = decode(latset, -90, 90);  
  57.   
  58.         return new double[] { lon, lat };  
  59.     }  
  60.   
  61.     private static double decode(BitSet bs, double floor, double ceiling) {  
  62.         double mid = 0;  
  63.         for (int i = 0; i < bs.length(); i++) {  
  64.             mid = (floor + ceiling) / 2;  
  65.             if (bs.get(i))  
  66.                 floor = mid;  
  67.             else  
  68.                 ceiling = mid;  
  69.         }  
  70.         return mid;  
  71.     }  
  72.   
  73.     public String encode(double lat, double lon) {  
  74.         BitSet latbits = getBits(lat, -90, 90);  
  75.         BitSet lonbits = getBits(lon, -180, 180);  
  76.         StringBuilder buffer = new StringBuilder();  
  77.         for (int i = 0; i < numbits; i++) {  
  78.             buffer.append((lonbits.get(i)) ? '1' : '0');  
  79.             buffer.append((latbits.get(i)) ? '1' : '0');  
  80.         }  
  81.         return base32(Long.parseLong(buffer.toString(), 2));  
  82.     }  
  83.   
  84.     private BitSet getBits(double lat, double floor, double ceiling) {  
  85.         BitSet buffer = new BitSet(numbits);  
  86.         for (int i = 0; i < numbits; i++) {  
  87.             double mid = (floor + ceiling) / 2;  
  88.             if (lat >= mid) {  
  89.                 buffer.set(i);  
  90.                 floor = mid;  
  91.             } else {  
  92.                 ceiling = mid;  
  93.             }  
  94.         }  
  95.         return buffer;  
  96.     }  
  97.   
  98.     public static String base32(long i) {  
  99.         char[] buf = new char[65];  
  100.         int charPos = 64;  
  101.         boolean negative = (i < 0);  
  102.         if (!negative)  
  103.             i = -i;  
  104.         while (i <= -32) {  
  105.             buf[charPos--] = digits[(int) (-(i % 32))];  
  106.             i /= 32;  
  107.         }  
  108.         buf[charPos] = digits[(int) (-i)];  
  109.   
  110.         if (negative)  
  111.             buf[--charPos] = '-';  
  112.         return new String(buf, charPos, (65 - charPos));  
  113.     }  
  114.     /**  
  115.      * 獲取圓內的所有結果  
  116.      * @param myData sql中like前4位(wq32%)  
  117.      * @param radius 附近距離相當於一個圓的半徑  
  118.      * @param longitude 經度  
  119.      * @param latitude 緯度  
  120.      * @return  
  121.      */  
  122.     public static String getFinalResult(String myData, int radius,  
  123.             double longitude, double latitude) {  
  124.         String finalResult = "";  
  125.         try {  
  126.             if (myData != null && !"".equals(myData)) {  
  127.                 // 實際經度半徑  
  128.                 double lonRadius = radius % 1000 == 0 ? (0.01 * radius / 1000)  
  129.                         : (radius % 100 == 0 ? (0.001 * radius / 100)  
  130.                                 : (radius % 10 == 0 ? (0.0001 * radius / 10)  
  131.                                         : (0.00001 * radius)));  
  132.                 // 實際緯度半徑  
  133.                 double latRadius = radius % 1000 == 0 ? (0.01 * radius * radius / (1113 * 1000))  
  134.                         : (radius % 100 == 0 ? (0.001 * radius * radius / (100 * 111))  
  135.                                 : (radius % 10 == 0 ? (0.0001 * radius * radius / (10 * 11))  
  136.                                         : (0.00001 * radius * radius / 1.1)));  
  137.                 String[] dataSplit = myData.split("#KP2#");  
  138.                 for (int i = 0; i < dataSplit.length; i++) {  
  139.                     String myTemp = dataSplit[i];  
  140.                     //當前的緯度  
  141.                     double currentLat = Geohash.decode(myTemp)[1];  
  142.                     //當前的經度  
  143.                     double currentLon = Geohash.decode(myTemp)[0];  
  144.                     //當前的緯度和圓中心的緯度差  
  145.                     double y = getFiveDecimal(Math.abs(latitude - currentLat)) * radius / latRadius;  
  146.                     //當前的經度和圓中心的經度差  
  147.                     double x = getFiveDecimal(Math.abs(longitude - currentLon)) * radius / lonRadius;  
  148.                     //判斷當前點是否在圓內  
  149.                     if ((x * x + y * y) <= (radius * radius)) {  
  150.                         finalResult += myTemp + "#KP2#";  
  151.                     }  
  152.                 }  
  153.             }  
  154.         } catch (Exception e) {  
  155.             e.printStackTrace();  
  156.             finalResult = "";  
  157.         }  
  158.         if (finalResult != null && !"".equals(finalResult)) {  
  159.             finalResult = finalResult.substring(0, finalResult.length() - 5);  
  160.         }  
  161.         return finalResult;  
  162.     }  
  163.   
  164.     // 獲取最多保留5位小數點  
  165.     public static double getFiveDecimal(double d) {  
  166.         DecimalFormat df = new DecimalFormat("0.00000");  
  167.         return Double.parseDouble(df.format(d));  
  168.     }  
  169.   
  170. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章