news 2026/4/16 14:58:36

【算法分享】R树索引

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【算法分享】R树索引

车联网、POI 分析、地理围栏、行政区划判断等业务中,我们经常会遇到一个非常基础、但极其高频的问题:

👉给定一个经纬度点,它属于哪个(或哪些)多边形?

比如:

  • 一个 GPS 点属于哪个行政区?
  • 一个车辆轨迹点是否落在某个物流园围栏内?
  • 一个 POI 是否在规划的服务区域中?

这个问题在 GIS 领域被称为:Point in Polygon(PIP)问题

本文将结合一份完整可运行的 Python 代码,从:

  • 算法原理
  • 工程实现
  • 性能瓶颈
  • 加速手段(包围盒、R 树)

几个层面,系统性地讲清楚:点在哪个多边形里,到底该怎么算、怎么选、怎么快。

一、最原始的方法:逐个多边形判断

1️⃣ 思路

最直观的方式是:

  • 对每一个多边形
  • 判断点是否在这个多边形内部

数学上常见的实现有:

  • 射线法(Ray Casting)

    它计算从点P开始的射线穿过多边形边界的次数。当交叉数是偶数时,点在外面;当交叉数是奇数时,点在里面。

  • 环绕数法(Winding Number)

    他计算多边形围着点P旋转地次数,只有当圈数wn=0时,点在外面;否则,点在里面。

在工程中,我们通常不会自己手写几何算法,而是直接使用成熟库。

2️⃣ 代码实现

defpoint_in_polygon(point_lon,point_lat,polygon_coords):""" 判断一个点是否在多边形内 参数: point_lon: 点的经度(float) point_lat: 点的纬度(float) polygon_coords: 多边形顶点列表,格式为 [(lon1, lat1), (lon2, lat2), ...] 返回: True: 点在多边形内 False: 点不在多边形内 """# 方式一:基于射线法importmatplotlib.pathasmplPathimportnumpyasnp polygon=mplPath.Path(np.array(polygon_coords))returnpolygon.contains_point(point)# 方式二:基于射线法importcv2 cv2.pointPolygonTest(np.array(polygon_coords),point,measureDist=False)# 方式三:基于环绕数法fromshapely.geometryimportPoint,Polygon point=Point(point_lon,point_lat)polygon=Polygon(polygon_coords)returnpolygon.contains(point)

如果有多个多边形:

deffind_point_in_polygons(point_lon,point_lat,polygons_dict):""" 查找点所在的多边形(不使用索引,适合小数据集) 参数: point_lon: 点的经度 point_lat: 点的纬度 polygons_dict: 多边形字典 返回: 包含该点的多边形名称列表 """point=Point(point_lon,point_lat)result=[]forname,coordsinpolygons_dict.items():polygon=Polygon(coords)ifpolygon.contains(point):result.append(name)returnresult

3️⃣ 性能分析

  • 时间复杂度:O(N)
  • N = 多边形数量

如果:

  • 多边形数量 < 100
  • 查询点数量不多

👉完全没问题,简单、清晰、好维护。

但一旦进入真实业务场景:

  • 上千 / 上万围栏
  • 每秒上百 / 上千点

性能会迅速成为瓶颈。

二、第一步加速:包围盒(Bounding Box)过滤

1️⃣ 核心思想

绝大多数点,
连多边形的“外接矩形”都不在里面。

那我们就可以:

  1. 先判断点是否在多边形的包围盒内(极快)
  2. 再对“可能命中”的少量多边形做精确判断

2️⃣ 什么是包围盒?

对一个多边形:

(minx, miny, maxx, maxy)

只要:

minx ≤ lon ≤ maxx miny ≤ lat ≤ maxy

有可能在多边形内。

3️⃣ 代码结构

