Redis `GEOHASH` 与 `GEODIST`:地理位置编码与距离计算原理

好的,咱们开始今天的“Redis GEOHASH 与 GEODIST:地理位置编码与距离计算原理”讲座!

大家好!今天咱们来聊聊 Redis 里面两个非常有意思,也相当实用的命令:GEOHASHGEODIST。这两个家伙能让你在 Redis 里玩转地理位置信息,比如查找附近的人,计算两地之间的距离,听起来是不是很酷?

一、什么是 GEOHASH?别怕,不是什么神秘代码

首先,我们得搞清楚 GEOHASH 到底是个什么玩意儿。简单来说,GEOHASH 是一种地理位置编码,它能把地球上的任何一个经纬度坐标转换成一个字符串。 这个字符串越长,代表的精度越高,也就是范围越小。

想象一下,你拿着一张世界地图,想告诉你的朋友“我就在这里!” 最粗略的方式是说:“我在亚洲!”。稍精确一点:“我在中国!”。再精确一点:“我在北京!”。最后,你可以说:“我在北京海淀区中关村大街XX号”。 GEOHASH 就有点类似这种层层递进的过程,只不过它用的是字符串编码。

GEOHASH 的原理:递归分割与交错合并

GEOHASH 的核心思想是递归分割交错合并

  1. 递归分割: 首先,把地球想象成一个矩形,这个矩形包含所有的经度和纬度。然后,我们把这个矩形一分为二,分成东西两半。如果你的位置在东半边,就记作“1”,如果在西半边,就记作“0”。

    接着,再把包含你位置的那一半矩形分成南北两半。如果在北半边,记作“1”,如果在南半边,记作“0”。

    就这样,不断地分割下去,每次分割都产生一个 0 或者 1。分割的次数越多,得到的 01 串就越长,代表的位置就越精确。

  2. 交错合并: 刚才我们分别对经度和纬度进行了分割,得到了两串 01 串。现在,我们要把这两串 01 串交错合并起来。 比如,经度得到的 01 串是 "1010",纬度得到的 01 串是 "0101",那么交错合并后的 01 串就是 "10011001"。

  3. Base32 编码: 最后,把合并后的 01 串转换成 Base32 编码。 Base32 是一种用 32 个字符(通常是 A-Z 和 2-7)来表示 01 串的编码方式。 这样,我们就得到了 GEOHASH 字符串。

举个栗子:

假设我们要编码的经纬度是 (116.397428, 39.90923)。

  1. 经度分割:

    • 经度范围是 -180 到 180。
    • 116.397428 > ( -180 + 180 ) / 2 = 0,所以第一位是 1。
    • 继续分割包含 116.397428 的范围,直到达到所需的精度。
  2. 纬度分割:

    • 纬度范围是 -90 到 90。
    • 39.90923 > ( -90 + 90 ) / 2 = 0,所以第一位是 1。
    • 继续分割包含 39.90923 的范围,直到达到所需的精度。
  3. 交错合并: 将经度和纬度的 01 串交错合并。

  4. Base32 编码: 将合并后的 01 串转换成 Base32 编码,得到 GEOHASH 字符串,比如 "wx4g0s"。

GEOHASH 的优点:

  • 简单高效: 编码和解码过程都比较简单,计算速度快。
  • 可变精度: GEOHASH 的长度决定了精度,可以根据需求选择合适的长度。
  • 前缀匹配: GEOHASH 的一个重要特性是,如果两个位置的 GEOHASH 字符串具有相同的前缀,那么它们的位置也比较接近。 这使得我们可以通过前缀匹配来查找附近的地点。

二、Redis GEOHASH 命令:GEOHASH

在 Redis 中,GEOHASH 命令的作用是获取指定地理位置的 GEOHASH 字符串

语法:

GEOHASH key member [member ...]
  • key:存储地理位置信息的 Redis 键名。
  • member:要查询的地理位置的成员名。

示例:

假设我们已经用 GEOADD 命令添加了一些地理位置信息到 Redis 中:

GEOADD places 116.397428 39.90923 "北京"
GEOADD places 121.473701 31.230416 "上海"

现在,我们可以使用 GEOHASH 命令来获取 "北京" 和 "上海" 的 GEOHASH 字符串:

> GEOHASH places 北京 上海
1) "wx4g0s"
2) "wtw37r"

可以看到,GEOHASH 命令返回了一个数组,包含了 "北京" 和 "上海" 的 GEOHASH 字符串。

三、Redis GEODIST 命令:计算距离,不再靠感觉

GEODIST 命令用于计算两个地理位置之间的距离

语法:

GEODIST key member1 member2 [unit]
  • key:存储地理位置信息的 Redis 键名。
  • member1:第一个地理位置的成员名。
  • member2:第二个地理位置的成员名。
  • unit:距离单位,可选值有:
    • m:米(默认值)
    • km:千米
    • mi:英里
    • ft:英尺

示例:

继续使用上面的例子,我们可以使用 GEODIST 命令来计算 "北京" 和 "上海" 之间的距离:

> GEODIST places 北京 上海 km
"1204.1733"

结果显示,"北京" 和 "上海" 之间的距离约为 1204.1733 千米。

四、GEOHASH 和 GEODIST 的结合应用:查找附近的人

