9. Multi-Resolution Support

9.1 Resolution Level Structure

Each pyramid level lives under a bare-integer sub-group of the store root: 0/ is full resolution, 1/, 2/, … are progressively coarser. Per-level metadata (zarr.json["zarr_vectors_level"]) carries the level index, vertex count, source level (parent_level), bin grid, and an optional chunk_shape override (v0.7).

Levels are independent Zarr groups — a reader that only needs the coarsest level fetches only that level’s blobs. Levels do not duplicate root metadata (axes, bounds, CRS, conventions); those are all root-only.

9.2 Downsampling Strategies

Below is a toy example to help you visualize and conceptualize what a multi-scale coarsening of a graph with edges would look like

Toy Example of A 1d graph coarsening

Per-object coarsening (coarsening_method = "per_object")

The default. Each surviving object’s vertices are aggregated into metavertices on a coarser bin grid:

  1. Each level-0 fragment is mapped to a coarse-level bin via bin_ratio (per-axis integer fold-change of the bin shape).

  2. Per object, per coarse bin, the contributing level-0 vertices reduce to a single metavertex (centroid, mean, mode — depending on the writer). Per-vertex attributes follow the same reduction.

  3. The coarse-level fragment for that object then carries the metavertex rows; the fragment’s parent edges live in links/+1/<chunk> (when emitted — see §9.6).

Per-object coarsening preserves object identity across levels: an object dropped at a coarser level retains its OID slot, and its manifest is empty. Levels emitted this way set preserves_object_ids = true and carry inherited_num_objects (the OID-space size copied from the parent level).

The object_sparsity field records the fraction of source-level objects that survived to this level (1.0 = none dropped).

Shared metavertices (shared_fragments)

A single coarse-bin metavertex may be referenced by more than one object’s manifest — e.g. two streamlines that pass through the same coarse bin both reference the same fragment row. Levels using this representation set shared_fragments = true and the store advertises the CAP_SHARED_FRAGMENTS token (renamed from the pre-0.6 shared_vertex_groups token).

Manual or none (coarsening_method = "manual" | "none")

"manual" indicates the level was authored by a caller-supplied coarsening function (no schema constraint beyond the standard level invariants). "none" is used for level 0 itself.

Other geometries

  • Meshes: typically use edge-collapse decimation; the resulting vertex / face arrays are written under coarsening_method = "manual".

  • Skeletons: path simplification (Douglas-Peucker) keeps branch points and reduces straight-segment density.

  • Streamlines / polylines: point reduction along paths.

9.3 Spatial Chunk Scaling (v0.7)

RootMetadata.chunk_shape defines the finest (level-0) chunk grid. Each pyramid level may override the chunk shape via zarr_vectors_level.chunk_shape. The override is constrained:

  • Nested grids: per axis, level chunk_shape_axis must be a positive integer multiple r_i of root chunk_shape_axis. A level-N chunk’s parent in the level-(N-1) grid is a single chunk; the inverse cover is r_i level-(N-1) chunks.

  • Bin compatibility: per axis, level chunk_shape_axis must be an integer multiple of level bin_shape_axis (bins still tile chunks cleanly at every level).

chunk_scale_factor(root_meta, level_meta) exposes the tuple (r_0, …, r_{ndim-1}); cross-level chunk-coord translation is integer division:

coord_level_N = coord_level_0 // (r_0_cumulative, …)

Writers that don’t grow chunks per level leave the override unset; those levels inherit root chunk_shape exactly. The build_pyramid(..., chunk_scale_factors=[r1, r2, ...]) entry point in zarr_vectors.multiresolution emits the per-level metadata in one pass.

This plays the same role for vector pyramids that voxel-size scaling plays for OME-Zarr image pyramids: coarser levels can amortise per-chunk overhead by holding larger physical regions, while readers keep using integer arithmetic to walk between levels.

9.4 Level-of-Detail Selection

Readers choose a level by:

  • Vertex budget: pick the coarsest level whose vertex_count fits the budget for the visible region.

  • Pixel size: pick the level whose per-vertex physical spacing approaches one screen pixel — for a per-object pyramid, level N’s metavertex spacing is bin_shape_N.

  • Manual: pick a specific level index.

reduction_factor (root metadata) lets a writer indicate that levels should differ by ≥ that factor in vertex count, so an LOD picker can assume each adjacent pair is meaningfully coarser.

9.5 Consistency Across Levels

The format keeps these invariants:

  • bounds, crs, geometry_types, and conventions are root-only and shared by every level.

  • bin_shape and (optionally) chunk_shape are per-level overrides; level 0 inherits both from root.

  • object_id space is preserved by per-object pyramids; dropped objects retain empty manifest rows.

  • Cross-level edges, when emitted, live in links/<delta>/<chunk> (intra-chunk) and cross_chunk_links/<delta>/data (cross-chunk), with endpoint 0 at the owning level and endpoint k > 0 at level owning + delta.