openlayers中RBush使用
微风斜阳,浊酒一壶 2024-08-20 10:33:02 阅读 84
前言
在之前的博文自定义Label标签抽稀(openlayers)中,我们自定义了抽稀标签的方法,但是检测两个标签是否碰撞的算法是简单暴力的遍历检测。如果标签一多,我们的方法效率就肯定会变慢。
有没有方法可以提过检测的效率呢,有,使用数据结构R树即可解决。之前在博文也提到过,openlayers的ol.layer.Vector类可以传入参数declutter,设置为true即可解决。其实openlayer使用的是RBush,这个库封装了对RTree增删查、碰撞检测等方法。
1、openlayers里rbush使用源码分析
对ol.layer.Vector类里feature是如何绘制的,可以参考之前的分析openlayer, 由一个图标遮盖线段需求引发的思考,可以查看地图详细的渲染逻辑。这里简单梳理下
ol.PluggableMap#render()
ol.renderer.canvas.Map#renderFrame(frameState)
ol.renderer.canvas.VectorLayer#prepareFrame(frameState, layerState)
ol.renderer.canvas.VectorLayer#composeFrame(frameState, layerState, context)
ol.render.canvas.ReplayGroup#replay(replayContext, transform, rotation, skippedFeatureUids)
ol.render.canvas.Replay#replay(context, transform, viewRotation, skippedFeaturesHash)
ol.render.canvas.Replay#replay_(context, transform, skippedFeaturesHash, instructions, featureCallback, opt_hitExtent)
最终在类ol.render.canvas.Replay的replay_方法里,在执行switch case语句时,当指令条件是ol.render.canvas.Instruction.DRAW_IMAGE、或者ol.render.canvas.Instruction.DRAW_CHARS时,会有如下代码,也就是说如果最后是绘制文字(ol.style.Text)或者图片(ol.style.Image)时,如果ol.layer.Vector设置declutter为true时,会进行抽稀操作
这个方法就是openlayer抽稀的核心逻辑了,只有两句。如下红框所示,判断当前的box是否和已有的空间数据碰撞,如果没有碰撞,说明可以执行绘制,将当前的空间数据范围加入rtree并执行绘制。
这个declutterTree_是哪里来的?是在ol.renderer.canvas.VectorLayer构造里初始化的,ol.ext.rbush其实就是RBush库
2、我们如何使用RBush库
node环境下直接npm安装;
如果要js引用的话,先到github上找到rbush库,clone下来后,进入目录依次执行npm install、npm run build命令进行编译,可以在根目录找到rbush.min.js,引入项目即可。
上面说了openlayer是如何使用rbush的,我们可以参照着写,针对碰撞检测,只需要先new一个Rbush对象,然后使用collides方法,如果返回false,就将新的空间数据insert即可。其api也不复杂,只是要注意下其空间数据的格式,是一个矩形对象,必须要4个属性minX、minY、maxX、maxY。
针对之前的标签抽稀方法,碰撞检测可以使用RBush来提高效率,代码修改如下
<code> TextDeclutter.prototype.canShow = function (label) {
let x = label.position[0]
let y = label.position[1]
let item = {
minX: x - label.labelWidth / 2 - this.marginX / 2,
minY: y - label.labelHeight / 2 - this.marginY / 2,
maxX: x + label.labelWidth / 2 + this.marginX / 2,
maxY: y + label.labelHeight / 2 + this.marginY / 2
}
if (this.rtree_.collides(item)) {
return false
}
this.rtree_.insert(item)
return true
}
3、RBush常用的API
3.1 创建实例对象
const tree = new RBush();
RBush的可选参数定义了树节点中的最大条目数。9(默认使用)是大多数应用程序的合理选择。值越高,插入速度越快,搜索速度越慢,反之亦然。
const tree = new RBush(16);
3.2 添加数据
插入一条数据:
const item = {
minX: 20,
minY: 40,
maxX: 30,
maxY: 50,
foo: 'bar'
};
tree.insert(item);
3.3 删除数据
删除之前插入的数据:
tree.remove(item);
默认情况下,RBush通过引用删除对象。但是,您可以传递一个自定义的equals函数,通过值进行比较以进行删除,这在您只有需要删除的对象的副本时非常有用(例如从服务器加载):
tree.remove(itemCopy, (a, b) => {
return a.id === b.id;
});
清除所有的数据:
tree.clear();
3.4 数据格式化
默认情况下,RBush假定数据点的格式为具有minX、minY、maxX和maxY属性的对象。您可以通过如下方式重写toBBox、compareMinX和compareMinY方法来对此进行自定义:
class MyRBush extends RBush {
toBBox([x, y]) { return {minX: x, minY: y, maxX: x, maxY: y}; }
compareMinX(a, b) { return a.x - b.x; }
compareMinY(a, b) { return a.y - b.y; }
}
const tree = new MyRBush();
tree.insert([20, 50]); // accepts [x, y] points
如果你正在索引一个静态的点列表(索引后不需要添加/删除点),你应该使用kdbush,它的点索引速度比RBush快5-8倍。
3.5 批量插入数据
将给定数据批量插入树中:
tree.load([item1, item2, ...]);
批量插入通常比逐一插入项目快2~3倍。批量加载(批量插入到空树中)后,后续查询性能也提高了约20-30%。
请注意,当您对现有树进行批量插入时,它会将给定的数据批量加载到单独的树中,并将较小的树插入较大的树中。这意味着批量插入对于集群数据(一次更新中的项目彼此靠近)非常有效,但如果数据分散,则会使查询性能变差。
3.6 搜索查询
const result = tree.search({
minX: 40,
minY: 20,
maxX: 80,
maxY: 70
});
返回给定边界框相交的数据项(点或矩形)数组。请注意,搜索方法接受{minX,minY,maxX,maxY}格式的边界框,而不管数据格式如何。
const allItems = tree.all();
返回树中的所有数据。
3.7 碰撞检测
const result = tree.collides({minX: 40, minY: 20, maxX: 80, maxY: 70});
如果有任何项目与给定的边界框相交,则返回true,否则返回false。
3.8 导出和导入
// export data as JSON object
const treeData = tree.toJSON();
// import previously exported data
const tree = rbush(9).fromJSON(treeData);
导入和导出为JSON允许您在服务器(使用Node.js)和浏览器上组合使用RBush,
例如,首先在服务器上对数据进行索引,然后在客户端上导入生成的树数据进行搜索。
请注意,传递给构造函数的`nodeSize`选项在两个树中必须相同,才能使导出/导入正常工作。
上一篇: 【保姆级教程】服务器Ubuntu22.04系统下的Stale diffusion+Webui部署安装
下一篇: Echart图表 之 X轴(xAxis)与 Y轴(yAxis)配置项大全
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。