eorst/
tests.rs

1#[cfg(test)]
2mod test {
3    use crate::array_ops::{trimm_array4_owned, fill_nodata_simple};
4    use crate::types::{Dimension, RasterDataShape, BlockSize, ImageSize, SamplingMethod};
5 
6    use crate::data_sources::{DataSource, DataSourceBuilder};
7    use crate::metadata::Layer;
8    use crate::selection::Stack;
9    use crate::rasterdataset::builder::get_overlap;
10    use crate::*;
11    use std::path::PathBuf;
12    use std::collections::BTreeMap;
13    use ndarray::Array4;
14
15    macro_rules! td {
16        ($rel:expr_2021) => {
17            std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
18                .join("data")
19                .join($rel)
20        };
21    }
22
23    use num_traits::FromPrimitive;
24
25    use std::ops::{Add, Div};
26    #[test]
27    fn test_fill_nodata_simple() {
28        // Create a 2-band 4x4 array with some nodata values
29        let nodata = -999.0;
30        let bands = 3;
31        let size = 256;
32
33        // Initialize array with sequential values
34        let mut data = Array3::<f32>::zeros((bands, size, size));
35        for b in 0..bands {
36            for r in 0..size {
37                for c in 0..size {
38                    data[[b, r, c]] = (r * size + c) as f32 + b as f32 * 10000.0;
39                }
40            }
41        }
42
43        // Insert holes in the same place across all bands
44        // 1px hole
45        data[[0, 50, 50]] = nodata;
46        data[[1, 50, 50]] = nodata;
47        data[[2, 50, 50]] = nodata;
48
49        // 10px hole (10x1 rectangle)
50        for i in 0..10 {
51            data[[0, 100 + i, 100]] = nodata;
52            data[[1, 100 + i, 100]] = nodata;
53            data[[2, 100 + i, 100]] = nodata;
54        }
55
56        // 20px hole (20x1 rectangle)
57        for i in 0..20 {
58            data[[0, 150 + i, 150]] = nodata;
59            data[[1, 150 + i, 150]] = nodata;
60            data[[2, 150 + i, 150]] = nodata;
61        }
62
63        // Run the fill function
64        fill_nodata_simple(&mut data, nodata);
65
66        println!("data {data:?}");
67
68        // Call the fill function
69        fill_nodata_simple(&mut data, nodata);
70        println!("data {data:?}");
71
72        // Assert that no nodata values remain
73        for val in data.iter() {
74            assert_ne!(*val, nodata, "Found remaining nodata value!");
75        }
76
77        // Optionally: check some expected filled values
78        // (just as an example for first band, position [0, 2])
79        let filled_val = data[[0, 0, 2]];
80        assert!(
81            filled_val > 0.0 && filled_val < 10.0,
82            "Filled value seems wrong"
83        );
84    }
85
86    #[test]
87    fn test_align() {
88        let data_source = DataSourceBuilder::from_file(&PathBuf::from(
89            "data/cemsre_t55jfm_20200614_sub_abam5.tif",
90        ))
91        .build();
92        let rds_1 = RasterDatasetBuilder::<i16>::from_source(&data_source).build();
93
94        let data_source = DataSourceBuilder::from_file(&PathBuf::from(
95            "data/cemsre_t55jfm_20200614_suba_abam5.tif",
96        ))
97        .build();
98        let rds_2 = RasterDatasetBuilder::<i16>::from_source(&data_source)
99            .set_template(&rds_1)
100            .build();
101        assert_eq!(rds_1.metadata.shape, rds_2.metadata.shape);
102        assert_eq!(rds_1.metadata.geo_transform, rds_2.metadata.geo_transform);
103
104        let data_source = DataSourceBuilder::from_file(&PathBuf::from(
105            "data/cemsre_t55jfm_20200614_suba_abam6.tif",
106        ))
107        .build();
108        let rds_2 = RasterDatasetBuilder::<i16>::from_source(&data_source)
109            .set_template(&rds_1)
110            .build();
111        println!(
112            "rds_1 {:?} rds_2{:?}",
113            rds_1.metadata.geo_transform, rds_2.metadata.geo_transform
114        );
115        assert_eq!(rds_1.metadata.shape, rds_2.metadata.shape);
116        assert_eq!(rds_1.metadata.geo_transform, rds_2.metadata.geo_transform);
117    }
118
119    #[test]
120    fn test_iter() {
121        let data_source = DataSourceBuilder::from_file(&PathBuf::from(td!(
122            "cemsre_t55jfm_20200614_sub_abam5.tif"
123        )))
124        .build();
125        let rds = RasterDatasetBuilder::<i16>::from_source(&data_source).build();
126        for iter in rds.iter() {
127            let _ = iter.parent.read_block::<i32>(iter.iter_index);
128        }
129    }
130    #[test]
131    fn test_stacked_dims() {
132        let mut shp_1 = RasterDataShape {
133            times: 1,
134            layers: 1,
135            rows: 1024,
136            cols: 1024,
137        };
138        let shp_2 = RasterDataShape {
139            times: 1,
140            layers: 1,
141            rows: 1024,
142            cols: 1024,
143        };
144        let e = RasterDataShape {
145            times: 1,
146            layers: 2,
147            rows: 1024,
148            cols: 1024,
149        };
150        shp_1.stack(shp_2, Dimension::Layer);
151        assert_eq!(shp_1, e);
152        let mut shp_1 = RasterDataShape {
153            times: 1,
154            layers: 1,
155            rows: 1024,
156            cols: 1024,
157        };
158        let shp_2 = RasterDataShape {
159            times: 1,
160            layers: 2,
161            rows: 1024,
162            cols: 1024,
163        };
164        let e = RasterDataShape {
165            times: 1,
166            layers: 3,
167            rows: 1024,
168            cols: 1024,
169        };
170        shp_1.stack(shp_2, Dimension::Layer);
171        assert_eq!(shp_1, e);
172        let mut shp_1 = RasterDataShape {
173            times: 1,
174            layers: 1,
175            rows: 1024,
176            cols: 1024,
177        };
178        let shp_2 = RasterDataShape {
179            times: 1,
180            layers: 1,
181            rows: 1024,
182            cols: 1024,
183        };
184        let e = RasterDataShape {
185            times: 2,
186            layers: 1,
187            rows: 1024,
188            cols: 1024,
189        };
190        shp_1.stack(shp_2, Dimension::Time);
191        assert_eq!(shp_1, e);
192    }
193
194    #[test]
195    fn test_shape_extend() {
196        let mut shp_1 = RasterDataShape {
197            times: 1,
198            layers: 1,
199            rows: 1024,
200            cols: 1024,
201        };
202        let shp_2 = RasterDataShape {
203            times: 1,
204            layers: 1,
205            rows: 1024,
206            cols: 1024,
207        };
208        let e = RasterDataShape {
209            times: 2,
210            layers: 1,
211            rows: 1024,
212            cols: 1024,
213        };
214        shp_1.extend(shp_2);
215        assert_eq!(shp_1, e);
216
217        let mut shp_1 = RasterDataShape {
218            times: 1,
219            layers: 2,
220            rows: 1024,
221            cols: 1024,
222        };
223        let shp_2 = RasterDataShape {
224            times: 1,
225            layers: 2,
226            rows: 1024,
227            cols: 1024,
228        };
229        let e = RasterDataShape {
230            times: 2,
231            layers: 2,
232            rows: 1024,
233            cols: 1024,
234        };
235        shp_1.extend(shp_2);
236        assert_eq!(shp_1, e);
237    }
238    #[test]
239    #[should_panic(expected = "Unable to extend layers")]
240    fn test_shape_extend_diff_layer() {
241        let mut shp_1 = RasterDataShape {
242            times: 1,
243            layers: 1,
244            rows: 1024,
245            cols: 1024,
246        };
247        let shp_2 = RasterDataShape {
248            times: 1,
249            layers: 2,
250            rows: 1024,
251            cols: 1024,
252        };
253        let _e = RasterDataShape {
254            times: 2,
255            layers: 2,
256            rows: 1024,
257            cols: 1024,
258        };
259        shp_1.extend(shp_2);
260    }
261
262    #[test]
263    #[should_panic(expected = "Unable to stack layers")]
264    fn test_shape_stack_diff_time() {
265        let mut shp_1 = RasterDataShape {
266            times: 1,
267            layers: 1,
268            rows: 1024,
269            cols: 1024,
270        };
271        let shp_2 = RasterDataShape {
272            times: 2,
273            layers: 1,
274            rows: 1024,
275            cols: 1024,
276        };
277        shp_1.stack(shp_2, Dimension::Layer);
278    }
279
280    #[test]
281    #[should_panic(expected = "Unable to stack layers")]
282    fn test_shape_stack_diff_time2() {
283        let mut shp_1 = RasterDataShape {
284            times: 1,
285            layers: 1,
286            rows: 1024,
287            cols: 1024,
288        };
289        let shp_2 = RasterDataShape {
290            times: 2,
291            layers: 2,
292            rows: 1024,
293            cols: 1024,
294        };
295        shp_1.stack(shp_2, Dimension::Layer);
296    }
297
298    #[test]
299    fn test_layer_stack_position() {
300        let layer_1: Layer = Layer {
301            source: PathBuf::from("image_1.img"),
302            layer_pos: 0,
303            time_pos: 0,
304        };
305        let mut layer_2: Layer = Layer {
306            source: PathBuf::from("image_1.img"),
307            layer_pos: 0,
308            time_pos: 0,
309        };
310        let max_time_rds_1 = 3;
311        layer_2.stack_position(layer_1, Dimension::Layer, max_time_rds_1);
312        assert_eq!(layer_2.layer_pos, 3);
313    }
314
315    #[test]
316    fn test_reduce() {
317        // TODO: Audit that the environment access only happens in single-threaded code.
318        unsafe { std::env::set_var("RAYON_NUM_THREADS", "1") };
319
320        fn worker<T>(rdb: &RasterDataBlock<T>, dimension: Dimension) -> Array3<T>
321        where
322            T: RasterType + FromPrimitive + Add<Output = T> + Div<Output = T>,
323        {
324            use crate::selection::SumDimension;
325            rdb.data.sum_dimension(dimension)
326        }
327        // Create a ds with 2 time steps
328        let source = DataSource {
329            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
330            bands: [1, 2].to_vec(),
331            layer_names: Vec::new(),
332        };
333        let source_2 = DataSource {
334            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
335            bands: [1, 2].to_vec(),
336            layer_names: Vec::new(),
337        };
338
339        let sources = vec![source, source_2];
340
341        let rds = RasterDatasetBuilder::<i16>::from_sources(&sources)
342            .block_size(BlockSize {
343                rows: 1024,
344                cols: 1024,
345            })
346            .source_composition_dimension(Dimension::Time)
347            .band_composition_dimension(Dimension::Layer)
348            .build();
349
350        let out_file = PathBuf::from("/tmp/tmp.tif");
351        let dimension = Dimension::Layer;
352        rds.apply_reduction::<i16>(worker, dimension, 1, &out_file, 32767);
353    }
354
355    #[test]
356    fn test_overlap() {
357        let source = DataSource {
358            source: PathBuf::from("data/cemsre_t55jfm_20200614_sub_abam5.tif"),
359            bands: [1].to_vec(),
360            layer_names: Vec::new(),
361        };
362        let rds_over_0: RasterDataset<i16> = RasterDatasetBuilder::<i16>::from_source(&source)
363            .overlap_size(0)
364            .build();
365
366        let source = DataSource {
367            source: PathBuf::from("data/cemsre_t55jfm_20200614_sub_abam5.tif"),
368            bands: [1].to_vec(),
369            layer_names: Vec::new(),
370        };
371        let rds_over_1: RasterDataset<i16> = RasterDatasetBuilder::<i16>::from_source(&source)
372            .overlap_size(1)
373            .build();
374        println!("rds_over: {rds_over_1}");
375
376        // The idea is that reading with no overlap should give the same result
377        // as reading with overlap + trimming the excess
378        let n_blocks = 4;
379        for block_id in 0..n_blocks {
380            //println!("block_id {block_id:?}");
381            let array_with_overlap = rds_over_1.read_block::<i32>(block_id);
382            //println!("read 1 {array_with_overlap:?}");
383            let overlap = rds_over_1.blocks[block_id].overlap;
384            //println!("array_with overlap {array_with_overlap:?}");
385
386            let array_with_overlap_trimmed = trimm_array4_owned(&array_with_overlap, &overlap);
387            //println!("array_trimmed {array_with_overlap_trimmed:?}");
388
389            // should be the same than with no overlaps!
390            let array_with_no_overlap = rds_over_0.read_block::<i32>(block_id);
391            //println!("read 2 {array_with_no_overlap:?}");
392            assert_eq!(array_with_no_overlap, array_with_overlap_trimmed);
393        }
394    }
395    #[test]
396    fn test_get_overlap() {
397        let image_size = ImageSize {
398            rows: 1580,
399            cols: 1514,
400        };
401        let block_size = BlockSize {
402            rows: 1024,
403            cols: 1024,
404        };
405        let overlap_size = 1;
406        let block_col_rows = vec![(0, 0), (1, 0), (0, 1), (1, 1)];
407        for block_col_row in block_col_rows {
408            get_overlap(image_size, block_size, block_col_row, overlap_size);
409        }
410    }
411
412    #[test]
413    fn test_extract_blockwise() {
414        let mut e: BTreeMap<i16, Vec<i16>> = BTreeMap::new();
415        e.insert(1, vec![1910]);
416        e.insert(2, vec![957]);
417        e.insert(3, vec![1680]);
418        e.insert(4, vec![1847]);
419        e.insert(5, vec![1746]);
420        e.insert(6, vec![624]);
421        e.insert(7, vec![818]);
422        e.insert(8, vec![1103]);
423
424        let raster_path = td!("cemsre_t55jfm_20200614_sub_abam5.tif");
425        let vector_path = td!("cemsre_t55jfm_20200614_sub_abam5.gpkg");
426        let bls: Vec<usize> = vec![16, 32, 64, 128, 256, 512];
427        bls.iter().for_each(|b| {
428            let data_source = DataSourceBuilder::from_file(&raster_path)
429                .bands(vec![1])
430                .build();
431            let rds = RasterDatasetBuilder::<i16>::from_source(&data_source)
432                .block_size(BlockSize { rows: *b, cols: *b })
433                .build();
434            let data = rds.extract_blockwise(&vector_path, "id", SamplingMethod::Value, None);
435            assert_eq!(e, data);
436        });
437    }
438
439    #[test]
440    #[cfg(feature = "use_polars")]
441    fn extract_blockwise_time() {
442        let source = DataSource {
443            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
444            bands: [1, 3].to_vec(),
445            layer_names: Vec::new(),
446        };
447
448        let vector_path = td!("cemsre_t55jfm_20200614_sub_abam5.gpkg");
449        let overlap_size = 0;
450        // two band,
451        let rds: RasterDataset<i16> = RasterDatasetBuilder::from_source(&source)
452            .overlap_size(overlap_size)
453            .build();
454        let _extracted_2b =
455            rds.extract_blockwise(&vector_path, "id", SamplingMethod::Value, None);
456        println!("Extracted: {_extracted_2b:?}");
457    }
458
459    #[test]
460    fn test_extract_blockwise_padding() {
461        let source = DataSource {
462            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
463            bands: [1, 3].to_vec(),
464            layer_names: Vec::new(),
465        };
466
467        let vector_path = td!("cemsre_t55jfm_20200614_sub_abam5.gpkg");
468        let overlap_size = 0;
469        // two band,
470        let rds: RasterDataset<i16> = RasterDatasetBuilder::<i16>::from_source(&source)
471            .overlap_size(overlap_size)
472            .build();
473        let _extracted_2b = rds.extract_blockwise(&vector_path, "id", SamplingMethod::Value, None);
474        println!("Extracted: {_extracted_2b:?}");
475
476        // Using value
477        let source = DataSource {
478            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
479            bands: [1, 3].to_vec(),
480            layer_names: Vec::new(),
481        };
482        let rds: RasterDataset<i16> = RasterDatasetBuilder::<i16>::from_source(&source)
483            .overlap_size(overlap_size)
484            .build();
485        let extracted_1 = rds.extract_blockwise(&vector_path, "id", SamplingMethod::Value, None);
486
487        let overlap_size = 1;
488        let source = DataSource {
489            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
490            bands: [1, 3].to_vec(),
491            layer_names: Vec::new(),
492        };
493        let rds: RasterDataset<i16> = RasterDatasetBuilder::<i16>::from_source(&source)
494            .overlap_size(overlap_size)
495            .build();
496        let extracted_2 = rds.extract_blockwise(&vector_path, "id", SamplingMethod::Value, None);
497        assert_eq!(extracted_1, extracted_2);
498
499        // Using mode
500        let overlap_size = 0;
501        let source = DataSource {
502            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
503            bands: [1, 3].to_vec(),
504            layer_names: Vec::new(),
505        };
506        let rds: RasterDataset<i16> = RasterDatasetBuilder::<i16>::from_source(&source)
507            .overlap_size(overlap_size)
508            .build();
509        let extracted_mode = rds.extract_blockwise(&vector_path, "id", SamplingMethod::Value, None);
510        assert_eq!(extracted_1, extracted_mode);
511    }
512
513    #[test]
514    fn test_stack_datasets() {
515        let src_fn = td!("cemsre_t55jfm_20200614_sub_abam5.tif");
516        let source_1 = DataSourceBuilder::from_file(&src_fn).build();
517        let source_2 = source_1.clone();
518        let mut rds_1 = RasterDatasetBuilder::<i16>::from_source(&source_1)
519            .band_composition_dimension(Dimension::Layer)
520            .build();
521        let rds_2 = RasterDatasetBuilder::<i16>::from_source(&source_2)
522            .band_composition_dimension(Dimension::Layer)
523            .build();
524        rds_1.stack(&rds_2, Dimension::Layer);
525
526        let block_id = 0;
527        let data = rds_1.read_block::<i32>(block_id);
528        assert_eq!(data.shape(), [1, 12, 1024, 1024]);
529    }
530
531    #[test]
532    fn test_extend_datasets() {
533        let source = DataSource {
534            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
535            bands: [1, 2, 3, 4, 5, 6].to_vec(),
536            layer_names: Vec::new(),
537        };
538        let source_2 = source.clone();
539        let mut rds_1 = RasterDatasetBuilder::<i16>::from_source(&source)
540            .band_composition_dimension(Dimension::Layer)
541            .build();
542        let rds_2 = RasterDatasetBuilder::<i16>::from_source(&source_2)
543            .band_composition_dimension(Dimension::Layer)
544            .build();
545        rds_1.extend(&rds_2);
546        let block_id = 0;
547        let data = rds_1.read_block::<i32>(block_id);
548        assert_eq!(data.shape(), [2, 6, 1024, 1024]);
549    }
550
551    #[test]
552    fn test_epsg() {
553        fn worker(rdb: &RasterDataBlock<i16>) -> anyhow::Result<Array4<i16>> {
554            Ok(rdb.data.to_owned())
555        }
556
557        let source = DataSource {
558            source: td!("cemsre_t55jfm_20200614_suba_abam5.tif"),
559            bands: [1, 2].to_vec(),
560            layer_names: Vec::new(),
561        };
562
563        let rds = RasterDatasetBuilder::<i16>::from_source(&source)
564            .set_epsg(3577)
565            .build();
566        assert_eq!(
567            rds.metadata.shape,
568            RasterDataShape {
569                times: 1,
570                layers: 2,
571                rows: 1419,
572                cols: 1613
573            }
574        );
575
576        rds.apply(worker, 1, &PathBuf::from("test.tif")).ok();
577
578        let rds = RasterDatasetBuilder::<i16>::from_source(&source)
579            .band_composition_dimension(Dimension::Time)
580            .set_epsg(4326)
581            .build();
582        assert_eq!(
583            rds.metadata.shape,
584            RasterDataShape {
585                times: 2,
586                layers: 1,
587                rows: 1179,
588                cols: 1559
589            }
590        );
591    }
592
593    #[test]
594    fn test_layers_from_source() {
595        let source = DataSource {
596            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
597            bands: [1, 2, 3, 4, 5, 6].to_vec(),
598            layer_names: Vec::new(),
599        };
600
601        let rds = RasterDatasetBuilder::<i16>::from_source(&source)
602            .band_composition_dimension(Dimension::Layer)
603            .build();
604        assert_eq!(
605            rds.metadata.shape,
606            RasterDataShape {
607                times: 1,
608                layers: 6,
609                rows: 1580,
610                cols: 1514
611            }
612        );
613
614        let source = DataSource {
615            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
616            bands: [1, 2, 3, 4, 5, 6].to_vec(),
617            layer_names: Vec::new(),
618        };
619
620        let rds = RasterDatasetBuilder::<i16>::from_source(&source)
621            .band_composition_dimension(Dimension::Time)
622            .build();
623        assert_eq!(
624            rds.metadata.shape,
625            RasterDataShape {
626                times: 6,
627                layers: 1,
628                rows: 1580,
629                cols: 1514
630            }
631        );
632    }
633
634    #[test]
635    fn test_zip() {
636        let data_source = DataSourceBuilder::from_file(&PathBuf::from(td!(
637            "cemsre_t55jfm_20200614_sub_abam5.tif"
638        )))
639        .build();
640
641        let rds_1 = RasterDatasetBuilder::<i16>::from_source(&data_source).build();
642        let rds_2 = RasterDatasetBuilder::<i16>::from_source(&data_source).build();
643
644        for (b_1, b_2) in rds_1.iter().zip(rds_2.iter()) {
645            let id = b_1.iter_index;
646            let b1_data = b_1.parent.read_block::<i16>(id);
647            let b2_data = b_2.parent.read_block::<i16>(id);
648            let mask = b2_data.mapv(|v| (v > 1000) as i16);
649            let _result = b1_data * mask;
650        }
651    }
652
653    #[test]
654    fn test_raster_data_source_builder() {
655        let data_source = DataSourceBuilder::from_file(&PathBuf::from(
656            "data/cemsre_t55jfm_20200614_sub_abam5.tif",
657        ))
658        .build();
659
660        let _ = RasterDatasetBuilder::<i16>::from_source(&data_source).build();
661    }
662
663    #[test]
664    fn test_data_source_builder() {
665        let _data_source = DataSourceBuilder::from_file(&PathBuf::from(
666            "data/cemsre_t55jfm_20200614_sub_abam5.tif",
667        ))
668        .build();
669        let _data_source = DataSourceBuilder::from_file(&PathBuf::from(
670            "data/cemsre_t55jfm_20200614_sub_abam5.tif",
671        ))
672        .bands(vec![1, 3])
673        .build();
674        let _e = [
675            "cemsre_t55jfm_20200614_sub_abam5_1",
676            "cemsre_t55jfm_20200614_sub_abam5_3",
677        ];
678
679        let _data_source = DataSourceBuilder::from_file(&PathBuf::from(
680            "data/cemsre_t55jfm_20200614_sub_abam5.tif",
681        ))
682        .bands(vec![1, 4])
683        .build();
684    }
685
686    #[test]
687    fn test_compositions() {
688        let data_source = DataSource {
689            source: PathBuf::from("data/cemsre_t55jfm_20200614_sub_abam5.tif"),
690            bands: [1, 2, 3].to_vec(),
691            layer_names: Vec::new(),
692        };
693        let data_source_2 = DataSource {
694            source: PathBuf::from("data/cemsre_t55jfm_20200614_sub_abam5.tif"),
695            bands: [1, 2, 3].to_vec(),
696            layer_names: Vec::new(),
697        };
698        let rds =
699            RasterDatasetBuilder::<i16>::from_sources(&vec![data_source_2, data_source]).build();
700        assert_eq!(
701            rds.metadata.shape,
702            RasterDataShape {
703                times: 2,
704                layers: 3,
705                rows: 1580,
706                cols: 1514
707            }
708        );
709
710        let data_source = DataSource {
711            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
712            bands: [1, 2, 3].to_vec(),
713            layer_names: Vec::new(),
714        };
715        let data_source_2 = DataSource {
716            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
717            bands: [1, 2, 3].to_vec(),
718            layer_names: Vec::new(),
719        };
720        let rds = RasterDatasetBuilder::<i16>::from_sources(&vec![data_source_2, data_source])
721            .band_composition_dimension(Dimension::Layer)
722            .source_composition_dimension(Dimension::Layer)
723            .build();
724        assert_eq!(
725            rds.metadata.shape,
726            RasterDataShape {
727                times: 1,
728                layers: 6,
729                rows: 1580,
730                cols: 1514
731            }
732        );
733
734        let data_source = DataSource {
735            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
736            bands: [1, 2, 3].to_vec(),
737            layer_names: Vec::new(),
738        };
739        let data_source_2 = DataSource {
740            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
741            bands: [1, 2, 3].to_vec(),
742            layer_names: Vec::new(),
743        };
744        let rds = RasterDatasetBuilder::<i16>::from_sources(&vec![data_source_2, data_source])
745            .band_composition_dimension(Dimension::Time)
746            .source_composition_dimension(Dimension::Time)
747            .build();
748        assert_eq!(
749            rds.metadata.shape,
750            RasterDataShape {
751                times: 6,
752                layers: 1,
753                rows: 1580,
754                cols: 1514
755            }
756        );
757
758        let data_source = DataSource {
759            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
760            bands: [1, 2, 3].to_vec(),
761            layer_names: Vec::new(),
762        };
763        let data_source_2 = DataSource {
764            source: td!("cemsre_t55jfm_20200614_sub_abam5.tif"),
765            bands: [1, 2, 3].to_vec(),
766            layer_names: Vec::new(),
767        };
768        let rds = RasterDatasetBuilder::<i16>::from_sources(&vec![data_source_2, data_source])
769            .band_composition_dimension(Dimension::Time)
770            .source_composition_dimension(Dimension::Layer)
771            .build();
772        assert_eq!(
773            rds.metadata.shape,
774            RasterDataShape {
775                times: 3,
776                layers: 2,
777                rows: 1580,
778                cols: 1514
779            }
780        );
781    }
782
783    // ─── Parallel writer tests ───
784
785    #[test]
786    fn test_trimm_array3_asymmetric_trimming() {
787        use ndarray::Array3;
788        use crate::types::Overlap;
789
790        // Create a 3-band 10x10 array with distinguishable values
791        let mut data: Array3<u16> = Array3::zeros((3, 10, 10));
792        for b in 0..3 {
793            for r in 0..10 {
794                for c in 0..10 {
795                    data[[b, r, c]] = (r * 10 + c) as u16 + b as u16 * 100;
796                }
797            }
798        }
799
800        // Test 1: zero overlap (no trimming)
801        let overlap = Overlap { left: 0, top: 0, right: 0, bottom: 0 };
802        let trimmed = crate::array_ops::trimm_array3_asymmetric(&data, &overlap);
803        assert_eq!(trimmed.shape(), &[3, 10, 10]);
804        assert_eq!(trimmed[[0, 0, 0]], 0);
805
806        // Test 2: symmetric overlap
807        let overlap = Overlap { left: 2, top: 2, right: 2, bottom: 2 };
808        let trimmed = crate::array_ops::trimm_array3_asymmetric(&data, &overlap);
809        assert_eq!(trimmed.shape(), &[3, 6, 6]);
810        assert_eq!(trimmed[[0, 0, 0]], data[[0, 2, 2]]);
811
812        // Test 3: asymmetric overlap
813        let overlap = Overlap { left: 1, top: 2, right: 3, bottom: 4 };
814        let trimmed = crate::array_ops::trimm_array3_asymmetric(&data, &overlap);
815        assert_eq!(trimmed.shape(), &[3, 4, 6]);
816        assert_eq!(trimmed[[0, 0, 0]], data[[0, 2, 1]]);
817    }
818
819    #[test]
820    fn test_create_output_geotiff_small() {
821        use crate::parallel_writer::create_output_geotiff;
822        use crate::types::GeoTransform;
823 
824
825        let dir = std::env::temp_dir();
826        let path = dir.join(format!("test_output_{}.tif", std::process::id()));
827        let _ = std::fs::remove_file(&path);
828
829        let geo_transform = GeoTransform {
830            x_ul: 500000.0,
831            x_res: 30.0,
832            x_rot: 0.0,
833            y_ul: 7000000.0,
834            y_rot: 0.0,
835            y_res: -30.0,
836        };
837
838        let result = create_output_geotiff::<u16>(
839            &path,
840            &geo_transform,
841            32755,
842            64,
843            64,
844            3,
845            0u16,
846        );
847        assert!(result.is_ok(), "Failed to create GeoTIFF: {:?}", result.err());
848        assert!(path.exists(), "Output file was not created");
849
850        // Verify with GDAL
851        let ds = gdal::Dataset::open(&path).expect("Failed to open created GeoTIFF");
852        assert_eq!(ds.raster_count(), 3);
853        let size = ds.raster_size();
854        assert_eq!(size.0, 64);  // cols
855        assert_eq!(size.1, 64);  // rows
856
857        let _ = std::fs::remove_file(&path);
858    }
859
860    #[test]
861    fn test_write_block_roundtrip() {
862        use crate::parallel_writer::{self, ParallelGeoTiffWriter};
863        use crate::types::{GeoTransform, Offset, ReadWindow, Size};
864        use ndarray::Array3;
865
866        let dir = std::env::temp_dir();
867        let path = dir.join(format!("test_write_{}.tif", std::process::id()));
868        let _ = std::fs::remove_file(&path);
869
870        let geo_transform = GeoTransform {
871            x_ul: 500000.0,
872            x_res: 30.0,
873            x_rot: 0.0,
874            y_ul: 7000000.0,
875            y_rot: 0.0,
876            y_res: -30.0,
877        };
878
879        // Create output file
880        parallel_writer::create_output_geotiff::<u16>(
881            &path,
882            &geo_transform,
883            32755,
884            128,    // total cols
885            128,    // total rows
886            2,      // 2 bands
887            0u16,
888        ).expect("Failed to create output");
889
890        let writer = ParallelGeoTiffWriter::new(
891            path.clone(),
892            geo_transform,
893            32755,
894            128,
895            128,
896            2,
897        );
898
899        // Write a block at offset (32, 32) with size (64, 64)
900        let mut block_data: Array3<u16> = Array3::zeros((2, 64, 64));
901        // Fill with distinguishable values
902        for b in 0..2 {
903            for r in 0..64 {
904                for c in 0..64 {
905                    block_data[[b, r, c]] = (r * 64 + c) as u16 + b as u16 * 10000;
906                }
907            }
908        }
909
910        let window = ReadWindow {
911            offset: Offset { cols: 32, rows: 32 },
912            size: Size { cols: 64, rows: 64 },
913        };
914
915        parallel_writer::write_block(
916            &writer,
917            block_data.view(),
918            window,
919        ).expect("Failed to write block");
920
921        // Drop writer to flush/close
922        drop(writer);
923
924        // Verify: read back the data and check it matches
925        let ds = gdal::Dataset::open(&path).expect("Failed to open for verification");
926        assert_eq!(ds.raster_count(), 2);
927
928        for band_idx in 1..=2 {
929            let band = ds.rasterband(band_idx).expect("Failed to get band");
930            let data = band.read_as::<u16>(
931                (32, 32),    // offset
932                (64, 64),    // window size
933                (64, 64),    // output size
934                None,
935            ).expect("Failed to read band").to_array().expect("Failed to convert to array");
936
937            for r in 0..64 {
938                for c in 0..64 {
939                    let expected = (r * 64 + c) as u16 + (band_idx - 1) as u16 * 10000;
940                    assert_eq!(data[[r, c]], expected,
941                        "Mismatch at band={}, row={}, col={}", band_idx, r, c);
942                }
943            }
944        }
945
946        let _ = std::fs::remove_file(&path);
947    }
948
949    // ─── COG Validation Tests ───
950
951    #[test]
952    fn test_build_overviews() {
953        use crate::parallel_writer::{self, ParallelGeoTiffWriter};
954        use crate::types::GeoTransform;
955
956        let dir = std::env::temp_dir();
957        let path = dir.join(format!("test_overviews_{}.tif", std::process::id()));
958        let _ = std::fs::remove_file(&path);
959
960        let geo_transform = GeoTransform {
961            x_ul: 500000.0,
962            x_res: 30.0,
963            x_rot: 0.0,
964            y_ul: 7000000.0,
965            y_rot: 0.0,
966            y_res: -30.0,
967        };
968
969        // Create output file
970        parallel_writer::create_output_geotiff::<u16>(
971            &path,
972            &geo_transform,
973            32755,
974            128,
975            128,
976            1,
977            0u16,
978        ).expect("Failed to create output");
979
980        let writer = ParallelGeoTiffWriter::new(
981            path.clone(),
982            geo_transform,
983            32755,
984            128,
985            128,
986            1,
987        );
988
989        // Build overviews
990        writer.build_overviews("CUBIC", &[2, 4, 8]).expect("Failed to build overviews");
991        drop(writer);
992
993        // Verify overviews exist
994        let ds = gdal::Dataset::open(&path).expect("Failed to open for verification");
995        let band = ds.rasterband(1).expect("Failed to get band");
996        let overview_count = band.overview_count().expect("Failed to get overview count");
997        assert!(
998            overview_count > 0,
999            "Expected overviews to exist, but got count={}",
1000            overview_count
1001        );
1002
1003        let _ = std::fs::remove_file(&path);
1004    }
1005
1006    #[test]
1007    fn test_translate_to_cog() {
1008        use crate::gdal_utils::translate_to_cog;
1009        use crate::parallel_writer::{self, ParallelGeoTiffWriter};
1010        use crate::types::{GeoTransform, Offset, ReadWindow, Size};
1011        use ndarray::Array3;
1012
1013        let dir = std::env::temp_dir();
1014        let intermediate = dir.join(format!("test_cog_inter_{}.tif", std::process::id()));
1015        let cog_output = dir.join(format!("test_cog_{}.tif", std::process::id()));
1016        let _ = std::fs::remove_file(&intermediate);
1017        let _ = std::fs::remove_file(&cog_output);
1018
1019        let geo_transform = GeoTransform {
1020            x_ul: 500000.0,
1021            x_res: 30.0,
1022            x_rot: 0.0,
1023            y_ul: 7000000.0,
1024            y_rot: 0.0,
1025            y_res: -30.0,
1026        };
1027
1028        // Use 1024x1024 to ensure COG driver auto-generates overviews
1029        let img_size = 1024usize;
1030        let block_size = 256usize;
1031
1032        // Create intermediate file and write some data
1033        parallel_writer::create_output_geotiff::<u16>(
1034            &intermediate,
1035            &geo_transform,
1036            32755,
1037            img_size,
1038            img_size,
1039            1,
1040            0u16,
1041        ).expect("Failed to create intermediate");
1042
1043        let writer = ParallelGeoTiffWriter::new(
1044            intermediate.clone(),
1045            geo_transform,
1046            32755,
1047            img_size,
1048            img_size,
1049            1,
1050        );
1051
1052        // Write a block of data to make the file non-empty
1053        let mut block_data: Array3<u16> = Array3::zeros((1, block_size, block_size));
1054        for r in 0..block_size {
1055            for c in 0..block_size {
1056                block_data[[0, r, c]] = (r * block_size + c) as u16;
1057            }
1058        }
1059        let window = ReadWindow {
1060            offset: Offset { cols: block_size as isize, rows: block_size as isize },
1061            size: Size { cols: block_size as isize, rows: block_size as isize },
1062        };
1063        parallel_writer::write_block(&writer, block_data.view(), window)
1064            .expect("Failed to write block");
1065        drop(writer);
1066
1067        // Translate to COG via in-process create_copy (AUTO overviews generated by COG driver)
1068        translate_to_cog(
1069            &intermediate, &cog_output,
1070            "DEFLATE", "NEAREST",
1071        ).expect("Failed to translate to COG");
1072
1073        // Verify COG has overviews (auto-generated by COG driver)
1074        let ds = gdal::Dataset::open(&cog_output).expect("Failed to open COG");
1075        let band = ds.rasterband(1).expect("Failed to get band");
1076        let overview_count = band.overview_count().expect("Failed to get overview count");
1077        assert!(
1078            overview_count > 0,
1079            "COG should have auto-generated overviews, but got count={}",
1080            overview_count
1081        );
1082
1083        // Verify COG has correct size
1084        let size = ds.raster_size();
1085        assert_eq!(size.0, img_size); // cols
1086        assert_eq!(size.1, img_size); // rows
1087
1088        // Verify data integrity — read back the written block
1089        let band = ds.rasterband(1).expect("Failed to get band");
1090        let data = band.read_as::<u16>(
1091            (block_size as isize, block_size as isize),
1092            (block_size, block_size),
1093            (block_size, block_size),
1094            None,
1095        ).expect("Failed to read band").to_array().expect("Failed to convert to array");
1096        assert_eq!(data[[0, 0]], 0u16);
1097        assert_eq!(data[[1, 1]], (block_size + 1) as u16);
1098
1099        // Verify overviews contain valid data (not all zeros)
1100        let ov = band.overview(0).expect("Failed to get overview");
1101        let ov_size_x = ov.x_size();
1102        let ov_size_y = ov.y_size();
1103        // Read from center of overview where data should exist
1104        let read_offset = ((ov_size_x as isize) / 2 - 8, (ov_size_y as isize) / 2 - 8);
1105        let ov_data = ov.read_as::<u16>(
1106            read_offset,
1107            (16, 16),
1108            (16, 16),
1109            None,
1110        ).expect("Failed to read overview").to_array().expect("Failed to convert overview to array");
1111        assert!(
1112            ov_data.iter().any(|v| *v != 0),
1113            "Overview should contain non-zero data"
1114        );
1115
1116        let _ = std::fs::remove_file(&intermediate);
1117        let _ = std::fs::remove_file(&cog_output);
1118    }
1119
1120    #[test]
1121    fn test_output_config_defaults() {
1122        use crate::types::{OutputConfig, OutputFormat};
1123
1124        let config = OutputConfig::default();
1125        assert_eq!(config.format, OutputFormat::GeoTiff);
1126        assert_eq!(config.compression, "LZW");
1127        assert_eq!(config.overview_resampling, "CUBIC");
1128        assert_eq!(config.overview_levels, vec![2, 4, 8, 16, 32]);
1129    }
1130
1131    #[test]
1132    fn test_output_format_variants() {
1133        use crate::types::OutputFormat;
1134
1135        // Test all variants can be created
1136        let _geotiff = OutputFormat::GeoTiff;
1137        let _geotiff_overviews = OutputFormat::GeoTiffOverviews;
1138        let _cog = OutputFormat::COG;
1139
1140        // Test default is GeoTiff
1141        assert_eq!(OutputFormat::default(), OutputFormat::GeoTiff);
1142
1143        // Test equality
1144        assert_eq!(OutputFormat::GeoTiff, OutputFormat::GeoTiff);
1145        assert_ne!(OutputFormat::GeoTiff, OutputFormat::COG);
1146    }
1147}