use crate::Transformer;
fn n_terms(order: usize) -> usize {
match order {
1 => 3,
2 => 6,
3 => 10,
_ => panic!("GCP polynomial order must be 1, 2, or 3"),
}
}
fn min_gcps(order: usize) -> usize {
n_terms(order)
}
fn eval_terms(x: f64, y: f64, order: usize, out: &mut [f64]) {
out[0] = 1.0;
out[1] = x;
out[2] = y;
if order >= 2 {
out[3] = x * x;
out[4] = x * y;
out[5] = y * y;
}
if order >= 3 {
out[6] = x * x * x;
out[7] = x * x * y;
out[8] = x * y * y;
out[9] = y * y * y;
}
}
fn eval_poly(coeffs: &[f64], terms: &[f64]) -> f64 {
coeffs.iter().zip(terms.iter()).map(|(c, t)| c * t).sum()
}
pub struct GcpTransformer {
order: usize,
to_geo_x: Vec<f64>,
to_geo_y: Vec<f64>,
pixel_mean: f64,
line_mean: f64,
from_geo_x: Vec<f64>, from_geo_y: Vec<f64>, geo_x_mean: f64,
geo_y_mean: f64,
}
impl GcpTransformer {
pub fn new(
pixel: &[f64],
line: &[f64],
geo_x: &[f64],
geo_y: &[f64],
order: usize,
) -> Option<Self> {
let n = pixel.len();
assert_eq!(n, line.len());
assert_eq!(n, geo_x.len());
assert_eq!(n, geo_y.len());
let order = if order == 0 {
if n >= min_gcps(3) {
2
} else if n >= min_gcps(2) {
2
} else if n >= min_gcps(1) {
1
} else {
return None;
}
} else {
order
};
if n < min_gcps(order) {
return None;
}
let nt = n_terms(order);
let pixel_mean = pixel.iter().sum::<f64>() / n as f64;
let line_mean = line.iter().sum::<f64>() / n as f64;
let geo_x_mean = geo_x.iter().sum::<f64>() / n as f64;
let geo_y_mean = geo_y.iter().sum::<f64>() / n as f64;
let to_geo_x = fit_polynomial(
pixel, line, geo_x, pixel_mean, line_mean, order, nt, n,
)?;
let to_geo_y = fit_polynomial(
pixel, line, geo_y, pixel_mean, line_mean, order, nt, n,
)?;
let from_geo_x = fit_polynomial(
geo_x, geo_y, pixel, geo_x_mean, geo_y_mean, order, nt, n,
)?;
let from_geo_y = fit_polynomial(
geo_x, geo_y, line, geo_x_mean, geo_y_mean, order, nt, n,
)?;
Some(GcpTransformer {
order,
to_geo_x,
to_geo_y,
pixel_mean,
line_mean,
from_geo_x,
from_geo_y,
geo_x_mean,
geo_y_mean,
})
}
pub fn order(&self) -> usize {
self.order
}
pub fn to_geo_x_coeffs(&self) -> &[f64] {
&self.to_geo_x
}
pub fn to_geo_y_coeffs(&self) -> &[f64] {
&self.to_geo_y
}
pub fn from_geo_pixel_coeffs(&self) -> &[f64] {
&self.from_geo_x
}
pub fn from_geo_line_coeffs(&self) -> &[f64] {
&self.from_geo_y
}
}
impl Transformer for GcpTransformer {
fn transform(&self, inverse: bool, x: &mut [f64], y: &mut [f64]) -> Vec<bool> {
let nt = n_terms(self.order);
let mut terms = vec![0.0; nt];
let n = x.len();
if !inverse {
for i in 0..n {
let cx = x[i] - self.pixel_mean;
let cy = y[i] - self.line_mean;
eval_terms(cx, cy, self.order, &mut terms);
x[i] = eval_poly(&self.to_geo_x, &terms);
y[i] = eval_poly(&self.to_geo_y, &terms);
}
} else {
for i in 0..n {
let cx = x[i] - self.geo_x_mean;
let cy = y[i] - self.geo_y_mean;
eval_terms(cx, cy, self.order, &mut terms);
let new_x = eval_poly(&self.from_geo_x, &terms);
let new_y = eval_poly(&self.from_geo_y, &terms);
x[i] = new_x;
y[i] = new_y;
}
}
vec![true; n]
}
}
fn fit_polynomial(
in_x: &[f64],
in_y: &[f64],
out: &[f64],
x_mean: f64,
y_mean: f64,
order: usize,
nt: usize,
n: usize,
) -> Option<Vec<f64>> {
let mut m = vec![0.0; nt * nt];
let mut rhs = vec![0.0; nt];
let mut terms = vec![0.0; nt];
for k in 0..n {
let cx = in_x[k] - x_mean;
let cy = in_y[k] - y_mean;
eval_terms(cx, cy, order, &mut terms);
for i in 0..nt {
rhs[i] += terms[i] * out[k];
for j in 0..nt {
m[i * nt + j] += terms[i] * terms[j];
}
}
}
solve_linear_system(&mut m, &mut rhs, nt)
}
fn solve_linear_system(m: &mut [f64], rhs: &mut [f64], nt: usize) -> Option<Vec<f64>> {
for col in 0..nt {
let mut max_val = m[col * nt + col].abs();
let mut max_row = col;
for row in (col + 1)..nt {
let val = m[row * nt + col].abs();
if val > max_val {
max_val = val;
max_row = row;
}
}
if max_val < 1e-15 {
return None; }
if max_row != col {
for j in 0..nt {
m.swap(col * nt + j, max_row * nt + j);
}
rhs.swap(col, max_row);
}
let pivot = m[col * nt + col];
for row in (col + 1)..nt {
let factor = m[row * nt + col] / pivot;
for j in col..nt {
m[row * nt + j] -= factor * m[col * nt + j];
}
rhs[row] -= factor * rhs[col];
}
}
let mut result = vec![0.0; nt];
for col in (0..nt).rev() {
let mut sum = rhs[col];
for j in (col + 1)..nt {
sum -= m[col * nt + j] * result[j];
}
result[col] = sum / m[col * nt + col];
}
Some(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_affine_roundtrip() {
let cols: Vec<f64> = vec![0.0, 100.0, 200.0, 0.0, 100.0, 200.0];
let rows: Vec<f64> = vec![0.0, 0.0, 0.0, 100.0, 100.0, 100.0];
let geo_x: Vec<f64> = cols.iter().map(|c| 300000.0 + c * 10.0).collect();
let geo_y: Vec<f64> = rows.iter().map(|r| 5200000.0 + r * -10.0).collect();
let t = GcpTransformer::new(&cols, &rows, &geo_x, &geo_y, 1).unwrap();
let mut px = vec![50.0, 150.0];
let mut py = vec![50.0, 75.0];
t.transform(false, &mut px, &mut py);
assert!((px[0] - 300500.0).abs() < 1e-6);
assert!((py[0] - 5199500.0).abs() < 1e-6);
assert!((px[1] - 301500.0).abs() < 1e-6);
assert!((py[1] - 5199250.0).abs() < 1e-6);
let mut gx = vec![300500.0, 301500.0];
let mut gy = vec![5199500.0, 5199250.0];
t.transform(true, &mut gx, &mut gy);
assert!((gx[0] - 50.0).abs() < 1e-6);
assert!((gy[0] - 50.0).abs() < 1e-6);
assert!((gx[1] - 150.0).abs() < 1e-6);
assert!((gy[1] - 75.0).abs() < 1e-6);
}
#[test]
fn test_order2_quadratic() {
let mut cols = Vec::new();
let mut rows = Vec::new();
let mut geo_x = Vec::new();
let mut geo_y = Vec::new();
for c in (0..=200).step_by(40) {
for r in (0..=200).step_by(40) {
let cf = c as f64;
let rf = r as f64;
cols.push(cf);
rows.push(rf);
geo_x.push(100.0 + 2.0 * cf + 0.001 * cf * cf);
geo_y.push(200.0 + 3.0 * rf + 0.0005 * rf * rf);
}
}
let t = GcpTransformer::new(&cols, &rows, &geo_x, &geo_y, 2).unwrap();
let mut px = vec![73.0];
let mut py = vec![117.0];
let expected_x = 100.0 + 2.0 * 73.0 + 0.001 * 73.0 * 73.0;
let expected_y = 200.0 + 3.0 * 117.0 + 0.0005 * 117.0 * 117.0;
t.transform(false, &mut px, &mut py);
assert!((px[0] - expected_x).abs() < 1e-6, "got {} expected {}", px[0], expected_x);
assert!((py[0] - expected_y).abs() < 1e-6, "got {} expected {}", py[0], expected_y);
}
#[test]
fn test_auto_order() {
let cols: Vec<f64> = vec![0.0, 100.0, 200.0, 0.0, 100.0, 200.0];
let rows: Vec<f64> = vec![0.0, 0.0, 0.0, 100.0, 100.0, 100.0];
let geo_x: Vec<f64> = cols.iter().map(|c| 300000.0 + c * 10.0).collect();
let geo_y: Vec<f64> = rows.iter().map(|r| 5200000.0 + r * -10.0).collect();
let t = GcpTransformer::new(&cols, &rows, &geo_x, &geo_y, 0).unwrap();
assert_eq!(t.order(), 2);
}
#[test]
fn test_insufficient_gcps() {
let cols = vec![0.0, 100.0];
let rows = vec![0.0, 100.0];
let geo_x = vec![300000.0, 301000.0];
let geo_y = vec![5200000.0, 5199000.0];
assert!(GcpTransformer::new(&cols, &rows, &geo_x, &geo_y, 1).is_none());
assert!(GcpTransformer::new(&cols, &rows, &geo_x, &geo_y, 0).is_none());
}
}