classPolygonIndexWithBounds:""" 使用包围盒快速过滤的空间索引 """def__init__(self,polygons_dict):"""初始化包围盒索引"""self.polygons={}self.bounds_map={}forname,coordsinpolygons_dict.items():polygon=Polygon(coords)self.polygons[name]=polygon self.bounds_map[name]=polygon.boundsdeffind_point_in_polygons(self,point_lon,point_lat):"""查找点所在的多边形"""point=Point(point_lon,point_lat)result=[]# 通过包围盒过滤candidates=[]forname,boundsinself.bounds_map.items():minx,miny,maxx,maxy=boundsifminx<=point_lon<=maxxandminy<=point_lat<=maxy:candidates.append(name)# 精确检查fornameincandidates:ifself.polygons[name].contains(point):result.append(name)returnresultdeffind_points_in_polygons_batch(self,points_list):"""批量查询"""results=[]forlon,latinpoints_list:polygons=self.find_point_in_polygons(lon,lat)results.append({"point":(lon,lat),"polygons":polygons})returnresults

4️⃣ 性能效果

  • 包围盒判断:纯数值比较,极快
  • 精确几何判断次数大幅减少

在测试中:

🚀通常可提速 10 ~ 100 倍

5️⃣ 适用场景

  • 多边形数量:100 ~ 1000
  • 实现成本低
  • 不依赖额外空间索引库

👉性价比极高的工程方案

三、终极方案:R 树(Spatial Index)

1️⃣ 问题再升级

当你遇到:

  • 上万 / 数十万多边形
  • 实时或准实时查询

包围盒 O(N) 扫描也开始吃力。

这时候,就需要真正的空间索引

2️⃣ 什么是 R 树?

R 树是一种:

为多维空间查询设计的树结构

步骤:

  1. 对每个空间对象(点、线、面)构建最小外接矩形MBR。(如图D1|R8、D2|R9、D3|R10)
  2. 建树过程:自底向上分组
    1. 把若干个对象的 MBR→ 组合成一个“节点”
    2. 对一个节点,再算一个更大的 MBR→ 覆盖所有子节点
    3. 节点数量超过阈值→ 进行 节点分裂(split)
  3. 查询过程:从根开始逐层过滤,向下递归,到达叶子节点

特点:

  • 多层包围盒,类似 B+ 树的分层结构
  • 专为“空间相交 / 包含”优化,查询复杂度接近O(log N)

你可以把它理解为:

空间世界里的索引(像数据库索引一样)

3️⃣ Shapely 中的 R 树

Shapely 2.x 提供了STRtree

fromshapely.strtreeimportSTRtree tree=STRtree(geometry_list)

查询时:

candidates=tree.query(point,predicate='intersects')

再进行一次精确判断即可。

classPolygonIndexWithRtree:""" 使用R树空间索引的多边形查询 性能:最快(100-1000x) Rtree使用动态边界体积树,专门为空间查询优化 """def__init__(self,polygons_dict):"""初始化R树索引"""try:fromshapely.strtreeimportSTRtreeexceptImportError:print("⚠️ 需要 shapely >= 2.0,使用 pip install --upgrade shapely")raiseself.polygons={}self.geometry_list=[]self.name_list=[]# 构建几何体列表用于R树forname,coordsinpolygons_dict.items():polygon=Polygon(coords)self.polygons[name]=polygon self.geometry_list.append(polygon)self.name_list.append(name)# 创建R树索引self.tree=STRtree(self.geometry_list)deffind_point_in_polygons(self,point_lon,point_lat):""" 使用R树查询点所在的多边形 """point=Point(point_lon,point_lat)result=[]# R树查询:获取可能包含该点的多边形索引candidates_idx=self.tree.query(point,predicate='intersects')# 精确检查(因为intersects只是粗略检查)foridxincandidates_idx:ifself.geometry_list[idx].contains(point):result.append(self.name_list[idx])returnresultdeffind_points_in_polygons_batch(self,points_list):"""批量查询"""results=[]forlon,latinpoints_list:polygons=self.find_point_in_polygons(lon,lat)results.append({"point":(lon,lat),"polygons":polygons})returnresults

4️⃣ 性能表现

在示例代码中,使用:

  • 1000 个多边形
  • 500 个查询点

代码链接:点击查看

测试结果:

多边形越多,R 树优势越明显。

四、三种方案的对比总结

方法时间复杂度构建成本查询速度适用场景
直接遍历O(N)小数据、一次性分析
包围盒过滤O(N)极低中等规模围栏
R 树索引O(log N)极快大规模、实时场景

五、工程选型建议

  • 多边形 < 100 个: 直接 contains 判断
  • 多边形 100 ~ 1000: 包围盒过滤
  • 多边形 > 1000: R 树索引(强烈推荐)

📬 关注我 · 获取更多内容

📌 南墨的技术小栈

这里是我的个人知识分享空间。我会定期整理和分享工作与学习中积累的经验与资源,内容涵盖:

  • 算法分享 —— 深入讲解算法原理、实现思路与代码示例。
  • 工具分享 —— 推荐实用工具与脚本,包括我个人开发的小工具和精选开源工具。
  • 开源项目 —— 精选 GitHub 上高星项目,拆解原理、使用方法和最佳实践。
  • 数据分享 —— 工作学习中收集整理的数据资源。

无论你是技术爱好者、算法研究者,还是对数据与开源感兴趣的朋友,这里都希望能成为你学习、探索和实践的参考空间。

若在阅读或使用过程中有任何疑问,欢迎在公众号私信我,我会尽快与您交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 7:47:12

强烈安利10个AI论文工具,专科生搞定毕业论文不求人!

强烈安利10个AI论文工具&#xff0c;专科生搞定毕业论文不求人&#xff01; AI工具&#xff0c;让论文写作不再难 对于专科生来说&#xff0c;毕业论文的撰写往往是一道难以跨越的难关。面对繁重的写作任务、复杂的格式要求以及对学术规范的不熟悉&#xff0c;很多学生感到无从…

作者头像 李华
网站建设 2026/4/16 4:53:55

汽车制造行业KindEditor如何处理设计图WORD粘贴?

企业网站Word粘贴和导入功能开发全记录 需求分析与技术评估 作为福建某软件公司的前端工程师&#xff0c;我最近接到一个企业网站后台管理系统的功能升级需求。客户希望在现有的KindEditor编辑器中增加Word粘贴和文档导入功能&#xff0c;同时支持微信公众号内容粘贴并自动处…

作者头像 李华
网站建设 2026/4/16 12:52:01

免费Nano Banana 制作PPT,SpeedAI 智能体一句话生成

大家好&#xff0c;这里是K姐。 一个帮你追踪最新AI应用的女子&#xff01; 2026了&#xff0c;不会还有打工人述职汇报是自己哼哧哼哧手搓 PPT 吧&#xff1f; 想用 AI 做 PPT 省时间&#xff0c;很多人第一反应就是 Nano Banana。确实不少大佬用做 Nano Banana 的 PPT&…

作者头像 李华
网站建设 2026/4/16 9:22:00

Flutter 三端应用实战:OpenHarmony 简易文本首字母提取器开发指南

一、为什么需要“简易文本首字母提取器”&#xff1f; 在 OpenHarmony 的内容摘要、笔记整理与快速索引场景中&#xff0c;“首字符”具有独特的信息密度价值&#xff1a; 学生&#xff1a;从课堂笔记中快速提取关键词首字&#xff0c;构建记忆锚点&#xff1b;程序员&#x…

作者头像 李华
网站建设 2026/4/16 9:24:50

从 Flutter 到 OpenHarmony:实现底部弹窗的跨端实践

文章目录从 Flutter 到 OpenHarmony&#xff1a;实现底部弹窗的跨端实践前言背景Flutter OpenHarmony 跨端开发介绍开发核心代码&#xff08;详细解析&#xff09;1️⃣ 构建底部弹窗示例卡片2️⃣ 弹出底部弹窗方法心得总结从 Flutter 到 OpenHarmony&#xff1a;实现底部弹窗…

作者头像 李华
网站建设 2026/4/16 9:20:15

零基础吃透Linux内核核心配置文件(Kconfig/.config/defconfig)

零基础吃透Linux内核核心配置文件&#xff08;Kconfig/.config/defconfig&#xff09; &#xff08;附实战避坑面试考点&#xff09; 作为Linux内核/嵌入式开发新手&#xff0c;你大概率会被 Kconfig、.config、defconfig 这些文件搞晕——它们到底是干嘛的&#xff1f;为什么编…

作者头像 李华