Redis 数据类型——geospatial

一、geospatial 简介

Redis 在 3.2 版本中加入了地理空间(geospatial)以及索引半径查询的功能,主要用在需要地理位置的应用上。将指定的地理空间位置(经度、纬度、名称)添加到指定的 key 中,这些数据将会存储到 sorted set。这样的目的是为了方便使用 GEORADIUS 或者 GEORADIUSBYMEMBER 命令对数据进行半径查询等操作。也就是说,推算地理位置的信息,两地之间的距离,周围方圆的人等等场景都可以用它实现。

小结:geo 底层原理是使用 zset来实现的,因此我们也可以使用 zset 的命令操作 geo。

二、geohash

要理解 Redis 的 GEO 相关的命令是如何实现了,就得先理解 geohash 的原理,本质上这些命令就是对 geohash 数据的封装而已。

geohash 是 2008 年 Gustavo Niemeye 发明用来编码经纬度信息的一种编码方式,比如北京市中心的经纬度坐标是 116.404844,39.912279,通过 12 位 geohash 编码后就变成了 wx4g0cg3vknd,它究竟是如何实现的?其实原理非常简单,就是二分,整个编码过程可以分为如下几步。

1、转二进制

上过初中地理的我们都知道,地球上任何一个点都可以标识为某个经纬度坐标,经度的取值范围是东经 0-180 度和西经 0-180 度,纬度的取值范围是北纬 0-90 和南纬 0-90 度。去掉东西南北,可以分别认为经度和纬度的取值范围为[-180,180]和[-90,90]。

我们先来看经度,[-180,180]可以简单分成两个部分[-180,0]和[0,180],对于给定的一个具体值,我们用一个bit 来标识是在[-180,0]还是[0,180]区间里。然后我们可以对这两个子区间继续细分,用更多的 bit 来标识是这个值是在哪个子区间里。就好比用二分查找,记录下每次查找的路径,往左就是 0 往右是 1,查找完后我们就会得到一个 0101 的串,这个串就可以用来标识这个经度值。

同理纬度也是一样,只不过他的取值返回变成了[-90,90]而已。通过这两种方式编码完成后,任意经纬度我们都可以得到两个由0和1组成的串。

比如还是北京市中心的经纬度坐标 116.404844,39.912279,我们先对 116.404844 做编码,得到其二进制为:
11010010110001101101
然后我们对维度 39.912279 编码得到二进制为:
10111000110000111001

2. 经纬度二进制合并

接下来我们只需要将上述二进制交错合并成一个即可,这里注意经度占偶数位,纬度占奇数位,得到最终的二进制:
image.png
1110011101001000111100000010110111100011

3. 将合并后的二进制做 base32 编码

最后我们将合并后的二进制做 base32 编码,将连续 5 位转化为一个 0-31 的十进制数,然后用对应的字符代替,将所有二进制位处理完后我们就完成了 base32 编码。编码表如下:image.png
最终得到 geohash 值 wx4g0cg3vknd。
image.png
geohash 是将空间不断的二分,然后将二分的路径转化为 base32 编码,最后保存下来。从原理可以看出,geohash 表示的是一个区间,而不是一个点,geohash 值越长,这个区间就越小,标识的位置也就越精确,下图是维基百科中不同长度 geohash 下的经纬度误差(lat:纬度,lng:经度)
image.png

4、geohash 的用途

geohash 成功的将一个二维信息编码成了一个一维信息,这样编码我觉得有两个好处:

  1. 编码后数据长度变短,利于节省存储。
  2. 利于使用前缀检索。

    我们来详细说下第二点。

从上文中 geohash 的实现来看,只要两个坐标点的 geohash 有共同的前缀,我们就可以肯定这两个点在同一个区域内 (区域大小取决于共同前缀的长度)。这种特性给我们带来的好处就是,我们可以把所有坐标点按 geohash 做增序索引,然后查找的时候按前缀筛选,大幅提升检索的性能。

举个例子,假设我要找北京国贸附近 3 公里内的餐馆,已知国贸的 geohash 是 wx4g41,那我也很轻易就可以计算出来我需要扫描哪些区域内的点。但有个点需要注意,上文我已经提到过,geohash 值实际上是代表一个区域,而不是一个点,找到一批候选点之后还需要遍历一次计算下精确距离。
image.png

