1use crate::error::{Result, VrtError};
4use oxigdal_core::types::{GeoTransform, NoDataValue, RasterDataType};
5use serde::{Deserialize, Serialize};
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct SourceFilename {
11 pub path: PathBuf,
13 pub relative_to_vrt: bool,
15 pub shared: bool,
17}
18
19impl SourceFilename {
20 pub fn new<P: AsRef<Path>>(path: P, relative_to_vrt: bool) -> Self {
22 Self {
23 path: path.as_ref().to_path_buf(),
24 relative_to_vrt,
25 shared: false,
26 }
27 }
28
29 pub fn absolute<P: AsRef<Path>>(path: P) -> Self {
31 Self::new(path, false)
32 }
33
34 pub fn relative<P: AsRef<Path>>(path: P) -> Self {
36 Self::new(path, true)
37 }
38
39 pub fn with_shared(mut self, shared: bool) -> Self {
41 self.shared = shared;
42 self
43 }
44
45 pub fn resolve<P: AsRef<Path>>(&self, vrt_path: P) -> Result<PathBuf> {
50 if self.relative_to_vrt {
51 let vrt_dir = vrt_path.as_ref().parent().ok_or_else(|| {
52 VrtError::path_resolution(
53 self.path.display().to_string(),
54 "VRT path has no parent directory",
55 )
56 })?;
57 Ok(vrt_dir.join(&self.path))
58 } else {
59 Ok(self.path.clone())
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
66pub struct PixelRect {
67 pub x_off: u64,
69 pub y_off: u64,
71 pub x_size: u64,
73 pub y_size: u64,
75}
76
77impl PixelRect {
78 pub fn new(x_off: u64, y_off: u64, x_size: u64, y_size: u64) -> Self {
80 Self {
81 x_off,
82 y_off,
83 x_size,
84 y_size,
85 }
86 }
87
88 pub fn is_valid(&self) -> bool {
90 self.x_size > 0 && self.y_size > 0
91 }
92
93 pub fn contains(&self, x: u64, y: u64) -> bool {
95 x >= self.x_off
96 && x < self.x_off.saturating_add(self.x_size)
97 && y >= self.y_off
98 && y < self.y_off.saturating_add(self.y_size)
99 }
100
101 pub fn intersect(&self, other: &Self) -> Option<Self> {
103 let x1 = self.x_off.max(other.x_off);
104 let y1 = self.y_off.max(other.y_off);
105 let x2 = (self.x_off + self.x_size).min(other.x_off + other.x_size);
106 let y2 = (self.y_off + self.y_size).min(other.y_off + other.y_size);
107
108 if x2 > x1 && y2 > y1 {
109 Some(Self::new(x1, y1, x2 - x1, y2 - y1))
110 } else {
111 None
112 }
113 }
114
115 pub fn intersects(&self, other: &Self) -> bool {
117 self.intersect(other).is_some()
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub struct SourceWindow {
124 pub src_rect: PixelRect,
126 pub dst_rect: PixelRect,
128}
129
130impl SourceWindow {
131 pub fn new(src_rect: PixelRect, dst_rect: PixelRect) -> Self {
133 Self { src_rect, dst_rect }
134 }
135
136 pub fn identity(width: u64, height: u64) -> Self {
138 let rect = PixelRect::new(0, 0, width, height);
139 Self::new(rect, rect)
140 }
141
142 pub fn validate(&self) -> Result<()> {
147 if !self.src_rect.is_valid() {
148 return Err(VrtError::invalid_window("Source rectangle is invalid"));
149 }
150 if !self.dst_rect.is_valid() {
151 return Err(VrtError::invalid_window("Destination rectangle is invalid"));
152 }
153 Ok(())
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub struct VrtSource {
160 pub filename: SourceFilename,
162 pub source_band: usize,
164 pub window: Option<SourceWindow>,
166 pub nodata: Option<NoDataValue>,
168 pub data_type: Option<RasterDataType>,
170 pub properties: Option<SourceProperties>,
172}
173
174impl VrtSource {
175 pub fn new(filename: SourceFilename, source_band: usize) -> Self {
177 Self {
178 filename,
179 source_band,
180 window: None,
181 nodata: None,
182 data_type: None,
183 properties: None,
184 }
185 }
186
187 pub fn simple<P: AsRef<Path>>(path: P, band: usize) -> Self {
189 Self::new(SourceFilename::absolute(path), band)
190 }
191
192 pub fn with_window(mut self, window: SourceWindow) -> Self {
194 self.window = Some(window);
195 self
196 }
197
198 pub fn with_nodata(mut self, nodata: NoDataValue) -> Self {
200 self.nodata = Some(nodata);
201 self
202 }
203
204 pub fn with_data_type(mut self, data_type: RasterDataType) -> Self {
206 self.data_type = Some(data_type);
207 self
208 }
209
210 pub fn with_properties(mut self, properties: SourceProperties) -> Self {
212 self.properties = Some(properties);
213 self
214 }
215
216 pub fn validate(&self) -> Result<()> {
221 if self.source_band == 0 {
222 return Err(VrtError::invalid_source("Source band must be >= 1"));
223 }
224
225 if let Some(ref window) = self.window {
226 window.validate()?;
227 }
228
229 Ok(())
230 }
231
232 pub fn dst_rect(&self) -> Option<PixelRect> {
234 self.window.as_ref().map(|w| w.dst_rect)
235 }
236
237 pub fn src_rect(&self) -> Option<PixelRect> {
239 self.window.as_ref().map(|w| w.src_rect)
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub struct SourceProperties {
246 pub width: u64,
248 pub height: u64,
250 pub band_count: usize,
252 pub data_type: RasterDataType,
254 pub geo_transform: Option<GeoTransform>,
256 pub nodata: NoDataValue,
258}
259
260impl SourceProperties {
261 pub fn new(width: u64, height: u64, band_count: usize, data_type: RasterDataType) -> Self {
263 Self {
264 width,
265 height,
266 band_count,
267 data_type,
268 geo_transform: None,
269 nodata: NoDataValue::None,
270 }
271 }
272
273 pub fn with_geo_transform(mut self, geo_transform: GeoTransform) -> Self {
275 self.geo_transform = Some(geo_transform);
276 self
277 }
278
279 pub fn with_nodata(mut self, nodata: NoDataValue) -> Self {
281 self.nodata = nodata;
282 self
283 }
284
285 pub fn validate_band(&self, band: usize) -> Result<()> {
290 if band == 0 || band > self.band_count {
291 return Err(VrtError::band_out_of_range(band, self.band_count));
292 }
293 Ok(())
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_source_filename() {
303 let filename = SourceFilename::absolute("/path/to/file.tif");
304 assert_eq!(filename.path, PathBuf::from("/path/to/file.tif"));
305 assert!(!filename.relative_to_vrt);
306
307 let filename = SourceFilename::relative("data/file.tif");
308 assert!(filename.relative_to_vrt);
309 }
310
311 #[test]
312 fn test_pixel_rect() {
313 let rect = PixelRect::new(10, 20, 100, 200);
314 assert!(rect.is_valid());
315 assert!(rect.contains(10, 20));
316 assert!(rect.contains(50, 100));
317 assert!(!rect.contains(5, 20));
318 assert!(!rect.contains(200, 20));
319 }
320
321 #[test]
322 fn test_rect_intersection() {
323 let rect1 = PixelRect::new(0, 0, 100, 100);
324 let rect2 = PixelRect::new(50, 50, 100, 100);
325
326 let intersection = rect1.intersect(&rect2);
327 assert!(intersection.is_some());
328 let inter = intersection.expect("Should have intersection");
329 assert_eq!(inter.x_off, 50);
330 assert_eq!(inter.y_off, 50);
331 assert_eq!(inter.x_size, 50);
332 assert_eq!(inter.y_size, 50);
333
334 let rect3 = PixelRect::new(200, 200, 100, 100);
335 assert!(rect1.intersect(&rect3).is_none());
336 }
337
338 #[test]
339 fn test_source_window() {
340 let src_rect = PixelRect::new(0, 0, 512, 512);
341 let dst_rect = PixelRect::new(100, 100, 512, 512);
342 let window = SourceWindow::new(src_rect, dst_rect);
343
344 assert!(window.validate().is_ok());
345 }
346
347 #[test]
348 fn test_vrt_source() {
349 let source = VrtSource::simple("/path/to/file.tif", 1);
350 assert_eq!(source.source_band, 1);
351 assert!(source.validate().is_ok());
352
353 let invalid_source = VrtSource::simple("/path/to/file.tif", 0);
354 assert!(invalid_source.validate().is_err());
355 }
356
357 #[test]
358 fn test_source_properties() {
359 let props = SourceProperties::new(512, 512, 3, RasterDataType::UInt8);
360 assert_eq!(props.width, 512);
361 assert_eq!(props.band_count, 3);
362
363 assert!(props.validate_band(1).is_ok());
364 assert!(props.validate_band(3).is_ok());
365 assert!(props.validate_band(0).is_err());
366 assert!(props.validate_band(4).is_err());
367 }
368}