MapLibre Tile Specification v1¶
This document specifies the byte layout of an MLT v1 layer. The data model it assumes, tiles, extents, layers, features, columns and streams, is described in the Overview.
Only implemented features are specified here. Complex and nested property types, logical types, linear referencing, vertex-scoped m-values and separate tileset metadata were designed for v1 but never implemented. They are being redesigned for MLT v2.
Everything below is normative.
All integers are VarInt-encoded unless stated otherwise.
VarInt is Protobuf's base 128 varint, least significant group first.
Signed varints are ZigZag-encoded before being written.
Tile Layout¶
A tile is a concatenation of tagged layer records. There is no tile header. Layers are encoded independently and can be concatenated without re-encoding.
sizecounts thetagbyte plus the body, so the next layer startssizebytes after thesizevarint.tagnames the format the body is written in.0x01is the format specified on this page.- A decoder MUST skip a layer whose
tagit does not recognize, usingsizeto find the next one.
A decoder MUST NOT assume layer names are unique or ordered.
Layer Body¶
A v1 layer body is a header, then every column's metadata, then every column's data:
body := [string name]
[varint extent]
[varint column_count]
column_meta * column_count
column_data * column_count
Strings are a VarInt byte length followed by that many UTF-8 bytes:
---
config:
class:
hideEmptyMembersBox: true
title: StringsSchema
---
classDiagram
direction TB
class String {
+VarInt length
+u8 bytes[length] %% encoding is always UTF-8
}
name MUST NOT be empty.
extent defines the coordinate space size for the tile's geometry and MUST NOT be zero.
Geometry coordinates are signed integers in vector-tile grid coordinates, typically near the 0..=extent range but not restricted to it.
Values MAY be negative or exceed extent for geometry that crosses tile boundaries.
Encoders MAY default to 4096 when a user does not specify the extent.
Metadata and data are in two separate sections, both in column_count order.
A layer MUST contain exactly one geometry column and at most one id column.
Column Metadata¶
Each column's metadata is a single type byte, optionally followed by a name and, for shared dictionaries, its children:
column_meta := [u8 column_type]
[string name] -- unless the type is Id or Geometry
[varint child_count] [child_meta*] -- SharedDict only
child_meta := [u8 column_type] [string name]
Id and Geometry columns are named implicitly by their type and carry no name field.
Column Types¶
The low bit of the type byte is the nullability flag: an odd code is the nullable variant of the even code below it.
A nullable column writes a Present stream before its data.
| Code | Type | Nullable variant | Description |
|---|---|---|---|
0 |
Id |
1 (OptId) |
Feature id, up to 32 bits |
2 |
LongId |
3 (OptLongId) |
Feature id, up to 64 bits |
4 |
Geometry |
- | The layer's geometry column, never nullable |
10 |
Bool |
11 |
|
12 |
Int8 |
13 |
|
14 |
UInt8 |
15 |
|
16 |
Int32 |
17 |
|
18 |
UInt32 |
19 |
|
20 |
Int64 |
21 |
|
22 |
UInt64 |
23 |
|
24 |
Float |
25 |
IEEE 754 binary32 |
26 |
Double |
27 |
IEEE 754 binary64 |
28 |
String |
29 |
UTF-8 |
30 |
SharedDict |
- | A string dictionary and the columns that index into it |
Codes 5-9 and 31 are unassigned.
A decoder MUST reject a column whose type byte is unassigned.
Column Data¶
What follows in the data section depends on the column's type.
Every [stream] below is one stream, laid out as described under Streams.
| Column type | Data layout |
|---|---|
Id, LongId |
[data stream] |
Geometry |
[varint stream_count] [types stream] [stream * (stream_count - 1)] |
Scalar (Bool … Double) |
[data stream] |
String |
[varint stream_count] [stream * stream_count] |
SharedDict |
see Shared Dictionary Columns |
A nullable column prefixes its data with a Present stream, which is counted in stream_count where one is present.
Streams¶
A logical column is separated into several physical streams (sub-columns), inspired by the ORC file format.
These streams are stored contiguously.
A stream is a sequence of values of a known length in a continuous memory chunk, all sharing the same type.
Each stream is a header followed by its payload:
stream := [u8 stream_type] category (bits 7-4) | subtype (bits 3-0)
[u8 encoding] logical1 (bits 7-5) | logical2 (bits 4-2) | physical (bits 1-0)
[varint num_values]
[varint byte_length]
[varint runs] RLE streams only, except Present streams
[varint num_rle_values] RLE streams only, except Present streams
[varint bits] Morton streams only
[varint shift] Morton streams only
[u8 payload[byte_length]]
num_values is the number of values the stream holds after decoding.
byte_length is the payload size and allows a decoder to skip the stream.
A Present stream is always boolean and derives its RLE parameters from num_values.
Stream Types¶
The stream_type byte names what role the stream plays.
The high nibble is the category and the low nibble is a category-specific subtype.
| Category | Code | Subtypes |
|---|---|---|
Present |
0x0 |
- |
Data |
0x1 |
0 None, 1 Single dictionary, 2 Shared dictionary, 3 Vertex, 4 Morton, 5 FSST |
Offset |
0x2 |
0 Vertex, 1 Index, 2 String, 3 Key |
Length |
0x3 |
0 VarBinary, 1 Geometries, 2 Parts, 3 Rings, 4 Triangles, 5 Symbol, 6 Dictionary |
What each category holds:
- Present: enables efficient encoding of sparse columns by indicating value presence via a bit flag. Omitted when the column is not nullable.
- Data: the actual column data -
boolean,int,floatorstringvalues, dictionary entries, or geometry coordinates. For fixed-size data types this is the only required stream besides the optionalPresentstream. - Length: the number of elements for variable-sized data types like strings or rings.
- Offset: offsets into a data stream when using dictionary encoding, for strings or vertices.
Encoding Byte¶
The encoding byte gives the encoding of the payload. The two logical fields form one combination. Any combination not listed below is invalid.
| Combination | logical1 (bits 7-5) | logical2 (bits 4-2) | Meaning |
|---|---|---|---|
None |
000 |
000 |
Values as they are |
Delta |
001 |
000 |
ZigZag deltas between consecutive values |
DeltaRle |
001 |
011 |
Delta, then RLE |
ComponentwiseDelta |
010 |
000 |
Deltas per coordinate component, for vertex streams |
Rle |
011 |
000 |
Run-length encoded |
Morton |
100 |
000 |
Morton (Z-order) codes, for vertex streams |
MortonDelta |
100 |
001 |
Deltas between Morton codes |
MortonRle |
100 |
011 |
RLE over Morton codes |
The physical field (bits 1-0) says how the resulting integers are laid out in bytes:
| Code | Physical |
|---|---|
00 |
None - fixed-width little-endian words |
01 |
SIMD-FastPFOR, 256-value big-endian blocks |
10 |
VarInt |
RLE streams store all run lengths first, then all values, and carry runs and num_rle_values in the header.
The algorithms themselves are specified in Encoding Definitions.
Property Columns¶
ID Column¶
An id column is not mandatory.
If included, it should be UInt64 or narrower (UInt32 if possible) for MVT compatibility.
A narrower type enables the use of efficient encodings like SIMD-FastPFOR.
Scalar Columns¶
Boolean, integer and floating-point columns are a Present stream when nullable, then one data stream.
Boolean data streams are bit-packed, one bit per present value; float and double data streams hold fixed-width IEEE 754 words.
String Columns¶
A string column declares how many streams follow, and the number of streams determines its layout:
| Streams | Layout | Stream order |
|---|---|---|
| 2 | Plain | Length/VarBinary, Data/None |
| 3 | Dictionary | Length/Dictionary, Offset/String, Data/Single |
| 4 | FSST | Length/Symbol, Data/FSST, Length/Dictionary, Data/Single |
| 5 | FSST dictionary | Length/Symbol, Data/FSST, Length/Dictionary, Data/Single, Offset/String |
A nullable column's Present stream precedes these and is counted in stream_count.
In the plain layout the Length stream holds the byte length of each present value and the Data stream holds their UTF-8 bytes back to back.
In a dictionary layout the Offset/String stream holds one dictionary index per present value, and the Length and Data streams describe the distinct values.
FSST layouts compress the value bytes with a symbol table; see FSST.
The offset stream comes last in the 5-stream layout and before the data stream in the 3-stream layout.
An encoder MAY also use the 5-stream layout for an undeduplicated FSST corpus, writing the identity [0, 1, 2, …] as its offsets.
Shared Dictionary Columns¶
Several string columns, such as name:en, name:de and name:fr, can share a single dictionary.
shared_dict := [varint stream_count]
[dictionary streams] 2 (plain) or 4 (FSST), as in the table above
child * child_count child_count comes from the column metadata
child := [varint stream_count]
[present stream] only when the child's type is nullable
[offset stream] one dictionary index per present value
stream_count on the column counts every stream that follows: the dictionary streams, one offset stream per child, and one present stream per nullable child.
The dictionary streams end at the Data/Single or Data/Shared stream.
The children begin after it.
Note
Some tiles in the wild were written with stream_count one too high, by an encoder bug since fixed.
Decoders SHOULD accept expected + 1 as well so those files still parse.
Each child's own stream_count is 1, plus 1 when it is nullable.
Geometry Column¶
The geometry column uses a Structure of Arrays (SoA) layout (data-oriented design).
The x, y coordinates are stored interleaved in a VertexBuffer for efficient CPU processing and direct copying to GPU buffers.
The geometry information is separated into different streams, partly inspired by the geoarrow specification. This separation enables better compression optimization and faster processing. Pre-tessellated polygon meshes can also be stored directly to avoid runtime triangulation.
A geometry column can consist of the following streams:
| Stream Name | Data Type | Encoding | Mandatory |
|---|---|---|---|
| GeometryType | Byte | Integer | ✓ |
| NumGeometries | UInt32 | Integer | |
| NumParts | UInt32 | Integer | |
| NumRings | UInt32 | Integer | |
| NumTriangles | UInt32 | Integer | |
| IndexBuffer | UInt32 | Integer | |
| VertexOffsets | UInt32 | Integer | |
| VertexBuffer | Int32 or Vertex[] | Plain, Dictionary, Morton | ✓ |
Depending on the geometry type, the following streams are used in addition to GeometryType:
- Point: VertexBuffer
- LineString: NumParts, VertexBuffer
- Polygon: NumParts (Polygon), NumRings (LinearRing), VertexBuffer
- MultiPoint: NumGeometries, VertexBuffer
- MultiLineString: NumGeometries, NumParts (LineString), VertexBuffer
- MultiPolygon: NumGeometries, NumParts (Polygon), NumRings (LinearRing), VertexBuffer
When LineString and Polygon types are mixed in the same column, LineString vertex counts are stored in the NumRings stream (see Length Stream Encoding Rules below).
An additional VertexOffsets stream is present when using Dictionary or Morton-Dictionary encoding.
If geometries (mainly polygons) are pre-tessellated for direct GPU use, NumTriangles and IndexBuffer streams must be provided.
Geometry Types¶
Six geometry types are supported, encoded as unsigned integers:
| Value | Type | Description |
|---|---|---|
| 0 | Point | Single coordinate |
| 1 | LineString | Sequence of coordinates forming a line |
| 2 | Polygon | Closed rings (exterior + optional interior holes) |
| 3 | MultiPoint | Collection of points |
| 4 | MultiLineString | Collection of line strings |
| 5 | MultiPolygon | Collection of polygons |
Binary Structure¶
A geometry column is stored as a sequence of streams, prefixed by a stream count:
graph LR
subgraph "Stream Count"
SC[varint]
end
subgraph "Meta Stream"
MS[geometry types]
end
subgraph "Topology Streams"
TS[lengths/offsets]
end
subgraph "Vertex Stream"
VS[coordinates]
end
SC --> MS --> TS --> VS
The streams listed above map to the following physical and logical stream types:
| Physical Type | Logical Type | Specification Name | Content |
|---|---|---|---|
DATA |
NONE |
GeometryType | Geometry type per feature (see Geometry Types) |
LENGTH |
GEOMETRIES |
NumGeometries | Number of sub-geometries in Multi* types |
LENGTH |
PARTS |
NumParts | Number of rings per polygon or lines per multi-linestring |
LENGTH |
RINGS |
NumRings | Number of vertices per ring or per linestring segment |
LENGTH |
TRIANGLES |
NumTriangles | Number of triangles per polygon (tessellated) |
OFFSET |
INDEX |
IndexBuffer | Triangle vertex indices (tessellated) |
OFFSET |
VERTEX |
VertexOffsets | Indices into vertex dictionary |
DATA |
VERTEX |
VertexBuffer | Vertex coordinates (x, y pairs) |
DATA |
MORTON |
VertexBuffer | Morton-encoded vertex coordinates |
Topology Encoding¶
MLT uses a length-based encoding for geometry topology rather than explicit drawing commands. This enables efficient random access to individual features.
Conceptual Hierarchy¶
graph TD
subgraph "Feature Table"
GT["Geometry Types Stream<br/>[Point, Polygon, MultiPolygon, ...]"]
end
subgraph "Level 1: Geometries"
GL["Geometries Length Stream<br/>sub-geometry counts<br/>for Multi* types"]
end
subgraph "Level 2: Parts"
PL["Parts Length Stream<br/>ring counts per polygon"]
end
subgraph "Level 3: Rings"
RL["Rings Length Stream<br/>vertex counts per ring"]
end
subgraph "Vertex Data"
VB["Vertex Buffer<br/>[x₀,y₀, x₁,y₁, ...]"]
end
GT --> GL --> PL --> RL --> VB
Which Streams Are Present¶
The streams included depend on the geometry types in the column:
| Geometry Type | Geometries | Parts | Rings |
|---|---|---|---|
| Point | - | - | - |
| MultiPoint | ✓ | - | - |
| LineString | - | ✓* | - |
| MultiLineString | ✓ | ✓* | - |
| Polygon | - | ✓ | ✓ |
| MultiPolygon | ✓ | ✓ | ✓ |
*LineString and MultiLineString parts are stored in the Rings stream when Polygons are also present in the same column.
Length Stream Encoding Rules¶
Length values are stored only for geometry types that need them. The key insight is that simple types have an implicit count of 1, while Multi* types store their sub-geometry counts explicitly.
The encoding uses a type threshold to determine which geometries need explicit lengths:
| Stream | Threshold | Types Needing Explicit Length |
|---|---|---|
geometry_offsets |
Polygon | MultiPoint, MultiLineString, MultiPolygon |
part_offsets (Rings stream present) |
LineString | Polygon, MultiPoint, MultiLineString, MultiPolygon |
part_offsets (no Rings stream) |
Point | LineString |
Rule: If a geometry type's value is greater than the threshold type's value (see Geometry Types table), store its length explicitly. Otherwise, the length is implicitly 1.
Example: A column with [Point, MultiPolygon, Polygon] geometry types:
Encoded geometries lengths: [3] // Only MultiPolygon needs explicit count
// (MultiPolygon=5 > Polygon=2)
Decoding to offsets:
Point: implicit 1 -> offset 0->1 (Point=0, not greater than Polygon=2)
MultiPolygon: explicit 3 -> offset 1->4 (MultiPolygon=5 > Polygon=2, read from stream)
Polygon: implicit 1 -> offset 4->5 (Polygon=2, not greater than Polygon=2)
Result: geometry_offsets = [0, 1, 4, 5]
Vertex Encoding¶
Componentwise Delta Encoding¶
Vertices are stored as interleaved (x, y) coordinate pairs using componentwise delta encoding:
graph LR
subgraph "Original Coordinates"
C["(100,200), (105,210), (102,215)"]
end
subgraph "Delta per Component"
D["ΔX: 100, 5, -3<br/>ΔY: 200, 10, 5"]
end
subgraph "Zigzag Encoded"
Z["X: 200, 10, 5<br/>Y: 400, 20, 10"]
end
subgraph "Interleaved Output"
O["200, 400, 10, 20, 5, 10"]
end
C --> D --> Z --> O
Encoding steps:
- Track previous X and previous Y separately (both start at 0)
- For each vertex, compute
delta_x = x - prev_xanddelta_y = y - prev_y - Apply zigzag encoding:
zigzag(n) = (n << 1) ^ (n >> 31)(maps negatives to positives) - Output as varint:
[zigzag(Δx₀), zigzag(Δy₀), zigzag(Δx₁), zigzag(Δy₁), ...]
Decoding steps:
- Read varints in pairs
- Apply zigzag decoding:
n = (zigzag >> 1) ^ -(zigzag & 1) - Accumulate:
x = prev_x + delta_x,y = prev_y + delta_y
Dictionary Encoding (Optional)¶
When vertices repeat frequently, a dictionary encoding may be used:
graph TD
subgraph "With Dictionary"
VO["Vertex Offsets<br/>[0, 1, 2, 1, 0, 2]"]
VD["Vertex Dictionary<br/>[(0,0), (10,10), (20,20)]"]
end
subgraph "Resolved Vertices"
RV["(0,0), (10,10), (20,20), (10,10), (0,0), (20,20)"]
end
VO --> RV
VD --> RV
The vertex dictionary is sorted by Hilbert curve index for spatial locality. The OFFSET/VERTEX stream contains indices into this dictionary.
Morton Encoding (Optional)¶
For spatial optimization, vertices can be Morton-encoded (Z-order curve):
Coordinate (5, 3):
X bits: 1 0 1
Y bits: 0 1 1
Interleaved: 01 10 11 = 011011₂ = 27
Stored as: Morton code + metadata (numBits, coordinateShift)
Morton encoding enables efficient spatial clustering and range queries.
Integer Stream Encoding¶
Topology streams (lengths, offsets) use adaptive encoding selected for minimal size. All integer encodings use variable-length quantity (varint) as the base encoding:
| Logical Encoding | Format | Best For |
|---|---|---|
| None | Plain varints | Random values |
| Delta | Delta + zigzag + varint | Monotonic sequences |
| RLE | Run-length encoded | Repeated values |
| DeltaRLE | Delta + RLE | Constant increments |
RLE Format¶
Run-length encoding stores runs followed by values:
Input: [5, 5, 5, 3, 3, 3, 3]
Runs: [3, 4] // 3 fives, 4 threes
Values: [5, 3]
Output: [3, 4, 5, 3] // runs concatenated with values
Stream metadata includes runs and num_rle_values counts for decoding.
Tessellation Data (Optional)¶
Pre-tessellated polygons include additional streams for direct GPU rendering:
graph TD
subgraph "Tessellated Polygon Data"
TRI["Triangles Stream<br/>(LENGTH/TRIANGLES)"]
IDX["Index Buffer<br/>(OFFSET/INDEX)"]
VTX["Vertices<br/>polygon vertices"]
end
TRI --> |"how many"| GPU[GPU Rendering]
IDX --> |"which vertices"| GPU
VTX --> |"coordinates"| GPU
Example - A square polygon tessellated into 2 triangles:
Vertices: [(0,0), (100,0), (100,100), (0,100)]
Triangles: [2] // This polygon has 2 triangles
Index buffer: [0, 1, 2, 0, 2, 3] // Triangle 1: v0,v1,v2; Triangle 2: v0,v2,v3
Decoding Examples¶
Point¶
Streams:
META: types = [0] // Point
DATA/VERTEX: [200, 400] // zigzag-encoded deltas
Decode:
zigzag(200) = 100, zigzag(400) = 200
Result: Point(100, 200)
LineString¶
Streams:
META: types = [1] // LineString
LENGTH/PARTS: [3] // 3 vertices
DATA/VERTEX: [0,0, 200,0, 0,200] // encoded deltas
Decode:
part_offsets = [0, 3] // from lengths
vertices = [(0,0), (100,0), (100,100)] // delta-decoded
Result: LineString with 3 vertices
Polygon with Hole¶
Streams:
META: types = [2] // Polygon
LENGTH/PARTS: [2] // 2 rings (exterior + 1 hole)
LENGTH/RINGS: [4, 4] // 4 vertices each ring
DATA/VERTEX: [...] // 8 vertices total
Decode:
part_offsets = [0, 2] // 1 polygon with 2 rings
ring_offsets = [0, 4, 8] // ring boundaries
Result: Polygon with exterior ring (4 verts) and hole (4 verts)
MultiPolygon¶
Streams:
META: types = [5] // MultiPolygon
LENGTH/GEOMETRIES: [2] // 2 polygons
LENGTH/PARTS: [1, 1] // 1 ring each
LENGTH/RINGS: [4, 4] // 4 vertices each
DATA/VERTEX: [...] // 8 vertices total
Decode:
geometry_offsets = [0, 2] // spans indices 0-1 in parts
part_offsets = [0, 1, 2] // each polygon has 1 ring
ring_offsets = [0, 4, 8] // vertex boundaries
Result: MultiPolygon with 2 simple polygons
Mixed Geometry Columns¶
A single column can contain mixed geometry types. The decoding logic handles this by:
- Reading geometry types to determine which length streams to expect
- Iterating through features, applying type-specific offset rules
- Using implicit length=1 for simple types (Point, LineString, Polygon) and reading explicit lengths for Multi* types
Example: [Point, LineString, Polygon] in one column:
Streams:
META: types = [0, 1, 2]
LENGTH/PARTS: [1] // Polygon: 1 ring
LENGTH/RINGS: [3, 4] // LineString: 3 verts, Polygon ring: 4 verts
DATA/VERTEX: [...] // 1 + 3 + 4 = 8 vertices
Decode:
Point: vertices[0]
LineString: vertices[1..4] // from ring_offsets (Polygon present)
Polygon: vertices[4..8] // from ring_offsets
The integer and vertex encodings referenced above are specified in detail in the Encoding Definitions document.
Choosing Encodings¶
MLT uses various lightweight compression schemes for space-efficient storage and fast decoding. Encodings can be recursively cascaded (hybrid encodings) to a certain degree. For example, integer columns resulting from dictionary encoding can be further compressed using integer encoding schemes.
The following encoding pool was selected based on analysis of compression ratio and decoding speed on test datasets like OpenMapTiles and Bing Maps tilesets.
| Data Type | Logical Level Technique | Physical Level Technique |
|---|---|---|
| Boolean | Boolean RLE | |
| Integer | Plain, RLE, Delta, Delta-RLE | SIMD-FastPFOR, Varint |
| Float | Plain, RLE | |
| String | Plain, Dictionary, FSST, FSST Dictionary | |
| Geometry | Plain, Dictionary, Morton-Dictionary |
SIMD-FastPFOR is generally preferred over Varint encoding due to its smaller output and faster decoding speed. Varint encoding is included mainly for compatibility and simplicity, and it can be more efficient when combined with heavyweight compression like GZip.
A brute-force search for the best encoding scheme is too costly. Instead, we recommend the selection strategy from the BTRBlocks paper:
- Calculate data metrics to exclude unsuitable encodings early (e.g., exclude RLE if the average run length is less than 2).
- Use a sampling-based algorithm: randomly select parts of the data totaling ~1% of the full dataset and apply the candidate encodings from step 1. Choose the scheme that produces the smallest output.
Choosing the right column to sort features by can also significantly reduce the size of a layer, and is crucial for leveraging the columnar layout fully. Exhaustively testing every possible sorting order for every column in every layer is computationally expensive, so the same sampling heuristic applies.
Examples¶
Every fixture under test/synthetic/0x01/ has an annotated hexdump and the .json a decoder MUST produce from it.
The annotations are generated from the tiles.
The same annotation can be produced for any tile with the mlt CLI:
One point with one id: the tile envelope, the column metadata section and the data section.
00000000 | layer[0] (30 B)
00000000 1d . | size: 29 (varint) - tag + body
00000001 01 . | tag: 0x01 -> Tag01
00000002 06 6c 61 79 65 72 31 .layer1 | name: "layer1"
00000009 50 P | extent: 80
0000000a 02 . | column_count: 2
0000000b | schema (2 B)
0000000b | column[0] (1 B)
0000000b 00 . | type: 0x00 Id
| └ bits 7-1 = 0000000 -> base type = Id
| └ bit 0 = 0 -> not optional: each feature has a non-NULL value
0000000c | column[1] (1 B)
0000000c 04 . | type: 0x04 Geometry
| └ bits 7-1 = 0000010 -> base type = Geometry
| └ bit 0 = 0 -> not optional: each feature has a non-NULL value
0000000d | column data (17 B)
0000000d | column[0] Id (5 B)
0000000d | id (5 B)
0000000d | header (4 B)
0000000d 10 . | stream_type: 0x10 Data(None)
| └ bits 7-4 = 0001 -> category = Data
| └ bits 3-0 = 0000 -> subtype = None
0000000e 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
0000000f 01 . | num_values: 1
00000010 01 . | byte_length: 1
00000011 64 d | data [Data(None) Int(None)/VarInt, 1 values, 1 B]
| decoded: [100]
00000012 | column[1] Geometry (12 B)
00000012 02 . | stream_count: 2
00000013 | meta (5 B)
00000013 | header (4 B)
00000013 30 0 | stream_type: 0x30 Length(VarBinary)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0000 -> subtype = VarBinary
00000014 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000015 01 . | num_values: 1
00000016 01 . | byte_length: 1
00000017 00 . | data [Length(VarBinary) Int(None)/VarInt, 1 values, 1 B]
| decoded: [0]
00000018 | stream[0] (6 B)
00000018 | header (4 B)
00000018 13 . | stream_type: 0x13 Data(Vertex)
| └ bits 7-4 = 0001 -> category = Data
| └ bits 3-0 = 0011 -> subtype = Vertex
00000019 42 B | encoding: 0x42 logical=Vertex(ComponentwiseDelta) physical=VarInt
| └ bits 7-5 = 010 -> logical1 = ComponentwiseDelta
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
0000001a 02 . | num_values: 2
0000001b 02 . | byte_length: 2
0000001c 1a 54 .T | data [Data(Vertex) Vertex(ComponentwiseDelta)/VarInt, 2 values, 2 B]
| decoded: [13, 42]
Part and ring length streams.
00000000 | layer[0] (47 B)
00000000 2e . | size: 46 (varint) - tag + body
00000001 01 . | tag: 0x01 -> Tag01
00000002 06 6c 61 79 65 72 31 .layer1 | name: "layer1"
00000009 50 P | extent: 80
0000000a 01 . | column_count: 1
0000000b | schema (1 B)
0000000b | column[0] (1 B)
0000000b 04 . | type: 0x04 Geometry
| └ bits 7-1 = 0000010 -> base type = Geometry
| └ bit 0 = 0 -> not optional: each feature has a non-NULL value
0000000c | column data (35 B)
0000000c | column[0] Geometry (35 B)
0000000c 04 . | stream_count: 4
0000000d | meta (5 B)
0000000d | header (4 B)
0000000d 30 0 | stream_type: 0x30 Length(VarBinary)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0000 -> subtype = VarBinary
0000000e 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
0000000f 01 . | num_values: 1
00000010 01 . | byte_length: 1
00000011 02 . | data [Length(VarBinary) Int(None)/VarInt, 1 values, 1 B]
| decoded: [2]
00000012 | stream[0] (5 B)
00000012 | header (4 B)
00000012 32 2 | stream_type: 0x32 Length(Parts)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0010 -> subtype = Parts
00000013 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000014 01 . | num_values: 1
00000015 01 . | byte_length: 1
00000016 02 . | data [Length(Parts) Int(None)/VarInt, 1 values, 1 B]
| decoded: [2]
00000017 | stream[1] (8 B)
00000017 | header (6 B)
00000017 33 3 | stream_type: 0x33 Length(Rings)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0011 -> subtype = Rings
00000018 62 b | encoding: 0x62 logical=Int(Rle(Split { runs: 1, num_rle_values: 2 })) physical=VarInt
| └ bits 7-5 = 011 -> logical1 = Rle
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000019 02 . | num_values: 2
0000001a 02 . | byte_length: 2
0000001b 01 . | runs: 1
0000001c 02 . | num_rle_values: 2
0000001d 02 03 .. | data [Length(Rings) Int(Rle(Split { runs: 1, num_rle_values: 2 }))/VarInt, 2 values, 2 B]
| decoded: [3, 3]
0000001f | stream[2] (16 B)
0000001f | header (4 B)
0000001f 13 . | stream_type: 0x13 Data(Vertex)
| └ bits 7-4 = 0001 -> category = Data
| └ bits 3-0 = 0011 -> subtype = Vertex
00000020 42 B | encoding: 0x42 logical=Vertex(ComponentwiseDelta) physical=VarInt
| └ bits 7-5 = 010 -> logical1 = ComponentwiseDelta
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000021 0c . | num_values: 12
00000022 0c . | byte_length: 12
00000023 16 68 78 28 13 63 08 58 3b 13 28 27 .hx(.c.X;.(' | data [Data(Vertex) Vertex(ComponentwiseDelta)/VarInt, 12 values, 12 B]
| decoded: [11, 52, 71, 72, 61, 22, 65, 66, 35, 56, 55, 36]
A plain string column: its stream_count, then its length and data streams.
00000000 | layer[0] (234 B)
00000000 e8 01 .. | size: 232 (varint) - tag + body
00000002 01 . | tag: 0x01 -> Tag01
00000003 06 6c 61 79 65 72 31 .layer1 | name: "layer1"
0000000a 50 P | extent: 80
0000000b 02 . | column_count: 2
0000000c | schema (6 B)
0000000c | column[0] (1 B)
0000000c 04 . | type: 0x04 Geometry
| └ bits 7-1 = 0000010 -> base type = Geometry
| └ bit 0 = 0 -> not optional: each feature has a non-NULL value
0000000d | column[1] (5 B)
0000000d 1d . | type: 0x1D OptStr
| └ bits 7-1 = 0001110 -> base type = OptStr
| └ bit 0 = 1 -> optional: a Present stream precedes the data
0000000e 03 76 61 6c .val | name: "val"
00000012 | column data (216 B)
00000012 | column[0] Geometry (25 B)
00000012 02 . | stream_count: 2
00000013 | meta (8 B)
00000013 | header (6 B)
00000013 30 0 | stream_type: 0x30 Length(VarBinary)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0000 -> subtype = VarBinary
00000014 62 b | encoding: 0x62 logical=Int(Rle(Split { runs: 1, num_rle_values: 6 })) physical=VarInt
| └ bits 7-5 = 011 -> logical1 = Rle
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000015 02 . | num_values: 2
00000016 02 . | byte_length: 2
00000017 01 . | runs: 1
00000018 06 . | num_rle_values: 6
00000019 06 00 .. | data [Length(VarBinary) Int(Rle(Split { runs: 1, num_rle_values: 6 }))/VarInt, 2 values, 2 B]
| decoded: [0, 0, 0, 0, 0, 0]
0000001b | stream[0] (16 B)
0000001b | header (4 B)
0000001b 13 . | stream_type: 0x13 Data(Vertex)
| └ bits 7-4 = 0001 -> category = Data
| └ bits 3-0 = 0011 -> subtype = Vertex
0000001c 42 B | encoding: 0x42 logical=Vertex(ComponentwiseDelta) physical=VarInt
| └ bits 7-5 = 010 -> logical1 = ComponentwiseDelta
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
0000001d 0c . | num_values: 12
0000001e 0c . | byte_length: 12
0000001f 16 68 78 28 13 63 08 58 3b 13 28 27 .hx(.c.X;.(' | data [Data(Vertex) Vertex(ComponentwiseDelta)/VarInt, 12 values, 12 B]
| decoded: [11, 52, 71, 72, 61, 22, 65, 66, 35, 56, 55, 36]
0000002b | column[1] OptStr "val" (191 B)
0000002b 03 . | stream_count: 3
0000002c | present (6 B)
0000002c | header (4 B)
0000002c 00 . | stream_type: 0x00 Present
| └ bits 7-4 = 0000 -> category = Present
| └ bits 3-0 = 0000 -> subtype = -
0000002d 60 ` | encoding: 0x60 logical=Bool(ByteRle(Split { runs: 1, num_rle_values: 2 })) physical=None
| └ bits 7-5 = 011 -> logical1 = Rle
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 00 -> physical = None
0000002e 06 . | num_values: 6
0000002f 02 . | byte_length: 2
00000030 ff 3f .? | data [Present Bool(ByteRle(Split { runs: 1, num_rle_values: 2 }))/None, 6 values, 2 B]
| decoded: 6 present-bits: 111111
00000032 | stream[0] (10 B)
00000032 | header (4 B)
00000032 30 0 | stream_type: 0x30 Length(VarBinary)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0000 -> subtype = VarBinary
00000033 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000034 06 . | num_values: 6
00000035 06 . | byte_length: 6
00000036 1f 1e 1d 17 19 1f ...... | data [Length(VarBinary) Int(None)/VarInt, 6 values, 6 B]
| decoded: [31, 30, 29, 23, 25, 31]
0000003c | stream[1] (174 B)
0000003c | header (5 B)
0000003c 10 . | stream_type: 0x10 Data(None)
| └ bits 7-4 = 0001 -> category = Data
| └ bits 3-0 = 0000 -> subtype = None
0000003d 00 . | encoding: 0x00 logical=Int(None) physical=None
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 00 -> physical = None
0000003e 06 . | num_values: 6
0000003f a9 01 .. | byte_length: 169
00000041 72 65 73 69 64 65 6e 74 69 61 6c 5f 7a 6f 6e 65 residential_zone | data [Data(None) Int(None)/None, 6 values, 169 B]
00000051 5f 6e 6f 72 74 68 5f 73 65 63 74 6f 72 5f 31 63 _north_sector_1c |
00000061 6f 6d 6d 65 72 63 69 61 6c 5f 7a 6f 6e 65 5f 73 ommercial_zone_s |
00000071 6f 75 74 68 5f 73 65 63 74 6f 72 5f 32 69 6e 64 outh_sector_2ind |
00000081 75 73 74 72 69 61 6c 5f 7a 6f 6e 65 5f 65 61 73 ustrial_zone_eas |
00000091 74 5f 73 65 63 74 6f 72 5f 33 70 61 72 6b 5f 7a t_sector_3park_z |
000000a1 6f 6e 65 5f 77 65 73 74 5f 73 65 63 74 6f 72 5f one_west_sector_ |
000000b1 34 77 61 74 65 72 5f 7a 6f 6e 65 5f 6e 6f 72 74 4water_zone_nort |
000000c1 68 5f 73 65 63 74 6f 72 5f 35 72 65 73 69 64 65 h_sector_5reside |
000000d1 6e 74 69 61 6c 5f 7a 6f 6e 65 5f 73 6f 75 74 68 ntial_zone_south |
000000e1 5f 73 65 63 74 6f 72 5f 36 _sector_6 |
| decoded: utf-8 "residential_zone_north_sector_1commercial_zone_south_sector_2industrial_zone_east_sector_3park_zone_west_sector_4water_zone_north_sector_5residential_zone_south_sector_6"
A dictionary and the child columns that index into it, each with its own presence and offset stream.
00000000 | layer[0] (104 B)
00000000 67 g | size: 103 (varint) - tag + body
00000001 01 . | tag: 0x01 -> Tag01
00000002 06 6c 61 79 65 72 31 .layer1 | name: "layer1"
00000009 50 P | extent: 80
0000000a 02 . | column_count: 2
0000000b | schema (17 B)
0000000b | column[0] (1 B)
0000000b 04 . | type: 0x04 Geometry
| └ bits 7-1 = 0000010 -> base type = Geometry
| └ bit 0 = 0 -> not optional: each feature has a non-NULL value
0000000c | column[1] (16 B)
0000000c 1e . | type: 0x1E SharedDict
| └ bits 7-1 = 0001111 -> base type = SharedDict
| └ bit 0 = 0 -> not optional: each feature has a non-NULL value
0000000d 05 6e 61 6d 65 3a .name: | name: "name:"
00000013 02 . | child_count: 2
00000014 | column[0] (4 B)
00000014 1d . | type: 0x1D OptStr
| └ bits 7-1 = 0001110 -> base type = OptStr
| └ bit 0 = 1 -> optional: a Present stream precedes the data
00000015 02 64 65 .de | name: "de"
00000018 | column[1] (4 B)
00000018 1d . | type: 0x1D OptStr
| └ bits 7-1 = 0001110 -> base type = OptStr
| └ bit 0 = 1 -> optional: a Present stream precedes the data
00000019 02 65 6e .en | name: "en"
0000001c | column data (76 B)
0000001c | column[0] Geometry (12 B)
0000001c 02 . | stream_count: 2
0000001d | meta (5 B)
0000001d | header (4 B)
0000001d 30 0 | stream_type: 0x30 Length(VarBinary)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0000 -> subtype = VarBinary
0000001e 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
0000001f 01 . | num_values: 1
00000020 01 . | byte_length: 1
00000021 00 . | data [Length(VarBinary) Int(None)/VarInt, 1 values, 1 B]
| decoded: [0]
00000022 | stream[0] (6 B)
00000022 | header (4 B)
00000022 13 . | stream_type: 0x13 Data(Vertex)
| └ bits 7-4 = 0001 -> category = Data
| └ bits 3-0 = 0011 -> subtype = Vertex
00000023 42 B | encoding: 0x42 logical=Vertex(ComponentwiseDelta) physical=VarInt
| └ bits 7-5 = 010 -> logical1 = ComponentwiseDelta
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000024 02 . | num_values: 2
00000025 02 . | byte_length: 2
00000026 1a 54 .T | data [Data(Vertex) Vertex(ComponentwiseDelta)/VarInt, 2 values, 2 B]
| decoded: [13, 42]
00000028 | column[1] SharedDict "name:" (64 B)
00000028 06 . | stream_count: 6
00000029 | dict_stream[0] (5 B)
00000029 | header (4 B)
00000029 36 6 | stream_type: 0x36 Length(Dictionary)
| └ bits 7-4 = 0011 -> category = Length
| └ bits 3-0 = 0110 -> subtype = Dictionary
0000002a 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
0000002b 01 . | num_values: 1
0000002c 01 . | byte_length: 1
0000002d 1e . | data [Length(Dictionary) Int(None)/VarInt, 1 values, 1 B]
| decoded: [30]
0000002e | dict_stream[1] (34 B)
0000002e | header (4 B)
0000002e 12 . | stream_type: 0x12 Data(Shared)
| └ bits 7-4 = 0001 -> category = Data
| └ bits 3-0 = 0010 -> subtype = Shared
0000002f 00 . | encoding: 0x00 logical=Int(None) physical=None
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 00 -> physical = None
00000030 01 . | num_values: 1
00000031 1e . | byte_length: 30
00000032 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA | data [Data(Shared) Int(None)/None, 1 values, 30 B]
00000042 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAA |
| decoded: utf-8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
00000050 | child[0] OptStr (12 B)
00000050 02 . | stream_count: 2
00000051 | present (6 B)
00000051 | header (4 B)
00000051 00 . | stream_type: 0x00 Present
| └ bits 7-4 = 0000 -> category = Present
| └ bits 3-0 = 0000 -> subtype = -
00000052 60 ` | encoding: 0x60 logical=Bool(ByteRle(Split { runs: 1, num_rle_values: 2 })) physical=None
| └ bits 7-5 = 011 -> logical1 = Rle
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 00 -> physical = None
00000053 01 . | num_values: 1
00000054 02 . | byte_length: 2
00000055 ff 01 .. | data [Present Bool(ByteRle(Split { runs: 1, num_rle_values: 2 }))/None, 1 values, 2 B]
| decoded: 1 present-bits: 1
00000057 | data (5 B)
00000057 | header (4 B)
00000057 22 " | stream_type: 0x22 Offset(String)
| └ bits 7-4 = 0010 -> category = Offset
| └ bits 3-0 = 0010 -> subtype = String
00000058 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000059 01 . | num_values: 1
0000005a 01 . | byte_length: 1
0000005b 00 . | data [Offset(String) Int(None)/VarInt, 1 values, 1 B]
| decoded: [0]
0000005c | child[1] OptStr (12 B)
0000005c 02 . | stream_count: 2
0000005d | present (6 B)
0000005d | header (4 B)
0000005d 00 . | stream_type: 0x00 Present
| └ bits 7-4 = 0000 -> category = Present
| └ bits 3-0 = 0000 -> subtype = -
0000005e 60 ` | encoding: 0x60 logical=Bool(ByteRle(Split { runs: 1, num_rle_values: 2 })) physical=None
| └ bits 7-5 = 011 -> logical1 = Rle
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 00 -> physical = None
0000005f 01 . | num_values: 1
00000060 02 . | byte_length: 2
00000061 ff 01 .. | data [Present Bool(ByteRle(Split { runs: 1, num_rle_values: 2 }))/None, 1 values, 2 B]
| decoded: 1 present-bits: 1
00000063 | data (5 B)
00000063 | header (4 B)
00000063 22 " | stream_type: 0x22 Offset(String)
| └ bits 7-4 = 0010 -> category = Offset
| └ bits 3-0 = 0010 -> subtype = String
00000064 02 . | encoding: 0x02 logical=Int(None) physical=VarInt
| └ bits 7-5 = 000 -> logical1 = None
| └ bits 4-2 = 000 -> logical2 = None
| └ bits 1-0 = 10 -> physical = VarInt
00000065 01 . | num_values: 1
00000066 01 . | byte_length: 1
00000067 00 . | data [Offset(String) Int(None)/VarInt, 1 values, 1 B]
| decoded: [0]