GEODIST:计算两个位置之间的直线距离

在使用 GEOADD 命令将位置及其坐标存储到位置集合中之后,可以使用 GEODIST 命令计算两个给定位置之间的直线距离:

GEODIST location_set name1 name2

在默认情况下,GEODIST 命令将以米为单位,返回两个给定位置之间的直线距离。

举个例子,通过执行以下代码,我们可以计算出清远和广州这两座城市之间的直线距离:

redis> GEODIST Guangdong-cities Qingyuan Guangzhou
"52094.433840356309" -- 清远和广州之间的直线距离约为52094m

而通过执行以下命令,我们可以计算出清远和深圳之间的直线距离:

redis> GEODIST Guangdong-cities Qingyuan Shenzhen
"144220.75758239598" -- 清远和深圳之间的直线距离约为144220m

指定距离的单位

GEODIST 命令在默认情况下将以米为单位返回两个给定位置之间的直线距离,用户也可以在有需要的情况下,通过可选的 unit 参数来指定自己想要使用的单位:

GEODIST location_set name1 name2 [unit]

unit 参数的值可以是以下单位中的任意一个:

  • m——以米为单位,为默认单位。

  • km——以千米为单位。

  • mi——以英里为单位。

  • ft——以英尺为单位。

前面在计算两座城市之间的距离时,使用了默认的米作为单位,因为两座城市之间的距离较远,所以 GEODIST 命令给出的计算结果看上去不够简洁。为此,我们可以改用千米为单位,再次调用 GEODIST 命令计算清远和广州以及清远和深圳之间的直线距离:

redis> GEODIST Guangdong-cities Qingyuan Guangzhou km
"52.094433840356309" -- 清远和广州之间直线距离约为52km
redis> GEODIST Guangdong-cities Qingyuan Shenzhen km
"144.22075758239598" -- 清远和深圳之间直线距离约为144km

改用千米为单位之后,GEODIST 命令给出的计算结果就简洁多了。

处理不存在的位置

在调用 GEODIST 命令时,如果用户给定的某个位置并不存在于位置集合中,那么命令将返回空值,表示计算失败。

比如在以下示例中,因为 Guangdong-cities 位置集合中并未存储肇庆市的坐标,所以尝试使用 GEODIST 命令去计算清远和肇庆之间的直线距离将得到一个空值:

redis> GEODIST Guangdong-cities Qingyuan Zhaoqing
(nil)

其他信息

  • 复杂度:O(log(N)),其中 N 为位置集合目前包含的位置数量。

  • 版本要求:GEODIST 命令从 Redis 3.2.0 版本开始可用。

示例:具有基本功能的用户地理位置程序

很多社交网站都提供了与地理位置相关的功能,比如记录用户的位置、获取指定用户的位置,或者查找指定范围内的其他用户等。

在学习了 GEOADD、GEOPOS 和 GEODIST 这 3 个命令之后,我们同样可以构建出一个具有基本功能的用户地理位置程序,该程序能够记录用户所在的位置、获取指定用户所在的位置以及计算两个用户之间的直线距离,具体实现如代码清单9-1所示。

代码清单9-1 具有基本功能的用户地理位置程序:/geo/location.py
import random

USER_LOCATION_KEY = "user_locations"

class Location:

    def __init__(self, client):
        self.client = client
        self.key = USER_LOCATION_KEY

    def pin(self, user, longitude, latitude):
        """
        记录指定用户的坐标。
        """
        self.client.geoadd(self.key, longitude, latitude, user)

    def get(self, user):
        """
        获取指定用户的坐标。
        """
        position_list = self.client.geopos(self.key, user)
        # geopos() 允许用户输入多个位置,然后以列表形式返回各个位置的坐标。
        # 因为我们这里只传入了一个位置,所以只需要取出列表的第一个元素即可。
        if len(position_list) != 0:
            return position_list[0]

    def calculate_distance(self, user_a, user_b):
        """
        以公里为单位,计算两个用户之间的直线距离。
        """
        return self.client.geodist(self.key, user_a, user_b, unit="km")

    def find_nearby(self, user, radius=1):
        """
        以公里为单位,寻找并返回 user 指定半径范围内的所有其他用户。
        """
        all_nearby_users = self.client.georadiusbymember(self.key, user, radius, unit="km")
        # 因为 georadiusbymember() 方法会把 user 本身也包含在结果里面,
        # 但由于我们并不需要这个用户,所以使用 remove() 方法移除他
        all_nearby_users.remove(user)
        return all_nearby_users

    def find_random_nearby(self, user, radius=1):
        """
        以公里为单位,随机地返回一个位于 user 指定半径范围内的其他用户。
        """
        # random.choice() 方法用于从列表中随机地选择并返回一个项
        return random.choice(self.find_nearby(user, radius))

这个程序所做的就是使用 GEOADD 命令把用户的 ID 以及用户所在的经纬度关联起来,然后使用 GEOPOS 命令去获取用户所在的经纬度,或者使用 GEODIST 命令去计算两个用户之间的直线距离。

通过执行以下代码,我们可以创建出相应的用户位置对象,并使用它记录 peter、jack 和 tom 这 3 个用户的地理位置:

>>> from redis import Redis
>>> from location import Location
>>> client = Redis(decode_responses=True)
>>> location = Location(client)
>>> location.pin("peter", 113.20996731519699, 23.593675019671288)
>>> location.pin("jack", 113.22784155607224, 23.125598202060807)
>>> location.pin("tom", 113.40603142976761, 22.511156445825442)

然后通过执行以下代码,获取 peter 和 jack 的坐标:

>>> location.get("peter")
(113.20996731519699, 23.593675019671288)
>>> location.get("jack")
(113.22784155607224, 23.125598202060807)

最后通过执行以下代码,计算 peter 和 jack 之间的直线距离以及 peter 和 tom 之间的直线距离:

>>> location.calculate_distance("peter", "jack")
52.0944
>>> location.calculate_distance("peter", "tom")
122.0651