Redis GeoHash核心原理解析

1. 引言

小麥同窗是個吃貨+技術宅,平日裏就喜歡拿着手機地圖點點按按來查詢一些好玩的東西。某一天到北海公園遊玩,肚肚餓了,因而乎打開手機地圖,搜索北海公園附近的餐館,並選了其中一家用餐。在這裏插入圖片描述 飽暖思yin欲的麥叔飯後思考地圖後臺如何根據本身所在位置查詢來查詢附近餐館的呢?苦思冥想了半天,小麥想出了個方法:計算所在位置P與北京全部餐館的距離,而後返回距離<=1000米的餐館。小得意了一下子,小麥發現北京的餐館何其多啊,這樣計算不得了,因而想了,既然知道經緯度了,那它應該知道本身在西城區,那應該計算所在位置P與西城區全部餐館的距離啊,機機運用了遞歸的思想,想到了西城區也不少餐館啊,應該計算所在位置P與所在街道全部餐館的距離,這樣計算量又小了,效率也提高了。html

小麥的計算思想很樸素,就是經過過濾的方法來減少參與計算的餐館數目,從某種角度上講,機機在使用索引技術。mysql

一提到索引,你們腦子裏立刻浮現出B樹索引,由於大量的數據庫(如MySQL、oracle、PostgreSQL等)都在使用B樹。B樹索引本質上是對索引字段進行排序,而後經過相似二分查找的方法進行快速查找,即它要求索引的字段是可排序的,通常而言,可排序的是一維字段,好比時間、年齡、薪水等等。可是對於空間上的一個點(二維,包括經度和緯度),如何排序呢?又如何索引呢?解決的方法不少,下文介紹一種方法來解決這一問題。web

思想:若是能經過某種方法將二維的點數據轉換成一維的數據,那樣不就能夠繼續使用B樹索引了嘛。那這種方法真的存在嘛,答案是確定的。目前很火的GeoHash算法就是運用了上述思想,下面咱們就開始GeoHash之旅吧。算法

2. 感性認識

先來兩個乾貨,在線查看GPS某個區域的GeoHash值。sql

1. geohash.gofreerange.com/

在這裏插入圖片描述
在這裏插入圖片描述

2. www.geohash.cn/

跟更好用些 在這裏插入圖片描述數據庫

3. 通俗說

GeoHash將二維的經緯度轉換成字符串,好比下圖展現了北京9個區域的GeoHash字符串,分別是WX4ER,WX4G二、WX4G3等等,每個字符串表明了某一矩形區域。也就是說,這個矩形區域內全部的點(經緯度座標)都共享相同的GeoHash字符串,這樣既能夠保護隱私(只表示大概區域位置而不是具體的點),又比較容易作緩存,好比左上角這個區域內的用戶不斷髮送位置信息請求餐館數據,因爲這些用戶的GeoHash字符串都是WX4ER,因此能夠把WX4ER看成key,把該區域的餐館信息看成value來進行緩存,而若是不使用GeoHash的話,因爲區域內的用戶傳來的經緯度是各不相同的,很難作緩存。 在這裏插入圖片描述 字符串越長,表示的範圍越精確。如圖所示,5位的編碼能表示10平方公里範圍的矩形區域,而6位編碼能表示更精細的區域(約0.34平方公里)在這裏插入圖片描述 字符串類似的表示距離相近(特殊狀況後文闡述),這樣能夠利用字符串的前綴匹配來查詢附近的POI信息。以下兩個圖所示,第一個在城區,第二個在郊區,城區的GeoHash字符串之間比較類似,郊區的字符串之間也比較類似,而城區和郊區的GeoHash字符串類似程度要低些。在這裏插入圖片描述 在這裏插入圖片描述 經過上面的介紹咱們知道了GeoHash就是一種將經緯度轉換成字符串的方法,而且使得在大部分狀況下,字符串前綴匹配越多的距離越近,回到咱們的案例,根據所在位置查詢來查詢附近餐館時,只須要將所在位置經緯度轉換成GeoHash字符串,並與各個餐館的GeoHash字符串進行前綴匹配,匹配越多的距離越近緩存

4. GeoHash算法的步驟

下面以北海公園附近隨便一個位置爲例介紹GeoHash算法的計算步驟,先用百度 GPS反定位系統查找看下經緯度。 在這裏插入圖片描述 緯度=116.395371,經度=39.931957。oracle

根據經緯度計算GeoHash二進制編碼

地球緯度區間是[-90,90], 北海公園的緯度是39.928167,能夠經過下面算法對緯度39.928167進行逼近編碼:編輯器

  1. 區間[-90,90]進行二分爲[-90,0),[0,90],稱爲左右區間,能夠肯定39.928167屬於右區間[0,90],給標記爲1;
  2. 接着將區間[0,90]進行二分爲 [0,45),[45,90],能夠肯定39.928167屬於左區間 [0,45),給標記爲0;
  3. 遞歸上述過程39.928167老是屬於某個區間[a,b]。隨着每次迭代區間[a,b]總在縮小,並愈來愈逼近39.928167;
  4. 若是給定的緯度x(39.928167)屬於左區間,則記錄0,若是屬於右區間則記錄1,這樣隨着算法的進行會產生一個序列1011100,序列的長度跟給定的區間劃分次數有關。

