Skip to content

Add CovJSON API design alternatives analysis#26

Open
chuckwondo wants to merge 4 commits into
mainfrom
docs/api-design-alternatives
Open

Add CovJSON API design alternatives analysis#26
chuckwondo wants to merge 4 commits into
mainfrom
docs/api-design-alternatives

Conversation

@chuckwondo

Copy link
Copy Markdown
Contributor

What this is

A design alternatives analysis (an RFC, not code) for the titiler-covjson HTTP surface, added as docs/07-api-design-alternatives.md. The Grid model layer (helpers/input/modeler) is implemented and tested, but there's no HTTP layer yet, so this will settle the endpoint shape before we wire the first end-to-end slice and a Docker container.

It compares three non-bespoke directions (TiTiler-native output format; hybrid EDR-vocabulary extension; full OGC API-EDR conformance), contrasts them with the current bespoke design in doc 02, and lands on a recommendation. Every factual claim is grounded against the installed titiler.core 0.24.2, the OGC API-EDR standard, and the CoverageJSON spec (see the doc's Sources).

This PR does not change 02-api-definition.md; it proposes how that doc might evolve once we agree on a direction.

Review ask

The doc opens with a "How to review this" NOTE box giving a tiered reading path for limited time. The short version:

  • The one decision that's yours: is EDR conformance a project goal? (yes / maybe / no) — everything else already carries a recommendation.
  • ~2-min path: Section 8 (Recommendation → Option B), the Section 8.1 table (your answer → option + first endpoint), and the five questions in Section 9.
  • Recommendation: Option B (hybrid EDR-vocabulary FactoryExtension) with a first /cube Grid slice; only an explicit "no" to conformance changes that.

Please comment inline wherever you disagree, and answer the conformance question (Section 8.1) so we can pick the path. Next step after that is a short spec for the chosen Grid endpoint, then implementation.

Secondary ask

Separately, are you interested in setting up some lightweight ADR scaffolding (a docs/adr/ folder with a short MADR template) to record this and future decisions as 1-page records? This analysis would remain the supporting study; a short ADR would capture just the chosen direction. Happy to scaffold it if you're keen, or skip it for now.

Adds docs/07-api-design-alternatives.md: a grounded comparison of three
non-bespoke directions for the titiler-covjson HTTP surface
(TiTiler-native output format, hybrid EDR-vocabulary extension, and full
OGC API-EDR conformance), contrasted with the current bespoke design in
doc 02.

The Grid model layer (helpers/input/modeler) is implemented and tested,
but there is no HTTP layer yet, so this settles the endpoint shape
before the first end-to-end slice and Docker container. Recommends
Option B (hybrid EDR-vocabulary FactoryExtension) with a first /cube
Grid endpoint; the one open decision is whether EDR conformance is a
project goal.

Does not change 02-api-definition.md; proposes how it might evolve.
Claims are grounded against the installed titiler.core 0.24.2, the OGC
API-EDR standard, and the CoverageJSON spec (see the doc's Sources).
@emmanuelmathot

Copy link
Copy Markdown
Contributor

cc @vincentsarago

Comment thread docs/07-api-design-alternatives.md Outdated
Comment on lines +104 to +114
### 2.3 One practical wrinkle: `.{format}` is not free real estate

TiTiler's image endpoints select output via a `.{format}` path suffix bound to
the `ImageType` enum and its render pipeline (`png`, `tif`, `jpeg`, `npy`, ...).
**CovJSON is not an `ImageType`** and is not produced by the render pipeline, so
it cannot ride that suffix machinery directly. Whichever option we pick, the
extension therefore registers its **own** routes and selects the format itself
rather than through the image `.{format}` suffix -- the selection mechanism is
settled in Section 7.1 (`f=CoverageJSON` or `Accept`). This is a small but real
constraint that rules out "just add `.covjson` to the existing image route for
free."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Strong agree on the selection mechanism (f=/Accept, no suffix). One reframe: .{format} isn't really blocked by the "Image" conceptnpy already rides that suffix and is not an image either. The actual blocker is narrower: CovJSON isn't a member of the ImageType enum / render pipeline, so it's a format-registration problem, not an Image-semantics one. I wouldn't lean on the "not an image" framing; lean on "not in the ImageType enum / render pipeline." Conclusion (own routes + f/Accept) is unchanged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

f=/Accept is definitely a more OGC friendly way. 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reframed in Section 2.3: the blocker is now stated as "not a member of the ImageType enum / render pipeline" (with npy as the example of a non-image that does ride the suffix) — a format-registration gap, not image-semantics. Conclusion unchanged.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed as the selector in Section 7.1 (f=CoverageJSON else Accept, no path suffix), reusing TiTiler's existing f-else-Accept idiom.

Comment on lines +147 to +152

EDR conformance is modular: a server must implement **at least one** query type,
plus the Core/Collections plumbing (a landing page, `/conformance`, and a
`/collections` model with per-collection `data_queries`, `parameter_names`, and
extents). That plumbing is the line between "EDR-flavored" and "EDR-conformant"
-- the key axis separating Options B and C below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Proposing we anchor this API on titiler-stacapi (https://developmentseed.org/titiler-stacapi/) as the primary deployment model:

  • Collection-scoped routes give us EDR collectionId semantics for free (and make the /collections/{id}/cube conformant paths reachable without net-new collections plumbing).
  • Resolving a collection to its STAC items gives us the datetime/t axis the current 2-D GridInput is missing — so /cube z/t and PointSeries get a real backing instead of a TODO.

Implication for the extension: it must register against two dependency surfaces — titiler-stacapi's collection_id/item-search deps (the EDR, collection-scoped path) and titiler.core's url=/DatasetParams (the single-dataset escape hatch). Worth stating that dual-mount explicitly in the spec so it's a deliberate design, not two divergent code paths.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Partly adopted, partly reframed — flagging the divergence. The collection-scoped t-axis backing is captured, but as one option behind a resolver seam (Section 7.6) rather than as the primary deployment with the dependency taken now. Reasoning: the t-axis is real titiler-stacapi runtime behavior (a genuine eventual dependency), but slice 1 stays dependency-free against a single COG via url=, which remains a permanent path alongside any collection backing. Your "two surfaces" become "one seam, swappable backing, url= always available." If you want titiler-stacapi committed as the primary surface sooner, that's a product call I'd rather make explicitly than bake in — let me know.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Basically, we don't have to commit to adding titiler-stacapi as a dependency right now. We can keep our aim to reach an end-to-end capability lean by avoiding adding it now without blocking us from adding it later.

Comment thread docs/07-api-design-alternatives.md Outdated
Comment on lines +233 to +239
- **Pros:** recognizable, standards-aligned vocabulary; interoperable with EDR /
CovJSON tooling at the query-shape level; future-proof -- a clean, additive
path to full conformance (Option C) without renaming endpoints; does not force
the heavy Collections model on day one.
- **Cons:** more up-front concept mapping than Option A (WKT parsing, EDR param
names); "EDR-flavored but not conformant" can mislead clients that probe
`/conformance` -- we must be explicit that conformance is not yet claimed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Worth being precise about what Option B's "interop at the query-shape level" delivers on its own: EDR query types are resources under /collections/{collectionId}/{queryType}, so a bare top-level /cube is not a path any EDR client discovers. B over a plain factory therefore buys EDR parameter vocabulary (coords, parameter-name, f) but not EDR path-level interop.

The good news (see my note on building this on titiler-stacapi): once the surface is collection-scoped, that path-level interop comes essentially for free — which is also why I think the B→C distance is smaller than the matrix suggests. Can we state explicitly which interop B delivers standalone vs. via collection scoping, so we don't oversell discoverability?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Section 3 now separates the two levels explicitly: B standalone delivers EDR parameter interop (coords, parameter-name, f); path-level discoverability needs collection scoping (C, or B mounted collection-scoped). The matrix has two distinct interop rows, and Option B's Pros/Cons say so directly. The smaller B→C distance is noted in Option C and the matrix.

Comment thread docs/07-api-design-alternatives.md Outdated
Comment on lines +385 to +403
4. **Aggregation vs extraction (the doc-02 `format=aggregated` conflation).**
doc 02 Section 3.2 overloads one `/bbox` endpoint with a `format` flag that
switches between two unrelated operations: `format=full` *extracts* the raster
cells (a `Grid`, shape `[H, W]`), while `format=aggregated` *reduces* them to a
single statistic (mean/median/...) returned as a `Polygon` (shape `[1]`). They
differ in operation (extraction vs reduction), output domain (`Grid` vs
`Polygon`), shape (array vs scalar), and which parameters apply -- so bundling
them behind one `format` flag forces a caller to read `format` to know whether
they get an array or a scalar. Both TiTiler (`/bbox` vs `/statistics`) and EDR
(an `area`/`cube` query vs a separate aggregation concern) keep these apart; we
should too: make aggregation its own endpoint, and do not let it gate the Grid
slice.

Note: this is **no longer an upstream blocker.** `Polygon` and `PolygonSeries`
landed in covjson-pydantic 0.8.0 (now the project's pinned floor and the
installed version), so the aggregated path is buildable today; it should simply
be a *separate* endpoint rather than a `format` mode on Grid extraction.
(`Section`, `MultiPolygon`, and `MultiPolygonSeries` remain absent from the
upstream `DomainType` enum, but none are needed here.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think there's a genuine misunderstanding here, and it's the most important one. The aggregation I originally intended is not a statistical reducer (mean/median → scalar Polygon). It's downsampling a large extent to reduced values — Map-tile-as-CovJSON. Real case: ground-motion / PSI over a large bbox with millions of points; we cannot extract the ndarray at full resolution, so we aggregate onto a reduced width × height Grid. That's still a Grid (array), not a scalar — closer to TiTiler's part(width,height) / overview path than to /statistics.

Two asks: (1) confirm this reduced-resolution Grid capability is preserved as a first-class concern in the new design (it looks like it survives via the factory's width/height/max_size, but I don't want it folded into "statistics"); and (2) note the harder case — for vector point clouds (PSI), reducing to a grid is spatial binning, a different pipeline than rio-tiler raster resampling, and arguably an EDR extension rather than core. If EDR doesn't cover "tile-as-coverage aggregation," that's fine — let's name it as our extension.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Section 7.4 now names three operations: full-resolution extraction, reduced-resolution Grid (downsampling via part(width,height)/max_size — first-class, explicitly not /statistics), and statistical reduction. The reduced-resolution Grid is preserved as a first-class concern, and the vector point-cloud case is called out as spatial binning — a separate pipeline, out of scope for the raster path, named as our own EDR extension ("tile-as-coverage aggregation").

Comment thread docs/07-api-design-alternatives.md Outdated
Comment on lines +424 to +427
- **Band selection can stay TiTiler-native for now.** Adopting EDR's
`parameter-name` as the selector (vs TiTiler's `bands`/`bidx`/`expression`)
is not required for the slice; keep the TiTiler parameters and revisit
`parameter-name` only if/when full EDR conformance (Option C) is pursued.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Genuine question, not rhetorical: what actually blocks mapping EDR parameter-name onto TiTiler's bands/bidx/expression now? It reads like a thin alias over the existing band-selection dependency.

My worry is that deferring it undercuts the headline "rename-free growth path" claim: if we ship /cube with bands=/bidx= and later go conformant, we do introduce a new public selector (parameter-name) — exactly the churn B is supposed to avoid. If it's cheap, I'd rather accept parameter-name as an alias from day one (keeping bands working) so the public vocabulary is stable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — nothing blocks it. Section 7.6 adopts parameter-name from day one as an alias for band_names (with bands/bidx for indices and expression for band math), so the parameter vocabulary is stable before any conformance step.

Comment on lines +246 to +267
### Option C -- Full OGC API-EDR conformance

Commit to EDR as the API contract: landing page, `/conformance`, a
`/collections` model with per-collection `data_queries`, `parameter_names`,
spatial/temporal/vertical extents, `/instances` for time-versioned data, and the
query types as conformant resources, with CovJSON as the primary `f` encoding.

- **Format selection:** the standard OGC API `f`/`Accept` mechanism; plus full
content negotiation and the collection-description machinery.
- **Reuse:** moderate -- reader reuse as in B, but a substantial amount of
net-new EDR plumbing (collections, conformance, metadata) that has no TiTiler
equivalent to inherit.
- **Pros:** the most interoperable and discoverable outcome; a real, certifiable
OGC API that EDR clients consume with zero custom glue; clearest long-term
story.
- **Cons:** largest scope and slowest to a first end-to-end slice; the
Collections/conformance model is a meaningful design effort in its own right;
arguably over-scoped for a TiTiler *extension* whose first job is simply to
emit CovJSON; risks blocking the near-term goal (prove the Grid path in a
container).
- **Effort:** highest.
- **Growth path:** terminal -- this *is* the conformant end state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Following up on the collection-identity concern: I think we resolve it by building the EDR surface on titiler-stacapi rather than reconciling url= with a hand-rolled collections model. Its factories are already collection-scoped (/collections/{collection_id}/…) and backed by a configured STAC API, so STAC collection IDs serve directly as EDR collectionIds — consistent with doc 02's existing "STAC-first" framing. We keep a url= path as a single-dataset escape hatch via a plain TilerFactory.

Net effect: the data-identity cost I'd otherwise worry about for Option C mostly disappears, which further shortens the B→C distance. Suggest a sentence here noting that C's real effort is not a bespoke collections model if we adopt titiler-stacapi.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The sentence you asked for is in Option C: if collection identity comes from a STAC backing, STAC collection IDs serve directly as EDR collectionIds, so C's collections model isn't bespoke — which is why the B→C distance is shorter than the matrix's raw "High" suggests. Stated conditionally ("if STAC-backed") to match the resolver-seam framing in the related thread rather than committing the dependency.

Comment thread docs/07-api-design-alternatives.md Outdated
Comment on lines +86 to +102

```python
@define
class FactoryExtension(metaclass=abc.ABCMeta):
@abc.abstractmethod
def register(self, factory: "BaseFactory"): ...
```

An extension's `register()` adds routes onto an existing factory's router, so the
new routes inherit that factory's dependencies. This is the same mechanism
TiTiler's own first-party extensions use -- e.g., `cogValidateExtension`,
`cogViewerExtension`, `stacViewerExtension`, `wmsExtension`, and `wmtsExtension`
in the `titiler.extensions` package (see Sources). All three options below are
delivered as a `FactoryExtension`;
they differ only in the **endpoint names/vocabulary and the conformance
commitment**, not in the attach mechanism or the format selector (which is shared
-- Section 7.1).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Proposing we add a cross-cutting decision: the mount prefix should be a factory-level setting, not hard-coded. For a pure-EDR deployment you'd want it unprefixed (or /edr); inside a standard TiTiler deploy you'd want to choose a prefix (/coverage, /edr, …) to sit alongside the image endpoints. Please expose this as a setting on the FactoryExtension (with a sensible default) rather than baking a fixed path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — Section 7.7. BaseFactory already exposes router_prefix, so the mount prefix is a factory setting with a sensible default, not a baked path.

Comment thread docs/07-api-design-alternatives.md Outdated
Comment on lines +472 to +476
2. **First Grid endpoint = `/cube`?** *Recommend yes.* `/cube` (bbox + optional
z/t) maps directly onto the existing bbox `Reader.part()` -> `GridInput` path
and parses a plain `bbox` rather than Well-Known Text geometry (Section 7.6).
`/area` (polygon via `coords`) is the more general EDR primitive and can follow
via `Reader.feature()`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Heads-up on a naming-expectation trap. EDR cube is semantically bbox + z + datetime (a 3-D/4-D hypercube), but the implemented GridInput is (bands, height, width) — purely 2-D + bands, no z/t axis. If we expose /cube for the first slice, EDR-aware clients will reasonably expect z/datetime to do something.

Two clean exits: (a) accept z/datetime as validated no-ops for now and document the 2-D limitation, or (b) name the first slice for what it actually returns (a 2-D area/grid) and promote to /cube when z/t land. I'd prefer we not ship an EDR verb whose dimensionality we don't yet honor. (Note: building on titiler-stacapi gives us the natural t-axis source — a collection resolves to STAC items carrying datetime — so option (a) becomes realistic rather than a TODO.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Took your exit (b): Section 7.6 names the first slice for what it returns (2-D /bbox → Grid) and promotes to /cube only when a real z/t axis backs it. Exit (a) — z/datetime as documented no-ops — is explicitly rejected as dishonest while the dimensionality is unhonored.

Comment thread docs/07-api-design-alternatives.md Outdated
Comment on lines +450 to +454
| Answer to "is EDR conformance a goal?" | Recommended option | First Grid endpoint | What the answer changes |
| --- | --- | --- | --- |
| **Yes** -- a committed goal | **B now, on a planned road to C** | `/cube` | Build the EDR-vocabulary slice first, then add the Collections / `/conformance` plumbing as committed follow-on work, and start sketching collection metadata sooner. Do *not* build all of C before the first slice. |
| **Maybe** -- open but uncommitted | **B** (the default recommendation) | `/cube` | Nothing extra now. B keeps C reachable later without renaming endpoints, so the decision is deferrable at no cost. |
| **No** -- an explicit non-goal | **A** (TiTiler-native) | `/bbox/{minx},{miny},{maxx},{maxy}` via `?f=CoverageJSON` | Drop the EDR vocabulary (its interop / conformance payoff is moot) and use TiTiler-native endpoint names, with the `f=CoverageJSON` / `Accept` selection from Section 7.1 (no `/coverage` path suffix). |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My answer to the one decision: "maybe" — open but uncommitted, which keeps your Option B recommendation. I want to be precise that this is not a "no": my gut said no, but the more I read this, the more I think anchoring on the OGC standard makes our implementation more robust and better-specified, even if we never certify. So: not a committed goal, but an explicit non-rejected future direction → Option B, with C kept reachable.

Flagging because under this table a literal "no" would point to Option A, which isn't what I want.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Recorded as "maybe" in the NOTE box, the Section 8.1 table (marked as the answer given), and Section 9 Q1. The decision guide makes clear "maybe" → Option B, with only an explicit "no" pointing to A.

@emmanuelmathot

Copy link
Copy Markdown
Contributor

Thank you for this — genuinely excellent work, and exactly the kind of "settle the shape before the code" rigor I was hoping for. The grounding against installed titiler.core, EDR, and the CovJSON spec is much appreciated.

Decision on the one open question (Q1): conformance is a "maybe" — open, uncommitted, but explicitly not a "no". I want OGC API-EDR as a guiding standard for robustness even if we never certify. So I'm endorsing Option B with a /cube-class Grid first slice, on the understanding it stays cleanly promotable to C.

Architecture base: I'd like us to anchor the EDR surface on titiler-stacapi. Its collection-scoped routes give us EDR collectionId semantics for free, and resolving a collection to its STAC items gives us the datetime/t axis the current 2-D GridInput lacks — so /cube z/t and PointSeries get a real backing. A plain TilerFactory with url= stays as a single-dataset escape hatch. This also shrinks the B→C distance considerably (no bespoke collections model needed). See the inline thread.

My inline notes net out to a few things I'd like reflected before we write the endpoint spec:

  1. Be precise about which interop B delivers standalone (EDR parameters) vs. via collection scoping (EDR paths).
  2. Don't ship the /cube verb until its z/t dimensionality is honored, or name the 2-D slice for what it is.
  3. Preserve aggregation-as-downsampling (reduced-resolution Grid for huge extents / PSI) — distinct from statistical reduction, and likely our own extension; the vector-point-cloud case is spatial binning, a separate pipeline.
  4. Consider adopting parameter-name as an alias now to keep the public vocabulary stable.
  5. Make the mount prefix a factory-level setting.

On the secondary ask: yes to lightweight docs/adr/ MADR scaffolding — capture this as ADR-0001 with this analysis as the supporting study.


All three options build on the same substrate, stated once here.

### 2.1 `TilerFactory` already implements the query taxonomy

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wouldn't not directly extend the TilerFactor or create an extension but create a new Factory based on titiler.core.factories.BaseTilerFactory like https://github.com/developmentseed/titiler-stacapi/blob/14cb0348d62a34f7ea36f95bff885175de19e87b/titiler/stacapi/factory.py#L313

you don't get the regular endpoints but they are usually easy to copy/paste

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adopted — Section 2.2 is rewritten around a dedicated factory subclassing titiler.core.factory.BaseFactory with its own register_routes(), citing OGCEndpointsFactory(BaseFactory) as the exemplar. One correction for the record: in the pinned titiler.core (0.24.2) the base is named BaseFactory (renamed from BaseTilerFactory in 0.19.0), and conforms_to arrived in 0.22.0 — so this is also why the dependency floor has to rise (Section 7.8 / #27).

@chuckwondo

Copy link
Copy Markdown
Contributor Author

Conformance recorded as "maybe" → Option B confirmed, C kept reachable. The doc is reformulated against the feedback; ADR scaffolding (docs/adr/, MADR-lite) added with this analysis as the supporting study for ADR-0001. Summary of what changed:

  • Mechanism is now a dedicated BaseFactory subclass, not a FactoryExtension (Section 2.2) — converging with Vincent's note.
  • Interop split into parameter-level (B, free) vs path-level (C / collection-scoped) throughout (Section 3, matrix).
  • First slice is the honest 2-D /bbox grid; /cube deferred until z/t is backed (Section 7.6).
  • Aggregation disentangled into three operations — extraction, reduced-resolution Grid (first-class, not statistics), statistical reduction — with vector point-cloud binning named as a separate pipeline / our extension (Section 7.4).
  • parameter-name adopted as an alias now (Section 7.6); mount prefix is a factory setting via router_prefix (Section 7.7).

Two places I went a different way than your summary, deliberately, and want to flag rather than paper over:

  1. titiler-stacapi. Rather than anchor on it as the primary deployment model / take the dependency now, I've put it behind a resolver seam as one recommended (STAC-first) temporal backing, with no dependency in slice 1 (single COG via url=, which stays a permanent escape hatch). The t-axis payoff is genuine titiler-stacapi runtime behavior, so it's a real eventual dependency — but it's cleanly deferrable, and sequencing it keeps slice 1 dependency-free and protects the near-term container goal. Everything it buys you (t-axis source, STAC IDs as EDR collectionIds, a shorter B→C distance) is captured as the documented promotion path (Sections 7.6, 5-Option C, matrix). Happy to revisit if you'd rather commit to it sooner.
  2. First slice. Your summary said a "/cube-class Grid first slice," but your inline note argued against shipping /cube whose dimensionality we don't honor. I followed the inline: slice 1 is /bbox (2-D), /cube lands with z/t.

ADR scaffolding is in — ADR-0001 records the direction. Follow-on doc 02 / doc 05 rewrites and the titiler.core floor bump (#27) are noted as out-of-scope-for-this-PR.

chuckwondo and others added 3 commits June 29, 2026 09:21
Add docs/adr/ -- a README (numbering, when-to-write rules) and a MADR-lite template -- plus ADR-0001 recording the chosen direction: Option B (EDR-vocabulary surface) delivered as a dedicated titiler.core BaseFactory subclass, an honest 2-D /bbox first slice with /cube deferred until z/t is backed, and a resolver seam with titiler-stacapi as one optional temporal backing. docs/07-api-design-alternatives.md remains the supporting study.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Incorporate reviewer feedback (Emmanuel Mathot, Vincent Sarago) on the
CovJSON HTTP API direction:

- Mechanism: deliver the surface as a dedicated titiler.core BaseFactory
  subclass rather than a FactoryExtension grafted onto TilerFactory
  (Section 2.2), with reuse at the dependency-injector level, not route
  inheritance.
- First slice: an honest 2-D /bbox Grid route; defer the EDR /cube verb
  until a real z/t axis backs it (Section 7.6), instead of shipping a
  hypercube verb whose dimensionality we cannot honor.
- Interop: separate parameter-level (Option B, free) from path-level
  (collection-scoped / Option C) interoperability throughout (Section 3,
  matrix), and caveat the "rename-free" claim to the one planned
  /bbox -> /cube transition.
- Aggregation: disentangle into three operations -- full-resolution
  extraction, reduced-resolution Grid downsampling (first-class, not
  statistics), and statistical reduction -- and subsume the bespoke
  /overview endpoint; name vector point-cloud binning as a separate
  pipeline / future extension (Section 7.4).
- Adopt EDR parameter-name as a band_names alias from day one; make the
  mount prefix a factory setting via router_prefix (Sections 7.6, 7.7).
- Record the resolver seam (url= today, titiler-stacapi/xarray as optional
  temporal backings) and the titiler.core dependency floor / issue #27
  prerequisite (Sections 7.6, 7.8); reframe the ImageType selection wrinkle
  (Section 2.3); record Q1 = "maybe" -> Option B.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the full ADR lifecycle (Proposed, Accepted, Rejected, Deprecated,
Superseded) in the template and README, and number ADRs in order of creation
rather than acceptance so a Proposed ADR can hold its number before ratification.
Mark ADR-0001 as Proposed while PR #26 is under review.

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

Copy link
Copy Markdown
Contributor Author

Thanks @emmanuelmathot and @vincentsarago. I've made adjustments to the proposal and also added an ADR. Please have another look and let me know if you have any further suggestions.

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.

3 participants