Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
Geodetic.hpp
Go to the documentation of this file.
1// Vulcan Geodetic Utilities
2// ECEF <-> Geodetic conversions using Vermeille (2004) closed-form algorithm
3#pragma once
4
7
8#include <janus/math/Arithmetic.hpp>
9#include <janus/math/Logic.hpp>
10#include <janus/math/Trig.hpp>
11
12namespace vulcan {
13
14// =============================================================================
15// LLA - Geodetic Coordinates
16// =============================================================================
17
27template <typename Scalar> struct LLA {
28 Scalar lon;
29 Scalar lat;
30 Scalar alt;
31
32 LLA() : lon(Scalar(0)), lat(Scalar(0)), alt(Scalar(0)) {}
33 LLA(Scalar lon_, Scalar lat_, Scalar alt_)
34 : lon(lon_), lat(lat_), alt(alt_) {}
35};
36
37// =============================================================================
38// Spherical - Geocentric Coordinates
39// =============================================================================
40
49template <typename Scalar> struct Spherical {
50 Scalar lon;
51 Scalar lat_gc;
52 Scalar radius;
53
54 Spherical() : lon(Scalar(0)), lat_gc(Scalar(0)), radius(Scalar(0)) {}
55 Spherical(Scalar lon_, Scalar lat_gc_, Scalar radius_)
56 : lon(lon_), lat_gc(lat_gc_), radius(radius_) {}
57};
58
59// =============================================================================
60// ECEF to LLA - Vermeille (2004) Closed-Form Algorithm
61// =============================================================================
62
77template <typename Scalar>
78LLA<Scalar> ecef_to_lla(const Vec3<Scalar> &r,
79 const EarthModel &m = EarthModel::WGS84()) {
80 const Scalar x = r(0);
81 const Scalar y = r(1);
82 const Scalar z = r(2);
83
84 const double a = m.a;
85 const double e2 = m.e2;
86 const double e4 = e2 * e2;
87
88 // Compute intermediate values
89 // p = (x² + y²) / a²
90 // q = (1 - e²) z² / a²
91 const Scalar p = (x * x + y * y) / (a * a);
92 const Scalar q = (1.0 - e2) * z * z / (a * a);
93
94 // r = (p + q - e⁴) / 6
95 const Scalar r_val = (p + q - e4) / 6.0;
96
97 // Evolute parameters
98 // s = e⁴ p q / (4 r³)
99 const Scalar r_cubed = r_val * r_val * r_val;
100 const Scalar s = e4 * p * q / (4.0 * r_cubed);
101
102 // t = ∛(1 + s + √(s(2+s)))
103 // Note: For numerical stability when s is small, this still works
104 const Scalar s_term = s * (2.0 + s);
105 const Scalar t = janus::pow(1.0 + s + janus::sqrt(s_term), 1.0 / 3.0);
106
107 // u = r (1 + t + 1/t)
108 const Scalar u = r_val * (1.0 + t + 1.0 / t);
109
110 // v = √(u² + e⁴ q)
111 const Scalar v = janus::sqrt(u * u + e4 * q);
112
113 // w = e² (u + v - q) / (2v)
114 const Scalar w = e2 * (u + v - q) / (2.0 * v);
115
116 // k = √(u + v + w²) - w
117 const Scalar k = janus::sqrt(u + v + w * w) - w;
118
119 // D = k √(x² + y²) / (k + e²)
120 const Scalar xy_dist = janus::sqrt(x * x + y * y);
121 const Scalar D = k * xy_dist / (k + e2);
122
123 // Compute geodetic latitude
124 // φ = 2 atan2(z, D + √(D² + z²))
125 const Scalar lat = 2.0 * janus::atan2(z, D + janus::sqrt(D * D + z * z));
126
127 // Compute altitude
128 // h = (k + e² - 1) / k · √(D² + z²)
129 const Scalar alt = (k + e2 - 1.0) / k * janus::sqrt(D * D + z * z);
130
131 // Compute longitude with pole handling
132 // At poles (xy_dist ≈ 0), longitude is undefined; we set it to 0
133 constexpr double eps = 1e-15;
134 const Scalar is_pole = xy_dist < eps;
135 const Scalar lon = janus::where(is_pole, Scalar(0.0), janus::atan2(y, x));
136
137 return LLA<Scalar>(lon, lat, alt);
138}
139
140// =============================================================================
141// LLA to ECEF - Closed-Form Conversion
142// =============================================================================
143
151template <typename Scalar>
152Vec3<Scalar> lla_to_ecef(const LLA<Scalar> &lla,
153 const EarthModel &m = EarthModel::WGS84()) {
154 const Scalar sin_lat = janus::sin(lla.lat);
155 const Scalar cos_lat = janus::cos(lla.lat);
156 const Scalar sin_lon = janus::sin(lla.lon);
157 const Scalar cos_lon = janus::cos(lla.lon);
158
159 const double a = m.a;
160 const double e2 = m.e2;
161
162 // Radius of curvature in the prime vertical
163 // N = a / √(1 - e² sin²φ)
164 const Scalar N = a / janus::sqrt(1.0 - e2 * sin_lat * sin_lat);
165
166 // ECEF coordinates
167 // x = (N + h) cos(φ) cos(λ)
168 // y = (N + h) cos(φ) sin(λ)
169 // z = (N(1 - e²) + h) sin(φ)
170 Vec3<Scalar> r;
171 r(0) = (N + lla.alt) * cos_lat * cos_lon;
172 r(1) = (N + lla.alt) * cos_lat * sin_lon;
173 r(2) = (N * (1.0 - e2) + lla.alt) * sin_lat;
174
175 return r;
176}
177
178// =============================================================================
179// ECEF to Spherical (Geocentric) Coordinates
180// =============================================================================
181
189template <typename Scalar>
190Spherical<Scalar> ecef_to_spherical(const Vec3<Scalar> &r) {
191 const Scalar x = r(0);
192 const Scalar y = r(1);
193 const Scalar z = r(2);
194
195 // Radius (distance from Earth center)
196 const Scalar radius = janus::sqrt(x * x + y * y + z * z);
197
198 // Geocentric latitude (angle from equatorial plane to position vector)
199 const Scalar lat_gc = janus::asin(z / radius);
200
201 // Longitude with pole handling
202 const Scalar xy_dist = janus::sqrt(x * x + y * y);
203 constexpr double eps = 1e-15;
204 const Scalar is_pole = xy_dist < eps;
205 const Scalar lon = janus::where(is_pole, Scalar(0.0), janus::atan2(y, x));
206
207 return Spherical<Scalar>(lon, lat_gc, radius);
208}
209
210// =============================================================================
211// Spherical (Geocentric) to ECEF Conversion
212// =============================================================================
213
220template <typename Scalar>
221Vec3<Scalar> spherical_to_ecef(const Spherical<Scalar> &geo) {
222 const Scalar sin_lat = janus::sin(geo.lat_gc);
223 const Scalar cos_lat = janus::cos(geo.lat_gc);
224 const Scalar sin_lon = janus::sin(geo.lon);
225 const Scalar cos_lon = janus::cos(geo.lon);
226
227 Vec3<Scalar> r;
228 r(0) = geo.radius * cos_lat * cos_lon;
229 r(1) = geo.radius * cos_lat * sin_lon;
230 r(2) = geo.radius * sin_lat;
231
232 return r;
233}
234
235// =============================================================================
236// Convenience Functions
237// =============================================================================
238
247template <typename Scalar>
248Scalar geodetic_to_geocentric_lat(Scalar lat_gd,
249 const EarthModel &m = EarthModel::WGS84()) {
250 // tan(φ_gc) = (1 - e²) tan(φ_gd)
251 return janus::atan((1.0 - m.e2) * janus::tan(lat_gd));
252}
253
259template <typename Scalar>
260Scalar geocentric_to_geodetic_lat(Scalar lat_gc,
261 const EarthModel &m = EarthModel::WGS84()) {
262 // tan(φ_gd) = tan(φ_gc) / (1 - e²)
263 return janus::atan(janus::tan(lat_gc) / (1.0 - m.e2));
264}
265
274template <typename Scalar>
275Scalar radius_of_curvature_N(Scalar lat,
276 const EarthModel &m = EarthModel::WGS84()) {
277 const Scalar sin_lat = janus::sin(lat);
278 return m.a / janus::sqrt(1.0 - m.e2 * sin_lat * sin_lat);
279}
280
289template <typename Scalar>
290Scalar radius_of_curvature_M(Scalar lat,
291 const EarthModel &m = EarthModel::WGS84()) {
292 const Scalar sin_lat = janus::sin(lat);
293 const Scalar denom = 1.0 - m.e2 * sin_lat * sin_lat;
294 return m.a * (1.0 - m.e2) / janus::pow(denom, 1.5);
295}
296
297} // namespace vulcan
Definition Aerodynamics.hpp:11
Scalar geocentric_to_geodetic_lat(Scalar lat_gc, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:260
Vec3< Scalar > lla_to_ecef(const LLA< Scalar > &lla, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:152
Vec3< Scalar > spherical_to_ecef(const Spherical< Scalar > &geo)
Definition Geodetic.hpp:221
Scalar radius_of_curvature_M(Scalar lat, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:290
Spherical< Scalar > ecef_to_spherical(const Vec3< Scalar > &r)
Definition Geodetic.hpp:190
Scalar geodetic_to_geocentric_lat(Scalar lat_gd, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:248
LLA< Scalar > ecef_to_lla(const Vec3< Scalar > &r, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:78
Scalar radius_of_curvature_N(Scalar lat, const EarthModel &m=EarthModel::WGS84())
Definition Geodetic.hpp:275
Definition EarthModel.hpp:27
static constexpr EarthModel WGS84()
WGS84 reference ellipsoid (most common for GPS/navigation).
Definition EarthModel.hpp:45
Definition Geodetic.hpp:27
Scalar lon
Longitude [rad], positive East, range [-π, π].
Definition Geodetic.hpp:28
LLA()
Definition Geodetic.hpp:32
Scalar alt
Altitude above ellipsoid [m].
Definition Geodetic.hpp:30
Scalar lat
Geodetic latitude [rad], range [-π/2, π/2].
Definition Geodetic.hpp:29
LLA(Scalar lon_, Scalar lat_, Scalar alt_)
Definition Geodetic.hpp:33
Definition Geodetic.hpp:49
Spherical(Scalar lon_, Scalar lat_gc_, Scalar radius_)
Definition Geodetic.hpp:55
Spherical()
Definition Geodetic.hpp:54
Scalar lon
Longitude [rad], positive East, range [-π, π].
Definition Geodetic.hpp:50
Scalar radius
Distance from Earth center [m].
Definition Geodetic.hpp:52
Scalar lat_gc
Geocentric latitude [rad], range [-π/2, π/2].
Definition Geodetic.hpp:51