四叉树应用于地图(点聚合)、碰撞检测等问题

简介: 四叉树应用于地图(点聚合)、碰撞检测等问题

常用场景


  • 图像处理
  • 空间数据索引
  • 2D 中的快速碰撞检测
  • 稀疏数据


算法场景一:游戏开发碰撞检测

游戏开发中经常有碰撞检测的算法,对于平面上N个图形,如果需要检测互相之间是否发生碰撞。


基本算法:进行N(N-1)次比较,时间复杂度是O(n^2)四叉树算法:时间复杂度是O(nlog n)


image.png


算法场景二:地图上标注的显示逻辑

为了保证规律性和平均分配,采用区域划分方法显示标注。简单来说就是把屏幕分割成若干个区域,每个区域最多显示一个标注,然后根据地图缩放比例动态的设置这些区域的大小以达到最佳的用户体验。


解决:非满四叉树算法如果有100条数据,我们可以嵌套循环找到合适的点放入相应的区域,循环10000次,如果有10000条数据,可能我们的操作系统就要抓狂了。这种计算方法是低效的,时间复杂度至少为O(n^2)。


image.png


实现过程


1)创建四叉树

将数据逐个放入这个树状结构,给每一个节点一个范围。创建过程较为费时,建议另开线程。


2)四叉树查找出需要的标注

首先,我们要理清一个逻辑,如果数据量很大,构建四叉树仍然需要一些时间,但是这个时间是我们允许的,相信你也不会频繁的请求大量标注数据。

关键问题就是在用户拖拽旋转结束的时候,我们需要调整我们界面的显示UI,也就是说,关键问题就是从这个四叉树里面查找出需要的标注,这部分的速度至关重要。

四叉树没有删除的方法,因为删除成本大于重建树结构成本。


主要逻辑:第一步:遍历屏幕划分的区域 第二步:比较该区域是否和四叉树元素范围有交集,无交集则舍弃,有交集继续向下查找

优化:我对查找逻辑做了小优化。在第二步的时候,如果有交集,给是否继续向下查找做了一个开关。理由是,我们查找出所有在该划分区域内的标注,如果目的只是为了算出他们的平均中心点,就显得意义不大,大可查找出几个就停止查找。

聚合分为四个级别:市、区、街道,点(不聚合)


image.png


非满四叉树优化:为每个结点添加一个“容量”的属性,在四叉树初始化时只有一个根结点,在插入数据时,如果一个结点内的数据量大于了结点“容量”,再将结点进行分裂。如此,可以保证每个结点内都存储着数据,避免了内存空间的浪费。


查询:只有找到了位置对应的结点,那么结点下的所有点都会是此位置的附近点,更小的“容量”意味着每个结点内点越少,也就意味着查询的精度会越高。


边界点问题:每个结点内的点必然是相邻的,但相邻的点越不一定在同一个结点内


image.png


特性: 字典树,又称前缀树或trie树,是一种有序树,用于保存关联数组,其中的键通常是字符串。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。

解决: 继续优化四叉树,给结点添加一个“编号”属性。


image.png


代码实现


1)四分区域标识

typedef enum
{
     UR = 0, // UR第一象限
     UL = 1, // UL为第二象限
     LL = 2, // LL为第三象限
     LR = 3  // LR为第四象限
}QuadrantEnum;


2)空间对象数据结构

typedef struct SHPMBRInfo
{
     int nID;       // 空间对象ID号
     MapRect Box;   // 空间对象MBR范围坐标
}SHPMBRInfo;


3)四叉树节点数据结构

typedef struct QuadNode
{
     MapRect Box;           // 节点所代表的矩形区域
     Int nShpCount;         // 节点所包含的所有空间对象个数
     SHPMBRInfo* pShapeObj; // 空间对象指针数组
     Int nChildCount;       // 子节点个数
     QuadNode *children[4]; // 指向节点的四个孩子
}QuadNode;


四叉树原理


四叉树是种很直接的空间索引技术。在四叉树中,每个节点表示覆盖了部分进行索引的空间的边界框,根节点覆盖了整个区域。每个节点要么是叶节点,有包含一个或多个索引点的列表,没有孩子。要么是内部节点,有四个孩子,每个孩子对应将区域沿两根轴对半分得到的四个象限中的一个,四叉树也因此得名。


