Skip to content

Decode vector tiles verbosely only when a numeric property is read #392

Description

@slesaad

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

  1. Replace the flat disjunction with a narrower question: does any accessor read a value
    whose absence the compact form would report as zero?
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions