use crate::transform::Transformer;
#[derive(Debug, Clone)]
pub struct SourceWindow {
pub xoff: i32,
pub yoff: i32,
pub xsize: i32,
pub ysize: i32,
pub fill_ratio: f64,
pub n_failed: i32,
pub n_samples: i32,
}
const DEFAULT_STEP_COUNT: usize = 21;
pub fn compute_source_window(
transformer: &impl Transformer,
dst_off: [i32; 2],
dst_size: [i32; 2],
src_raster_size: [i32; 2],
resample_padding: i32,
) -> Option<SourceWindow> {
let step_count = DEFAULT_STEP_COUNT;
let use_all = {
let mut cx = vec![
dst_off[0] as f64,
(dst_off[0] + dst_size[0]) as f64,
dst_off[0] as f64,
(dst_off[0] + dst_size[0]) as f64,
];
let mut cy = vec![
dst_off[1] as f64,
dst_off[1] as f64,
(dst_off[1] + dst_size[1]) as f64,
(dst_off[1] + dst_size[1]) as f64,
];
let ok = transformer.transform(true, &mut cx, &mut cy);
!ok[0] || !ok[1] || !ok[2] || !ok[3]
};
let (min_x, min_y, max_x, max_y, n_samples, n_failed) = if use_all {
sample_edges_all(transformer, dst_off, dst_size)
} else {
sample_edges_step(transformer, dst_off, dst_size, step_count)
};
let (min_x, min_y, max_x, max_y, n_samples, n_failed) = if n_failed > 0 {
sample_grid(transformer, dst_off, dst_size, step_count)
} else {
(min_x, min_y, max_x, max_y, n_samples, n_failed)
};
if n_failed > n_samples - 5 {
return None;
}
let src_w = src_raster_size[0] as f64;
let src_h = src_raster_size[1] as f64;
if min_x > src_w || max_x < 0.0 || min_y > src_h || max_y < 0.0 {
return Some(SourceWindow {
xoff: 0, yoff: 0, xsize: 0, ysize: 0,
fill_ratio: 0.0, n_failed, n_samples,
});
}
let snap = |v: f64| -> f64 {
let r = v.round();
if (r - v).abs() < 1e-6 { r } else { v }
};
let min_x = snap(min_x);
let min_y = snap(min_y);
let max_x = snap(max_x);
let max_y = snap(max_y);
let dx_scale = (dst_size[0] as f64 / (max_x - min_x)).max(1e-3);
let dy_scale = (dst_size[1] as f64 / (max_y - min_y)).max(1e-3);
let mut x_radius = if dx_scale < 0.95 {
(resample_padding as f64 / dx_scale).ceil() as i32
} else {
resample_padding
};
let mut y_radius = if dy_scale < 0.95 {
(resample_padding as f64 / dy_scale).ceil() as i32
} else {
resample_padding
};
if resample_padding > 0 {
x_radius += 1;
y_radius += 1;
}
if n_failed > 0 {
x_radius += 10;
y_radius += 10;
}
let min_x_clamped = min_x.max(0.0) as i32;
let min_y_clamped = min_y.max(0.0) as i32;
let max_x_clamped = max_x.ceil().min(src_w) as i32;
let max_y_clamped = max_y.ceil().min(src_h) as i32;
let _src_x_size_raw = (max_x - min_x)
.max(0.0)
.min((src_raster_size[0] - min_x_clamped) as f64);
let _src_y_size_raw = (max_y - min_y)
.max(0.0)
.min((src_raster_size[1] - min_y_clamped) as f64);
let mut antimeridian_snap = false;
let (xoff, xsize) = if (max_x_clamped - min_x_clamped) as f64 > 0.9 * src_w {
antimeridian_snap = true;
(0, src_raster_size[0])
} else {
let xoff = 0.max((min_x_clamped - x_radius).min(src_raster_size[0]));
let xsize = 0.max(
(max_x_clamped - xoff + x_radius).min(src_raster_size[0] - xoff),
);
(xoff, xsize)
};
let (yoff, ysize) = if (max_y_clamped - min_y_clamped) as f64 > 0.9 * src_h {
(0, src_raster_size[1])
} else {
let yoff = 0.max((min_y_clamped - y_radius).min(src_raster_size[1]));
let ysize = 0.max(
(max_y_clamped - yoff + y_radius).min(src_raster_size[1] - yoff),
);
(yoff, ysize)
};
let fill_ratio = if antimeridian_snap {
0.01
} else {
(xsize as f64 * ysize as f64)
/ ((max_x - min_x + 2.0 * x_radius as f64)
* (max_y - min_y + 2.0 * y_radius as f64))
.max(1.0)
};
Some(SourceWindow {
xoff, yoff, xsize, ysize,
fill_ratio, n_failed, n_samples,
})
}
fn sample_edges_all(
transformer: &impl Transformer,
dst_off: [i32; 2],
dst_size: [i32; 2],
) -> (f64, f64, f64, f64, i32, i32) {
let n_max = 2 * (dst_size[0] + dst_size[1]) as usize;
let mut xs = Vec::with_capacity(n_max);
let mut ys = Vec::with_capacity(n_max);
for ix in 0..=dst_size[0] {
xs.push((dst_off[0] + ix) as f64);
ys.push(dst_off[1] as f64);
xs.push((dst_off[0] + ix) as f64);
ys.push((dst_off[1] + dst_size[1]) as f64);
}
for iy in 1..dst_size[1] {
xs.push(dst_off[0] as f64);
ys.push((dst_off[1] + iy) as f64);
xs.push((dst_off[0] + dst_size[0]) as f64);
ys.push((dst_off[1] + iy) as f64);
}
collect_bounds(transformer, &mut xs, &mut ys)
}
fn sample_edges_step(
transformer: &impl Transformer,
dst_off: [i32; 2],
dst_size: [i32; 2],
step_count: usize,
) -> (f64, f64, f64, f64, i32, i32) {
let n_max = step_count * 4;
let mut xs = Vec::with_capacity(n_max);
let mut ys = Vec::with_capacity(n_max);
let step_size = 1.0 / (step_count - 1) as f64;
let mut ratio = 0.0;
while ratio <= 1.0 + step_size * 0.5 {
let dx = ratio * dst_size[0] as f64;
let dy = ratio * dst_size[1] as f64;
xs.push(dst_off[0] as f64 + dx);
ys.push(dst_off[1] as f64);
xs.push(dst_off[0] as f64 + dx);
ys.push((dst_off[1] + dst_size[1]) as f64);
xs.push(dst_off[0] as f64);
ys.push(dst_off[1] as f64 + dy);
xs.push((dst_off[0] + dst_size[0]) as f64);
ys.push(dst_off[1] as f64 + dy);
ratio += step_size;
}
collect_bounds(transformer, &mut xs, &mut ys)
}
fn sample_grid(
transformer: &impl Transformer,
dst_off: [i32; 2],
dst_size: [i32; 2],
step_count: usize,
) -> (f64, f64, f64, f64, i32, i32) {
let n_grid = step_count + 2;
let n_max = n_grid * n_grid;
let mut xs = Vec::with_capacity(n_max);
let mut ys = Vec::with_capacity(n_max);
let step_size = 1.0 / (step_count - 1) as f64;
let near_edge = 0.5 / dst_size[0].max(1) as f64;
for iy in 0..n_grid {
let ratio_y = if iy == 0 {
near_edge
} else if iy <= step_count {
(iy - 1) as f64 * step_size
} else {
1.0 - near_edge
};
for ix in 0..n_grid {
let ratio_x = if ix == 0 {
near_edge
} else if ix <= step_count {
(ix - 1) as f64 * step_size
} else {
1.0 - near_edge
};
xs.push(ratio_x * dst_size[0] as f64 + dst_off[0] as f64);
ys.push(ratio_y * dst_size[1] as f64 + dst_off[1] as f64);
}
}
collect_bounds(transformer, &mut xs, &mut ys)
}
fn collect_bounds(
transformer: &impl Transformer,
xs: &mut Vec<f64>,
ys: &mut Vec<f64>,
) -> (f64, f64, f64, f64, i32, i32) {
let n = xs.len();
let ok = transformer.transform(true, xs, ys);
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
let mut n_failed = 0i32;
for i in 0..n {
if !ok[i] || xs[i].is_nan() || ys[i].is_nan() {
n_failed += 1;
continue;
}
if xs[i] < min_x { min_x = xs[i]; }
if xs[i] > max_x { max_x = xs[i]; }
if ys[i] < min_y { min_y = ys[i]; }
if ys[i] > max_y { max_y = ys[i]; }
}
(min_x, min_y, max_x, max_y, n as i32, n_failed)
}
#[derive(Debug, Clone)]
pub struct SourceGrid {
pub src_x: Vec<f64>,
pub src_y: Vec<f64>,
pub dst_x: Vec<f64>,
pub dst_y: Vec<f64>,
pub success: Vec<bool>,
pub grid_size: [usize; 2],
}
pub fn build_source_grid(
transformer: &impl Transformer,
src_raster_size: [i32; 2],
step_count: usize,
) -> SourceGrid {
let n_grid = step_count + 2;
let n_points = n_grid * n_grid;
let step_size = 1.0 / (step_count - 1).max(1) as f64;
let src_w = src_raster_size[0] as f64;
let src_h = src_raster_size[1] as f64;
let mut src_x = Vec::with_capacity(n_points);
let mut src_y = Vec::with_capacity(n_points);
for iy in 0..n_grid {
let ratio_y = if iy == 0 {
0.5 / src_h
} else if iy <= step_count {
(iy - 1) as f64 * step_size
} else {
1.0 - 0.5 / src_h
};
for ix in 0..n_grid {
let ratio_x = if ix == 0 {
0.5 / src_w
} else if ix <= step_count {
(ix - 1) as f64 * step_size
} else {
1.0 - 0.5 / src_w
};
src_x.push(ratio_x * src_w);
src_y.push(ratio_y * src_h);
}
}
let mut dst_x = src_x.clone();
let mut dst_y = src_y.clone();
let success = transformer.transform(false, &mut dst_x, &mut dst_y);
SourceGrid {
src_x,
src_y,
dst_x,
dst_y,
success,
grid_size: [n_grid, n_grid],
}
}
pub fn refine_from_source(
source_grid: &SourceGrid,
dst_off: [i32; 2],
dst_size: [i32; 2],
min_x: &mut f64,
min_y: &mut f64,
max_x: &mut f64,
max_y: &mut f64,
) {
let dst_x_min = dst_off[0] as f64;
let dst_x_max = (dst_off[0] + dst_size[0]) as f64;
let dst_y_min = dst_off[1] as f64;
let dst_y_max = (dst_off[1] + dst_size[1]) as f64;
for i in 0..source_grid.dst_x.len() {
if !source_grid.success[i] {
continue;
}
let dx = source_grid.dst_x[i];
let dy = source_grid.dst_y[i];
if dx >= dst_x_min && dx <= dst_x_max && dy >= dst_y_min && dy <= dst_y_max {
let sx = source_grid.src_x[i];
let sy = source_grid.src_y[i];
if sx < *min_x { *min_x = sx; }
if sx > *max_x { *max_x = sx; }
if sy < *min_y { *min_y = sy; }
if sy > *max_y { *max_y = sy; }
}
}
}
#[derive(Debug, Clone)]
pub struct ChunkPlan {
pub src_window: SourceWindow,
pub dst_off: [i32; 2],
pub dst_size: [i32; 2],
}
pub fn collect_chunk_list(
transformer: &impl Transformer,
src_raster_size: [i32; 2],
dst_off: [i32; 2],
dst_size: [i32; 2],
resample_padding: i32,
min_fill_ratio: f64,
min_dst_size: i32,
source_grid: Option<&SourceGrid>,
) -> Vec<ChunkPlan> {
let mut chunks = Vec::new();
collect_chunk_list_recursive(
transformer,
src_raster_size,
dst_off,
dst_size,
resample_padding,
min_fill_ratio,
min_dst_size,
source_grid,
&mut chunks,
);
chunks
}
fn collect_chunk_list_recursive(
transformer: &impl Transformer,
src_raster_size: [i32; 2],
dst_off: [i32; 2],
dst_size: [i32; 2],
resample_padding: i32,
min_fill_ratio: f64,
min_dst_size: i32,
source_grid: Option<&SourceGrid>,
out: &mut Vec<ChunkPlan>,
) {
let sw = compute_source_window(transformer, dst_off, dst_size, src_raster_size, resample_padding);
let sw = match sw { Some(sw) => sw, None => return };
if sw.xsize == 0 || sw.ysize == 0 { return; }
let should_split = sw.fill_ratio > 0.0
&& sw.fill_ratio < min_fill_ratio
&& (dst_size[0] > min_dst_size || dst_size[1] > min_dst_size);
if should_split {
let split_range = find_discontinuity_range(transformer, dst_off, dst_size, src_raster_size);
if let Some((col_min, col_max)) = split_range {
let left_width = col_min - dst_off[0];
let mid_width = col_max - col_min;
let right_width = dst_size[0] - (col_max - dst_off[0]);
if left_width > 0 {
collect_chunk_list_recursive(
transformer, src_raster_size, dst_off, [left_width, dst_size[1]],
resample_padding, min_fill_ratio, min_dst_size, source_grid, out);
}
if mid_width > 0 {
let mid_sw = compute_source_window(
transformer, [col_min, dst_off[1]], [mid_width, dst_size[1]],
src_raster_size, resample_padding);
if let Some(msw) = mid_sw {
if msw.xsize > 0 && msw.ysize > 0 {
out.push(ChunkPlan {
src_window: msw,
dst_off: [col_min, dst_off[1]],
dst_size: [mid_width, dst_size[1]],
});
}
}
}
if right_width > 0 {
collect_chunk_list_recursive(
transformer, src_raster_size, [col_max, dst_off[1]], [right_width, dst_size[1]],
resample_padding, min_fill_ratio, min_dst_size, source_grid, out);
}
} else {
if dst_size[0] >= dst_size[1] && dst_size[0] > 1 {
let chunk1 = dst_size[0] / 2;
let chunk2 = dst_size[0] - chunk1;
collect_chunk_list_recursive(
transformer, src_raster_size, dst_off, [chunk1, dst_size[1]],
resample_padding, min_fill_ratio, min_dst_size, source_grid, out);
collect_chunk_list_recursive(
transformer, src_raster_size,
[dst_off[0] + chunk1, dst_off[1]], [chunk2, dst_size[1]],
resample_padding, min_fill_ratio, min_dst_size, source_grid, out);
} else if dst_size[1] > 1 {
let chunk1 = dst_size[1] / 2;
let chunk2 = dst_size[1] - chunk1;
collect_chunk_list_recursive(
transformer, src_raster_size, dst_off, [dst_size[0], chunk1],
resample_padding, min_fill_ratio, min_dst_size, source_grid, out);
collect_chunk_list_recursive(
transformer, src_raster_size,
[dst_off[0], dst_off[1] + chunk1], [dst_size[0], chunk2],
resample_padding, min_fill_ratio, min_dst_size, source_grid, out);
} else {
out.push(ChunkPlan { src_window: sw, dst_off, dst_size });
}
}
} else {
out.push(ChunkPlan { src_window: sw, dst_off, dst_size });
}
}
fn find_discontinuity_range(
transformer: &impl Transformer,
dst_off: [i32; 2],
dst_size: [i32; 2],
src_raster_size: [i32; 2],
) -> Option<(i32, i32)> {
let ncol = dst_size[0] as usize;
if ncol < 3 { return None; }
let src_w = src_raster_size[0] as f64;
let threshold = src_w * 0.5;
let nrow = dst_size[1] as usize;
let row_step = if nrow <= 16 { 1 } else { 8.min(nrow / 4) };
let sample_rows: Vec<i32> = (0..nrow).step_by(row_step)
.map(|r| dst_off[1] + r as i32).collect();
let mut global_min: Option<i32> = None;
let mut global_max: Option<i32> = None;
for row in &sample_rows {
let mut xs: Vec<f64> = (0..ncol)
.map(|c| (dst_off[0] + c as i32) as f64 + 0.5).collect();
let mut ys = vec![*row as f64 + 0.5; ncol];
let ok = transformer.transform(true, &mut xs, &mut ys);
let mut max_gap = 0.0f64;
let mut gap_right: Option<usize> = None;
let mut prev_x: Option<f64> = None;
for col in 0..ncol {
if !ok[col] || xs[col].is_nan() { continue; }
if let Some(px) = prev_x {
let gap = (xs[col] - px).abs();
if gap > max_gap { max_gap = gap; gap_right = Some(col); }
}
prev_x = Some(xs[col]);
}
if max_gap > threshold {
if let Some(rc) = gap_right {
let abs_left = dst_off[0] + rc as i32 - 1;
let abs_right = dst_off[0] + rc as i32 + 1;
global_min = Some(match global_min {
Some(p) => p.min(abs_left), None => abs_left
});
global_max = Some(match global_max {
Some(p) => p.max(abs_right), None => abs_right
});
}
}
}
match (global_min, global_max) {
(Some(mn), Some(mx)) => {
let mn = mn.max(dst_off[0]);
let mx = mx.min(dst_off[0] + dst_size[0]);
if mn < mx { Some((mn, mx)) } else { None }
}
_ => None,
}
}