Skip to content

GeoZarr driver for EOPF CPM

eopf-geozarr is a driver for ESA's EOPF CPM (the eopf package). It adds a writer with the engine name geozarr to CPM's writer registry and a convert-geozarr command to the eopf CLI. With it, CPM converts a product straight to cloud-optimized GeoZarr: a Zarr v3 store with multiscale pyramids, native projections and the GeoZarr conventions.

Use the driver when you start from a native product (for example a .SAFE) or from any product that CPM can read. If you already have an EOPF Zarr product, the standalone converter does the same conversion without CPM.

Installation

The driver needs the cpm extra, which installs eopf 3.x. EOPF CPM supports Python 3.13 and later only.

pip install "eopf-geozarr[cpm]"
uv add "eopf-geozarr[cpm]"

Note

On Python 3.12 the cpm extra installs nothing. import eopf_geozarr.cpm.writer then raises an ImportError that explains how to install the driver.

Command line: eopf convert-geozarr

The driver registers convert-geozarr in the eopf CLI through the eopf.cli entry point, so it is listed in eopf --help.

# Sentinel-2 L2A SAFE to GeoZarr
eopf convert-geozarr S2B_MSIL2A_20250113T103309_N0511_R108_T32TLQ.SAFE out.zarr

# Sentinel-3 OLCI EFR, warped once onto a regular grid
eopf convert-geozarr S3A_OL_1_EFR____….SEN3 out.zarr --output-grid EPSG:4326

# Write to S3: convert locally, then upload
eopf convert-geozarr S2B_MSIL2A_….SAFE s3://bucket/path/out.zarr --stage-output
Option Pipelines Description
--spatial-chunk INTEGER all but Sentinel-3 OLCI Spatial chunk size. Default: 256 (Sentinel-2), 4096 (generic), 1024 (generic rechunker). Sentinel-3 OLCI accepts it but does not apply it yet.
--enable-sharding all but Sentinel-3 OLCI Enable Zarr v3 sharding. Sentinel-3 OLCI accepts it but does not apply it yet.
--scale-offset-codec Sentinel-2, generic rechunker Pack reflectance (or, on the generic rechunker, every packed variable) with the Zarr scale_offset + cast_value codecs instead of the default ESA layout (CF scale_factor/add_offset/_FillValue). See Encoding.
--output-grid TEXT Sentinel-3 OLCI native (default) keeps the instrument swath geometry. A CRS string (for example EPSG:4326) warps the swath onto a regular grid.
--min-dimension INTEGER Sentinel-3 OLCI, generic Minimum dimension of the coarsest overview level. Default: 256.
--groups TEXT generic DataTree group to convert. Repeat the option for more groups. Required on the generic pipeline.
--crs-groups TEXT generic Group that gets CRS information added. Repeatable.
--gcp-group TEXT generic Group that holds the ground control points (Sentinel-1).
--no-s2-optimized — Force the generic pipeline for Sentinel-2 inputs.
--no-s3-olci-optimized — Do not use the OLCI pipeline for Sentinel-3 OLCI inputs; fall back to Sentinel-2 or generic detection.
--generic-rechunker — Send products other than Sentinel-2 and Sentinel-3 OLCI (for example Sentinel-1 SLC) to the generic rechunker. Sentinel-2 and OLCI products still go to their optimized pipelines. Cannot be combined with --no-s2-optimized or --no-s3-olci-optimized.
--stage-source — Download the source product to a local temporary folder before converting.
--stage-output — Write locally first, then upload to the target path. Required for S3 targets.

Python

Importing eopf_geozarr.cpm.writer registers the geozarr engine. Select it by name: CPM's built-in cpm_zarr writer already claims the .zarr extension, so the driver is not chosen from the target path.

# test: skip (needs eopf-cpm and a source product)
import eopf_geozarr.cpm.writer  # registers the "geozarr" engine
from eopf.store.convert import convert

convert(
    "S2B_MSIL2A_20250113T103309_N0511_R108_T32TLQ.SAFE",
    "out.zarr",
    target_store_kwargs={"engine": "geozarr", "enable_sharding": True},
)
# test: skip (needs eopf-cpm and a source product)
import eopf_geozarr.cpm.writer  # registers the "geozarr" engine
from eopf.store import write_datatree

write_datatree(dtree, "out.zarr", engine="geozarr", spatial_chunk=1024)

Writer options

Pass these options in target_store_kwargs (with convert) or as keyword arguments (with write_datatree). Unknown options raise NotImplementedError.

Option Default Description
mode "w" "w" replaces an existing store; "w-" fails if the target exists.
s2_optimized None True forces the Sentinel-2 pipeline, False forces the generic pipeline, None detects the product.
s3_olci_optimized None True forces the Sentinel-3 OLCI pipeline, False falls back to Sentinel-2 or generic detection, None detects the product.
generic_rechunker None True sends products other than Sentinel-2 and Sentinel-3 OLCI to the generic rechunker; Sentinel-2 and OLCI products still go to their optimized pipelines, so it cannot be combined with s2_optimized=False or s3_olci_optimized=False. Only one of s2_optimized, s3_olci_optimized and generic_rechunker may be True.
spatial_chunk per pipeline 256 (Sentinel-2), 4096 (generic), 1024 (generic rechunker). Sentinel-3 OLCI: accepted, not applied yet.
enable_sharding False Enable Zarr v3 sharding. Sentinel-3 OLCI: accepted, not applied yet.
scale_offset_codec False Sentinel-2 and generic rechunker: True packs reflectance (or every packed variable) with the Zarr scale_offset + cast_value codecs. False keeps the ESA layout with CF scale attributes.
compression_level 3 Sentinel-2 and generic rechunker: Blosc zstd compression level. Sentinel-3 OLCI: accepted, not applied yet.
validate_output False Sentinel-2: validate the output after writing.
output_grid "native" Sentinel-3 OLCI: native or a CRS string to warp onto.
min_dimension 256 Sentinel-3 OLCI and generic: minimum dimension of the coarsest overview level.
groups — Generic: DataTree groups to convert (required).
crs_groups None Generic: groups that get CRS information added.
gcp_group None Generic: group with ground control points (Sentinel-1).
max_retries 3 Generic: retries for network operations.
keep_scale_offset — Deprecated. keep_scale_offset=True is the default ESA layout; keep_scale_offset=False is scale_offset_codec=True.

Product routing

The driver selects a pipeline from the product type that CPM declares in stac_discovery.properties["product:type"]:

product:type Pipeline
S02MSIL1C, S02MSIL2A Sentinel-2 optimized
S03OLCEFR, S03OLCERR Sentinel-3 OLCI optimized
anything else generic (needs groups)
anything else, with generic_rechunker=True generic rechunker: every data group is rechunked (and sharded with enable_sharding) and written without multiscales

When a product has no product:type, the driver checks its structure: a measurements/reflectance group with r10m, r20m and r60m is Sentinel-2, and an oa01_radiance variable under measurements is Sentinel-3 OLCI. Sentinel-2 is checked first, then Sentinel-3 OLCI.

Limitations

  • The target must be a local path. For S3, use --stage-output (stage_target=True in Python). S3 credentials come from the environment.
  • The driver writes Zarr format 3 only, with consolidated metadata, and writes synchronously (compute=False is not supported).
  • Select the driver by name (engine="geozarr"); it is not discovered from the target extension.

See also