Fall back to the configured style when a vector tile feature lacks the property - #360
Draft
slesaad wants to merge 1 commit into
Draft
Fall back to the configured style when a vector tile feature lacks the property#360slesaad wants to merge 1 commit into
slesaad wants to merge 1 commit into
Conversation
deck.gl's MVTLayer decodes tiles into a binary form by default. That
format hoists every numeric property into one tile-wide typed array
covering every feature in the tile, zero filled for the features that
never carried it, and merges the whole set into the object handed to
style accessors. A landcover polygon with no elevation therefore arrives
as `{class: 'grass', ele: 0}` - a real number, indistinguishable from a
measured zero.
The `*Prop` style fields added in #341 read straight into that zero fill.
A feature lacking the property named by `weightProp` took line width 0
and drew invisibly; one lacking `radiusProp` took radius 0 and did not
draw at all; one lacking `fillOpacityProp` or `opacityProp` took alpha 0
and vanished - each of them instead of falling back to the layer's
configured `weight`, `radius` or opacity, which is the whole point of
having a flat field beside the prop. The colour props name string
properties, which the binary form does omit when absent, but they are
resolved by the same accessors, so they are covered by the same switch.
Nothing in the binary payload records presence. The string properties it
keeps alongside omit the key, but the numeric arrays are dense and
untagged, and there is no mask - confirmed against @loaders.gl/gis,
where the arrays are allocated with `new T(n)` and written only where
`propName in properties`. So presence cannot be recovered downstream;
the format has to change.
It changes only where it buys something. `binary: false` costs parse
time and memory on every tile, so it is switched on exactly when some
accessor reads a feature property, and every other vector tile layer
keeps the binary fast path. Picking, autoHighlight and uniqueIdProperty
all carry a branch for each mode, and GlobeView already forces this same
setting, so nothing else depends on it. A mission can still override it
through nativeOptions.
Refs #341
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related: #341
Problem
deck.gl's
MVTLayerdefaults tobinary: true. In that format a numeric feature property is not stored per feature — it is hoisted into a tile-wide typed array covering every feature in the tile, allocated zero-filled and written only for the features that actually carry the key. deck then merges that array into the object it hands every style accessor, unconditionally.The result is that a feature which never carried the property is indistinguishable from one carrying
0. From@deck.gl/layers/dist/geojson-layer/geojson-binary.js:So the six per-feature style fields #341 added to vector tile layers silently misbehave for any feature missing the named property:
weightPropresolves to line width 0 — the feature draws invisiblyradiusPropresolves to radius 0 — the point disappears0rather than falling back to the configured valueThis only bites when at least one feature in the tile carries the property; if none do, the typed array is never created and the fallback works correctly. That is why it went unnoticed — a tileset where every feature carries the field behaves perfectly.
Change
resolveStyleAccessorsnow reports whether anything it built actually reads a feature property, and the vector tile layer decodes tiles as GeoJSON when so:In GeoJSON form an absent key is genuinely absent, so the existing
undefinedfallbacks work as written. Layers with no*Propconfigured keep the binary fast path untouched. It sits ahead of thenativeOptionsspread, so a mission can still override it.binary: falseis a first-class path in this deck.gl version, not a corner:getPickingInfo's binary branch exists only to rebuild viabinaryToGeojsonwhat GeoJSON already has,getHighlightedObjectIndexcarries both branches, and deck itself forcesbinary: falseunderGlobeView.Cost
Tiles for layers that use per-feature styling are parsed to GeoJSON rather than binary, which is slower and heavier per tile. It is scoped to the layers that need it, but a dense tileset with per-feature styling will be measurably heavier than one without. The alternative is drawing the wrong thing.
Verified
Unit tests cover each of the six props individually, that a layer with none of them keeps binary decoding, and that
nativeOptionscan still override. Reverting the one-line predicate fails them.🤖 Generated with Claude Code