-
Notifications
You must be signed in to change notification settings - Fork 54
Description
Context / Motivation
I’d like to contribute support for nested property types—starting with List<T> and later Map<K,V>—in the official TypeScript decoder (this repo, ts/).
In parallel, I’m developing a Rust encoder that converts GeoBuf, FlatGeoBuf, and GeoJSON into MLT. I need List support (and likely Map later) to represent real-world attributes—for example:
- A parcel with multiple transaction years:
[2015, 2018, 2021] - A feature with repeated price history:
[{price: 100000, year: 2015}, {price: 150000, year: 2021}]
That’s why I’m motivated to contribute this work upstream.
Before implementing anything, I need guidance on the wire-format choice, because the current Tag 0x01 format appears intentionally “MVT-equivalent”.
What I found in the current repo
Tag 0x01 type codes (current state)
Across the existing implementations (C++ / Java / TS / Rust parser), Tag 0x01 supports:
| Code range | Meaning |
|---|---|
0–3 |
ID (32/64 + nullable variants) |
4 |
GEOMETRY |
10–29 |
scalar types (nullable via bit0) |
30 |
STRUCT (only complex type with children) |
There is no distinct LIST or MAP type code today.
Relevant references:
- C++ type map:
cpp/include/mlt/metadata/type_map.hpp - TS type map:
ts/src/metadata/tileset/typeMap.ts - Java type map:
java/mlt-core/src/main/java/org/maplibre/mlt/converter/encodings/MltTypeMap.java - Rust (v01 parser):
rust/mlt-nom/src/v01/column.rs - Proto schema shows
ComplexType = { GEOMETRY, STRUCT }:spec/schema/mlt_tileset_metadata.proto
Implication
To support List / Map in the TS decoder, we likely need to extend the format (or introduce a new tag), otherwise there is nothing encoded to decode.
Decision needed
What is the preferred approach for adding nested types (List/Map) to MLT while keeping compatibility expectations clear?
Option A — Extend Tag 0x01
Add new type codes for LIST and MAP (including nullable variants) and define their stream layouts.
Pros:
- Minimal framing change (still
tag == 1) - Easiest to iterate on in all decoders/encoders
Cons:
- Might weaken the “MVT-equivalent” framing goal of Tag
0x01 - Requires coordinating updates across languages and fixtures
Option B — Introduce a new tag (e.g. 0x02) for nested types
Keep Tag 0x01 strictly “MVT-equivalent”, and add a new block/tag for tiles that use nested types.
Pros:
- Preserves the meaning of Tag
0x01 - Clear separation between “baseline” and “extended” tiles
Cons:
- More format surface area: new framing + more tooling changes (CLI, tests, fixtures)
Specific questions
- Which option is preferred for MLT going forward (A: extend
0x01vs B: new tag)? - If Option A:
- Do we want to preserve the current “nullable via bit0” pattern for new type codes?
- Are there reserved code ranges we should use for new complex types?
- Should
LIST<T>be represented as a complex type with exactly one child (the element type)?
- If Option B:
- What should the new tag framing look like (block structure, metadata placement, versioning)?
- For both options:
- What is the intended JS output shape? (e.g.,
nullvs[], maps asRecord<string, T>vsArray<[K,V]>) - Any constraints on supported nesting depth in v1 (e.g.,
List<List<T>>)? - How should this be tested/validated (new fixtures, or small synthetic tiles)?
- What is the intended JS output shape? (e.g.,
- For
MAP<K,V>specifically: I'd prefer to defer detailed design untilLISTis validated, unless maintainers prefer to define both layouts at once.
Strawman proposal (only if Option A is accepted)
Reserve new type codes while keeping bit0 == nullable consistent with existing encodings:
LIST = 32,OPT_LIST = 33MAP = 34,OPT_MAP = 35
Potential stream layout for LIST<T>:
- Optional
PRESENTstream (if nullable) - A per-feature length stream (lengths → offsets)
- Child column streams for
T, decoded oversum(lengths)concatenated values
Goal of this issue
Get a maintainer decision on the format direction, so I can:
- implement the TS decoder changes,
- add tests and fixtures accordingly,
- and submit a PR that matches the project’s intended evolution path.