1use crate::band::VrtBand;
4use crate::dataset::VrtDataset;
5use crate::error::{Result, VrtError};
6use crate::source::{PixelRect, SourceWindow, VrtSource};
7use crate::xml::VrtXmlWriter;
8use oxigdal_core::types::{GeoTransform, RasterDataType};
9use std::path::{Path, PathBuf};
10
11pub struct VrtBuilder {
13 dataset: VrtDataset,
14 auto_calculate_extent: bool,
15 vrt_path: Option<PathBuf>,
16}
17
18impl VrtBuilder {
19 pub fn new() -> Self {
21 Self {
22 dataset: VrtDataset::new(0, 0),
23 auto_calculate_extent: true,
24 vrt_path: None,
25 }
26 }
27
28 pub fn with_size(width: u64, height: u64) -> Self {
30 Self {
31 dataset: VrtDataset::new(width, height),
32 auto_calculate_extent: false,
33 vrt_path: None,
34 }
35 }
36
37 pub fn with_vrt_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
39 self.vrt_path = Some(path.into());
40 self
41 }
42
43 pub fn with_srs<S: Into<String>>(mut self, srs: S) -> Self {
45 self.dataset = self.dataset.with_srs(srs);
46 self
47 }
48
49 pub fn with_geo_transform(mut self, geo_transform: GeoTransform) -> Self {
51 self.dataset = self.dataset.with_geo_transform(geo_transform);
52 self
53 }
54
55 pub fn with_block_size(mut self, width: u32, height: u32) -> Self {
57 self.dataset = self.dataset.with_block_size(width, height);
58 self
59 }
60
61 pub fn add_source<P: AsRef<Path>>(
66 mut self,
67 path: P,
68 band_num: usize,
69 source_band: usize,
70 ) -> Result<Self> {
71 let source = VrtSource::simple(path.as_ref(), source_band);
72 self.add_source_to_band(band_num, source)?;
73 Ok(self)
74 }
75
76 pub fn add_source_with_window<P: AsRef<Path>>(
81 mut self,
82 path: P,
83 band_num: usize,
84 source_band: usize,
85 src_rect: PixelRect,
86 dst_rect: PixelRect,
87 ) -> Result<Self> {
88 let window = SourceWindow::new(src_rect, dst_rect);
89 let source = VrtSource::simple(path.as_ref(), source_band).with_window(window);
90 self.add_source_to_band(band_num, source)?;
91 Ok(self)
92 }
93
94 pub fn add_tile<P: AsRef<Path>>(
99 mut self,
100 path: P,
101 x_off: u64,
102 y_off: u64,
103 width: u64,
104 height: u64,
105 ) -> Result<Self> {
106 let src_rect = PixelRect::new(0, 0, width, height);
107 let dst_rect = PixelRect::new(x_off, y_off, width, height);
108 let window = SourceWindow::new(src_rect, dst_rect);
109
110 let source = VrtSource::simple(path.as_ref(), 1).with_window(window);
111 self.add_source_to_band(1, source)?;
112 Ok(self)
113 }
114
115 pub fn add_tile_grid<P>(
120 mut self,
121 paths: &[P],
122 tile_width: u64,
123 tile_height: u64,
124 cols: usize,
125 ) -> Result<Self>
126 where
127 P: AsRef<Path>,
128 {
129 for (idx, path) in paths.iter().enumerate() {
130 let col = idx % cols;
131 let row = idx / cols;
132 let x_off = col as u64 * tile_width;
133 let y_off = row as u64 * tile_height;
134
135 let src_rect = PixelRect::new(0, 0, tile_width, tile_height);
136 let dst_rect = PixelRect::new(x_off, y_off, tile_width, tile_height);
137 let window = SourceWindow::new(src_rect, dst_rect);
138
139 let source = VrtSource::simple(path.as_ref(), 1).with_window(window);
140 self.add_source_to_band(1, source)?;
141 }
142
143 Ok(self)
144 }
145
146 pub fn add_band(mut self, band: VrtBand) -> Result<Self> {
151 band.validate()?;
152 self.dataset.add_band(band);
153 Ok(self)
154 }
155
156 pub fn set_dimensions(mut self, width: u64, height: u64) -> Self {
158 self.dataset.raster_x_size = width;
159 self.dataset.raster_y_size = height;
160 self.auto_calculate_extent = false;
161 self
162 }
163
164 pub fn build(mut self) -> Result<VrtDataset> {
169 if self.auto_calculate_extent && self.dataset.raster_x_size == 0 {
171 self.calculate_extent()?;
172 }
173
174 if let Some(path) = self.vrt_path {
176 self.dataset.vrt_path = Some(path);
177 }
178
179 self.dataset.validate()?;
181
182 Ok(self.dataset)
183 }
184
185 pub fn build_file<P: AsRef<Path>>(mut self, path: P) -> Result<VrtDataset> {
190 let path = path.as_ref();
192 self.vrt_path = Some(path.to_path_buf());
193
194 let dataset = self.build()?;
195 VrtXmlWriter::write_file(&dataset, path)?;
196 Ok(dataset)
197 }
198
199 fn add_source_to_band(&mut self, band_num: usize, source: VrtSource) -> Result<()> {
201 source.validate()?;
202
203 let band_idx = band_num - 1;
205 while self.dataset.bands.len() <= band_idx {
206 let new_band_num = self.dataset.bands.len() + 1;
207 let data_type = source
208 .data_type
209 .or_else(|| source.properties.as_ref().map(|p| p.data_type))
210 .unwrap_or(RasterDataType::UInt8);
211 self.dataset
212 .bands
213 .push(VrtBand::new(new_band_num, data_type));
214 }
215
216 if let Some(band) = self.dataset.get_band_mut(band_idx) {
217 band.add_source(source);
218 }
219
220 Ok(())
221 }
222
223 fn calculate_extent(&mut self) -> Result<()> {
225 if self.dataset.bands.is_empty() {
226 return Err(VrtError::invalid_structure(
227 "No bands to calculate extent from",
228 ));
229 }
230
231 let mut max_x = 0u64;
232 let mut max_y = 0u64;
233
234 for band in &self.dataset.bands {
235 for source in &band.sources {
236 if let Some(dst_rect) = source.dst_rect() {
237 max_x = max_x.max(dst_rect.x_off + dst_rect.x_size);
238 max_y = max_y.max(dst_rect.y_off + dst_rect.y_size);
239 } else if let Some(ref props) = source.properties {
240 max_x = max_x.max(props.width);
241 max_y = max_y.max(props.height);
242 }
243 }
244 }
245
246 if max_x == 0 || max_y == 0 {
247 return Err(VrtError::invalid_structure(
248 "Cannot calculate extent from sources",
249 ));
250 }
251
252 self.dataset.raster_x_size = max_x;
253 self.dataset.raster_y_size = max_y;
254
255 Ok(())
256 }
257}
258
259impl Default for VrtBuilder {
260 fn default() -> Self {
261 Self::new()
262 }
263}
264
265pub struct MosaicBuilder {
267 builder: VrtBuilder,
268 tile_width: u64,
269 tile_height: u64,
270 current_x: u64,
271 current_y: u64,
272 max_x: u64,
273 max_y: u64,
274}
275
276impl MosaicBuilder {
277 pub fn new(tile_width: u64, tile_height: u64) -> Self {
279 Self {
280 builder: VrtBuilder::new(),
281 tile_width,
282 tile_height,
283 current_x: 0,
284 current_y: 0,
285 max_x: 0,
286 max_y: 0,
287 }
288 }
289
290 pub fn add_tile<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
295 let src_rect = PixelRect::new(0, 0, self.tile_width, self.tile_height);
296 let dst_rect = PixelRect::new(
297 self.current_x,
298 self.current_y,
299 self.tile_width,
300 self.tile_height,
301 );
302 let window = SourceWindow::new(src_rect, dst_rect);
303
304 let source = VrtSource::simple(path.as_ref(), 1).with_window(window);
305 self.builder.add_source_to_band(1, source)?;
306
307 self.max_x = self.max_x.max(self.current_x + self.tile_width);
309 self.max_y = self.max_y.max(self.current_y + self.tile_height);
310
311 Ok(self)
312 }
313
314 pub fn add_tile_at<P: AsRef<Path>>(mut self, path: P, x: u64, y: u64) -> Result<Self> {
319 let src_rect = PixelRect::new(0, 0, self.tile_width, self.tile_height);
320 let dst_rect = PixelRect::new(x, y, self.tile_width, self.tile_height);
321 let window = SourceWindow::new(src_rect, dst_rect);
322
323 let source = VrtSource::simple(path.as_ref(), 1).with_window(window);
324 self.builder.add_source_to_band(1, source)?;
325
326 self.max_x = self.max_x.max(x + self.tile_width);
328 self.max_y = self.max_y.max(y + self.tile_height);
329
330 Ok(self)
331 }
332
333 pub fn next_column(mut self) -> Self {
335 self.current_x += self.tile_width;
336 self
337 }
338
339 pub fn next_row(mut self) -> Self {
341 self.current_x = 0;
342 self.current_y += self.tile_height;
343 self
344 }
345
346 pub fn at(mut self, x: u64, y: u64) -> Self {
348 self.current_x = x;
349 self.current_y = y;
350 self
351 }
352
353 pub fn with_srs<S: Into<String>>(mut self, srs: S) -> Self {
355 self.builder = self.builder.with_srs(srs);
356 self
357 }
358
359 pub fn with_geo_transform(mut self, geo_transform: GeoTransform) -> Self {
361 self.builder = self.builder.with_geo_transform(geo_transform);
362 self
363 }
364
365 pub fn build(mut self) -> Result<VrtDataset> {
370 self.builder = self.builder.set_dimensions(self.max_x, self.max_y);
372 self.builder.build()
373 }
374
375 pub fn build_file<P: AsRef<Path>>(mut self, path: P) -> Result<VrtDataset> {
380 self.builder = self.builder.set_dimensions(self.max_x, self.max_y);
382 self.builder.build_file(path)
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_vrt_builder() {
392 let builder = VrtBuilder::with_size(512, 512);
393 let result = builder.add_source("/test1.tif", 1, 1);
394
395 assert!(result.is_ok());
396 let builder = result.expect("Should add source");
397 let dataset = builder.build();
398 assert!(dataset.is_ok());
399 }
400
401 #[test]
402 fn test_mosaic_builder() {
403 let builder = MosaicBuilder::new(256, 256);
404 let result = builder.add_tile("/tile1.tif");
405
406 assert!(result.is_ok());
407 let builder = result.expect("Should add tile");
408 let result = builder.next_column().add_tile("/tile2.tif");
409 assert!(result.is_ok());
410
411 let builder = result.expect("Should add tile");
412 let dataset = builder.build();
413 assert!(dataset.is_ok());
414 let ds = dataset.expect("Should build");
415 assert_eq!(ds.band_count(), 1);
416 }
417
418 #[test]
419 fn test_tile_grid() {
420 let paths = vec!["/tile1.tif", "/tile2.tif", "/tile3.tif", "/tile4.tif"];
421 let builder = VrtBuilder::new();
422 let result = builder.add_tile_grid(&paths, 256, 256, 2);
423
424 assert!(result.is_ok());
425 let builder = result.expect("Should add tiles");
426 let result = builder.set_dimensions(512, 512).build();
427 assert!(result.is_ok());
428 }
429
430 #[test]
431 fn test_geo_transform() {
432 let gt = GeoTransform {
433 origin_x: 0.0,
434 pixel_width: 1.0,
435 row_rotation: 0.0,
436 origin_y: 0.0,
437 col_rotation: 0.0,
438 pixel_height: -1.0,
439 };
440
441 let builder = VrtBuilder::with_size(512, 512);
442 let result = builder
443 .with_geo_transform(gt)
444 .with_srs("EPSG:4326")
445 .add_source("/test.tif", 1, 1);
446
447 assert!(result.is_ok());
448 let builder = result.expect("Should configure");
449 let dataset = builder.build();
450 assert!(dataset.is_ok());
451 let ds = dataset.expect("Should build");
452 assert!(ds.geo_transform.is_some());
453 assert!(ds.srs.is_some());
454 }
455}