use crate::grid_shift::Helmert7Params;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GeocentricEllipsoid {
pub semimajor_a: f64,
pub flattening_f: f64,
}
impl GeocentricEllipsoid {
#[inline]
pub fn eccentricity_squared(&self) -> f64 {
self.flattening_f * (2.0 - self.flattening_f)
}
#[inline]
pub fn semiminor_b(&self) -> f64 {
self.semimajor_a * (1.0 - self.flattening_f)
}
pub fn wgs84() -> Self {
Self {
semimajor_a: 6_378_137.0,
flattening_f: 1.0 / 298.257_223_563,
}
}
pub fn grs80() -> Self {
Self {
semimajor_a: 6_378_137.0,
flattening_f: 1.0 / 298.257_222_101,
}
}
pub fn airy1830() -> Self {
Self {
semimajor_a: 6_377_563.396,
flattening_f: 1.0 / 299.324_964_6,
}
}
pub fn bessel1841() -> Self {
Self {
semimajor_a: 6_377_397.155,
flattening_f: 1.0 / 299.152_812_8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EcefCoordinate {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl EcefCoordinate {
#[inline]
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
#[inline]
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
#[inline]
pub fn subtract(&self, other: &EcefCoordinate) -> EcefCoordinate {
EcefCoordinate {
x: self.x - other.x,
y: self.y - other.y,
z: self.z - other.z,
}
}
}
#[derive(Debug, Clone)]
pub struct GeocentricCrs {
pub name: String,
pub epsg_code: Option<u32>,
pub ellipsoid: GeocentricEllipsoid,
}
impl GeocentricCrs {
pub fn wgs84() -> Self {
Self {
name: String::from("WGS 84"),
epsg_code: Some(4978),
ellipsoid: GeocentricEllipsoid::wgs84(),
}
}
pub fn etrs89() -> Self {
Self {
name: String::from("ETRS89"),
epsg_code: Some(4936),
ellipsoid: GeocentricEllipsoid::grs80(),
}
}
pub fn nad83() -> Self {
Self {
name: String::from("NAD83"),
epsg_code: Some(4978),
ellipsoid: GeocentricEllipsoid::grs80(),
}
}
pub fn custom<S: Into<String>>(name: S, ellipsoid: GeocentricEllipsoid) -> Self {
Self {
name: name.into(),
epsg_code: None,
ellipsoid,
}
}
}
pub fn geographic_to_ecef(
ellipsoid: &GeocentricEllipsoid,
lon_deg: f64,
lat_deg: f64,
h_m: f64,
) -> EcefCoordinate {
let a = ellipsoid.semimajor_a;
let e2 = ellipsoid.eccentricity_squared();
let phi = lat_deg.to_radians();
let lambda = lon_deg.to_radians();
let sin_phi = phi.sin();
let cos_phi = phi.cos();
let sin_lambda = lambda.sin();
let cos_lambda = lambda.cos();
let n = a / (1.0 - e2 * sin_phi * sin_phi).sqrt();
let x = (n + h_m) * cos_phi * cos_lambda;
let y = (n + h_m) * cos_phi * sin_lambda;
let z = (n * (1.0 - e2) + h_m) * sin_phi;
EcefCoordinate { x, y, z }
}
pub fn ecef_to_geographic(
ellipsoid: &GeocentricEllipsoid,
ecef: &EcefCoordinate,
) -> (f64, f64, f64) {
let a = ellipsoid.semimajor_a;
let f = ellipsoid.flattening_f;
let e2 = ellipsoid.eccentricity_squared();
let b = ellipsoid.semiminor_b();
let x = ecef.x;
let y = ecef.y;
let z = ecef.z;
let lambda = y.atan2(x);
let p = (x * x + y * y).sqrt();
let ep2 = (a * a - b * b) / (b * b);
let theta = (z * a).atan2(p * b);
let sin_theta = theta.sin();
let cos_theta = theta.cos();
let phi_numer = z + ep2 * b * sin_theta * sin_theta * sin_theta;
let phi_denom = p - e2 * a * cos_theta * cos_theta * cos_theta;
let phi = phi_numer.atan2(phi_denom);
let sin_phi = phi.sin();
let cos_phi = phi.cos();
let n = a / (1.0 - e2 * sin_phi * sin_phi).sqrt();
let h = if cos_phi.abs() > f {
p / cos_phi - n
} else {
z / sin_phi - n * (1.0 - e2)
};
(lambda.to_degrees(), phi.to_degrees(), h)
}
pub fn ecef_to_geographic_iterative(
ellipsoid: &GeocentricEllipsoid,
ecef: &EcefCoordinate,
) -> (f64, f64, f64) {
let a = ellipsoid.semimajor_a;
let e2 = ellipsoid.eccentricity_squared();
let x = ecef.x;
let y = ecef.y;
let z = ecef.z;
let lambda = y.atan2(x);
let p = (x * x + y * y).sqrt();
let mut phi = (z / (p * (1.0 - e2))).atan();
for _ in 0..10 {
let sin_phi = phi.sin();
let n = a / (1.0 - e2 * sin_phi * sin_phi).sqrt();
let phi_new = (z + e2 * n * sin_phi).atan2(p);
let delta = (phi_new - phi).abs();
phi = phi_new;
if delta < 1e-12 {
break;
}
}
let sin_phi = phi.sin();
let cos_phi = phi.cos();
let n = a / (1.0 - e2 * sin_phi * sin_phi).sqrt();
let h = if cos_phi.abs() > 1e-10 {
p / cos_phi - n
} else {
z / sin_phi - n * (1.0 - e2)
};
(lambda.to_degrees(), phi.to_degrees(), h)
}
#[derive(Debug, Clone)]
pub struct EcefTransformer {
pub src: GeocentricCrs,
pub dst: GeocentricCrs,
pub helmert: Option<Helmert7Params>,
}
impl EcefTransformer {
pub fn new(src: GeocentricCrs, dst: GeocentricCrs) -> Self {
Self {
src,
dst,
helmert: None,
}
}
pub fn with_helmert(mut self, params: Helmert7Params) -> Self {
self.helmert = Some(params);
self
}
pub fn transform(&self, coord: &EcefCoordinate) -> EcefCoordinate {
match &self.helmert {
Some(h) => {
let (x2, y2, z2) = h.apply(coord.x, coord.y, coord.z);
EcefCoordinate::new(x2, y2, z2)
}
None => *coord,
}
}
pub fn transform_batch(&self, coords: &[EcefCoordinate]) -> Vec<EcefCoordinate> {
coords.iter().map(|c| self.transform(c)).collect()
}
}
pub fn geographic_to_geographic_via_ecef(
src_ell: &GeocentricEllipsoid,
dst_ell: &GeocentricEllipsoid,
helmert: Option<&Helmert7Params>,
lon_deg: f64,
lat_deg: f64,
h_m: f64,
) -> (f64, f64, f64) {
let ecef_src = geographic_to_ecef(src_ell, lon_deg, lat_deg, h_m);
let ecef_dst = match helmert {
Some(h) => {
let (x2, y2, z2) = h.apply(ecef_src.x, ecef_src.y, ecef_src.z);
EcefCoordinate::new(x2, y2, z2)
}
None => ecef_src,
};
ecef_to_geographic(dst_ell, &ecef_dst)
}