Rendering big geodata on the fly with GeoJSON-VT
- 低zoom等级,简化几何形状,减小数据量
- 高zoom等级,只渲染屏幕内的数据,在 CPU 中使用预先计算的瓦片进行裁剪
- Mapnik
- PostGIS
- geojson-vt
- 使用Web-Mercator投影将所有坐标投影到屏幕坐标中(整个数据集)
- 要素简化、瓦片裁剪都是在平面坐标系下进行 screen_x = [tile_size × (2^zoom x − tile_x)] screen_y = [tile_size × (2^zoom y − tile_y)]
-
简化的依据
- 过滤掉面积太小和长度太短的面和线
- 一根折线中对整体形状影响较小的顶点可以过滤掉
-
并没有立即简化数据,而是用一个重要值标记每个顶点,因此可以在缩放级别上快速包含或排除它
-
因为像素距离随着2^zoom而线性缩放,所以我们可以对数据进行一次简化,并使用相同的值在每个缩放级别上优化细节,几乎立即过滤掉不必要的顶点
-
然后我们计算每个多边形环的面积和每条多段线的长度。面积和长度以与距离相同的方式按缩放级别缩放,这使我们能够在线性时间内使用这些预先计算的值过滤出太小而无法在任何特定缩放级别上显示的几何图形
-
最后,我们保存每个feature的bounding box,这将使我们能够更快地进行剪裁。
-
线状要素使用Ramer–Douglas–Peucker algorithm进行抽稀,实现参考simplify.js
- 切片涉及到将数据剪裁到正方形的程度,但我们不能对每个切片在整个数据集上运行剪裁算法——这太慢了
- 解决方法
- 裁剪顶级瓦片z0到四个子瓦片z1
- 裁剪每个父瓦片z1到四个子瓦片z2
- 递归执行...
-
因为每一张瓦片的数据只需要裁剪其父瓦片而获得,而不需要裁剪整个数据集。但是可以继续优化。
-
最常见的矩形剪切算法Sutherland–Hodgman algorithm
- ...
-
最后...
-
为了在没有明显延迟的情况下实时提供矢量瓦片,理想情况下,我们将生成所有矢量瓦片,直到最大限度地放大到缓存中,然后在请求时立即提供瓦片
-
这对于小数据集来说可能很好,但对于更大的数据集,瓦片切片例程最终会耗尽内存。仅在缩放级别15上,就可能有4⁵ = ~10亿个瓦片——这太多了,尤其是对浏览器来说。
-
因此,我们不能在初始数据加载时将瓦片切片到最大缩放,但我们可以将它们切片到后续按需切片所需的时间非常短,以至于加载延迟无法察觉的程度。Geo JSON-VT通过以下几种方式解决了这一问题:
- 最初,瓦片被生成到一定的缩放级别,剩下的一切都留给按需切片。
- 切片瓦片时,我们会跟踪每个瓦片中组合的点数。当瓦片低于某个阈值时,初始切片停止 (默认为100000点)。如果平铺包含的几何图形很少,则可以根据需要快速生成其子图形。
- 当向下钻取到特定的瓦片时,Geo JSON-VT在缩放范围内每次缩放仅将一个瓦片切片为4个子瓦片,并缓存所有切片的瓦片。这种向下钻取相邻瓦片的方式非常快,因为将从上一个瓦片向下钻取中缓存小得多的父瓦片。
-
我发现初始切片默认值(z5和100k点)是我测试算法的大多数样本数据集的最佳点——初始处理相对较快,而后续的向下钻取几乎是即时的。
-
我们还需要最小化算法的每个步骤中使用的内存量。这是通过以下几点实现的:
- 原始几何体仅保持在最高缩放瓦片中,并且一旦瓦片被切割成下一缩放级别的4个瓦片就被丢弃, 仅保留简化的数据。这样,当切片更深时,存储所有平铺数据所需的内存不会呈指数级增长。
- 所有递归都用迭代算法消除。简化和瓦片切片(本质上是递归的)都是使用在循环中处理的简单数组队列来实现的。这避免了不再需要的几何体堆积在调用堆栈中的情况:它被正确地垃圾收集。
- 将投影的(0..1,0..1)坐标转换为屏幕坐标总是按需进行,因为如果在对所有瓦片进行切片后立即构建新阵列,则会占用大量内存。
- 填充到平铺范围的实心正方形(完全位于多边形内的平铺)永远不会进一步切片,因为无论缩放级别如何,其所有子对象都与父对象相同。