image.png


四叉树的操作


1.节点分裂

  • 当满足特定条件时,为了获得更好的检索效率,四叉树的节点会发生分裂,分裂的做法是:以当前节点的矩形为中心, 划分出四个等大的矩形,作为四个子节点,每个节点深度=当前节点深度+1,根节点深度为0;并且将当前节点的元素重新插入到对应的子节点。


2.插入元素

  • 平面的元素多种多样,点,线,图形,但都能够做一个统一,第一步都是要找出图形所覆盖的节点,这里以矩形为例
  • 从根节点开始,如果当前节点无子节点,将元素插入到当前节点。如果存在子节点K,并且元素能够完全被子节K点所“包围”,就将元素插入到子节点K,对于子节点K进行上述递归操作;如果元素跨越了多个子节点,那就将元素存储在当前节点
  • 如果某一节点元素超过特定值,并且深度小于四叉树允许的最大深度,分裂当前节点。


3.检索

  • 对于给定检索区域,从根节点起,如果检索区域与当前节点有交集,返回当前节点的所有元素。
  • 如果当前节点还有子节点,递归检索与检索区域有交集的子节点。


业务定义


后台实现聚合算法,App 拿到数据后,直接显示聚合点或非聚合点。具体实现如下:

  • App 传入缩放级别,来获取标注数据,标注数据需要一个字段来标识这些点的聚合级别(市、区、街道,点)。
  • 根据标注数据向地图上添加标注。
  • 地图缩放时,重复步骤 1 和 步骤 2。这里需要注意的是,我们要将地图缩放级别按照聚合级别划分区间,同一区间内避免多次获取数据(同一聚合级别的数据没有变化,或者说变化不大,可以依赖于其他的数据刷新机制,如:再次进入 App)。


源码


主文配套:github.com/indulgeIn/Y…

API 设计合理 + 中文注释:github.com/hadesh/iOS_…


目录
相关文章
|
XML Web App开发 人工智能
SVG图像——为 PPT 增添视觉趣味/03/O365智能系列(二)
SVG图像——为 PPT 增添视觉趣味/03/O365智能系列(二)
2844 0
SVG图像——为 PPT 增添视觉趣味/03/O365智能系列(二)
|
编解码 程序员 atlas
Unity 之 图集属性详解和代码示例 -- 拓展一键自动打包图集工具
图集只是当所有给低昂的纹理需要相同的着色器时采用的一种方法,如果一些纹理需要通过着色器应用独立的图形效果,它们就必须分离到自己的材质中,并在单独的组中打图集。
3278 0
Unity 之 图集属性详解和代码示例 -- 拓展一键自动打包图集工具
|
JavaScript Windows
Cocos Creator3.8 项目实战(十)使用 protobuf详细教程
Cocos Creator3.8 项目实战(十)使用 protobuf详细教程
2265 0
|
Web App开发 缓存 网络协议
解决Github不能下载,下载速度慢的小妙招
解决Github不能下载,下载速度慢的小妙招
解决Github不能下载,下载速度慢的小妙招
|
算法 机器人 Python
【启发式算法】RRT*算法详细介绍(Python)
RRT(Rapidly-exploring Random Tree Star)* 是一种用于机器人路径规划的启发式算法,它是在经典的 RRT(Rapidly-exploring Random Tree)算法的基础上进行改进的。RRT* 通过优化路径质量,能够找到最短的路径,适用于高维空间中的路径规划问题。
1743 2
|
安全 API 开发者
国内Claude 3.5怎么注册使用?
Claude 3.5由Anthropic开发,可通过特定平台如Slack或API使用。注册需用稳定邮箱如Gmail,并通过国外手机号验证。Slack用户可直接添加Claude 3.5应用进行交互,支持中英文。开发者可注册Anthropic API获取密钥。此外,国内有镜像服务可供使用。请注意隐私政策及访问限制。
5153 2
国内Claude 3.5怎么注册使用?
|
Docker 容器
docker设置国内镜像源
docker设置国内镜像源
49706 5
|
设计模式 JavaScript 前端开发
【Vue3】4个比较重要的设计模式!!
【Vue3】4个比较重要的设计模式!!