5、geohash 的问题

geohash 有个需要注意的问题。geohash 是将二维的坐标点做了线下编码,有时候可能会给人一个误解就是如果两个 geohash 之间二进制的差异越小,这两个区间距离就越近,这完全是错误的。比如如下图 0111 和 1000,这俩区间二进制只差 0001 但实际物理距离比较远。
image.png
如果上图还不明显的话,我从 Wikipedia 上拿到一张图,虚线是线性索引的路径,被虚线连接的两个块 geohash 值是非常相近的,如下图的(7,3)和(0,4),geohash 值会非常相近,但实际物理距离非常远,这就是 geohash 的突变现象,这也导致了不能直接根据 geohash 的值来直接判定两个区域的距离大小。

image.png
但在实际使用 geohash 过程中,时常会遇到跨域搜索的情况,比如我要在上图(3,3)这个区间内某个点上搜索距它 1 个距离单位的所有其他点集,这个点集有可能横跨(3,3)加上它周围 8 个邻域的 9 个区间,突变的问题会导致这 9 个区间的 geohash 不是线性跳转的。

但也不是没法计算,实际上可以通过特殊的位运算可以很轻易计算出某个 geohash 的 8 个邻域,具体可参考 Redis 源码中 src/geohash.c 中 geohashNeighbors() 的具体实现, geohashNeighbors 使用了 geohash_move_x 和 geohash_move_y 两个函数实现了 geohash 左右和上下的移动,这样可以很容易组合出 8 个邻域的 geohash 值了。

其实,就是将该区块上下左右以及四个对角的 8 个区块的 hash 都计算一遍,分别计算这些区间和自己之间的距离,找到其中距离一个距离单位的所有点集就可以了。因为这时的数据量已经非常小了,计算周边的 8 个块也很快。

三、Geo in Redis

本质上 Redis 中的 geo 就是对 geohash 的封装。接下来介绍一下 geo 六个命令

1、geoadd

geoadd 添加地理位置,可以将指定的地理空间位置(经度、纬度、名称)添加到指定的 key 中。

命令格式:geoadd key longitude latitude member [longitude latitude member …]

往 china:city 这个 key 里添加了 5 个地方的经纬度:北京、上海、广州、深圳、杭州。
image.png

2、geopos

geopos 获取指定城市的地理位置经纬度,可以从 key 里返回所有给定地理位置的经纬度。

命令格式:geopos key member [member …]

从 china:city 这个 key 里返回 北京、杭州的经纬度。
image.png

3、geodist

geodist 返回两个坐标之间的距离,也就是两个人之间的距离,如果两个位置之间的其中一个不存在,则会返回空值。指定单位的参数 unit 必须是以下单位的其中之一:

  1. m 表示单位为米(默认)
  2. km 表示单位为千米
  3. mi 表示单位为英里
  4. ft 表示单位为英尺

    命令格式:geodist key member1 member2 [unit]

    返回北京和杭州之间的距离,单位 km。
    image.png

4、georadius

georadius 以给定的经纬度为中心,返回与中心的距离不超过给定最大距离的所有位置元素。

命令格式:georadius key lopngitude latitude radius unit [WITHCOORD] [WITHDIST] [COUNT count] [ASC|DESC] […]

找出离杭州滨江区 1100km 的地方。
image.png
加上 WITHCOORD 可以返回经纬度
image.png
加上 WITHDIST 可以显示到中心的距离
image.png
加上 COUNT 可以只返回指定数量结果
image.png
加上 ASC 可以按距离升序排序,加上 DESC 可以按距离降序排序
image.png

5、georadiusbymember

georadiusbymember 跟 georadius 命令一样,都可以找出位于指定范围的位置元素,但是这里不是指定中心点坐标,而是指定以哪个元素为中心点。

命令格式:georadiusbymember key member radius unit […(跟 georadius 一致)]

找出离杭州 200km 的地方。
image.png

6、geohash

geohash 将二维的经纬度转换成一维的字符串,也就是对经纬度进行 hash 计算。

命令格式:geohash key member [member …]

对杭州、上海、北京取 hash。
image.png

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值