use super::*;
use crate::authoring::*;
pub trait Latitudes: EllipsoidBase {
#[must_use]
fn latitude_geographic_to_geocentric(&self, geographic: f64) -> f64 {
let f = self.flattening();
((1.0 - f * (2.0 - f)) * geographic.tan()).atan()
}
#[must_use]
fn latitude_geocentric_to_geographic(&self, geocentric: f64) -> f64 {
(geocentric.tan() / (1.0 - self.eccentricity_squared())).atan()
}
#[must_use]
fn latitude_geographic_to_reduced(&self, geographic: f64) -> f64 {
geographic.tan().atan2(1. / (1. - self.flattening()))
}
#[must_use]
fn latitude_reduced_to_geographic(&self, reduced: f64) -> f64 {
reduced.tan().atan2(1. - self.flattening())
}
#[must_use]
fn latitude_geographic_to_isometric(&self, geographic: f64) -> f64 {
let e = self.eccentricity();
gudermannian::inv(geographic) - (e * geographic.sin()).atanh() * e
}
#[must_use]
fn latitude_isometric_to_geographic(&self, isometric: f64) -> f64 {
let e = self.eccentricity();
ancillary::sinhpsi_to_tanphi(isometric.sinh(), e).atan()
}
fn coefficients_for_rectifying_latitude_computations(&self) -> FourierCoefficients {
self.latitude_fourier_coefficients(&constants::RECTIFYING)
}
fn latitude_geographic_to_rectifying(
&self,
geographic_latitude: f64,
coefficients: &FourierCoefficients,
) -> f64 {
coefficients.etc[0]
* (geographic_latitude + fourier::sin(2. * geographic_latitude, &coefficients.fwd))
}
fn latitude_rectifying_to_geographic(
&self,
rectifying_latitude: f64,
coefficients: &FourierCoefficients,
) -> f64 {
let rlat = rectifying_latitude / coefficients.etc[0];
rlat + fourier::sin(2. * rlat, &coefficients.inv)
}
fn coefficients_for_conformal_latitude_computations(&self) -> FourierCoefficients {
self.latitude_fourier_coefficients(&constants::CONFORMAL)
}
fn latitude_geographic_to_conformal(
&self,
geographic_latitude: f64,
coefficients: &FourierCoefficients,
) -> f64 {
geographic_latitude + fourier::sin(2. * geographic_latitude, &coefficients.fwd)
}
fn latitude_conformal_to_geographic(
&self,
conformal_latitude: f64,
coefficients: &FourierCoefficients,
) -> f64 {
conformal_latitude + fourier::sin(2. * conformal_latitude, &coefficients.inv)
}
fn coefficients_for_authalic_latitude_computations(&self) -> FourierCoefficients {
self.latitude_fourier_coefficients(&constants::AUTHALIC)
}
fn latitude_geographic_to_authalic(
&self,
geographic_latitude: f64,
coefficients: &FourierCoefficients,
) -> f64 {
geographic_latitude + fourier::sin(2. * geographic_latitude, &coefficients.fwd)
}
fn latitude_authalic_to_geographic(
&self,
authalic_latitude: f64,
coefficients: &FourierCoefficients,
) -> f64 {
authalic_latitude + fourier::sin(2. * authalic_latitude, &coefficients.inv)
}
fn latitude_fourier_coefficients(
&self,
coefficients: &PolynomialCoefficients,
) -> FourierCoefficients {
let n = self.third_flattening();
let mut result = fourier_coefficients(n, coefficients);
result.etc[0] = self.normalized_meridian_arc_unit();
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::FRAC_PI_2;
#[test]
fn geocentric() -> Result<(), Error> {
let ellps = Ellipsoid::named("GRS80")?;
let lats = Vec::from([35., 45., 55.]);
for lat in &lats {
let lat: f64 = *lat;
let theta = ellps.latitude_geographic_to_geocentric(lat.to_radians());
let roundtrip = ellps.latitude_geocentric_to_geographic(theta).to_degrees();
assert!((lat - roundtrip).abs() < 1e-14);
}
assert!(ellps.latitude_geographic_to_geocentric(0.0).abs() < 1.0e-10);
assert!((ellps.latitude_geographic_to_geocentric(FRAC_PI_2) - FRAC_PI_2).abs() < 1.0e-10);
Ok(())
}
#[test]
fn reduced() -> Result<(), Error> {
let ellps = Ellipsoid::named("GRS80")?;
let lat = 55_f64.to_radians();
let lat1 = ellps.latitude_geographic_to_reduced(lat);
let lat2 = ellps.latitude_reduced_to_geographic(lat1);
assert!((lat - lat2) < 1.0e-12);
assert!(ellps.latitude_geographic_to_reduced(0.0).abs() < 1.0e-10);
assert!((ellps.latitude_geographic_to_reduced(FRAC_PI_2) - FRAC_PI_2).abs() < 1.0e-10);
Ok(())
}
#[test]
fn isometric() -> Result<(), Error> {
let ellps = Ellipsoid::named("GRS80")?;
let angle = 45_f64.to_radians();
let isometric = 50.227465815385806_f64.to_radians();
assert!((ellps.latitude_geographic_to_isometric(angle) - isometric).abs() < 1e-15);
assert!((ellps.latitude_isometric_to_geographic(isometric) - angle).abs() < 1e-15);
assert!((ellps.latitude_isometric_to_geographic(-isometric) + angle).abs() < 1e-15);
Ok(())
}
#[test]
fn rectifying() -> Result<(), Error> {
let ellps = Ellipsoid::named("GRS80")?;
let latitudes = [35., 45., 55., -35., -45., -55., 0., 90.];
let coefficients = ellps.coefficients_for_rectifying_latitude_computations();
#[allow(clippy::unnecessary_cast)]
for phi in latitudes {
let lat = (phi as f64).to_radians();
let mu = ellps.latitude_geographic_to_rectifying(lat, &coefficients);
let phi = ellps.latitude_rectifying_to_geographic(mu, &coefficients);
let ihp = ellps.latitude_rectifying_to_geographic(-mu, &coefficients);
assert!((lat - phi).abs() < 1e-14);
assert!((lat + ihp).abs() < 1e-14); }
Ok(())
}
#[test]
fn conformal() -> Result<(), Error> {
let ellps = Ellipsoid::named("GRS80")?;
let latitudes = [35., 45., 55., -35., -45., -55., 0., 90.];
#[rustfmt::skip]
#[allow(clippy::excessive_precision)]
let conformal_latitudes = [
34.819454814955349775, 44.807684055145067248, 54.819109023689023275, -34.819454814955349775, -44.807684055145067248, -54.819109023689023275, 0., 90., ];
let chi_coefs = ellps.latitude_fourier_coefficients(&constants::CONFORMAL);
let pairs = latitudes.iter().zip(conformal_latitudes.iter());
#[allow(clippy::unnecessary_cast)]
for pair in pairs {
let phi = (*(pair.0) as f64).to_radians();
let chi = (*(pair.1) as f64).to_radians();
assert!((chi - ellps.latitude_geographic_to_conformal(phi, &chi_coefs)).abs() < 1e-14);
assert!((phi - ellps.latitude_conformal_to_geographic(chi, &chi_coefs)).abs() < 1e-14);
}
let lat = 55_f64.to_radians();
let chi = ellps.latitude_geographic_to_conformal(lat, &chi_coefs);
let phi = ellps.latitude_conformal_to_geographic(chi, &chi_coefs);
assert!((chi.to_degrees() - 54.819_109_023_689_02).abs() < 1e-12);
assert_eq!(phi.to_degrees(), 55.0);
Ok(())
}
#[test]
fn authalic() -> Result<(), Error> {
let ellps = Ellipsoid::named("GRS80")?;
let authalic = ellps.coefficients_for_authalic_latitude_computations();
let geographic_latitudes = [35., 45., 50., 55., -35., -45., -50., -55., -90., 0., 90.];
let xi_50_iogp = 0.870_458_708;
let xi_50_karney = ellps.latitude_geographic_to_authalic(50_f64.to_radians(), &authalic);
assert!((xi_50_karney - xi_50_iogp).abs() < 1e-8);
#[rustfmt::skip]
let authalic_latitudes = [
34.879518549145985, 44.87170287280388, 49.87361022014683, 54.879361594517796, -34.879518549145985, -44.87170287280388, -49.87361022014683, -54.879361594517796, -90., 0., 90. ];
let pairs = geographic_latitudes.iter().zip(authalic_latitudes.iter());
#[allow(clippy::unnecessary_cast)]
for pair in pairs.clone() {
let phi = (*(pair.0) as f64).to_radians();
let xi = (*(pair.1) as f64).to_radians();
let xi_karney = ellps.latitude_geographic_to_authalic(phi, &authalic);
assert!((xi - xi_karney).abs() < 1e-14);
let phi_karney = ellps.latitude_authalic_to_geographic(xi_karney, &authalic);
assert!((phi - phi_karney).abs() < 1e-14);
}
let proj_coefs = authset(ellps.eccentricity_squared());
#[allow(clippy::unnecessary_cast)]
for pair in pairs {
let phi = (*(pair.0) as f64).to_radians();
let xi = (*(pair.1) as f64).to_radians();
let phi_evenden = authlat(xi, &proj_coefs);
assert!((phi - phi_evenden).abs() < 1e-9);
}
Ok(())
}
const P00: f64 = 1. / 3.;
const P01: f64 = 31. / 180.;
const P02: f64 = 517. / 5040.;
const P10: f64 = 23. / 360.;
const P11: f64 = 251. / 3780.;
const P20: f64 = 761. / 45360.;
fn authset(es: f64) -> [f64; 3] {
let mut apa = [0.0; 3];
let mut t = es;
apa[0] = t * P00;
t *= es;
apa[0] += t * P01;
apa[1] = t * P10;
t *= es;
apa[0] += t * P02;
apa[1] += t * P11;
apa[2] = t * P20;
apa
}
fn authlat(authalic: f64, coefs: &[f64; 3]) -> f64 {
let t = 2. * authalic;
authalic + coefs[0] * t.sin() + coefs[1] * (t + t).sin() + coefs[2] * (t + t + t).sin()
}
}