好的,咱们开始今天的“Redis GEOHASH 与 GEODIST:地理位置编码与距离计算原理”讲座!
大家好!今天咱们来聊聊 Redis 里面两个非常有意思,也相当实用的命令:GEOHASH
和 GEODIST
。这两个家伙能让你在 Redis 里玩转地理位置信息,比如查找附近的人,计算两地之间的距离,听起来是不是很酷?
一、什么是 GEOHASH?别怕,不是什么神秘代码
首先,我们得搞清楚 GEOHASH 到底是个什么玩意儿。简单来说,GEOHASH 是一种地理位置编码,它能把地球上的任何一个经纬度坐标转换成一个字符串。 这个字符串越长,代表的精度越高,也就是范围越小。
想象一下,你拿着一张世界地图,想告诉你的朋友“我就在这里!” 最粗略的方式是说:“我在亚洲!”。稍精确一点:“我在中国!”。再精确一点:“我在北京!”。最后,你可以说:“我在北京海淀区中关村大街XX号”。 GEOHASH 就有点类似这种层层递进的过程,只不过它用的是字符串编码。
GEOHASH 的原理:递归分割与交错合并
GEOHASH 的核心思想是递归分割和交错合并。
-
递归分割: 首先,把地球想象成一个矩形,这个矩形包含所有的经度和纬度。然后,我们把这个矩形一分为二,分成东西两半。如果你的位置在东半边,就记作“1”,如果在西半边,就记作“0”。
接着,再把包含你位置的那一半矩形分成南北两半。如果在北半边,记作“1”,如果在南半边,记作“0”。
就这样,不断地分割下去,每次分割都产生一个 0 或者 1。分割的次数越多,得到的 01 串就越长,代表的位置就越精确。
-
交错合并: 刚才我们分别对经度和纬度进行了分割,得到了两串 01 串。现在,我们要把这两串 01 串交错合并起来。 比如,经度得到的 01 串是 "1010",纬度得到的 01 串是 "0101",那么交错合并后的 01 串就是 "10011001"。
-
Base32 编码: 最后,把合并后的 01 串转换成 Base32 编码。 Base32 是一种用 32 个字符(通常是 A-Z 和 2-7)来表示 01 串的编码方式。 这样,我们就得到了 GEOHASH 字符串。
举个栗子:
假设我们要编码的经纬度是 (116.397428, 39.90923)。
-
经度分割:
- 经度范围是 -180 到 180。
- 116.397428 > ( -180 + 180 ) / 2 = 0,所以第一位是 1。
- 继续分割包含 116.397428 的范围,直到达到所需的精度。
-
纬度分割:
- 纬度范围是 -90 到 90。
- 39.90923 > ( -90 + 90 ) / 2 = 0,所以第一位是 1。
- 继续分割包含 39.90923 的范围,直到达到所需的精度。
-
交错合并: 将经度和纬度的 01 串交错合并。
-
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 的结合应用:查找附近的人
现在,我们把 GEOHASH
和 GEODIST
结合起来,实现一个“查找附近的人”的功能。
-
存储地理位置信息: 首先,我们需要把所有人的地理位置信息存储到 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 中。")
-
获取目标位置的 GEOHASH: 假设我们要查找 "张三" 附近的人,首先需要获取 "张三" 的 GEOHASH 字符串:
zhangsan_geohash = r.geohash("people_locations", "张三")[0] print(f"张三的 GEOHASH 是:{zhangsan_geohash.decode()}")
-
查找具有相同 GEOHASH 前缀的人: 我们可以根据 GEOHASH 的前缀来缩小搜索范围。 比如,我们可以查找与 "张三" 具有相同前 5 位 GEOHASH 前缀的人。 但是,直接用 GEOHASH 前缀搜索 Redis 效率不高。 更常用的方法是使用
GEORADIUS
或GEORADIUSBYMEMBER
命令,它们可以根据给定的中心点和半径来查找附近的地点。使用
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。
-
过滤结果:
GEORADIUS
和GEORADIUSBYMEMBER
命令已经帮我们过滤出了附近的地点,我们可以根据实际需求对结果进行进一步的处理。
五、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 中的 GEOHASH
和 GEODIST
命令,了解了 GEOHASH 的原理和应用,以及如何利用这两个命令实现“查找附近的人”的功能。
GEOHASH
:用于获取地理位置的 GEOHASH 字符串。GEODIST
:用于计算两个地理位置之间的距离。GEORADIUS
和GEORADIUSBYMEMBER
:更常用的查找附近地点命令。
希望今天的讲座对大家有所帮助! 掌握了这些技能,你就可以在你的项目中轻松地添加地理位置相关的特性,比如:
- 查找附近的餐馆、酒店、电影院等。
- 实时追踪车辆、人员的位置。
- 根据用户的位置推荐相关的信息。
下次见!