From 83900a9a720856e2be1b7e7217b3ec4da206e4a2 Mon Sep 17 00:00:00 2001 From: Emmanuel Mathot Date: Wed, 13 May 2026 23:18:13 +0200 Subject: [PATCH 1/4] Restrict spatial:dimensions to arrays; clarify dimension_names requirement - spatial:dimensions is now required on arrays and forbidden on groups (it has no unambiguous meaning at the group level since child arrays may have different dimensions). Closes #23. - Document that arrays using this convention must declare Zarr V3's dimension_names metadata field, and that spatial:dimensions entries match dimension_names by name (not by position). Closes #22, #17. - Drop the row-major ordering prescription from spatial:dimensions; matching is by name, so order is not significant. Closes #17. - Update examples to add dimension_names on arrays and remove spatial:dimensions from the multiscales group example. --- README.md | 35 ++++++++++++++++++++--------------- examples/multiscales.json | 1 - examples/proj.json | 1 + schema.json | 31 ++++++++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 03af12f..bcbe397 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ All properties use the `spatial:` namespace prefix and are placed at the root `a | Property | Type | Description | Required | Reference | | -------------------------- | ---------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------ | -| **spatial:dimensions** | `string[]` | Names of spatial dimensions (e.g., ["y", "x"]) | Yes | [spatial:dimensions](#spatialdimensions) | +| **spatial:dimensions** | `string[]` | Names of spatial dimensions (e.g., ["y", "x"]) | Arrays only (required on arrays, forbidden on groups) | [spatial:dimensions](#spatialdimensions) | | **spatial:bbox** | `number[]` | Bounding box in coordinate space | No | [spatial:bbox](#spatialbbox) | | **spatial:transform_type** | `string` | Type of coordinate transformation (default: "affine") | No | [spatial:transform_type](#spatialtransform_type) | | **spatial:transform** | `number[]` | Affine transformation coefficients | No | [spatial:transform](#spatialtransform) | @@ -75,17 +75,18 @@ Additional properties are allowed. #### spatial:dimensions -Names of spatial dimensions +Names of the array dimensions that are spatial. - **Type**: `string[]` -- **Required**: Yes +- **Required**: On arrays only. Forbidden on groups (see [Can I use spatial: at the group level?](#can-i-use-spatial-at-the-group-level)). -Identifies which dimensions in the Zarr array correspond to spatial axes. This is particularly useful when arrays have multiple dimensions (e.g., time, bands, y, x). +Identifies which of the array's dimensions correspond to spatial axes. This is particularly useful when arrays have non-spatial dimensions as well (e.g., time, bands, y, x). -For 2D spatial data, provide 2 elements in row-major order: `["y", "x"]` -For 3D spatial data, provide 3 elements: `["z", "y", "x"]` +Each entry in `spatial:dimensions` MUST match one of the names declared in the array's [`dimension_names`](https://github.com/zarr-developers/zarr-specs/blob/main/docs/v3/core/index.rst#dimension_names) metadata field (a top-level field of the Zarr V3 array metadata, not an attribute). Arrays using this convention MUST therefore declare `dimension_names`. -The dimension names must match the dimension names defined in the array's shape. +Matching is **by name, not by position**: the order of entries in `spatial:dimensions` is not significant for identifying which dimensions are spatial. Conventions that consume this list (e.g., for axis ordering in `spatial:transform` or `spatial:shape`) define their own ordering rules. + +For 2D spatial data, provide 2 entries, e.g. `["y", "x"]`. #### spatial:bbox @@ -268,6 +269,7 @@ For non-geospatial data or when CRS is not needed: { "zarr_format": 3, "node_type": "array", + "dimension_names": ["y", "x"], "attributes": { "zarr_conventions": [ { @@ -295,6 +297,7 @@ For a Digital Elevation Model using node registration (grid-registered): { "zarr_format": 3, "node_type": "array", + "dimension_names": ["y", "x"], "attributes": { "zarr_conventions": [ { @@ -326,6 +329,7 @@ For geospatial data, combine `spatial:` with `proj:` for complete coordinate inf { "zarr_format": 3, "node_type": "array", + "dimension_names": ["Y", "X"], "attributes": { "zarr_conventions": [ { @@ -410,7 +414,6 @@ The spatial: convention can extend multiscales layouts by adding spatial propert ] }, "proj:code": "EPSG:32633", - "spatial:dimensions": ["Y", "X"], "spatial:bbox": [500000.0, 4988000.0, 512000.0, 5000000.0] } ``` @@ -418,7 +421,8 @@ The spatial: convention can extend multiscales layouts by adding spatial propert In this example: - The group-level `proj:code` defines the CRS for all resolution levels -- The group-level `spatial:dimensions` and `spatial:bbox` apply to all resolution levels +- The group-level `spatial:bbox` applies to all resolution levels +- Each underlying asset array declares its own `dimension_names` and `spatial:dimensions` (forbidden at group level; see [Can I use spatial: at the group level?](#can-i-use-spatial-at-the-group-level)) - Each layout item has its own `spatial:shape` and `spatial:transform` specific to that resolution - The multiscales convention defines the relative transformations between levels via the `transform` object - This enables efficient storage of multi-resolution geospatial data with proper georeferencing at each level @@ -483,11 +487,13 @@ Yes! The `spatial:` convention is useful on its own for: ### Can I use spatial: at the group level? -Yes, `spatial:` properties can be defined at both group and array levels. When defined at the group level: +Most `spatial:` properties may be defined at the group level as shared defaults for direct child arrays that do not define their own values. `spatial:dimensions` is the exception: it is **required on arrays and forbidden on groups**, because a group can contain arrays with different dimensions and the property only has unambiguous meaning relative to a specific array's [`dimension_names`](https://github.com/zarr-developers/zarr-specs/blob/main/docs/v3/core/index.rst#dimension_names). + +When defined at the group level, the remaining properties: -- Properties apply to direct child arrays that don't define their own `spatial:` properties -- This is useful when multiple arrays share the same spatial coordinate system -- Arrays can define their own `spatial:` properties to override or supplement group-level definitions +- Apply to direct child arrays that don't define their own `spatial:` properties +- Are useful when multiple arrays share the same spatial coordinate system +- Can be overridden or supplemented by array-level definitions This is particularly useful with multiscales, where the CRS (`proj:`) is shared but spatial properties (`spatial:shape`, `spatial:transform`) vary per resolution level. @@ -496,9 +502,8 @@ This is particularly useful with multiscales, where the CRS (`proj:`) is shared When composing `spatial:` with a `proj:` convention: - Both conventions must be listed in `zarr_conventions` -- `spatial:dimensions` must be provided to identify which array dimensions are spatial +- `spatial:dimensions` must be provided on each array to identify which of the array's `dimension_names` are spatial - The coordinate values in `spatial:bbox` and `spatial:transform` should be in the coordinate system defined by `proj:` -- For geospatial compatibility, follow standard axis ordering conventions (typically Y, X or Z, Y, X) ### How does the convention support different transformation types? diff --git a/examples/multiscales.json b/examples/multiscales.json index 4cb53f8..ced1d0e 100644 --- a/examples/multiscales.json +++ b/examples/multiscales.json @@ -59,7 +59,6 @@ ] }, "proj:code": "EPSG:32633", - "spatial:dimensions": ["Y", "X"], "spatial:bbox": [500000.0, 4988000.0, 512000.0, 5000000.0] } } diff --git a/examples/proj.json b/examples/proj.json index f580753..1e5998f 100644 --- a/examples/proj.json +++ b/examples/proj.json @@ -1,6 +1,7 @@ { "zarr_format": 3, "node_type": "array", + "dimension_names": ["Y", "X"], "attributes": { "zarr_conventions": [ { diff --git a/schema.json b/schema.json index 41f5a80..4cc1bee 100644 --- a/schema.json +++ b/schema.json @@ -32,6 +32,36 @@ }, "required": ["zarr_format", "node_type", "attributes"], "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { "node_type": { "const": "array" } }, + "required": ["node_type"] + }, + "then": { + "properties": { + "attributes": { + "type": "object", + "required": ["spatial:dimensions"] + } + } + } + }, + { + "if": { + "properties": { "node_type": { "const": "group" } }, + "required": ["node_type"] + }, + "then": { + "properties": { + "attributes": { + "type": "object", + "not": { "required": ["spatial:dimensions"] } + } + } + } + } + ], "$defs": { "conventionMetadata": { "type": "object", @@ -219,7 +249,6 @@ "additionalProperties": true } }, - "required": ["spatial:dimensions"], "additionalProperties": true } } From 08cfef19665d272d2c08c10aab34d93d21b6c9d4 Mon Sep 17 00:00:00 2001 From: Emmanuel Mathot Date: Sun, 17 May 2026 21:36:09 +0200 Subject: [PATCH 2/4] Update README.md Co-authored-by: Patrick --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abc31a5..5a5e35c 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Additional properties are allowed. #### spatial:dimensions -Names of the array dimensions that are spatial. +Names of the array dimensions that have spatial coordinates. - **Type**: `string[2]` - **Required**: On arrays only. Forbidden on groups (see [Can I use spatial: at the group level?](#can-i-use-spatial-at-the-group-level)). From a44c6edbfb365b0e3898e2271d1cae380c23ac03 Mon Sep 17 00:00:00 2001 From: Emmanuel Mathot Date: Sun, 17 May 2026 22:06:34 +0200 Subject: [PATCH 3/4] Drop #23 changes from this PR; keep only #22/#17 scope Per @maxrjones review on #25: the array-only/group-forbidden restriction (issue #23) is independent from the dimension_names clarification (#22) and should land in its own PR. Reverts: - schema.json: removes the allOf conditional and restores unconditional 'required: spatial:dimensions' in spatialAttributes. - README: properties table and section header back to 'Required: Yes'; FAQ 'Can I use spatial: at the group level?' restored to original prose; multiscales example and bullets restore group-level spatial:dimensions. - examples/multiscales.json: re-adds group-level spatial:dimensions. Kept (still #22 / #17): - spatial:dimensions entries must match the array's dimension_names (link to zarr-specs); matching is by name, not position. - Row-major ordering prescription removed. - dimension_names added to examples/proj.json and to the README array examples. --- README.md | 18 ++++++++---------- examples/multiscales.json | 1 + schema.json | 31 +------------------------------ 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 5a5e35c..ddfa495 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ All properties use the `spatial:` namespace prefix and are placed at the root `a | Property | Type | Description | Required | Reference | | -------------------------- | ----------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------ | -| **spatial:dimensions** | `string[2]` | Names of the two X/Y spatial dimensions (e.g., ["y", "x"]) | Arrays only (required on arrays, forbidden on groups) | [spatial:dimensions](#spatialdimensions) | +| **spatial:dimensions** | `string[2]` | Names of the two X/Y spatial dimensions (e.g., ["y", "x"]) | Yes | [spatial:dimensions](#spatialdimensions) | | **spatial:bbox** | `number[4]` | 2D bounding box [xmin, ymin, xmax, ymax] | No | [spatial:bbox](#spatialbbox) | | **spatial:transform_type** | `string` | Type of coordinate transformation (default: "affine") | No | [spatial:transform_type](#spatialtransform_type) | | **spatial:transform** | `number[6]` | 2D affine transformation coefficients | No | [spatial:transform](#spatialtransform) | @@ -82,7 +82,7 @@ Additional properties are allowed. Names of the array dimensions that have spatial coordinates. - **Type**: `string[2]` -- **Required**: On arrays only. Forbidden on groups (see [Can I use spatial: at the group level?](#can-i-use-spatial-at-the-group-level)). +- **Required**: Yes Identifies which of the array's dimensions correspond to spatial axes. This is particularly useful when arrays have non-spatial dimensions as well (e.g., time, bands). @@ -412,6 +412,7 @@ The spatial: convention can extend multiscales layouts by adding spatial propert ] }, "proj:code": "EPSG:32633", + "spatial:dimensions": ["Y", "X"], "spatial:bbox": [500000.0, 4988000.0, 512000.0, 5000000.0] } ``` @@ -419,8 +420,7 @@ The spatial: convention can extend multiscales layouts by adding spatial propert In this example: - The group-level `proj:code` defines the CRS for all resolution levels -- The group-level `spatial:bbox` applies to all resolution levels -- Each underlying asset array declares its own `dimension_names` and `spatial:dimensions` (forbidden at group level; see [Can I use spatial: at the group level?](#can-i-use-spatial-at-the-group-level)) +- The group-level `spatial:dimensions` and `spatial:bbox` apply to all resolution levels - Each layout item has its own `spatial:shape` and `spatial:transform` specific to that resolution - The multiscales convention defines the relative transformations between levels via the `transform` object - This enables efficient storage of multi-resolution geospatial data with proper georeferencing at each level @@ -496,13 +496,11 @@ In those cases the explicit coordinate arrays carry the values; `spatial:` still ### Can I use spatial: at the group level? -Most `spatial:` properties may be defined at the group level as shared defaults for direct child arrays that do not define their own values. `spatial:dimensions` is the exception: it is **required on arrays and forbidden on groups**, because a group can contain arrays with different dimensions and the property only has unambiguous meaning relative to a specific array's [`dimension_names`](https://github.com/zarr-developers/zarr-specs/blob/main/docs/v3/core/index.rst#dimension_names). +Yes, `spatial:` properties can be defined at both group and array levels. When defined at the group level: -When defined at the group level, the remaining properties: - -- Apply to direct child arrays that don't define their own `spatial:` properties -- Are useful when multiple arrays share the same spatial coordinate system -- Can be overridden or supplemented by array-level definitions +- Properties apply to direct child arrays that don't define their own `spatial:` properties +- This is useful when multiple arrays share the same spatial coordinate system +- Arrays can define their own `spatial:` properties to override or supplement group-level definitions This is particularly useful with multiscales, where the CRS (`proj:`) is shared but spatial properties (`spatial:shape`, `spatial:transform`) vary per resolution level. diff --git a/examples/multiscales.json b/examples/multiscales.json index cf344e2..bc706d4 100644 --- a/examples/multiscales.json +++ b/examples/multiscales.json @@ -59,6 +59,7 @@ ] }, "proj:code": "EPSG:32633", + "spatial:dimensions": ["Y", "X"], "spatial:bbox": [500000.0, 4988000.0, 512000.0, 5000000.0] } } diff --git a/schema.json b/schema.json index ff39929..9e30b38 100644 --- a/schema.json +++ b/schema.json @@ -32,36 +32,6 @@ }, "required": ["zarr_format", "node_type", "attributes"], "additionalProperties": true, - "allOf": [ - { - "if": { - "properties": { "node_type": { "const": "array" } }, - "required": ["node_type"] - }, - "then": { - "properties": { - "attributes": { - "type": "object", - "required": ["spatial:dimensions"] - } - } - } - }, - { - "if": { - "properties": { "node_type": { "const": "group" } }, - "required": ["node_type"] - }, - "then": { - "properties": { - "attributes": { - "type": "object", - "not": { "required": ["spatial:dimensions"] } - } - } - } - } - ], "$defs": { "conventionMetadata": { "type": "object", @@ -193,6 +163,7 @@ "additionalProperties": true } }, + "required": ["spatial:dimensions"], "additionalProperties": true } } From 7e731658a6ac232a9edea05b54bf5456f2b38651 Mon Sep 17 00:00:00 2001 From: Emmanuel Mathot Date: Sun, 17 May 2026 22:08:20 +0200 Subject: [PATCH 4/4] Keep array-required check; drop only the group-forbidden rule (#23) The 'spatial:dimensions is required on arrays' constraint stays as part of #22 (arrays must declare the spatial axes that index into their dimension_names). Only the 'forbidden on groups' rule from #23 is removed from this PR. Schema: re-add the array-only conditional via allOf/if/then on node_type, but without the group-side 'not required' branch. Groups can still carry spatial:dimensions as a default for child arrays. README: properties table 'On arrays'; spec section 'Required: Yes on arrays; optional on groups (where it acts as a default for child arrays).' --- README.md | 4 ++-- schema.json | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ddfa495..728e2ff 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ All properties use the `spatial:` namespace prefix and are placed at the root `a | Property | Type | Description | Required | Reference | | -------------------------- | ----------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------ | -| **spatial:dimensions** | `string[2]` | Names of the two X/Y spatial dimensions (e.g., ["y", "x"]) | Yes | [spatial:dimensions](#spatialdimensions) | +| **spatial:dimensions** | `string[2]` | Names of the two X/Y spatial dimensions (e.g., ["y", "x"]) | On arrays | [spatial:dimensions](#spatialdimensions) | | **spatial:bbox** | `number[4]` | 2D bounding box [xmin, ymin, xmax, ymax] | No | [spatial:bbox](#spatialbbox) | | **spatial:transform_type** | `string` | Type of coordinate transformation (default: "affine") | No | [spatial:transform_type](#spatialtransform_type) | | **spatial:transform** | `number[6]` | 2D affine transformation coefficients | No | [spatial:transform](#spatialtransform) | @@ -82,7 +82,7 @@ Additional properties are allowed. Names of the array dimensions that have spatial coordinates. - **Type**: `string[2]` -- **Required**: Yes +- **Required**: Yes on arrays; optional on groups (where it acts as a default for child arrays). Identifies which of the array's dimensions correspond to spatial axes. This is particularly useful when arrays have non-spatial dimensions as well (e.g., time, bands). diff --git a/schema.json b/schema.json index 9e30b38..a4b6282 100644 --- a/schema.json +++ b/schema.json @@ -32,6 +32,22 @@ }, "required": ["zarr_format", "node_type", "attributes"], "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { "node_type": { "const": "array" } }, + "required": ["node_type"] + }, + "then": { + "properties": { + "attributes": { + "type": "object", + "required": ["spatial:dimensions"] + } + } + } + } + ], "$defs": { "conventionMetadata": { "type": "object", @@ -163,7 +179,6 @@ "additionalProperties": true } }, - "required": ["spatial:dimensions"], "additionalProperties": true } }