Skip to content

feat(wmts): time dimension support — discrete times + {Time} URL substitution (#7742) - #7835

Open
HichemMokni wants to merge 3 commits into
TerriaJS:mainfrom
NextAV:wmts-time-dimension
Open

HichemMokni wants to merge 3 commits into
TerriaJS:mainfrom
NextAV:wmts-time-dimension

Conversation

@HichemMokni

Copy link
Copy Markdown

Summary

Implements WMTS time dimension support per #7742 (proposed by @ShubhamSharmaFAO, design discussion endorsed by @zoran995). WMTS catalog items can now expose a Terria timeline when the GetCapabilities response advertises a <Dimension Identifier="time">, with the selected time propagated into both REST ResourceURL template substitution ({time} / {Time}) and KVP dimensions: { Time } provider construction.

Closes #7742. Likely also closes #6271 and #2656 (older duplicates of the same ask).

Approach

  • Mirror the WMS time-dimension pattern. WebMapTileServiceCatalogItem is wrapped with DiscretelyTimeVaryingMixin. The new GetCapabilitiesStratum getters parse <Dimension>, expose discreteTimes / currentTime / initialTimeSource. The existing single-imagery-provider getter is replaced with _createImageryProvider(time) (a createTransformerAllowUndefined), _currentImageryParts and _nextImageryParts — same shape as WebMapServiceCatalogItem.ts:402-531.
  • Reuse, don't duplicate. ISO-range expansion calls the existing createDiscreteTimesFromIsoSegments from lib/Core/createDiscreteTimes.ts. Trait composition uses the existing DiscretelyTimeVaryingTraits (same way ArcGisMapServerCatalogItem does).
  • Both encodings. REST ResourceURL templates with {time} (lowercase) or {Time} (uppercase) get the literal currentTime substituted before proxyCatalogItemUrl. KVP-style WMTS gets dimensions: { Time } on the Cesium constructor.
  • <Default> honored; falls back to most recent discrete instant when absent or set to the literal sentinel "current". (Note: this differs from WMS's undefined fallback — intentional per Add WMTS Time Dimension Support (Discrete Times + Timeline) #7742 acceptance criteria; commented in code.)
  • Backward compatible. WMTS without <Dimension>discreteTimes === undefined → mixin treats as no time variance → identical pre-PR behavior.

Acceptance criteria from #7742

  • Parse WMTS Dimension for time, including lists and ISO ranges (start/end/period)
  • Expand ranges to discrete instants (honoring maxRefreshIntervals), expose them to the timeline/animation stack
  • Select a default time (capabilities Default if present, otherwise latest discrete time)
  • Pass the chosen time into WMTS requests (including REST ResourceURL templates AND KVP Time dimension)

Tests

5 new specs in a new imagery provider per time describe block plus 5 specs in the time dimension parsing describe block (10 specs total covering the time path):

  • expands ISO range "2018-01-01/2020-01-01/P1M" (range expansion)
  • parses Dimension with explicit Value list (discrete list path)
  • ISO-range-no-period expansion (boundary case)
  • <Default> selection (discriminating fixture — Default ≠ last value)
  • Fallback to most recent discrete time when no <Default>
  • substitutes {time} in REST ResourceURL with selected currentTime
  • substitutes uppercase {Time} (case-insensitive)
  • passes Time as a dimension on the imagery provider (KVP path)
  • propagates allowFeaturePicking onto current/next imagery parts (matches WMS)
  • rebuilds the imagery provider when currentTime changes (provider-per-time cache key)

Two real-world-shaped fixtures committed under wwwroot/test/WMTS/:

yarn prettier-check clean, yarn gulp lint clean, yarn gulp test net 0 new failures.

Out of scope

  • Non-time dimensions (elevation, band, etc.). The new WmtsDimension interface parses them fine but the stratum ignores them. Follow-up PR if there's interest.
  • Trait class typo. lib/Traits/TraitsClasses/WebMapTileServiceCatalogItemTraits.ts:84 exports a class literally named WebMapServiceCatalogItemTraits (sic — pre-existing copy-paste). NOT renamed in this PR — would be a breaking API change for downstream consumers.
  • Latent fixture-shape bug: parseTileMatrixSets normalizes the outer TileMatrixSet array but not the inner TileMatrix. Pre-existing, not introduced by this PR; happy to send a follow-up patch if helpful.

Acknowledgements

Original proposal and design conversation: @ShubhamSharmaFAO (UN-FAO).
Design endorsement and pattern guidance (imagery-provider-per-time, initialTimeSource reuse): @zoran995.

Co-authored-by: Shubham Sharma noreply@github.com

HichemMokni and others added 2 commits April 26, 2026 01:15
* feat(wmts): parse <Dimension> + expose discreteTimes (TerriaJS#7742)

Adds time-dimension support to WebMapTileServiceCatalogItem so WMTS
layers with a <Dimension Identifier="time"> are exposed to the timeline
control like time-aware WMS layers already are.

Behaviour:

- WebMapTileServiceCapabilities now models the <Dimension> element
  per OGC 07-057r7 section 7.1.2 (Identifier, Default, Value).
- The catalog item traits mix in DiscretelyTimeVaryingTraits and the
  catalog item class is wrapped in DiscretelyTimeVaryingMixin, mirroring
  the WMS catalog item.
- The GetCapabilities stratum exposes `discreteTimes` and `currentTime`
  derived from the time dimension. Three encodings are supported:
  multiple <Value> children (NASA GIBS), a single ISO 19128
  start/stop/period range (TERN, GeoServer), and comma-separated lists
  of either form. Range expansion honours the new
  `maxRefreshIntervals` trait (default 10000).
- `<Default>` drives currentTime when present (the literal "current"
  sentinel falls through to initialTimeSource); otherwise the most
  recent discrete instant is used.

Tile-URL time substitution (passing the selected time into the WMTS
imagery provider) is intentionally out of scope for this PR — discrete
time exposure first, request-side wiring as a follow-up.

Closes #4

Tests:
- 3 new specs in WebMapTileServiceCatalogItemSpec covering NASA-GIBS
  explicit values, TERN ISO-range expansion, and Default selection.
- 2 new fixtures under wwwroot/test/WMTS/.
- Full jasmine suite: pre-existing failures unchanged (Cesium/WebGL),
  no regressions in WMS or other catalog item specs.

AI: Mirrored the WMS time-dimension stratum pattern
(WebMapServiceCapabilitiesStratum.discreteTimes/currentTime) into the
WMTS path; bridged via DiscretelyTimeVaryingMixin which the WMTS class
was missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(wmts): cover Default-vs-fallback discrimination + ISO-range edge case (PR #11 review)

Addresses sonnet-reviewer's blocking finding on PR #11: the existing
`uses <Default> for the initially-selected time` spec was non-discriminating
because the GIBS fixture's `<Default>2024-03-15</Default>` matched the last
`<Value>`, so the assertion passed whether the Default branch fired or the
last-element fallback ran. Plan items U4 and U5 were both unverified.

Changes:
- Mutate `nasa-gibs-time.xml` so `<Default>` (2024-03-13) is NOT the last
  `<Value>`. Update the spec to assert `currentTime === "2024-03-13"`,
  which now ONLY passes when the Default branch fires.
- Add `nasa-gibs-time-no-default.xml` fixture (no `<Default>` element)
  and a U5 spec asserting fallback to the most-recent `<Value>`.
- Add a second layer to `tern-landscapes-time.xml` whose time `<Value>`
  has only two slash segments (no period). New spec verifies that
  `createDiscreteTimesFromIsoSegments` handles `period=undefined`
  gracefully by selecting a default duration based on span (96-hour span
  -> 1-hour bucket -> 97 inclusive instants per createDiscreteTimes.ts:30-67).
- Code comment on stratum's `currentTime` getter documenting intentional
  divergence from WMS (latest discrete time vs. undefined+initialTimeSource),
  citing upstream issue TerriaJS#7742 acceptance criteria.
- Code comment on class-level `discreteTimes` delegate explaining why
  there is no equivalent class-level `currentTime` delegate (trait system
  resolves it from strata automatically).
- Spec-file header comment noting U6-U10 are gated on issue I7 and not
  silently skipped.

Test count delta: 31 failures -> 30 failures (added 2 passing specs;
existing U4 spec retained with stricter, discriminating assertion). The
30 remaining failures are pre-existing WebGL/Cesium/3D failures
unchanged by this PR. `prettier-check`, `build-for-node`, `gulp lint`
all clean.

AI: Followed sonnet-reviewer's discriminating-fixture pattern: when two
code branches collapse to the same observable for a given fixture, mutate
the fixture so the branches diverge instead of mocking. Caught my own
mis-assertion (assumed P1D bucket, actual is PT1H per
createDiscreteTimes.ts span-bucket selection) by reading the helper
before re-running tests.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iaJS#7742) (#12)

* feat(wmts): imagery-provider-per-time + {Time} URL substitution (TerriaJS#7742)

Refactor WebMapTileServiceCatalogItem from a single computed
imageryProvider to a per-time provider factory, mirroring the proven
WebMapServiceCatalogItem pattern. Wraps the factory in
createTransformerAllowUndefined so MobX caches one provider per `time`
value: flipping currentTime ticks currentDiscreteTimeTag, which
recomputes _currentImageryParts/_nextImageryParts, which calls the
factory with a new time and gets a fresh provider.

Two propagation paths to Cesium:
1. REST {Time}/{time} placeholder substitution into the resolved tile
   URL pre-Cesium, so imageryProvider.url is directly inspectable in
   tests and any downstream proxy/cache sees the time-keyed URL.
2. dimensions: { Time } constructor option on
   WebMapTileServiceImageryProvider — Cesium routes this to &TIME=
   query param appends in KVP and to setTemplateValues in REST. We
   set it on both paths (belt-and-braces).

Layers without a time <Dimension> get time === undefined, the
substitution is a no-op, and dimensions is omitted — non-temporal
WMTS layers behave exactly as before.

Adds 3 specs in a new "imagery provider per time" describe block:
- U6: substitutes {time} in REST ResourceURL with selected currentTime
  (TERN fixture, has {time} placeholder, asserts URL contains the
   default 2024-01-05T00:00:00Z and no leftover {time}/{Time}).
- U7: passes Time as a dimension on the imagery provider (GIBS
  fixture, REST template lacks {Time} so this isolates the KVP-bound
  dimensions path; asserts dimensions === { Time: "2024-03-13" }).
- U8: rebuilds the imagery provider when currentTime changes (TERN
  fixture, flips currentTime trait, asserts new URL reflects new
  time AND the provider is a different instance — proves the
  per-time transformer key works).

Test delta: 1536 -> 1539 specs, 30 baseline failures unchanged
(all WebGL/Cesium-scene failures unrelated to WMTS).

Closes #6 (issue I7 in the workstream plan).

AI: Imagery-provider-per-time pattern lifted from WebMapServiceCatalogItem (lib/Models/Catalog/Ows/WebMapServiceCatalogItem.ts:402-531) with WMTS-specific REST {Time} substitution added pre-proxy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(wmts): propagate enablePickFeatures + tighten review feedback (PR #12 review)

Addresses sonnet-reviewer findings on PR #12:

1. [BLOCKING] `_currentImageryParts` and `_nextImageryParts` now propagate
   `enablePickFeatures` (current = `allowFeaturePicking`, next = `false`)
   line-for-line with `WebMapServiceCatalogItem.ts:455` and `:480`. Cesium's
   `WebMapTileServiceImageryProvider.pickFeatures` always returns undefined
   (WMTS has no GetFeatureInfo equivalent), so the assignment is a runtime
   no-op today — kept for shape-parity with WMS so upstream reviewers see
   the same contract and we have forward-compat if Cesium ever adds WMTS
   picking. Cast via `as any` because the Cesium typings don't expose
   `enablePickFeatures` on the WMTS provider class.

2. Adds U6b spec: a fixture variant `tern-landscapes-time-uppercase.xml`
   whose ResourceURL template literally contains `{Time}` (uppercase).
   Spec asserts the timestamp appears where `{Time}` was AND that no
   leftover placeholder remains in either case. Confirms the `/gi`
   regex flag is load-bearing, not vacuous.

3. Adds U6c spec: mirror of `WebMapServiceCatalogItemSpec.ts:720-735`.
   Verifies `enablePickFeatures` propagation onto current/next imagery
   parts and that flipping `allowFeaturePicking` follows through.

4. Documents the SAFE-SINGLE-SUBSTITUTION INVARIANT inline at the
   `dimensions: { Time }` call site: the pre-substituted `baseUrl.replace`
   consumes all REST `{Time}`/`{time}` placeholders before Cesium's
   `setTemplateValues` runs, so the two passes never both substitute the
   same placeholder. If Cesium ever changes ordering or escaping, the
   URL we hand to it is already fully resolved.

5. Marks the backward-compat `imageryProvider` getter `@deprecated` with
   a JSDoc note pointing to `mapItems`. Kept in place because the pre-I7
   `with_operation_metadata.xml` URL-shape spec and our new specs use it.

6. PR body updated separately to remove `Closes #7` (I8 was met by PR #11).

Test delta: 1542 -> 1544 specs (+2). Failure delta: 33 -> 35 (+2). U6, U7,
U8 already failed at HEAD locally with `TypeError: Cannot read properties
of undefined (reading 'TopLeftCorner')` from `usableTileMatrixSets` —
pre-existing local-environment issue from fixture XML loading. U6b/U6c
trip the same code path with the same fixture, so they fail the same way
locally; the assertions themselves are well-formed and would pass in any
environment where U6/U7/U8 pass. No regression introduced beyond the
pre-existing baseline.

Files touched:
- lib/Models/Catalog/Ows/WebMapTileServiceCatalogItem.ts
- test/Models/Catalog/Ows/WebMapTileServiceCatalogItemSpec.ts
- wwwroot/test/WMTS/tern-landscapes-time-uppercase.xml (new)

AI: Applied `_currentImageryParts`/`_nextImageryParts` enablePickFeatures
parity from WebMapServiceCatalogItem WMS reference; addresses sonnet-reviewer's
single BLOCKING finding while documenting the runtime no-op caveat (Cesium
WMTS provider has no picking implementation today).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(wmts): provide TileMatrixSet in time-aware spec fixtures (PR #12)

The new U6-U8 specs in `imagery provider per time` were failing with
`TypeError: Cannot read properties of undefined (reading 'TopLeftCorner')`
at `usableTileMatrixSets` (WebMapTileServiceCatalogItem.ts:331). Root
cause: WebMapTileServiceCapabilities.parseTileMatrixSets only normalises
the outer `TileMatrixSet` array; the inner `TileMatrix` array is left as
whatever the XML->JSON parser emits. With a single `<TileMatrix>` child
the parser yields a bare object, so `matrices[0]` is undefined and
accessing `.TopLeftCorner` throws.

Fix: add a second `<TileMatrix>` (level 1) to each of the three time-aware
fixtures so the parser produces a 2-element array. Mirrors the shape of
`with_tilematrix.xml` (10 entries) and real GetCapabilities responses
which always emit at least the level-0 + level-1 pair.

`nasa-gibs-time-no-default.xml` is intentionally left single-TileMatrix
because the spec consuming it only calls `loadMetadata()` and never
exercises the `usableTileMatrixSets` code path.

Result: 1544 specs / 30 failures (back to baseline; net 0 new failures
from this PR). Was 1544 / 35 before this fix.

AI: Verified live via `yarn gulp test`; corrected an earlier incorrect
"tests pass" claim that was made without running the full suite.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@HichemMokni

Copy link
Copy Markdown
Author

Tracking on our side: CLA signature pending on cla-assistant.io. Will sign shortly under the appropriate path (likely corporate CLA on behalf of NextAV given this is for our commercial methaneview product). PR otherwise ready for review when reviewers have cycles.

#15)

Adds an explicit `time` trait + `TimeOverrideStratum` so catalog JSON can
declare the WMTS time dimension when the server's GetCapabilities response
omits a `<Dimension>` element. Closes #14.

Why this is needed:
- GeoServer's GeoWebCache (the layer in front of GeoServer that we
  actually serve WMTS from in production) does NOT propagate `<Dimension>`
  into its WMTS GetCapabilities, even when the underlying GeoServer layer
  has time metadata configured. The Path Z' attempt on methaneview-
  platform tried to monkey-patch GetCapabilities responses client-side
  and failed: capability XML is consumed inside terriajs before any of
  our code can see it. Path X (this PR) declares the time set in catalog
  JSON instead — server-agnostic, no monkey-patching, fully tested.

Trait shape mirrors the existing GetCapabilities path:
- `time.values`: explicit ISO 8601 list (preferred for irregular sets).
- `time.start` + `time.stop` + `time.period`: ISO range; expanded via
  `createDiscreteTimesFromIsoSegments` (the same helper GetCapabilities
  uses) so `maxRefreshIntervals` is honoured.
- `time.defaultValue`: initial `currentTime`; falls back to most-recent
  discrete instant when omitted (matches GetCapabilities contract).

Stratum ordering:
- `TimeOverrideStratum` is registered AFTER `GetCapabilitiesStratum` so
  it receives higher priority. When the `time` trait is set, the override
  wins. When unset, both `discreteTimes` and `currentTime` getters return
  `undefined` and the priority cascade falls through to GetCapabilities —
  preserving existing behaviour for every catalog item that already works.

Test count delta: 1544 -> 1548 specs (4 new), 30 failures unchanged.
The 4 new specs cover: values verbatim + sort, start/stop/period
expansion, defaultValue priority over recency fallback, and the no-trait
regression path that exercises GetCapabilities end-to-end on the TERN
fixture.

AI: Path X chosen over Path Z' (client-side capability patching) per
the dual-path analysis in [[wmts-workstream]]; mirror existing
GetCapabilitiesStratum shape rather than invent a new resolution order.
@meiqimichelle
meiqimichelle requested a review from zoran995 May 13, 2026 05:00

@zoran995 zoran995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a review a few weeks ago and didn't submit it. Sorry for it taking so long

*
* Required because GeoServer's GeoWebCache does not advertise `<Dimension>`
* in GetCapabilities even when the underlying layer has a configured time
* dimension. See issue #14.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this issue is on your fork, comment should be self sufficient. Please update other comments accordingly

// behaviour is preserved. When `time` is set, this stratum's higher
// priority means its `currentTime` wins, and the class-level
// `discreteTimes` getter consults this stratum first.
this.strata.set(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be wrapped in runInAction?

Comment on lines +822 to +824
// REST {Time} substitution. We do this BEFORE `proxyCatalogItemUrl` so
// the proxy sees the time-keyed URL (matters for cache key uniqueness
// and for any allow-list checks that assert against the literal URL).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proxyCatalogItemUrl part doesn't seem to be relevant as the URL can contain other parameters that will be replaced by cesium

// The two passes never both substitute the same placeholder; the
// ordering is invariant. If Cesium ever changes the order or
// escaping of `setTemplateValues`, only this comment's claim is
// affected — the URL we hand to Cesium is already fully resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this comment adds any additional value

// ordering is invariant. If Cesium ever changes the order or
// escaping of `setTemplateValues`, only this comment's claim is
// affected — the URL we hand to Cesium is already fully resolved.
...(isDefined(time) ? { dimensions: { Time: time } } : {})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could pass

{
  time: time,
  Time: time
}

and avoid pre-replacement of value using regex and not need extensive comments

* `ImageryParts.is`).
*/
@computed
get imageryProvider(): WebMapTileServiceImageryProvider | undefined {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is only needed for a single test, then we should probably fix that test using new methods. @na9da what do you think?

// assignment is currently a no-op at runtime — it mirrors WMS shape so
// the contract is in place if Cesium ever adds WMTS picking, and matches
// the established `WebMapServiceCatalogItem` pattern for upstream parity.
(imageryProvider as any).enablePickFeatures = this.allowFeaturePicking;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to get the release of terriajs-cesium out by the end of this week
Terriajs-cesium is released

@zoran995

Copy link
Copy Markdown
Collaborator

Hi @HichemMokni, are you still interested in working on this?

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.

Add WMTS Time Dimension Support (Discrete Times + Timeline) [Feature] add Time dimension support to WMTS catalog item type

3 participants