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}