在生活中我們有時候需要點外賣、騎共享單車等等,我們打開軟件找到附近餐廳、離我最近的單車,那麼他們是怎麼快速定位到的呢?我們把地圖看作一個二維平面,我們在某個點上然後找到附近10km內的所有餐廳,這時候我們知道求兩點直接的距離是需要一個公式,且需要知道兩點的x,y軸座標才能計算出距離,通過計算出距離然後進行排列,那麼我們就能過找到離我最近的一個餐廳了。
那麼這裏我們考慮幾個問題,我每次搜索的時候需要計算我離餐廳的距離,那麼我們能不能把這個距離保存起來呢?如果我保存了,我下次在另一個地方那是不是還得重新計算?所以在數量級小的時候可以考慮這個做法,但是數量級起來了這個方法不是一個好的方法。
我們打開地圖發現地圖並不是一開始把所有的細節都顯示,比如我首先看到的是中國,然後放大看到的是廣東,再放大看到的是深圳,那麼我們假設這個是一個大盒子,裏面有很多個小盒子,小盒子裏面有很多更小的盒子,每個小盒子都用二進制標記比如小盒子裏:00,01,10,11....
我們通過把(x,y)轉換成的編碼連接起來,如上圖所示,是不是就可以把他們轉換成一個一維的線性結構,那麼對於我們的排序非常有利。那麼這個就是我們今天所說的GeoHash。
GeoHash算法分爲三步
第一步:把經緯度轉換成二進制
第二步:把二進制的經緯度合併
第三步:按照base32進行編碼
Base32編碼表的其中一種如下,是用0-9、b-z(去掉a, i, l, o)這32個字母進行編碼。具體操作是先將上一步得到的合併後二進制轉換爲10進制數據,然後對應生成Base32碼。需要注意的是,將5個二進制位轉換成一個base32碼。在Redis中經緯度使用52位的整數進行編碼,最後的結果放在zset裏面,ZSet的value是元素的key,score是GeoHash的52位數值,這裏注意score是一個浮點型,但是對於52位的整形是無損存儲。Redis給我們提供6個Geo命令,下面我們就開始使用這個Geo命令了
查詢
這裏我們注意添加是geoadd,可以添加多個點,刪除就是直接使用zrem即可
> geoadd name 110.50000 30.50000 zhangsan
(integer) 1
> geoadd name 110.60000 30.60000 lisi
(integer) 1
> geoadd name 120.50000 30.60000 wangwu
(integer) 1
> geoadd name 111.45000 29.90000 zhaoliu 111.50000 30.10000 mango
(integer) 2
距離
geodist 指令可以用來計算兩個元素之間的距離,攜帶集合名稱、兩個名稱和距離單位,距離單位可以是 m、 km、ml 和丘,分別代表米、幹米、英里和尺。
> geodist name zhangsan mango km
"105.8372"
獲取元素位置
geopos 指令可以獲取
> geopos name zhangsan
1) 1) "110.49999743700027"
2) "30.499999345868211"
獲取元素hash值
GeoHash 可以獲取元素的經緯度編碼字符串,上面已經提到,它是 base32 編碼。你可以使用這個編碼值去http://geohash.org/$ {hash}上進行直接定位,它是 GeoHash的標準編碼值。
> geohash name zhangsan
1) "wmqtd2reke0"
附近的元素
georadiusbymember指令是最爲關鍵的指令之一,它可以用來查詢指定元素附近的其他元素,它的參數非常複雜 。
#### 獲取據離zhangsan20km最近的三個人
> georadiusbymember name zhangsan 20 km count 3 asc
1) "zhangsan"
2) "lisi"
#### withcoord withdist withhash三個可選參數
> georadiusbymember name zhangsan 20 km withcoord withdist withhash count 3
1) 1) "zhangsan"
2) "0.0000"
3) (integer) 4028187045502241
4) 1) "110.49999743700027"
2) "30.499999345868211"
2) 1) "lisi"
2) "14.6789"
3) (integer) 4028372360456825
4) 1) "110.60000091791153"
2) "30.599999165046462"
除了georadiusbyrnember指令根據元素查詢附近的元素,Redis還提供了根據座標值來查詢附近的元素的指令georadius,這個指令更加有用,它可以根據用戶的定位來計算附近的餐館”等。它的參數和georadiusbyrnernber基本一致,唯一的差別是將目標元素改成經緯度座標值。
> georadius name 110.50000 30.50000 20 km count 3
1) "zhangsan"
2) "lisi"
注意:這裏的GeoHash雖然好用,它實際上還是個ZSet,數據量超過一定的閾值後查詢速度會減慢,那麼我們在處理這些問題的時候需要對地圖進行切割或者進行分塊處理。
一名正在搶救的coder
筆名:mangolove
CSDN地址:https://blog.csdn.net/mango_love
GitHub地址:https://github.com/mangoloveYu