在生活中我们有时候需要点外卖、骑共享单车等等,我们打开软件找到附近餐厅、离我最近的单车,那么他们是怎么快速定位到的呢?我们把地图看作一个二维平面,我们在某个点上然后找到附近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