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: add store-root spatial:bbox and tighten minispec requirements (EOPF-Explorer#164)
* feat: add store-root spatial:bbox and tighten minispec requirements
Introduces a GeoZarr "Store Root" layer in the minispec so clients can read
a summary footprint without walking into child groups, and tightens the
multiscale profile so `spatial:bbox` at the root and `spatial:transform` +
`spatial:shape` on every layout entry are mandatory.
Adds a new `geozarr.store` pydantic module (`GeoZarrStoreAttrs`,
`GeoZarrMultiscaleGroupAttrs`, `GeoZarr`) enforcing the tightened profile,
and updates the S2 converter to union child-group bboxes into an EPSG:4326
footprint written at the store root.
ClosesEOPF-Explorer#156. Addresses the clear parts of EOPF-Explorer#163; the array-level and
non-multiscale-group parts of that issue need further clarification.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: update GeoZarr store model and optimize S2 converter for improved bbox handling
* Addresses review feedback on EOPF-Explorer#164:
- Adds a "GeoZarr Hierarchy & Identification" subsection to the Store Root
section of the minispec, codifying d-v-b's proposed rules: single root,
root prefix ends with `.zarr`, suffix occurs at most once in the
hierarchy, and explicit terminal-path conditions.
- Promotes the store-root CRS to mandatory: at least one of `proj:code`,
`proj:wkt2`, `proj:projjson` MUST be set; there is no implicit EPSG:4326
default. Per @vincentsarago's review.
- Promotes `zarr_conventions` declaration at the store root from
RECOMMENDED to required.
- Converter now writes `proj:code: "EPSG:4326"` at the root alongside
`spatial:bbox`.
- Pydantic `GeoZarrStoreAttrs` enforces the new CRS-required rule.
Cross-links the new hierarchy + spatial:extent follow-ups upstream:
zarr-developers/geozarr-spec#132 (hierarchy & root identification, also
addresses EOPF-Explorer#124 URL parsing) and EOPF-Explorer#133 (STAC-style spatial:extent).
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/geozarr-minispec.md
+106-5Lines changed: 106 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,6 +49,107 @@ and their attributes before moving to higher-level structures like groups and th
49
49
50
50
This specification targets **Zarr V3** exclusively. The geo-proj, spatial, and multiscales conventions are all Zarr V3.
51
51
52
+
## Store Root
53
+
54
+
A GeoZarr Store is the outermost Zarr group of a dataset — the group located at the root of the Zarr store (e.g. `my_dataset.zarr/zarr.json`). It sits above any [Dataset](#dataset) or [Multiscale Dataset](#multiscale-dataset) nodes in the hierarchy and is intended to give clients a single place to read a **summary spatial footprint** for the whole store without having to walk into child groups.
55
+
56
+
> [!Note]
57
+
> The hierarchy and identification rules below are local choices made for this implementation. They are subject to revision once the OGC GeoZarr Standards Working Group decides on canonical wording. A follow-up to surface these rules upstream is tracked in [zarr-developers/geozarr-spec#132](https://github.com/zarr-developers/geozarr-spec/issues/132). See also [GeoZarr Specification Contribution](geozarr-specification-contribution.md).
58
+
59
+
### Hierarchy & Identification
60
+
61
+
A GeoZarr hierarchy has these properties:
62
+
63
+
-**Single root**: every GeoZarr store has exactly one root group.
64
+
-**Root naming**: the root group is stored under a prefix ending with `.zarr` (e.g. `my_dataset.zarr/`). Clients given any sub-path inside a GeoZarr store MAY recover the root by walking up the path until they hit a segment ending in `.zarr`.
65
+
-**No nested stores**: the `.zarr` suffix SHOULD occur exactly once in any path within the hierarchy. Nesting GeoZarr stores (e.g. `a.zarr/b/c.zarr/`) is strongly discouraged because it breaks the root-recovery rule above.
66
+
-**Terminal paths**: a path `p` within the hierarchy is *terminal* (a leaf) when any of the following holds:
67
+
- an array node is reached at `p`,
68
+
- a group with no members is reached at `p`,
69
+
-`p` ends with a `zarr.json` file.
70
+
71
+
### Attributes
72
+
73
+
The store root carries a top-level spatial footprint. This is **informative**: it helps clients place the store spatially (STAC-style discovery) but is not authoritative — per-Dataset / per-Multiscale Dataset `spatial:` and `proj:` attributes remain the source of truth for reading individual variables.
74
+
75
+
| key | type | required | notes |
76
+
| --- | ---- | -------- | ----- |
77
+
|`zarr_conventions`| array | yes | MUST declare the `spatial:` and `proj:` conventions used at the root |
78
+
|`spatial:bbox`| number[4]| yes | Bounding box `[xmin, ymin, xmax, ymax]` covering the union of all spatial assets in the store, expressed in the CRS named by `proj:code` (or `proj:wkt2` / `proj:projjson`) |
79
+
|`proj:code`| string | conditional*| Authority:code identifier of the bbox CRS, e.g. `"EPSG:4326"`|
80
+
|`proj:wkt2`| string | conditional*| WKT2 representation of the bbox CRS |
81
+
|`proj:projjson`| object | conditional*| PROJJSON representation of the bbox CRS |
82
+
83
+
\* At least one of `proj:code`, `proj:wkt2`, `proj:projjson` MUST be provided. Use `"EPSG:4326"` (longitude/latitude, degrees) when no other CRS is meaningful — the store-root CRS is always declared explicitly; there is no implicit default.
84
+
85
+
> [!Note]
86
+
> The store-root `spatial:bbox` is **a summary** — e.g. the union of footprints from all nested Datasets / Multiscale Datasets. It is not a substitute for the per-group `spatial:bbox` defined by the [spatial convention](https://github.com/zarr-conventions/spatial).
87
+
88
+
> [!Note]
89
+
> A future extension may carry a list-of-bboxes at the root (one global + one per child group), STAC-style. This is tracked in [zarr-developers/geozarr-spec#133](https://github.com/zarr-developers/geozarr-spec/issues/133).
@@ -244,7 +345,7 @@ The `zarr_conventions` array at the Multiscale Dataset's root group MUST declare
244
345
|`multiscales`|[MultiscalesMetadata](#multiscalesmetadata)| yes | Pyramid layout for this group |
245
346
|`proj:code`| string | conditional*| CRS for all resolution levels (group-level inheritance) |
246
347
|`spatial:dimensions`| string[]| yes | Names of spatial dimensions, e.g. `["Y", "X"]`|
247
-
|`spatial:bbox`| number[]|no| Overall bounding box in the CRS coordinate space |
348
+
|`spatial:bbox`| number[4]|yes| Overall bounding box `[xmin, ymin, xmax, ymax]`in the CRS coordinate space, covering every level in the pyramid|
248
349
249
350
\* At least one of `proj:code`, `proj:wkt2`, or `proj:projjson` must be provided for geospatial datasets.
250
351
@@ -267,8 +368,8 @@ Each object in the `layout` array represents one resolution level:
267
368
|`derived_from`| string | no | Path to the source level from which this level was generated. Required to define the transform |
268
369
|`transform`|[TransformObject](#transformobject)| conditional | Required when `derived_from` is present. Describes the relative coordinate transformation from the source level |
269
370
|`resampling_method`| string | no | Per-level override of the default resampling method |
270
-
|`spatial:shape`| integer[]|no| Shape of the spatial dimensions at this level `[height, width]`|
271
-
|`spatial:transform`| number[6]|no|Affine transform coefficients for this level, in Rasterio/Affine ordering `[a, b, c, d, e, f]`|
371
+
|`spatial:shape`| integer[2]|yes| Shape of the spatial dimensions at this level `[height, width]`|
372
+
|`spatial:transform`| number[6]|yes|Absolute affine transform coefficients for this level, in Rasterio/Affine ordering `[a, b, c, d, e, f]`|
272
373
273
374
#### TransformObject
274
375
@@ -496,8 +597,8 @@ Key aspects:
496
597
|`derived_from`| string | no | Relative path to the source level |
497
598
|`transform`| TransformObject | conditional | Required when `derived_from` is present |
498
599
|`resampling_method`| string | no | Per-level resampling override |
499
-
|`spatial:shape`| integer[]|no| Spatial dimensions shape `[height, width]` at this level |
500
-
|`spatial:transform`| number[6]|no| Absolute affine transform for this level (Rasterio/Affine ordering) |
600
+
|`spatial:shape`| integer[2]|yes| Spatial dimensions shape `[height, width]` at this level |
601
+
|`spatial:transform`| number[6]|yes| Absolute affine transform for this level (Rasterio/Affine ordering) |
**Impact:** This approach prevents chunk overlap issues with Dask while optimizing for actual data dimensions rather than arbitrary tile sizes, significantly improving performance.
**Problem:** The current draft does not define how a GeoZarr store is bounded as a set of paths. The base Zarr spec treats the `.zarr` suffix as advisory only, so two implementations can disagree on whether nested stores (e.g. `a.zarr/b/c.zarr/`) are allowed, how a client given a deep URL recovers the root, and where a traversal should stop.
56
+
57
+
**Our Solution:** We adopted an explicit set of rules in our [Store Root section](geozarr-minispec.md#hierarchy--identification): single root, root prefix ends with `.zarr`, the suffix occurs at most once in the hierarchy, and an enumerated list of terminal-path conditions.
58
+
59
+
**Impact:** Clients reading a sub-path like `https://example.org/foo.zarr/measurements/reflectance/r10m` can now reliably recover the store root and read its summary `spatial:bbox` + `proj:code`. This also resolves a recurring URL-parsing question raised in [EOPF-Explorer/data-model#124](https://github.com/EOPF-Explorer/data-model/issues/124) without needing fragment-based URL workarounds.
**Issue:**[EOPF-Explorer/data-model#156](https://github.com/EOPF-Explorer/data-model/issues/156) — to be surfaced upstream once [#132](https://github.com/zarr-developers/geozarr-spec/issues/132) lands.
64
+
65
+
**Problem:** Without a top-level summary footprint, clients have to walk into child groups to discover where a store sits geographically. That is expensive over network, and prevents STAC-style discovery patterns.
66
+
67
+
**Our Solution:** A mandatory `spatial:bbox` plus an explicit CRS (one of `proj:code`, `proj:wkt2`, or `proj:projjson`) at the store root, defined in the [Store Root section](geozarr-minispec.md#store-root). The CRS is always declared explicitly — there is no implicit default.
68
+
69
+
**Impact:** A single read of the root `zarr.json` is enough for catalogues and viewers to place a store on a map, without disturbing per-group `spatial:` attributes which remain authoritative for individual variables.
70
+
71
+
### 5. STAC-style `spatial:extent` (multi-bbox at root)
**Problem:** A single union `spatial:bbox` at the store root loses per-asset footprint information. Stores that mix several Datasets / Multiscale Datasets cannot expose individual extents without forcing clients to walk every child group.
76
+
77
+
**Our Solution (proposed):** Mirror STAC's `extent.spatial.bbox` by introducing `spatial:extent` as a list of bboxes — first entry is the global union, subsequent entries are per-child extents. Suggested by @vincentsarago during review of #4 above; deferred until the upstream `spatial:` convention adopts it.
78
+
79
+
**Impact:** Lets clients fetch all per-child footprints in one request, aligning Zarr discovery with STAC conventions.
0 commit comments