eorst/
metadata.rs

1//! Metadata types for raster datasets.
2//!
3//! This module provides types for describing the geographic extent, layers,
4//! and metadata of raster datasets.
5
6use crate::core_types::{RasterData, RasterType};
7use crate::data_sources::DateType;
8use crate::types::{BlockSize, GeoTransform, RasterDataShape};
9use anyhow::Result;
10use num_traits::NumCast;
11use std::path::PathBuf;
12use std::str::FromStr;
13
14/// Geographic extent defined by bounding box coordinates.
15#[derive(Debug, PartialEq, Clone)]
16pub struct Extent {
17    /// Minimum x coordinate (longitude/west)
18    pub xmin: f64,
19    /// Minimum y coordinate (latitude/south)
20    pub ymin: f64,
21    /// Maximum x coordinate (longitude/east)
22    pub xmax: f64,
23    /// Maximum y coordinate (latitude/north)
24    pub ymax: f64,
25}
26
27impl FromStr for Extent {
28    type Err = String;
29
30    fn from_str(s: &str) -> Result<Self, Self::Err> {
31        let parts: Vec<&str> = s.split(',').collect();
32        if parts.len() != 4 {
33            return Err("Extent must be in the format xmin,ymin,xmax,ymax".to_string());
34        }
35
36        let xmin = parts[0].parse().map_err(|_| "Invalid xmin")?;
37        let ymin = parts[1].parse().map_err(|_| "Invalid ymin")?;
38        let xmax = parts[2].parse().map_err(|_| "Invalid xmax")?;
39        let ymax = parts[3].parse().map_err(|_| "Invalid ymax")?;
40
41        Ok(Extent {
42            xmin,
43            ymin,
44            xmax,
45            ymax,
46        })
47    }
48}
49
50impl Extent {
51    /// Round extent coordinates outward so they align with a resolution grid.
52    ///
53    /// `xmin`/`ymin` are floored, `xmax`/`ymax` are ceiled, ensuring the
54    /// snapped extent fully covers the original extent.
55    pub fn snap_to_grid(&self, resolution: f64) -> Self {
56        Extent {
57            xmin: (self.xmin / resolution).floor() * resolution,
58            ymin: (self.ymin / resolution).floor() * resolution,
59            xmax: (self.xmax / resolution).ceil() * resolution,
60            ymax: (self.ymax / resolution).ceil() * resolution,
61        }
62    }
63
64    /// Expand this extent to include another extent (union).
65    pub fn union(&self, other: &Self) -> Self {
66        Extent {
67            xmin: self.xmin.min(other.xmin),
68            ymin: self.ymin.min(other.ymin),
69            xmax: self.xmax.max(other.xmax),
70            ymax: self.ymax.max(other.ymax),
71        }
72    }
73}
74
75/// Represents a layer (band) within a raster dataset.
76///
77/// Each layer corresponds to a specific band or variable in the data.
78#[derive(Debug, Clone, PartialEq)]
79pub struct Layer {
80    pub source: PathBuf,
81    pub(crate) layer_pos: usize,
82    pub(crate) time_pos: usize,
83}
84
85impl Layer {
86    pub fn new(source: PathBuf, layer_pos: usize, time_pos: usize) -> Self {
87        Layer {
88            source,
89            layer_pos,
90            time_pos,
91        }
92    }
93
94    /// Updates the layer position after stacking.
95    pub fn stack_position(
96        &mut self,
97        original_layer: Layer,
98        dimension_to_stack: crate::types::Dimension,
99        max_dim: usize,
100    ) -> &mut Layer {
101        match dimension_to_stack {
102            crate::types::Dimension::Layer => self.layer_pos = original_layer.layer_pos + max_dim,
103            crate::types::Dimension::Time => self.time_pos = original_layer.time_pos + max_dim,
104        }
105        self
106    }
107}
108
109/// Metadata describing a raster dataset's structure, CRS, and band configuration.
110#[derive(Debug, Clone, PartialEq)]
111pub struct RasterMetadata<U>
112where
113    U: RasterType,
114{
115    /// Layers (bands) in this dataset
116    pub layers: Vec<Layer>,
117    /// Shape of the raster data (times, layers, rows, cols)
118    pub shape: RasterDataShape,
119    pub(crate) block_size: BlockSize,
120    /// EPSG coordinate reference system code
121    pub epsg_code: u32,
122    /// Geographic transformation parameters
123    pub geo_transform: GeoTransform,
124    /// Size of overlap between blocks
125    pub overlap_size: usize,
126    /// Date indices for time-series data
127    pub date_indices: Vec<DateType>,
128    /// Layer identifiers
129    pub layer_indices: Vec<String>,
130    /// No-data value
131    pub na_value: U,
132}
133
134impl<U> Default for RasterMetadata<U>
135where
136    U: RasterType,
137{
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl<U> RasterMetadata<U>
144where
145    U: RasterType,
146{
147    /// Creates a new empty RasterMetadata.
148    pub fn new() -> Self {
149        let layers = Vec::new();
150        let shape = RasterDataShape {
151            times: 0,
152            layers: 0,
153            rows: 0,
154            cols: 0,
155        };
156        let block_size = BlockSize { rows: 0, cols: 0 };
157        let epsg_code = 4326;
158        let geo_transform = GeoTransform {
159            x_ul: 0.,
160            x_res: 0.,
161            x_rot: 0.,
162            y_ul: 0.,
163            y_rot: 0.,
164            y_res: 0.,
165        };
166        let overlap_size = 0;
167        let date_indices = Vec::new();
168        let layer_indices = Vec::new();
169        let na_value = U::zero();
170
171        RasterMetadata {
172            layers,
173            shape,
174            block_size,
175            epsg_code,
176            geo_transform,
177            overlap_size,
178            date_indices,
179            layer_indices,
180            na_value: NumCast::from(na_value).unwrap(),
181        }
182    }
183}
184
185/// A block of raster data with its associated metadata and no-data value.
186#[derive(Debug, Clone, PartialEq)]
187pub struct RasterDataBlock<T>
188where
189    T: RasterType,
190{
191    /// The raster data array
192    pub data: RasterData<T>,
193    /// Metadata associated with this block
194    pub metadata: RasterMetadata<T>,
195    /// No-data value for this block
196    pub no_data: T,
197}