Problem
A vector tile layer whose features are styled from their own properties is decoded from a
compact binary form into a verbose one, on every tile, for the whole session. That switch
exists for a real reason — but it is currently applied more widely than the reason
justifies.
The compact form loses information about numbers only. It gathers each numeric property
into one array covering every feature in the tile, filled with zero for the features that
never carried it, so "this feature has no measurement" and "this feature measured zero"
become indistinguishable. Text and true/false properties are not gathered that way: they
stay attached to the features that actually have them, and a missing one stays missing.
So a layer coloured by a category name, or reading a colour string out of a property, pays
the full cost of the verbose decoding and gets nothing for it. The cost is real — slower
parsing and more memory per tile — and it scales with how dense the tileset is, which is
exactly the case where a layer is least able to afford it.
What we want
The verbose decoding is used only where the compact form would actually produce a wrong
answer, and layers that read only text or true/false properties keep the fast path.
Acceptance
- A layer whose per-feature styling reads only text or true/false properties decodes tiles
in the compact form.
- A layer whose per-feature styling reads a number — directly, or through a colour ramp
keyed on a numeric property — still decodes in the verbose form, and a feature missing
that property still falls back to the layer's fixed style rather than being treated as
zero.
- No visible change to how any existing layer draws.
- The narrowing is driven by what the styling actually resolves to, not by which
configuration fields happen to be filled in.
Notes and open questions
- Two cases are subtler than they look. A colour ramp only matters if it actually
compiles to a live ramp — a ramp configured with a single stop resolves to nothing and
styles no features, so it should not force the verbose form. And exact-value matching only
becomes ambiguous when one of the values being matched is itself zero; matching against
any other number behaves identically either way.
- A legend can mix rules keyed on different properties. If any one of them is a live
numeric ramp, a feature missing that property is mis-styled even when the rule that
should have applied to it is keyed on text — so the decision has to consider the legend
as a whole rather than rule by rule.
- Purely an efficiency change. The current behaviour is over-cautious, never wrong, so
there is no urgency and no user-visible defect to fix.
Implementation sketch — written as of abefac78 on 2026-09-03; a rough guide only, re-verify against latest
How it works today
The adapter builds per-feature style accessors for a layer and, alongside them, a single
boolean meaning "some accessor reads a feature property". That boolean is computed by
listing every per-feature style field by name and OR-ing them together with "a legend is
configured". The vector tile layer then asks for the verbose decoding whenever that boolean
is true.
Rough plan
- Replace the flat disjunction with a narrower question: does any accessor read a value
whose absence the compact form would report as zero?
- For the legend half, ask the compiled legend rather than the raw configuration — a
helper next to the compile step is the natural home, since only it knows whether a ramp
came out live and whether any exact-match value is zero.
- Keep the per-feature opacity, width and radius fields forcing the verbose form; drop the
two colour fields, which resolve identically for zero and for absent.
Gotchas
- Do not derive the flag from whether the accessors came out as functions. That
equivalence holds today and is tempting, but it re-broadens the predicate to exactly what
this issue narrows, silently undoing the change the first time someone tidies it.
- The reasoning depends on how the tile decoder classifies a property: a key is gathered
into the zero-filled numeric arrays only when its value is a finite number. Anything else —
text, true/false, null — is kept per feature. Re-check that classification against the
version in use before relying on it.
- There is one pathological configuration where narrowing changes behaviour: an exact-match
rule whose value is the literal text "undefined" matches an absent property in the verbose
form but not in the compact one. Not worth guarding.
Problem
A vector tile layer whose features are styled from their own properties is decoded from a
compact binary form into a verbose one, on every tile, for the whole session. That switch
exists for a real reason — but it is currently applied more widely than the reason
justifies.
The compact form loses information about numbers only. It gathers each numeric property
into one array covering every feature in the tile, filled with zero for the features that
never carried it, so "this feature has no measurement" and "this feature measured zero"
become indistinguishable. Text and true/false properties are not gathered that way: they
stay attached to the features that actually have them, and a missing one stays missing.
So a layer coloured by a category name, or reading a colour string out of a property, pays
the full cost of the verbose decoding and gets nothing for it. The cost is real — slower
parsing and more memory per tile — and it scales with how dense the tileset is, which is
exactly the case where a layer is least able to afford it.
What we want
The verbose decoding is used only where the compact form would actually produce a wrong
answer, and layers that read only text or true/false properties keep the fast path.
Acceptance
in the compact form.
keyed on a numeric property — still decodes in the verbose form, and a feature missing
that property still falls back to the layer's fixed style rather than being treated as
zero.
configuration fields happen to be filled in.
Notes and open questions
compiles to a live ramp — a ramp configured with a single stop resolves to nothing and
styles no features, so it should not force the verbose form. And exact-value matching only
becomes ambiguous when one of the values being matched is itself zero; matching against
any other number behaves identically either way.
numeric ramp, a feature missing that property is mis-styled even when the rule that
should have applied to it is keyed on text — so the decision has to consider the legend
as a whole rather than rule by rule.
there is no urgency and no user-visible defect to fix.
Implementation sketch — written as of
abefac78on 2026-09-03; a rough guide only, re-verify against latestHow it works today
The adapter builds per-feature style accessors for a layer and, alongside them, a single
boolean meaning "some accessor reads a feature property". That boolean is computed by
listing every per-feature style field by name and OR-ing them together with "a legend is
configured". The vector tile layer then asks for the verbose decoding whenever that boolean
is true.
Rough plan
whose absence the compact form would report as zero?
helper next to the compile step is the natural home, since only it knows whether a ramp
came out live and whether any exact-match value is zero.
two colour fields, which resolve identically for zero and for absent.
Gotchas
equivalence holds today and is tempting, but it re-broadens the predicate to exactly what
this issue narrows, silently undoing the change the first time someone tidies it.
into the zero-filled numeric arrays only when its value is a finite number. Anything else —
text, true/false, null — is kept per feature. Re-check that classification against the
version in use before relying on it.
rule whose value is the literal text "undefined" matches an absent property in the verbose
form but not in the compact one. Not worth guarding.