现在,我们把 GEOHASHGEODIST 结合起来,实现一个“查找附近的人”的功能。

  1. 存储地理位置信息: 首先,我们需要把所有人的地理位置信息存储到 Redis 中。可以使用 GEOADD 命令:

    import redis
    
    r = redis.Redis(host='localhost', port=6379)
    
    people = {
        "张三": (116.397428, 39.90923),  # 北京
        "李四": (116.46, 39.92),  # 北京附近
        "王五": (121.473701, 31.230416),  # 上海
        "赵六": (114.05, 22.54),  # 深圳
    }
    
    for name, location in people.items():
        r.geoadd("people_locations", location[0], location[1], name)
    
    print("地理位置信息已存储到 Redis 中。")
  2. 获取目标位置的 GEOHASH: 假设我们要查找 "张三" 附近的人,首先需要获取 "张三" 的 GEOHASH 字符串:

    zhangsan_geohash = r.geohash("people_locations", "张三")[0]
    print(f"张三的 GEOHASH 是:{zhangsan_geohash.decode()}")
  3. 查找具有相同 GEOHASH 前缀的人: 我们可以根据 GEOHASH 的前缀来缩小搜索范围。 比如,我们可以查找与 "张三" 具有相同前 5 位 GEOHASH 前缀的人。 但是,直接用 GEOHASH 前缀搜索 Redis 效率不高。 更常用的方法是使用 GEORADIUSGEORADIUSBYMEMBER 命令,它们可以根据给定的中心点和半径来查找附近的地点。

    使用 GEORADIUS 命令:

    #查找距离张三 5km 以内的人
    nearby_people = r.georadius("people_locations", people["张三"][0], people["张三"][1], 5, unit="km", withdist=True, withcoord=True, withhash=True)
    
    print("张三附近的人:")
    for person in nearby_people:
        name = person[0].decode()
        distance = person[1]
        coordinates = person[2]
        geohash = person[3]
    
        print(f"  - {name}: 距离 {distance} km, 坐标 {coordinates}, GEOHASH {geohash}")

    解释:

    • r.georadius("people_locations", people["张三"][0], people["张三"][1], 5, unit="km", withdist=True, withcoord=True, withhash=True) 这个命令的意思是:
      • "people_locations":在名为 "people_locations" 的 Redis 键中查找。
      • people["张三"][0], people["张三"][1]:以 "张三" 的经纬度作为中心点。
      • 5:查找半径为 5 km 的范围。
      • unit="km":距离单位为千米。
      • withdist=True:返回结果包含距离。
      • withcoord=True:返回结果包含坐标。
      • withhash=True:返回结果包含 GEOHASH。

    使用 GEORADIUSBYMEMBER 命令:

    #查找距离张三 5km 以内的人
    nearby_people = r.georadiusbymember("people_locations", "张三", 5, unit="km", withdist=True, withcoord=True, withhash=True)
    
    print("张三附近的人:")
    for person in nearby_people:
        name = person[0].decode()
        distance = person[1]
        coordinates = person[2]
        geohash = person[3]
    
        print(f"  - {name}: 距离 {distance} km, 坐标 {coordinates}, GEOHASH {geohash}")

    解释:

    • r.georadiusbymember("people_locations", "张三", 5, unit="km", withdist=True, withcoord=True, withhash=True) 这个命令的意思是:
      • "people_locations":在名为 "people_locations" 的 Redis 键中查找。
      • "张三":以 "张三" 的位置作为中心点。
      • 5:查找半径为 5 km 的范围。
      • unit="km":距离单位为千米。
      • withdist=True:返回结果包含距离。
      • withcoord=True:返回结果包含坐标。
      • withhash=True:返回结果包含 GEOHASH。
  4. 过滤结果: GEORADIUSGEORADIUSBYMEMBER 命令已经帮我们过滤出了附近的地点,我们可以根据实际需求对结果进行进一步的处理。

五、GEOHASH 精度与长度对照表

GEOHASH 的长度决定了精度,下面是一个 GEOHASH 长度与精度的大致对照表:

GEOHASH 长度 纬度精度 (m) 经度精度 (m)
1 ± 2500000 ± 5000000
2 ± 630000 ± 1300000
3 ± 78000 ± 157000
4 ± 20000 ± 20000
5 ± 2400 ± 5000
6 ± 610 ± 610
7 ± 76 ± 153
8 ± 19 ± 19
9 ± 2.4 ± 4.8
10 ± 0.61 ± 0.61
11 ± 0.15 ± 0.30
12 ± 0.038 ± 0.038

注意: 这个表格只是一个大致的参考,实际精度可能会受到纬度的影响。 纬度越高,经度方向的精度会降低。

六、总结

今天我们学习了 Redis 中的 GEOHASHGEODIST 命令,了解了 GEOHASH 的原理和应用,以及如何利用这两个命令实现“查找附近的人”的功能。

  • GEOHASH:用于获取地理位置的 GEOHASH 字符串。
  • GEODIST:用于计算两个地理位置之间的距离。
  • GEORADIUSGEORADIUSBYMEMBER:更常用的查找附近地点命令。

希望今天的讲座对大家有所帮助! 掌握了这些技能,你就可以在你的项目中轻松地添加地理位置相关的特性,比如:

  • 查找附近的餐馆、酒店、电影院等。
  • 实时追踪车辆、人员的位置。
  • 根据用户的位置推荐相关的信息。

下次见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注