• redis GEO使用及基本原理——实现对经纬度的操作


    写在前面

    redis的GEO数据结构,专门用来存储位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

    举个例子:
    网约车平台存储所有的司机经纬度位置信息,用户发起打车请求的时候,我们记录用户的经纬度,并且计算哪一辆车距离用户最近,然后进行派单。一辆车的经纬度信息是不断变化的,修改频繁。
    此时就可以使用redis的GEO数据类型了。

    实际上,GEO 类型的底层数据结构就是用 Sorted Set 来实现的。
    用 Sorted Set 来保存车辆的经纬度信息时,Sorted Set 的元素是车辆 ID,元素的权重分数是经纬度信息,如下图所示:
    在这里插入图片描述
    一个经纬度其实是包含两个值的,如何将两个值变成一个值进行存储呢?那就是GeoHash编码。

    GEO 类型中的 GeoHash 编码

    为了能高效地对经纬度进行比较,Redis 采用了业界广泛使用的 GeoHash 编码方法,这个方法的基本原理就是“二分区间,区间编码”。

    当我们要对一组经纬度进行 GeoHash 编码时,我们要先对经度和纬度分别编码,然后再把经纬度各自的编码组合成一个最终编码。

    经度和纬度的单独编码过程

    对于一个地理位置信息来说,它的经度范围是[-180,180]。GeoHash 编码会把一个经度值编码成一个 N 位的二进制值,我们来对经度范围[-180,180]做 N 次的二分区操作,其中 N 可以自定义。

    在进行第一次二分区时,经度范围[-180,180]会被分成两个子区间:[-180,0) 和[0,180](我称之为左、右分区)。此时,我们可以查看一下要编码的经度值落在了左分区还是右分区。如果是落在左分区,我们就用 0 表示;如果落在右分区,就用 1 表示。这样一来,每做完一次二分区,我们就可以得到 1 位编码值。

    然后,我们再对经度值所属的分区再做一次二分区,同时再次查看经度值落在了二分区后的左分区还是右分区,按照刚才的规则再做 1 位编码。当做完 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。这个编码过程如下表所示:
    在这里插入图片描述
    对纬度的编码方式,和对经度的一样,只是纬度的范围是[-90,90],下面这张表显示了对纬度值 39.86 的编码过程。

    在这里插入图片描述
    当一组经纬度值都编完码后,我们再把它们的各自编码值组合在一起,组合的规则是:最终编码值的偶数位上依次是经度的编码值,奇数位上依次是纬度的编码值,其中,偶数位从 0 开始,奇数位从 1 开始。

    我们刚刚计算的经纬度(116.37,39.86)的各自编码值是 11010 和 10111,组合之后,第 0 位是经度的第 0 位 1,第 1 位是纬度的第 0 位 1,第 2 位是经度的第 1 位 1,第 3 位是纬度的第 1 位 0,以此类推,就能得到最终编码值 1110011101,如下图所示:
    在这里插入图片描述
    用了 GeoHash 编码后,原来无法用一个权重分数表示的一组经纬度(116.37,39.86)就可以用 1110011101 这一个值来表示,就可以保存为 Sorted Set 的权重分数了。

    当然,使用 GeoHash 编码后,我们相当于把整个地理空间划分成了一个个方格,每个方格对应了 GeoHash 中的一个分区。

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

    • 分区一:[-180,0) 和[-90,0),编码 00;
    • 分区二:[-180,0) 和[0,90],编码 01;
    • 分区三:[0,180]和[-90,0),编码 10;
    • 分区四:[0,180]和[0,90],编码 11。

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

    在这里插入图片描述
    所以,我们使用 Sorted Set 范围查询得到的相近编码值,在实际的地理空间上,也是相邻的方格,这就可以实现 LBS 应用“搜索附近的人或物”的功能了。

    不过,我要提醒你一句,有的编码值虽然在大小上接近,但实际对应的方格却距离比较远。例如,我们用 4 位来做 GeoHash 编码,把经度区间[-180,180]和纬度区间[-90,90]各分成了 4 个分区,一共 16 个分区,对应了 16 个方格。编码值为 0111 和 1000 的两个方格就离得比较远,如下图所示:
    在这里插入图片描述
    所以,为了避免查询不准确问题,我们可以同时查询给定经纬度所在的方格周围的 4 个或 8 个方格。

    现在我们就知道了,GEO 类型是把经纬度所在的区间编码作为 Sorted Set 中元素的权重分数,把和经纬度相关的车辆 ID 作为 Sorted Set 中元素本身的值保存下来,这样相邻经纬度的查询就可以通过编码值的大小范围查询来实现了。

    GEO类型的使用

    GEOADD命令

    geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。

    基本语法:

    GEOADD key longitude latitude member [longitude latitude member ...]
    
    • 1

    基本用法:

    # key为Sicily ,其中存储了Palermo、Catania的经纬度,第一个参数为经度,第二个参数为纬度
    127.0.0.1:6379> geoadd Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
    (integer) 2
    
    • 1
    • 2
    • 3

    GEOPOS命令

    geopos 用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。

    基本语法:

    GEOPOS key member [member ...]
    
    • 1

    基本用法:

    127.0.0.1:6379> geopos  Sicily Palermo
    1) 1) "13.361389338970184"
       2) "38.115556395496299"
    127.0.0.1:6379> geopos  Sicily China
    1) (nil)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    GEODIST命令

    geodist 用于返回两个给定位置之间的距离。

    基本语法:

    GEODIST key member1 member2 [unit]
    
    • 1

    member1 member2 为两个地理位置。

    最后一个距离单位参数说明:

    • m :米,默认单位。
    • km :千米。
    • mi :英里。
    • ft :英尺。

    基本用法:

    127.0.0.1:6379> geodist Sicily "Palermo" "Catania"
    "166274.1516"
    127.0.0.1:6379> geodist Sicily "Palermo" "Catania" m
    "166274.1516"
    127.0.0.1:6379> geodist Sicily "Palermo" "Catania" km
    "166.2742"
    127.0.0.1:6379> geodist Sicily "Palermo" "Catania" mi
    "103.3182"
    127.0.0.1:6379> geodist Sicily "Palermo" "Catania" ft
    "545518.8700"
    127.0.0.1:6379> geodist Sicily "Palermo" "china"
    (nil)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    GEORADIUS 命令

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

    基本语法:

     GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
    
    • 1

    参数说明:

    m :米,默认单位。
    km :千米。
    mi :英里。
    ft :英尺。
    WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
    WITHCOORD: 将位置元素的经度和纬度也一并返回。
    WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
    COUNT 限定返回的记录数。
    ASC: 查找结果根据距离从近到远排序。
    DESC: 查找结果根据从远到近排序。

    基本用法:

    # 返回key为Sicily中经度15,纬度37,距离不超过200千米,返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
    127.0.0.1:6379> georadius Sicily 15 37 200 km WITHDIST
    1) 1) "Palermo"
       2) "190.4424"
    2) 1) "Catania"
       2) "56.4413"
    # 返回key为Sicily中经度15,纬度37,距离不超过200千米,返回位置元素的同时, 将位置元素的经度和纬度也一并返回。
    127.0.0.1:6379> georadius Sicily 15 37 200 km WITHCOORD
    1) 1) "Palermo"
       2) 1) "13.361389338970184"
          2) "38.115556395496299"
    2) 1) "Catania"
       2) 1) "15.087267458438873"
          2) "37.50266842333162"
    # 返回key为Sicily中经度15,纬度37,距离不超过200千米,返回位置元素的同时,将位置元素与中心之间的距离、位置元素的经度和纬度一并返回。
    127.0.0.1:6379>  georadius Sicily 15 37 200 km WITHDIST WITHCOORD
    1) 1) "Palermo"
       2) "190.4424"
       3) 1) "13.361389338970184"
          2) "38.115556395496299"
    2) 1) "Catania"
       2) "56.4413"
       3) 1) "15.087267458438873"
          2) "37.50266842333162"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    GEORADIUSBYMEMBER命令

    georadiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。

    基本语法:

    GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
    
    • 1

    基本使用:

    # 添加一个位置信息
    127.0.0.1:6379> GEOADD Sicily 13.583333 37.316667 "Agrigento"
    (integer) 1
    # 返回key为Sicily的,距离Agrigento 100千米以内的位置信息
    127.0.0.1:6379> GEORADIUSBYMEMBER Sicily Agrigento 100 km
    1) "Agrigento"
    2) "Palermo"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    GEOHASH命令

    Redis GEO 使用 geohash 来保存地理位置的坐标。

    geohash 用于获取一个或多个位置元素的 geohash 值。

    基本语法:

    GEOHASH key member [member ...]
    
    • 1

    基本用法:

    127.0.0.1:6379> geohash Sicily Palermo Catania
    1) "sqc8b49rny0"
    2) "sqdtr74hyu0"
    
    • 1
    • 2
    • 3
  • 相关阅读:
    计算机图形学实验——2023-09-28
    浅识k8s中的准入控制器
    有哪些比较流行的 C、C++和Java 的入门级项目?
    java毕业设计乒乓球俱乐部管理源码+lw文档+mybatis+系统+mysql数据库+调试
    线程池里对异常的处理方式
    【JavaSE专栏49】Java集合类LinkedList解析,链表和顺序表有什么不同?
    基于JAVA,SpringBoot和HTML校园二手商城系统设计
    JDBC 从入门到放弃
    k8s nginx .yaml 测试
    【网站架构】集群性能并非无限扩展,几千万几亿的网站系统贵在哪
  • 原文地址:https://blog.csdn.net/A_art_xiang/article/details/126768398