Redis計算地理位置距離-GeoHash

OldBoy~發表於2018-02-19

Redis 在 3.2 版本以後增加了地理位置 GEO 模組,意味著我們可以使用 Redis 來實現摩拜單車「附近的 Mobike」、美團和餓了麼「附近的餐館」這樣的功能了。

地圖元素的位置資料使用二維的經緯度表示,經度範圍 (-180, 180],緯度範圍 (-90, 90],緯度正負以赤道為界,北正南負,經度正負以本初子午線 (英國格林尼治天文臺) 為 界,東正西負。比如掘金辦公室在望京 SOHO,它的經緯度座標是 (116.48105,39.996794), 都是正數,因為中國位於東北半球。

當兩個元素的距離不是很遠時,可以直接使用勾股定理就能算得元素之間的距離。我們 平時使用的「附近的人」的功能,元素距離都不是很大,勾股定理算距離足矣。不過需要注 意的是,經緯度座標的密度不一樣 (經度總共 360 度,緯度總共 180 度)

 

效經度為-180至180度。
有效緯度為-85.05112878至85.05112878度。

 

勾股定律計算平 方差時之後再求和時,需要按一定的係數比加權求和。

現在,如果要計算「附近的人」,也就是給定一個元素的座標,然後計算這個座標附近的其它元素,按照距離進行排序,該如何下手?

如果現在元素的經緯度座標使用關聯式資料庫 (元素 id, 經度 x, 緯度 y) 儲存,你該如何 計算?

GeoHash 演算法

Redis 的 GEO 特性將在 Redis 3.2 版本釋出, 這個功能可以將使用者給定的地理位置資訊儲存起來, 並對這些資訊進行操作   
將指定的地理空間專案(緯度,經度,名稱)新增到指定的鍵。資料作為排序集儲存到金鑰中,使得可以使用GEORADIUS或GEORADIUSBYMEMBER命令使用半徑查詢稍後檢索專案。

注意:沒有GEODEL命令,可以使用ZREM來刪除元素。地理索引結構只是一個排序集。

 zrem company juejin     //company相當於一個地理儲存空間, juejin是一個地名

Redis GEO實現主要包含了以下兩項技術:
1、使用geohash儲存地理位置的座標。
2、使用有序集合(zset)儲存地理位置的集合。

簡單操作

新增

geoadd 指令攜帶集合名稱以及多個經緯度名稱三元組,注意這裡可以加入多個三元組
127.0.0.1:6379> geoadd company 116.48105 39.996794 juejin (integer) 1
127.0.0.1:6379> geoadd company 116.514203 39.905409 ireader (integer) 1
127.0.0.1:6379> geoadd company 116.489033 40.007669 meituan
(integer) 1
127.0.0.1:6379> geoadd company 116.562108 39.787602 jd 116.334255 40.027400 xiaomi (integer) 2

計算距離

geodist 指令可以用來計算兩個元素之間的距離,攜帶集合名稱、2 個名稱和距離單位。
127.0.0.1:6379> geodist company juejin ireader km "10.5501"
127.0.0.1:6379> geodist company juejin meituan km "1.3878"
127.0.0.1:6379> geodist company juejin jd km "24.2739"
127.0.0.1:6379> geodist company juejin xiaomi km "12.9606"
127.0.0.1:6379> geodist company juejin juejin km "0.0000"

我們可以看到掘金離美團最近,因為它們都在望京。距離單位可以是 m、km、ml、ft, 分別代表米、千米、英里和尺。

獲取元素位置

127.0.0.1:6379> geopos company juejin 
1) 1) "116.48104995489120483"
2) "39.99679348858259686"
127.0.0.1:6379> geopos company ireader 
1) 1) "116.5142020583152771"
2) "39.90540918662494363" 
127.0.0.1:6379> geopos company juejin ireader 
1) 1) "116.48104995489120483"
2) "39.99679348858259686" 
2) 1) "116.5142020583152771"
2) "39.90540918662494363"

