pub mod chunk;
pub mod iter;
pub use chunk::{Chunk, InMemoryRasterSource, RasterError, RasterSource};
pub use iter::ChunkIterator;
use crate::raster::focal::{BoundaryMode, WindowShape, focal_mean};
use crate::raster::hillshade::{HillshadeParams, hillshade};
use crate::raster::slope_aspect::slope;
use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::RasterDataType;
fn buffer_from_f32_slice(
data: &[f32],
width: usize,
height: usize,
) -> Result<RasterBuffer, RasterError> {
RasterBuffer::from_typed_vec(width, height, data.to_vec(), RasterDataType::Float32)
.map_err(|e| RasterError::AlgorithmError(e.to_string()))
}
fn f32_vec_from_buffer(buf: &RasterBuffer) -> Result<Vec<f32>, RasterError> {
buf.as_slice::<f32>()
.map(|s| s.to_vec())
.map_err(|e| RasterError::AlgorithmError(e.to_string()))
}
pub struct ChunkedRaster<R: RasterSource> {
pub source: R,
pub total_width: usize,
pub total_height: usize,
pub chunk_size: usize,
pub halo: usize,
}
impl<R: RasterSource> ChunkedRaster<R> {
pub fn new(source: R, chunk_size: usize, halo: usize) -> Result<Self, RasterError> {
if chunk_size == 0 {
return Err(RasterError::InvalidChunkSize);
}
let total_width = source.width();
let total_height = source.height();
Ok(Self {
source,
total_width,
total_height,
chunk_size,
halo,
})
}
pub fn iter(&self) -> ChunkIterator<'_, R> {
ChunkIterator::new(self)
}
}
pub fn process_streaming<R, F>(
raster: &ChunkedRaster<R>,
mut kernel: F,
) -> Result<Vec<f32>, RasterError>
where
R: RasterSource,
F: FnMut(&Chunk) -> Result<Vec<f32>, RasterError>,
{
let mut output = vec![0.0_f32; raster.total_width * raster.total_height];
for chunk_result in raster.iter() {
let chunk = chunk_result?;
let chunk_output = kernel(&chunk)?;
assert_eq!(
chunk_output.len(),
chunk.width * chunk.height,
"streaming kernel must return width×height values (no halo)"
);
for row in 0..chunk.height {
for col in 0..chunk.width {
let src_idx = row * chunk.width + col;
let dst_x = chunk.x + col;
let dst_y = chunk.y + row;
if dst_x < raster.total_width && dst_y < raster.total_height {
output[dst_y * raster.total_width + dst_x] = chunk_output[src_idx];
}
}
}
}
Ok(output)
}
pub fn extract_inner(
data: &[f32],
full_w: usize,
full_h: usize,
halo: usize,
inner_w: usize,
inner_h: usize,
) -> Vec<f32> {
let mut inner = Vec::with_capacity(inner_w * inner_h);
for row in halo..(halo + inner_h) {
for col in halo..(halo + inner_w) {
if row < full_h && col < full_w {
inner.push(data[row * full_w + col]);
} else {
inner.push(0.0_f32);
}
}
}
inner
}
pub fn streaming_hillshade<R: RasterSource>(
raster: &ChunkedRaster<R>,
azimuth: f64,
altitude: f64,
) -> Result<Vec<f32>, RasterError> {
process_streaming(raster, |chunk| {
let full_w = chunk.full_width();
let full_h = chunk.full_height();
let dem = buffer_from_f32_slice(&chunk.data, full_w, full_h)?;
let params = HillshadeParams::new(azimuth.clamp(0.0, 360.0), altitude.clamp(0.0, 90.0));
let result_buf =
hillshade(&dem, params).map_err(|e| RasterError::AlgorithmError(e.to_string()))?;
let result_data = f32_vec_from_buffer(&result_buf)?;
Ok(extract_inner(
&result_data,
full_w,
full_h,
chunk.halo,
chunk.width,
chunk.height,
))
})
}
pub fn streaming_slope<R: RasterSource>(
raster: &ChunkedRaster<R>,
) -> Result<Vec<f32>, RasterError> {
process_streaming(raster, |chunk| {
let full_w = chunk.full_width();
let full_h = chunk.full_height();
let dem = buffer_from_f32_slice(&chunk.data, full_w, full_h)?;
let result_buf =
slope(&dem, 1.0, 1.0).map_err(|e| RasterError::AlgorithmError(e.to_string()))?;
let result_data = f32_vec_from_buffer(&result_buf)?;
Ok(extract_inner(
&result_data,
full_w,
full_h,
chunk.halo,
chunk.width,
chunk.height,
))
})
}
pub fn streaming_focal_mean<R: RasterSource>(
raster: &ChunkedRaster<R>,
radius: usize,
) -> Result<Vec<f32>, RasterError> {
process_streaming(raster, |chunk| {
let full_w = chunk.full_width();
let full_h = chunk.full_height();
let src_buf = buffer_from_f32_slice(&chunk.data, full_w, full_h)?;
let window = WindowShape::circular(radius as f64)
.map_err(|e| RasterError::AlgorithmError(e.to_string()))?;
let result_buf = focal_mean(&src_buf, &window, &BoundaryMode::Edge)
.map_err(|e| RasterError::AlgorithmError(e.to_string()))?;
let result_data = f32_vec_from_buffer(&result_buf)?;
Ok(extract_inner(
&result_data,
full_w,
full_h,
chunk.halo,
chunk.width,
chunk.height,
))
})
}