39.928167 根據緯度算編碼函數

bit min mid max
1 -90.000 0.000 90.000
0 0.000 45.000 90.000
1 0.000 22.500 45.000
1 22.500 33.750 45.000
1 33.750 39.375 45.000
0 39.375 42.188 45.000
0 39.375 40.7815 42.188
0 39.375 40.07825 40.7815
1 39.375 39.726625 40.07825
1 39.726625 39.9024375 40.07825

同理,地球經度區間是[-180,180],能夠對經度116.389550進行編碼。根據經度算編碼

bit min mid max
1 -180 0.000 180
1 0.000 90 180
0 90 135 180
1 90 112.5 135
0 112.5 123.75 135
0 112.5 118.125 123.75
1 112.5 115.3125 118.125
0 115.3125 116.71875 118.125
1 115.3125 116.015625 116.71875
1 116.015625 116.3671875 116.71875

組碼

經過上述計算,緯度產生的編碼爲10111 00011,經度產生的編碼爲11010 01011偶數位放經度,奇數位放緯度,把2串編碼組合生成新串:11100 11101 00100 01111。 最後使用用0-九、b-z(去掉a, i, l, o)這32個字母進行base32編碼,首先將11100 11101 00100 01111轉成十進制,對應着2八、2九、四、15,十進制對應的編碼就是wx4g。同理,將編碼轉換成經緯度的解碼算法與之相反,具體再也不贅述。 在這裏插入圖片描述

5. GeoHash Base32編碼長度與精度

能夠看出,當geohash base32編碼長度爲8時,精度在19米左右,而當編碼長度爲9時,精度在2米左右,編碼長度須要根據數據狀況進行選擇。 在這裏插入圖片描述

1、經緯度距離換算

在緯度相等的狀況下:
  • 經度每隔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米。

6. GeoHash算法

上文講了GeoHash的計算步驟,僅僅說明是什麼而沒有說明爲何?爲何分別給經度和維度編碼?爲何須要將經緯度兩串編碼交叉組合成一串編碼?本節試圖回答這一問題。

以下圖所示,咱們將二進制編碼的結果填寫到空間中,當將空間劃分爲四塊時候,編碼的順序分別是左下角00,左上角01,右下腳10,右上角11,也就是相似於Z的曲線,當咱們遞歸的將各個塊分解成更小的子塊時,編碼的順序是自類似的(分形),每個子快也造成Z曲線,這種類型的曲線被稱爲Peano空間填充曲線。

這種類型的空間填充曲線的優勢是將二維空間轉換成一維曲線(事實上是分形維),對大部分而言,編碼類似的距離也相近, 但Peano空間填充曲線最大的缺點就是突變性,有些編碼相鄰但距離卻相差很遠,好比0111與1000,編碼是相鄰的,但距離相差很大。   在這裏插入圖片描述 除Peano空間填充曲線外,還有不少空間填充曲線,如圖所示,其中效果公認較好是Hilbert空間填充曲線,相較於Peano曲線而言,Hilbert曲線沒有較大的突變。爲何GeoHash不選擇Hilbert空間填充曲線呢?多是Peano曲線思路以及計算上比較簡單吧,事實上,Peano曲線就是一種四叉樹線性編碼方式在這裏插入圖片描述

7. 使用注意點

1. 臨界問題

因爲GeoHash是將區域劃分爲一個個規則矩形,並對每一個矩形進行編碼,這樣在查詢附近POI信息時會致使如下問題,好比紅色的點是咱們的位置,綠色的兩個點分別是附近的兩個餐館,可是在查詢的時候會發現距離較遠餐館的GeoHash編碼與咱們同樣(由於在同一個GeoHash區域塊上),而較近餐館的GeoHash編碼與咱們不一致。這個問題每每產生在邊界處

解決的思路很簡單,咱們查詢時,除了使用定位點的GeoHash編碼進行匹配外,還使用周圍8個區域的GeoHash編碼,這樣能夠避免這個問題。

2. 注意點

咱們已經知道現有的GeoHash算法使用的是Peano空間填充曲線,這種曲線會產生突變,形成了編碼雖然類似但距離可能相差很大的問題,所以在查詢附近餐館時候,首先篩選GeoHash編碼類似的POI(point of interest)點,而後進行實際距離計算。

3. 使用心得

GeoHash只是空間索引的一種方式,特別適合點數據,而對線、面數據採用R樹索引更有優點(可爲何須要空間索引)。

