use crate::datum_transform::Datum;
use crate::errors::{Error, Result};
use crate::geocent::{geocentric_to_geodetic, geodetic_to_geocentric};
use crate::math::adjlon;
use crate::math::consts::{EPS_12, FRAC_PI_2};
use crate::proj::{Axis, Proj, ProjType};
pub trait TransformClosure: FnMut(f64, f64, f64) -> Result<(f64, f64, f64)> {}
impl<F: FnMut(f64, f64, f64) -> Result<(f64, f64, f64)>> TransformClosure for F {}
pub trait Transform {
fn transform_coordinates<F: TransformClosure>(&mut self, f: &mut F) -> Result<()>;
}
#[derive(PartialEq)]
pub enum Direction {
Forward,
Inverse,
}
use Direction::*;
pub fn transform<P>(src: &Proj, dst: &Proj, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
if !src.has_inverse() {
return Err(Error::NoInverseProjectionDefined);
}
if !dst.has_forward() {
return Err(Error::NoForwardProjectionDefined);
}
adjust_axes(src, Inverse, points)?;
height_unit(src, Inverse, points)?;
projected_to_geographic(src, points)?;
prime_meridian(src, Inverse, points)?;
datum_transform(src, dst, points)?;
prime_meridian(dst, Forward, points)?;
geographic_to_projected(dst, points)?;
height_unit(dst, Forward, points)?;
adjust_axes(dst, Forward, points)?;
Ok(())
}
fn datum_transform<P>(src: &Proj, dst: &Proj, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
let src_datum = src.datum();
let dst_datum = dst.datum();
if src_datum.no_datum() || dst_datum.no_datum() || src_datum.is_identical_to(dst_datum) {
return Ok(());
}
points.transform_coordinates(&mut |x, y, z| Datum::transform(src_datum, dst_datum, x, y, z))
}
fn projected_to_geographic<P>(p: &Proj, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
match p.projection_type() {
ProjType::Latlong => {
if p.geoc() {
let rone_es = p.ellipsoid().rone_es;
points.transform_coordinates(&mut |lam, phi, z| {
Ok((lam, (rone_es * phi.tan()).atan(), z))
})
} else {
Ok(())
}
}
ProjType::Geocentric => geographic_to_cartesian(p, Inverse, points),
ProjType::Other => {
let d = &p.data();
let (lam0, x0, y0) = (d.lam0, d.x0, d.y0);
let (ra, to_meter) = (d.ellps.ra, d.to_meter);
let over = p.over();
let proj = p.projection();
points.transform_coordinates(&mut |x, y, z| {
let (mut lam, phi, z) = proj.inverse(
(x * to_meter - x0) * ra,
(y * to_meter - y0) * ra,
z,
)?;
lam += lam0;
if !over {
lam = adjlon(lam);
}
Ok((lam, phi, z))
})
}
}
}
fn geographic_to_projected<P>(p: &Proj, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
match p.projection_type() {
ProjType::Latlong => {
if p.geoc() {
let one_es = p.ellipsoid().one_es;
points.transform_coordinates(&mut |lam, phi, z| {
Ok(if (phi.abs() - FRAC_PI_2).abs() > EPS_12 {
(lam, (one_es * phi.tan()).atan(), z)
} else {
(lam, phi, z)
})
})
} else {
Ok(())
}
}
ProjType::Geocentric => geographic_to_cartesian(p, Forward, points),
ProjType::Other => {
let d = p.data();
let (lam0, x0, y0) = (d.lam0, d.x0, d.y0);
let a = d.ellps.a;
let proj = p.projection();
let over = p.over();
let fr_meter = 1. / p.to_meter();
points.transform_coordinates(&mut |lam, phi, z| {
let t = phi.abs() - FRAC_PI_2;
if t > EPS_12 || lam.abs() > 10. {
Err(Error::CoordinateOutOfRange)
} else {
let (x, y, z) = proj.forward(
if !over {
adjlon(lam - lam0)
} else {
lam - lam0
},
if t.abs() <= EPS_12 {
if phi < 0. {
-FRAC_PI_2
} else {
FRAC_PI_2
}
} else {
phi
},
z,
)?;
Ok((fr_meter * (a * x + x0), fr_meter * (a * y + y0), z))
}
})
}
}
}
fn geographic_to_cartesian<P>(p: &Proj, dir: Direction, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
if !p.is_geocent() {
return Ok(());
}
let datum = p.datum();
let (a, b, es) = (datum.a, datum.b, datum.es);
let fac = p.to_meter();
if fac != 1.0 {
match dir {
Forward => points.transform_coordinates(&mut |x, y, z| {
geodetic_to_geocentric(x, y, z, a, es).map(|(x, y, z)| (x * fac, y * fac, z * fac))
}),
Inverse => points.transform_coordinates(&mut |x, y, z| {
geocentric_to_geodetic(x * fac, y * fac, z * fac, a, es, b)
}),
}
} else {
match dir {
Forward => {
points.transform_coordinates(&mut |x, y, z| geodetic_to_geocentric(x, y, z, a, es))
}
Inverse => points
.transform_coordinates(&mut |x, y, z| geocentric_to_geodetic(x, y, z, a, es, b)),
}
}
}
fn prime_meridian<P>(p: &Proj, dir: Direction, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
let mut pm = p.from_greenwich();
if pm == 0. || p.is_geocent() || p.is_latlong() {
Ok(())
} else {
if dir == Forward {
pm = -pm;
}
points.transform_coordinates(&mut |x, y, z| Ok((x + pm, y, z)))
}
}
fn adjust_axes<P>(p: &Proj, dir: Direction, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
if !p.is_normalized_axis() {
match dir {
Forward => denormalize_axis(p.axis(), points),
Inverse => normalize_axis(p.axis(), points),
}
} else {
Ok(())
}
}
fn normalize_axis<P: Transform + ?Sized>(axis: &Axis, points: &mut P) -> Result<()> {
points.transform_coordinates(&mut |x, y, z| {
let (mut x_out, mut y_out, mut z_out) = (x, y, z);
axis.iter().enumerate().for_each(|(i, axe)| {
let value = match i {
0 => x,
1 => y,
_ => z,
};
match axe {
b'e' => x_out = value,
b'w' => x_out = -value,
b'n' => y_out = value,
b's' => y_out = -value,
b'u' => z_out = value,
b'd' => z_out = -value,
_ => unreachable!(),
}
});
Ok((x_out, y_out, z_out))
})
}
fn denormalize_axis<P: Transform + ?Sized>(axis: &Axis, points: &mut P) -> Result<()> {
points.transform_coordinates(&mut |x, y, z| {
let (mut x_out, mut y_out, mut z_out) = (x, y, z);
axis.iter().enumerate().for_each(|(i, axe)| {
let value = match axe {
b'e' => x,
b'w' => -x,
b'n' => y,
b's' => -y,
b'u' => z,
b'd' => -z,
_ => unreachable!(),
};
match i {
0 => x_out = value,
1 => y_out = value,
_ => z_out = value,
}
});
Ok((x_out, y_out, z_out))
})
}
fn height_unit<P>(p: &Proj, dir: Direction, points: &mut P) -> Result<()>
where
P: Transform + ?Sized,
{
let fac = if dir == Forward {
1. / p.vto_meter()
} else {
p.vto_meter()
};
if fac != 1.0 {
points.transform_coordinates(&mut |x, y, z| Ok((x, y, z * fac)))
} else {
Ok(())
}
}