在Observable Vector Tile Dissector 一文中,我评估了不同的矢量切片供应商提供的切片大小。文中提到的一些限制,其实是完全随机选择的,依据的是我丰富的经验积累……但如何才能真正减小切片大小?我将尝试优化自己的矢量切片并逐步解释。
以下所有空间数据均基于 OpenStreetMap,投影为web 墨卡托 (EPSG:3857) ,并托管于PostGIS 数据库。
长话短说
使用一些方法后,我们可以将给定矢量切片的大小减小到原始大小的 14%。

这些“无损”的方法是:
- 移除不使用的数据
- 减小缓冲区
- 合并要素
- 压缩
现状
选择两个不同缩放级别的切片,让我们观察一下它们的大小:
切片 | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
大小 | 64 KiB (64984 Bytes) | 352 KiB (359820 Bytes) |
原始数据 |
![]() |
![]() |
渲染后 |
![]() |
![]() |
数据本身已经根据缩放层级进行了删减,并且还概化 [1] 了(降低空间复杂性)低缩放级别的要素。还能搞点啥?
Identifier
让我们看看切片里都有啥:

我们看到许多存储为数字的 ID
。z10这个层级大概有几千个。然而当前配图并没有用到 ID
,因此我们可以从Layer Schema
中删除对 osm_id/id
的所有引用:

看看结果如何:

数字类型的属性少了很多,切片大小也显著减小:
Tile | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
原始切片 | 64 KiB (64984 Bytes) | 352 KiB (359820 Bytes) |
移除Id 后 |
51 KiB (51966 Bytes) | 278 KiB (283677 Bytes) |
相对大小 | 80% | 79% |
Nice!仅仅是删除Id
————这个未使用的属性,大小就减少了约 20%!
然而在某些情况下挂载ID字段是有意义的,例如需要根据ID从切片中提取数据(比如通过OnClick()事件)看哈这个Azure Maps
:

Anyway,咱们再压榨点空间试试。
Tile Buffer
最近我写过一篇关于切片缓冲区的文章。目前我缓冲大小为256。试着减小到64:
切片 | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
原始数据 |
![]() |
![]() |
渲染后 |
![]() |
![]() |
没有出现visual artefacts,但切片也没小多少。意料之中。
Tile | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
previous step | 51 KiB (51966 Bytes) | 278 KiB (283677 Bytes) |
64 coordinate unit buffer | 50 KiB (51114 Bytes) | 276 KiB (282250 Bytes) |
relative size | 98% | 99% |
Merge
看看缩放层级为10时的其他统计数据:

可以发现:
公路和铁路层占所有要素的 87%。
这些要素中超过 95% 仅包含 2 个点。
让我们随机选一条道路并检查一下数据库:
SELECT
ST_NPoints(geometry) as points,
geometry
FROM import.roads_gen10
where (geometry && ST_Transform(ST_MakeEnvelope(11.2500, 47.9899, 11.6016, 48.2247, 4326), 3857)) AND ref = 'St 2345' order by points DESC;

将这条道路在 postgis 中编码为矢量切片:
SELECT
length(ST_AsMVT(q, 'roads', 4096, 'geom')) as length
FROM
(
SELECT
ST_AsMVTGeom(
geometry,
ST_Transform(ST_MakeEnvelope(11.2500, 47.9899, 11.6016, 48.2247, 4326), 3857),
4096,
1) AS geom
FROM
import.roads_gen10
WHERE
(geometry && ST_Transform(ST_MakeEnvelope(11.2500, 47.9899, 11.6016, 48.2247, 4326), 3857))
AND ref = 'St 2345' ) AS q;
看一下结果:
Name | Value |
---|---|
length | 1639 |
这么说来。该切片中一条由 112 个单独的 LineString 组成的连续道路,编码为矢量瓦片后,占用了 1639 字节空间。而这112条LineString中,大部分(104条)都只有两个点。
如果我们 使用ST_LineMerge() 将只有 两个个 点的 LINESTRING 合并为一个大的 MULTILINESTRING 会发生什么?
SELECT
length(ST_AsMVT(q, 'roads', 4096, 'geom')) AS length
FROM
(
SELECT
ST_AsMVTGeom(
ST_LineMerge( ST_Collect(geometry) ),
ST_Transform(ST_MakeEnvelope(11.2500, 47.9899, 11.6016, 48.2247, 4326), 3857),
4096,
0) AS geom
FROM
import.roads_gen10
WHERE
(geometry && ST_Transform(ST_MakeEnvelope(11.2500, 47.9899, 11.6016, 48.2247, 4326), 3857))
AND ref = 'St 2345' ) as q;
results in:
Name | Value |
---|---|
length | 398 |
So,仅仅是合并 LINESTRINGs 我们就将这条路的大小减少了 75%。背后的原理是,每次绘制一条新的 LINESTRING ,必须先将cursor移动到该LineString的起点,才能开始绘制。如果新Linestring的起点与上一个LineString的终点相同,则可以省略。也许我会写另一篇文章来详细解释。在此之前,请参阅 Vector Tile 规范。
Anyway,在真实场景中试试:

干得漂亮!现在我们的切片里的要素又少又大。我们也可以合并其他一些LineStrings,例如waterways图层。
其实面图层也可以采用这种方法。我们还可以使用 ST_Union()函数 将切片内的所有建筑物多边形合并为一个大的MultiPolygon,而且效果不错,尤其是在低缩放级别的情况下:
Tile | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
上一步骤 | 50 KiB (51114 Bytes) | 276 KiB (282250 Bytes) |
合并要素后 | 31 KiB (31518 Bytes) | 73 KiB (74081 Bytes) |
相对大小 | 62% | 26% |
要使用此功能,您需要指定一个新的属性 (geom_query) 并对所有的keys施加group by
:

请记住,这些合并操作代价不菲,有可能会减慢未缓存的访问。
GZIP
在减小了数据编码为切片的大小后,我们可以使用压缩算法进一步减小大小。所有可用的浏览器都支持 GZIP,因此无需担心兼容性问题。

Tile | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
上一步骤 | 31 KiB (31518 Bytes) | 73 KiB (74081 Bytes) |
压缩后 | 22 KiB (22382 Bytes) | 49 KiB (50061 Bytes) |
相对大小 | 71% | 68% |
这不仅有助于节省存储成本,还可以缩短最终用户的加载时间。
Summary
Tile | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
Orignal | 64 KiB (64984 Bytes) | 352 KiB (359820 Bytes) |
移除Id 后 |
51 KiB (51966 Bytes) | 278 KiB (283677 Bytes) |
调整Buffer后 | 50 KiB (51114 Bytes) | 276 KiB (282250 Bytes) |
合并要素后 | 31 KiB (31518 Bytes) | 73 KiB (74081 Bytes) |
相对大小 | 48% | 21% |
gzip压缩后 | 22 KiB (22382 Bytes) | 49 KiB (50061 Bytes) |
相对大小 | 34% | 14% |
渲染图显示出一些细微差异,主要原始是某些要素的排序发生了变化:
Tile | 14/8717/5683.mvt | 10/544/355.mvt |
---|---|---|
之前 |
![]() |
![]() |
之后 |
![]() |
![]() |
通过分析矢量切片及其空间数据,我们能够将矢量切片减小到原始大小的 21%(算上压缩的话,14%),同时并没有降低视觉体验。这些方法适用于几乎所有矢量切片服务器。
我期待更多关于如何进一步减小切片大小的想法。
參考
原文链接:Optimizing vector tile size(cyclemap.link)
网友评论