GEO数据结构

[TOC]

简介

这时问题来了,Sorted Set 元素的权重分数是一个浮点数(float 类型),而一组经纬度包
含的是经度和纬度两个值,是没法直接保存为一个浮点数的,那具体该怎么进行保存呢?
这就要用到 GEO 类型中的 GeoHash 编码了。

GeoHash 的编码方法
为了能高效地对经纬度进行比较,Redis 采用了业界广泛使用的 GeoHash 编码方法,这
个方法的基本原理就是“二分区间,区间编码”。
当我们要对一组经纬度进行 GeoHash 编码时,我们要先对经度和纬度分别编码,然后再
把经纬度各自的编码组合成一个最终编码。
首先,我们来看下经度和纬度的单独编码过程。
对于一个地理位置信息来说,它的经度范围是[-180,180]。GeoHash 编码会把一个经度值
编码成一个 N 位的二进制值,我们来对经度范围[-180,180]做 N 次的二分区操作,其中 N
可以自定义。

位编码。当做完 N 次的二分区后,经度值就
可以用一个 N bit 的数来表示了。
举个例子,假设我们要编码的经度值是 116.37,我们用 5 位编码值(也就是 N=5,做 5
次分区)。
我们先做第一次二分区操作,把经度区间[-180,180]分成了左分区[-180,0) 和右分区
[0,180],此时,经度值 116.37 是属于右分区[0,180],所以,我们用 1 表示第一次二分区
后的编码值。
接下来,我们做第二次二分区:把经度值 116.37 所属的[0,180]区间,分成[0,90) 和[90,
180]。此时,经度值 116.37 还是属于右分区[90,180],所以,第二次分区后的编码值仍然
为 1。等到第三次对[90,180]进行二分区,经度值 116.37 落在了分区后的左分区[90, 135)
中,所以,第三次分区后的编码值就是 0。
按照这种方法,做完 5 次分区后,我们把经度值 116.37 定位在[112.5, 123.75]这个区
间,并且得到了经度值的 5 位编码值,即 11010。这个编码过程如下表所示:

image-20210207195146607

对纬度的编码方式,和对经度的一样,只是纬度的范围是[-90,90],下面这张表显示了对
纬度值 39.86 的编码过程。

image-20210207195537897

当一组经纬度值都编完码后,我们再把它们的各自编码值组合在一起,组合的规则是:最
终编码值的偶数位上依次是经度的编码值,奇数位上依次是纬度的编码值,其中,偶数位
从 0 开始,奇数位从 1 开始。
我们刚刚计算的经纬度(116.37,39.86)的各自编码值是 11010 和 10111,组合之后,
第 0 位是经度的第 0 位 1,第 1 位是纬度的第 0 位 1,第 2 位是经度的第 1 位 1,第 3
位是纬度的第 1 位 0,以此类推,就能得到最终编码值 1110011101,如下图所示:

image-20210207195618112

用了 GeoHash 编码后,原来无法用一个权重分数表示的一组经纬度(116.37,39.86)就
可以用 1110011101 这一个值来表示,就可以保存为 Sorted Set 的权重分数了。
当然,使用 GeoHash 编码后,我们相当于把整个地理空间划分成了一个个方格,每个方
格对应了 GeoHash 中的一个分区。

举个例子。我们把经度区间[-180,180]做一次二分区,把纬度区间[-90,90]做一次二分区,
就会得到 4 个分区。我们来看下它们的经度和纬度范围以及对应的 GeoHash 组合编码

image-20210207195713105

这 4 个分区对应了 4 个方格,每个方格覆盖了一定范围内的经纬度值,分区越多,每个方
格能覆盖到的地理空间就越小,也就越精准。我们把所有方格的编码值映射到一维空间
时,相邻方格的 GeoHash 编码值基本也是接近的,如下图所示:

image-20210207195728668

所以,我们使用 Sorted Set 范围查询得到的相近编码值,在实际的地理空间上,也是相邻
的方格,这就可以实现 LBS 应用“搜索附近的人或物”的功能了。
分区一:[-180,0) 和[-90,0),编码 00;
分区二:[-180,0) 和[0,90],编码 01;
分区三:[0,180]和[-90,0),编码 10;
分区四:[0,180]和[0,90],编码 11。

