1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
//!
//! Geodetic to/from geocentrique conversion
//!
use crate::errors::{Error, Result};
use crate::math::consts::{FRAC_PI_2, PI, TAU};
const GENAU: f64 = 1.0e-12;
const GENAU2: f64 = GENAU * GENAU;
const MAXITER: usize = 30;
const FRAC_PI_2_EPS: f64 = 1.001 * FRAC_PI_2;
/// Convert geodetic coordinates to geocentric coordinatesa
///
/// The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
/// (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
/// according to the current ellipsoid parameters.
///
/// Latitude : Geodetic latitude in radians (input)
/// Longitude : Geodetic longitude in radians (input)
/// Height : Geodetic height, in meters (input)
/// X : Calculated Geocentric X coordinate, in meters (output)
/// Y : Calculated Geocentric Y coordinate, in meters (output)
/// Z : Calculated Geocentric Z coordinate, in meters (output)
///
/// This conversion converts geodetic coordinate values (longitude, latitude, elevation above ellipsoid)
/// to their geocentric (X, Y, Z) representation, where the first axis (X) points from the Earth centre
/// to the point of longitude=0, latitude=0, the second axis (Y) points from the
/// Earth centre to the point of longitude=90, latitude=0 and the third axis (Z) points to the North pole
///
pub fn geodetic_to_geocentric(x: f64, y: f64, z: f64, a: f64, es: f64) -> Result<(f64, f64, f64)> {
let mut lon = x;
let mut lat = y;
if lat < -FRAC_PI_2 && lat > -FRAC_PI_2_EPS {
lat = -FRAC_PI_2
} else if lat > FRAC_PI_2 && lat < FRAC_PI_2_EPS {
lat = FRAC_PI_2
} else if !(-FRAC_PI_2..=FRAC_PI_2).contains(&lat) {
return Err(Error::LatitudeOutOfRange);
};
if lon > PI {
// TAU is 2PI
lon -= TAU;
}
let (sin_lat, cos_lat) = lat.sin_cos();
// Earth radius at location
let rn = a / (1. - es * (sin_lat * sin_lat)).sqrt();
Ok((
(rn + z) * cos_lat * lon.cos(),
(rn + z) * cos_lat * lon.sin(),
((rn * (1. - es)) + z) * sin_lat,
))
}
/// Convert geocentric coordinates to geodetic coordinates
///
/// ### Reference...
///
/// Wenzel, H.-G.(1985): Hochauflösende Kugelfunktionsmodelle für
/// das Gravitationspotential der Erde. Wiss. Arb. Univ. Hannover
/// Nr. 137, p. 130-131.
///
/// Programmed by GGA- Leibniz-Institute of Applied Geophysics
/// Stilleweg 2
/// D-30655 Hannover
/// Federal Republic of Germany
/// Internet: www.gga-hannover.de
///
/// Hannover, March 1999, April 2004.
/// see also: comments in statements
///
/// remarks:
/// Mathematically exact and because of symmetry of rotation-ellipsoid,
/// each point (X,Y,Z) has at least two solutions (Latitude1,Longitude1,Height1) and
/// (Latitude2,Longitude2,Height2). Is point=(0.,0.,Z) (P=0.), so you get even
/// four solutions,» every two symmetrical to the semi-minor axis.
/// Here Height1 and Height2 have at least a difference in order of
/// radius of curvature (e.g. (0,0,b)=> (90.,0.,0.) or (-90.,0.,-2b);
/// (a+100.)*(sqrt(2.)/2.,sqrt(2.)/2.,0.) => (0.,45.,100.) or
/// (0.,225.,-(2a+100.))).
/// The algorithm always computes (Latitude,Longitude) with smallest |Height|.
/// For normal computations, that means |Height|<10000.m, algorithm normally
/// converges after to 2-3 steps!!!
/// But if |Height| has the amount of length of ellipsoid's axis
/// (e.g. -6300000.m),» algorithm needs about 15 steps.
pub fn geocentric_to_geodetic(
x: f64,
y: f64,
z: f64,
a: f64,
es: f64,
b: f64,
) -> Result<(f64, f64, f64)> {
let d2 = (x * x) + (y * y);
// distance between semi-minor axis and location
let p = d2.sqrt();
// distance between center and location
let rr = (d2 + z * z).sqrt();
// if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
// of ellipsoid (=center of mass), Latitude becomes PI/2
let lon = if p / a < GENAU {
if rr / a < GENAU {
return Ok((0., FRAC_PI_2, -b));
}
0.
} else {
y.atan2(x)
};
//--------------------------------------------------------------
// Following iterative algorithm was developed by
// Institut for Erdmessung", University of Hannover, July 1988.
// Internet: www.ife.uni-hannover.de
// Iterative computation of CPHI,SPHI and Height.
// Iteration of CPHI and SPHI to 10**-12 radian resp.
// 2*10**-7 arcsec.
// --------------------------------------------------------------
let ct = z / rr;
let st = p / rr;
let mut rx = 1.0 / (1.0 - es * (2.0 - es) * st * st).sqrt();
let mut cphi0 = st * (1.0 - es) * rx;
let mut sphi0 = ct * rx;
let (mut rk, mut rn, mut cphi, mut sphi, mut sdphi, mut height);
// loop to find sin(Latitude) resp. Latitude
// until |sin(Latitude(iter)-Latitude(iter-1))| < genau
// Note: using `for _ in 0..MAXITER { ... }` lead to compiler error
// about unitialized variables
let mut iter = 0;
loop {
iter += 1;
rn = a / (1.0 - es * sphi0 * sphi0).sqrt();
// ellipsoidal (geodetic) height
height = p * cphi0 + z * sphi0 - rn * (1.0 - es * sphi0 * sphi0);
// avoid zero division
if (rn + height) == 0. {
return Ok((lon, 0., height));
}
rk = es * rn / (rn + height);
rx = 1.0 / (1.0 - rk * (2.0 - rk) * st * st).sqrt();
cphi = st * (1.0 - rk) * rx;
sphi = ct * rx;
sdphi = sphi * cphi0 - cphi * sphi0;
cphi0 = cphi;
sphi0 = sphi;
if sdphi * sdphi <= GENAU2 {
break;
}
if iter >= MAXITER {
break;
}
}
// ellipsoidal (geodetic) latitude
Ok((lon, sphi.atan2(cphi.abs()), height))
}