当前位置: 首页 > ops >正文

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 应用提供稳定支持。

http://www.xdnf.cn/news/15219.html

相关文章:

  • SpringAI实现聊天记录保存到MySQL
  • 「日拱一码」025 机器学习——评价指标
  • Spring 框架中的设计模式:从实现到思想的深度解析
  • C++类模板继承部分知识及测试代码
  • 在 Android 库模块(AAR)中,BuildConfig 默认不会自动生成 VERSION_CODE 和 VERSION_NAME 字段
  • Linux之Zabbix分布式监控篇(一)
  • 云原生技术与应用-生产环境构建高可用Harbor私有镜像仓库
  • 网络通信模型对比:OSI与TCP/IP参考模型解析
  • BGP 路由优选属性(7)【MED】官方考试综合实验题【bgp】【acl】【ip-prefix】【route-policy】【icmp 环路】精讲
  • Frida绕过SSL Pinning (证书绑定)抓包;Frida注入;app无法抓包问题解决。
  • 哔哩哔哩第三方TV-BBLL最新版
  • Pyqt5 FlexRay
  • Redis事件机制
  • 特辑:Ubuntu,前世今生
  • Claude code在Windows上的配置流程
  • 基于Opencv的缺陷检测实战
  • cuDNN 的 IMPLICIT_GEMM 算法
  • 深入理解设计模式:建造者模式详解
  • Spring Boot 2.4+中bootstrap.yml加载顺序的源码深度解析
  • NLP:RNN文本生成案例分享
  • 常用控件QWidget
  • 第10讲——一元函数积分学的几何应用
  • 关于解决win 11安装mathtype报错的问题(toolbar.eql)
  • 计算机毕业设计ssm基于Web的高校食堂管理系统 基于SSM框架的大学智慧餐饮服务平台 JavaWeb校园食堂一站式订餐与供应链系统
  • 【kubernetes】--controller(DaemonSet)
  • SD卡初始化、命令及响应命令格式(详细)讲解
  • 分层架构的C++高并发内存池性能优化
  • 无法打开windows安全中心解决方案
  • DirectX Repair修复工具下载,.NET修复,DirectX修复
  • 2025 全球酒店用品厂家竞争力排行榜发布:扬州卓韵领衔,布草工厂实力重塑行业格局