在Solr中基於空間地址查詢主要圍繞2個概念實現: 算法
Cartesian Tiers 笛卡爾層 編碼
Cartesian Tiers是經過將一個平面地圖的根據設定的層次數,將每層的分解成若干個網格,以下圖所示: 索引
每層以2的評方遞增,因此第一層爲4個網格,第二層爲16 個,因此整個地圖的經緯度將在每層的網格中體現: 圖片
笛卡爾層在Lucene中對空間地理位置查詢最大的用處在查找周邊地址的時候有效的減小查詢量,即將查詢量能夠控制在分層後最小的網格中的若干docId。那麼如何構建這樣的索引結構呢,其實很簡單,只須要對應笛卡爾層的層數來構建域便可。也便是tiers0->field_0,tiers1->field_1,tiers2-field_2,……,tiers19->field_19。(通常20層便可)。每一個對應笛卡爾層次的域將根據當前這條記錄的經緯度經過笛卡爾算法計算出歸屬於當前層的網格,而後將gridId(網格惟一標示)以term的方式存入索引。這樣每條記錄關於笛卡爾0-19的域將都會有一個gridId對應起來。可是查詢的時候通常是須要查周邊的地址,那麼可能周邊的範圍超過一個網格的範圍,那麼實際操做過程是根據經緯度和一個距離肯定出須要涉及查詢的從19-0(從高往低查,留給讀者思考)若干層對應的若干網格的數據(關於代碼實如今後面的文章內容闡述)。那麼一個經緯度周邊地址的查詢只須要以下圖圓圈內的數據: 字符串
因此經過這樣的數據過濾,將極大的減小計算量。 get
GeoHash算法 hash
在Lucene索引中將經緯度的二維座標經過geohash,變成一個一維的字符串base32的座標,例如,經緯度對應一個base32的座標爲DRT2Y,那這個base32的字符串什麼意思呢?其實編碼中每一個字符都是表明一個區域,而且前面的字符是後面字符的父區域,即R是D區域內的子區域,T又爲D區域的子區域,你們能夠從以下圖片得到base32的層級關係(如下圖片均來自互聯網): file
進入D區域,則看到又分爲若干區域,而R爲其子區域: 互聯網
繼續進入R區域,能夠繼續看到有子區域T區域: grid
而2Y也是基於以上的關係類推,因此一個base32的編碼是標示一個區域,而編碼過程當中會根據經緯度的精度來肯定這個區域大小。從上面的解釋你們確定會想到編碼的前綴是表示更大的區域。例如wx4g0ec1,它的前綴wx4g0e表示包含編碼wx4g0ec1在內的更大區域。因此根據這個特色,利用模糊查詢是能夠達到一種附近地點的查詢。
Geohash算法實現其實很是簡單,網上有不少例子,在這裏借用下這些例子再加上比較詳細的說明。基本算法流程是基於多輪的收斂,以達到知足精度要求爲止。具體流程以(39.92324 緯度, 116.3906 經度)爲例,首先將緯度的範圍(-90, 90)平分紅兩個區間(-90, 0)、(0, 90),若是目標緯度位在(-90,0),則編碼爲0,在(0,90)則編碼爲1。因爲上面的例子中維度39.92324是屬於(0, 90),因此第一輪得到的編碼位取1。接下來再將(0, 90)分紅 (0, 45), (45, 90)兩個區間,而39.92324位於(0, 45),因此編碼爲0。以此類推,直到精度符合要求爲止,以下圖所示:
因此經過16輪的計算後獲得經度39.92324的編碼爲:1011 1000 1100 0111 1001
經度也用一樣的算法,對(-180, 180)多輪的依次細分計算:
獲得經度116.3906的編碼爲1101 0010 1100 0100 0100
經緯度的編碼都計算完畢後,接下來就須要合併經緯度的編碼,規則是以經度開始,依次每次取一位合併成5位的新編碼,如上圖紅色字標示順序所示:
完成合並編碼後就須要將該編碼和base32編碼表對應起來,作法是每5位爲一個十進制數,以11100爲例,它的十進制數是28,因此對應的base32編碼表示W,以下圖所示:
其餘的五位編碼依次從表中找到對應位置後,(39.92324 緯度, 116.3906 經度)的base32編碼爲:wx4g0ec1
解碼算法與編碼算法相反,先進行base32解碼,而後分離出經緯度,最後根據二進制編碼對經緯度範圍進行細分便可,這裏再也不贅述。不過因爲geohash表示的是區間,編碼越長越精確,但不可能解碼出徹底一致的地址