Skip to content

Commit 38c777f

Browse files
authored
feat(rust/sedona-raster-gdal,c/sedona-gdal): Add in-db GDAL raster loading functions (#811)
## Summary - add `append_as_indb_raster()` and `dataset_to_indb_raster` to materialize any GDAL dataset as in-db arrow array representation of SedonaDB rasters. - add focused loader tests covering single-band, multi-band, `UInt64`, and `Int64` GeoTIFF inputs
1 parent ba06a25 commit 38c777f

9 files changed

Lines changed: 855 additions & 55 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

c/sedona-gdal/src/dyn_load.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ fn load_all_symbols(lib: &Library, api: &mut SedonaGdalApi) -> Result<(), GdalIn
112112
load_fn!(lib, api, GDALGetRasterBandYSize);
113113
load_fn!(lib, api, GDALGetBlockSize);
114114
load_fn!(lib, api, GDALGetRasterNoDataValue);
115+
load_fn!(lib, api, GDALGetRasterNoDataValueAsUInt64);
116+
load_fn!(lib, api, GDALGetRasterNoDataValueAsInt64);
115117
load_fn!(lib, api, GDALSetRasterNoDataValue);
116118
load_fn!(lib, api, GDALDeleteRasterNoDataValue);
117119
load_fn!(lib, api, GDALSetRasterNoDataValueAsUInt64);

c/sedona-gdal/src/gdal_dyn_bindgen.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@ pub(crate) struct SedonaGdalApi {
357357
>,
358358
pub GDALGetRasterNoDataValue:
359359
Option<unsafe extern "C" fn(hBand: GDALRasterBandH, pbSuccess: *mut c_int) -> c_double>,
360+
pub GDALGetRasterNoDataValueAsUInt64:
361+
Option<unsafe extern "C" fn(hBand: GDALRasterBandH, pbSuccess: *mut c_int) -> u64>,
362+
pub GDALGetRasterNoDataValueAsInt64:
363+
Option<unsafe extern "C" fn(hBand: GDALRasterBandH, pbSuccess: *mut c_int) -> i64>,
360364
pub GDALSetRasterNoDataValue:
361365
Option<unsafe extern "C" fn(hBand: GDALRasterBandH, dfValue: c_double) -> CPLErr>,
362366
pub GDALDeleteRasterNoDataValue: Option<unsafe extern "C" fn(hBand: GDALRasterBandH) -> CPLErr>,

c/sedona-gdal/src/raster/rasterband.rs

Lines changed: 288 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ pub struct RasterBand<'a> {
3434
_dataset: PhantomData<&'a Dataset>,
3535
}
3636

37+
struct RasterIoRequest {
38+
window: (isize, isize),
39+
window_size: (usize, usize),
40+
size: (usize, usize),
41+
e_resample_alg: Option<ResampleAlg>,
42+
}
43+
3744
impl<'a> RasterBand<'a> {
3845
pub(crate) fn new(
3946
api: &'static GdalApi,
@@ -66,36 +73,70 @@ impl<'a> RasterBand<'a> {
6673
// for which zeroed memory is a valid bit pattern.
6774
let mut data: Vec<T> = vec![unsafe { std::mem::zeroed() }; len];
6875

69-
let resample_alg = e_resample_alg.unwrap_or(ResampleAlg::NearestNeighbour);
70-
let mut extra_arg = GDALRasterIOExtraArg {
71-
eResampleAlg: resample_alg.to_gdal(),
72-
..GDALRasterIOExtraArg::default()
73-
};
76+
self.read_impl(
77+
RasterIoRequest {
78+
window,
79+
window_size,
80+
size,
81+
e_resample_alg,
82+
},
83+
data.as_mut_ptr() as *mut std::ffi::c_void,
84+
T::gdal_ordinal(),
85+
)?;
7486

75-
let rv = unsafe {
76-
call_gdal_api!(
77-
self.api,
78-
GDALRasterIOEx,
79-
self.c_rasterband,
80-
GF_Read,
81-
i32::try_from(window.0)?,
82-
i32::try_from(window.1)?,
83-
i32::try_from(window_size.0)?,
84-
i32::try_from(window_size.1)?,
85-
data.as_mut_ptr() as *mut std::ffi::c_void,
86-
i32::try_from(size.0)?,
87-
i32::try_from(size.1)?,
88-
T::gdal_ordinal(),
89-
0, // nPixelSpace (auto)
90-
0, // nLineSpace (auto)
91-
&mut extra_arg
92-
)
93-
};
94-
if rv != CE_None {
95-
return Err(self.api.last_cpl_err(rv as u32));
87+
Ok(Buffer::new(size, data))
88+
}
89+
90+
/// Read a window of this band into a byte buffer using the band's native GDAL data type.
91+
///
92+
/// The returned bytes use GDAL's in-memory representation for the current platform.
93+
/// If `e_resample_alg` is `None`, use nearest-neighbour resampling.
94+
pub fn read_as_bytes(
95+
&self,
96+
window: (isize, isize),
97+
window_size: (usize, usize),
98+
size: (usize, usize),
99+
e_resample_alg: Option<ResampleAlg>,
100+
) -> Result<Vec<u8>> {
101+
let len = self.expected_byte_len(size)?;
102+
let mut data = vec![0u8; len];
103+
self.read_into_bytes(window, window_size, size, &mut data, e_resample_alg)?;
104+
Ok(data)
105+
}
106+
107+
/// Read a window of this band into a caller-provided byte buffer using the band's native
108+
/// GDAL data type.
109+
///
110+
/// The buffer length must equal `size.0 * size.1 * band_type().byte_size()`.
111+
/// If `e_resample_alg` is `None`, use nearest-neighbour resampling.
112+
pub fn read_into_bytes(
113+
&self,
114+
window: (isize, isize),
115+
window_size: (usize, usize),
116+
size: (usize, usize),
117+
data: &mut [u8],
118+
e_resample_alg: Option<ResampleAlg>,
119+
) -> Result<()> {
120+
let expected_len = self.expected_byte_len(size)?;
121+
if data.len() != expected_len {
122+
return Err(GdalError::BadArgument(format!(
123+
"byte buffer length {} does not match expected size {} for raster window {:?}",
124+
data.len(),
125+
expected_len,
126+
size
127+
)));
96128
}
97129

98-
Ok(Buffer::new(size, data))
130+
self.read_impl(
131+
RasterIoRequest {
132+
window,
133+
window_size,
134+
size,
135+
e_resample_alg,
136+
},
137+
data.as_mut_ptr() as *mut std::ffi::c_void,
138+
self.c_band_type(),
139+
)
99140
}
100141

101142
/// Write a buffer to this raster band.
@@ -188,6 +229,44 @@ impl<'a> RasterBand<'a> {
188229
}
189230
}
190231

232+
/// Fetch the band's nodata value as `u64`.
233+
/// Return `None` if no nodata value is set.
234+
pub fn no_data_value_u64(&self) -> Option<u64> {
235+
let mut success: i32 = 0;
236+
let value = unsafe {
237+
call_gdal_api!(
238+
self.api,
239+
GDALGetRasterNoDataValueAsUInt64,
240+
self.c_rasterband,
241+
&mut success
242+
)
243+
};
244+
if success != 0 {
245+
Some(value)
246+
} else {
247+
None
248+
}
249+
}
250+
251+
/// Fetch the band's nodata value as `i64`.
252+
/// Return `None` if no nodata value is set.
253+
pub fn no_data_value_i64(&self) -> Option<i64> {
254+
let mut success: i32 = 0;
255+
let value = unsafe {
256+
call_gdal_api!(
257+
self.api,
258+
GDALGetRasterNoDataValueAsInt64,
259+
self.c_rasterband,
260+
&mut success
261+
)
262+
};
263+
if success != 0 {
264+
Some(value)
265+
} else {
266+
None
267+
}
268+
}
269+
191270
/// Set the band's nodata value.
192271
/// Pass `None` to clear any existing nodata value.
193272
pub fn set_no_data_value(&self, value: Option<f64>) -> Result<()> {
@@ -248,6 +327,57 @@ impl<'a> RasterBand<'a> {
248327
pub fn api(&self) -> &'static GdalApi {
249328
self.api
250329
}
330+
331+
fn read_impl(
332+
&self,
333+
request: RasterIoRequest,
334+
data_ptr: *mut std::ffi::c_void,
335+
buf_type: GDALDataType,
336+
) -> Result<()> {
337+
let resample_alg = request
338+
.e_resample_alg
339+
.unwrap_or(ResampleAlg::NearestNeighbour);
340+
let mut extra_arg = GDALRasterIOExtraArg {
341+
eResampleAlg: resample_alg.to_gdal(),
342+
..GDALRasterIOExtraArg::default()
343+
};
344+
345+
let rv = unsafe {
346+
call_gdal_api!(
347+
self.api,
348+
GDALRasterIOEx,
349+
self.c_rasterband,
350+
GF_Read,
351+
i32::try_from(request.window.0)?,
352+
i32::try_from(request.window.1)?,
353+
i32::try_from(request.window_size.0)?,
354+
i32::try_from(request.window_size.1)?,
355+
data_ptr,
356+
i32::try_from(request.size.0)?,
357+
i32::try_from(request.size.1)?,
358+
buf_type,
359+
0,
360+
0,
361+
&mut extra_arg
362+
)
363+
};
364+
if rv != CE_None {
365+
return Err(self.api.last_cpl_err(rv as u32));
366+
}
367+
368+
Ok(())
369+
}
370+
371+
fn expected_byte_len(&self, size: (usize, usize)) -> Result<usize> {
372+
let bytes_per_value = self.band_type().byte_size();
373+
if bytes_per_value == 0 {
374+
return Err(GdalError::BadArgument(
375+
"Cannot read bytes for band with unknown GDAL data type".to_string(),
376+
));
377+
}
378+
379+
Ok(size.0 * size.1 * bytes_per_value)
380+
}
251381
}
252382

253383
/// Return the actual block size for a block index.
@@ -285,7 +415,7 @@ mod tests {
285415
use crate::driver::DriverManager;
286416
use crate::gdal_dyn_bindgen::*;
287417
use crate::global::with_global_gdal_api;
288-
use crate::raster::types::ResampleAlg;
418+
use crate::raster::types::{Buffer, ResampleAlg};
289419

290420
fn fixture(name: &str) -> String {
291421
sedona_testing::data::test_raster(name).unwrap()
@@ -336,6 +466,102 @@ mod tests {
336466
.unwrap();
337467
}
338468

469+
#[test]
470+
fn test_read_raster_as_bytes_u8() {
471+
with_global_gdal_api(|api| {
472+
let driver = DriverManager::get_driver_by_name(api, "MEM").unwrap();
473+
let dataset = driver.create_with_band_type::<u8>("", 2, 3, 1).unwrap();
474+
let rasterband = dataset.rasterband(1).unwrap();
475+
let mut buffer = Buffer::new((2, 3), vec![7u8, 7, 7, 10, 8, 12]);
476+
rasterband.write((0, 0), (2, 3), &mut buffer).unwrap();
477+
478+
let rv = rasterband
479+
.read_as_bytes((0, 0), (2, 3), (2, 3), None)
480+
.unwrap();
481+
assert_eq!(rv, vec![7u8, 7, 7, 10, 8, 12]);
482+
})
483+
.unwrap();
484+
}
485+
486+
#[test]
487+
fn test_read_raster_as_bytes_u16() {
488+
with_global_gdal_api(|api| {
489+
let driver = DriverManager::get_driver_by_name(api, "MEM").unwrap();
490+
let dataset = driver.create_with_band_type::<u16>("", 2, 2, 1).unwrap();
491+
let rasterband = dataset.rasterband(1).unwrap();
492+
let mut buffer = Buffer::new((2, 2), vec![1u16, 256, 511, 1024]);
493+
rasterband.write((0, 0), (2, 2), &mut buffer).unwrap();
494+
495+
let rv = rasterband
496+
.read_as_bytes((0, 0), (2, 2), (2, 2), None)
497+
.unwrap();
498+
let expected: Vec<u8> = vec![1u16, 256, 511, 1024]
499+
.into_iter()
500+
.flat_map(|v| v.to_ne_bytes())
501+
.collect();
502+
assert_eq!(rv, expected);
503+
})
504+
.unwrap();
505+
}
506+
507+
#[test]
508+
fn test_read_into_bytes_matches_read_as_bytes() {
509+
with_global_gdal_api(|api| {
510+
let driver = DriverManager::get_driver_by_name(api, "MEM").unwrap();
511+
let dataset = driver.create_with_band_type::<u16>("", 2, 2, 1).unwrap();
512+
let rasterband = dataset.rasterband(1).unwrap();
513+
let mut buffer = Buffer::new((2, 2), vec![5u16, 6, 7, 8]);
514+
rasterband.write((0, 0), (2, 2), &mut buffer).unwrap();
515+
516+
let expected = rasterband
517+
.read_as_bytes((0, 0), (2, 2), (2, 2), None)
518+
.unwrap();
519+
let mut actual = vec![0u8; expected.len()];
520+
rasterband
521+
.read_into_bytes((0, 0), (2, 2), (2, 2), &mut actual, None)
522+
.unwrap();
523+
524+
assert_eq!(actual, expected);
525+
})
526+
.unwrap();
527+
}
528+
529+
#[test]
530+
fn test_read_into_bytes_with_wrong_len() {
531+
with_global_gdal_api(|api| {
532+
let driver = DriverManager::get_driver_by_name(api, "MEM").unwrap();
533+
let dataset = driver.create_with_band_type::<u16>("", 2, 2, 1).unwrap();
534+
let rasterband = dataset.rasterband(1).unwrap();
535+
let mut data = vec![0u8; 7];
536+
537+
let err = rasterband
538+
.read_into_bytes((0, 0), (2, 2), (2, 2), &mut data, None)
539+
.unwrap_err();
540+
assert!(matches!(err, crate::errors::GdalError::BadArgument(_)));
541+
})
542+
.unwrap();
543+
}
544+
545+
#[test]
546+
fn test_read_as_bytes_with_average_resample() {
547+
with_global_gdal_api(|api| {
548+
let driver = DriverManager::get_driver_by_name(api, "MEM").unwrap();
549+
let dataset = driver.create_with_band_type::<u8>("", 4, 4, 1).unwrap();
550+
let rasterband = dataset.rasterband(1).unwrap();
551+
let mut buffer = Buffer::new(
552+
(4, 4),
553+
vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
554+
);
555+
rasterband.write((0, 0), (4, 4), &mut buffer).unwrap();
556+
557+
let rv = rasterband
558+
.read_as_bytes((0, 0), (4, 4), (2, 2), Some(ResampleAlg::Average))
559+
.unwrap();
560+
assert_eq!(rv.len(), 4);
561+
})
562+
.unwrap();
563+
}
564+
339565
#[test]
340566
fn test_get_no_data_value() {
341567
with_global_gdal_api(|api| {
@@ -369,4 +595,38 @@ mod tests {
369595
})
370596
.unwrap();
371597
}
598+
599+
#[test]
600+
fn test_set_no_data_value_u64() {
601+
with_global_gdal_api(|api| {
602+
let driver = DriverManager::get_driver_by_name(api, "MEM").unwrap();
603+
let dataset = driver.create_with_band_type::<u64>("", 20, 10, 1).unwrap();
604+
let rasterband = dataset.rasterband(1).unwrap();
605+
let nodata = 9_007_199_254_740_993u64;
606+
607+
assert_eq!(rasterband.no_data_value_u64(), None);
608+
assert!(rasterband.set_no_data_value_u64(Some(nodata)).is_ok());
609+
assert_eq!(rasterband.no_data_value_u64(), Some(nodata));
610+
assert!(rasterband.set_no_data_value_u64(None).is_ok());
611+
assert_eq!(rasterband.no_data_value_u64(), None);
612+
})
613+
.unwrap();
614+
}
615+
616+
#[test]
617+
fn test_set_no_data_value_i64() {
618+
with_global_gdal_api(|api| {
619+
let driver = DriverManager::get_driver_by_name(api, "MEM").unwrap();
620+
let dataset = driver.create_with_band_type::<i64>("", 20, 10, 1).unwrap();
621+
let rasterband = dataset.rasterband(1).unwrap();
622+
let nodata = -9_007_199_254_740_993i64;
623+
624+
assert_eq!(rasterband.no_data_value_i64(), None);
625+
assert!(rasterband.set_no_data_value_i64(Some(nodata)).is_ok());
626+
assert_eq!(rasterband.no_data_value_i64(), Some(nodata));
627+
assert!(rasterband.set_no_data_value_i64(None).is_ok());
628+
assert_eq!(rasterband.no_data_value_i64(), None);
629+
})
630+
.unwrap();
631+
}
372632
}

0 commit comments

Comments
 (0)