當兩個元素的距離不是很遠時,可以直接使⽤勾股定理就能算得元素之間的距離。我們平時使⽤的「附近的⼈」的功能,元素距離都不是很⼤,勾股定理算距離⾜矣。不過需要注意的是,經緯度坐標的密度不⼀樣 (地球是⼀個橢圓),勾股定律計算平⽅差時之后再求和時,需要按⼀定的系數⽐加權求和,如果不求精確的話,也可以不必加權。
業界⽐較通⽤的地理位置距離排序算法是 GeoHash 算法,Redis 也使⽤ GeoHash 算法。GeoHash 算法將⼆維的經緯度數據映射到⼀維的整數,這樣所有的元素都將在掛載到⼀條線上,距離靠近的⼆維坐標映射到⼀維后的點之間距離也會很接近。當我們想要計算「附近的⼈時」,⾸先將⽬標位置映射到這條線上,然后在這個⼀維的線上獲取附近的點就⾏了。那這個映射算法具體是怎樣的呢?它將整個地球看成⼀個⼆維平⾯,然后划分成了⼀系列正⽅形的⽅格,就好⽐圍棋棋盤。所有的地圖元素坐標都將放置於唯⼀的⽅格中。⽅格越⼩,坐標越精確。然后對這些⽅格進⾏整數編碼,越是靠近的⽅格編碼越是接近。那如何編碼呢?⼀個最簡單的⽅案就是切蛋糕法。設想⼀個正⽅形的蛋糕擺在你⾯前,⼆⼑下去均分分成四塊⼩正⽅形,這四個⼩正⽅形可以分別標記為 00,01,10,11 四個⼆進制整數。然后對每⼀個⼩正⽅形繼續⽤⼆⼑法切割⼀下,這時每個⼩⼩正⽅形就可以使⽤ 4bit 的⼆進制整數予以表示。然后繼續切下去,正⽅形就會越來越⼩,⼆進制整數也會越來越⻓,精確度就會越來越⾼。
增加
geoadd 指令攜帶集合名稱以及多個經緯度名稱三元組,注意這⾥可以加⼊多個三元組
127.0.0.1:6379> geoadd company 116.48105 39.996794 juejin (integer) 1 127.0.0.1:6379> geoadd company 116.514203 (error) ERR wrong number of arguments for 'geoadd' command 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
也許你會問為什么 Redis 沒有提供 geo 刪除指令?前⾯我們提到 geo 存儲結構上使⽤的是 zset,意味着我們可以使⽤ zset 相關的 指令來操作 geo 數據,所以刪除指令可以直接使⽤ zrem 指令即 可。
距離
geodist 指令可以⽤來計算兩個元素之間的距離,攜帶集合名稱、2個名稱和距離單位。距離單位可以是m、km、ml、ft,分別代表⽶、千⽶、英⾥和尺。
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 xiaomi km "12.9606"
獲取元素位置
geopos 指令可以獲取集合中任意元素的經緯度坐標,可以⼀次獲取多個。
127.0.0.1:6379> geopos company juejin 1) 1) "116.48104995489120483" 2) "39.99679348858259686" 127.0.0.1:6379> geopos company juejin ireader 1) 1) "116.48104995489120483" 2) "39.99679348858259686" 2) 1) "116.5142020583152771" 2) "39.90540918662494363"
獲取元素的 hash 值
geohash 可以獲取元素的經緯度編碼字符串,上⾯已經提到,它是base32 編碼。你可以使⽤這個編碼值去 http://geohash.org/${hash}中進⾏直接定位,它是 geohash 的標准編碼值。
geohash計算過程依據上述原理,接下來詳細介紹一下geohash的計算過程,這里拿經緯度(116.389550, 39.928167)進行算法說明。
a. 緯度計算
中學學過的地理知識知道,地球分為南緯與北緯,分別都是0~90°,但是在計算機中,用文字定義南緯與北緯較為麻煩,所以計算機中用區間定義[-90,0)與[0,90]分為南北緯,同時叫做左右區間。區分了左右區間,接下來就是整個計算過程:
判斷當前緯度39.928167是在左區間還是右區間,發現是在右區間[0,90]中,在右區間標識為1;接着將區間[0,90]進行左右區間二分,二分后為 [0,45),[45,90],可以確定39.928167屬於左區間 [0,45),標記為0;不斷重復上述過程39.928167總是屬於某個區間[a,b]。隨着每次迭代區間[a,b]總在縮小,並越來越逼近39.928167;依據最大精度,定義一個最大重復次數,這里我們定義為15,這樣就能得出一個01字串;
b. 全局計算
經度計算與緯度計算類似,也是依據區間划分,左右判斷來進行,這里就不在復述了,給出最終計算結果為:1 1 0 1 0 0 1 0 1 1 0 0 0 1 0,接下來就是如何通過經度與緯度的01字串,編碼成相應的字母+數字的組合。將經度與緯度的01字串進行合並,合並方法為:基數為放緯度,偶數位放經度
- 最終字串為:11011, 01110, 00010, 01111, 00001, 00100
- 將字串轉換成十進制,得到:27, 14, 2, 15, 1, 4
- 對應base32編碼表