geonative_processing/raster/
pyramid.rs1use geonative_core::raster::{RasterTile, ResamplingMethod};
17
18use crate::raster::resample;
19
20#[derive(Debug, Clone)]
21pub struct PyramidOptions {
22 pub method: ResamplingMethod,
26 pub min_dimension: u32,
28 pub max_levels: u8,
31}
32
33impl Default for PyramidOptions {
34 fn default() -> Self {
35 Self {
36 method: ResamplingMethod::Average,
37 min_dimension: 256,
38 max_levels: 16,
39 }
40 }
41}
42
43pub fn build_overviews(base: &RasterTile, opts: PyramidOptions) -> Vec<RasterTile> {
47 let mut out = Vec::new();
48 let mut current = base.clone();
49 for _ in 0..opts.max_levels {
50 let next_w = (current.width / 2).max(1);
51 let next_h = (current.height / 2).max(1);
52 if next_w <= opts.min_dimension && next_h <= opts.min_dimension {
53 let overview = resample::resample_tile(¤t, next_w, next_h, opts.method);
55 out.push(overview);
56 break;
57 }
58 if next_w == current.width && next_h == current.height {
59 break;
61 }
62 let overview = resample::resample_tile(¤t, next_w, next_h, opts.method);
63 out.push(overview.clone());
64 current = overview;
65 }
66 out
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use geonative_core::raster::{Band, BandDescriptor, GeoTransform, PixelType};
73 use geonative_core::Crs;
74
75 fn make_tile(width: u32, height: u32) -> RasterTile {
76 RasterTile {
77 width,
78 height,
79 bands: vec![Band::new(
80 BandDescriptor::new(Some("v".into()), PixelType::U8),
81 vec![100u8; (width * height) as usize],
82 )],
83 geo_transform: GeoTransform::north_up(0.0, 0.0, 1.0, 1.0),
84 crs: Crs::Epsg(3857),
85 }
86 }
87
88 #[test]
89 fn builds_overviews_until_min_dimension() {
90 let base = make_tile(1024, 1024);
91 let overviews = build_overviews(
92 &base,
93 PyramidOptions {
94 min_dimension: 256,
95 ..PyramidOptions::default()
96 },
97 );
98 assert_eq!(overviews.len(), 2);
101 assert_eq!(overviews[0].width, 512);
102 assert_eq!(overviews[1].width, 256);
103 }
104
105 #[test]
106 fn respects_max_levels_cap() {
107 let base = make_tile(8192, 8192);
108 let overviews = build_overviews(
109 &base,
110 PyramidOptions {
111 min_dimension: 64,
112 max_levels: 3,
113 ..PyramidOptions::default()
114 },
115 );
116 assert_eq!(overviews.len(), 3);
117 assert_eq!(overviews[2].width, 1024);
119 }
120
121 #[test]
122 fn stops_at_min_dimension_input() {
123 let base = make_tile(128, 128);
124 let overviews = build_overviews(
125 &base,
126 PyramidOptions {
127 min_dimension: 256,
128 ..PyramidOptions::default()
129 },
130 );
131 assert_eq!(overviews.len(), 1);
133 }
134
135 #[test]
136 fn pixel_size_scales_correctly_through_pyramid() {
137 let base = make_tile(512, 512);
138 let overviews = build_overviews(&base, PyramidOptions::default());
139 assert_eq!(overviews[0].geo_transform.pixel_size, [2.0, -2.0]);
142 }
143}