Skip to main content

oxigdal_core/buffer/
mod.rs

1// Allow unsafe blocks for low-level buffer operations that require
2// direct memory access for performance-critical typed slice conversions
3#![allow(unsafe_code)]
4
5//! Buffer types for raster and vector data
6//!
7//! This module provides efficient buffer types for storing and manipulating
8//! geospatial data. When the `arrow` feature is enabled, buffers are backed
9//! by Apache Arrow arrays for zero-copy interoperability.
10//!
11//! # Overview
12//!
13//! The [`RasterBuffer`] type is the core buffer abstraction in `OxiGDAL`, providing
14//! type-safe storage for raster pixel data with automatic memory management.
15//!
16//! # Examples
17//!
18//! ## Creating buffers
19//!
20//! ```
21//! use oxigdal_core::buffer::RasterBuffer;
22//! use oxigdal_core::types::{RasterDataType, NoDataValue};
23//!
24//! // Create a zero-filled buffer
25//! let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
26//!
27//! // Create a buffer with nodata value
28//! let nodata = NoDataValue::Float(-9999.0);
29//! let buffer = RasterBuffer::nodata_filled(1000, 1000, RasterDataType::Float32, nodata);
30//! ```
31//!
32//! ## Working with pixel data
33//!
34//! ```
35//! use oxigdal_core::buffer::RasterBuffer;
36//! use oxigdal_core::types::RasterDataType;
37//!
38//! let mut buffer = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
39//!
40//! // Set pixel value
41//! buffer.set_pixel(50, 50, 255.0)?;
42//!
43//! // Get pixel value
44//! let value = buffer.get_pixel(50, 50)?;
45//! assert_eq!(value, 255.0);
46//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
47//! ```
48//!
49//! ## Computing statistics
50//!
51//! ```
52//! use oxigdal_core::buffer::RasterBuffer;
53//! use oxigdal_core::types::RasterDataType;
54//!
55//! let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
56//! let stats = buffer.compute_statistics()?;
57//!
58//! println!("Min: {}, Max: {}", stats.min, stats.max);
59//! println!("Mean: {}, StdDev: {}", stats.mean, stats.std_dev);
60//! println!("Valid pixels: {}", stats.valid_count);
61//! # Ok::<(), oxigdal_core::error::OxiGdalError>(())
62//! ```
63//!
64//! # See Also
65//!
66//! - [`RasterDataType`] - Supported pixel data types
67//! - [`NoDataValue`] - Representation of missing data
68//! - [`RasterStatistics`] - Pixel statistics
69//!
70//! [`RasterDataType`]: crate::types::RasterDataType
71//! [`NoDataValue`]: crate::types::NoDataValue
72//! [`RasterStatistics`]: crate::types::RasterStatistics
73
74use core::fmt;
75
76use crate::error::{OxiGdalError, Result};
77use crate::types::{NoDataValue, RasterDataType};
78
79mod band_iterator;
80pub use band_iterator::{BandIterator, BandRef, MultiBandBuffer};
81
82mod raster_window;
83pub use raster_window::RasterWindow;
84
85pub mod mask;
86pub use mask::Mask;
87
88pub use crate::simd_buffer::{ArenaTile, TileIteratorArena};
89
90#[cfg(feature = "arrow")]
91pub mod arrow_convert;
92
93/// A typed buffer for raster data
94#[derive(Clone)]
95pub struct RasterBuffer {
96    /// The underlying bytes
97    data: Vec<u8>,
98    /// Width in pixels
99    width: u64,
100    /// Height in pixels
101    height: u64,
102    /// Data type
103    data_type: RasterDataType,
104    /// `NoData` value
105    nodata: NoDataValue,
106}
107
108impl RasterBuffer {
109    /// Creates a new raster buffer
110    ///
111    /// # Errors
112    /// Returns an error if the data size doesn't match the dimensions and type
113    pub fn new(
114        data: Vec<u8>,
115        width: u64,
116        height: u64,
117        data_type: RasterDataType,
118        nodata: NoDataValue,
119    ) -> Result<Self> {
120        let expected_size = width * height * data_type.size_bytes() as u64;
121        if data.len() as u64 != expected_size {
122            return Err(OxiGdalError::InvalidParameter {
123                parameter: "data",
124                message: format!(
125                    "Data size mismatch: expected {} bytes for {}x{} {:?}, got {}",
126                    expected_size,
127                    width,
128                    height,
129                    data_type,
130                    data.len()
131                ),
132            });
133        }
134
135        Ok(Self {
136            data,
137            width,
138            height,
139            data_type,
140            nodata,
141        })
142    }
143
144    /// Creates a zero-filled buffer
145    #[must_use]
146    pub fn zeros(width: u64, height: u64, data_type: RasterDataType) -> Self {
147        let size = (width * height * data_type.size_bytes() as u64) as usize;
148        Self {
149            data: vec![0u8; size],
150            width,
151            height,
152            data_type,
153            nodata: NoDataValue::None,
154        }
155    }
156
157    /// Creates a buffer filled with the nodata value
158    #[must_use]
159    pub fn nodata_filled(
160        width: u64,
161        height: u64,
162        data_type: RasterDataType,
163        nodata: NoDataValue,
164    ) -> Self {
165        let mut buffer = Self::zeros(width, height, data_type);
166        buffer.nodata = nodata;
167
168        // Fill with nodata value if defined
169        if let Some(value) = nodata.as_f64() {
170            buffer.fill_value(value);
171        }
172
173        buffer
174    }
175
176    /// Fills the buffer with a constant value
177    pub fn fill_value(&mut self, value: f64) {
178        match self.data_type {
179            RasterDataType::UInt8 => {
180                let v = value as u8;
181                self.data.fill(v);
182            }
183            RasterDataType::Int8 => {
184                let v = value as i8;
185                self.data.fill(v as u8);
186            }
187            RasterDataType::UInt16 => {
188                let v = (value as u16).to_ne_bytes();
189                for chunk in self.data.chunks_exact_mut(2) {
190                    chunk.copy_from_slice(&v);
191                }
192            }
193            RasterDataType::Int16 => {
194                let v = (value as i16).to_ne_bytes();
195                for chunk in self.data.chunks_exact_mut(2) {
196                    chunk.copy_from_slice(&v);
197                }
198            }
199            RasterDataType::UInt32 => {
200                let v = (value as u32).to_ne_bytes();
201                for chunk in self.data.chunks_exact_mut(4) {
202                    chunk.copy_from_slice(&v);
203                }
204            }
205            RasterDataType::Int32 => {
206                let v = (value as i32).to_ne_bytes();
207                for chunk in self.data.chunks_exact_mut(4) {
208                    chunk.copy_from_slice(&v);
209                }
210            }
211            RasterDataType::Float32 => {
212                let v = (value as f32).to_ne_bytes();
213                for chunk in self.data.chunks_exact_mut(4) {
214                    chunk.copy_from_slice(&v);
215                }
216            }
217            RasterDataType::Float64 => {
218                let v = value.to_ne_bytes();
219                for chunk in self.data.chunks_exact_mut(8) {
220                    chunk.copy_from_slice(&v);
221                }
222            }
223            RasterDataType::UInt64 => {
224                let v = (value as u64).to_ne_bytes();
225                for chunk in self.data.chunks_exact_mut(8) {
226                    chunk.copy_from_slice(&v);
227                }
228            }
229            RasterDataType::Int64 => {
230                let v = (value as i64).to_ne_bytes();
231                for chunk in self.data.chunks_exact_mut(8) {
232                    chunk.copy_from_slice(&v);
233                }
234            }
235            RasterDataType::CFloat32 | RasterDataType::CFloat64 => {
236                // Complex types: fill with (value, 0)
237                // This is a simplified implementation
238            }
239        }
240    }
241
242    /// Returns the width in pixels
243    #[must_use]
244    pub const fn width(&self) -> u64 {
245        self.width
246    }
247
248    /// Returns the height in pixels
249    #[must_use]
250    pub const fn height(&self) -> u64 {
251        self.height
252    }
253
254    /// Returns the data type
255    #[must_use]
256    pub const fn data_type(&self) -> RasterDataType {
257        self.data_type
258    }
259
260    /// Returns the nodata value
261    #[must_use]
262    pub const fn nodata(&self) -> NoDataValue {
263        self.nodata
264    }
265
266    /// Returns the total number of pixels
267    #[must_use]
268    pub const fn pixel_count(&self) -> u64 {
269        self.width * self.height
270    }
271
272    /// Returns the raw bytes
273    #[must_use]
274    pub fn as_bytes(&self) -> &[u8] {
275        &self.data
276    }
277
278    /// Returns mutable raw bytes
279    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
280        &mut self.data
281    }
282
283    /// Consumes the buffer and returns the raw bytes
284    #[must_use]
285    pub fn into_bytes(self) -> Vec<u8> {
286        self.data
287    }
288
289    /// Creates a buffer from typed vector data
290    ///
291    /// # Arguments
292    /// * `width` - Width in pixels
293    /// * `height` - Height in pixels
294    /// * `data` - Typed data (e.g., `Vec<f32>`, `Vec<u8>`)
295    /// * `data_type` - The raster data type
296    ///
297    /// # Errors
298    /// Returns an error if the data size doesn't match dimensions and type
299    pub fn from_typed_vec<T: Copy + 'static>(
300        width: usize,
301        height: usize,
302        data: Vec<T>,
303        data_type: RasterDataType,
304    ) -> Result<Self> {
305        let expected_pixels = width * height;
306        if data.len() != expected_pixels {
307            return Err(OxiGdalError::InvalidParameter {
308                parameter: "data",
309                message: format!(
310                    "Data length mismatch: expected {} pixels for {}x{}, got {}",
311                    expected_pixels,
312                    width,
313                    height,
314                    data.len()
315                ),
316            });
317        }
318
319        // Convert typed data to bytes
320        let type_size = core::mem::size_of::<T>();
321        let expected_type_size = data_type.size_bytes();
322        if type_size != expected_type_size {
323            return Err(OxiGdalError::InvalidParameter {
324                parameter: "data_type",
325                message: format!(
326                    "Type size mismatch: provided type has {} bytes, {:?} expects {} bytes",
327                    type_size, data_type, expected_type_size
328                ),
329            });
330        }
331
332        let byte_data: Vec<u8> = data
333            .iter()
334            .flat_map(|v| {
335                // SAFETY: We're reading the bytes of a Copy type
336                let ptr = v as *const T as *const u8;
337                unsafe { core::slice::from_raw_parts(ptr, type_size) }.to_vec()
338            })
339            .collect();
340
341        Self::new(
342            byte_data,
343            width as u64,
344            height as u64,
345            data_type,
346            NoDataValue::None,
347        )
348    }
349
350    /// Returns the buffer data as a typed slice
351    ///
352    /// # Type Parameters
353    /// * `T` - The target type (must match the buffer's data type size)
354    ///
355    /// # Errors
356    /// Returns an error if the type size doesn't match the data type
357    pub fn as_slice<T: Copy + 'static>(&self) -> Result<&[T]> {
358        let type_size = core::mem::size_of::<T>();
359        let expected_size = self.data_type.size_bytes();
360
361        if type_size != expected_size {
362            return Err(OxiGdalError::InvalidParameter {
363                parameter: "T",
364                message: format!(
365                    "Type size mismatch: requested type has {} bytes, buffer contains {:?} ({} bytes)",
366                    type_size, self.data_type, expected_size
367                ),
368            });
369        }
370
371        let pixel_count = (self.width * self.height) as usize;
372        // SAFETY: We've verified the type size matches, and the data is properly aligned
373        // for the original type it was created with
374        let slice =
375            unsafe { core::slice::from_raw_parts(self.data.as_ptr() as *const T, pixel_count) };
376        Ok(slice)
377    }
378
379    /// Returns the buffer data as a mutable typed slice
380    ///
381    /// # Type Parameters
382    /// * `T` - The target type (must match the buffer's data type size)
383    ///
384    /// # Errors
385    /// Returns an error if the type size doesn't match the data type
386    pub fn as_slice_mut<T: Copy + 'static>(&mut self) -> Result<&mut [T]> {
387        let type_size = core::mem::size_of::<T>();
388        let expected_size = self.data_type.size_bytes();
389
390        if type_size != expected_size {
391            return Err(OxiGdalError::InvalidParameter {
392                parameter: "T",
393                message: format!(
394                    "Type size mismatch: requested type has {} bytes, buffer contains {:?} ({} bytes)",
395                    type_size, self.data_type, expected_size
396                ),
397            });
398        }
399
400        let pixel_count = (self.width * self.height) as usize;
401        // SAFETY: We've verified the type size matches, and the data is properly aligned
402        // for the original type it was created with
403        let slice = unsafe {
404            core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut T, pixel_count)
405        };
406        Ok(slice)
407    }
408
409    /// Gets a pixel value as f64
410    ///
411    /// # Errors
412    /// Returns an error if coordinates are out of bounds
413    pub fn get_pixel(&self, x: u64, y: u64) -> Result<f64> {
414        if x >= self.width || y >= self.height {
415            return Err(OxiGdalError::OutOfBounds {
416                message: format!(
417                    "Pixel ({}, {}) out of bounds for {}x{} buffer",
418                    x, y, self.width, self.height
419                ),
420            });
421        }
422
423        let pixel_size = self.data_type.size_bytes();
424        let offset = (y * self.width + x) as usize * pixel_size;
425
426        let value = match self.data_type {
427            RasterDataType::UInt8 => f64::from(self.data[offset]),
428            RasterDataType::Int8 => f64::from(self.data[offset] as i8),
429            RasterDataType::UInt16 => {
430                let bytes: [u8; 2] = self.data[offset..offset + 2].try_into().map_err(|_| {
431                    OxiGdalError::Internal {
432                        message: "Invalid slice length".to_string(),
433                    }
434                })?;
435                f64::from(u16::from_ne_bytes(bytes))
436            }
437            RasterDataType::Int16 => {
438                let bytes: [u8; 2] = self.data[offset..offset + 2].try_into().map_err(|_| {
439                    OxiGdalError::Internal {
440                        message: "Invalid slice length".to_string(),
441                    }
442                })?;
443                f64::from(i16::from_ne_bytes(bytes))
444            }
445            RasterDataType::UInt32 => {
446                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
447                    OxiGdalError::Internal {
448                        message: "Invalid slice length".to_string(),
449                    }
450                })?;
451                f64::from(u32::from_ne_bytes(bytes))
452            }
453            RasterDataType::Int32 => {
454                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
455                    OxiGdalError::Internal {
456                        message: "Invalid slice length".to_string(),
457                    }
458                })?;
459                f64::from(i32::from_ne_bytes(bytes))
460            }
461            RasterDataType::Float32 => {
462                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
463                    OxiGdalError::Internal {
464                        message: "Invalid slice length".to_string(),
465                    }
466                })?;
467                f64::from(f32::from_ne_bytes(bytes))
468            }
469            RasterDataType::Float64 => {
470                let bytes: [u8; 8] = self.data[offset..offset + 8].try_into().map_err(|_| {
471                    OxiGdalError::Internal {
472                        message: "Invalid slice length".to_string(),
473                    }
474                })?;
475                f64::from_ne_bytes(bytes)
476            }
477            RasterDataType::UInt64 => {
478                let bytes: [u8; 8] = self.data[offset..offset + 8].try_into().map_err(|_| {
479                    OxiGdalError::Internal {
480                        message: "Invalid slice length".to_string(),
481                    }
482                })?;
483                u64::from_ne_bytes(bytes) as f64
484            }
485            RasterDataType::Int64 => {
486                let bytes: [u8; 8] = self.data[offset..offset + 8].try_into().map_err(|_| {
487                    OxiGdalError::Internal {
488                        message: "Invalid slice length".to_string(),
489                    }
490                })?;
491                i64::from_ne_bytes(bytes) as f64
492            }
493            RasterDataType::CFloat32 | RasterDataType::CFloat64 => {
494                // Return only the real part for complex types
495                let bytes: [u8; 4] = self.data[offset..offset + 4].try_into().map_err(|_| {
496                    OxiGdalError::Internal {
497                        message: "Invalid slice length".to_string(),
498                    }
499                })?;
500                f64::from(f32::from_ne_bytes(bytes))
501            }
502        };
503
504        Ok(value)
505    }
506
507    /// Sets a pixel value
508    ///
509    /// # Errors
510    /// Returns an error if coordinates are out of bounds
511    pub fn set_pixel(&mut self, x: u64, y: u64, value: f64) -> Result<()> {
512        if x >= self.width || y >= self.height {
513            return Err(OxiGdalError::OutOfBounds {
514                message: format!(
515                    "Pixel ({}, {}) out of bounds for {}x{} buffer",
516                    x, y, self.width, self.height
517                ),
518            });
519        }
520
521        let pixel_size = self.data_type.size_bytes();
522        let offset = (y * self.width + x) as usize * pixel_size;
523
524        match self.data_type {
525            RasterDataType::UInt8 => {
526                self.data[offset] = value as u8;
527            }
528            RasterDataType::Int8 => {
529                self.data[offset] = (value as i8) as u8;
530            }
531            RasterDataType::UInt16 => {
532                let bytes = (value as u16).to_ne_bytes();
533                self.data[offset..offset + 2].copy_from_slice(&bytes);
534            }
535            RasterDataType::Int16 => {
536                let bytes = (value as i16).to_ne_bytes();
537                self.data[offset..offset + 2].copy_from_slice(&bytes);
538            }
539            RasterDataType::UInt32 => {
540                let bytes = (value as u32).to_ne_bytes();
541                self.data[offset..offset + 4].copy_from_slice(&bytes);
542            }
543            RasterDataType::Int32 => {
544                let bytes = (value as i32).to_ne_bytes();
545                self.data[offset..offset + 4].copy_from_slice(&bytes);
546            }
547            RasterDataType::Float32 => {
548                let bytes = (value as f32).to_ne_bytes();
549                self.data[offset..offset + 4].copy_from_slice(&bytes);
550            }
551            RasterDataType::Float64 => {
552                let bytes = value.to_ne_bytes();
553                self.data[offset..offset + 8].copy_from_slice(&bytes);
554            }
555            RasterDataType::UInt64 => {
556                let bytes = (value as u64).to_ne_bytes();
557                self.data[offset..offset + 8].copy_from_slice(&bytes);
558            }
559            RasterDataType::Int64 => {
560                let bytes = (value as i64).to_ne_bytes();
561                self.data[offset..offset + 8].copy_from_slice(&bytes);
562            }
563            RasterDataType::CFloat32 => {
564                // Set only the real part
565                let bytes = (value as f32).to_ne_bytes();
566                self.data[offset..offset + 4].copy_from_slice(&bytes);
567            }
568            RasterDataType::CFloat64 => {
569                // Set only the real part
570                let bytes = value.to_ne_bytes();
571                self.data[offset..offset + 8].copy_from_slice(&bytes);
572            }
573        }
574
575        Ok(())
576    }
577
578    // ─── Typed Pixel Accessors ────────────────────────────────────────────
579
580    /// Gets a pixel value as `u8`.
581    ///
582    /// # Errors
583    /// Returns an error if coordinates are out of bounds or the buffer type is not `UInt8`.
584    pub fn get_u8(&self, x: u64, y: u64) -> Result<u8> {
585        self.check_bounds(x, y)?;
586        self.check_type(RasterDataType::UInt8)?;
587        let offset = (y * self.width + x) as usize;
588        Ok(self.data[offset])
589    }
590
591    /// Gets a pixel value as `i8`.
592    ///
593    /// # Errors
594    /// Returns an error if coordinates are out of bounds or the buffer type is not `Int8`.
595    pub fn get_i8(&self, x: u64, y: u64) -> Result<i8> {
596        self.check_bounds(x, y)?;
597        self.check_type(RasterDataType::Int8)?;
598        let offset = (y * self.width + x) as usize;
599        Ok(self.data[offset] as i8)
600    }
601
602    /// Gets a pixel value as `u16`.
603    ///
604    /// # Errors
605    /// Returns an error if coordinates are out of bounds or the buffer type is not `UInt16`.
606    pub fn get_u16(&self, x: u64, y: u64) -> Result<u16> {
607        self.check_bounds(x, y)?;
608        self.check_type(RasterDataType::UInt16)?;
609        let offset = (y * self.width + x) as usize * 2;
610        let bytes: [u8; 2] =
611            self.data[offset..offset + 2]
612                .try_into()
613                .map_err(|_| OxiGdalError::Internal {
614                    message: "Invalid slice length".to_string(),
615                })?;
616        Ok(u16::from_ne_bytes(bytes))
617    }
618
619    /// Gets a pixel value as `i16`.
620    ///
621    /// # Errors
622    /// Returns an error if coordinates are out of bounds or the buffer type is not `Int16`.
623    pub fn get_i16(&self, x: u64, y: u64) -> Result<i16> {
624        self.check_bounds(x, y)?;
625        self.check_type(RasterDataType::Int16)?;
626        let offset = (y * self.width + x) as usize * 2;
627        let bytes: [u8; 2] =
628            self.data[offset..offset + 2]
629                .try_into()
630                .map_err(|_| OxiGdalError::Internal {
631                    message: "Invalid slice length".to_string(),
632                })?;
633        Ok(i16::from_ne_bytes(bytes))
634    }
635
636    /// Gets a pixel value as `u32`.
637    ///
638    /// # Errors
639    /// Returns an error if coordinates are out of bounds or the buffer type is not `UInt32`.
640    pub fn get_u32(&self, x: u64, y: u64) -> Result<u32> {
641        self.check_bounds(x, y)?;
642        self.check_type(RasterDataType::UInt32)?;
643        let offset = (y * self.width + x) as usize * 4;
644        let bytes: [u8; 4] =
645            self.data[offset..offset + 4]
646                .try_into()
647                .map_err(|_| OxiGdalError::Internal {
648                    message: "Invalid slice length".to_string(),
649                })?;
650        Ok(u32::from_ne_bytes(bytes))
651    }
652
653    /// Gets a pixel value as `i32`.
654    ///
655    /// # Errors
656    /// Returns an error if coordinates are out of bounds or the buffer type is not `Int32`.
657    pub fn get_i32(&self, x: u64, y: u64) -> Result<i32> {
658        self.check_bounds(x, y)?;
659        self.check_type(RasterDataType::Int32)?;
660        let offset = (y * self.width + x) as usize * 4;
661        let bytes: [u8; 4] =
662            self.data[offset..offset + 4]
663                .try_into()
664                .map_err(|_| OxiGdalError::Internal {
665                    message: "Invalid slice length".to_string(),
666                })?;
667        Ok(i32::from_ne_bytes(bytes))
668    }
669
670    /// Gets a pixel value as `u64`.
671    ///
672    /// # Errors
673    /// Returns an error if coordinates are out of bounds or the buffer type is not `UInt64`.
674    pub fn get_u64(&self, x: u64, y: u64) -> Result<u64> {
675        self.check_bounds(x, y)?;
676        self.check_type(RasterDataType::UInt64)?;
677        let offset = (y * self.width + x) as usize * 8;
678        let bytes: [u8; 8] =
679            self.data[offset..offset + 8]
680                .try_into()
681                .map_err(|_| OxiGdalError::Internal {
682                    message: "Invalid slice length".to_string(),
683                })?;
684        Ok(u64::from_ne_bytes(bytes))
685    }
686
687    /// Gets a pixel value as `i64`.
688    ///
689    /// # Errors
690    /// Returns an error if coordinates are out of bounds or the buffer type is not `Int64`.
691    pub fn get_i64(&self, x: u64, y: u64) -> Result<i64> {
692        self.check_bounds(x, y)?;
693        self.check_type(RasterDataType::Int64)?;
694        let offset = (y * self.width + x) as usize * 8;
695        let bytes: [u8; 8] =
696            self.data[offset..offset + 8]
697                .try_into()
698                .map_err(|_| OxiGdalError::Internal {
699                    message: "Invalid slice length".to_string(),
700                })?;
701        Ok(i64::from_ne_bytes(bytes))
702    }
703
704    /// Gets a pixel value as `f32`.
705    ///
706    /// # Errors
707    /// Returns an error if coordinates are out of bounds or the buffer type is not `Float32`.
708    pub fn get_f32(&self, x: u64, y: u64) -> Result<f32> {
709        self.check_bounds(x, y)?;
710        self.check_type(RasterDataType::Float32)?;
711        let offset = (y * self.width + x) as usize * 4;
712        let bytes: [u8; 4] =
713            self.data[offset..offset + 4]
714                .try_into()
715                .map_err(|_| OxiGdalError::Internal {
716                    message: "Invalid slice length".to_string(),
717                })?;
718        Ok(f32::from_ne_bytes(bytes))
719    }
720
721    /// Gets a pixel value as `f64`.
722    ///
723    /// # Errors
724    /// Returns an error if coordinates are out of bounds or the buffer type is not `Float64`.
725    pub fn get_f64(&self, x: u64, y: u64) -> Result<f64> {
726        self.check_bounds(x, y)?;
727        self.check_type(RasterDataType::Float64)?;
728        let offset = (y * self.width + x) as usize * 8;
729        let bytes: [u8; 8] =
730            self.data[offset..offset + 8]
731                .try_into()
732                .map_err(|_| OxiGdalError::Internal {
733                    message: "Invalid slice length".to_string(),
734                })?;
735        Ok(f64::from_ne_bytes(bytes))
736    }
737
738    // ─── Typed Pixel Setters ─────────────────────────────────────────────
739
740    /// Sets a pixel value from `u8`.
741    ///
742    /// # Errors
743    /// Returns an error if coordinates are out of bounds or the buffer type is not `UInt8`.
744    pub fn set_u8(&mut self, x: u64, y: u64, value: u8) -> Result<()> {
745        self.check_bounds(x, y)?;
746        self.check_type(RasterDataType::UInt8)?;
747        let offset = (y * self.width + x) as usize;
748        self.data[offset] = value;
749        Ok(())
750    }
751
752    /// Sets a pixel value from `f32`.
753    ///
754    /// # Errors
755    /// Returns an error if coordinates are out of bounds or the buffer type is not `Float32`.
756    pub fn set_f32(&mut self, x: u64, y: u64, value: f32) -> Result<()> {
757        self.check_bounds(x, y)?;
758        self.check_type(RasterDataType::Float32)?;
759        let offset = (y * self.width + x) as usize * 4;
760        self.data[offset..offset + 4].copy_from_slice(&value.to_ne_bytes());
761        Ok(())
762    }
763
764    /// Sets a pixel value from `f64`.
765    ///
766    /// # Errors
767    /// Returns an error if coordinates are out of bounds or the buffer type is not `Float64`.
768    pub fn set_f64(&mut self, x: u64, y: u64, value: f64) -> Result<()> {
769        self.check_bounds(x, y)?;
770        self.check_type(RasterDataType::Float64)?;
771        let offset = (y * self.width + x) as usize * 8;
772        self.data[offset..offset + 8].copy_from_slice(&value.to_ne_bytes());
773        Ok(())
774    }
775
776    // ─── Row & Window Access ─────────────────────────────────────────────
777
778    /// Returns a row of pixel data as a typed slice.
779    ///
780    /// # Errors
781    /// Returns an error if `y` is out of bounds or type size mismatches.
782    pub fn row_slice<T: Copy + 'static>(&self, y: u64) -> Result<&[T]> {
783        if y >= self.height {
784            return Err(OxiGdalError::OutOfBounds {
785                message: format!("Row {} out of bounds for height {}", y, self.height),
786            });
787        }
788        let type_size = core::mem::size_of::<T>();
789        let expected_size = self.data_type.size_bytes();
790        if type_size != expected_size {
791            return Err(OxiGdalError::InvalidParameter {
792                parameter: "T",
793                message: format!(
794                    "Type size {} doesn't match {:?} size {}",
795                    type_size, self.data_type, expected_size
796                ),
797            });
798        }
799        let row_start = (y * self.width) as usize * expected_size;
800        let row_end = row_start + self.width as usize * expected_size;
801        // SAFETY: type size verified above, row bounds verified
802        let slice = unsafe {
803            core::slice::from_raw_parts(
804                self.data[row_start..row_end].as_ptr() as *const T,
805                self.width as usize,
806            )
807        };
808        Ok(slice)
809    }
810
811    /// Returns a rectangular window of pixel data as a new `RasterBuffer`.
812    ///
813    /// # Errors
814    /// Returns an error if the window extends outside buffer bounds.
815    pub fn window(&self, x: u64, y: u64, width: u64, height: u64) -> Result<Self> {
816        if x + width > self.width || y + height > self.height {
817            return Err(OxiGdalError::OutOfBounds {
818                message: format!(
819                    "Window ({},{}) {}x{} exceeds buffer {}x{}",
820                    x, y, width, height, self.width, self.height
821                ),
822            });
823        }
824        let pixel_size = self.data_type.size_bytes();
825        let row_bytes = width as usize * pixel_size;
826        let mut data = Vec::with_capacity(height as usize * row_bytes);
827        for row in y..y + height {
828            let src_start = (row * self.width + x) as usize * pixel_size;
829            data.extend_from_slice(&self.data[src_start..src_start + row_bytes]);
830        }
831        Self::new(data, width, height, self.data_type, self.nodata)
832    }
833
834    // ─── Private Helpers ─────────────────────────────────────────────────
835
836    fn check_bounds(&self, x: u64, y: u64) -> Result<()> {
837        if x >= self.width || y >= self.height {
838            return Err(OxiGdalError::OutOfBounds {
839                message: format!(
840                    "Pixel ({}, {}) out of bounds for {}x{} buffer",
841                    x, y, self.width, self.height
842                ),
843            });
844        }
845        Ok(())
846    }
847
848    fn check_type(&self, expected: RasterDataType) -> Result<()> {
849        if self.data_type != expected {
850            return Err(OxiGdalError::InvalidParameter {
851                parameter: "data_type",
852                message: format!(
853                    "Buffer contains {:?} data, requested {:?}",
854                    self.data_type, expected
855                ),
856            });
857        }
858        Ok(())
859    }
860
861    /// Returns true if the given value equals the nodata value
862    #[must_use]
863    pub fn is_nodata(&self, value: f64) -> bool {
864        match self.nodata.as_f64() {
865            Some(nd) => {
866                if nd.is_nan() && value.is_nan() {
867                    true
868                } else {
869                    (nd - value).abs() < f64::EPSILON
870                }
871            }
872            None => false,
873        }
874    }
875
876    /// Converts the buffer to a different data type
877    ///
878    /// # Errors
879    /// Returns an error if conversion fails
880    pub fn convert_to(&self, target_type: RasterDataType) -> Result<Self> {
881        if target_type == self.data_type {
882            return Ok(self.clone());
883        }
884
885        let mut result = Self::zeros(self.width, self.height, target_type);
886        result.nodata = self.nodata;
887
888        for y in 0..self.height {
889            for x in 0..self.width {
890                let value = self.get_pixel(x, y)?;
891                result.set_pixel(x, y, value)?;
892            }
893        }
894
895        Ok(result)
896    }
897
898    /// Computes basic statistics
899    pub fn compute_statistics(&self) -> Result<BufferStatistics> {
900        let mut min = f64::MAX;
901        let mut max = f64::MIN;
902        let mut sum = 0.0;
903        let mut sum_sq = 0.0;
904        let mut valid_count = 0u64;
905
906        for y in 0..self.height {
907            for x in 0..self.width {
908                let value = self.get_pixel(x, y)?;
909                if !self.is_nodata(value) && value.is_finite() {
910                    min = min.min(value);
911                    max = max.max(value);
912                    sum += value;
913                    sum_sq += value * value;
914                    valid_count += 1;
915                }
916            }
917        }
918
919        if valid_count == 0 {
920            return Ok(BufferStatistics {
921                min: f64::NAN,
922                max: f64::NAN,
923                mean: f64::NAN,
924                std_dev: f64::NAN,
925                valid_count: 0,
926                histogram: None,
927            });
928        }
929
930        let mean = sum / valid_count as f64;
931        let variance = (sum_sq / valid_count as f64) - (mean * mean);
932        let std_dev = variance.sqrt();
933
934        Ok(BufferStatistics {
935            min,
936            max,
937            mean,
938            std_dev,
939            valid_count,
940            histogram: None,
941        })
942    }
943
944    /// Computes statistics and an optional histogram in one pass.
945    ///
946    /// The returned [`BufferStatistics`] contains a `histogram` of `bin_count` bins
947    /// with uniform spacing covering the range `[min, max]`. Each bin holds the count
948    /// of valid pixels whose value falls within that bin's interval.
949    ///
950    /// # Arguments
951    ///
952    /// * `bin_count` — Number of histogram bins. Must be ≥ 1.
953    ///
954    /// # Errors
955    ///
956    /// Returns [`crate::error::OxiGdalError::InvalidParameter`] if `bin_count` is 0.
957    /// Returns errors from pixel access on corrupt buffers.
958    ///
959    /// # Notes
960    ///
961    /// - NaN and infinite values are excluded (same as [`RasterBuffer::compute_statistics`]).
962    /// - When all valid values are identical (`min == max`), all counts go into bin 0.
963    /// - When no valid pixels exist, `histogram` is `Some(vec![0; bin_count])`.
964    pub fn compute_statistics_with_histogram(&self, bin_count: usize) -> Result<BufferStatistics> {
965        if bin_count == 0 {
966            return Err(OxiGdalError::InvalidParameter {
967                parameter: "bin_count",
968                message: "bin_count must be at least 1".to_string(),
969            });
970        }
971
972        // First pass: collect min/max/sum/sum_sq and all valid values for histogram.
973        let mut min = f64::MAX;
974        let mut max = f64::MIN;
975        let mut sum = 0.0f64;
976        let mut sum_sq = 0.0f64;
977        let mut valid_count = 0u64;
978        // Collect valid values so histogram binning can reuse them without a re-read.
979        let total_pixels = (self.width * self.height) as usize;
980        let mut valid_values: Vec<f64> = Vec::with_capacity(total_pixels);
981
982        for y in 0..self.height {
983            for x in 0..self.width {
984                let value = self.get_pixel(x, y)?;
985                if !self.is_nodata(value) && value.is_finite() {
986                    min = min.min(value);
987                    max = max.max(value);
988                    sum += value;
989                    sum_sq += value * value;
990                    valid_count += 1;
991                    valid_values.push(value);
992                }
993            }
994        }
995
996        // Build histogram bins (all-zero when there are no valid pixels).
997        let mut bins = vec![0u64; bin_count];
998
999        if valid_count > 0 {
1000            let range = max - min;
1001            if range == 0.0 {
1002                // All valid values are identical: everything goes into bin 0.
1003                bins[0] = valid_count;
1004            } else {
1005                for v in &valid_values {
1006                    let bin_idx = (((v - min) / range) * bin_count as f64).floor() as usize;
1007                    // Clamp to [0, bin_count-1] (handles the max edge case exactly).
1008                    let bin_idx = bin_idx.min(bin_count - 1);
1009                    bins[bin_idx] += 1;
1010                }
1011            }
1012        }
1013
1014        if valid_count == 0 {
1015            return Ok(BufferStatistics {
1016                min: f64::NAN,
1017                max: f64::NAN,
1018                mean: f64::NAN,
1019                std_dev: f64::NAN,
1020                valid_count: 0,
1021                histogram: Some(bins),
1022            });
1023        }
1024
1025        let mean = sum / valid_count as f64;
1026        let variance = (sum_sq / valid_count as f64) - (mean * mean);
1027        let std_dev = variance.max(0.0).sqrt();
1028
1029        Ok(BufferStatistics {
1030            min,
1031            max,
1032            mean,
1033            std_dev,
1034            valid_count,
1035            histogram: Some(bins),
1036        })
1037    }
1038}
1039
1040impl fmt::Debug for RasterBuffer {
1041    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1042        f.debug_struct("RasterBuffer")
1043            .field("width", &self.width)
1044            .field("height", &self.height)
1045            .field("data_type", &self.data_type)
1046            .field("nodata", &self.nodata)
1047            .field("bytes", &self.data.len())
1048            .finish()
1049    }
1050}
1051
1052/// Statistics computed from a buffer
1053///
1054/// Note: `Copy` is intentionally not derived because the optional histogram
1055/// contains a `Vec`, making bitwise copy semantics inappropriate.
1056#[derive(Debug, Clone, PartialEq)]
1057pub struct BufferStatistics {
1058    /// Minimum value
1059    pub min: f64,
1060    /// Maximum value
1061    pub max: f64,
1062    /// Mean value
1063    pub mean: f64,
1064    /// Standard deviation
1065    pub std_dev: f64,
1066    /// Number of valid (non-nodata) pixels
1067    pub valid_count: u64,
1068    /// Optional histogram bin counts (uniform spacing from `min` to `max`)
1069    ///
1070    /// `None` when computed via [`RasterBuffer::compute_statistics`].
1071    /// `Some(bins)` when computed via [`RasterBuffer::compute_statistics_with_histogram`].
1072    pub histogram: Option<Vec<u64>>,
1073}
1074
1075#[cfg(feature = "arrow")]
1076mod arrow_support {
1077    //! Arrow integration for zero-copy interoperability
1078
1079    use arrow_array::{Array, Float64Array};
1080
1081    use super::{OxiGdalError, RasterBuffer, Result};
1082
1083    impl RasterBuffer {
1084        /// Creates a `RasterBuffer` from an Arrow array
1085        ///
1086        /// # Errors
1087        /// Returns an error if the array type doesn't match
1088        pub fn from_arrow_array<A: Array>(_array: &A, _width: u64, _height: u64) -> Result<Self> {
1089            // This is a simplified implementation
1090            // A full implementation would handle all Arrow types
1091            Err(OxiGdalError::NotSupported {
1092                operation: "Arrow array conversion".to_string(),
1093            })
1094        }
1095
1096        /// Converts to an Arrow `Float64Array`
1097        pub fn to_float64_array(&self) -> Result<Float64Array> {
1098            let mut values = Vec::with_capacity(self.pixel_count() as usize);
1099            for y in 0..self.height {
1100                for x in 0..self.width {
1101                    values.push(self.get_pixel(x, y)?);
1102                }
1103            }
1104            Ok(Float64Array::from(values))
1105        }
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    #![allow(clippy::expect_used)]
1112
1113    use super::*;
1114
1115    #[test]
1116    fn test_buffer_creation() {
1117        let buffer = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
1118        assert_eq!(buffer.width(), 100);
1119        assert_eq!(buffer.height(), 100);
1120        assert_eq!(buffer.pixel_count(), 10_000);
1121        assert_eq!(buffer.as_bytes().len(), 10_000);
1122    }
1123
1124    #[test]
1125    fn test_pixel_access() {
1126        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
1127
1128        buffer.set_pixel(5, 5, 42.0).expect("set should work");
1129        let value = buffer.get_pixel(5, 5).expect("get should work");
1130        assert!((value - 42.0).abs() < f64::EPSILON);
1131
1132        // Out of bounds
1133        assert!(buffer.get_pixel(100, 0).is_err());
1134        assert!(buffer.set_pixel(0, 100, 0.0).is_err());
1135    }
1136
1137    #[test]
1138    fn test_nodata() {
1139        let buffer = RasterBuffer::nodata_filled(
1140            10,
1141            10,
1142            RasterDataType::Float32,
1143            NoDataValue::Float(-9999.0),
1144        );
1145
1146        assert!(buffer.is_nodata(-9999.0));
1147        assert!(!buffer.is_nodata(0.0));
1148    }
1149
1150    #[test]
1151    fn test_statistics() {
1152        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
1153
1154        // Fill with values 0-99
1155        for y in 0..10 {
1156            for x in 0..10 {
1157                let value = (y * 10 + x) as f64;
1158                buffer.set_pixel(x, y, value).expect("set should work");
1159            }
1160        }
1161
1162        let stats = buffer.compute_statistics().expect("stats should work");
1163        assert!((stats.min - 0.0).abs() < f64::EPSILON);
1164        assert!((stats.max - 99.0).abs() < f64::EPSILON);
1165        assert!((stats.mean - 49.5).abs() < 0.01);
1166        assert_eq!(stats.valid_count, 100);
1167    }
1168
1169    #[test]
1170    fn test_data_validation() {
1171        // Wrong size should fail
1172        let result = RasterBuffer::new(
1173            vec![0u8; 100],
1174            10,
1175            10,
1176            RasterDataType::UInt16, // Needs 200 bytes
1177            NoDataValue::None,
1178        );
1179        assert!(result.is_err());
1180
1181        // Correct size should succeed
1182        let result = RasterBuffer::new(
1183            vec![0u8; 200],
1184            10,
1185            10,
1186            RasterDataType::UInt16,
1187            NoDataValue::None,
1188        );
1189        assert!(result.is_ok());
1190    }
1191
1192    #[test]
1193    fn test_typed_get_u8() {
1194        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
1195        buffer.set_pixel(3, 4, 200.0).expect("set should work");
1196        assert_eq!(buffer.get_u8(3, 4).expect("get_u8"), 200);
1197        // Wrong type should error
1198        assert!(buffer.get_f32(3, 4).is_err());
1199    }
1200
1201    #[test]
1202    fn test_typed_get_i8() {
1203        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Int8);
1204        buffer.set_pixel(0, 0, -42.0).expect("set should work");
1205        assert_eq!(buffer.get_i8(0, 0).expect("get_i8"), -42);
1206    }
1207
1208    #[test]
1209    fn test_typed_get_u16() {
1210        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt16);
1211        buffer.set_pixel(5, 5, 60000.0).expect("set should work");
1212        assert_eq!(buffer.get_u16(5, 5).expect("get_u16"), 60000);
1213    }
1214
1215    #[test]
1216    fn test_typed_get_i16() {
1217        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Int16);
1218        buffer.set_pixel(2, 3, -1234.0).expect("set should work");
1219        assert_eq!(buffer.get_i16(2, 3).expect("get_i16"), -1234);
1220    }
1221
1222    #[test]
1223    fn test_typed_get_u32() {
1224        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt32);
1225        buffer.set_pixel(0, 0, 100_000.0).expect("set should work");
1226        assert_eq!(buffer.get_u32(0, 0).expect("get_u32"), 100_000);
1227    }
1228
1229    #[test]
1230    fn test_typed_get_i32() {
1231        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Int32);
1232        buffer.set_pixel(1, 1, -50_000.0).expect("set should work");
1233        assert_eq!(buffer.get_i32(1, 1).expect("get_i32"), -50_000);
1234    }
1235
1236    #[test]
1237    fn test_typed_get_u64() {
1238        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt64);
1239        buffer
1240            .set_pixel(0, 0, 1_000_000.0)
1241            .expect("set should work");
1242        assert_eq!(buffer.get_u64(0, 0).expect("get_u64"), 1_000_000);
1243    }
1244
1245    #[test]
1246    fn test_typed_get_i64() {
1247        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Int64);
1248        buffer.set_pixel(0, 0, -999_999.0).expect("set should work");
1249        assert_eq!(buffer.get_i64(0, 0).expect("get_i64"), -999_999);
1250    }
1251
1252    #[test]
1253    fn test_typed_get_f32() {
1254        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
1255        buffer
1256            .set_f32(7, 8, core::f32::consts::PI)
1257            .expect("set_f32 should work");
1258        let val = buffer.get_f32(7, 8).expect("get_f32");
1259        assert!((val - core::f32::consts::PI).abs() < 1e-5);
1260    }
1261
1262    #[test]
1263    fn test_typed_get_f64() {
1264        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::Float64);
1265        buffer
1266            .set_f64(9, 9, core::f64::consts::E)
1267            .expect("set_f64 should work");
1268        let val = buffer.get_f64(9, 9).expect("get_f64");
1269        assert!((val - core::f64::consts::E).abs() < 1e-9);
1270    }
1271
1272    #[test]
1273    fn test_typed_set_u8() {
1274        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
1275        buffer.set_u8(0, 0, 255).expect("set_u8 should work");
1276        assert_eq!(buffer.get_u8(0, 0).expect("get_u8"), 255);
1277    }
1278
1279    #[test]
1280    fn test_typed_out_of_bounds() {
1281        let buffer = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
1282        assert!(buffer.get_f32(10, 0).is_err());
1283        assert!(buffer.get_f32(0, 10).is_err());
1284    }
1285
1286    #[test]
1287    fn test_typed_wrong_type() {
1288        let buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
1289        assert!(buffer.get_f32(0, 0).is_err());
1290        assert!(buffer.get_u16(0, 0).is_err());
1291        assert!(buffer.get_i32(0, 0).is_err());
1292        assert!(buffer.get_f64(0, 0).is_err());
1293    }
1294
1295    #[test]
1296    fn test_row_slice() {
1297        let mut buffer = RasterBuffer::zeros(5, 3, RasterDataType::Float32);
1298        for x in 0..5 {
1299            buffer
1300                .set_pixel(x, 1, (x + 10) as f64)
1301                .expect("set should work");
1302        }
1303        let row: &[f32] = buffer.row_slice(1).expect("row_slice should work");
1304        assert_eq!(row.len(), 5);
1305        assert!((row[0] - 10.0).abs() < 1e-5);
1306        assert!((row[4] - 14.0).abs() < 1e-5);
1307    }
1308
1309    #[test]
1310    fn test_row_slice_out_of_bounds() {
1311        let buffer = RasterBuffer::zeros(5, 3, RasterDataType::Float32);
1312        assert!(buffer.row_slice::<f32>(3).is_err());
1313    }
1314
1315    #[test]
1316    fn test_window() {
1317        let mut buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
1318        buffer.set_pixel(5, 5, 42.0).expect("set should work");
1319        buffer.set_pixel(6, 6, 99.0).expect("set should work");
1320
1321        let win = buffer.window(4, 4, 4, 4).expect("window should work");
1322        assert_eq!(win.width(), 4);
1323        assert_eq!(win.height(), 4);
1324        // (5,5) in original -> (1,1) in window
1325        let val = win.get_pixel(1, 1).expect("get should work");
1326        assert!((val - 42.0).abs() < f64::EPSILON);
1327        // (6,6) in original -> (2,2) in window
1328        let val = win.get_pixel(2, 2).expect("get should work");
1329        assert!((val - 99.0).abs() < f64::EPSILON);
1330    }
1331
1332    #[test]
1333    fn test_window_out_of_bounds() {
1334        let buffer = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
1335        assert!(buffer.window(8, 8, 4, 4).is_err());
1336    }
1337}