Hotdry.
systems

MapLibre Tile 格式深度解析:Schema 设计原理与压缩效率基准

深入分析 MLT 矢量切片格式的列式存储架构,对比 MVT 的 schema 设计差异,详解编码选择策略与工程落地参数。

Mapbox Vector Tile(MVT)格式自 2014 年发布以来,几乎成为在线地图渲染的事实标准。AWS、Meta、Microsoft 等巨头将其用于各自的地图服务,OpenMapTiles 等开源项目也基于 MVT 构建了全球覆盖的底图数据。然而,MVT 诞生于十年前,其设计理念源于当时的硬件环境和数据规模。当地理信息的采集精度从米级提升至厘米级,数据源从人工标注扩展到 AI 自动检测,MVT 的局限性逐渐显现。MapLibre Tile(MLT)格式正是为解决这些问题而设计的下一代矢量切片规范。

MVT 的历史局限与 MLT 的设计动机

MVT 采用的是面向记录的行式存储结构,每个切片内部以 Layer 为单位组织,每个 Layer 包含一组 Feature,每条 Feature 又混合存储几何信息与属性数据。这种设计在 WebGL 渲染场景下自然直观,客户端可以直接按 Feature 顺序读取数据进行绘制。但随着地图数据规模的急剧膨胀,行式存储的压缩效率瓶颈日益突出。相邻 Feature 的同类型属性无法利用列式压缩算法,导致属性数据,尤其是字符串类型的道路名称、地名标签等,压缩率远低于理论最优值。

MLT 的核心设计理念是将分析型数据库的列式存储思想引入矢量切片领域。根据 MapLibre 团队在 2025 年 8 月发布的论文,MLT 在编码后的 tileset 上实现了最高三倍的压缩比,在大型切片上更是达到了六倍的压缩效率提升。这一性能跃升并非来自单一技术的突破,而是源于整个存储架构的重构:列式布局、流式分离、增量编码以及内存格式的零拷贝优化。

Schema 架构差异:列式存储与流式设计

理解 MLT 的 Schema 设计,首先需要区分几个核心概念:Tile、FeatureTable 和 Stream。Tile 是地理区域的数据载体,对应传统的 Layer 概念;FeatureTable 是属性数据的逻辑分组,包含若干 Column;每个 Column 又被物理拆分为多个 Stream,分别存储 presence、length、offset 和实际数据。这种分层设计借鉴了 Apache ORC 文件格式的思想,使得每个 Stream 可以独立选择最优编码方案。

以一个包含道路名称的字符串属性列为例,MVT 会将每个 Feature 的名称作为完整字符串写入,而 MLT 会将其拆分为 presence 标记流、长度流和数据流。如果该列存在大量重复值(如同一道路的多段共享同一个名称),字典编码可以将这些重复值压缩至单一副本,后续仅需存储指向字典的整数索引。对于包含数千个要素的城市道路层,这种优化可以将属性存储开销降低一个数量级。

Stream 类型的定义同样体现了工程务实性。MLT 定义了 Present Stream 用于处理可空列的稀疏编码,避免为不存在的值分配存储空间;Data Stream 承载实际数据,对于定长类型(如布尔值、整型、浮点型)这是唯一必需的流;Length Stream 用于变长类型(字符串、列表)的元素长度编码;Offset Stream 则在字典编码场景下提供快速随机访问能力。这种四流组合覆盖了绝大多数地理信息的数据模式,且实现复杂度可控。

编码方案选择:压缩率与解码速度的权衡

MLT 并非追求单一指标的最优化,而是提供了一套可配置的编码选择框架。规格文档推荐了一套经过基准测试验证的编码池,针对不同数据类型给出了物理层和逻辑层的推荐方案。布尔类型使用 RLE 编码,对于连续出现的相同值(如道路分类标识)压缩效果显著;整型数据支持 Plain、RLE、Delta、Delta-RLE 四种逻辑编码,同时在物理层提供 SIMD-FastPFOR 和 Varint 两种实现。

FastPFOR 是一种面向 SIMD 优化的整数压缩算法,在现代 x86 和 ARM 处理器上能够利用向量化指令批量解压数据。实验数据表明,FastPFOR 的解码速度显著快于传统的 Varint 编码,且输出尺寸更小。Varint 编码仍被保留,主要用于与旧系统的兼容性,以及与 GZip 等 heavyweight 压缩算法级联时的场景。对于浮点型数据,MLT 引入的 ALP 编码在保持数值精度的同时实现了高压缩率,特别适合存储高程、坡度等连续数值。

