Mixed-Extractor Custom Store#
This tutorial shows how to use CustomStoreBuilder to create
a single Zarr store that contains multiple named arrays, each backed by a
different extraction strategy. You will build a store with two arrays holding
the same 10 m u-wind field. One uses the standard Grib extractor for
efficient full-field access, the other uses GribJump. You then plot the field
as a global map.
Note
This tutorial uses ExtractorType.GribJump, which is built only when fdb is configured
with -DENABLE_ZARR_GRIBJUMP_EXTRACTOR=ON, off by default. On a build without it,
build() raises and names the flag. Check with
pychunked_data_view.has_gribjump_extractor, and see Optional: the GribJump Extractor.
When to use CustomStoreBuilder#
SimpleStoreBuilder always places a single array at the store
root. CustomStoreBuilder lifts that restriction: you can
register any number of arrays at named paths inside one store and open them
as a Zarr group.
This is useful when:
You want to expose multiple parameters or level types through one store object.
Different arrays in the store benefit from different extractors. For example, surface fields used for map visualisation work well with a
Gribextractor (one FDB retrieve returns the full field), while the same field accessed at individual grid points is better served byGribJump.
The MARS Request#
The example below retrieves the 10 m u-wind (param 165.128) from a
single ensemble member at analysis time:
_REQUEST = {
"class": "od",
"date": "20260818",
"expver": "0001",
"levtype": "sfc",
"domain": "g",
"stream": "enfo",
"type": "pf",
"number": [1], # one ensemble member
"param": ["165.128"], # 10 m u-wind
"step": [0], # analysis step
"time": ["00:00:00"],
}
With a single value on every axis the resulting array has shape
(1, 1, 1, 1, N) where N is the number of grid points in the GRIB
field.
Building the Custom Store#
import zarr
import numpy as np
import matplotlib.pyplot as plt
zarr.config.set({"async.concurrency": 1, "threading.max_workers": 1})
from z3fdb import AxisDefinition, Chunking, ExtractorType, CustomStoreBuilder
_AXES = [
AxisDefinition(keys=["date", "time"], chunking=Chunking.SINGLE_VALUE),
AxisDefinition(keys=["number"], chunking=Chunking.SINGLE_VALUE),
AxisDefinition(keys=["step"], chunking=Chunking.SINGLE_VALUE),
AxisDefinition(keys=["param"], chunking=Chunking.SINGLE_VALUE),
]
builder = CustomStoreBuilder()
# Array 1 - full-field Grib extraction: one FDB retrieve gives the complete field.
# Best choice when you need all grid points (e.g. for maps).
builder.add_part("grib/u10", _REQUEST, axes=_AXES, extractor=ExtractorType.Grib())
# Array 2 - GribJump extraction with one grid point per chunk.
# Best choice for sparse access (e.g. time series at a station).
builder.add_part("gribjump/u10", _REQUEST, axes=_AXES,
extractor=ExtractorType.GribJump(
field_chunking=Chunking.FixedSizeChunk(chunk_shape=1)
))
store = builder.build()
add_part("grib/u10", ...)Registers an array at the path
grib/u10inside the store. The path follows zarr conventions:/-separated segments, leading slash optional. Heregribbecomes an intermediate group andu10is the array name.ExtractorType.Grib()Standard extraction: one FDB retrieve per chunk, returning a full GRIB field decoded to float32. With
SINGLE_VALUEchunking on every explicit axis, each chunk holds one complete field. That is ideal for reading all grid points at once.ExtractorType.GribJump(field_chunking=Chunking.FixedSizeChunk(chunk_shape=1))GribJump jumps to the bytes inside each GRIB message that correspond to the requested grid points, without decoding the rest.
FixedSizeChunk(1)makes every grid point its own chunk: readingarr[..., k]extracts exactly the value at grid pointk. This is expensive for full-field access but efficient for sparse access across many time steps.
Opening the Store#
Because the store contains multiple named arrays, open it as a group, not an array:
grp = zarr.open_group(store, mode="r")
# Access each array by its registered path
grib_arr = grp["grib/u10"]
gribjump_arr = grp["gribjump/u10"]
print("shape :", grib_arr.shape) # (1, 1, 1, 1, N)
print("chunks:", grib_arr.chunks) # (1, 1, 1, 1, N) - whole field per chunk
grp["grib/u10"][dt, member, step, param, grid_point]
^ ^ ^ ^ ^
| | | | grid point [implicit, size N]
| | | param [size 1]
| | step [size 1]
| member [size 1]
date x time [size 1]
No data is fetched from FDB until you index. Building and opening the store only validates the MARS request and determines the field layout.
Plotting the 10 m u-wind Map#
Read the full field from the Grib-backed array and plot it as a global
map. Z3FDB returns a flat 1-D float32 array of N grid-point values;
reshaping it to 2-D requires the latitude and longitude coordinates for your
grid. These can be obtained from the GRIB message metadata via eccodes
or cfgrib.
# Retrieve the full field - shape (1, 1, 1, 1, N), index to (N,)
u10_flat = grib_arr[0, 0, 0, 0, :]
# Obtain lat/lon coordinates for your grid.
# The example below assumes a regular 0.25 deg global grid (1440 x 721).
# Replace nlat, nlon, lats, lons with values matching your actual grid.
nlat, nlon = 721, 1440
lats = np.linspace(90, -90, nlat)
lons = np.linspace(0, 360, nlon, endpoint=False)
u10_2d = u10_flat.reshape(nlat, nlon)
fig, ax = plt.subplots(figsize=(12, 5))
img = ax.pcolormesh(lons, lats, u10_2d, cmap="RdBu_r", shading="auto")
fig.colorbar(img, ax=ax, label="10 m u-wind (m/s)")
ax.set_xlabel("Longitude (deg)")
ax.set_ylabel("Latitude (deg)")
ax.set_title(f"10 m u-wind, {_REQUEST['date']} {_REQUEST['time'][0]}, step 0, member 1")
plt.tight_layout()
plt.savefig("u10_map.png", dpi=150)
plt.show()
Note
For non-regular grids (e.g. the ECMWF O1280 reduced Gaussian grid) you
cannot reshape to a simple 2-D array. In that case retrieve the
(lat, lon) coordinates for each grid point from an eccodes-opened
sample field and pass them to matplotlib.pyplot.tricontourf().
Next Steps#
Ensemble Forecast Time Series with GribJump for using
GribJumpwithFixedSizeChunk(1)across a full ensemble and plot time series.Dimension Mapping and Data Model for the complete reference on axis mapping, chunking strategies, and multi-part views.