Redis在3.2版本悄悄的加入了一个地理位置的功能,哈哈,3.2版本推出已经好久了,一直没有机会尝试一下,今天专门敲数据使用了一番,新增了一共6个方法,看了看相关数据结构和特点,了解了大概的轮廓,今天就来记录一下。
先简单说说GeoHash的原理吧
定义
GeoHash通过切分地图区域的方式将二维的经纬度转换成字符串,切分次数越多字符串越长,表示的范围越精确。字符串相似的表示距离相近,这样可以利用字符串的前缀匹配来查询附近的POI信息。
切分方法
切分方法是矩形切分,通过二分法依次缩小范围,所以GeoHash字符串最终定位的是某一个矩形区域,可以看作是一个范围,当范围足够小的时候,就无限接近一个点了
Tips
但其实没有必要精确到点,矩形刚好反映元素的位置范围,不是具体的位置点,所以能很好的保护隐私。
以北海公园为例
A. 算出纬度序列
地球纬度区间是[-90, 90], 北海公园的纬度是39.928167,可以通过下面算法对纬度进行二分逼近编码:
区间[-90, 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;
标记
左边界值
中间值
右边界值
1
-90.000
0.000 [hits]
90.000
0
0.000 [hits]
45.000
90.000
1
0.000
22.500 [hits]
45.000
1
22.500
33.750 [hits]
45.000
1
33.750
39.375 [hits]
45.000
规定:给定的纬度x(39.928167)属于左区间,则记录0,如果属于右区间则记录1,这样随着算法的深入会产生一个序列10111 00011 ...【递归次数越多,区块划分越细,精度越高,当然数字也就越长】。
B. 同理算出经度序列为11010 01011
C. 奇数位放纬度,偶数位放经度,把2串编码组合生成新串:11100 11101 00100 01111
D. 然后将新串转化为10进制 28 29 4 15
E. 对十进制数字进行Base32编码,即转换为32进制,得到wx4g,即为GeoHash字符串
Redis 中的 GeoHash
3.2中关于GeoHash新加入的方法有6个
geoadd: 增加地理位置的坐标
geodist: 获取两个地理位置的距离
geohash: 获取地理位置的GeoHash值
geopos: 获取地理位置的坐标
georadius: 根据给定经纬度坐标获取指定范围内的地理位置集合
georadiusbymember: 根据给定对象获取该对象范围内的地理位置集合
GEOADD
把一个对象的经纬度位置添加到库中
geoadd someKeyYouDecided 43.34567 49.34567 aaa
# (integer) 1
# 一次添加多个
geoadd someKeyYouDecided 43.34567 49.34567 aaa 44.34567 50.34567 bbb
# (integer) 1
GEODIST
返回两个对象之间的距离
geodist someKeyYouDecided aaa bbb [可选返回值单位 km千米 mi英里 ft英尺 ]
# "132343.9786"
GEOHASH
返回对象对应的GeoHash字符串
geohash someKeyYouDecided aaa
# 1) "ubybdr57770"
geohash someKeyYouDecided aaa bbb
# 1) "ubybdr57770"
# 2) "ubzw3j5sc10"
GEOPOS
返回对象的经纬度信息
geopos someKeyYouDecided aaa
# 1) 1) "43.34566980600357056"
# 2) "49.34566890860142507"
GEORADIUS
根据经纬度信息,返回周围范围内的其他对象[位置]
# 返回距离经纬度坐标[43.345669, 49.345669] 10000m范围内的其他对象
georadius someKeyYouDecided 43.345669 49.345669 10000 m [WITHDIST WITHCOORD WITHHASH ASC|DESC]
# 1) "aaa"
# 返回距离[43.345669, 49.345669] 经纬度10000m范围内的其他对象 并返回距离
georadius someKeyYouDecided 43.345669 49.345669 10000 m WITHDIST
# 1) 1) "aaa"
# 2) "0.0593"
# 返回距离[43.345669, 49.345669] 经纬度10000m范围内的其他对象 并返回距离、经纬度坐标
georadius someKeyYouDecided 43.345669 49.345669 10000 m WITHDIST WITHCOORD
# 1) 1) "aaa"
# 2) "0.0593"
# 3) 1) "43.34566980600357056"
# 2) "49.34566890860142507"
GEORADIUSBYMEMBER
根据对象,返回其范围内的其他对象[位置]
# 返回距离aaa对象1000m范围内的其他对象 包括aaa自己
georadiusbymember someKeyYouDecided aaa 1000 m
# 1) "aaa"
# 返回距离aaa对象1000m范围内的其他对象、距离 包括aaa自己
georadiusbymember someKeyYouDecided aaa 1000 m WITHDIST
# 1) 1) "aaa"
# 2) "0.0000"
# 返回距离aaa对象1000m范围内的其他对象、距离、经纬度坐标 包括aaa自己
georadiusbymember someKeyYouDecided aaa 1000 m WITHDIST WITHCOORD
# 1) 1) "aaa"
# 2) "0.0000"
# 3) 1) "43.34566980600357056"
# 2) "49.34566890860142507"
Redis 实现原理
注意到上述操作中第二个参数都是一个string类型的key,其实Redis是把对应的数据存到了一个zset有序集合中,其中someKeyYouDecided就是zset的key,对象名称aaa bbb为zset的member,对象的GeoHash的int值为zset的value,如果你zrange someKeyYouDecided 0 -1 withscores就会窥探这个zset的真实数据如下
1) "aaa"
2) "3710624661911432"
3) "bbb"
4) "3710839010869441"
所以Redis关于地理位置的方法没有删除,因为zrem(key, member)即可实现
核心方法georadius 其实就是根据经纬度换算成GeoHash值,然后根据查询范围换算出上下浮动的range范围,然后zrangebyscore(GeoHash - range, GeoHash + range)查询附近[上下]的几个对象即可
核心方法georadiusbymember 同理,根据member得到该对象的GeoHash,后续计算与georadius相同
如果感觉本文章有用,请帮忙点击一下页面上的广告,生活不易,多谢多谢!