GeoHash值能夠區分精度,位數越多,精度越高,表達的地理位置越精細;如一位的GeoHash值把地球劃分爲32個矩形,8位的geohash值把地球劃分爲32^8個小矩形

適合根據某個經緯度座標position計算出GeoHash值,而後和數據庫中精度更高的GeoHash值作前綴比較

8.空間索引

常見問題:如何根據本身所在位置查詢來查詢附近50米的POI(point of interest,好比商家、景點等)呢(圖1a)?在這裏插入圖片描述 每一個POI都有經緯度信息,用圖1b的SQL語句在mySQL中創建了POI_spatial的表,其中lat和lng兩個字段來表明緯度經度。爲後續分析方便起見,我人造了40萬個POI數據。

方法一:暴力方法

該方法的思路很直接:計算位置與全部POI的距離,並保留距離小於50米的POI。

插句題外話,計算經緯度之間的距離不能像求歐式距離那樣平方開根號,由於地球是個不規整的球體(圖2a),普通計算適合都是默認按最簡單的完美球體假設,兩點之間的距離函數應該如圖2b所示。

在這裏插入圖片描述 該方法的複雜度爲:40萬*距離函數。咱們將球體距離函數寫爲mysql存儲過程distance,以後咱們執行查詢操做(圖3),發現花費了4.66秒。 在這裏插入圖片描述

方法二:矩形過濾方法

該方法採用逐步細化的方式,通常分爲兩部:

  1. 先用矩形框過濾(圖4a),判斷一個點在矩形框內很簡單,只要進行兩次判斷(LtMin<lat<LtMax; LnMin<lng<LnMax),落在矩形框內的POI個數爲n(n<<40萬);
  2. 用球面距離公式計算位置與矩形框內n個POI的距離(圖4b),並保留距離小於50米的POI

矩形過濾方法的複雜度:40萬矩形過濾函數 + n距離函數(n<<40萬)。 在這裏插入圖片描述  根據這個思路咱們執行SQl查詢(圖5)(注: 經度或緯度每隔0.001度,距離相差約100米,由此推算出矩形左下角和右上角座標),發現過濾後正好剩下兩個POI。

此查詢花費了0.36秒,相比於方法一查詢時間大大下降,可是對於一次查詢來講仍是很長。時間長的緣由在於遍歷了40萬次。 在這裏插入圖片描述

方法三:B樹對經度或緯度創建索引

方法二耗時的緣由在於執行了遍歷操做,爲了避免進行遍歷,咱們天然想到了索引。咱們對緯度進行了B樹索引。

alter table poi_spatial add index latindex(lat);
alter table poi_spatial add index lngindex(lng); 複製代碼

此方法包括三個步驟:

  1. 經過B樹快速找到某緯度範圍的POI(圖6a),個數爲m(m<40萬),複雜度爲Log(40萬)*過濾函數;
  2. 在步驟a過濾獲得的m個POI中查找某經度範圍的POI(圖6b),個數爲n(n<m),複雜度爲m*過濾函數;
  3. 用球面距離公式計算位置與步驟b獲得的n個POI的距離(圖6c),並保留距離小於50米的POI

在這裏插入圖片描述 執行SQL查詢(圖7),發現時間已經大大下降,從方法2的0.36秒降低到0.01秒 在這裏插入圖片描述

3、B樹能索引空間數據嗎?

這時候有人會說了:方法三效果如此好,可以知足咱們附近POI查詢問題啊,看來B樹用來索引空間數據也是能夠的嘛! 那麼B樹真的可以索引空間數據嗎?

  1. 只能對經度或緯度索引(一維索引),與指望的不符 咱們期待的是快速找出落在某一空間範圍的POI(如矩形)(圖8a),而不是快速找出落在某緯度或經度範圍的POI(圖8b),想象一下,我要查詢北京某區的POI,可是B樹索引不只給我找出了北京的,還有與北京同一維度的天津、大同、甚至國外城市的POI,當數據量很大時,效率很低。在這裏插入圖片描述

  2. 當數據是多維,好比三維(x,y,z),B樹怎麼索引? 好比z多是高程值,也多是時間。有人會說B樹其實能夠對多個字段進行索引,但這時須要指定優先級,造成一個組合字段,而空間數據在各個維度方向上不存在優先級,咱們不能說緯度比經度更重要,也不能說緯度比高程更重要。

  3. 當空間數據不是點,而是線(道路、地鐵、河流等),面(行政區邊界、建築物等),B樹怎麼索引? 對於面來講,它由一系列首尾相連的經緯度座標點組成,一個面可能有成百上千個座標,這時數據庫怎麼存儲,B樹怎麼索引,這些都是問題。

既然傳統的索引不能很好的索引空間數據,咱們天然須要一種方法能對空間數據進行索引,即空間索引

參考

Java實現GPS範圍查找

浙大大佬通俗說GPS

相關文章
相關標籤/搜索