我們觀察到獲取的經緯度座標和 geoadd 進去的座標有輕微的誤差,原因是 geohash 對 二維座標進行的一維對映是有損的,通過對映再還原回來的值會出現較小的差別。對於「附 近的人」這種功能來說,這點誤差根本不是事。

獲取元素的 hash 值

geohash 可以獲取元素的經緯度編碼字串,上面已經提到,它是 base32 編碼。 你可 以使用這個編碼值去 http://geohash.org/${hash}中進行直接定位,它是 geohash 的標準編碼 值。

127.0.0.1:6379> geohash company ireader 
1) "wx4g52e1ce0"
127.0.0.1:6379> geohash company juejin 
1) "wx4gd94yjn0"

geohash的思想是將二維的經緯度轉換成一維的字串,geohash有以下三個特點:
1、字串越長,表示的範圍越精確。編碼長度為8時,精度在19米左右,而當編碼長度為9時,精度在2米左右。
2、字串相似的表示距離相近,利用字串的字首匹配,可以查詢附近的地理位置。這樣就實現了快速查詢某個座標附近的地理位置。
3、geohash計算的字串,可以反向解碼出原來的經緯度。

可以開啟網站檢視地理位置是否正確 http://geohash.org/wx4g52e1ce0

附近的公司

georadiusbymember 指令是最為關鍵的指令,它可以用來查詢指定元素附近的其它元 素,它的引數非常複雜。

# 範圍 20 公里以內最多 3 個元素按距離正排,它不會排除自身
127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 asc 
1) "ireader"
2) "juejin"
3) "meituan"
# 範圍 20 公里以內最多 3 個元素按距離倒排
127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 desc 
1) "jd"
2) "meituan"
3) "juejin"
# 三個可選引數 withcoord withdist withhash 用來攜帶附加引數
# withdist 很有用,它可以用來顯示距離
127.0.0.1:6379>  georadiusbymember company ireader 20 km withcoord withdist withhash count 3 asc 
1) 1) "ireader"
   2) "0.0000"
   3) (integer) 4069886008361398
   4) 1) "116.5142020583152771"
      2) "39.90540918662494363"
2) 1) "ireaderexit"
   2) "0.0000"
   3) (integer) 4069886008361398
   4) 1) "116.5142020583152771"
      2) "39.90540918662494363"
3) 1) "meituan"
   2) "11.5748"
   3) (integer) 4069887179083478
   4) 1) "116.48903220891952515"
      2) "40.00766997707732031"

除了 georadiusbymember 指令根據元素查詢附近的元素,Redis 還提供了根據座標值來 查詢附近的元素,這個指令更加有用,它可以根據使用者的定位來計算「附近的車」,「附近 的餐館」等。它的引數和 georadiusbymember 基本一致,除了將目標元素改成經緯度座標 值。

127.0.0.1:6379> georadius company 116.514202 39.905409 20 km withdist count 3 asc 
1) 1) "ireader"
   2) "0.0000"
2) 1) "ireaderexit"
   2) "0.0000"
3) 1) "meituan"
   2) "11.5748"

在一個地圖應用中,車的資料、餐館的資料、人的資料可能會有百萬千萬條,如果使用Redis 的 Geo 資料結構,它們將全部放在一個 zset 集合中。在 Redis 的叢集環境中,集合 可能會從一個節點遷移到另一個節點,如果單個 key 的資料過大,會對叢集的遷移工作造成 較大的影響,在叢集環境中單個 key 對應的資料量不宜超過 1M,否則會導致叢集遷移出現 卡頓現象,影響線上服務的正常執行。

所以,這裡建議 Geo 的資料使用單獨的 Redis 例項部署,不使用叢集環境。

如果資料量過億甚至更大,就需要對 Geo 資料進行拆分,按國家拆分、按省拆分,按 市拆分,在人口特大城市甚至可以按區拆分。這樣就可以顯著降低單個 zset 集合的大小。

 

 

不錯的文章

https://blog.csdn.net/weixin_36135773/article/details/78800215

 

 

相關文章