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

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

常用场景


  • 图像处理
  • 空间数据索引
  • 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_…


目录
相关文章
|
机器学习/深度学习
883. 三维形体投影面积 : 简单模拟题
883. 三维形体投影面积 : 简单模拟题
|
定位技术
ArcEngine中的缩放地图
在ArcEngine地图操作中,缩放地图的功能经常用到,这里做一个小结。 缩放地图一般可分为以下几种情况: 1.缩放地图:与放大地图相对,一般是手动绘制区域或固定比例缩放,可调用命令或Expand函数来; 2.缩放到图层:这一种用得比较多,通常是将图层转为GeoDataset,利用其他Extent属性来缩放到图层; 3.缩放到选中:选中一个或多个要素,根据选择的要素,创建Geometry,获取Envelope。
1153 0
|
算法
ArcGIS生成根据点图层生成等值面并减小栅格锯齿的操作步骤
原文:ArcGIS生成根据点图层生成等值面并减小栅格锯齿的操作步骤 一、打开ArcMAP并加载上相应的点图层和边界面图层 二、ArcToolbox--Spatial Analyst工具--差值分析--克里金法(根据不同的情况选择不同的算法,这次的处理实际上使用的是样条函数法,比其它算法更平滑,...
2094 0
|
算法 前端开发 定位技术
【GIS新探索】算法实现在不规则区域内均匀分布点
1 概要         在不规则区域内均匀分布点,这个需求初看可能不好理解。如果设想一下需求场景就比较简单了。         场景1:在某个地区范围内,例如A市区有100W人口,需要将这100W人口在地图上面相对均匀的标识出来。
1884 0
|
算法
基于序列图像的三维体绘的视线投射算法
基于序列图像的三维体绘的视线投射算法(Ray Casting) 一.体绘算法步骤   1.体数据的生成:将序列图像的象素数据部分剥离出来(如果是JPG等压缩类型的数据,还需要先解压缩),按照相对的上下层之间的关系,将其存到一内存区中,保存的原则比较简单,即能够根据象素的三维坐标最快的检索到体素的值(此处的象素值可以是灰度值、颜色值或者CT值等)。
1076 0
|
9月前
|
定位技术
基于ENVI实现栅格遥感影像按图层行列号与像元数量划定矩形研究区域并裁剪
基于ENVI实现栅格遥感影像按图层行列号与像元数量划定矩形研究区域并裁剪
106 1
|
定位技术
ArcGIS地形起伏度+地形粗糙度+地表切割深度+高程变异系数提取
ArcGIS地形起伏度+地形粗糙度+地表切割深度+高程变异系数提取
8455 0
|
6月前
|
算法 数据建模
平面中判断点在三角形内算法(重心法)
平面中判断点在三角形内算法(重心法)
60 0

热门文章

最新文章