1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! Per-band raster statistics: [`BandStatistics`] and
//! [`Dataset::statistics`](crate::Dataset::statistics).
//!
//! The computation reuses the same clip-window plumbing as the pixel readers in
//! [`crate::raster_read`], so statistics of a clipped dataset describe the
//! clipped region rather than the whole file.
use crate::{Dataset, OxiGeoError, Result};
#[cfg(feature = "geotiff")]
use crate::DatasetFormat;
/// Statistics for a single raster band.
///
/// Returned by [`Dataset::statistics`].
#[derive(Debug, Clone, PartialEq)]
pub struct BandStatistics {
/// 0-based band index.
pub band: u32,
/// Minimum valid pixel value (non-nodata, finite).
pub min: f64,
/// Maximum valid pixel value (non-nodata, finite).
pub max: f64,
/// Arithmetic mean of valid pixels.
pub mean: f64,
/// Population standard deviation of valid pixels.
pub std_dev: f64,
/// Count of valid (non-nodata, finite) pixels.
pub valid_count: u64,
}
impl Dataset {
/// Compute per-band raster statistics (min / max / mean / std_dev / valid_count).
///
/// Currently supported for GeoTIFF datasets (requires the `geotiff` feature).
/// For all other formats or when the feature flag is absent the method returns
/// [`OxiGeoError::NotSupported`].
///
/// `band` is **0-based**: band 0 is the first raster band.
///
/// # Errors
///
/// - [`OxiGeoError::NotSupported`] — format is not a supported raster type or
/// the required feature flag is disabled.
/// - [`OxiGeoError::InvalidParameter`] — `band` index is out of range.
/// - [`OxiGeoError::Io`] / [`OxiGeoError::Format`] — underlying read failure.
pub fn statistics(&self, band: u32) -> Result<BandStatistics> {
self.compute_band_statistics(band)
}
/// Inner implementation for [`Self::statistics`].
fn compute_band_statistics(&self, band: u32) -> Result<BandStatistics> {
// Validate band range against known band count (only when we have metadata)
if self.info.band_count > 0 && band >= self.info.band_count {
return Err(OxiGeoError::InvalidParameter {
parameter: "band",
message: format!(
"band index {} is out of range (dataset has {} bands)",
band, self.info.band_count
),
});
}
// Dispatch to the GeoTIFF reader path when the feature is compiled in.
#[cfg(feature = "geotiff")]
if matches!(self.info.format, DatasetFormat::GeoTiff) {
return self.statistics_geotiff(band);
}
Err(OxiGeoError::NotSupported {
operation: format!(
"statistics() is not supported for format '{}' (enable the 'geotiff' feature for GeoTIFF support)",
self.info.format.driver_name()
),
})
}
/// GeoTIFF-specific statistics reader.
///
/// Delegates the pixel fetch to [`Dataset::read_band`], which already reads
/// only the blocks the dataset's current (possibly clipped) extent covers —
/// so statistics of a clipped dataset cost a windowed read, not a full-band
/// read followed by a crop.
#[cfg(feature = "geotiff")]
fn statistics_geotiff(&self, band: u32) -> Result<BandStatistics> {
let buf = self.read_band(band)?;
let buf_stats = buf.compute_statistics()?;
Ok(BandStatistics {
band,
min: buf_stats.min,
max: buf_stats.max,
mean: buf_stats.mean,
std_dev: buf_stats.std_dev,
valid_count: buf_stats.valid_count,
})
}
}