字符串编码的选择策略最为复杂。FSST(Fast Static Symbol Table)字典编码在通用文本压缩上表现优异,但当地理数据包含大量专有名词时,共享字典机制允许跨列复用同一字典。例如,OSM 数据中的 name:* 多语言名称列可以共享同一个城市名称字典,进一步提升压缩效率。MLT 规格建议采用采样选择策略:对约 1% 的数据进行候选编码的穷举测试,选取输出最小的方案作为最终配置。这一比例经过权衡,既能反映数据分布特征,又不会带来过重的计算负担。

几何数据的编码是 MLT 与 MVT 差异最大的部分。MVT 采用 Google Protocol Buffers 的 MessagePack 格式编码几何,MLT 则引入了 Structure of Arrays(SoA)布局。坐标数据被分离为独立的 Stream:GeometryType 标识要素类型,NumGeometries 和 NumParts 描述要素结构,VertexBuffer 存储实际的坐标序列。对于需要预三角化的多边形,IndexBuffer 和 NumTriangles Stream 直接存储 GPU 可用的三角形索引,省去了客户端的运行时三角化开销。字典编码和 Morton 编码(空间填充曲线编码)可用于 VertexBuffer,前者适合顶点高度重复的数据模式,后者则能提升空间查询的局部性。

工程落地参数与监控指标

将 MLT 投入生产环境需要关注几个关键配置参数。首先是列排序策略,FeatureTable 内要素的排列顺序直接影响压缩效果。文档建议使用与编码选择相同的采样方法穷举候选排序,选取压缩后体积最小的方案。虽然穷举排序的计算成本较高,但一旦确定排序规则,后续的增量更新可以仅对新增要素进行排序,避免全量重排。

内存对齐是另一个常被忽视的优化点。MLT 的内存格式借鉴了 Apache Arrow 的设计理念,规定向量数据应按 64 字节边界对齐。这一对齐粒度匹配了现代 CPU 的 SIMD 寄存器宽度,使得解码器能够直接利用向量化指令处理数据,避免边界检查和跨缓存行的内存访问开销。在 Java 和 Rust 等有显式内存管理能力的语言中,实现这一对齐要求相对直接;在 C++ 中需要使用 aligned_alloc 或手动填充。

Property 分为 feature-scoped 和 vertex-scoped 两种作用域。前者为每个要素分配单一属性值,后者为每个顶点分配独立值,可用于模拟 GIS 中的 M 坐标。值得注意的是,vertex-scoped 属性必须位于 feature-scoped 属性之前,且这一顺序约束是强制性的。部分客户端实现可能会忽略 vertex-scoped 属性,因此在使用这一特性前需要确认目标渲染引擎的支持情况。

RangeMap 是 MLT 为下一代数据源(如 Overture Maps)预留的支持特性。传统的矢量切片仅能表达离散的点、线、面要素,RangeMap 则支持线性参照系统,可以高效编码沿道路分布的限速区段、公交线路等连续数据。其内部将区间范围和关联数据分别存储在两个 Stream 中,配合 range stream 的双精度边界值实现快速区间查询。

从 MVT 迁移至 MLT 并非简单的文件格式转换,而是数据管道架构的重构。MLT 的列式布局要求数据处理引擎具备列式聚合和编码选择能力,传统的基于行缓冲的 ETL 流程需要改造。MapLibre 社区已经提供了 Rust、C++、Java、TypeScript 等多语言的参考实现,开发者可以根据现有技术栈选择集成方案。对于数据规模在 TB 级别的底图服务,MLT 的压缩效率提升可以直接转化为存储成本的大幅削减;对于注重交互延迟的客户端应用,零拷贝的内存格式和 SIMD 优化的解码路径则能带来更流畅的渲染体验。

资料来源:MapLibre Tile Specification(https://www.maplibre.org/maplibre-tile-spec/specification/)、arXiv 论文 "MapLibre Tile: A Next Generation Vector Tile Format"(https://arxiv.org/abs/2508.10791)。

查看归档