GeoHash
適用場景
當(dāng)需要查詢“附近的人員”這種功能的時候,如果使用mysql數(shù)據(jù)庫這種方式存儲經(jīng)緯度的信息,指定一定的范圍信息后,再通過計算當(dāng)前人員距離值進(jìn)行排序,這個過程,只要是數(shù)據(jù)查詢量稍微大一點就會出現(xiàn)性能問題,好在redis提供現(xiàn)成的解決方案GeoHash
實現(xiàn)原理
Redis中實現(xiàn)的GeoHash算法是將顯示中的地點信息轉(zhuǎn)化成一個長度為52的整數(shù)。然后將其存放在zset里面(其底層是使用zset進(jìn)行實現(xiàn)的。 我們可以使用zrem 進(jìn)行數(shù)據(jù)的刪除。),zset的value值就是用戶的ID ,score值就是GeoHash的52位整數(shù)值,在redis中使用的時候,通過zset的score排序就可以得到附近的元素,然后在將score值還原成經(jīng)緯度坐標(biāo)信息即可
使用方式
-
geoadd:加入經(jīng)緯度以及集合名稱的多個三維數(shù)組
- (例:geoadd company 116.48105 33.999484 jili)加入吉利汽車公司的地址
- (例:geoadd company 146.48105 77.999484 baoma)加入寶馬公司的地址
-
geodist:用于計算兩個元素之間的距離,攜帶集合名稱,兩個節(jié)點的名稱和距離單位
- (例:geodist company baoma jili km)獲取寶馬和吉利公司的距離(單位km)
-
geopos:獲取集合元素中任意元素的經(jīng)緯度坐標(biāo)
- (例:geodist company baoma)獲取寶馬公司的經(jīng)緯度坐標(biāo)(會稍微有一些誤差)
-
georadiusbymember:最主要的方法,用來指定查詢元素附近的其他元素
- (例:georadiusbymember company baoma 20 km count 3 desc)獲取寶馬公司附近20km內(nèi)最多三個元素按照距離倒序
注意:在真是場景中使用Geo的時候如果業(yè)務(wù)數(shù)據(jù)過大,例如順分車查找附件的人,人員和車比較多的時候,所有的數(shù)據(jù)都存儲在一個zset中,就會導(dǎo)致一個節(jié)點過大的問題,所以可以對數(shù)據(jù)根據(jù)地理位置進(jìn)行拆分,例如順風(fēng)車可以根據(jù)不同的城市,或者不同的區(qū),使用不同的zset
原理
GeoHash算法使用二分切割法,將二維的經(jīng)緯度數(shù)據(jù)映射到一維的整數(shù),這樣所有的元素都將掛載到一條線上,地球緯度區(qū)間是[-90,90],經(jīng)度區(qū)間是[-180,180]。 將它展開想象長一個矩形

通過剛才的方法,我們能夠?qū)⒌厍虻谋砻孓D(zhuǎn)換成二維空間的平面。那接下來要將二維轉(zhuǎn)變成一維。如果切割二維空間,可以切割出很多正方形。如何表示這個正方形呢?最簡單的方法是在平面上進(jìn)行遍歷。每遍歷到一個點,就給它標(biāo)注一個值,比如00、01、10、11,隨著二進(jìn)制數(shù)字增加,相當(dāng)于遍歷面上不同的位置。

當(dāng)將空間劃分為四塊時候,編碼的順序分別是左下角00,左上角01,右下腳10,右上角11,也就是類似于Z的曲線。當(dāng)我們遞歸的將各個塊分解成更小的子塊時可以標(biāo)識更小的空間范圍(如上圖二中所示),如從0000開始到1111結(jié)束編碼的順序是自相似的(分形),每一個子快也形成Z曲線,這種類型的曲線被稱為Peano空間填充曲線
Geohash 通過將節(jié)點數(shù)據(jù)編碼轉(zhuǎn)換成一維數(shù)據(jù),(base 32 和 base 36) 會將落到網(wǎng)格中的二進(jìn)制數(shù)據(jù)編碼成字符串,再使用B樹索引快速查找出需要的數(shù)據(jù)。
Java使用案例(偽代碼)
import redis.clients.jedis.*;
import redis.clients.jedis.params.geo.GeoRadiusParam;
/**
* 增加地理位置的坐標(biāo)
* @param key
* @param coordinate
* @param memberId
* @return
*/
public static Long geoadd(String key, GeoCoordinate coordinate, String memberId) {
Jedis jedis = jedisPool.getResource();
jedis.geoadd(key,coordinate.getLongitude(),coordinate.getLatitude(),memberName);
jedis.close();
}
/**
* 增加地理位置的坐標(biāo)
* @param key
* @param coordinate
* @param memberId
* @return
*/
public static Long geoadd(String key, GeoCoordinate coordinate, String memberId) {
Jedis jedis = jedisPool.getResource();
jedis.geoadd(key,coordinate.getLongitude(),coordinate.getLatitude(),memberName);
jedis.close();
}
/**
* 根據(jù)給定地理位置坐標(biāo)獲取指定范圍內(nèi)的地理位置集合
*(返回匹配位置的經(jīng)緯度 + 匹配位置與給定地理位置的距離 + 從近到遠(yuǎn)排序,)
* @param key
* @param coordinate
* @param radius
* @return List<GeoRadiusResponse>
*/
public static List<GeoRadiusResponse> geoRadius(String key, GeoCoordinate coordinate, double radius) {
Jedis jedis= jedisPool.getResource();
List<GeoRadiusResponse> list = jedis.georadius(
key,
coordinate.getLongitude(),
coordinate.getLatitude(),
radius,
GeoUnit.KM,
GeoRadiusParam.geoRadiusParam().withDist().withCoord().sortAscending());
jedis.close();
return
}