Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion rust/sedona-raster-functions/src/rs_setsrid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,12 @@ mod tests {
for band_idx in 0..orig_bands.len() {
let orig_band = orig_bands.band(band_idx + 1).unwrap();
let mod_band = mod_bands.band(band_idx + 1).unwrap();
assert_eq!(orig_band.data(), mod_band.data());
let orig_nd = orig_band.nd_buffer().unwrap();
let mod_nd = mod_band.nd_buffer().unwrap();
assert_eq!(
orig_nd.as_contiguous().unwrap(),
mod_nd.as_contiguous().unwrap()
);
assert_eq!(
orig_band.metadata().data_type().unwrap(),
mod_band.metadata().data_type().unwrap()
Expand Down
35 changes: 26 additions & 9 deletions rust/sedona-raster-gdal/src/gdal_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,27 @@ pub(crate) fn convert_gdal_err(e: GdalError) -> DataFusionError {
DataFusionError::External(Box::new(e))
}

/// This function creates a GDAL dataset backed by the MEM driver that directly
/// references the band data stored in the [RasterRef]. No data copying occurs -
/// the GDAL bands point to the same memory as the data buffer held by [RasterRef].
/// Build a GDAL MEM dataset whose bands point at the bytes held by `raster`.
///
/// Each band's bytes are borrowed zero-copy via `NdBuffer::as_contiguous()`:
/// the GDAL band points directly at the StructArray's backing buffer, so the
/// caller must keep `raster` alive for the dataset's lifetime. Only 2-D
/// identity-view bands are accepted (enforced by `is_2d()` below), so the
/// bytes are always already packed row-major and no materialization happens
/// here — a strided view would be rejected, directing the user to
/// `RS_EnsureContiguous`.
///
/// # Arguments
/// * `raster` - The RasterRef value
/// * `band_indices` - The indices of the bands to include in the GDAL dataset (1-based)
///
/// # Returns
/// A [`Dataset`] that provides access to the GDAL dataset.
/// The `Dataset`, whose band pointers borrow into `raster` (which must
/// outlive it).
///
/// # Errors
/// Returns an error if:
/// - Any band is N-D (not the legacy `["y","x"]` 2-D shape with identity view)
/// - Any band uses OutDb storage
/// - GDAL driver operations fail
/// - Accessing RasterRef fails
Expand All @@ -212,7 +220,7 @@ pub unsafe fn raster_ref_to_gdal_mem<R: RasterRef + ?Sized>(
// Create internal MEM dataset via sedona-gdal shim to avoid open dataset list contention.
let mut mem_ds_builder = MemDatasetBuilder::new(width, height);

// Add bands with DATAPOINTER option (zero-copy)
// Add bands with DATAPOINTER option.
//
// Note: GDALAddBand always appends a new band, so the destination band index
// is sequential (1..=band_indices.len()), even if the source band indices are
Expand All @@ -238,8 +246,16 @@ pub unsafe fn raster_ref_to_gdal_mem<R: RasterRef + ?Sized>(
let band_metadata = band.metadata();
let band_type = band_metadata.data_type()?;
let gdal_type = band_data_type_to_gdal(&band_type);
let band_data = band.data();
let data_ptr = band_data.as_ptr();
// `is_2d()` above guarantees an identity view, so the band's bytes
// are already packed row-major and `as_contiguous()` borrows them
// zero-copy from the StructArray (never materializes). GDAL holds the
// resulting pointer for the dataset's lifetime; the caller keeps
// `raster` alive to back it. A strided view would surface here as an
// error directing the user to RS_EnsureContiguous, but `is_2d()`
// rejects those first.
let buf = band.nd_buffer().map_err(|e| arrow_datafusion_err!(e))?;
let band_bytes = buf.as_contiguous().map_err(|e| arrow_datafusion_err!(e))?;
let data_ptr: *const u8 = band_bytes.as_ptr();
unsafe {
mem_ds_builder = mem_ds_builder.add_band(gdal_type, data_ptr as *mut u8);
}
Expand Down Expand Up @@ -307,8 +323,9 @@ pub unsafe fn raster_ref_to_gdal_mem<R: RasterRef + ?Sized>(

pub fn raster_ref_to_gdal_empty<R: RasterRef + ?Sized>(gdal: &Gdal, raster: &R) -> Result<Dataset> {
unsafe {
// SAFETY: raster_ref_to_gdal_mem is safe to call with an empty band list. The
// returned dataset will have zero bands and references no external memory.
// SAFETY: raster_ref_to_gdal_mem is safe to call with an empty band
// list. The returned dataset has zero bands and references no
// external memory.
raster_ref_to_gdal_mem(gdal, raster, &[])
}
}
Expand Down
5 changes: 2 additions & 3 deletions rust/sedona-raster-gdal/src/gdal_dataset_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,8 @@ impl<'a> GDALDatasetProvider<'a> {
}

let mut gdal_mem_source = if !indb_band_indices.is_empty() {
Some(Rc::new(unsafe {
raster_ref_to_gdal_mem(self.gdal, raster, &indb_band_indices)?
}))
let mem_ds = unsafe { raster_ref_to_gdal_mem(self.gdal, raster, &indb_band_indices)? };
Some(Rc::new(mem_ds))
} else {
None
};
Expand Down
33 changes: 22 additions & 11 deletions rust/sedona-raster-gdal/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ mod tests {
assert_eq!(band.metadata().storage_type().unwrap(), StorageType::InDb);
assert_eq!(band.metadata().data_type().unwrap(), BandDataType::UInt8);
assert_eq!(band.metadata().nodata_value().unwrap(), [255u8]);
assert_eq!(band.data(), [1u8, 2, 3, 4, 5, 6]);
let ndb = band.nd_buffer().unwrap();
assert_eq!(ndb.as_contiguous().unwrap(), [1u8, 2, 3, 4, 5, 6]);
}

#[test]
Expand Down Expand Up @@ -301,8 +302,10 @@ mod tests {
&nodata.to_le_bytes()
);

let pixels: Vec<u64> = band
.data()
let ndb = band.nd_buffer().unwrap();
let pixels: Vec<u64> = ndb
.as_contiguous()
.unwrap()
.chunks_exact(8)
.map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()))
.collect();
Expand Down Expand Up @@ -333,8 +336,10 @@ mod tests {
&nodata.to_le_bytes()
);

let pixels: Vec<i64> = band
.data()
let ndb = band.nd_buffer().unwrap();
let pixels: Vec<i64> = ndb
.as_contiguous()
.unwrap()
.chunks_exact(8)
.map(|chunk| i64::from_le_bytes(chunk.try_into().unwrap()))
.collect();
Expand Down Expand Up @@ -365,8 +370,10 @@ mod tests {
&nodata.to_le_bytes()
);

let pixels: Vec<u16> = band
.data()
let ndb = band.nd_buffer().unwrap();
let pixels: Vec<u16> = ndb
.as_contiguous()
.unwrap()
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes(chunk.try_into().unwrap()))
.collect();
Expand Down Expand Up @@ -395,12 +402,14 @@ mod tests {
assert_eq!(band1.metadata().storage_type().unwrap(), StorageType::InDb);
assert_eq!(band1.metadata().data_type().unwrap(), BandDataType::UInt8);
assert_eq!(band1.metadata().nodata_value().unwrap(), [255u8]);
assert_eq!(band1.data(), [10u8, 11, 12, 13]);
let nd1 = band1.nd_buffer().unwrap();
assert_eq!(nd1.as_contiguous().unwrap(), [10u8, 11, 12, 13]);

assert_eq!(band2.metadata().storage_type().unwrap(), StorageType::InDb);
assert_eq!(band2.metadata().data_type().unwrap(), BandDataType::UInt8);
assert_eq!(band2.metadata().nodata_value().unwrap(), [255u8]);
assert_eq!(band2.data(), [100u8, 0, 200, 0]);
let nd2 = band2.nd_buffer().unwrap();
assert_eq!(nd2.as_contiguous().unwrap(), [100u8, 0, 200, 0]);
}

#[test]
Expand All @@ -420,12 +429,14 @@ mod tests {
assert_eq!(band1.metadata().storage_type().unwrap(), StorageType::InDb);
assert_eq!(band1.metadata().data_type().unwrap(), BandDataType::UInt8);
assert_eq!(band1.metadata().nodata_value().unwrap(), [0u8]);
assert_eq!(band1.data(), [10u8, 11, 12, 13]);
let nd1 = band1.nd_buffer().unwrap();
assert_eq!(nd1.as_contiguous().unwrap(), [10u8, 11, 12, 13]);

assert_eq!(band2.metadata().storage_type().unwrap(), StorageType::InDb);
assert_eq!(band2.metadata().data_type().unwrap(), BandDataType::UInt8);
assert_eq!(band2.metadata().nodata_value().unwrap(), [255u8]);
assert_eq!(band2.data(), [100u8, 0, 200, 0]);
let nd2 = band2.nd_buffer().unwrap();
assert_eq!(nd2.as_contiguous().unwrap(), [100u8, 0, 200, 0]);
}

#[test]
Expand Down
Loading
Loading