Consolidate tile-URL building into shared tileUrlUtils - #213
Conversation
Rename cogUrlUtils -> tileUrlUtils and grow it into compileTileUrl(), shared by the Leaflet per-tile middleware and the DeckGL URL builder so time placeholders and STAC/COG params resolve identically per engine.
🤖 Version Auto-BumpedThe version has been automatically incremented to This commit was added to your PR branch. When you merge this PR, the new version will be included. If you want a different version, update |
…MMGIS into feat/tile-url-consolidation
|
#: 1 🔴 |
buildTileUrlOptions now returns a closed set of tile-URL keys instead of spreading the whole layer config, so refresh() can no longer overwrite creation options like the parseInt'd min/maxZoom. Map_ and TimeControl share that one object. Also fixes the DeckGL branch's early return, empty time placeholder substitution, and the Deck setLayerWmsParams path.
…Url →
stac-collection:/COG:/titiler-url: prefix handling) used by both
Map_.makeTileLayer and TimeControl.reloadLayer, so a time-driven reload
can no longer resolve a layer onto a different source than creation did
- TimeControl: resolve the tile source before time URL replacements so
nocache= lands on the tile request instead of inside TiTiler's url=
param for COG: layers; leave layer.url unmutated for tile layers
- leaflet-tilelayer-middleware: normalize the {t} placeholder on every
_url assignment (creation and refresh) and extract WMS URL splitting
- tileUrlUtils: apply {time}/{starttime}/{endtime} replacements before
param injection, since URLSearchParams percent-encodes the braces and
DeckGL (unlike Leaflet) has no earlier substitution step
- Move tile-url-pipeline.md next to the code; add unit tests for
tileLayerSource, the middleware, and tileUrlUtils
… adding support for STAC mosaic limits
| | `titiler-url` | strip prefix, absolutize against `L_.missionPath` | | ||
| | plain template | falls through untouched | | ||
|
|
||
| Output: a real base `url`, the `splitColonType` (the stripped prefix, which |
There was a problem hiding this comment.
can we rename splitcolontype to something that actually indicated the value? such as layerType (if that ends up making sense)
| `compileTileUrl` later keys off of), the tile level's `tileElevation`, and the | ||
| resolved `tileFormat` (forced to `wmts` for `stac-collection` sources). | ||
|
|
||
| > The resolver is **pure**. The `layerObj.tileformat` write for stac layers is |
There was a problem hiding this comment.
why is tileformat not camelCase?
| > **Both** `Map_.makeTileLayer` and `TimeControl.reloadLayer` call this. They | ||
| > used to each carry their own copy of the logic, and the reload copy silently |
There was a problem hiding this comment.
to claude: future agents don't need to know the details of how something used to work -- it is no longer relevant. documentation like this should only describe what literally happens or the motivation behind the existing architecture.
| > dropped the tile-level selection — a time change would swap the layer back to | ||
| > its default source. Keep this single implementation. | ||
|
|
||
| > **Note:** `makeTileLayer` calls `TimeControl.performTimeUrlReplacements(...)` |
There was a problem hiding this comment.
don't call it that then, lmao
| > **not** part of the `buildTileUrlOptions` / `compileTileUrl` pair — don't | ||
| > confuse it for Stage 3. | ||
|
|
||
| ### Stage 2 — Option building (`buildTileUrlOptions`) |
There was a problem hiding this comment.
meta question here: this is basically formatting per each arg type. is there a structural reason to group them together (such as many downstream tasks want them to be bundled -- or each of theme needs a shared util supplied by the parent class) or does it make more sense to define them at the level of the arg itself?
| | ----------- | -------------------- | ------------------------- | --------------------------------------- | | ||
| | **DeckGL** | compile **once** | `Map_.makeTileLayer` | rebuild URL + `Map_.engine.updateLayer` | | ||
| | **Leaflet** | compile **per tile** | `Map_.makeTileLayer` | `tileLayer.refresh(...)` | | ||
| | **WMS** | own substitution | `L.tileLayer.colorFilter` | reads shared `this.options` times | |
| // that ordering for the three standard tokens (L.Util.template substitutes | ||
| // them from `options` before this function is ever called); DeckGL has no | ||
| // such step, so a placeholder inside a query string reached the server raw. | ||
| nextUrl = nextUrl.replace(/{time}/g, timeStr || '') |
There was a problem hiding this comment.
processedUrl? populatedUrl?
Summary
Consolidates tile-URL construction into shared modules used by every rendering path — the Leaflet per-tile middleware, the DeckGL static URL builder, and the TimeControl time-change reload — so all engines apply the same source resolution, time formatting, and STAC/COG/TMS parameter logic instead of each maintaining its own copy.
This replaces the narrower
cogUrlUtilshelper and removes duplicated substitution logic that had drifted between paths.What changed
tileUrlUtilswith a clear two-stage contract:buildTileUrlOptions— formats a layer's time values once and resolvestms → tileFormat.compileTileUrl— a pure substituter: replaces{time}/{starttime}/{endtime}/{customtime.N}, then injectsdatetime=and the STAC/COG/TMS params. It never re-formats time values.tileLayerSource—resolveTileLayerSourceturns a layer config into its base URL (active tile level →L_.getUrl→stac-collection:/COG:/titiler-url:handling). Layer creation and time-driven reload both call it, so they cannot resolve to different sources.compileTileUrlper tile fromgetTileUrl; the WMS path keeps its own substitution but shares the formatted time values.refresh()re-applies the two creation-time URL transformations ({t}rewriting, the WMS base/params split) that a plain_urlassignment would skip.resolveTileLayerSource, in the same order creation does, and pushes the result into the active engine.cogUrlUtilsand its spec; added specs fortileUrlUtils,tileLayerSource, and the Leaflet middleware.src/essence/Basics/Layers_/tile-url-pipeline.mddescribing the full pipeline, the three call sites, and the WMS/globe exceptions.Why
Four real defects, all previously silent:
TimeControl.reloadLayercalledrefresh()unconditionally and wrotel.options.timeon layers that have neither — a deck layer hasprops, notoptions. Time changes on the deck engine now work rather than throwing.Map_.makeTileLayersettime: layerObj.time.endstraight from config — including a literalundefinedin the URL when unset — until the first time change replaced them. They are now formatted from the moment the layer is created.datetime=/was appended for STAC/COG/titiler layers with no time configured (the old guard was!= null, which treats''as a real value). Now omitted. This is a server-visible URL change, and an improvement.0NaN-NaN-…in the URL. They now yield an empty string.Beyond the fixes, one source of truth for source resolution, time formatting, and param injection across Leaflet, DeckGL, and WMS — which also aligns with the engine-agnostic-core direction of moving apply/refresh behind the engine-adapter boundary.
Design intent, not a bug fix:
compileTileUrlis a pure substituter and must stay one. Leaflet calls it per tile, so formatting there would re-bite on every tile fetch and shift dates across timezones, while DeckGL's single URL bake would hide it. (An earlier version of this description claimed the base code had such a double-format bug. It did not — the old per-tile path substitutedthis.optionsverbatim. The invariant is a going-forward guarantee.)Notes
datetime=/omission and the now-formatted creation-time values both change the URL the server sees, in both cases for the better.Testing
tests/unit/tileUrlUtils.spec.js— the formatting invariant, empty/unparseable time handling, STAC/COG/TMS param injection, the TMS de-duplication branches, the global mosaic limits, the closed key set, and placeholders inside query strings.tests/unit/tileLayerSource.spec.js— tile-level selection, the service-prefix branches, and elevation resolution.tests/unit/leafletTileLayerMiddleware.spec.js—refresh()'s{t}rewriting and WMS base/params split, against a minimal Leaflet stub.🤖 Generated with Claude Code
https://claude.ai/code/session_011ZkGkzkgvKXv9p8uiP5hwp