Skip to content

Layer editor: fill a tile layer from a VEDA STAC collection - #338

Draft
BhattaraiSijan wants to merge 3 commits into
developmentfrom
feat/veda-stac-layer-source
Draft

Layer editor: fill a tile layer from a VEDA STAC collection#338
BhattaraiSijan wants to merge 3 commits into
developmentfrom
feat/veda-stac-layer-source

Conversation

@BhattaraiSijan

Copy link
Copy Markdown
Collaborator

Closes #333 (part of epic #331). Builds on the time-policy vocabulary from #332's PR (independent code; best merged after it so the written now policies render).

Summary

Adding a VEDA dataset meant hand-collecting facts a machine can read — tile endpoint, render parameters, temporal coverage, bbox, description — and typing them into the layer form. The tile layer editor's Actions row now has VEDA STAC Source: a two-step dialog that looks a collection up, shows what it offers, and fills the layer from the collection's own metadata.

  • Look Up fetches collection-level metadata only (no item crawling) and presents the facts: title, the available renders (one distinct option reads as a plain fact line; several become a dropdown with identical duplicates collapsed; none disables filling), and the temporal coverage with granularity (2024-12-18 → ongoing · P1D).
  • The admin makes the only two genuine choices — which render and time-enabled vs static — both preselected from the facts (dashboard render per VEDA convention; a span or open end suggests time-enabled).
  • Fill Layer writes name, tile URL (titiler-pgstac collection mosaic; time-enabled adds datetime={starttime}/{endtime}, static omits the filter), a legend sampled from the render's colormap across its rescale range (legend and tiles agree by construction), bounding box, description, and the time block — where an ongoing collection (null extent end) gets dataEndTime: "now" plus interval/isPeriodic, so the layer stays current as data grows.

The system decides nothing silently: collections without renders are refused with a clear message (no asset guessing), band counts are never inferred, analysis support and layer identity (uuid) are untouched, and every filled field stays editable.

Commits

  1. Engine — pure, I/O-free mapping (scripts/lib/vedaStacLayer.js), 21 specs on fixtures recorded from the live catalogs.
  2. BackendPOST /api/stac/vedastac/inspect + /fill beside the existing STAC routes (admin-only, validated inputs, at most two upstream requests, warnings surfaced); fetch orchestration with injectable fetch, 6 fake-fetch specs.
  3. Dialog — the two-step UI wired into the tile-layer metaconfig.

Testing

27 unit specs; full suite green. Verified live against both dev catalogs (screenshots in #333):

  • Closed daily collection (sentinel2-nbr-daily): concrete dates, P1D, gray legend matching the tiles.
  • Single-render (landsat-ndvi-daily) → fact line; multi-render (opera-displacement-daily, opera-dswx-daily, sentinel-1-coherence-yearly with P1Y) → dropdown with the VEDA default preselected.
  • Ongoing collection (blackmarble-all-vars-daily-addMetadata): fills with end now — its timeline bar reaches the current date, while closed collections' bars correctly stop at their real ends.
  • Filled layers render on the map with their generated URLs and legends.

Not yet exercised (follow-up verification planned): the no-renders refusal, no-temporal-extent collections, very large render lists, missing dashboard:time_interval, and the error paths (bad id, unreachable raster root) — all covered by unit specs, pending eyes-on checks.

🤖 Generated with Claude Code

…yer fields)

Pure, I/O-free mapping from a VEDA STAC collection document to MMGIS tile layer fields: render-driven tile URL and legend (agreeing by construction), spatial extent to boundingBox, and the temporal extent written as a time policy - an ongoing collection (null extent end) gets dataEndTime 'now' plus the collection's granularity and periodicity (dashboard:time_interval / is_periodic), per #332's vocabulary. The caller chooses render and time mode; nothing is guessed - renders-less collections are refused with a named error. 21 specs on fixtures recorded from the live dev catalogs, including the ongoing-extent case. Engine half of #333.
Two admin-only routes beside the existing STAC ones: POST /api/stac/vedastac/inspect returns the facts a human decides from (renders with parameters, temporal coverage with null-end-as-ongoing, granularity/periodicity, preselections); POST /api/stac/vedastac/fill maps their choices to TileLayer fields via the pure engine. At most two upstream requests per fill (collection, plus the colormap only when a legend is possible), injectable fetch with timeouts, validated inputs, warnings surfaced in the response. Server-side so the layer editor needs no CORS against the catalog. Fake-fetch specs cover the facts shape, choice pass-through, and every degradation path. Backend half of #333.
…ction

