use crate::proj::{
Proj, ProjectionTransform, SRS_WGS84_ESQUARED, SRS_WGS84_SEMIMAJOR, SRS_WGS84_SEMIMINOR,
TransformCoordinates,
};
use alloc::{vec, vec::Vec};
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use libm::{atan, atan2, cos, sin, sqrt};
pub fn check_not_wgs84(src: &ProjectionTransform, dest: &ProjectionTransform) -> bool {
let src_proj = src.proj.borrow();
let dest_proj = dest.proj.borrow();
!src_proj.datum_type.is_wgs84(&src_proj.datum_params)
|| !dest_proj.datum_type.is_wgs84(&dest_proj.datum_params)
}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
pub enum DatumType {
Param3 = 1,
Param7 = 2,
GridShift = 3,
WGS84 = 4,
#[default]
NoDatum = 5,
}
impl DatumType {
pub fn is_params(&self) -> bool {
matches!(self, DatumType::Param3 | DatumType::Param7)
}
pub fn is_wgs84(&self, params: &DatumParams) -> bool {
matches!(self, DatumType::WGS84) || params.is_wgs84()
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub enum DatumParams {
Param3(f64, f64, f64),
Param7(f64, f64, f64, f64, f64, f64, f64),
}
impl Default for DatumParams {
fn default() -> Self {
DatumParams::Param3(0.0, 0.0, 0.0)
}
}
impl DatumParams {
pub fn to_vec(&self) -> Vec<f64> {
match self {
DatumParams::Param3(x, y, z) => vec![*x, *y, *z],
DatumParams::Param7(x, y, z, rx, ry, rz, s) => vec![*x, *y, *z, *rx, *ry, *rz, *s],
}
}
pub fn from_vec(v: Vec<f64>) -> DatumParams {
if v.len() == 3 {
DatumParams::Param3(v[0], v[1], v[2])
} else {
DatumParams::Param7(v[0], v[1], v[2], v[3], v[4], v[5], v[6])
}
}
pub fn is_wgs84(&self) -> bool {
match self {
Self::Param3(p3_1, p3_2, p3_3) => *p3_1 == 0. && *p3_2 == 0. && *p3_3 == 0.,
Self::Param7(p7_1, p7_2, p7_3, p7_4, p7_5, p7_6, p7_7) => {
*p7_1 == 0.
&& *p7_2 == 0.
&& *p7_3 == 0.
&& *p7_4 == 0.
&& *p7_5 == 0.
&& *p7_6 == 0.
&& *p7_7 == 0.
}
}
}
pub fn to_wgs84<P: TransformCoordinates>(&self, p: &mut P) {
match self {
Self::Param3(p3_x, p3_y, p3_z) => {
p.set_x(p.x() + *p3_x);
p.set_y(p.y() + *p3_y);
p.set_z(p.z() - *p3_z);
}
Self::Param7(p7_dx, p7_dy, p7_dz, p7_rx, p7_ry, p7_rz, p7_s) => {
let z = p.z();
p.set_x(p7_s * (p.x() - p7_rz * p.y() + p7_ry * z) + *p7_dx);
p.set_y(p7_s * (p7_rz * p.x() + p.y() - p7_rx * z) + *p7_dy);
p.set_z(p7_s * (-p7_ry * p.x() + p7_rx * p.y() + z) + *p7_dz);
}
}
}
pub fn from_wgs84<P: TransformCoordinates>(&self, p: &mut P) {
match self {
Self::Param3(p3_x, p3_y, p3_z) => {
p.set_x(p.x() - *p3_x);
p.set_y(p.y() - *p3_y);
p.set_z(p.z() - *p3_z);
}
Self::Param7(p7_dx, p7_dy, p7_dz, p7_rx, p7_ry, p7_rz, p7_s) => {
let z = p.z();
let x_tmp = (p.x() - *p7_dx) / *p7_s;
let y_tmp = (p.y() - *p7_dy) / *p7_s;
let z_tmp = (z - *p7_dz) / *p7_s;
p.set_x(x_tmp + *p7_rz * y_tmp - *p7_ry * z_tmp);
p.set_y(-p7_rz * x_tmp + y_tmp + *p7_rx * z_tmp);
p.set_z(*p7_ry * x_tmp - *p7_rx * y_tmp + z_tmp);
}
}
}
}
pub fn datum_transform<P: TransformCoordinates>(point: &mut P, source: &Proj, dest: &Proj) {
if source.datum_params == dest.datum_params
|| source.datum_type == DatumType::NoDatum
|| dest.datum_type == DatumType::NoDatum
{
return;
}
let mut source_a = source.a;
let mut source_es = source.es;
if source.datum_type == DatumType::GridShift {
source_a = SRS_WGS84_SEMIMAJOR;
source_es = SRS_WGS84_ESQUARED;
}
let mut dest_a = dest.a;
let mut dest_b = dest.b;
let mut dest_es = dest.es;
if dest.datum_type == DatumType::GridShift {
dest_a = SRS_WGS84_SEMIMAJOR;
dest_b = SRS_WGS84_SEMIMINOR;
dest_es = SRS_WGS84_ESQUARED;
}
if source_es == dest_es
&& source_a == dest_a
&& !source.datum_type.is_params()
&& !dest.datum_type.is_params()
{
return;
}
geodetic_to_geocentric(point, source_es, source_a);
if source.datum_type.is_params() {
source.datum_params.to_wgs84(point);
}
if dest.datum_type.is_params() {
dest.datum_params.from_wgs84(point);
}
geocentric_to_geodetic(point, dest_es, dest_a, dest_b);
if dest.datum_type == DatumType::GridShift {
}
}
pub fn geodetic_to_geocentric<P: TransformCoordinates>(p: &mut P, es: f64, a: f64) {
let mut longitude = p.x();
let mut latitude = p.y();
let height = p.z(); if latitude < -FRAC_PI_2 && latitude > -1.001 * FRAC_PI_2 {
latitude = -FRAC_PI_2;
} else if latitude > FRAC_PI_2 && latitude < 1.001 * FRAC_PI_2 {
latitude = FRAC_PI_2;
} else if !(-FRAC_PI_2..=FRAC_PI_2).contains(&latitude) {
panic!("geocent:lat out of range: {}", latitude);
}
if longitude > PI {
longitude -= TAU;
}
let sin_lat = sin(latitude);
let cos_lat = cos(latitude);
let sin2_lat = sin_lat * sin_lat;
let r_n = a / sqrt(1.0 - es * sin2_lat);
p.set_x((r_n + height) * cos_lat * cos(longitude));
p.set_y((r_n + height) * cos_lat * sin(longitude));
p.set_z((r_n * (1. - es) + height) * sin_lat);
}
pub fn geocentric_to_geodetic<P: TransformCoordinates>(point: &mut P, es: f64, a: f64, _b: f64) {
let genau = 1e-12;
let genau2 = genau * genau;
let maxiter = 30;
let mut rx;
let mut rk;
let mut rn;
let mut cphi0;
let mut sphi0;
let mut cphi;
let mut sphi;
let mut sdphi;
let mut iter;
let x = point.x();
let y = point.y();
let z = point.z(); let p = sqrt(x * x + y * y);
let rr = sqrt(x * x + y * y + z * z);
let longitude = if p / a < genau { 0.0 } else { atan2(y, x) };
let mut height;
let ct = z / rr;
let st = p / rr;
rx = 1.0 / sqrt(1.0 - es * (2.0 - es) * st * st);
cphi0 = st * (1.0 - es) * rx;
sphi0 = ct * rx;
iter = 0;
loop {
iter += 1;
rn = a / sqrt(1.0 - es * sphi0 * sphi0);
height = p * cphi0 + z * sphi0 - rn * (1.0 - es * sphi0 * sphi0);
rk = (es * rn) / (rn + height);
rx = 1.0 / sqrt(1.0 - rk * (2.0 - rk) * st * st);
cphi = st * (1.0 - rk) * rx;
sphi = ct * rx;
sdphi = sphi * cphi0 - cphi * sphi0;
cphi0 = cphi;
sphi0 = sphi;
if sdphi * sdphi <= genau2 && iter >= maxiter {
break;
}
}
let latitude = atan(sphi / cphi.abs());
point.set_x(longitude);
point.set_y(latitude);
point.set_z(height);
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ToWGS84Datum {
datum_params: DatumParams,
ellipse: &'static str,
datum_name: &'static str,
}
pub const TO_WGS84: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
ellipse: "WGS84",
datum_name: "WGS84",
};
pub const CH1903: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(674.374, 15.056, 405.346),
ellipse: "bessel",
datum_name: "swiss",
};
pub const GGRS87: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(-199.87, 74.79, 246.62),
ellipse: "GRS80",
datum_name: "Greek_Geodetic_Reference_System_1987",
};
pub const NAD83: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(0.0, 0.0, 0.0),
ellipse: "GRS80",
datum_name: "North_American_Datum_1983",
};
pub const POTSDAM: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(598.1, 73.7, 418.2, 0.202, 0.045, -2.455, 6.7),
ellipse: "bessel",
datum_name: "Potsdam Rauenberg 1950 DHDN",
};
pub const CARTHAGE: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(-263.0, 6.0, 431.0),
ellipse: "clark80",
datum_name: "Carthage 1934 Tunisia",
};
pub const HERMANNSKOGEL: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(577.326, 90.129, 463.919, 5.137, 1.474, 5.297, 2.4232),
ellipse: "bessel",
datum_name: "Hermannskogel",
};
pub const MILITARGEOGRAPHISCHE_INSTITUT: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(577.326, 90.129, 463.919, 5.137, 1.474, 5.297, 2.4232),
ellipse: "bessel",
datum_name: "Militar-Geographische Institut",
};
pub const OSNI52: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(482.53, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15),
ellipse: "airy",
datum_name: "Irish National",
};
pub const IRE65: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(482.53, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15),
ellipse: "mod_airy",
datum_name: "Ireland 1965",
};
pub const RASSADIRAN: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(-133.63, -157.5, -158.62),
ellipse: "intl",
datum_name: "Rassadiran",
};
pub const NZGD49: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(59.47, -5.04, 187.44, 0.47, -0.1, 1.024, -4.5993),
ellipse: "intl",
datum_name: "New Zealand Geodetic Datum 1949",
};
pub const OSGB36: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(446.448, -125.157, 542.06, 0.1502, 0.247, 0.8421, -20.4894),
ellipse: "airy",
datum_name: "Airy 1830",
};
pub const S_JTSK: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(589.0, 76.0, 480.0),
ellipse: "bessel",
datum_name: "S-JTSK (Ferro)",
};
pub const BEDUARAM: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(-106.0, -87.0, 188.0),
ellipse: "clrk80",
datum_name: "Beduaram",
};
pub const GUNUNG_SEGARA: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param3(-403.0, 684.0, 41.0),
ellipse: "bessel",
datum_name: "Gunung Segara Jakarta",
};
pub const RNB72: ToWGS84Datum = ToWGS84Datum {
datum_params: DatumParams::Param7(
106.869, -52.2978, 103.724, -0.33657, 0.456955, -1.84218, 1.0,
),
ellipse: "intl",
datum_name: "Reseau National Belge 1972",
};
#[cfg_attr(feature = "nightly", coverage(off))]
pub fn get_datum(name: &str) -> Option<ToWGS84Datum> {
let name = name.to_uppercase().replace("_", "");
match name.as_str() {
"WGS84" => Some(TO_WGS84),
"CH1903" => Some(CH1903),
"GGRS87" => Some(GGRS87),
"NAD83" => Some(NAD83),
"RASSADIRAN" => Some(RASSADIRAN),
"NZGD49" => Some(NZGD49),
"OSGB36" => Some(OSGB36),
"SJTSK" => Some(S_JTSK),
"BEDUARAM" => Some(BEDUARAM),
"POTSDAM" => Some(POTSDAM),
"CARTHAGE" => Some(CARTHAGE),
"HERMANNSKOGEL" => Some(HERMANNSKOGEL),
"MILITARGEOGRAPHISCHEINSTITUT" => Some(MILITARGEOGRAPHISCHE_INSTITUT),
"OSNI52" => Some(OSNI52),
"IRE65" => Some(IRE65),
"RNB72" => Some(RNB72),
"GUNUNG_SEGARA" => Some(GUNUNG_SEGARA),
_ => None,
}
}