use super::TransformCoordinates;
use crate::proj::{
Direction, IoUnits, ProjJSON, ProjectionTransform, Step, adjlon, check_not_wgs84,
datum_transform, geocentric_latitude,
};
use alloc::{collections::BTreeMap, fmt::Debug, string::String};
use core::f64::consts::FRAC_PI_2;
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Transformer {
epsgs: BTreeMap<String, String>,
src: ProjectionTransform,
dest: ProjectionTransform,
}
impl Transformer {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
epsgs: BTreeMap::new(),
src: ProjectionTransform::wgs84(),
dest: ProjectionTransform::wgs84(),
}
}
pub fn forward<P: TransformCoordinates + Debug>(&self, p: &P) -> P {
let mut res = p.clone();
transform_point(&self.src, &self.dest, &mut res);
res
}
pub fn forward_mut<P: TransformCoordinates + Debug>(&self, p: &mut P) {
transform_point(&self.src, &self.dest, p);
}
pub fn inverse<P: TransformCoordinates + Debug>(&self, p: &P) -> P {
let mut res = p.clone();
transform_point(&self.dest, &self.src, &mut res);
res
}
pub fn inverse_mut<P: TransformCoordinates + Debug>(&self, p: &mut P) {
transform_point(&self.dest, &self.src, p);
}
pub fn insert_epsg_code(&mut self, code: String, value: String) {
self.epsgs.insert(code, value);
}
pub fn set_source(&mut self, code: String) {
self.src = self.build_transformer(code);
}
pub fn set_source_def(&mut self, def: ProjectionTransform) {
self.src = def;
}
pub fn set_destination(&mut self, code: String) {
self.dest = self.build_transformer(code);
}
pub fn set_destination_def(&mut self, def: ProjectionTransform) {
self.dest = def;
}
fn build_transformer(&mut self, mut code: String) -> ProjectionTransform {
if let Some(epsg) = self.epsgs.get(&code) {
code = epsg.clone();
}
if let Ok(json) = serde_json::from_str::<ProjJSON>(&code) {
return json.to_projection_transform();
}
ProjJSON::parse_wkt(&code).to_projection_transform()
}
pub fn get_epsg_code(&self, code: String) -> Option<String> {
self.epsgs.get(&code).cloned()
}
}
pub fn transform_point<P: TransformCoordinates + Debug>(
src: &ProjectionTransform,
dest: &ProjectionTransform,
point: &mut P,
) {
if src == dest || (Step::same_step(&src.method, &dest.method)) {
return;
}
let has_z = point.has_z();
if check_not_wgs84(src, dest) {
transform_point(src, &ProjectionTransform::wgs84(), point);
}
transform_inv(src, point);
datum_transform(point, &src.proj.borrow(), &dest.proj.borrow());
transform_fwd(dest, point);
if !has_z {
point.set_z(0.0);
}
}
fn transform_inv<P: TransformCoordinates + Debug>(
proj_trans: &ProjectionTransform,
coords: &mut P,
) {
transform_inv_prepare(proj_trans, coords);
proj_trans.method.inverse(coords);
transform_inv_finalize(proj_trans, coords);
}
fn transform_inv_prepare<P: TransformCoordinates + Debug>(
proj_trans: &ProjectionTransform,
coords: &mut P,
) {
let proj = &proj_trans.proj.borrow();
if f64::INFINITY == coords.z() && proj_trans.helmert.is_some() {
coords.set_z(0.0);
}
if f64::INFINITY == coords.t() && proj_trans.helmert.is_some() {
coords.set_t(0.0);
}
if let Some(axisswap) = &proj_trans.axisswap {
axisswap.method.inverse(coords);
}
match proj.right {
IoUnits::CARTESIAN => {
let to_meter = proj.to_meter;
coords.set_x(coords.x() * to_meter);
coords.set_y(coords.y() * to_meter);
coords.set_z(coords.z() * to_meter);
if proj.is_geocent
&& let Some(cart) = &proj_trans.cart
{
transform_inv(cart, coords);
}
}
IoUnits::PROJECTED | IoUnits::CLASSIC => {
let to_meter = proj.to_meter;
let vto_meter = proj.vto_meter;
coords.set_x(coords.x() * to_meter - proj.x0);
coords.set_y(coords.y() * to_meter - proj.y0);
coords.set_z(coords.z() * vto_meter - proj.z0);
if proj.right == IoUnits::PROJECTED {
return;
}
if proj.ra != 0. {
coords.set_x(coords.x() * proj.ra);
coords.set_y(coords.y() * proj.ra);
}
}
IoUnits::RADIANS => {
coords.set_z(coords.z() * proj.vto_meter - proj.z0);
}
_ => {}
}
}
fn transform_inv_finalize<P: TransformCoordinates + Debug>(
proj_trans: &ProjectionTransform,
coords: &mut P,
) {
let proj = &proj_trans.proj.borrow();
if proj.left == IoUnits::RADIANS {
coords.set_lam(coords.lam() + proj.from_greenwich + proj.lam0);
if !proj.over {
coords.set_lam(adjlon(coords.lam()));
}
if let Some(vgridshift) = &proj_trans.vgridshift {
transform_inv(vgridshift, coords);
}
if let Some(hgridshift) = &proj_trans.hgridshift {
transform_fwd(hgridshift, coords);
} else if (proj_trans.cart_wgs84.is_some() && proj_trans.cart.is_some())
|| proj_trans.helmert.is_some()
{
if let Some(cart) = &proj_trans.cart {
transform_fwd(cart, coords);
}
if let Some(helmert) = &proj_trans.helmert {
transform_fwd(helmert, coords);
}
if let Some(cart_wgs84) = &proj_trans.cart_wgs84 {
transform_inv(cart_wgs84, coords);
}
}
if proj.geoc {
geocentric_latitude(proj, Direction::FWD, coords);
}
}
}
fn transform_fwd<P: TransformCoordinates + Debug>(
proj_trans: &ProjectionTransform,
coords: &mut P,
) {
transform_fwd_prepare(proj_trans, coords);
proj_trans.method.forward(coords);
transform_fwd_finalize(proj_trans, coords);
}
fn transform_fwd_prepare<P: TransformCoordinates + Debug>(
proj_trans: &ProjectionTransform,
coords: &mut P,
) {
let proj = &proj_trans.proj.borrow();
if coords.z() == f64::INFINITY && proj_trans.helmert.is_some() {
coords.set_z(0.0);
}
if coords.t() == f64::INFINITY && proj_trans.helmert.is_some() {
coords.set_t(0.0);
}
if proj.left == IoUnits::RADIANS {
let t = (if coords.phi() < 0. { -coords.phi() } else { coords.phi() }) - FRAC_PI_2;
if t > 1e-12 || coords.lam() > 10. || coords.lam() < -10. {
panic!("Invalid latitude");
}
if coords.phi() > FRAC_PI_2 {
coords.set_phi(FRAC_PI_2);
}
if coords.phi() < -FRAC_PI_2 {
coords.set_phi(-FRAC_PI_2);
}
if proj.geoc {
geocentric_latitude(proj, Direction::INV, coords);
}
if !proj.over {
coords.set_lam(adjlon(coords.lam()));
}
if let Some(hgridshift) = &proj_trans.hgridshift {
transform_inv(hgridshift, coords);
} else if (proj_trans.cart_wgs84.is_some() && proj_trans.cart.is_some())
|| proj_trans.helmert.is_some()
{
if let Some(cart_wgs84) = &proj_trans.cart_wgs84 {
transform_fwd(cart_wgs84, coords);
}
if let Some(helmert) = &proj_trans.helmert {
transform_inv(helmert, coords);
}
if let Some(cart) = &proj_trans.cart {
transform_inv(cart, coords);
}
}
if let Some(vgridshift) = &proj_trans.vgridshift {
transform_fwd(vgridshift, coords);
}
coords.set_lam((coords.lam() - proj.from_greenwich) - proj.lam0);
if !proj.over {
coords.set_lam(adjlon(coords.lam()));
}
return;
}
if proj.left == IoUnits::CARTESIAN
&& let Some(helmert) = &proj_trans.helmert
{
transform_inv(helmert, coords);
}
}
fn transform_fwd_finalize<P: TransformCoordinates + Debug>(
proj_trans: &ProjectionTransform,
coords: &mut P,
) {
let proj = &proj_trans.proj.borrow();
match proj.right {
IoUnits::CARTESIAN => {
if proj.is_geocent
&& let Some(cart) = &proj_trans.cart
{
transform_fwd(cart, coords);
}
coords.set_x(coords.x() * proj.fr_meter);
coords.set_y(coords.y() * proj.fr_meter);
coords.set_z(coords.z() * proj.vfr_meter);
}
IoUnits::CLASSIC => {
if proj.a != 0. {
coords.set_x(coords.x() * proj.a);
coords.set_y(coords.y() * proj.a);
}
coords.set_x(proj.fr_meter * (coords.x() + proj.x0));
coords.set_y(proj.fr_meter * (coords.y() + proj.y0));
coords.set_z(proj.vfr_meter * (coords.z() + proj.z0));
}
IoUnits::PROJECTED => {
coords.set_x(proj.fr_meter * (coords.x() + proj.x0));
coords.set_y(proj.fr_meter * (coords.y() + proj.y0));
coords.set_z(proj.vfr_meter * (coords.z() + proj.z0));
}
IoUnits::RADIANS => {
coords.set_z(proj.vfr_meter * (coords.z() + proj.z0));
}
_ => {}
}
if let Some(axisswap) = &proj_trans.axisswap {
axisswap.method.forward(coords);
}
}