![d19edb8b73f83c9c85ad37f868535598.png](https://i-blog.csdnimg.cn/blog_migrate/101f7db9a6a34728e56a2087a517f601.jpeg)
在上一篇文章中,我们介绍了 Mapbox 对于海量数据展示的最佳实践,即数据瓦片方案,以及其中涉及的对于 LineString 和 Polygon 要素的简化算法。
潘与其:使用数据瓦片展示海量数据zhuanlan.zhihu.com![68077b7c9b2522dc56c67088d425b5c9.png](https://i-blog.csdnimg.cn/blog_migrate/36e2b56de6aa708963b9b2f06a7854bb.jpeg)
最后我们遗留了两个问题:
- 对于 Point 要素,我们需要不同于 LineString 和 Polygon 要素的简化算法
- 将要素简化、裁剪瓦片放入 WebWorker 中进行。保证主线程中用户流畅的地图交互
在本文中我们将解决第一个问题,按以下顺序介绍:
- 基于 k-d tree 的空间索引和查询操作
- 结合数据瓦片方案,应用 k-d tree
- 实现点聚合图,包括在 WebGL 中绘制圆形的方法
点聚合场景
对于展现 Point 要素的点分布图来说,在低缩放等级下同样不需要渲染全量数据,我们可以采用聚合的方式展示点集合的概览。例如下图(不是给安居客打广告哈),随着缩放等级的提升,将出现更多更小的集合,直至最终每个点的分布被展现出来:
![bdcce0c1546161b14136f8ea9fc13b04.png](https://i-blog.csdnimg.cn/blog_migrate/22664896ba32d692c069bf476c8bf9d9.jpeg)
在以上点聚合的场景中,我们有两个问题需要解决:
- 如何聚合?即给定一个点,以此为圆心,如何找到一定半径范围内所有点?
- 聚合完毕后,给定一个包围盒(例如当前视口),如何找到其中包含的聚合后的要素?
对于这两个问题(radius & range query),在海量点数据下,如果使用暴力遍历每个点的方法必然是低效的。为了高效搜索,我们需要使用空间索引。
空间索引
强烈推荐阅读 Mapbox 工程师的这篇文章:「A dive into spatial search algorithms」,本文后续引用的 k-d tree 的 JS 实现 kdbush 正是出自他手,此外他还写了 r-tree 的 JS 实现 rbush 以及 rbush-knn。
https://blog.mapbox.com/a-dive-into-spatial-search-algorithms-ebd0c5e39d2ablog.mapbox.com知乎上也有一篇介绍 spatial index 空间索引的文章,介绍了 r-tree、k-d tree、八叉树等实现:
李喆:空间索引Spatial Indexingzhuanlan.zhihu.com![e05cf1aa76851cbfccf2849ab5e92e95.png](https://i-blog.csdnimg.cn/blog_migrate/e6ef0cfa8d93774d6f52881dda0870e6.png)
在空间数据库中肯定少不了索引,例如 PostGIS 中的索引就是基于 R-tree 实现的:
不睡觉的怪叔叔:PostGIS教程一:PostGIS介绍zhuanlan.zhihu.com![0a8056e48d4b3ecf5fa6cb4361dd8ddb.png](https://i-blog.csdnimg.cn/blog_migrate/8370d0a06a9ff18d9c45edf5ff787e2f.jpeg)
除了地理场景,在 光线追踪 场景中,k-d tree 也可用于 Ray 与包围盒的求交,参考玉米关于 PBRT 相关章节的笔记:
玉米:PBRT-E4.4-KD树(KD-Tree)zhuanlan.zhihu.com限于篇幅本文只介绍 k-d tree 的构建以及查询算法。如果只关心如何在点聚合场景中应用,可以直接跳到最后 DEMO 实现部分。
创建 k-d tree
K-d(k-dimensional)tree 是一棵二叉树,每个叶节点都是一个 k 维的点。而每个非叶节点表示分割平面,分割方向在 X 和 Y 轴间交替。下面我们通过 Wiki 中的例子简单感受 k-d tree 的构建过程。
https://en.wikipedia.org/wiki/K-d_treeen.wikipedia.org假设我们在空间中有一组点:(2,3), (5,4), (9,6), (4,7), (8,1), (7,2)。通过某种选择算法(下一节会详细介绍),我们首先选定 (7,2),以 X=7(红色) 为分割线将点集分成左右两部分,接着分别在左右两部分中选定 (5,4) 和 (9,6),切换方向以 Y=4 和 Y=6(蓝色)继续分割成上下两部分。