redis中的三种不常见数据类型之geo

这次介绍一个在地理定位系统中使用的比较多的一种数据类型——geo。geo是geography的缩写,说明和地理位置有关,geohash可以认为是一种特殊的编码格式。

geo

介绍

geo是redis在3.2.0及以上版本,中的一个特殊的集合,提供存储经纬度,计算距离范围等功能。和bitmap,hyperloglog不同的是,geo在redis中存储的类型是zset。也就是说除了支持geo相关的api之外,完全兼容zset的所有api。

geoadd

该指令用于添加地理位置信息,往某个集合中添加地理位置坐标——geoadd 集合名称 经度 纬度 地理位置名称

127.0.0.1:6379> geoadd mygeo 116.4 39.92 beijing
(integer) 1
127.0.0.1:6379>

geopos

用于获取集合中某个坐标具体的经纬度——geopos 集合名称 地理位置名称

127.0.0.1:6379> geopos mygeo beijing
1) 1) "116.39999896287918091"
   2) "39.9199990416181052"

geodist

用于计算同一个集合当中,两个坐标之间的距离,支持m,km,ft等单位——geodist 集合名称 地理位置名称1 地理位置名称2

127.0.0.1:6379> geoadd mygeo 114.0 30.58 wuhan
(integer) 1
127.0.0.1:6379> geodist mygeo beijing wuhan km
"1061.3546"
127.0.0.1:6379>

georadius

用于获取集合中,某个半径范围内的坐标信息,可以获取坐标值,也可以获取坐标对应的距离等。

127.0.0.1:6379> georadius mygeo 114.0 30.58 1070 km WITHCOORD WITHDIST
1) 1) "wuhan"
   2) "0.0001"
   3) 1) "114.00000125169754028"
      2) "30.58000021509926825"
2) 1) "beijing"
   2) "1061.3546"
   3) 1) "116.39999896287918091"
      2) "39.9199990416181052"
127.0.0.1:6379>

georadiusbymember

和georadius命令功能相同,只是指定中心点换成了坐标名称。

127.0.0.1:6379> geoadd mygeo 117.30 39.71 tianjing
(integer) 1
127.0.0.1:6379> georadiusbymember mygeo wuhan 1070 km WITHCOORD WITHDIST
1) 1) "wuhan"
   2) "0.0000"
   3) 1) "114.00000125169754028"
      2) "30.58000021509926825"
2) 1) "tianjing"
   2) "1058.6922"
   3) 1) "117.29999810457229614"
      2) "39.70999992828803471"
3) 1) "beijing"
   2) "1061.3546"
   3) 1) "116.39999896287918091"
      2) "39.9199990416181052"
127.0.0.1:6379>

值得注意的是,这里并没有提供geodel,因为本质上它是一个zset,所以是支持所有zset指令的,可以通过zrem来进行删除。

127.0.0.1:6379> zrem mygeo tianjing
(integer) 1
127.0.0.1:6379> zrange mygeo 0 10 WITHSCORES
1) "wuhan"
2) "4052113222296933"
3) "beijing"
4) "4069885561465124"
127.0.0.1:6379>

geohash编码

从上面zset的value可以看出,redis进行存储的时候,并不是直接存储的坐标值,而是将二维坐标做了编码。接下来看看这种geohash的编码是如何实现的。

Geohash本质上是空间索引的一种方式,其基本原理是将地球理解为一个二维平面,将平面递归分解成更小的子块,每个子块在一定经纬度范围内拥有相同的编码。以GeoHash方式建立空间索引,可以提高对空间数据进行经纬度检索的效率。首先是将世界地图划分为了32等分,每一等分分配一个编码,然后每一等分又继续分为32等分,按照同样的方式进行编码,依次递归。

geohash

这样递归了n次之后,就会得到一个长度为n的字符串,比如北京的位置,大概为:wx4g0e;如果还要更加精确的值,则获取更长的编码即可。那么通过这种方式简化了之后,对地理位置的查询,就由二维坐标的查询,变成了一维字符串的查询。性能较高,而且节省了存储空间,并且在某些场景下还可以达到保护隐私的效果——方便的提供大概位置。

编码过程

那么具体是怎么编码的呢,以经纬度值:(116.389550, 39.928167)为例进行说明:

  1. 区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.928167属于右区间[0,90],给标记为1;

  2. 接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定39.928167属于左区间 [0,45),给标记为0;

  3. 递归上述过程39.928167总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近39.928167;

  4. 如果给定的纬度x(39.928167)属于左区间,则记录0,如果属于右区间则记录1,序列的长度跟给定的区间划分次数有关;编码过程

  5. 同理,地球经度区间是[-180,180],可以对经度116.389550进行编码

  6. 通过上述计算,纬度产生的编码为1 1 0 1 0 0 1 0 1 1 0 0 0 1 0

    经度产生的编码为1 0 1 1 1 0 0 0 1 1 0 0 0 1 1

  7. 合并:偶数位放经度,奇数位放纬度,把2串编码组合生成新串:合并结果

  8. 然后将11100 11101 00100 01111 0000 01101转成十进制,对应着28、29、4、15,0,13 十进制对应的base32编码就是wx4g0e,如下图:

    编码

当geohash base32编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右。

精度说明

geohash编码的缺陷

对大部分而言,编码相似的距离也相近,但geohash是通过Peano空间曲线填充的,这种曲线最大的缺点就是突变性,有些编码相邻但距离却相差很远,比如下左图中g与h,编码是相邻的,但距离相差很大。

突变性的规避方法:
查询附近地点,除了使用定位点的GeoHash编码进行匹配外,还使用周围8个区域的GeoHash编码,这样可以避免这个问题。

peano

总结

redis中的geo特别适合微信中摇一摇,外卖骑手位置定位等,涉及热点定位数据的场景。

参考

http://geohash.org/wx4g0e

http://geohash.gofreerange.com

https://en.wikipedia.org/wiki/Geohash

http://geohash.org/

https://www.cnblogs.com/LBSer/p/3298057.html


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!