Two-step flow in the tile layer editor's Actions row: Look Up fetches collection-level facts and shows what the collection offers - its renders (one distinct option reads as a fact, several become a dropdown, none disables filling) and its temporal coverage with granularity - then the admin's two choices (render, time-enabled vs static, both preselected) fill the form in one update. Ongoing collections arrive with end 'now' per #332, so the layer stays current as data grows. Identity, analysis, and untouched fields keep their values; everything stays editable. UI half of #333.
@BhattaraiSijan
BhattaraiSijan marked this pull request as ready for review August 26, 2026 20:14
@BhattaraiSijan
BhattaraiSijan marked this pull request as draft September 8, 2026 15:57
BhattaraiSijan added a commit that referenced this pull request Sep 10, 2026
Closes #332 (part of epic #331)

## Summary

Time-enabled layers could only declare fixed data start/end timestamps —
but for growing collections the end is unknowable at authoring time, so
every config went stale the day after it was written. A layer's
`dataStartTime`/`dataEndTime` may now be a **policy** re-evaluated on
demand: `now`, or `now ± <ISO-8601 duration>` (`now - P1D`, `now +
P5D`). Concrete datetimes behave exactly as before.

Per veda-ui (our semantics standard), `now` resolves to the raw current
moment — never rounded. But raw `now` is only half the truth for a
**periodic** collection: a product that publishes every 7 days and
started ten days ago has its newest data three days ago, not today. So a
layer may also declare its cadence, `time.interval` (an ISO-8601
duration: `P1D`, `P7D`, `P1M`), and the extent's end **floors to the
last start-anchored step** — `start + N × interval` for the largest N
that fits. Data exists only on the grid; the extent no longer claims
otherwise. This matches how veda-ui reaches the same answer (raw open
end + start-anchored periodic steps).

**Core owns this vocabulary.** The config keeps what the author wrote;
core resolves it behind one provider, `layers:getTemporalExtent`, which
answers `{ start, end }` as plain ISO datetimes (or `null`) — for one
layer by id, or for all layers keyed by UUID, in the same call shapes as
`layers:getCogCapabilities` and `layers:getBounds`. It resolves at the
moment of asking, so an open-ended `now` is always fresh — and the
cadence flooring happens behind the provider too, so a plugin never sees
a policy string, never sees an interval, and never imports a resolver.

Resolution degrades, never breaks: an unparseable policy or interval, a
zero cadence (`P0D`), a missing start to anchor to — each is ignored and
consumers keep their fallbacks. Calendar units step by UTC date
components, not milliseconds, and `start + N × interval` is computed as
one calendar operation, so a monthly cadence stays anchored to the
start's day-of-month.

`isPeriodic` was dropped from an earlier revision of this PR as unused
(review feedback) — correctly so: `interval` alone carries the fact, and
its presence is what marks a layer periodic. #338's VEDA source can fill
it automatically from `dashboard:time_interval`; authors can type it by
hand today.

## Commits

1. **The policy resolver** — one pure function: policy string in,
resolved ISO datetime out, `null` for anything unparseable.
2. **The Timeline consumes it** — replacing its hand-parsed inline
`'now'` special case.
3. **Resolution moves into core** — the resolver relocates to
`Basics/TimeControl_/layerTimePolicy.ts`; `Layers_` registers
`layers:getTemporalExtent`; the Timeline reads extents instead of
resolving; `_shared/time/` removed. The Layers tool's "set global time
to layer extent" action goes through the same path — it previously did
`new Date("now")` and produced `NaN`.
4. **Periodic flooring** — `resolveTemporalExtent` floors a periodic
layer's end to its last data step. Constant-time: N is seeded from a
millisecond approximation, then settled by calendar math.
5. **Core wires through it** — `layers:getTemporalExtent` and the Layers
tool answer cadence-aware; the Timeline gets it with zero changes.
6. **Configure** — a Data Time Interval field beside the data start/end
times in the tile and vector layer forms.

## Testing

- 28 unit specs with an injected clock: all three policy forms, sub-day
and calendar-unit offsets, the 7-day-cadence-ends-three-days-ago
scenario, ends exactly on / off a step, month-end anchoring,
end-before-start clamping, and every failure mode (`P0D` included)
degrading to an unsnapped extent or null. Full suite green (124 files,
1790 tests); typecheck clean.
- Verified live: an ongoing layer configured with end `now` draws its
timeline bar reaching the current date (screenshot in #332), while a
closed-extent layer's bar correctly stops at its real end — and the
`now` config needs no edits, ever.

Mission-level time (`initialstart/initialend`) keeps its existing
seconds-offset syntax; unifying it with ISO durations is possible future
work.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a tile layer from a VEDA STAC collection

1 participant