Skip to main content

oxigdal_algorithms/resampling/
mod.rs

1//! Raster resampling algorithms
2//!
3//! This module provides various resampling algorithms for changing the resolution
4//! or dimensions of raster data while maintaining spatial accuracy.
5//!
6//! # Algorithms
7//!
8//! - Nearest neighbor (fastest, preserves exact values)
9//! - Bilinear interpolation (smooth, good for continuous data)
10//! - Bicubic interpolation (higher quality, smoother)
11//! - Lanczos resampling (highest quality, most expensive)
12//!
13//! # Example
14//!
15//! ```no_run
16//! use oxigdal_algorithms::resampling::{ResamplingMethod, Resampler};
17//! use oxigdal_core::buffer::RasterBuffer;
18//! use oxigdal_core::types::RasterDataType;
19//! # use oxigdal_algorithms::error::Result;
20//!
21//! # fn main() -> Result<()> {
22//! // Create a resampler
23//! let resampler = Resampler::new(ResamplingMethod::Bilinear);
24//!
25//! // Resample a raster buffer
26//! let src = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
27//! let dst = resampler.resample(&src, 500, 500)?;
28//! # Ok(())
29//! # }
30//! ```
31
32mod bicubic;
33mod bilinear;
34mod kernel;
35mod lanczos;
36mod nearest;
37
38pub use bicubic::BicubicResampler;
39pub use bilinear::BilinearResampler;
40pub use lanczos::LanczosResampler;
41pub use nearest::NearestResampler;
42
43use crate::error::Result;
44use oxigdal_core::buffer::RasterBuffer;
45
46/// Resampling methods
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
48pub enum ResamplingMethod {
49    /// Nearest neighbor - fastest, preserves exact values
50    ///
51    /// Best for: Categorical data, classification maps
52    /// Pros: Very fast, no new values introduced
53    /// Cons: Blocky appearance when upsampling
54    Nearest,
55
56    /// Bilinear interpolation - smooth, moderate quality
57    ///
58    /// Best for: Continuous data, DEMs, temperature maps
59    /// Pros: Smooth results, reasonably fast
60    /// Cons: Some blurring, not ideal for downsampling
61    #[default]
62    Bilinear,
63
64    /// Bicubic interpolation - high quality, smoother than bilinear
65    ///
66    /// Best for: High-quality imagery, DEMs requiring smoothness
67    /// Pros: Very smooth, better edge preservation than bilinear
68    /// Cons: Slower, can introduce slight ringing artifacts
69    Bicubic,
70
71    /// Lanczos resampling - highest quality, most expensive
72    ///
73    /// Best for: High-quality imagery, when quality matters most
74    /// Pros: Excellent quality, sharp edges
75    /// Cons: Slowest, can introduce ringing near sharp edges
76    Lanczos,
77}
78
79impl ResamplingMethod {
80    /// Returns a human-readable name for the method
81    #[must_use]
82    pub const fn name(&self) -> &'static str {
83        match self {
84            Self::Nearest => "Nearest Neighbor",
85            Self::Bilinear => "Bilinear",
86            Self::Bicubic => "Bicubic",
87            Self::Lanczos => "Lanczos",
88        }
89    }
90
91    /// Returns the kernel radius for this method
92    ///
93    /// This is the number of pixels in each direction that are sampled
94    /// for interpolation.
95    #[must_use]
96    pub const fn kernel_radius(&self) -> usize {
97        match self {
98            Self::Nearest => 0,
99            Self::Bilinear => 1,
100            Self::Bicubic => 2,
101            Self::Lanczos => 3,
102        }
103    }
104}
105
106/// Generic resampler that can use any resampling method
107pub struct Resampler {
108    method: ResamplingMethod,
109}
110
111impl Resampler {
112    /// Creates a new resampler with the specified method
113    #[must_use]
114    pub const fn new(method: ResamplingMethod) -> Self {
115        Self { method }
116    }
117
118    /// Resamples a raster buffer to new dimensions
119    ///
120    /// # Arguments
121    ///
122    /// * `src` - Source raster buffer
123    /// * `dst_width` - Target width in pixels
124    /// * `dst_height` - Target height in pixels
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if:
129    /// - Target dimensions are zero
130    /// - Data type is unsupported
131    /// - Memory allocation fails
132    pub fn resample(
133        &self,
134        src: &RasterBuffer,
135        dst_width: u64,
136        dst_height: u64,
137    ) -> Result<RasterBuffer> {
138        if dst_width == 0 || dst_height == 0 {
139            return Err(crate::error::AlgorithmError::InvalidParameter {
140                parameter: "dimensions",
141                message: "Target dimensions must be non-zero".to_string(),
142            });
143        }
144
145        match self.method {
146            ResamplingMethod::Nearest => {
147                let resampler = NearestResampler;
148                resampler.resample(src, dst_width, dst_height)
149            }
150            ResamplingMethod::Bilinear => {
151                let resampler = BilinearResampler;
152                resampler.resample(src, dst_width, dst_height)
153            }
154            ResamplingMethod::Bicubic => {
155                let resampler = BicubicResampler::new();
156                resampler.resample(src, dst_width, dst_height)
157            }
158            ResamplingMethod::Lanczos => {
159                let resampler = LanczosResampler::new(3);
160                resampler.resample(src, dst_width, dst_height)
161            }
162        }
163    }
164
165    /// Returns the resampling method
166    #[must_use]
167    pub const fn method(&self) -> ResamplingMethod {
168        self.method
169    }
170}
171
172impl Default for Resampler {
173    fn default() -> Self {
174        Self::new(ResamplingMethod::default())
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use oxigdal_core::types::RasterDataType;
182
183    #[test]
184    fn test_resampling_method_names() {
185        assert_eq!(ResamplingMethod::Nearest.name(), "Nearest Neighbor");
186        assert_eq!(ResamplingMethod::Bilinear.name(), "Bilinear");
187        assert_eq!(ResamplingMethod::Bicubic.name(), "Bicubic");
188        assert_eq!(ResamplingMethod::Lanczos.name(), "Lanczos");
189    }
190
191    #[test]
192    fn test_kernel_radius() {
193        assert_eq!(ResamplingMethod::Nearest.kernel_radius(), 0);
194        assert_eq!(ResamplingMethod::Bilinear.kernel_radius(), 1);
195        assert_eq!(ResamplingMethod::Bicubic.kernel_radius(), 2);
196        assert_eq!(ResamplingMethod::Lanczos.kernel_radius(), 3);
197    }
198
199    #[test]
200    fn test_resampler_creation() {
201        let resampler = Resampler::new(ResamplingMethod::Bilinear);
202        assert_eq!(resampler.method(), ResamplingMethod::Bilinear);
203    }
204
205    #[test]
206    fn test_resample_zero_dimensions() {
207        let src = RasterBuffer::zeros(100, 100, RasterDataType::Float32);
208        let resampler = Resampler::new(ResamplingMethod::Nearest);
209
210        assert!(resampler.resample(&src, 0, 100).is_err());
211        assert!(resampler.resample(&src, 100, 0).is_err());
212    }
213}