use geonative_core::raster::{RasterTile, ResamplingMethod};
use crate::raster::resample;
#[derive(Debug, Clone)]
pub struct PyramidOptions {
pub method: ResamplingMethod,
pub min_dimension: u32,
pub max_levels: u8,
}
impl Default for PyramidOptions {
fn default() -> Self {
Self {
method: ResamplingMethod::Average,
min_dimension: 256,
max_levels: 16,
}
}
}
pub fn build_overviews(base: &RasterTile, opts: PyramidOptions) -> Vec<RasterTile> {
let mut out = Vec::new();
let mut current = base.clone();
for _ in 0..opts.max_levels {
let next_w = (current.width / 2).max(1);
let next_h = (current.height / 2).max(1);
if next_w <= opts.min_dimension && next_h <= opts.min_dimension {
let overview = resample::resample_tile(¤t, next_w, next_h, opts.method);
out.push(overview);
break;
}
if next_w == current.width && next_h == current.height {
break;
}
let overview = resample::resample_tile(¤t, next_w, next_h, opts.method);
out.push(overview.clone());
current = overview;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use geonative_core::raster::{Band, BandDescriptor, GeoTransform, PixelType};
use geonative_core::Crs;
fn make_tile(width: u32, height: u32) -> RasterTile {
RasterTile {
width,
height,
bands: vec![Band::new(
BandDescriptor::new(Some("v".into()), PixelType::U8),
vec![100u8; (width * height) as usize],
)],
geo_transform: GeoTransform::north_up(0.0, 0.0, 1.0, 1.0),
crs: Crs::Epsg(3857),
}
}
#[test]
fn builds_overviews_until_min_dimension() {
let base = make_tile(1024, 1024);
let overviews = build_overviews(
&base,
PyramidOptions {
min_dimension: 256,
..PyramidOptions::default()
},
);
assert_eq!(overviews.len(), 2);
assert_eq!(overviews[0].width, 512);
assert_eq!(overviews[1].width, 256);
}
#[test]
fn respects_max_levels_cap() {
let base = make_tile(8192, 8192);
let overviews = build_overviews(
&base,
PyramidOptions {
min_dimension: 64,
max_levels: 3,
..PyramidOptions::default()
},
);
assert_eq!(overviews.len(), 3);
assert_eq!(overviews[2].width, 1024);
}
#[test]
fn stops_at_min_dimension_input() {
let base = make_tile(128, 128);
let overviews = build_overviews(
&base,
PyramidOptions {
min_dimension: 256,
..PyramidOptions::default()
},
);
assert_eq!(overviews.len(), 1);
}
#[test]
fn pixel_size_scales_correctly_through_pyramid() {
let base = make_tile(512, 512);
let overviews = build_overviews(&base, PyramidOptions::default());
assert_eq!(overviews[0].geo_transform.pixel_size, [2.0, -2.0]);
}
}