You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(time-slider): drive a layer's internal time dimension via a generic temporal adapter (#1450)
* feat(time-slider): drive a layer's internal time dimension via a temporal adapter
The Time Slider understood two kinds of temporal layer: a vector layer
filtered by a timestamp property, and a raster time series of dated
sources. Neither fits a data cube, which is one store whose time is an
internal dimension the timeline should index into. Zarr layers therefore
could not join the timeline at all, and each ended up with its own
bespoke time control.
Add a third binding kind: a generic per-layer temporal adapter.
- `TemporalLayerAdapter` (getTimeValues + setTime) with a registry, and a
`SelectorTimeBinding` persisted on `layer.metadata.timeBinding` beside
the vector binding so it survives a project round-trip. The time values
themselves stay transient: they belong to the data store, not the
project.
- The slider owns the snapping, so it skips a tick that lands on the
slice already showing and applies at most one change per throttle
interval per layer. A bound cube shares the track with vector bindings
and dated overlays: the range is their union and the widest dataset
sets the stepping granularity.
- `addZarrLayer` registers an adapter for a store's `time` axis,
decoding CF `units` ("days since 1970-01-01") from the store's own
metadata. The units are consulted before the generic reader because a
raw `1980` is a plausible calendar year *and* a plausible days-since
offset. A calendar Gregorian arithmetic cannot decode (360_day,
noleap) reports no axis rather than a drifting one.
- Plugins can register an adapter for their own custom layers via
`app.registerTemporalLayer(layerId, adapter, { bind: true })`.
- The Layers panel offers "Bind time dimension to Time Slider" for any
layer with an adapter, binding in one step with no property to pick.
Fixes#1448
* Address CodeRabbit review feedback
- Fix an unregister that escaped its guard: the Zarr control's `layerremove`
handler dropped *every* cube's temporal adapter when any one cube was
removed, so a second cube silently stopped tracking the timeline. The
new two-cube test CodeRabbit asked for is what caught it.
- Serialize `setTime` per layer. Throttled ticks fired detached async
tasks, so two chunk fetches for the same layer could resolve out of
order and strand the renderer on the older slice while the memo
recorded the newer index. Applies now chain per layer, so the last
requested index is also the last applied.
- Key the in-flight add context by store URL instead of two module-level
slots. `addCloudNetcdfLayer` runs off `addZarrRasterLayer`'s queue, so
overlapping adds could clobber each other's headers or kerchunk refs
and resolve a cube's time axis against the wrong credentials.
- Stop caching a metadata lookup that found nothing, so a transient
outage (or a first, unauthenticated add of a store later added with
credentials) no longer permanently disables the binding for that store.
- Bound each metadata fetch with `AbortSignal.timeout`, so a store that
accepts the connection and never answers cannot leave the cached
promise -- and every later add awaiting it -- pending forever.
- Clear a layer's transient `timeFilter` when a selector binding replaces
a vector one, matching what the vector bind dialog already does.
- Reuse each locale's established term for the Time Slider rather than
inventing a second name for the same dock, and fix the Arabic
gender agreement (`هذا الطبقة` -> `هذه الطبقة`).
* Address CodeRabbit review feedback (round 2)
Same-URL concurrent adds could still cross each other. `addCloudNetcdfLayer`
ran off the queue that `addZarrRasterLayer` uses, so two adds of one store
could both be inside `control.addLayer` at once. The control's `layeradd`
carries no correlation id, so each add's capture handler then latched onto
whichever event fired last: one layer got two adapters (the later
registration winning, with the wrong context) and the other got none.
Keying the pending context by URL, as the previous round did, could not fix
this -- the URL is the same. So:
- Route every programmatic add through one `queueZarrAdd` helper, which is
what `addZarrRasterLayer` already did and what its own comment says is
needed ("two overlapping adds would each see the other's event and could
return the wrong layer"). `addCloudNetcdfLayer` simply was not on it.
- Drop the URL-keyed context map. Each add now registers its own layer's
adapter after its add resolves, with its own headers/references in
lexical scope. A refcounted set of in-flight programmatic URLs is all
that remains, so the shared handler knows to leave those layers alone
and only registers the Zarr panel's own adds (which carry no context).
The new test asserts the guarantee directly -- `addLayer` is never
re-entered -- and fails against the un-queued version.
* feat(zarr): add a time-series sample dataset to the Zarr Layer panel
The panel's only sample was the CarbonPlan climate cube, whose non-spatial
dimensions are `band` and a bare 1-12 `month` climatology with no CF
`units`. That is not a series of instants, so no temporal adapter is
registered for it and the Layers panel correctly offers no bind action --
leaving the Time Slider binding from this PR with nothing to demonstrate.
Add NOAA OI SST V2 monthly means (1981-2023, 1-degree global, public
domain), published to Source Cooperative and chunked one time step per
chunk (~165 KiB) so stepping the timeline is a single small read. The ARCO
weather archives are chunked along time instead, which is right for
extracting a series at a point but means drawing one instant pulls the
whole series for every spatial tile.
The entry carries its own variable, color limits, colormap and selector,
because the panel's `default*` options are the CarbonPlan sample's: `sst`
against a 0-300 ramp is a flat white wash, and `{ band, month }` names
dimensions this store does not have. That needs maplibre-gl-components
0.28.0 (opengeos/maplibre-gl-components#123), bumped here.
`ZARR_COLORMAP_STOPS` moves above `ZARR_OPTIONS` because that initializer
now resolves a ramp; a const referenced before its declaration would throw
at module evaluation.
"bindDialogDescription": "Filtert die Objekte dieser Ebene anhand einer Datums- oder Zeitstempel-Eigenschaft, während Sie die Zeitleiste durchlaufen. Die Ebene behält ihre Symbolisierung und Deckkraft.",
3960
3962
"bindScanning": "Objekte werden nach Datums- und Zeitstempel-Eigenschaften durchsucht…",
Copy file name to clipboardExpand all lines: apps/geolibre-desktop/src/i18n/locales/en.json
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -4115,6 +4115,8 @@
4115
4115
"styleCopied": "Copied style from \"{{name}}\".",
4116
4116
"stylePasted": "Pasted style from \"{{name}}\".",
4117
4117
"bindToTimeSlider": "Bind to Time Slider…",
4118
+
"bindTimeDimensionToTimeSlider": "Bind time dimension to Time Slider",
4119
+
"bindNoTimeDimension": "This layer has no readable time dimension.",
4118
4120
"unbindFromTimeSlider": "Unbind from Time Slider",
4119
4121
"bindDialogDescription": "Filter this layer's features by a date or timestamp property as you scrub the timeline. The layer keeps its styling and opacity.",
4120
4122
"bindScanning": "Scanning features for date and timestamp properties…",
Copy file name to clipboardExpand all lines: apps/geolibre-desktop/src/i18n/locales/es.json
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -3966,6 +3966,8 @@
3966
3966
"styleCopied": "Estilo copiado de «{{name}}».",
3967
3967
"stylePasted": "Estilo pegado de «{{name}}».",
3968
3968
"bindToTimeSlider": "Vincular al control deslizante de tiempo…",
3969
+
"bindTimeDimensionToTimeSlider": "Vincular la dimensión temporal al control deslizante de tiempo",
3970
+
"bindNoTimeDimension": "Esta capa no tiene una dimensión temporal legible.",
3969
3971
"unbindFromTimeSlider": "Desvincular del control deslizante de tiempo",
3970
3972
"bindDialogDescription": "Filtra las entidades de esta capa por una propiedad de fecha o marca de tiempo mientras recorre la línea de tiempo. La capa conserva su estilo y opacidad.",
3971
3973
"bindScanning": "Buscando propiedades de fecha y marca de tiempo en las entidades…",
Copy file name to clipboardExpand all lines: apps/geolibre-desktop/src/i18n/locales/fr.json
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -3955,6 +3955,8 @@
3955
3955
"styleCopied": "Style copié depuis « {{name}} ».",
3956
3956
"stylePasted": "Style collé depuis « {{name}} ».",
3957
3957
"bindToTimeSlider": "Lier au curseur temporel…",
3958
+
"bindTimeDimensionToTimeSlider": "Lier la dimension temporelle au curseur temporel",
3959
+
"bindNoTimeDimension": "Cette couche n'a pas de dimension temporelle lisible.",
3958
3960
"unbindFromTimeSlider": "Délier du curseur temporel",
3959
3961
"bindDialogDescription": "Filtrer les entités de cette couche par une propriété de date ou d'horodatage lorsque vous parcourez la chronologie. La couche conserve son style et son opacité.",
3960
3962
"bindScanning": "Analyse des entités pour les propriétés de date et d'horodatage…",
Copy file name to clipboardExpand all lines: apps/geolibre-desktop/src/i18n/locales/hi.json
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -3955,6 +3955,8 @@
3955
3955
"styleCopied": "\"{{name}}\" से शैली कॉपी की गई।",
3956
3956
"stylePasted": "\"{{name}}\" से शैली पेस्ट की गई।",
3957
3957
"bindToTimeSlider": "टाइम स्लाइडर से बाइंड करें…",
3958
+
"bindTimeDimensionToTimeSlider": "समय आयाम को टाइम स्लाइडर से बाइंड करें",
3959
+
"bindNoTimeDimension": "इस लेयर में कोई पठनीय समय आयाम नहीं है।",
3958
3960
"unbindFromTimeSlider": "टाइम स्लाइडर से अनबाइंड करें",
3959
3961
"bindDialogDescription": "टाइमलाइन को स्क्रब करते समय इस लेयर के फ़ीचर को दिनांक या टाइमस्टैम्प प्रॉपर्टी के अनुसार फ़िल्टर करें। लेयर अपनी शैली और अपारदर्शिता बनाए रखती है।",
3960
3962
"bindScanning": "दिनांक और टाइमस्टैम्प प्रॉपर्टी के लिए फ़ीचर स्कैन किए जा रहे हैं…",
Copy file name to clipboardExpand all lines: apps/geolibre-desktop/src/i18n/locales/id.json
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -3897,6 +3897,8 @@
3897
3897
"styleCopied": "Gaya disalin dari \"{{name}}\".",
3898
3898
"stylePasted": "Gaya ditempel dari \"{{name}}\".",
3899
3899
"bindToTimeSlider": "Ikat ke Penggeser Waktu…",
3900
+
"bindTimeDimensionToTimeSlider": "Ikat dimensi waktu ke Penggeser Waktu",
3901
+
"bindNoTimeDimension": "Lapisan ini tidak memiliki dimensi waktu yang dapat dibaca.",
3900
3902
"unbindFromTimeSlider": "Lepas dari Penggeser Waktu",
3901
3903
"bindDialogDescription": "Filter fitur layer ini berdasarkan properti tanggal atau timestamp saat Anda menggeser linimasa. Layer mempertahankan gaya dan opasitasnya.",
3902
3904
"bindScanning": "Memindai fitur untuk properti tanggal dan timestamp…",
0 commit comments