6. Spatial Indexing¶
6.1 Spatial Index Definition¶
A Zarr Vectors store’s spatial index is a regular N-dimensional grid pinned to the level-0 chunk shape:
Axes: declared in NGFF style under
zarr.json["multiscales"][0].axes. The number of axes withtype == "space"issid_ndim. NGFF prescribes axis ordertime → channel → custom → space.Bounds: a root-only
(min_corner, max_corner)covering all data. There are no per-level bounds.Level-0 chunk shape:
RootMetadata.chunk_shape— one positive float per space axis. This is the finest chunk grid in the store; every coarser level nests cleanly into it.Optional bin grid: a
base_bin_shapefiner thanchunk_shapepartitioning each chunk into a regular grid of bins. Required to be an integer divisor ofchunk_shapealong every axis. When unset, one bin per chunk.Reference system: optional OME-Zarr RFC 4 / 5
crsdict, with per-axis UDUNITS-2 units on the NGFF axes.
Per-level overrides are allowed and described in §6.6.
6.2 Spatial Chunk Addressing¶
Chunk coordinates are integer tuples in chunk-grid space, one entry per space axis, written in the dot-separated form:
2-D:
<i.j>(e.g.3.7)3-D:
<i.j.k>(e.g.2.1.0)N-D:
<c_0.c_1.…c_{ndim-1}>
Empty chunks (no data) are simply absent from the store — Zarr v3 returns a “fill value” miss; Zarr Vectors readers treat missing chunks as “no data here.”
Translating between physical position and chunk coordinate:
chunk_coord_i = floor(position_i / chunk_shape_i)
Note that this is absolute — the store’s lower bound is not subtracted — so a store whose data extends below the origin has negative chunk coordinates, and that is well-defined rather than an error.
Chunk coordinate → array cell¶
A chunk coordinate is not directly an index into a per-chunk array.
Each such array is one array over the level’s chunk grid, and the
chunk at absolute coord c occupies
cell_i = chunk_coord_i - origin_i
origin_i = floor(bounds_min_i / chunk_shape_i)
The origin is stored as the array’s chunk_grid_origin attribute
only when it is non-zero; its absence means the two are the same
number. Keeping the offset in the array rather than in the coordinate
is what lets negative-coordinate data map onto a 0-indexed Zarr array
without renumbering anything. The dot-separated chunk key remains the
absolute coordinate — it is how a chunk is named in metadata such as
nonempty_chunks — while the cell index is where its bytes live.
At a coarser pyramid level with chunk_shape_level = r_i × root_chunk_shape_i,
a level-N chunk coord and a level-0 chunk coord at the same
physical position are related by integer division: coord_level = coord_0 // r_i. This is what makes per-level chunk-shape overrides
safe: the grids nest exactly.
6.3 Spatial Query Semantics¶
A bounding-box query (lo, hi) resolves to a chunk set:
chunks = {(c_0, …, c_{ndim-1})
: c_i ∈ [floor((lo_i - bounds_min_i)/chunk_shape_i),
ceil ((hi_i - bounds_min_i)/chunk_shape_i) - 1]}
Readers fetch only the chunks that exist within this set. When the
store has a bin grid (base_bin_shape != null), per-chunk filtering
can narrow further to individual bins using the fragment-index
boundaries — point clouds use the bin layout so that a single chunk
read can be sub-divided cheaply (see §7.3 and the per-bin fragment
mapping in zarr_vectors.spatial.chunking).
Point-in-volume queries reduce to one chunk lookup followed by a fragment-level intersection.
6.4 Boundary Conditions¶
A vertex’s chunk is determined by floor(position / chunk_shape).
Writers place each vertex in exactly one chunk; cross-chunk
connectivity is then expressed by one of two strategies:
Boundary deduplication (
cross_chunk_strategy = "boundary_deduplication"): a vertex right on a chunk seam is duplicated into both adjacent chunks, allowing readers to recover connectivity by coordinate matching. Simpler, costs one extra vertex per shared boundary point.Explicit cross-chunk links (
cross_chunk_strategy = "explicit_links", the default): each cross-chunk edge or face is written as a record inlinks/0/<offsets>at a non-zero offsets segment, in the cell of its source chunk. Each endpoint’s chunk is recovered from that cell plus the offsets, and its vertex index is local to that chunk. No vertex duplication.
A store may also set cross_chunk_strategy = "both" and emit both
representations.
6.5 Alternative Indexing Strategies¶
The format currently supports only the regular N-D grid index above. Future indexing strategies (octree / quadtree, Hilbert-curve ordering, etc.) are out of scope for this snapshot but would be expressed by replacing the chunk-key encoding while keeping the per-chunk byte payloads intact.
6.6 Per-Level Chunk Shape¶
RootMetadata.chunk_shape defines the level-0 grid. Each pyramid
level may carry its own chunk_shape in
zarr_vectors_level.chunk_shape, subject to:
Positive integer multiple of root along every axis — the level
chunk_shapeaxes arer_i × root_chunk_shape_ifor some positive integerr_i.Per-axis divisibility by the level’s
bin_shape— bins still tile chunks cleanly at the level’s resolution.
The per-axis multipliers r_i are exposed by
chunk_scale_factor(root_meta, level_meta) and are computed lazily
at read time. Writers that don’t grow chunks across levels leave
zarr_vectors_level.chunk_shape unset; the level inherits root
unchanged.
This mechanism plays the same role for vector pyramids that voxel-size
scaling plays for OME-Zarr image pyramids: coarser levels can
amortise the per-chunk overhead by holding bigger physical regions.
The cost: when r_i > 1, a level-N chunk physically covers a region
spanning multiple level-(N-1) chunks, so cross-pyramid-level link
arrays carry both endpoints’ chunk coords explicitly (the record
format already supports this — see §7.5 and §9.6).