Convention Composition¶
The proj: convention focuses solely on CRS definitions. For complete georeferencing, it is composed with the spatial: convention (affine transforms, bounding boxes) and the multiscales convention (multi-resolution pyramids).
This notebook demonstrates how these three conventions work together.
Prerequisites: The proj: Convention
import json
from geozarr_toolkit import (
MultiscalesConventionMetadata,
ProjConventionMetadata,
SpatialConventionMetadata,
create_multiscales_layout,
create_proj_attrs,
create_spatial_attrs,
create_zarr_conventions,
)
Composition with the spatial: Convention¶
The proj: convention focuses solely on CRS definitions. For complete georeferencing, it is typically composed with the spatial: convention, which defines how to transform between pixel coordinates and CRS coordinates.
| Convention | Responsibility |
|---|---|
proj: |
What coordinate system (CRS definition) |
spatial: |
How to transform (affine matrix, bounding box, dimensions) |
For our Sentinel-2 scene, the TCI band has 10 m pixels with an affine transform of Affine(10.0, 0.0, 300000.0, 0.0, -10.0, 4100040.0) — matching the output shown in the async-geotiff example. The spatial:transform uses the same Rasterio/Affine coefficient ordering [a, b, c, d, e, f]:
a= 10.0: pixel width (10 m east per column)b= 0.0: no row rotationc= 300000.0: easting of the upper-left cornerd= 0.0: no column rotatione= -10.0: pixel height (10 m south per row)f= 4100040.0: northing of the upper-left corner
# Sentinel-2 TCI band: 10m resolution, 10980x10980 pixels
attrs = create_proj_attrs(code="EPSG:32612")
attrs.update(
create_spatial_attrs(
dimensions=["Y", "X"],
transform=[10.0, 0.0, 300000.0, 0.0, -10.0, 4100040.0],
shape=[10980, 10980],
bbox=[300000.0, 3990240.0, 409800.0, 4100040.0],
)
)
attrs["zarr_conventions"] = create_zarr_conventions(
ProjConventionMetadata(),
SpatialConventionMetadata(),
)
print(json.dumps(attrs, indent=2))
{
"proj:code": "EPSG:32612",
"spatial:dimensions": [
"Y",
"X"
],
"spatial:bbox": [
300000.0,
3990240.0,
409800.0,
4100040.0
],
"spatial:transform_type": "affine",
"spatial:transform": [
10.0,
0.0,
300000.0,
0.0,
-10.0,
4100040.0
],
"spatial:shape": [
10980,
10980
],
"spatial:registration": "pixel",
"zarr_conventions": [
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
"schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
},
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
"name": "spatial:",
"description": "Spatial coordinate and transformation information"
}
]
}
Composition with Multiscales¶
Sentinel-2 bands are acquired at three native resolutions — 10 m, 20 m, and 60 m — making it a natural multi-resolution dataset. When the proj:, spatial:, and multiscales conventions are composed together:
proj:codeis defined once at the group level and applies to all resolution levelsspatial:dimensionsandspatial:bboxare shared across all levels (same geographic extent)- Each resolution level has its own
spatial:shapeandspatial:transformreflecting its pixel size - The multiscales
transform.scaledescribes the resampling relationship between levels, not the geospatial coordinate transformation
# Sentinel-2 multi-resolution group: 10m, 20m, and 60m bands
# CRS and bounding box are shared; shape and transform vary per resolution.
attrs = create_proj_attrs(code="EPSG:32612")
attrs.update(
create_spatial_attrs(
dimensions=["Y", "X"],
bbox=[300000.0, 3990240.0, 409800.0, 4100040.0],
)
)
attrs.update(
create_multiscales_layout(
[
{
"asset": "r10m",
"transform": {"scale": [1.0, 1.0], "translation": [0.0, 0.0]},
},
{
"asset": "r20m",
"derived_from": "r10m",
"transform": {"scale": [2.0, 2.0], "translation": [0.0, 0.0]},
},
{
"asset": "r60m",
"derived_from": "r10m",
"transform": {"scale": [6.0, 6.0], "translation": [0.0, 0.0]},
},
]
)
)
attrs["zarr_conventions"] = create_zarr_conventions(
MultiscalesConventionMetadata(),
ProjConventionMetadata(),
SpatialConventionMetadata(),
)
print(json.dumps(attrs, indent=2))
{
"proj:code": "EPSG:32612",
"spatial:dimensions": [
"Y",
"X"
],
"spatial:bbox": [
300000.0,
3990240.0,
409800.0,
4100040.0
],
"spatial:transform_type": "affine",
"spatial:registration": "pixel",
"multiscales": {
"layout": [
{
"asset": "r10m",
"transform": {
"scale": [
1.0,
1.0
],
"translation": [
0.0,
0.0
]
}
},
{
"asset": "r20m",
"derived_from": "r10m",
"transform": {
"scale": [
2.0,
2.0
],
"translation": [
0.0,
0.0
]
}
},
{
"asset": "r60m",
"derived_from": "r10m",
"transform": {
"scale": [
6.0,
6.0
],
"translation": [
0.0,
0.0
]
}
}
]
},
"zarr_conventions": [
{
"uuid": "d35379db-88df-4056-af3a-620245f8e347",
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/multiscales/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-conventions/multiscales/blob/v1/README.md",
"name": "multiscales",
"description": "Multiscale layout of zarr datasets"
},
{
"uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f",
"schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md",
"name": "proj:",
"description": "Coordinate reference system information for geospatial data"
},
{
"uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4",
"schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json",
"spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md",
"name": "spatial:",
"description": "Spatial coordinate and transformation information"
}
]
}
In this example, the 10 m level is the base (r10m). The 20 m level has scale: [2.0, 2.0] meaning each pixel covers 2x the area of the base, and 60 m has scale: [6.0, 6.0]. The actual geospatial coordinates are determined by each level's spatial:transform, not by the multiscales scale factors.
Convention Responsibilities¶
Each convention has a focused scope:
| Convention | Namespace | Responsibility |
|---|---|---|
| proj: | proj: |
What coordinate system (CRS definition) |
| spatial: | spatial: |
How to transform (affine matrix, bbox, dimensions) |
| multiscales | multiscales |
Which resolution levels and their relationships |
This separation means you can use proj: alone for CRS-only metadata, add spatial: when you need coordinate transforms, and layer on multiscales for pyramid structures — all without modifying the other conventions.
Summary¶
- proj: + spatial: provides complete georeferencing (CRS + pixel-to-coordinate transforms)
- proj: + spatial: + multiscales adds multi-resolution support
- proj: is defined once at the group level; spatial: properties like
shapeandtransformvary per resolution level - multiscales
transform.scaledescribes resampling relationships, not geospatial coordinates
Next: COG to Zarr for a real-world end-to-end example