不过,我要提醒你一句,有的编码值虽然在大小上接近,但实际对应的方格却距离比较
远。例如,我们用 4 位来做 GeoHash 编码,把经度区间[-180,180]和纬度区间[-90,90]各
分成了 4 个分区,一共 16 个分区,对应了 16 个方格。编码值为 0111 和 1000 的两个方
格就离得比较远,如下图所示:

image-20210207195744382

所以,为了避免查询不准确问题,我们可以同时查询给定经纬度所在的方格周围的 4 个或
8 个方格。
好了,到这里,我们就知道了,GEO 类型是把经纬度所在的区间编码作为 Sorted Set 中
元素的权重分数,把和经纬度相关的车辆 ID 作为 Sorted Set 中元素本身的值保存下来,
这样相邻经纬度的查询就可以通过编码值的大小范围查询来实现了。接下来,我们再来聊
聊具体如何操作 GEO 类型。

一、CEO概述

  • Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信 息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能,对于需 要实现这些功能的开发者来说是一大福音
  • GEO功能是Redis的另一位作者Matt Stancliff借鉴NoSQL数据库Ardb实现的,Ardb的作者来自中国,它提供了优秀的GEO功能

二、增加地理位置信息(geoadd)

1
geoadd key longitude latitude member [longitude latitude member ...]
  • 参数如下:
    • longitude:地址位置的经度
    • latitude:地址位置的纬度
    • member:成员
  • 相关注意事项:
    • geoadd一次可以添加多个地理位置信息
    • geoadd添加成功返回1
    • 如果member已经存在,那么该命令返回0,此时代表更新member的值
  • 例如:下面添加5个城市的经纬度

img

img

三、获取地理信息位置(geopos)

1
geodist key member1 member2 [unit]
  • 该命令用来获取两个地址位置的距离
  • unit参数代表返货结果的单位,包含以下4种:
    • m(meters)代表米
    • km(kilometers)代表公里
    • mi(miles)代表英里
    • ft(feet)代表尺
  • 例如:下面计算天津到北京的距离,以公里为单位

img

四、获取指定位置范围内的地理信息位置集合(georadius、georadiusbymember)

1
2
3
4
5
6
7
8
9
10
georadius key longitude latitude radiusm|km|ft|mi [withcoord] [withdist] 



[withhash] [COUNT count] [asc|desc] [store key] [storedist key]
georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist]



[withhash] [COUNT count] [asc|desc] [store key] [storedist key]
  • 两个命令作用相同,都是以一个地理位置为中心算出指定半径内的其他地理信息位置
  • 不同的是:
    • georadius命令的中心位置给出了具体的经纬度
    • georadiusbymember只需给出成员即可
  • 其中radiusm|km|ft|mi是必需参数,指定了半径(带单位),其他可选参数意义如下:
    • withcoord:返回结果中包含经纬度
    • withdist:返回结果中包含离中心节点位置的距离
    • withhash:返回结果中包含geohash,有关geohash后面介绍
    • COUNT count:指定返回结果的数量
    • asc|desc:返回结果按照离中心节点的距离做升序或者降序
    • store key:将返回结果的地理位置信息保存到指定键
    • storedist key:将返回结果离中心节点的距离保存到指定键。
  • 例如:下面计算5个城市中,距离北京150公里以内的城市

img

五、获取geohash

1
geohash key member [member ...]

img

  • geohash有如下特点:
    • GEO的数据类型为zset(见下图),Redis将所有地理位置信息的geohash存放在zset中
    • 字符串越长,表示的位置更精确,下图给出了字符串长度对应的精度,例如geohash长度为9时,精度在2米左右
    • 两个字符串越相似,它们之间的距离越近,Redis利用字符串前缀匹配算法实现相关的命令
    • geohash编码和经纬度是可以相互转换的

img

img

  • Redis正是使用有序集合并结合geohash的特性实现了GEO的若干命令

六、删除地理位置信息(zrem)

1
zrem key member
  • GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除
  • 例如,下面将cities:locations中的所有地理位置信息删除

img