Redis Geospatial 功能详解及多边形包含判断实现
在 LBS(基于位置的服务)应用开发中,判断点与地理区域的位置关系是常见需求。Redis 作为高性能的内存数据库,其 Geospatial 功能为地理位置处理提供了高效支持。本文将详细介绍 Redis Geospatial 的核心功能、使用示例,并深入探讨如何结合射线法实现多边形包含判断,以及解决射线法的边界偏差问题。
## 一、Redis Geospatial 功能概述
Redis Geospatial 是专门用于存储、查询和处理地理位置信息的模块,基于有序集合(Sorted Set)实现,通过 GeoHash 算法将经纬度坐标编码为整数,从而支持高效的地理空间操作。
### 1. 核心功能
- **地理位置存储**:将经纬度与对象关联(如用户、商家位置)。
- **距离计算**:计算两个地点的直线距离。
- **范围查询**:根据中心点和半径查询范围内的地点。
- **坐标获取**:获取指定对象的经纬度。
### 2. 常用命令
| 命令 | 功能描述 |
|------|----------|
| `GEOADD key longitude latitude member` | 添加地理位置(经纬度+对象名称) |
| `GEODIST key member1 member2 [unit]` | 计算两个对象的距离(支持 m/km/mi 等单位) |
| `GEORADIUS key longitude latitude radius unit [选项]` | 以指定经纬度为中心,查询半径范围内的对象 |
| `GEORADIUSBYMEMBER key member radius unit [选项]` | 以已有对象为中心,查询半径范围内的其他对象 |
| `GEOPOS key member` | 获取指定对象的经纬度坐标 |
| `GEOHASH key member` | 返回对象坐标的 GeoHash 编码 |
## 二、Redis Geospatial 实战示例:外卖平台附近商家查询
以“外卖平台查找附近商家”为例,演示 Redis Geospatial 的具体用法。
### 1. 存储商家地理位置
使用 `GEOADD` 命令存储商家经纬度:
```bash
# 格式:GEOADD 键名 经度 纬度 商家名称
GEOADD restaurant 116.403874 39.914885 "肯德基(天安门店)"
GEOADD restaurant 116.410088 39.91583 "麦当劳(王府井店)"
GEOADD restaurant 116.397470 39.908823 "必胜客(前门大街店)"
GEOADD restaurant 116.422092 39.913423 "汉堡王(东单店)"
```
### 2. 查询商家坐标
通过 `GEOPOS` 命令获取指定商家的经纬度:
```bash
GEOPOS restaurant "肯德基(天安门店)" "麦当劳(王府井店)"
```
返回结果:
```
1) 1) "116.40387344360351562"
2) "39.91488499783993867"
2) 1) "116.4100879430770874"
2) "39.91582990074949318"
```
### 3. 计算商家距离
使用 `GEODIST` 计算两个商家的直线距离(单位:千米):
```bash
GEODIST restaurant "肯德基(天安门店)" "麦当劳(王府井店)" km
```
返回结果(约 0.7 公里):
```
"0.7042"
```
### 4. 圆形范围查询
通过 `GEORADIUS` 查询用户附近 3 公里内的商家(返回距离和坐标):
```bash
GEORADIUS restaurant 116.407000 39.910000 3 km WITHCOORD WITHDIST COUNT 3
```
返回结果(按距离排序):
```
1) 1) "肯德基(天安门店)"
2) "0.5213" # 距离用户约 0.5 公里
3) 1) "116.40387344360351562"
2) "39.91488499783993867"
2) 1) "必胜客(前门大街店)"
2) "1.2345" # 距离用户约 1.2 公里
3) 1) "116.39747047424316406"
2) "39.90882301696463576"
```
## 三、多边形包含判断:点是否在多边形内部?
Redis Geospatial 原生不支持多边形包含判断,需结合应用层算法实现。下面介绍如何通过“Redis 存储坐标 + 射线法判断”实现该功能。
### 1. 实现思路
1. **Redis 存储点坐标**:用 `GEOADD` 存储待判断的点(如用户位置),通过 `GEOPOS` 获取经纬度。
2. **应用层定义多边形**:在代码中定义多边形顶点坐标(按顺时针/逆时针排序)。
3. **射线法判断**:通过射线法(Ray Casting Algorithm)判断点是否在多边形内部。
### 2. 射线法原理
射线法的核心逻辑:从目标点向右发射一条水平射线,统计射线与多边形边界的交点数量。若交点数为**奇数**,则点在多边形内部;若为**偶数**,则在外部。
### 3. 代码实现(Python)
#### 步骤 1:从 Redis 获取点坐标
```python
import redis
# 连接 Redis
r = redis.Redis(host='localhost', port=6379, db=0)
# 存储用户坐标
r.geoadd("users", 116.4050, 39.9120, "userA")
# 获取用户经纬度
user_coords = r.geopos("users", "userA")[0]
user_lng, user_lat = float(user_coords[0]), float(user_coords[1])
user_point = (user_lng, user_lat)
```
#### 步骤 2:定义多边形顶点
```python
# 多边形顶点(示例:商业区边界)
polygon = [
(116.4000, 39.9100), # 顶点 1
(116.4100, 39.9100), # 顶点 2
(116.4100, 39.9150), # 顶点 3
(116.4000, 39.9150) # 顶点 4
]
```
#### 步骤 3:射线法实现(含边界处理)
```python
def is_point_on_segment(point, seg_start, seg_end):
"""判断点是否在多边形的边上(含端点)"""
lng, lat = point
x1, y1 = seg_start
x2, y2 = seg_end
# 检查点是否在线段的经纬度范围内
if not (min(x1, x2) <= lng <= max(x1, x2) and min(y1, y2) <= lat <= max(y1, y2)):
return False
# 检查点是否在直线上(斜率相等)
if (y2 - y1) == 0: # 水平线
return lat == y1
if (x2 - x1) == 0: # 垂直线
return lng == x1
return (lat - y1) * (x2 - x1) == (y2 - y1) * (lng - x1)
def is_point_in_polygon(point, polygon):
"""判断点是否在多边形内(含边界)"""
lng, lat = point
n = len(polygon)
inside = False
# 先判断点是否在边上或顶点上
for i in range(n):
seg_start = polygon[i]
seg_end = polygon[(i+1) % n]
if is_point_on_segment(point, seg_start, seg_end):
return True # 边界点视为内部(可根据业务调整)
# 射线法核心逻辑
for i in range(n):
j = (i + 1) % n
xi, yi = polygon[i]
xj, yj = polygon[j]
# 判断射线是否与边相交
if ((yi > lat) != (yj > lat)):
# 计算交点经度
x_intersect = ( (lat - yi) * (xj - xi) ) / (yj - yi) + xi
if lng < x_intersect:
inside = not inside # 翻转内外状态
return inside
# 执行判断
result = is_point_in_polygon(user_point, polygon)
print(f"用户是否在多边形内:{result}") # 输出 True 或 False
```
### 4. 射线法的偏差与解决
射线法在**边界场景**(点在边上、顶点上、射线共线)可能出现偏差,需通过以下方式优化:
- **优先判断边界**:提前检查点是否在边上或顶点上,直接返回结果。
- **处理顶点交点**:当射线经过顶点时,仅在相邻边分属射线两侧时计数。
- **避免射线共线**:通过微小偏移射线(如略微倾斜)避免与边共线。
## 四、总结
1. **Redis Geospatial 优势**:高效支持地理位置存储、距离计算和圆形范围查询,适合高并发 LBS 场景(如外卖、打车软件)。
2. **多边形判断方案**:Redis 存储坐标 + 应用层射线法,可实现点与多边形的位置判断。
3. **注意事项**:射线法需处理边界场景以避免偏差,实际应用中可结合业务需求定义边界点的归属。
通过 Redis 与算法的结合,能够高效实现复杂的地理空间功能,为 LBS 应用提供稳定支持。