Skip to content

RasterBuilder: copy_raster_from / copy_band_from construction APIs #896

Description

@james-willis

Problem

RasterBuilder only supports constructing rasters/bands field-by-field (start_raster_nd, start_band_nd). Any consumer that wants to produce an output raster that's mostly a copy of an input — overriding one field — has to manually destructure every field and re-append it. That's fragile: the copy is only as complete as the author remembered to make it, and it silently rots as the band/raster schema grows.

RS_EnsureLoaded (rust/sedona-raster-functions/src/rs_ensure_loaded.rs) is the cautionary case. It's a copy-with-substitution — output = input raster, except OutDb bands get their data column filled in — implemented as a hand rebuild:

let dim_names = band.dim_names()...;
let source_shape = band.raw_source_shape().to_vec();
let data_type = band.data_type();
let nodata = band.nodata()...;
let outdb_uri = band.outdb_uri()...;
let outdb_format = band.outdb_format()...;
builder.start_band_nd(name, &dim_names, &source_shape, data_type, nodata, outdb_uri, outdb_format)?;

This list is missing band.view() — so the rebuild silently drops non-identity views. (Latent today: RasterRefImpl::band() rejects non-identity views, so they're unconstructible — but the moment view composition lands, this rebuild corrupts viewed rasters. Tracked separately.) Any field added to bands in the future hits the same trap.

Proposed API

Copy-everything-by-default, override what you're changing:

// raster-level: transform, spatial_dims, spatial_shape, crs
builder.copy_raster_from(&raster)?;

for band_idx in 0..raster.num_bands() {
    let band = raster.band(band_idx)?;
    // copies name, dim_names, source_shape, view, dtype, nodata,
    // outdb_uri, outdb_format — complete by construction
    builder.copy_band_from(&band)?;       // copies metadata + data
    // ...or copy metadata and substitute the data:
    // builder.copy_band_from_with_data(&band, resolved_bytes)?;
}

The exact override surface (a with_data-style follow-up vs. a metadata-only copy + separate data write) is open for design. The invariant that matters: a copy stays complete as the schema grows, so consumers like RS_EnsureLoaded can't silently drop fields.

Notes

Construction ergonomics: reduce None noise (BandBuilder / default args)

start_band_nd takes seven positional args, up to four of which (name, nodata, outdb_uri, outdb_format) are routinely None. Across the test suite this yields dozens of start_band_nd(None, &["x"], &[3], BandDataType::UInt8, None, None, None) calls where the meaningful fields are buried in noise.

This sits at — not above — the clippy::too_many_arguments threshold (seven args), so it is not lint-forced, unlike start_band_with_view / with_view (eight args), which were bundled into StartBandWithViewArgs / WithViewArgs. It is a discretionary readability item. Raised in PR #813 review.

Options:

  • A fluent BandBuilder that takes the required fields (dim_names, shape, data_type) up front and exposes .name() / .nodata() / .outdb() setters, so the optional tail defaults to None and the required fields can't be defaulted away.
  • A StartBandNdArgs struct with Default on the optional tail (..Default::default()). Simpler, but Default is all-or-nothing per struct, so it needs a guard against defaulting dim_names / shape / data_type into empty or placeholder values.

Either composes with copy_band_from above: the copy path covers "mostly a clone," the builder path covers "construct fresh with sensible defaults."

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions