use crate::{Dimension, Extent, GeoTransform};
pub fn extent_dim_to_gt(extent: &Extent, dim: &Dimension) -> GeoTransform {
let pixel_width = (extent[1] - extent[0]) / dim[0] as f64;
let pixel_height = -(extent[3] - extent[2]) / dim[1] as f64;
[extent[0], pixel_width, 0.0, extent[3], 0.0, pixel_height]
}
pub fn gt_to_extent(gt: &GeoTransform, dim: &Dimension) -> Extent {
let xmin = gt[0];
let xmax = gt[0] + dim[0] as f64 * gt[1];
let ymax = gt[3];
let ymin = gt[3] + dim[1] as f64 * gt[5];
[xmin, xmax, ymin, ymax]
}
pub fn inv_geotransform(gt: &GeoTransform) -> Option<GeoTransform> {
let det = gt[1] * gt[5] - gt[2] * gt[4];
if det.abs() < 1e-15 {
return None;
}
let inv_det = 1.0 / det;
Some([
(gt[2] * gt[3] - gt[0] * gt[5]) * inv_det,
gt[5] * inv_det,
-gt[2] * inv_det,
(gt[0] * gt[4] - gt[1] * gt[3]) * inv_det,
-gt[4] * inv_det,
gt[1] * inv_det,
])
}
pub fn x_from_col(gt: &GeoTransform, col: f64) -> f64 {
gt[0] + (col + 0.5) * gt[1]
}
pub fn y_from_row(gt: &GeoTransform, row: f64) -> f64 {
gt[3] + (row + 0.5) * gt[5]
}
pub fn xy_from_col_row(gt: &GeoTransform, col: f64, row: f64) -> (f64, f64) {
let pixel = col + 0.5;
let line = row + 0.5;
(
gt[0] + pixel * gt[1] + line * gt[2],
gt[3] + pixel * gt[4] + line * gt[5],
)
}
pub fn col_from_x(gt: &GeoTransform, x: f64) -> f64 {
(x - gt[0]) / gt[1] - 0.5
}
pub fn row_from_y(gt: &GeoTransform, y: f64) -> f64 {
(y - gt[3]) / gt[5] - 0.5
}
pub fn col_row_from_xy(gt: &GeoTransform, x: f64, y: f64) -> Option<(f64, f64)> {
let inv = inv_geotransform(gt)?;
let pixel = inv[0] + x * inv[1] + y * inv[2];
let line = inv[3] + x * inv[4] + y * inv[5];
Some((pixel - 0.5, line - 0.5))
}
pub fn x_res(dim: &Dimension, extent: &Extent) -> f64 {
(extent[1] - extent[0]) / dim[0] as f64
}
pub fn y_res(dim: &Dimension, extent: &Extent) -> f64 {
(extent[3] - extent[2]) / dim[1] as f64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_col() {
let gt = [100.0, 10.0, 0.0, 200.0, 0.0, -10.0];
let x = x_from_col(>, 5.0);
let c = col_from_x(>, x);
assert!((c - 5.0).abs() < 1e-10);
}
#[test]
fn roundtrip_row() {
let gt = [100.0, 10.0, 0.0, 200.0, 0.0, -10.0];
let y = y_from_row(>, 3.0);
let r = row_from_y(>, y);
assert!((r - 3.0).abs() < 1e-10);
}
#[test]
fn roundtrip_extent() {
let ext = [100.0, 200.0, -50.0, 50.0];
let dim = [1000, 500];
let gt = extent_dim_to_gt(&ext, &dim);
let ext2 = gt_to_extent(>, &dim);
for i in 0..4 {
assert!((ext[i] - ext2[i]).abs() < 1e-10);
}
}
#[test]
fn inv_geotransform_roundtrip() {
let gt = [100.0, 10.0, 0.0, 500.0, 0.0, -10.0];
let inv = inv_geotransform(>).unwrap();
let x = 105.0;
let y = 495.0;
let col = inv[0] + x * inv[1] + y * inv[2];
let row = inv[3] + x * inv[4] + y * inv[5];
assert!((col - 0.5).abs() < 1e-10);
assert!((row - 0.5).abs() < 1e-10);
}
#[test]
fn inv_geotransform_full_affine() {
let gt = [100.0, 10.0, 2.0, 500.0, -1.0, -10.0];
let inv = inv_geotransform(>).unwrap();
let pixel = 3.5;
let line = 7.5;
let x = gt[0] + pixel * gt[1] + line * gt[2];
let y = gt[3] + pixel * gt[4] + line * gt[5];
let pixel2 = inv[0] + x * inv[1] + y * inv[2];
let line2 = inv[3] + x * inv[4] + y * inv[5];
assert!((pixel - pixel2).abs() < 1e-10);
assert!((line - line2).abs() < 1e-10);
}
#[test]
fn inv_geotransform_singular() {
let gt = [100.0, 0.0, 0.0, 500.0, 0.0, -10.0];
assert!(inv_geotransform(>).is_none());
}
#[test]
fn col_row_from_xy_roundtrip() {
let gt = extent_dim_to_gt(&[100.0, 200.0, -50.0, 50.0], &[100, 100]);
for col in [0.0, 25.0, 50.0, 99.0] {
for row in [0.0, 25.0, 50.0, 99.0] {
let (x, y) = xy_from_col_row(>, col, row);
let (c2, r2) = col_row_from_xy(>, x, y).unwrap();
assert!((col - c2).abs() < 1e-10, "col {col} → {c2}");
assert!((row - r2).abs() < 1e-10, "row {row} → {r2}");
}
}
}
}