You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ja...@apache.org on 2026/08/10 20:14:36 UTC
(sedona-db) 01/01: refactor(rust/sedona-raster): add RasterError to replace ArrowError plumbing
This is an automated email from the ASF dual-hosted git repository.
james-willis pushed a commit to branch jw/raster-error-type
in repository https://gitbox.apache.org/repos/asf/sedona-db.git
commit 24e92f1b77fa8871f9c8e400969eab7f5c9d38f5
Author: jameswillis <ja...@wherobots.com>
AuthorDate: Mon Aug 10 13:13:56 2026 -0700
refactor(rust/sedona-raster): add RasterError to replace ArrowError plumbing
The raster crates leaned on arrow_schema::ArrowError as their error type,
forcing a map_err at every boundary. Introduce a dedicated RasterError
(mirroring sedona-geometry's SedonaGeometryError, plus an Arrow(#[from])
variant since raster code calls Arrow constantly) with From bridges into
DataFusionError and ArrowError and from DataFusionError, and a
RasterResultExt::context() helper. Migrate the sedona-raster library
modules to return RasterError and collapse the downstream map_err churn in
sedona-raster-{functions,gdal,zarr} to ?/.context(). The AsyncRasterLoader
trait stays on ArrowError to keep that cross-crate/cross-language boundary
stable; internal-assertion errors still route through sedona_internal_err!.
---
Cargo.lock | 1 +
rust/sedona-raster-functions/src/crs_utils.rs | 8 +-
rust/sedona-raster-functions/src/rs_dim_band.rs | 12 +-
rust/sedona-raster-functions/src/rs_dimensions.rs | 20 +--
.../src/rs_set_band_nodata.rs | 5 +-
.../src/rs_set_georeference.rs | 11 +-
rust/sedona-raster-functions/src/rs_slice.rs | 10 +-
.../src/rs_spatial_predicates.rs | 10 +-
rust/sedona-raster-functions/src/rs_value.rs | 27 +--
rust/sedona-raster-functions/src/rs_values.rs | 27 +--
rust/sedona-raster-functions/src/sampling.rs | 6 +-
rust/sedona-raster-gdal/src/gdal_common.rs | 14 +-
.../src/gdal_dataset_provider.rs | 23 ++-
rust/sedona-raster-gdal/src/mask.rs | 15 +-
rust/sedona-raster-gdal/src/rs_as_geotiff.rs | 16 +-
rust/sedona-raster-gdal/src/rs_as_raster.rs | 44 +++--
rust/sedona-raster-gdal/src/rs_clip.rs | 30 ++--
rust/sedona-raster-gdal/src/rs_from_gdal_raster.rs | 19 +-
rust/sedona-raster-gdal/src/rs_metadata.rs | 13 +-
rust/sedona-raster-gdal/src/rs_polygonize.rs | 28 ++-
rust/sedona-raster-gdal/src/rs_tile.rs | 24 ++-
rust/sedona-raster-gdal/src/rs_zonal_stats.rs | 13 +-
rust/sedona-raster-gdal/src/utils.rs | 72 ++++----
rust/sedona-raster-zarr/src/loader.rs | 4 +-
rust/sedona-raster/Cargo.toml | 1 +
rust/sedona-raster/src/affine_transformation.rs | 12 +-
rust/sedona-raster/src/array.rs | 30 ++--
rust/sedona-raster/src/builder.rs | 69 ++++----
rust/sedona-raster/src/error.rs | 192 +++++++++++++++++++++
rust/sedona-raster/src/geo_transform.rs | 18 +-
rust/sedona-raster/src/lib.rs | 1 +
rust/sedona-raster/src/traits.rs | 66 ++++---
rust/sedona-raster/src/view_entries.rs | 32 ++--
33 files changed, 495 insertions(+), 378 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 34a7121d4..325463499 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6220,6 +6220,7 @@ dependencies = [
"sedona-common",
"sedona-schema",
"sedona-testing",
+ "thiserror 2.0.17",
"tokio",
]
diff --git a/rust/sedona-raster-functions/src/crs_utils.rs b/rust/sedona-raster-functions/src/crs_utils.rs
index e18c7a2c6..69650e3b2 100644
--- a/rust/sedona-raster-functions/src/crs_utils.rs
+++ b/rust/sedona-raster-functions/src/crs_utils.rs
@@ -18,8 +18,9 @@
use std::borrow::Cow;
use datafusion_common::config::ConfigOptions;
-use datafusion_common::{exec_datafusion_err, exec_err, DataFusionError, Result};
+use datafusion_common::{exec_err, DataFusionError, Result};
use sedona_geometry::transform::{transform, CrsEngine};
+use sedona_raster::error::RasterResultExt;
use sedona_schema::crs::{deserialize_crs, CoordinateReferenceSystem, Crs, CrsRef};
use wkb::reader::read_wkb;
@@ -73,11 +74,10 @@ pub fn crs_transform_wkb(
) -> Result<Vec<u8>> {
let crs_transform = engine
.get_transform_crs_to_crs(&from_crs.to_crs_string(), &to_crs.to_crs_string(), None, "")
- .map_err(|e| exec_datafusion_err!("CRS transform error: {}", e))?;
+ .context("CRS transform error")?;
let geom = read_wkb(wkb).map_err(|e| DataFusionError::External(Box::new(e)))?;
let mut out = Vec::with_capacity(wkb.len());
- transform(geom, crs_transform.as_ref(), &mut out)
- .map_err(|e| exec_datafusion_err!("Transform error: {}", e))?;
+ transform(geom, crs_transform.as_ref(), &mut out).context("Transform error")?;
Ok(out)
}
diff --git a/rust/sedona-raster-functions/src/rs_dim_band.rs b/rust/sedona-raster-functions/src/rs_dim_band.rs
index 12c0ab540..34b56ca70 100644
--- a/rust/sedona-raster-functions/src/rs_dim_band.rs
+++ b/rust/sedona-raster-functions/src/rs_dim_band.rs
@@ -20,7 +20,7 @@ use std::sync::Arc;
use arrow_schema::DataType;
use datafusion_common::cast::as_string_view_array;
use datafusion_common::error::Result;
-use datafusion_common::{arrow_datafusion_err, exec_err};
+use datafusion_common::exec_err;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_common::sedona_internal_datafusion_err;
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
@@ -102,9 +102,7 @@ impl SedonaScalarKernel for RsDimToBand {
)?;
for band_idx in 0..raster.num_bands() {
- let band = raster
- .band(band_idx)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(band_idx)?;
let maybe_dim_idx = band.dim_index(name);
match maybe_dim_idx {
@@ -228,7 +226,7 @@ impl SedonaScalarKernel for RsBandToDim {
return exec_err!("RS_BandToDim: raster has no bands");
}
- let band0 = raster.band(0).map_err(|e| arrow_datafusion_err!(e))?;
+ let band0 = raster.band(0)?;
let ref_dim_names = band0.dim_names();
let ref_shape = band0.shape().to_vec();
let ref_data_type = band0.data_type();
@@ -246,7 +244,7 @@ impl SedonaScalarKernel for RsBandToDim {
}
for i in 1..num_bands {
- let band = raster.band(i).map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(i)?;
if band.dim_names() != ref_dim_names {
return exec_err!(
"RS_BandToDim: band {i} has different dim_names than band 0"
@@ -284,7 +282,7 @@ impl SedonaScalarKernel for RsBandToDim {
let mut concat_data = Vec::new();
for i in 0..num_bands {
- let band = raster.band(i).map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(i)?;
let ndb = band.nd_buffer()?;
let data = ndb.as_contiguous()?;
concat_data.extend_from_slice(data);
diff --git a/rust/sedona-raster-functions/src/rs_dimensions.rs b/rust/sedona-raster-functions/src/rs_dimensions.rs
index ff4e6e2f1..507f11842 100644
--- a/rust/sedona-raster-functions/src/rs_dimensions.rs
+++ b/rust/sedona-raster-functions/src/rs_dimensions.rs
@@ -21,7 +21,7 @@ use arrow_array::builder::{Int32Builder, Int64Builder, ListBuilder, StringViewBu
use arrow_schema::DataType;
use datafusion_common::cast::{as_int32_array, as_string_view_array};
use datafusion_common::error::Result;
-use datafusion_common::{arrow_datafusion_err, exec_err};
+use datafusion_common::exec_err;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_raster::traits::RasterRef;
@@ -41,7 +41,7 @@ fn check_band_agreement<T: PartialEq + std::fmt::Debug>(
if raster.num_bands() == 0 {
return exec_err!("{func_name}: raster has no bands");
}
- let band0 = raster.band(0).map_err(|e| arrow_datafusion_err!(e))?;
+ let band0 = raster.band(0)?;
let value = extractor(band0.as_ref());
for i in 1..raster.num_bands() {
if let Ok(band) = raster.band(i) {
@@ -170,9 +170,7 @@ impl SedonaScalarKernel for RsNumDimensionsWithBand {
// A null or out-of-range band index yields a null result.
Some(raster) => match band_index {
Some(bi) if bi >= 1 && bi <= raster.num_bands() as i32 => {
- let band = raster
- .band((bi - 1) as usize)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band((bi - 1) as usize)?;
builder.append_value(band.ndim() as i32);
Ok(())
}
@@ -281,9 +279,7 @@ impl SedonaScalarKernel for RsDimNamesWithBand {
// A null or out-of-range band index yields a null result.
Some(raster) => match band_index {
Some(bi) if bi >= 1 && bi <= raster.num_bands() as i32 => {
- let band = raster
- .band((bi - 1) as usize)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band((bi - 1) as usize)?;
for name in band.dim_names() {
list_builder.values().append_value(name);
}
@@ -424,9 +420,7 @@ impl SedonaScalarKernel for RsDimSizeWithBand {
// the band) yields a null result.
(Some(raster), Some(name)) => match band_index {
Some(bi) if bi >= 1 && bi <= raster.num_bands() as i32 => {
- let band = raster
- .band((bi - 1) as usize)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band((bi - 1) as usize)?;
match band.dim_size(name) {
Some(s) => builder.append_value(s),
None => builder.append_null(),
@@ -530,9 +524,7 @@ impl SedonaScalarKernel for RsShapeWithBand {
// A null or out-of-range band index yields a null result.
Some(raster) => match band_index {
Some(bi) if bi >= 1 && bi <= raster.num_bands() as i32 => {
- let band = raster
- .band((bi - 1) as usize)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band((bi - 1) as usize)?;
for &s in band.shape() {
list_builder.values().append_value(s);
}
diff --git a/rust/sedona-raster-functions/src/rs_set_band_nodata.rs b/rust/sedona-raster-functions/src/rs_set_band_nodata.rs
index c54c1ce37..4a1e3f42c 100644
--- a/rust/sedona-raster-functions/src/rs_set_band_nodata.rs
+++ b/rust/sedona-raster-functions/src/rs_set_band_nodata.rs
@@ -45,7 +45,7 @@ use arrow_array::types::{Float64Type, Int64Type};
use arrow_array::Array;
use arrow_schema::DataType;
use datafusion_common::error::Result;
-use datafusion_common::{arrow_datafusion_err, exec_err};
+use datafusion_common::exec_err;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_raster::builder::{RasterBuilder, RasterOverrides};
@@ -121,8 +121,7 @@ impl SedonaScalarKernel for RsSetBandNoDataValue {
let mut builder = RasterBuilder::new(n);
executor.execute_raster_void(|i, raster_opt| {
- let null_out =
- |b: &mut RasterBuilder| b.append_null().map_err(|e| arrow_datafusion_err!(e));
+ let null_out = |b: &mut RasterBuilder| b.append_null().map_err(Into::into);
// A null raster, band, or value yields a null raster.
let Some(raster) = raster_opt else {
diff --git a/rust/sedona-raster-functions/src/rs_set_georeference.rs b/rust/sedona-raster-functions/src/rs_set_georeference.rs
index 6b0ba72c4..17eaacfbc 100644
--- a/rust/sedona-raster-functions/src/rs_set_georeference.rs
+++ b/rust/sedona-raster-functions/src/rs_set_georeference.rs
@@ -41,7 +41,7 @@ use arrow_array::Array;
use arrow_schema::DataType;
use datafusion_common::cast::as_string_array;
use datafusion_common::error::Result;
-use datafusion_common::{arrow_datafusion_err, exec_datafusion_err, exec_err};
+use datafusion_common::{exec_datafusion_err, exec_err};
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_raster::builder::{RasterBuilder, RasterOverrides};
@@ -112,8 +112,7 @@ impl SedonaScalarKernel for RsSetGeoReference {
let mut builder = RasterBuilder::new(n);
executor.execute_raster_void(|i, raster_opt| {
- let null_out =
- |b: &mut RasterBuilder| b.append_null().map_err(|e| arrow_datafusion_err!(e));
+ let null_out = |b: &mut RasterBuilder| b.append_null().map_err(Into::into);
// A null georef or format is a null *input* and yields a null raster
// (matching RS_SetCRS); check those first so a null-driven row never
@@ -143,12 +142,10 @@ impl SedonaScalarKernel for RsSetGeoReference {
transform: Some(transform),
},
)
- .map_err(|e| arrow_datafusion_err!(e))
+ .map_err(Into::into)
})?;
- executor.finish(Arc::new(
- builder.finish().map_err(|e| arrow_datafusion_err!(e))?,
- ))
+ executor.finish(Arc::new(builder.finish()?))
}
}
diff --git a/rust/sedona-raster-functions/src/rs_slice.rs b/rust/sedona-raster-functions/src/rs_slice.rs
index 571b0c3f6..6ead2ed24 100644
--- a/rust/sedona-raster-functions/src/rs_slice.rs
+++ b/rust/sedona-raster-functions/src/rs_slice.rs
@@ -20,7 +20,7 @@ use std::sync::Arc;
use arrow_schema::DataType;
use datafusion_common::cast::{as_int64_array, as_string_array};
use datafusion_common::error::Result;
-use datafusion_common::{arrow_datafusion_err, exec_err};
+use datafusion_common::exec_err;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_common::sedona_internal_datafusion_err;
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
@@ -107,9 +107,7 @@ impl SedonaScalarKernel for RsSlice {
require_any_band_has_dim(raster, name, "RS_Slice")?;
for band_idx in 0..raster.num_bands() {
- let band = raster
- .band(band_idx)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(band_idx)?;
// Pass-through: bands that don't carry the named
// dimension are emitted unchanged. Same convention as
@@ -268,9 +266,7 @@ impl SedonaScalarKernel for RsSliceRange {
require_any_band_has_dim(raster, name, "RS_SliceRange")?;
for band_idx in 0..raster.num_bands() {
- let band = raster
- .band(band_idx)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(band_idx)?;
// Pass-through: bands that don't carry the named
// dimension are emitted unchanged. Same convention as
diff --git a/rust/sedona-raster-functions/src/rs_spatial_predicates.rs b/rust/sedona-raster-functions/src/rs_spatial_predicates.rs
index a7b02d879..a10a9b82b 100644
--- a/rust/sedona-raster-functions/src/rs_spatial_predicates.rs
+++ b/rust/sedona-raster-functions/src/rs_spatial_predicates.rs
@@ -39,12 +39,12 @@ use crate::footprint::write_convexhull_wkb;
use arrow_array::builder::BooleanBuilder;
use arrow_schema::DataType;
use datafusion_common::config::ConfigOptions;
-use datafusion_common::exec_datafusion_err;
use datafusion_common::exec_err;
use datafusion_common::Result;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_geometry::transform::CrsEngine;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_schema::crs::{lnglat, CrsRef};
use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
@@ -378,10 +378,10 @@ fn evaluate_predicate_with_crs<Op: tg::BinaryPredicate>(
/// Evaluate a spatial predicate between two WKB geometries
fn evaluate_predicate<Op: tg::BinaryPredicate>(wkb_a: &[u8], wkb_b: &[u8]) -> Result<bool> {
- let geom_a = tg::Geom::parse_wkb(wkb_a, tg::IndexType::Default)
- .map_err(|e| exec_datafusion_err!("Failed to parse WKB A: {e}"))?;
- let geom_b = tg::Geom::parse_wkb(wkb_b, tg::IndexType::Default)
- .map_err(|e| exec_datafusion_err!("Failed to parse WKB B: {e}"))?;
+ let geom_a =
+ tg::Geom::parse_wkb(wkb_a, tg::IndexType::Default).context("Failed to parse WKB A")?;
+ let geom_b =
+ tg::Geom::parse_wkb(wkb_b, tg::IndexType::Default).context("Failed to parse WKB B")?;
Ok(Op::evaluate(&geom_a, &geom_b))
}
diff --git a/rust/sedona-raster-functions/src/rs_value.rs b/rust/sedona-raster-functions/src/rs_value.rs
index 3b38d04ab..53157412c 100644
--- a/rust/sedona-raster-functions/src/rs_value.rs
+++ b/rust/sedona-raster-functions/src/rs_value.rs
@@ -38,12 +38,13 @@ use arrow_array::{builder::Float64Builder, Array, ArrayRef, Float64Array, Struct
use arrow_schema::DataType;
use datafusion_common::cast::as_int32_array;
use datafusion_common::config::ConfigOptions;
-use datafusion_common::{exec_datafusion_err, exec_err, Result, ScalarValue};
+use datafusion_common::{exec_err, Result, ScalarValue};
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_geometry::transform::CrsTransform;
use sedona_geometry::wkb_header::read_point_xy;
use sedona_raster::array::RasterStructArray;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
@@ -332,12 +333,8 @@ impl RsValuePoint {
"RS_Value supports 2-D rasters only; band is not a 2-D (y, x) grid"
);
}
- let buffer = band
- .nd_buffer()
- .map_err(|e| exec_datafusion_err!("RS_Value: {e}"))?;
- let nodata = band
- .nodata_as_f64()
- .map_err(|e| exec_datafusion_err!("RS_Value: {e}"))?;
+ let buffer = band.nd_buffer().context("RS_Value")?;
+ let nodata = band.nodata_as_f64().context("RS_Value")?;
for (i, x, y, _band) in selection {
if let Some((col, row)) = xy_to_pixel("RS_Value", transform, x, y)? {
out[i] = read_pixel("RS_Value", &buffer, nodata, col, row)?;
@@ -368,15 +365,11 @@ fn resolve_point_xy(
point_wkb: &[u8],
trans: Option<&dyn CrsTransform>,
) -> Result<Option<(f64, f64)>> {
- let Some(mut xy) =
- read_point_xy(point_wkb).map_err(|e| exec_datafusion_err!("RS_Value: {e}"))?
- else {
+ let Some(mut xy) = read_point_xy(point_wkb).context("RS_Value")? else {
return Ok(None);
};
if let Some(trans) = trans {
- trans
- .transform_coord(&mut xy)
- .map_err(|e| exec_datafusion_err!("RS_Value: {e}"))?;
+ trans.transform_coord(&mut xy).context("RS_Value")?;
}
Ok(Some(xy))
}
@@ -401,12 +394,8 @@ fn sample_pixel(
if !band.is_spatial_2d() {
return exec_err!("RS_Value supports 2-D rasters only; band is not a 2-D (y, x) grid");
}
- let buffer = band
- .nd_buffer()
- .map_err(|e| exec_datafusion_err!("RS_Value: {e}"))?;
- let nodata = band
- .nodata_as_f64()
- .map_err(|e| exec_datafusion_err!("RS_Value: {e}"))?;
+ let buffer = band.nd_buffer().context("RS_Value")?;
+ let nodata = band.nodata_as_f64().context("RS_Value")?;
read_pixel("RS_Value", &buffer, nodata, col, row)
}
diff --git a/rust/sedona-raster-functions/src/rs_values.rs b/rust/sedona-raster-functions/src/rs_values.rs
index ed839ffe3..a8cd92210 100644
--- a/rust/sedona-raster-functions/src/rs_values.rs
+++ b/rust/sedona-raster-functions/src/rs_values.rs
@@ -44,10 +44,11 @@ use arrow_array::{Array, ArrayRef, StructArray};
use arrow_schema::DataType;
use datafusion_common::cast::as_int32_array;
use datafusion_common::config::ConfigOptions;
-use datafusion_common::{exec_datafusion_err, exec_err, Result, ScalarValue};
+use datafusion_common::{exec_err, Result, ScalarValue};
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_raster::array::RasterStructArray;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::{BandRef, NdBuffer, RasterRef};
use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
@@ -185,12 +186,8 @@ impl RsValues {
// this row, then sample every sub-point against them.
let raster_crs = resolve_crs(raster.crs())?;
let band = resolve_band_2d(raster, band_num)?;
- let buffer = band
- .nd_buffer()
- .map_err(|e| exec_datafusion_err!("RS_Values: {e}"))?;
- let nodata = band
- .nodata_as_f64()
- .map_err(|e| exec_datafusion_err!("RS_Values: {e}"))?;
+ let buffer = band.nd_buffer().context("RS_Values")?;
+ let nodata = band.nodata_as_f64().context("RS_Values")?;
let transform = raster.transform();
// Sample each sub-point in one pass: the visitor transforms
@@ -353,12 +350,8 @@ impl RsValues {
match const_band {
Some(band_num) => {
let band = resolve_band_2d(&raster, band_num)?;
- let buffer = band
- .nd_buffer()
- .map_err(|e| exec_datafusion_err!("RS_Values: {e}"))?;
- let nodata = band
- .nodata_as_f64()
- .map_err(|e| exec_datafusion_err!("RS_Values: {e}"))?;
+ let buffer = band.nd_buffer().context("RS_Values")?;
+ let nodata = band.nodata_as_f64().context("RS_Values")?;
for row in &rows {
let Some((start, len, _band)) = row else {
list_builder.append_null();
@@ -377,12 +370,8 @@ impl RsValues {
continue;
};
let band = resolve_band_2d(&raster, *band_num)?;
- let buffer = band
- .nd_buffer()
- .map_err(|e| exec_datafusion_err!("RS_Values: {e}"))?;
- let nodata = band
- .nodata_as_f64()
- .map_err(|e| exec_datafusion_err!("RS_Values: {e}"))?;
+ let buffer = band.nd_buffer().context("RS_Values")?;
+ let nodata = band.nodata_as_f64().context("RS_Values")?;
for xy in &coords[*start..*start + *len] {
append_sample(*xy, transform, &buffer, nodata, &mut list_builder)?;
}
diff --git a/rust/sedona-raster-functions/src/sampling.rs b/rust/sedona-raster-functions/src/sampling.rs
index 0cca0522d..144c126f7 100644
--- a/rust/sedona-raster-functions/src/sampling.rs
+++ b/rust/sedona-raster-functions/src/sampling.rs
@@ -33,6 +33,7 @@ use datafusion_common::{exec_datafusion_err, exec_err, DataFusionError, Result};
use datafusion_expr::ColumnarValue;
use sedona_geometry::error::SedonaGeometryError;
use sedona_geometry::transform::{visit_point_coords, CrsEngine, CrsTransform};
+use sedona_raster::error::RasterResultExt;
use sedona_raster::geo_transform::GeoTransformEx;
use sedona_raster::traits::{nodata_bytes_to_f64_lossless, BandRef, NdBuffer, RasterRef};
use sedona_schema::crs::CrsRef;
@@ -180,7 +181,7 @@ pub(crate) fn visit_points(
trans: Option<&dyn CrsTransform>,
mut visit: impl FnMut(Option<(f64, f64)>) -> Result<()>,
) -> Result<()> {
- let geom = read_wkb(wkb).map_err(|e| exec_datafusion_err!("{func}: {e}"))?;
+ let geom = read_wkb(wkb).context(func)?;
visit_point_coords(&geom, trans, |xy| {
visit(xy).map_err(|e| SedonaGeometryError::External(Box::new(e)))
})
@@ -269,8 +270,7 @@ pub(crate) fn read_pixel(
// silently rounding) on Int64/UInt64 values beyond f64's exact-integer
// range (2^53) — the value functions return a Double, so such a pixel can't
// be represented faithfully; failing loudly is preferred over a wrong value.
- let value = nodata_bytes_to_f64_lossless(bytes, &buffer.data_type)
- .map_err(|e| exec_datafusion_err!("{func}: {e}"))?;
+ let value = nodata_bytes_to_f64_lossless(bytes, &buffer.data_type).context(func)?;
if let Some(nodata) = nodata {
if value == nodata || (value.is_nan() && nodata.is_nan()) {
diff --git a/rust/sedona-raster-gdal/src/gdal_common.rs b/rust/sedona-raster-gdal/src/gdal_common.rs
index f103e0142..6d3b3760f 100644
--- a/rust/sedona-raster-gdal/src/gdal_common.rs
+++ b/rust/sedona-raster-gdal/src/gdal_common.rs
@@ -28,9 +28,7 @@ use sedona_raster::geo_transform::GeoTransform;
use sedona_raster::traits::{is_spatial_dim_pair, RasterRef};
use sedona_schema::raster::BandDataType;
-use datafusion_common::{
- arrow_datafusion_err, exec_datafusion_err, exec_err, DataFusionError, Result,
-};
+use datafusion_common::{exec_datafusion_err, exec_err, DataFusionError, Result};
/// Execute a closure with a reference to the global [`Gdal`] handle,
/// converting initialization errors to [`DataFusionError`].
@@ -173,8 +171,8 @@ pub unsafe fn raster_ref_to_gdal_mem<R: RasterRef + ?Sized>(
raster: &R,
band_indices: &[usize],
) -> Result<Dataset> {
- let width = raster.width().map_err(|e| arrow_datafusion_err!(e))? as usize;
- let height = raster.height().map_err(|e| arrow_datafusion_err!(e))? as usize;
+ let width = raster.width()? as usize;
+ let height = raster.height()? as usize;
// The N-D → flat-GDAL plane layout (band-major, plane-major). Deriving it
// performs the trailing-(y, x)-pair check and records each band's plane
@@ -190,9 +188,7 @@ pub unsafe fn raster_ref_to_gdal_mem<R: RasterRef + ?Sized>(
let mut base_ptrs: Vec<*mut u8> = Vec::with_capacity(layout.bands.len());
for (&src_band_index, plan) in band_indices.iter().zip(&layout.bands) {
// `band_indices` are 1-based; the `band` accessor is 0-based.
- let band = raster
- .band(src_band_index - 1)
- .map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(src_band_index - 1)?;
// The plane's 2-D extent must equal the MEM dataset's (and the raster's
// spatial grid); otherwise the per-plane byte slicing would disagree
@@ -345,7 +341,7 @@ impl GdalBandLayout {
let mut plans = Vec::with_capacity(band_indices.len());
for &i in band_indices {
// `band_indices` are 1-based; the `band` accessor is 0-based.
- let band = raster.band(i - 1).map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(i - 1)?;
let dim_names: Vec<String> = band.dim_names().iter().map(|s| s.to_string()).collect();
let ndim = dim_names.len();
if ndim < 2 || !is_spatial_dim_pair(&dim_names[ndim - 2], &dim_names[ndim - 1]) {
diff --git a/rust/sedona-raster-gdal/src/gdal_dataset_provider.rs b/rust/sedona-raster-gdal/src/gdal_dataset_provider.rs
index 97798c8c2..8740178d3 100644
--- a/rust/sedona-raster-gdal/src/gdal_dataset_provider.rs
+++ b/rust/sedona-raster-gdal/src/gdal_dataset_provider.rs
@@ -19,13 +19,12 @@ use std::convert::TryInto;
use std::{cell::RefCell, marker::PhantomData, num::NonZeroUsize, rc::Rc};
use datafusion_common::config::ConfigOptions;
-use datafusion_common::{
- arrow_datafusion_err, exec_datafusion_err, exec_err, DataFusionError, Result,
-};
+use datafusion_common::{exec_datafusion_err, exec_err, DataFusionError, Result};
use sedona_gdal::dataset::Dataset;
use sedona_gdal::gdal::Gdal;
use sedona_gdal::raster::types::GdalDataType;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::geo_transform::{GeoTransform, GeoTransformEx};
use sedona_common::SedonaOptions;
@@ -214,8 +213,8 @@ impl GDALDatasetCache {
) -> Result<(Rc<Dataset>, Vec<Rc<Dataset>>)> {
let num_bands = raster.num_bands();
- let metadata_width = raster.width().map_err(|e| arrow_datafusion_err!(e))?;
- let metadata_height = raster.height().map_err(|e| arrow_datafusion_err!(e))?;
+ let metadata_width = raster.width()?;
+ let metadata_height = raster.height()?;
let width: i32 = metadata_width.try_into().map_err(|_| {
exec_datafusion_err!(
"Raster width {} exceeds supported GDAL/i32 limit {}",
@@ -259,7 +258,7 @@ impl GDALDatasetCache {
// `i` is 1-based here because it also indexes GDAL's own 1-based
// `rasterband(i)` below; the raster's `band` accessor is 0-based.
for i in 1..=num_bands {
- let band = raster.band(i - 1).map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(i - 1)?;
if !band.is_spatial_2d() {
return exec_err!(
@@ -306,8 +305,7 @@ impl GDALDatasetCache {
let uri = band.outdb_uri().ok_or_else(|| {
exec_datafusion_err!("Band {} is out-db but missing outdb_uri", i)
})?;
- let (url, source_band_num_u32) =
- split_outdb_band_fragment(uri).map_err(|e| arrow_datafusion_err!(e))?;
+ let (url, source_band_num_u32) = split_outdb_band_fragment(uri)?;
let source_band_num: usize = source_band_num_u32
.try_into()
.map_err(|_| exec_datafusion_err!("Band {} out-db band_id is too large", i))?;
@@ -386,7 +384,7 @@ impl<'a> GDALDatasetProvider<'a> {
let mut indb_band_indices = Vec::with_capacity(num_bands);
let mut has_outdb = false;
for i in 1..=num_bands {
- let band = raster.band(i - 1).map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(i - 1)?;
if band.is_indb() {
indb_band_indices.push(i);
} else {
@@ -482,7 +480,7 @@ impl VrtKey {
let mut band_keys = Vec::with_capacity(num_bands);
for i in 0..num_bands {
- let band = raster.band(i).map_err(|e| arrow_datafusion_err!(e))?;
+ let band = raster.band(i)?;
let band_type = band.data_type();
let nodata_bits = match (band.nodata(), band_type) {
(Some(bytes), BandDataType::UInt64) => {
@@ -504,8 +502,7 @@ impl VrtKey {
// URI; in-db bands have no URI hint here.
let (outdb_url, outdb_band_id) = match band.outdb_uri() {
Some(uri) => {
- let (url, band_num) =
- split_outdb_band_fragment(uri).map_err(|e| arrow_datafusion_err!(e))?;
+ let (url, band_num) = split_outdb_band_fragment(uri)?;
(Some(normalize_outdb_source_path(&url)), Some(band_num))
}
None => (None, None),
@@ -566,7 +563,7 @@ fn compute_vrt_simple_source_windows(
// Compute the pixel/line offset of the destination upper-left in the source grid.
let inv_src = src_gt
.invert()
- .map_err(|e| exec_datafusion_err!("Failed to invert source geotransform: {e}"))?;
+ .context("Failed to invert source geotransform")?;
let (off_x_f, off_y_f) = inv_src.apply(dst_gt[0], dst_gt[3]);
let off_x_r: f64 = off_x_f.round();
diff --git a/rust/sedona-raster-gdal/src/mask.rs b/rust/sedona-raster-gdal/src/mask.rs
index 3cc0aaea7..38de9867e 100644
--- a/rust/sedona-raster-gdal/src/mask.rs
+++ b/rust/sedona-raster-gdal/src/mask.rs
@@ -26,11 +26,12 @@
//! module owns only the window addressing and rasterization, not the
//! per-pixel consumption.
-use datafusion_common::{exec_datafusion_err, Result};
+use datafusion_common::Result;
use sedona_gdal::gdal::Gdal;
use sedona_gdal::mem::MemDatasetBuilder;
use sedona_gdal::raster::types::GdalDataType;
use sedona_gdal::vector::geometry::Geometry;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::geo_transform::{GeoTransform, GeoTransformEx};
/// A rectangular pixel window (offset + size) into a raster grid.
@@ -62,7 +63,7 @@ pub fn envelope_window(
let env = geometry.envelope();
let inverse = transform
.invert()
- .map_err(|e| exec_datafusion_err!("raster mask: geotransform is not invertible: {e}"))?;
+ .context("raster mask: geotransform is not invertible")?;
let corners = [
(env.MinX, env.MinY),
@@ -128,7 +129,7 @@ pub fn rasterize_geometry_mask(
) -> Result<()> {
let mask_dataset =
MemDatasetBuilder::create(gdal, window.width, window.height, 1, GdalDataType::UInt8)
- .map_err(|e| exec_datafusion_err!("raster mask: failed to create mask dataset: {e}"))?;
+ .context("raster mask: failed to create mask dataset")?;
let (window_ulx, window_uly) = transform.apply(window.col_off as f64, window.row_off as f64);
let mask_transform = [
window_ulx,
@@ -140,14 +141,14 @@ pub fn rasterize_geometry_mask(
];
mask_dataset
.set_geo_transform(&mask_transform)
- .map_err(|e| exec_datafusion_err!("raster mask: failed to set mask geotransform: {e}"))?;
+ .context("raster mask: failed to set mask geotransform")?;
gdal.rasterize_affine(&mask_dataset, &[1], &[geometry], &[1.0], all_touched)
- .map_err(|e| exec_datafusion_err!("raster mask: failed to rasterize geometry: {e}"))?;
+ .context("raster mask: failed to rasterize geometry")?;
let mask_band = mask_dataset
.rasterband(1)
- .map_err(|e| exec_datafusion_err!("raster mask: failed to read mask band: {e}"))?;
+ .context("raster mask: failed to read mask band")?;
let mask_buffer = mask_band
.read_as::<u8>(
(0, 0),
@@ -155,7 +156,7 @@ pub fn rasterize_geometry_mask(
(window.width, window.height),
None,
)
- .map_err(|e| exec_datafusion_err!("raster mask: failed to read mask: {e}"))?;
+ .context("raster mask: failed to read mask")?;
out.clear();
out.extend_from_slice(mask_buffer.data());
Ok(())
diff --git a/rust/sedona-raster-gdal/src/rs_as_geotiff.rs b/rust/sedona-raster-gdal/src/rs_as_geotiff.rs
index b00626127..0556afcf5 100644
--- a/rust/sedona-raster-gdal/src/rs_as_geotiff.rs
+++ b/rust/sedona-raster-gdal/src/rs_as_geotiff.rs
@@ -40,6 +40,7 @@ use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_gdal::vsi::VSIBuffer;
use sedona_raster::array::RasterRefImpl;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_raster_functions::RasterExecutor;
use sedona_schema::datatypes::SedonaType;
@@ -150,12 +151,12 @@ impl RsAsGeoTiff {
) -> Result<VSIBuffer> {
let raster_ds = provider
.raster_ref_to_gdal(raster)
- .map_err(|e| exec_datafusion_err!("Failed to create GDAL dataset: {}", e))?;
+ .context("Failed to create GDAL dataset")?;
let source_dataset = raster_ds.as_dataset();
let driver = gdal
.get_driver_by_name("GTiff")
- .map_err(|e| exec_datafusion_err!("Failed to get GTiff driver: {}", e))?;
+ .context("Failed to get GTiff driver")?;
// Validate and map the quality up front so an out-of-range value errors
// for every codec, not only JPEG (the codecs that ignore quality should
@@ -211,7 +212,7 @@ impl RsAsGeoTiff {
// the bytes to the vsimem file.
source_dataset
.create_copy(&driver, &vsi_path, &options_refs)
- .map_err(|e| exec_datafusion_err!("Failed to create GeoTiff: {}", e))?;
+ .context("Failed to create GeoTiff")?;
// Seize the vsimem file's buffer without copying: `VSIBuffer` owns the
// GDAL allocation (freed on drop) and unlinks the file, so the only
@@ -220,7 +221,7 @@ impl RsAsGeoTiff {
// `create_copy` or the seize fails.
let bytes = gdal
.get_vsi_mem_file_buffer_owned(&vsi_path)
- .map_err(|e| exec_datafusion_err!("Failed to read GeoTiff bytes: {}", e))?;
+ .context("Failed to read GeoTiff bytes")?;
drop(guard);
Ok(bytes)
@@ -297,9 +298,7 @@ fn predictor_for(raster: &RasterRefImpl) -> Result<i32> {
if raster.num_bands() == 0 {
return Ok(2);
}
- let band = raster
- .band(0)
- .map_err(|e| exec_datafusion_err!("RS_AsGeoTiff: {e}"))?;
+ let band = raster.band(0).context("RS_AsGeoTiff")?;
let data_type = band.data_type();
Ok(match data_type {
BandDataType::Float32 | BandDataType::Float64 => 3,
@@ -449,8 +448,7 @@ impl SedonaScalarKernel for RsAsGeoTiff {
with_gdal(|gdal| {
configure_thread_local_options(gdal, config_options)?;
- let provider = thread_local_provider(gdal)
- .map_err(|e| exec_datafusion_err!("Failed to init GDAL provider: {e}"))?;
+ let provider = thread_local_provider(gdal).context("Failed to init GDAL provider")?;
executor.execute_raster_void(|_i, raster_opt| {
let compression_opt = compression_iter.next().unwrap();
let quality_opt = quality_iter.next().unwrap();
diff --git a/rust/sedona-raster-gdal/src/rs_as_raster.rs b/rust/sedona-raster-gdal/src/rs_as_raster.rs
index 01ac1b076..349c33a10 100644
--- a/rust/sedona-raster-gdal/src/rs_as_raster.rs
+++ b/rust/sedona-raster-gdal/src/rs_as_raster.rs
@@ -37,6 +37,7 @@ use sedona_gdal::mem::MemDatasetBuilder;
use sedona_gdal::raster::{rasterband::RasterBand, types::Buffer};
use sedona_raster::array::RasterRefImpl;
use sedona_raster::builder::RasterBuilder;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_raster_functions::{
crs_utils::{align_wkb_to_crs, resolve_crs},
@@ -242,24 +243,22 @@ impl SedonaScalarKernel for RsAsRaster {
nodata_value_opt,
use_geometry_extent_opt,
)
- .map_err(|e| exec_datafusion_err!("RS_AsRaster failed: {}", e))?;
+ .context("RS_AsRaster failed")?;
rasterized
.grid
.start_raster_into(&mut builder, raster.crs())
- .map_err(|e| exec_datafusion_err!("Failed to start output raster: {}", e))?;
+ .context("Failed to start output raster")?;
builder
.start_band_2d(rasterized.data_type, rasterized.nodata.as_deref())
- .map_err(|e| {
- exec_datafusion_err!("Failed to start output raster band: {}", e)
- })?;
+ .context("Failed to start output raster band")?;
builder.band_data_writer().append_value(rasterized.data);
- builder.finish_band().map_err(|e| {
- exec_datafusion_err!("Failed to finish output raster band: {}", e)
- })?;
+ builder
+ .finish_band()
+ .context("Failed to finish output raster band")?;
builder
.finish_raster()
- .map_err(|e| exec_datafusion_err!("Failed to finish output raster: {}", e))?;
+ .context("Failed to finish output raster")?;
Ok(())
})
@@ -436,7 +435,7 @@ fn as_raster(
let geometry = gdal
.geometry_from_wkb(geom_wkb)
- .map_err(|e| exec_datafusion_err!("Failed to parse geometry from WKB: {}", e))?;
+ .context("Failed to parse geometry from WKB")?;
let (out_width, out_height, out_ulx, out_uly) = if use_geometry_extent {
let env = geometry.envelope();
@@ -491,7 +490,7 @@ fn as_raster(
1,
band_data_type_to_gdal(&band_type),
)
- .map_err(|e| exec_datafusion_err!("Failed to create dataset: {}", e))?;
+ .context("Failed to create dataset")?;
let out_transform = [
out_ulx,
@@ -503,17 +502,16 @@ fn as_raster(
];
out_dataset
.set_geo_transform(&out_transform)
- .map_err(|e| exec_datafusion_err!("Failed to set geotransform: {}", e))?;
+ .context("Failed to set geotransform")?;
- let provider = thread_local_provider(gdal)
- .map_err(|e| exec_datafusion_err!("Failed to init GDAL provider: {}", e))?;
+ let provider = thread_local_provider(gdal).context("Failed to init GDAL provider")?;
let ref_raster_ds = provider
.raster_ref_to_gdal(reference_raster)
- .map_err(|e| exec_datafusion_err!("Failed to create GDAL dataset: {}", e))?;
+ .context("Failed to create GDAL dataset")?;
if let Ok(srs) = ref_raster_ds.as_dataset().spatial_ref() {
out_dataset
.set_spatial_ref(&srs)
- .map_err(|e| exec_datafusion_err!("Failed to set spatial reference: {}", e))?;
+ .context("Failed to set spatial reference")?;
}
let init_value =
@@ -524,16 +522,16 @@ fn as_raster(
let nodata = cast_f64_to_band_value(nodata, band_type, "nodata value")?;
let band = out_dataset
.rasterband(1)
- .map_err(|e| exec_datafusion_err!("Failed to get output band: {}", e))?;
+ .context("Failed to get output band")?;
set_band_nodata(&band, nodata)?;
}
gdal.rasterize_affine(&out_dataset, &[1], &[geometry], &[burn_value], all_touched)
- .map_err(|e| exec_datafusion_err!("Failed to rasterize geometry: {}", e))?;
+ .context("Failed to rasterize geometry")?;
let band = out_dataset
.rasterband(1)
- .map_err(|e| exec_datafusion_err!("Failed to get output band: {}", e))?;
+ .context("Failed to get output band")?;
let band_bytes = band
.read_as_bytes(
(0, 0),
@@ -541,7 +539,7 @@ fn as_raster(
(out_width, out_height),
None,
)
- .map_err(|e| exec_datafusion_err!("Failed to read band data: {}", e))?;
+ .context("Failed to read band data")?;
let out_grid = Grid {
transform: out_transform,
@@ -621,12 +619,10 @@ fn initialize_band_t<T: sedona_gdal::raster::types::GdalType + Copy>(
height: usize,
init_value: T,
) -> Result<()> {
- let band = dataset
- .rasterband(1)
- .map_err(|e| exec_datafusion_err!("Failed to get output band: {}", e))?;
+ let band = dataset.rasterband(1).context("Failed to get output band")?;
let mut buffer = Buffer::new((width, height), vec![init_value; width * height]);
band.write((0, 0), (width, height), &mut buffer)
- .map_err(|e| exec_datafusion_err!("Failed to initialize band: {}", e))?;
+ .context("Failed to initialize band")?;
Ok(())
}
diff --git a/rust/sedona-raster-gdal/src/rs_clip.rs b/rust/sedona-raster-gdal/src/rs_clip.rs
index 96d38ce86..4ec48fdf9 100644
--- a/rust/sedona-raster-gdal/src/rs_clip.rs
+++ b/rust/sedona-raster-gdal/src/rs_clip.rs
@@ -29,7 +29,7 @@ use datafusion_common::cast::{as_boolean_array, as_float64_array, as_int32_array
use datafusion_common::config::ConfigOptions;
use datafusion_common::error::Result;
use datafusion_common::exec_err;
-use datafusion_common::{exec_datafusion_err, ScalarValue};
+use datafusion_common::ScalarValue;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_common::sedona_internal_err;
use sedona_gdal::gdal::Gdal;
@@ -38,6 +38,7 @@ use arrow_schema::DataType;
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_raster::array::RasterRefImpl;
use sedona_raster::builder::RasterBuilder;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::{is_spatial_dim_pair, RasterRef};
use sedona_raster_functions::crs_utils::{crs_transform_wkb, resolve_crs, with_crs_engine};
use sedona_raster_functions::rs_ensure_loaded::{
@@ -383,7 +384,7 @@ fn clip_raster(
// Parse geometry from WKB
let geometry = gdal
.geometry_from_wkb(geom_wkb)
- .map_err(|e| exec_datafusion_err!("Failed to parse geometry from WKB: {}", e))?;
+ .context("Failed to parse geometry from WKB")?;
// GDAL geotransform: [upper_left_x, scale_x, skew_x, upper_left_y, skew_y, scale_y].
let geotransform = raster_geo_transform(raster)?;
@@ -439,7 +440,7 @@ fn clip_raster(
// `band_idx` is 1-based; the `band`/`band_name` accessors are 0-based.
let band = raster
.band(band_idx - 1)
- .map_err(|e| exec_datafusion_err!("Failed to get band {}: {}", band_idx, e))?;
+ .with_context(|| format!("Failed to get band {band_idx}"))?;
let band_name = raster.band_name(band_idx - 1).map(|s| s.to_string());
let data_type = band.data_type();
@@ -483,12 +484,12 @@ fn clip_raster(
// `as_contiguous` borrows the band bytes; we only ever read them here
// (the mask/crop helpers write into the band's output buffer), so no
// copy is needed.
- let nd_buffer = band.nd_buffer().map_err(|e| {
- exec_datafusion_err!("RS_Clip: failed to read band {}: {}", band_idx, e)
- })?;
- let original_data = nd_buffer.as_contiguous().map_err(|e| {
- exec_datafusion_err!("RS_Clip: band {} is not contiguous: {}", band_idx, e)
- })?;
+ let nd_buffer = band
+ .nd_buffer()
+ .with_context(|| format!("RS_Clip: failed to read band {band_idx}"))?;
+ let original_data = nd_buffer
+ .as_contiguous()
+ .with_context(|| format!("RS_Clip: band {band_idx} is not contiguous"))?;
// nodata precedence: the explicit argument, then the band's own nodata
// bytes (used verbatim — no lossy f64 round-trip for Int64/UInt64),
@@ -498,9 +499,8 @@ fn clip_raster(
// rather than silently saturating — e.g. -9999 on UInt8 would collide
// with real zero-adjacent data.
let nodata_bytes: Vec<u8> = match custom_nodata {
- Some(cn) => nodata_f64_to_bytes(cn, &data_type).map_err(|e| {
- exec_datafusion_err!("RS_Clip: invalid no_data_value for band {band_idx}: {e}")
- })?,
+ Some(cn) => nodata_f64_to_bytes(cn, &data_type)
+ .with_context(|| format!("RS_Clip: invalid no_data_value for band {band_idx}"))?,
None => match band.nodata() {
Some(bytes) => bytes.to_vec(),
None => data_type.min_value_le_bytes(),
@@ -722,7 +722,7 @@ fn build_clipped_raster(
&spatial_shape,
original_raster.crs(),
)
- .map_err(|e| exec_datafusion_err!("Failed to start raster: {}", e))?;
+ .context("Failed to start raster")?;
for band in clipped_data.bands {
let dim_names: Vec<&str> = band.dim_names.iter().map(String::as_str).collect();
@@ -739,9 +739,7 @@ fn build_clipped_raster(
)?;
}
- builder
- .finish_raster()
- .map_err(|e| exec_datafusion_err!("Failed to finish raster: {}", e))?;
+ builder.finish_raster().context("Failed to finish raster")?;
Ok(())
}
diff --git a/rust/sedona-raster-gdal/src/rs_from_gdal_raster.rs b/rust/sedona-raster-gdal/src/rs_from_gdal_raster.rs
index 281692dce..fec0598a3 100644
--- a/rust/sedona-raster-gdal/src/rs_from_gdal_raster.rs
+++ b/rust/sedona-raster-gdal/src/rs_from_gdal_raster.rs
@@ -27,7 +27,7 @@ use arrow_array::{cast::AsArray, Array};
use arrow_schema::DataType;
use datafusion_common::config::ConfigOptions;
use datafusion_common::error::Result;
-use datafusion_common::{exec_datafusion_err, ScalarValue};
+use datafusion_common::ScalarValue;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_common::sedona_internal_err;
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
@@ -35,6 +35,7 @@ use sedona_gdal::gdal::Gdal;
use sedona_gdal::gdal_dyn_bindgen::{GDAL_OF_RASTER, GDAL_OF_READONLY};
use sedona_gdal::raster::types::DatasetOptions;
use sedona_raster::builder::RasterBuilder;
+use sedona_raster::error::RasterResultExt;
use sedona_raster_functions::rs_ensure_loaded::RETURNS_BYTES_METADATA_KEY;
use sedona_schema::datatypes::{SedonaType, RASTER};
use sedona_schema::matchers::ArgMatcher;
@@ -83,7 +84,7 @@ impl RsFromGDALRaster {
fn append_gdal_raster(gdal: &Gdal, content: &[u8], builder: &mut RasterBuilder) -> Result<()> {
let vsi_path = Self::generate_vsi_path();
gdal.create_mem_file(&vsi_path, content)
- .map_err(|e| exec_datafusion_err!("Failed to create VSI memory file: {e}"))?;
+ .context("Failed to create VSI memory file")?;
// Open + decode, then always unlink the VSI file (the dataset is dropped
// at the end of the closure, before the unlink).
@@ -113,9 +114,7 @@ impl RsFromGDALRaster {
) -> Result<()> {
for row in rows {
match row {
- None => builder
- .append_null()
- .map_err(|e| exec_datafusion_err!("Failed to append null: {e}"))?,
+ None => builder.append_null().context("Failed to append null")?,
Some(content) => Self::append_gdal_raster(gdal, content, builder)?,
}
}
@@ -132,9 +131,7 @@ impl RsFromGDALRaster {
) -> Result<arrow_array::StructArray> {
let mut builder = RasterBuilder::new(1);
Self::append_gdal_raster(gdal, content, &mut builder)?;
- builder
- .finish()
- .map_err(|e| exec_datafusion_err!("Failed to build raster: {e}"))
+ Ok(builder.finish().context("Failed to build raster")?)
}
}
@@ -166,7 +163,7 @@ impl SedonaScalarKernel for RsFromGDALRaster {
let content_array = match &args[0] {
ColumnarValue::Scalar(scalar) => scalar
.to_array()
- .map_err(|e| exec_datafusion_err!("Failed to convert scalar to array: {e}"))?,
+ .context("Failed to convert scalar to array")?,
ColumnarValue::Array(array) => array.clone(),
};
@@ -188,9 +185,7 @@ impl SedonaScalarKernel for RsFromGDALRaster {
)
}
}
- let result = builder
- .finish()
- .map_err(|e| exec_datafusion_err!("Failed to build raster: {e}"))?;
+ let result = builder.finish().context("Failed to build raster")?;
match &args[0] {
ColumnarValue::Scalar(_) => Ok(ColumnarValue::Scalar(ScalarValue::try_from_array(
diff --git a/rust/sedona-raster-gdal/src/rs_metadata.rs b/rust/sedona-raster-gdal/src/rs_metadata.rs
index 6cdd50121..46089b41c 100644
--- a/rust/sedona-raster-gdal/src/rs_metadata.rs
+++ b/rust/sedona-raster-gdal/src/rs_metadata.rs
@@ -25,9 +25,9 @@ use arrow_buffer::NullBufferBuilder;
use arrow_schema::{DataType, Field, Fields};
use datafusion_common::config::ConfigOptions;
use datafusion_common::error::Result;
-use datafusion_common::exec_datafusion_err;
use datafusion_expr::{ColumnarValue, Volatility};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_schema::crs::deserialize_crs;
use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
@@ -108,8 +108,7 @@ impl SedonaScalarKernel for RsMetaData {
with_gdal(|gdal| {
configure_thread_local_options(gdal, config_options)?;
- let provider = thread_local_provider(gdal)
- .map_err(|e| exec_datafusion_err!("Failed to init GDAL provider: {e}"))?;
+ let provider = thread_local_provider(gdal).context("Failed to init GDAL provider")?;
executor.execute_raster_void(|_i, raster_opt| {
match raster_opt {
@@ -162,13 +161,13 @@ impl SedonaScalarKernel for RsMetaData {
tile_width_builder.append_value(0);
tile_height_builder.append_value(0);
} else {
- let dataset = provider.raster_ref_to_gdal(raster).map_err(|e| {
- exec_datafusion_err!("Failed to create GDAL dataset: {e}")
- })?;
+ let dataset = provider
+ .raster_ref_to_gdal(raster)
+ .context("Failed to create GDAL dataset")?;
let band1 = dataset
.as_dataset()
.rasterband(1)
- .map_err(|e| exec_datafusion_err!("Failed to get band 1: {e}"))?;
+ .context("Failed to get band 1")?;
let (block_x, block_y) = band1.block_size();
tile_width_builder.append_value(block_x as u64);
tile_height_builder.append_value(block_y as u64);
diff --git a/rust/sedona-raster-gdal/src/rs_polygonize.rs b/rust/sedona-raster-gdal/src/rs_polygonize.rs
index 49a087117..b15aa94ae 100644
--- a/rust/sedona-raster-gdal/src/rs_polygonize.rs
+++ b/rust/sedona-raster-gdal/src/rs_polygonize.rs
@@ -35,6 +35,7 @@ use sedona_gdal::gdal::Gdal;
use sedona_gdal::gdal_dyn_bindgen::{OGRFieldType, OGRwkbGeometryType};
use sedona_gdal::raster::polygonize::PolygonizeOptions;
use sedona_gdal::raster::types::GdalDataType;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_raster_functions::RasterExecutor;
use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY_ITEM_CRS};
@@ -94,12 +95,11 @@ impl SedonaScalarKernel for RsPolygonize {
with_gdal(|gdal| {
configure_thread_local_options(gdal, config_options)?;
- let provider = thread_local_provider(gdal)
- .map_err(|e| exec_datafusion_err!("Failed to init GDAL provider: {e}"))?;
+ let provider = thread_local_provider(gdal).context("Failed to init GDAL provider")?;
// Note: We deliberately use "Memory" instead of "MEM" to be compatible with older GDAL versions.
let mem_driver = gdal
.get_driver_by_name("Memory")
- .map_err(|e| exec_datafusion_err!("Failed to get Memory driver: {e}"))?;
+ .context("Failed to get Memory driver")?;
executor.execute_raster_void(|_, raster_opt| {
let band_opt = band_iter.next().expect("band iteration should match rows");
@@ -122,7 +122,7 @@ impl SedonaScalarKernel for RsPolygonize {
let raster_ds = provider
.raster_ref_to_gdal(raster)
- .map_err(|e| exec_datafusion_err!("Failed to create GDAL dataset: {e}"))?;
+ .context("Failed to create GDAL dataset")?;
let crs_str = raster.crs();
let mut num_polygons = 0;
@@ -234,12 +234,12 @@ where
let raster_band = gdal_dataset
.rasterband(band_num)
- .map_err(|e| exec_datafusion_err!("Failed to get band {}: {}", band_num, e))?;
+ .with_context(|| format!("Failed to get band {band_num}"))?;
// Create a memory dataset containing one vector layer to hold the polygonized output.
let vector_ds = mem_driver
.create_vector_only("")
- .map_err(|e| exec_datafusion_err!("Failed to create vector dataset: {e}"))?;
+ .context("Failed to create vector dataset")?;
let spatial_ref = gdal_dataset.spatial_ref().ok();
let mut layer = vector_ds
@@ -249,14 +249,14 @@ where
ty: OGRwkbGeometryType::wkbPolygon,
options: None,
})
- .map_err(|e| exec_datafusion_err!("Failed to create layer: {e}"))?;
+ .context("Failed to create layer")?;
let field_defn = gdal
.create_field_defn("value", OGRFieldType::OFTReal)
- .map_err(|e| exec_datafusion_err!("Failed to create field definition: {e}"))?;
+ .context("Failed to create field definition")?;
layer
.create_field(&field_defn)
- .map_err(|e| exec_datafusion_err!("Failed to add field to layer: {e}"))?;
+ .context("Failed to add field to layer")?;
// Polygonize the raster band into the vector layer, using the "value" field to store pixel values.
let band_type = raster_band.band_type();
@@ -264,10 +264,10 @@ where
if is_float {
gdal.fpolygonize(&raster_band, None, &layer, 0, &PolygonizeOptions::default())
- .map_err(|e| exec_datafusion_err!("GDAL fpolygonize failed: {e}"))?;
+ .context("GDAL fpolygonize failed")?;
} else {
gdal.polygonize(&raster_band, None, &layer, 0, &PolygonizeOptions::default())
- .map_err(|e| exec_datafusion_err!("GDAL polygonize failed: {e}"))?;
+ .context("GDAL polygonize failed")?;
}
// Extract the WKB geometry and value for each feature in the layer.
@@ -276,16 +276,14 @@ where
let geom = feature
.geometry()
.ok_or_else(|| exec_datafusion_err!("Polygonize output feature missing geometry"))?;
- let wkb = geom
- .wkb()
- .map_err(|e| exec_datafusion_err!("Failed to export geometry to WKB: {e}"))?;
+ let wkb = geom.wkb().context("Failed to export geometry to WKB")?;
let idx = match value_field_idx {
Some(idx) => idx,
None => {
let idx = feature
.field_index("value")
- .map_err(|e| exec_datafusion_err!("Missing 'value' field: {e}"))?;
+ .context("Missing 'value' field")?;
value_field_idx = Some(idx);
idx
}
diff --git a/rust/sedona-raster-gdal/src/rs_tile.rs b/rust/sedona-raster-gdal/src/rs_tile.rs
index d3044be1c..c96b950d8 100644
--- a/rust/sedona-raster-gdal/src/rs_tile.rs
+++ b/rust/sedona-raster-gdal/src/rs_tile.rs
@@ -43,6 +43,7 @@ use sedona_common::sedona_internal_err;
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_raster::array::RasterRefImpl;
use sedona_raster::builder::RasterBuilder;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::geo_transform::{GeoTransform, GeoTransformEx};
use sedona_raster::traits::{is_spatial_dim_pair, nodata_f64_to_bytes, RasterRef};
use sedona_raster_functions::rs_ensure_loaded::NEEDS_PIXELS_METADATA_KEY;
@@ -355,9 +356,7 @@ impl RsTile {
/// The `List<Struct<x, y, tile>>` element struct fields, mirroring Spark's
/// `(x int, y int, tile raster)` generator output.
fn tile_struct_fields() -> Result<Fields> {
- let tile_field = RASTER
- .to_storage_field("tile", false)
- .map_err(|e| exec_datafusion_err!("RS_Tile: {e}"))?;
+ let tile_field = RASTER.to_storage_field("tile", false).context("RS_Tile")?;
Ok(Fields::from(vec![
Field::new("x", DataType::Int32, false),
Field::new("y", DataType::Int32, false),
@@ -558,7 +557,7 @@ fn append_tile(
&tile_spatial_shape,
raster.crs(),
)
- .map_err(|e| exec_datafusion_err!("RS_Tile: failed to start raster: {e}"))?;
+ .context("RS_Tile: failed to start raster")?;
for &band_idx in band_indices {
append_tile_band(raster, band_idx, window, params, rast_builder)?;
@@ -566,7 +565,7 @@ fn append_tile(
rast_builder
.finish_raster()
- .map_err(|e| exec_datafusion_err!("RS_Tile: failed to finish raster: {e}"))?;
+ .context("RS_Tile: failed to finish raster")?;
Ok(())
}
@@ -581,7 +580,7 @@ fn append_tile_band(
// `band_idx` is 1-based; the `band`/`band_name` accessors are 0-based.
let band = raster
.band(band_idx - 1)
- .map_err(|e| exec_datafusion_err!("RS_Tile: failed to get band {band_idx}: {e}"))?;
+ .with_context(|| format!("RS_Tile: failed to get band {band_idx}"))?;
let band_name = raster.band_name(band_idx - 1).map(|s| s.to_string());
let data_type = band.data_type();
@@ -617,10 +616,10 @@ fn append_tile_band(
// the tile's own buffer, so no copy of the source is needed here).
let nd_buffer = band
.nd_buffer()
- .map_err(|e| exec_datafusion_err!("RS_Tile: failed to read band {band_idx}: {e}"))?;
+ .with_context(|| format!("RS_Tile: failed to read band {band_idx}"))?;
let source = nd_buffer
.as_contiguous()
- .map_err(|e| exec_datafusion_err!("RS_Tile: band {band_idx} is not contiguous: {e}"))?;
+ .with_context(|| format!("RS_Tile: band {band_idx} is not contiguous"))?;
let in_plane_bytes = width * height * byte_size;
if source.len() != n_planes * in_plane_bytes {
return exec_err!(
@@ -637,9 +636,8 @@ fn append_tile_band(
// doesn't fit the band dtype errors rather than silently saturating.
let (pad_fill, tile_nodata): (Option<Vec<u8>>, Option<Vec<u8>>) = if window.needs_padding() {
let fill = match params.nodata {
- Some(value) => nodata_f64_to_bytes(value, &data_type).map_err(|e| {
- exec_datafusion_err!("RS_Tile: invalid nodata for band {band_idx}: {e}")
- })?,
+ Some(value) => nodata_f64_to_bytes(value, &data_type)
+ .with_context(|| format!("RS_Tile: invalid nodata for band {band_idx}"))?,
None => match band.nodata() {
Some(bytes) => bytes.to_vec(),
None => data_type.min_value_le_bytes(),
@@ -755,13 +753,13 @@ fn assemble_tile_list(
let tile_array: ArrayRef = Arc::new(
rast_builder
.finish()
- .map_err(|e| exec_datafusion_err!("RS_Tile: failed to build tiles: {e}"))?,
+ .context("RS_Tile: failed to build tiles")?,
);
let fields = tile_struct_fields()?;
let element_struct =
StructArray::try_new(fields.clone(), vec![x_array, y_array, tile_array], None)
- .map_err(|e| exec_datafusion_err!("RS_Tile: failed to build tile struct: {e}"))?;
+ .context("RS_Tile: failed to build tile struct")?;
let list_field = Arc::new(Field::new("item", DataType::Struct(fields), true));
ListArray::try_new(
diff --git a/rust/sedona-raster-gdal/src/rs_zonal_stats.rs b/rust/sedona-raster-gdal/src/rs_zonal_stats.rs
index 138e28ca0..e32190c98 100644
--- a/rust/sedona-raster-gdal/src/rs_zonal_stats.rs
+++ b/rust/sedona-raster-gdal/src/rs_zonal_stats.rs
@@ -64,6 +64,7 @@ use sedona_common::sedona_internal_err;
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_gdal::gdal::Gdal;
use sedona_raster::array::RasterRefImpl;
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_raster_functions::crs_utils::{align_wkb_to_crs, resolve_crs, with_crs_engine};
use sedona_raster_functions::rs_ensure_loaded::NEEDS_PIXELS_METADATA_KEY;
@@ -671,7 +672,7 @@ fn collect_zonal_values(
// `band_num` is 1-based (>= 1, guaranteed by `resolve_band`); `band` is 0-based.
let band = raster
.band(band_num - 1)
- .map_err(|e| exec_datafusion_err!("RS_ZonalStats: failed to read band {band_num}: {e}"))?;
+ .with_context(|| format!("RS_ZonalStats: failed to read band {band_num}"))?;
if !band.is_spatial_2d() {
return exec_err!(
"RS_ZonalStats supports 2-D rasters only; band {band_num} is not a 2-D (y, x) grid"
@@ -701,7 +702,7 @@ fn collect_zonal_values(
// pixels, so it is count 0 rather than no-intersection.
let geometry = gdal
.geometry_from_wkb(geom_wkb)
- .map_err(|e| exec_datafusion_err!("RS_ZonalStats: failed to parse geometry: {e}"))?;
+ .context("RS_ZonalStats: failed to parse geometry")?;
let Some(window) = envelope_window(&geometry, &transform, width, height)? else {
scratch.clear();
return Ok(RoiCoverage::Collected);
@@ -722,10 +723,10 @@ fn collect_zonal_values(
// Read the band once (zero-copy borrow) and collect the selected values.
let nd_buffer = band
.nd_buffer()
- .map_err(|e| exec_datafusion_err!("RS_ZonalStats: failed to read band {band_num}: {e}"))?;
- let band_bytes = nd_buffer.as_contiguous().map_err(|e| {
- exec_datafusion_err!("RS_ZonalStats: band {band_num} is not contiguous: {e}")
- })?;
+ .with_context(|| format!("RS_ZonalStats: failed to read band {band_num}"))?;
+ let band_bytes = nd_buffer
+ .as_contiguous()
+ .with_context(|| format!("RS_ZonalStats: band {band_num} is not contiguous"))?;
let expected = width
.checked_mul(height)
.and_then(|n| n.checked_mul(byte_size))
diff --git a/rust/sedona-raster-gdal/src/utils.rs b/rust/sedona-raster-gdal/src/utils.rs
index 27d5a067f..972dbc875 100644
--- a/rust/sedona-raster-gdal/src/utils.rs
+++ b/rust/sedona-raster-gdal/src/utils.rs
@@ -34,6 +34,7 @@ use arrow_schema::ArrowError;
use sedona_common::sedona_internal_err;
use sedona_raster::array::RasterRefImpl;
use sedona_raster::builder::{RasterBuilder, StartBandArgs};
+use sedona_raster::error::RasterResultExt;
use sedona_raster::traits::RasterRef;
use sedona_schema::raster::BandDataType;
@@ -48,7 +49,7 @@ pub fn append_as_indb_raster(dataset: &Dataset, builder: &mut RasterBuilder) ->
let geotransform = dataset
.geo_transform()
- .map_err(|e| exec_datafusion_err!("Failed to get geotransform: {}", e))?;
+ .context("Failed to get geotransform")?;
let grid = Grid::from_gdal(geotransform, width, height);
@@ -58,13 +59,13 @@ pub fn append_as_indb_raster(dataset: &Dataset, builder: &mut RasterBuilder) ->
.and_then(|sr: SpatialRef| sr.to_projjson().ok());
grid.start_raster_into(builder, crs.as_deref())
- .map_err(|e| exec_datafusion_err!("Failed to start raster: {}", e))?;
+ .context("Failed to start raster")?;
let band_count = dataset.raster_count();
for band_idx in 1..=band_count {
let band = dataset
.rasterband(band_idx)
- .map_err(|e| exec_datafusion_err!("Failed to get band {}: {}", band_idx, e))?;
+ .with_context(|| format!("Failed to get band {band_idx}"))?;
let gdal_type = band.band_type();
let band_data_type = gdal_to_band_data_type(gdal_type)
@@ -74,7 +75,7 @@ pub fn append_as_indb_raster(dataset: &Dataset, builder: &mut RasterBuilder) ->
let band_data = band
.read_as_bytes((0, 0), (width, height), (width, height), None)
- .map_err(|e| exec_datafusion_err!("Failed to read band {} data: {}", band_idx, e))?;
+ .with_context(|| format!("Failed to read band {band_idx} data"))?;
// Single-band native read: the 2-D `["y", "x"]` band `start_band_2d`
// would build, shape `[height, width]`.
append_band_from_buffer(
@@ -90,9 +91,7 @@ pub fn append_as_indb_raster(dataset: &Dataset, builder: &mut RasterBuilder) ->
)?;
}
- builder
- .finish_raster()
- .map_err(|e| exec_datafusion_err!("Failed to finish raster: {}", e))?;
+ builder.finish_raster().context("Failed to finish raster")?;
Ok(())
}
@@ -108,19 +107,14 @@ pub fn append_as_outdb_raster(gdal: &Gdal, path: &str, builder: &mut RasterBuild
..Default::default()
},
)
- .map_err(|e| {
- exec_datafusion_err!(
- "Failed to open raster file '{}' (GDAL path '{}'): {}",
- path,
- gdal_path,
- e
- )
+ .with_context(|| {
+ format!("Failed to open raster file '{path}' (GDAL path '{gdal_path}')")
})?;
let (width, height) = dataset.raster_size();
let geotransform = dataset
.geo_transform()
- .map_err(|e| exec_datafusion_err!("Failed to get geotransform: {}", e))?;
+ .context("Failed to get geotransform")?;
let grid = Grid::from_gdal(geotransform, width, height);
let crs = dataset
@@ -134,7 +128,7 @@ pub fn append_as_outdb_raster(gdal: &Gdal, path: &str, builder: &mut RasterBuild
for band_idx in 1..=band_count {
let band = dataset
.rasterband(band_idx)
- .map_err(|e| exec_datafusion_err!("Failed to get band {}: {}", band_idx, e))?;
+ .with_context(|| format!("Failed to get band {band_idx}"))?;
let gdal_type = band.band_type();
let band_data_type = gdal_to_band_data_type(gdal_type)
@@ -185,7 +179,7 @@ pub fn append_nd_from_dataset(
let geotransform = dataset
.geo_transform()
- .map_err(|e| exec_datafusion_err!("Failed to get geotransform: {}", e))?;
+ .context("Failed to get geotransform")?;
let grid = Grid::from_gdal(geotransform, width, height);
let crs = dataset
@@ -249,17 +243,19 @@ impl Grid {
crs: Option<&str>,
) -> Result<(), ArrowError> {
let t = self.transform;
- builder.start_raster_2d(
- self.width,
- self.height,
- t[0],
- t[3],
- t[1],
- t[5],
- t[2],
- t[4],
- crs,
- )
+ builder
+ .start_raster_2d(
+ self.width,
+ self.height,
+ t[0],
+ t[3],
+ t[1],
+ t[5],
+ t[2],
+ t[4],
+ crs,
+ )
+ .map_err(Into::into)
}
/// Bounding box of the four grid corners (handles skew; reduces to
@@ -564,16 +560,14 @@ pub(crate) fn append_band_from_buffer(
nodata: header.nodata,
..StartBandArgs::new(header.dim_names, header.shape, header.data_type)
})
- .map_err(|e| exec_datafusion_err!("Failed to start band: {e}"))?;
+ .context("Failed to start band")?;
// Hand the owned buffer to Arrow as a shared data block (a refcount bump,
// never a copy). `append_band_data_buffer` also stores sub-inline-threshold
// bands inline, keeping the view canonical.
builder
.append_band_data_buffer(&Buffer::from(band_data), 0, band_data_len)
- .map_err(|e| exec_datafusion_err!("Failed to append band data: {e}"))?;
- builder
- .finish_band()
- .map_err(|e| exec_datafusion_err!("Failed to finish band: {e}"))?;
+ .context("Failed to append band data")?;
+ builder.finish_band().context("Failed to finish band")?;
Ok(())
}
@@ -671,7 +665,7 @@ fn append_nd_from_dataset_inner(
let out_height = grid.height as usize;
grid.start_raster_into(builder, crs)
- .map_err(|e| exec_datafusion_err!("Failed to start raster: {}", e))?;
+ .context("Failed to start raster")?;
let total_planes: usize = layout.bands.iter().map(|b| b.plane_count).sum();
let gdal_band_count = dataset.raster_count();
@@ -706,7 +700,7 @@ fn append_nd_from_dataset_inner(
let gdal_band = gdal_band_base + plane;
let band = dataset
.rasterband(gdal_band)
- .map_err(|e| exec_datafusion_err!("Failed to get band {gdal_band}: {e}"))?;
+ .with_context(|| format!("Failed to get band {gdal_band}"))?;
let plane = band
.read_as_bytes(
(0, 0),
@@ -714,9 +708,7 @@ fn append_nd_from_dataset_inner(
(out_width, out_height),
alg,
)
- .map_err(|e| {
- exec_datafusion_err!("Failed to read band {gdal_band} data: {e}")
- })?;
+ .with_context(|| format!("Failed to read band {gdal_band} data"))?;
out.extend_from_slice(&plane);
Ok(())
},
@@ -724,9 +716,7 @@ fn append_nd_from_dataset_inner(
gdal_band += plan.plane_count;
}
- builder
- .finish_raster()
- .map_err(|e| exec_datafusion_err!("Failed to finish raster: {}", e))?;
+ builder.finish_raster().context("Failed to finish raster")?;
Ok(())
}
diff --git a/rust/sedona-raster-zarr/src/loader.rs b/rust/sedona-raster-zarr/src/loader.rs
index 9a1db9667..4cbacf1cb 100644
--- a/rust/sedona-raster-zarr/src/loader.rs
+++ b/rust/sedona-raster-zarr/src/loader.rs
@@ -186,7 +186,7 @@ impl ZarrChunkReader {
builder.band_data_writer().append_value([0u8; 0]);
builder.finish_band()?;
}
- builder.finish_raster()
+ Ok(builder.finish_raster()?)
}
}
@@ -218,7 +218,7 @@ impl Iterator for ZarrChunkReader {
}
let struct_arr = match builder.finish() {
Ok(arr) => arr,
- Err(e) => return Some(Err(e)),
+ Err(e) => return Some(Err(e.into())),
};
Some(RecordBatch::try_new(
self.schema.clone(),
diff --git a/rust/sedona-raster/Cargo.toml b/rust/sedona-raster/Cargo.toml
index f6b692429..d728b33bb 100644
--- a/rust/sedona-raster/Cargo.toml
+++ b/rust/sedona-raster/Cargo.toml
@@ -38,6 +38,7 @@ async-trait = { workspace = true }
datafusion-common = { workspace = true }
sedona-common = { workspace = true }
sedona-schema = { workspace = true }
+thiserror = { workspace = true }
[dev-dependencies]
sedona-testing = { workspace = true }
diff --git a/rust/sedona-raster/src/affine_transformation.rs b/rust/sedona-raster/src/affine_transformation.rs
index 7575abf78..8577d5a69 100644
--- a/rust/sedona-raster/src/affine_transformation.rs
+++ b/rust/sedona-raster/src/affine_transformation.rs
@@ -24,9 +24,9 @@
//!
//! [`GeoTransform`]: crate::geo_transform::GeoTransform
+use crate::error::RasterError;
use crate::geo_transform::GeoTransformEx;
use crate::traits::RasterRef;
-use arrow_schema::ArrowError;
/// Computes the rotation angle (in radians) of the raster based on its geotransform metadata.
#[inline]
@@ -57,11 +57,9 @@ pub fn to_raster_coordinate(
raster: &dyn RasterRef,
world_x: f64,
world_y: f64,
-) -> Result<(i64, i64), ArrowError> {
+) -> Result<(i64, i64), RasterError> {
let inverse = raster.transform().invert().map_err(|_| {
- ArrowError::InvalidArgumentError(
- "Cannot compute coordinate: determinant is zero.".to_string(),
- )
+ RasterError::Invalid("Cannot compute coordinate: determinant is zero.".to_string())
})?;
let (rx, ry) = inverse.apply(world_x, world_y);
Ok((rx as i64, ry as i64))
@@ -87,8 +85,8 @@ mod tests {
fn num_bands(&self) -> usize {
0
}
- fn band(&self, index: usize) -> Result<Box<dyn BandRef + '_>, ArrowError> {
- Err(ArrowError::InvalidArgumentError(format!(
+ fn band(&self, index: usize) -> Result<Box<dyn BandRef + '_>, RasterError> {
+ Err(RasterError::Invalid(format!(
"Band index {index} is out of range: this raster has 0 bands"
)))
}
diff --git a/rust/sedona-raster/src/array.rs b/rust/sedona-raster/src/array.rs
index 482df92cd..a4ea487c4 100644
--- a/rust/sedona-raster/src/array.rs
+++ b/rust/sedona-raster/src/array.rs
@@ -15,11 +15,11 @@
// specific language governing permissions and limitations
// under the License.
+use crate::error::RasterError;
use arrow_array::{
Array, BinaryArray, BinaryViewArray, Float64Array, Int64Array, ListArray, StringArray,
StringViewArray, StructArray, UInt32Array,
};
-use arrow_schema::ArrowError;
use datafusion_common::cast::{
as_binary_array, as_binary_view_array, as_float64_array, as_int64_array, as_list_array,
as_string_array, as_string_view_array, as_struct_array, as_uint32_array,
@@ -121,9 +121,9 @@ impl<'a> BandRef for BandRefImpl<'a> {
|| !self.data_array.value(self.band_row).is_empty()
}
- fn nd_buffer(&self) -> Result<NdBuffer<'_>, ArrowError> {
+ fn nd_buffer(&self) -> Result<NdBuffer<'_>, RasterError> {
if !self.is_indb() {
- return Err(ArrowError::NotYetImplemented(
+ return Err(RasterError::Invalid(
"OutDb byte access via nd_buffer() is not yet implemented; \
backend-specific OutDb resolvers are tracked separately"
.to_string(),
@@ -143,7 +143,7 @@ impl<'a> BandRef for BandRefImpl<'a> {
/// Zero-copy override: share the source row's backing `Buffer` into the
/// builder (refcount bump) instead of copying the visible bytes. OutDb
/// bands have an empty data column by design.
- fn append_data_into(&self, builder: &mut RasterBuilder) -> Result<(), ArrowError> {
+ fn append_data_into(&self, builder: &mut RasterBuilder) -> Result<(), RasterError> {
if self.is_indb() {
builder.append_band_data_from(self.data_array, self.band_row)
} else {
@@ -199,10 +199,10 @@ impl<'a> RasterRef for RasterRefImpl<'a> {
self.bands_list.value_length(self.raster_index) as usize
}
- fn band(&self, index: usize) -> Result<Box<dyn BandRef + '_>, ArrowError> {
+ fn band(&self, index: usize) -> Result<Box<dyn BandRef + '_>, RasterError> {
let nbands = self.num_bands();
if index >= nbands {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"Band index {index} is out of range: this raster has {nbands} bands"
)));
}
@@ -217,7 +217,7 @@ impl<'a> RasterRef for RasterRefImpl<'a> {
// Reject 0-D bands at the read boundary. Schema doesn't forbid them
// outright but every consumer assumes ndim >= 1.
if source_shape.is_empty() {
- return Err(ArrowError::ExternalError(Box::new(
+ return Err(RasterError::External(Box::new(
sedona_common::sedona_internal_datafusion_err!(
"band {band_row} has empty source_shape; ndim must be >= 1"
),
@@ -229,7 +229,7 @@ impl<'a> RasterRef for RasterRefImpl<'a> {
// here is appropriate.
let data_type_value = self.band_datatype_array.value(band_row);
let data_type = BandDataType::try_from_u32(data_type_value).ok_or_else(|| {
- ArrowError::ExternalError(Box::new(sedona_common::sedona_internal_datafusion_err!(
+ RasterError::External(Box::new(sedona_common::sedona_internal_datafusion_err!(
"band {band_row} has unknown data_type discriminant {data_type_value}"
)))
})?;
@@ -246,7 +246,7 @@ impl<'a> RasterRef for RasterRefImpl<'a> {
// request/response must round-trip the view — tracked in
// <https://github.com/apache/sedona-db/issues/897>.
if !self.band_view_list.is_null(band_row) {
- return Err(ArrowError::ExternalError(Box::new(
+ return Err(RasterError::External(Box::new(
sedona_common::sedona_internal_datafusion_err!(
"non-null view row at band {band_row}: view composition is not yet implemented"
),
@@ -418,9 +418,9 @@ impl<'a> RasterStructArray<'a> {
///
/// Returns an error if the array doesn't have the expected raster schema.
#[inline]
- pub fn try_new(raster_array: &'a StructArray) -> Result<Self, ArrowError> {
+ pub fn try_new(raster_array: &'a StructArray) -> Result<Self, RasterError> {
if raster_array.fields().len() != raster_indices::FIELD_COUNT {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Unexpected column count for raster array".to_string(),
));
}
@@ -439,7 +439,7 @@ impl<'a> RasterStructArray<'a> {
let bands_struct = as_struct_array(bands_list.values())?;
if bands_struct.fields().len() != band_indices::FIELD_COUNT {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Unexpected column count for band array".to_string(),
));
}
@@ -497,9 +497,9 @@ impl<'a> RasterStructArray<'a> {
/// Get a specific raster by index.
#[inline(always)]
- pub fn get(&self, index: usize) -> Result<RasterRefImpl<'a>, ArrowError> {
+ pub fn get(&self, index: usize) -> Result<RasterRefImpl<'a>, RasterError> {
if index >= self.raster_array.len() {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"Invalid raster index: {index}"
)));
}
@@ -869,7 +869,7 @@ mod tests {
let rasters = RasterStructArray::try_new(&mutated).unwrap();
let r = rasters.get(0).unwrap();
// band() surfaces the corruption through the standardized
- // SedonaDB-internal-error message routed via ArrowError::ExternalError.
+ // SedonaDB-internal-error message routed via RasterError::External.
// `Box<dyn BandRef>` isn't `Debug`, so unwrap_err doesn't compile —
// pull the error out via `.err().unwrap()` on the `Option<E>` side.
let err = r.band(0).err().unwrap();
diff --git a/rust/sedona-raster/src/builder.rs b/rust/sedona-raster/src/builder.rs
index 76782f9c8..65accd97c 100644
--- a/rust/sedona-raster/src/builder.rs
+++ b/rust/sedona-raster/src/builder.rs
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+use crate::error::RasterError;
use arrow_array::{
builder::{
ArrayBuilder, BinaryBuilder, BinaryViewBuilder, BooleanBuilder, Float64Builder,
@@ -23,7 +24,7 @@ use arrow_array::{
Array, ArrayRef, BinaryViewArray, ListArray, StructArray,
};
use arrow_buffer::{Buffer, NullBuffer, OffsetBuffer, ScalarBuffer};
-use arrow_schema::{ArrowError, DataType};
+use arrow_schema::DataType;
use std::collections::HashMap;
use std::sync::Arc;
@@ -237,9 +238,9 @@ impl RasterBuilder {
spatial_dims: &[&str],
spatial_shape: &[i64],
crs: Option<&str>,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
if spatial_dims.len() != spatial_shape.len() {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"spatial_dims.len() ({}) must equal spatial_shape.len() ({})",
spatial_dims.len(),
spatial_shape.len()
@@ -296,11 +297,11 @@ impl RasterBuilder {
&mut self,
source: &dyn RasterRef,
overrides: RasterOverrides,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
let transform: [f64; 6] = match overrides.transform {
Some(transform) => transform,
None => source.transform().try_into().map_err(|_| {
- ArrowError::InvalidArgumentError("raster transform is not 6 elements".to_string())
+ RasterError::Invalid("raster transform is not 6 elements".to_string())
})?,
};
let spatial_dims = source.spatial_dims();
@@ -321,7 +322,7 @@ impl RasterBuilder {
&mut self,
source: &dyn RasterRef,
overrides: RasterOverrides,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
self.start_raster_from(source, overrides)?;
for band_idx in 0..source.num_bands() {
source
@@ -349,7 +350,7 @@ impl RasterBuilder {
skew_x: f64,
skew_y: f64,
crs: Option<&str>,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
let transform = [origin_x, scale_x, skew_x, origin_y, skew_y, scale_y];
self.start_raster_nd(&transform, &["x", "y"], &[width, height], crs)?;
self.current_width = width;
@@ -372,7 +373,7 @@ impl RasterBuilder {
/// that identity null sentinel, so an identity `Some` view is stored the
/// same as `None` and a non-identity view is rejected. View persistence is
/// tracked in <https://github.com/apache/sedona-db/issues/897>.
- pub fn start_band(&mut self, args: StartBandArgs<'_>) -> Result<(), ArrowError> {
+ pub fn start_band(&mut self, args: StartBandArgs<'_>) -> Result<(), RasterError> {
let StartBandArgs {
name,
dim_names,
@@ -391,12 +392,12 @@ impl RasterBuilder {
if let Some(view) = view {
let ndim = dim_names.len();
if ndim == 0 {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
"start_band: 0-dimensional bands are not supported".into(),
));
}
if source_shape.len() != ndim || view.len() != ndim {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"start_band: dim_names ({}), source_shape ({}), and view ({}) \
must all have the same length",
ndim,
@@ -407,7 +408,7 @@ impl RasterBuilder {
let view_entries = ViewEntries::new(view.to_vec());
view_entries.validate(source_shape)?;
if !view_entries.is_identity(source_shape) {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
"start_band: persisting a non-identity band view is not yet \
supported (see https://github.com/apache/sedona-db/issues/897); \
materialize the band (e.g. via RS_EnsureContiguous) first"
@@ -417,12 +418,12 @@ impl RasterBuilder {
}
if dim_names.is_empty() {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
"start_band: 0-dimensional bands are not supported".into(),
));
}
if dim_names.len() != source_shape.len() {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"start_band: dim_names ({}) and shape ({}) must have the same length",
dim_names.len(),
source_shape.len(),
@@ -495,9 +496,9 @@ impl RasterBuilder {
&mut self,
data_type: BandDataType,
nodata: Option<&[u8]>,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
if self.current_width == 0 && self.current_height == 0 {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
"start_band_2d requires prior start_raster_2d (width and height are 0)".into(),
));
}
@@ -539,7 +540,7 @@ impl RasterBuilder {
buffer: &Buffer,
offset: u32,
len: u32,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
if len <= MAX_INLINE_VIEW_LEN {
self.band_data
.append_value(&buffer.as_slice()[offset as usize..(offset + len) as usize]);
@@ -554,7 +555,7 @@ impl RasterBuilder {
idx
}
};
- self.band_data.try_append_view(block, offset, len)
+ Ok(self.band_data.try_append_view(block, offset, len)?)
}
/// Append the current band's data by copying row `row` of `src` through —
@@ -570,7 +571,7 @@ impl RasterBuilder {
&mut self,
src: &BinaryViewArray,
row: usize,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
// Arrow BYTE_VIEW layout (u128, little-endian fields), fixed by the
// columnar format spec:
// bits 0..32 length
@@ -594,10 +595,10 @@ impl RasterBuilder {
/// Finish writing the current band.
///
/// Validates that exactly one data value was appended since `start_band()`.
- pub fn finish_band(&mut self) -> Result<(), ArrowError> {
+ pub fn finish_band(&mut self) -> Result<(), RasterError> {
let current_count = self.band_data.len();
if current_count != self.band_data_count_at_start + 1 {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
format!(
"Expected exactly one band data value per band, but got {} appended since start_band()",
current_count - self.band_data_count_at_start
@@ -612,14 +613,14 @@ impl RasterBuilder {
/// Strictly validates every band added since `start_raster_nd`: each name in
/// the top-level `spatial_dims` must appear in the band's own `dim_names`
/// with a size matching the corresponding entry in `spatial_shape`.
- pub fn finish_raster(&mut self) -> Result<(), ArrowError> {
+ pub fn finish_raster(&mut self) -> Result<(), RasterError> {
for (band_idx, (band_dims, band_shape)) in self.current_raster_bands.iter().enumerate() {
for (spatial_idx, spatial_dim) in self.current_spatial_dims.iter().enumerate() {
let pos = band_dims
.iter()
.position(|d| d == spatial_dim)
.ok_or_else(|| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"Band {band_idx} is missing spatial dimension {spatial_dim:?} \
(band dim_names = {band_dims:?})"
))
@@ -627,7 +628,7 @@ impl RasterBuilder {
let expected = self.current_spatial_shape[spatial_idx];
let actual = band_shape[pos];
if actual != expected {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"Band {band_idx} dimension {spatial_dim:?} has size {actual}, \
expected {expected} from top-level spatial_shape"
)));
@@ -645,7 +646,7 @@ impl RasterBuilder {
}
/// Append a null raster.
- pub fn append_null(&mut self) -> Result<(), ArrowError> {
+ pub fn append_null(&mut self) -> Result<(), RasterError> {
// Transform: append 6 zeros
for _ in 0..6 {
self.transform_values.append_value(0.0);
@@ -673,12 +674,12 @@ impl RasterBuilder {
}
/// Finish building and return the constructed StructArray.
- pub fn finish(mut self) -> Result<StructArray, ArrowError> {
+ pub fn finish(mut self) -> Result<StructArray, RasterError> {
// Build transform list
let transform_values = self.transform_values.finish();
let transform_offsets = OffsetBuffer::new(ScalarBuffer::from(self.transform_offsets));
let DataType::List(transform_field) = RasterSchema::transform_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected list type for transform".to_string(),
));
};
@@ -693,7 +694,7 @@ impl RasterBuilder {
let spatial_dims_values = self.spatial_dims_values.finish();
let spatial_dims_offsets = OffsetBuffer::new(ScalarBuffer::from(self.spatial_dims_offsets));
let DataType::List(spatial_dims_field) = RasterSchema::spatial_dims_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected list type for spatial_dims".to_string(),
));
};
@@ -709,7 +710,7 @@ impl RasterBuilder {
let spatial_shape_offsets =
OffsetBuffer::new(ScalarBuffer::from(self.spatial_shape_offsets));
let DataType::List(spatial_shape_field) = RasterSchema::spatial_shape_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected list type for spatial_shape".to_string(),
));
};
@@ -724,7 +725,7 @@ impl RasterBuilder {
let dim_names_values = self.band_dim_names_values.finish();
let dim_names_offsets = OffsetBuffer::new(ScalarBuffer::from(self.band_dim_names_offsets));
let DataType::List(dim_names_field) = RasterSchema::dim_names_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected list type for dim_names".to_string(),
));
};
@@ -739,7 +740,7 @@ impl RasterBuilder {
let source_shape_values = self.band_shape_values.finish();
let source_shape_offsets = OffsetBuffer::new(ScalarBuffer::from(self.band_shape_offsets));
let DataType::List(source_shape_field) = RasterSchema::source_shape_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected list type for source_shape".to_string(),
));
};
@@ -757,12 +758,12 @@ impl RasterBuilder {
let view_steps = self.band_view_steps_values.finish();
let view_offsets = OffsetBuffer::new(ScalarBuffer::from(self.band_view_offsets));
let DataType::List(view_list_field) = RasterSchema::view_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected list type for view".to_string(),
));
};
let DataType::Struct(view_struct_fields) = view_list_field.data_type().clone() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected struct type inside view list".to_string(),
));
};
@@ -792,7 +793,7 @@ impl RasterBuilder {
// Build band struct
let DataType::Struct(band_fields) = RasterSchema::band_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected struct type for band".to_string(),
));
};
@@ -812,7 +813,7 @@ impl RasterBuilder {
// Build bands list
let DataType::List(bands_field) = RasterSchema::bands_type() else {
- return Err(ArrowError::SchemaError(
+ return Err(RasterError::Invalid(
"Expected list type for bands".to_string(),
));
};
diff --git a/rust/sedona-raster/src/error.rs b/rust/sedona-raster/src/error.rs
new file mode 100644
index 000000000..4ab383890
--- /dev/null
+++ b/rust/sedona-raster/src/error.rs
@@ -0,0 +1,192 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! The raster crates' domain error type.
+//!
+//! The raster code used to plumb [`arrow_schema::ArrowError`] everywhere,
+//! which forced a `map_err` at every boundary that produced or consumed a
+//! raster error. [`RasterError`] mirrors `sedona-geometry`'s
+//! `SedonaGeometryError`: a small `thiserror` enum that library accessors
+//! return directly, with `From` bridges so callers use `?` instead of a
+//! hand-written `map_err`.
+//!
+//! The one raster-specific addition over the geometry mirror is the
+//! [`RasterError::Arrow`] variant with `#[from] ArrowError`: raster code
+//! calls Arrow APIs constantly, so `?` over an `ArrowError` needs to resolve
+//! to a `RasterError` without a `map_err`.
+//!
+//! Boundaries:
+//! - `From<ArrowError> for RasterError` — `?` over Arrow calls inside the
+//! raster library.
+//! - `From<RasterError> for DataFusionError` — `?` at UDF boundaries (the
+//! raster-functions / raster-gdal crates return `DataFusionError`).
+//! - `From<RasterError> for ArrowError` — the bridge back for signatures
+//! fixed by Arrow, notably `RecordBatchReader::next`.
+//!
+//! Internal-assertion errors ("this is a SedonaDB bug") are intentionally
+//! *not* a variant here: they stay a boundary concern raised through
+//! `sedona_common::sedona_internal_err!`, which produces a `DataFusionError`
+//! directly — same as `sedona-geometry`, which has no internal variant.
+
+use arrow_schema::ArrowError;
+use datafusion_common::DataFusionError;
+use thiserror::Error;
+
+/// Error type for the raster crates.
+///
+/// Mirrors `sedona_geometry::error::SedonaGeometryError`. Construct
+/// [`RasterError::Invalid`] for a bad-input/precondition failure and
+/// [`RasterError::External`] to carry an arbitrary source error; Arrow
+/// errors convert automatically via `?`.
+#[derive(Error, Debug)]
+pub enum RasterError {
+ /// Invalid input or a violated precondition, described by the message.
+ #[error("{0}")]
+ Invalid(String),
+ /// An Arrow error encountered while building or reading raster arrays.
+ #[error(transparent)]
+ Arrow(#[from] ArrowError),
+ /// Any other source error, boxed.
+ #[error(transparent)]
+ External(Box<dyn std::error::Error + Send + Sync>),
+ /// A raster error with no more specific classification.
+ #[error("Unknown raster error")]
+ Unknown,
+}
+
+impl From<RasterError> for DataFusionError {
+ fn from(value: RasterError) -> Self {
+ match value {
+ // Keep Arrow errors typed so DataFusion renders them as Arrow
+ // errors rather than an opaque external error.
+ RasterError::Arrow(e) => DataFusionError::ArrowError(Box::new(e), None),
+ // `Execution` (not `Internal`/`External`) matches the existing
+ // `exec_datafusion_err!` sites this replaces: a user-facing
+ // execution error without the "this is a DataFusion bug" framing.
+ RasterError::Invalid(msg) => DataFusionError::Execution(msg),
+ RasterError::External(e) => DataFusionError::External(e),
+ RasterError::Unknown => DataFusionError::Execution("Unknown raster error".to_string()),
+ }
+ }
+}
+
+impl From<RasterError> for ArrowError {
+ fn from(value: RasterError) -> Self {
+ match value {
+ RasterError::Arrow(e) => e,
+ other => ArrowError::ExternalError(Box::new(other)),
+ }
+ }
+}
+
+impl From<DataFusionError> for RasterError {
+ fn from(value: DataFusionError) -> Self {
+ // Raster accessors lean on `datafusion_common::cast::as_*`, which
+ // return `DataFusionError`. Carry it as a source so `?` works without
+ // a `map_err` and the original message survives round-tripping back to
+ // `DataFusionError` at the UDF boundary.
+ RasterError::External(Box::new(value))
+ }
+}
+
+/// Attach a message to a fallible result and convert it to a [`RasterError`].
+///
+/// Replaces the `map_err(|e| exec_datafusion_err!("context: {e}"))` pattern:
+/// `foo().context("context")?` formats the message as `"context: {source}"`
+/// and, via the `From` bridges above, resolves under `?` in functions that
+/// return `RasterError`, `DataFusionError`, or `ArrowError`.
+pub trait RasterResultExt<T> {
+ /// Prefix the error with an eagerly-evaluated `context` message.
+ fn context(self, context: impl std::fmt::Display) -> Result<T, RasterError>;
+
+ /// Prefix the error with a lazily-computed message, only paid on error.
+ fn with_context<C, F>(self, context: F) -> Result<T, RasterError>
+ where
+ C: std::fmt::Display,
+ F: FnOnce() -> C;
+}
+
+impl<T, E: std::fmt::Display> RasterResultExt<T> for Result<T, E> {
+ fn context(self, context: impl std::fmt::Display) -> Result<T, RasterError> {
+ self.map_err(|e| RasterError::Invalid(format!("{context}: {e}")))
+ }
+
+ fn with_context<C, F>(self, context: F) -> Result<T, RasterError>
+ where
+ C: std::fmt::Display,
+ F: FnOnce() -> C,
+ {
+ self.map_err(|e| RasterError::Invalid(format!("{}: {e}", context())))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn invalid_and_external_display() {
+ let invalid = RasterError::Invalid("bad band".to_string());
+ assert_eq!(invalid.to_string(), "bad band");
+
+ let source = Box::new(std::io::Error::other("boom"));
+ let external = RasterError::External(source);
+ assert_eq!(external.to_string(), "boom");
+
+ assert_eq!(RasterError::Unknown.to_string(), "Unknown raster error");
+ }
+
+ #[test]
+ fn arrow_round_trips_without_double_wrapping() {
+ // `?` over an ArrowError yields RasterError::Arrow ...
+ let err: RasterError = ArrowError::ComputeError("nope".to_string()).into();
+ assert!(matches!(err, RasterError::Arrow(_)));
+ // ... and the bridge back to ArrowError unwraps it rather than
+ // boxing it as an ExternalError.
+ let back: ArrowError = err.into();
+ assert!(matches!(back, ArrowError::ComputeError(_)));
+ }
+
+ #[test]
+ fn into_datafusion_preserves_kind() {
+ let df: DataFusionError = RasterError::Invalid("bad".to_string()).into();
+ assert!(matches!(df, DataFusionError::Execution(_)));
+
+ let df: DataFusionError = RasterError::Arrow(ArrowError::ComputeError("x".into())).into();
+ assert!(matches!(df, DataFusionError::ArrowError(_, _)));
+ }
+
+ #[test]
+ fn context_prefixes_message() {
+ // Use a plain-Display source so the assertion reads clearly; the point
+ // is that `context` prefixes the message with the source appended.
+ let r: Result<(), RasterError> = Err(RasterError::Invalid("underlying".to_string()));
+ let err = r.context("while doing the thing").unwrap_err();
+ assert_eq!(err.to_string(), "while doing the thing: underlying");
+
+ // Lazy variant is equivalent on the error path.
+ let r: Result<(), RasterError> = Err(RasterError::Invalid("underlying".to_string()));
+ let err = r.with_context(|| "while doing the thing").unwrap_err();
+ assert_eq!(err.to_string(), "while doing the thing: underlying");
+
+ // The source error's own Display is appended verbatim, so an Arrow
+ // source keeps its "Compute error:" prefix.
+ let r: Result<(), ArrowError> = Err(ArrowError::ComputeError("boom".to_string()));
+ let err = r.context("ctx").unwrap_err();
+ assert_eq!(err.to_string(), "ctx: Compute error: boom");
+ }
+}
diff --git a/rust/sedona-raster/src/geo_transform.rs b/rust/sedona-raster/src/geo_transform.rs
index c5cb2ca0f..72a5da27d 100644
--- a/rust/sedona-raster/src/geo_transform.rs
+++ b/rust/sedona-raster/src/geo_transform.rs
@@ -34,7 +34,7 @@
//!
//! [`RasterRef::transform`]: crate::traits::RasterRef::transform
-use arrow_schema::ArrowError;
+use crate::error::RasterError;
/// An affine geo-transform: six coefficients mapping pixel/line to projection coordinates.
///
@@ -59,7 +59,7 @@ pub trait GeoTransformEx {
/// Invert this geo-transform, returning the inverse coefficients for
/// computing (geo_x, geo_y) -> (x, y) transformations.
- fn invert(&self) -> Result<GeoTransform, ArrowError>;
+ fn invert(&self) -> Result<GeoTransform, RasterError>;
/// Rotation angle (radians) implied by the coefficients.
fn rotation(&self) -> f64;
@@ -88,7 +88,7 @@ impl GeoTransformEx for [f64] {
}
/// Pure-Rust equivalent of GDAL's `GDALInvGeoTransform`.
- fn invert(&self) -> Result<GeoTransform, ArrowError> {
+ fn invert(&self) -> Result<GeoTransform, RasterError> {
let gt = self;
// Fast path: no rotation/skew — avoid determinant and precision issues.
@@ -111,7 +111,7 @@ impl GeoTransformEx for [f64] {
.max(gt[4].abs().max(gt[5].abs()));
if det.abs() <= 1e-10 * magnitude * magnitude {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
"Geo transform is uninvertible".to_string(),
));
}
@@ -180,18 +180,18 @@ pub fn geotransform_from_bbox_and_spatial_shape(
height: u64,
width: u64,
registration: Option<&str>,
-) -> Result<GeoTransform, ArrowError> {
+) -> Result<GeoTransform, RasterError> {
let [xmin, ymin, xmax, ymax] = bbox;
// Reject a degenerate or inverted bbox: a zero span gives a non-invertible
// (zero-scale) transform and a negative span isn't north-up.
if !(xmax > xmin && ymax > ymin) {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"bbox must have xmin < xmax and ymin < ymax; got [{xmin}, {ymin}, {xmax}, {ymax}]"
)));
}
// A real raster axis is at least 1 cell.
if height == 0 || width == 0 {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
"raster spatial dimensions must be non-zero to derive a transform from a bbox".into(),
));
}
@@ -213,7 +213,7 @@ pub fn geotransform_from_bbox_and_spatial_shape(
// a cell beyond — the corner sits half a cell outside the bbox.
"node" => {
if width < 2.0 || height < 2.0 {
- return Err(ArrowError::InvalidArgumentError(
+ return Err(RasterError::Invalid(
"node-registered grid must be at least 2 cells in each spatial dimension"
.into(),
));
@@ -223,7 +223,7 @@ pub fn geotransform_from_bbox_and_spatial_shape(
(scale_x, scale_y, xmin - scale_x / 2.0, ymax - scale_y / 2.0)
}
other => {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"registration must be \"pixel\" or \"node\"; got {other:?}"
)))
}
diff --git a/rust/sedona-raster/src/lib.rs b/rust/sedona-raster/src/lib.rs
index 598623ba3..6de897fb7 100644
--- a/rust/sedona-raster/src/lib.rs
+++ b/rust/sedona-raster/src/lib.rs
@@ -19,6 +19,7 @@ pub mod affine_transformation;
pub mod array;
pub mod builder;
pub mod display;
+pub mod error;
pub mod geo_transform;
pub mod raster_loader;
pub mod traits;
diff --git a/rust/sedona-raster/src/traits.rs b/rust/sedona-raster/src/traits.rs
index 055392138..e0143a44d 100644
--- a/rust/sedona-raster/src/traits.rs
+++ b/rust/sedona-raster/src/traits.rs
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-use arrow_schema::ArrowError;
+use crate::error::RasterError;
use sedona_schema::raster::BandDataType;
use crate::builder::{RasterBuilder, StartBandArgs};
@@ -95,9 +95,9 @@ impl<'a> NdBuffer<'a> {
/// (<https://github.com/apache/sedona-db/issues/899>). Never copies or
/// allocates — a strided layout returns an error, it is not materialized
/// here.
- pub fn as_contiguous(&self) -> Result<&'a [u8], ArrowError> {
+ pub fn as_contiguous(&self) -> Result<&'a [u8], RasterError> {
if !self.is_contiguous() {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"band view is not contiguous (shape {:?}, strides {:?}); \
materialize it with RS_EnsureContiguous before contiguous \
byte access — see https://github.com/apache/sedona-db/issues/899",
@@ -110,7 +110,7 @@ impl<'a> NdBuffer<'a> {
let buffer: &'a [u8] = self.buffer;
match start.checked_add(len) {
Some(end) if end <= buffer.len() => Ok(&buffer[start..end]),
- _ => Err(ArrowError::ExternalError(Box::new(
+ _ => Err(RasterError::External(Box::new(
sedona_common::sedona_internal_datafusion_err!(
"contiguous region [{start}, {start}+{len}) is out of bounds \
for buffer of length {}",
@@ -139,12 +139,12 @@ impl<'a> NdBuffer<'a> {
///
/// This is the shared convention consumers use to recover the source path and
/// band from a band's `outdb_uri()`.
-pub fn split_outdb_band_fragment(uri: &str) -> Result<(String, u32), ArrowError> {
+pub fn split_outdb_band_fragment(uri: &str) -> Result<(String, u32), RasterError> {
if let Some((prefix, fragment)) = uri.rsplit_once('#') {
if let Some(band_str) = fragment.strip_prefix("band=") {
return match band_str.parse::<u32>() {
Ok(band) if band >= 1 => Ok((prefix.to_string(), band)),
- _ => Err(ArrowError::InvalidArgumentError(format!(
+ _ => Err(RasterError::Invalid(format!(
"Invalid band index in outdb URI fragment '#band={band_str}': expected a positive integer in 1..=u32::MAX"
))),
};
@@ -162,12 +162,12 @@ pub trait RasterRef {
/// Number of bands/variables
fn num_bands(&self) -> usize;
- /// Access a band by 0-based index. Returns an `ArrowError` when the
+ /// Access a band by 0-based index. Returns a `RasterError` when the
/// index is out of range or when the underlying schema is malformed
/// (unknown data-type discriminant, corrupt view, etc.). The latter
/// cases route through `sedona_common::sedona_internal_datafusion_err!`
/// so they carry the standardised "SedonaDB internal error" framing.
- fn band(&self, index: usize) -> Result<Box<dyn BandRef + '_>, ArrowError>;
+ fn band(&self, index: usize) -> Result<Box<dyn BandRef + '_>, RasterError>;
/// Band name (e.g., Zarr variable name). None for unnamed bands.
fn band_name(&self, index: usize) -> Option<&str>;
@@ -245,21 +245,19 @@ pub trait RasterRef {
/// Width in pixels — size of the X spatial dimension from the top-level
/// `spatial_shape`. Errors if `spatial_shape` is empty, which is an
/// invariant violation rather than a legitimate "no value" state.
- fn width(&self) -> Result<i64, ArrowError> {
+ fn width(&self) -> Result<i64, RasterError> {
let shape = self.spatial_shape();
shape.first().copied().ok_or_else(|| {
- ArrowError::InvalidArgumentError(
- "raster has no width (spatial_shape is empty)".to_string(),
- )
+ RasterError::Invalid("raster has no width (spatial_shape is empty)".to_string())
})
}
/// Height in pixels — size of the Y spatial dimension from the top-level
/// `spatial_shape`. Errors if `spatial_shape` has fewer than two entries.
- fn height(&self) -> Result<i64, ArrowError> {
+ fn height(&self) -> Result<i64, RasterError> {
let shape = self.spatial_shape();
shape.get(1).copied().ok_or_else(|| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"raster has no height (spatial_shape has {} entries, need >= 2)",
shape.len()
))
@@ -268,12 +266,10 @@ pub trait RasterRef {
/// Look up a band by name. Returns an error if no band has that
/// name or if the matching band is malformed.
- fn band_by_name(&self, name: &str) -> Result<Box<dyn BandRef + '_>, ArrowError> {
+ fn band_by_name(&self, name: &str) -> Result<Box<dyn BandRef + '_>, RasterError> {
let i = (0..self.num_bands())
.find(|&i| self.band_name(i) == Some(name))
- .ok_or_else(|| {
- ArrowError::InvalidArgumentError(format!("Band with name '{name}' not found"))
- })?;
+ .ok_or_else(|| RasterError::Invalid(format!("Band with name '{name}' not found")))?;
self.band(i)
}
}
@@ -416,7 +412,7 @@ pub trait BandRef {
/// `offset` are computed by composing the view with the source's
/// natural C-order byte strides. Strides may be zero (broadcast) or
/// negative (reverse iteration).
- fn nd_buffer(&self) -> Result<NdBuffer<'_>, ArrowError>;
+ fn nd_buffer(&self) -> Result<NdBuffer<'_>, RasterError>;
/// Nodata value interpreted as f64.
///
@@ -431,7 +427,7 @@ pub trait BandRef {
/// value. Use `nodata()` directly to recover the exact bytes when full
/// integer precision matters (e.g. when nodata is the type's extreme
/// value like `0xFF…FF`).
- fn nodata_as_f64(&self) -> Result<Option<f64>, ArrowError> {
+ fn nodata_as_f64(&self) -> Result<Option<f64>, RasterError> {
let bytes = match self.nodata() {
Some(b) => b,
None => return Ok(None),
@@ -465,7 +461,7 @@ pub trait BandRef {
&self,
builder: &mut RasterBuilder,
overrides: BandOverrides<'_>,
- ) -> Result<(), ArrowError> {
+ ) -> Result<(), RasterError> {
let inherited_dims = self.dim_names();
let dim_names: Vec<&str> = match overrides.dim_names {
Some(d) => d.to_vec(),
@@ -499,7 +495,7 @@ pub trait BandRef {
/// [`RasterBuilder::append_band_data_from`]), keeping the buffer plumbing
/// encapsulated rather than exposing a raw `Buffer` accessor. Call after the
/// band's schema has been written (e.g. by [`Self::copy_into`]).
- fn append_data_into(&self, builder: &mut RasterBuilder) -> Result<(), ArrowError> {
+ fn append_data_into(&self, builder: &mut RasterBuilder) -> Result<(), RasterError> {
if self.is_indb() {
let ndb = self.nd_buffer()?;
builder.band_data_writer().append_value(ndb.buffer);
@@ -515,11 +511,11 @@ pub trait BandRef {
/// The bytes are expected to be in little-endian order and exactly match the
/// byte size of the data type. Internal helper for the lossless wrapper;
/// non-i64/u64 callers reach for `nodata_bytes_to_f64_lossless` instead.
-fn nodata_bytes_to_f64(bytes: &[u8], dt: &BandDataType) -> Result<f64, ArrowError> {
+fn nodata_bytes_to_f64(bytes: &[u8], dt: &BandDataType) -> Result<f64, RasterError> {
macro_rules! read_le {
($t:ty, $n:expr) => {{
let arr: [u8; $n] = bytes.try_into().map_err(|_| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"Invalid nodata byte length for {:?}: expected {}, got {}",
dt,
$n,
@@ -533,7 +529,7 @@ fn nodata_bytes_to_f64(bytes: &[u8], dt: &BandDataType) -> Result<f64, ArrowErro
match dt {
BandDataType::UInt8 => {
if bytes.len() != 1 {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"Invalid nodata byte length for UInt8: expected 1, got {}",
bytes.len()
)));
@@ -542,7 +538,7 @@ fn nodata_bytes_to_f64(bytes: &[u8], dt: &BandDataType) -> Result<f64, ArrowErro
}
BandDataType::Int8 => {
if bytes.len() != 1 {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"Invalid nodata byte length for Int8: expected 1, got {}",
bytes.len()
)));
@@ -568,18 +564,18 @@ fn nodata_bytes_to_f64(bytes: &[u8], dt: &BandDataType) -> Result<f64, ArrowErro
/// pixel == nodata) should prefer this over the lossy variant — a rounded
/// `0xFFFF_FFFF_FFFF_FFFE` sentinel can silently collide with a real
/// pixel value.
-pub fn nodata_bytes_to_f64_lossless(bytes: &[u8], dt: &BandDataType) -> Result<f64, ArrowError> {
+pub fn nodata_bytes_to_f64_lossless(bytes: &[u8], dt: &BandDataType) -> Result<f64, RasterError> {
match dt {
BandDataType::UInt64 => {
let arr: [u8; 8] = bytes.try_into().map_err(|_| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"Invalid nodata byte length for UInt64: expected 8, got {}",
bytes.len()
))
})?;
let v = u64::from_le_bytes(arr);
if v > (1u64 << 53) {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"UInt64 nodata value {v} cannot be represented exactly as f64 \
(magnitude > 2^53); use the raw nodata bytes instead"
)));
@@ -588,14 +584,14 @@ pub fn nodata_bytes_to_f64_lossless(bytes: &[u8], dt: &BandDataType) -> Result<f
}
BandDataType::Int64 => {
let arr: [u8; 8] = bytes.try_into().map_err(|_| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"Invalid nodata byte length for Int64: expected 8, got {}",
bytes.len()
))
})?;
let v = i64::from_le_bytes(arr);
if v.unsigned_abs() > (1u64 << 53) {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"Int64 nodata value {v} cannot be represented exactly as f64 \
(magnitude > 2^53); use the raw nodata bytes instead"
)));
@@ -614,10 +610,10 @@ pub fn nodata_bytes_to_f64_lossless(bytes: &[u8], dt: &BandDataType) -> Result<f
/// integer beyond 2^53 (which can't have arrived losslessly through `f64`).
/// `Float32` is the one lossy case allowed — it rounds to the nearest `f32`, as
/// any f64 → f32 narrowing does.
-pub fn nodata_f64_to_bytes(value: f64, dt: &BandDataType) -> Result<Vec<u8>, ArrowError> {
- fn check_integer(value: f64, min: f64, max: f64, dt: &BandDataType) -> Result<(), ArrowError> {
+pub fn nodata_f64_to_bytes(value: f64, dt: &BandDataType) -> Result<Vec<u8>, RasterError> {
+ fn check_integer(value: f64, min: f64, max: f64, dt: &BandDataType) -> Result<(), RasterError> {
if value.fract() != 0.0 || value < min || value > max {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"nodata value {value} is not a valid {dt:?} value"
)));
}
@@ -847,7 +843,7 @@ mod tests {
fn nodata(&self) -> Option<&[u8]> {
None
}
- fn nd_buffer(&self) -> Result<NdBuffer<'_>, ArrowError> {
+ fn nd_buffer(&self) -> Result<NdBuffer<'_>, RasterError> {
unimplemented!("not used in is_spatial_2d tests")
}
fn is_indb(&self) -> bool {
diff --git a/rust/sedona-raster/src/view_entries.rs b/rust/sedona-raster/src/view_entries.rs
index 3e8f767d5..63d4d6681 100644
--- a/rust/sedona-raster/src/view_entries.rs
+++ b/rust/sedona-raster/src/view_entries.rs
@@ -20,7 +20,7 @@
//! and is the entry point for all view-machinery operations
//! (validation, identity check, visible-shape derivation, composition).
-use arrow_schema::ArrowError;
+use crate::error::RasterError;
/// One per-dimension entry of a band's logical view. Describes how a
/// visible axis maps onto an axis of the underlying source buffer.
@@ -65,7 +65,7 @@ impl ViewEntries {
/// Wrap a pre-built vector of entries and validate it against
/// `source_shape` in one step — [`Self::new`] followed by
/// [`Self::validate`].
- pub fn try_new(inner: Vec<ViewEntry>, source_shape: &[i64]) -> Result<Self, ArrowError> {
+ pub fn try_new(inner: Vec<ViewEntry>, source_shape: &[i64]) -> Result<Self, RasterError> {
let entries = Self::new(inner);
entries.validate(source_shape)?;
Ok(entries)
@@ -141,10 +141,10 @@ impl ViewEntries {
/// - When `steps > 0`: `start ∈ [0, source_shape[source_axis])`,
/// and when `step != 0` the last addressed element
/// `start + (steps - 1) * step` is also in that range.
- pub fn validate(&self, source_shape: &[i64]) -> Result<(), ArrowError> {
+ pub fn validate(&self, source_shape: &[i64]) -> Result<(), RasterError> {
let ndim = source_shape.len();
if self.0.len() != ndim {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"view length ({}) must equal source_shape length ({ndim})",
self.0.len()
)));
@@ -152,14 +152,14 @@ impl ViewEntries {
let mut seen = vec![false; ndim];
for (k, v) in self.0.iter().enumerate() {
if v.source_axis < 0 || (v.source_axis as usize) >= ndim {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"view[{k}].source_axis = {} is out of range [0, {ndim})",
v.source_axis
)));
}
let sa = v.source_axis as usize;
if seen[sa] {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"view source_axis values must be a permutation of 0..{ndim}; \
axis {sa} appears more than once"
)));
@@ -167,13 +167,13 @@ impl ViewEntries {
seen[sa] = true;
if v.steps < 0 {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"view[{k}].steps = {} must be >= 0",
v.steps
)));
}
if source_shape[sa] < 0 {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"source_shape[{sa}] = {} must be >= 0",
source_shape[sa]
)));
@@ -181,7 +181,7 @@ impl ViewEntries {
if v.steps > 0 {
let s = source_shape[sa];
if v.start < 0 || v.start >= s {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"view[{k}].start = {} is out of range [0, {s}) for source axis {sa}",
v.start
)));
@@ -195,14 +195,14 @@ impl ViewEntries {
.checked_mul(v.step)
.and_then(|d| v.start.checked_add(d))
.ok_or_else(|| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"view[{k}] last-element index overflows i64 for \
start={}, step={}, steps={} on source axis {sa}",
v.start, v.step, v.steps
))
})?;
if last < 0 || last >= s {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"view[{k}] addresses element {last} which is out of range \
[0, {s}) for source axis {sa}"
)));
@@ -234,11 +234,11 @@ impl ViewEntries {
/// [`validate`] the result against the source shape before use.
///
/// [`validate`]: Self::validate
- pub fn compose(&self, next: &Self) -> Result<Self, ArrowError> {
+ pub fn compose(&self, next: &Self) -> Result<Self, RasterError> {
let mut out = Vec::with_capacity(next.0.len());
for (k, next_entry) in next.0.iter().enumerate() {
if next_entry.source_axis < 0 || (next_entry.source_axis as usize) >= self.0.len() {
- return Err(ArrowError::InvalidArgumentError(format!(
+ return Err(RasterError::Invalid(format!(
"compose: next[{k}].source_axis ({}) is out of range \
for input with {} visible axes",
next_entry.source_axis,
@@ -247,21 +247,21 @@ impl ViewEntries {
}
let input = &self.0[next_entry.source_axis as usize];
let step = next_entry.step.checked_mul(input.step).ok_or_else(|| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"compose: step product overflows i64 at axis {k} \
(next.step={}, input.step={})",
next_entry.step, input.step
))
})?;
let start_offset = next_entry.start.checked_mul(input.step).ok_or_else(|| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"compose: next.start * input.step overflows i64 at axis {k} \
(next.start={}, input.step={})",
next_entry.start, input.step
))
})?;
let start = input.start.checked_add(start_offset).ok_or_else(|| {
- ArrowError::InvalidArgumentError(format!(
+ RasterError::Invalid(format!(
"compose: composed start overflows i64 at axis {k} \
(input.start={}, offset={})",
input.start, start_offset