INSTINCT Code Coverage Report


Directory: src/
File: Navigation/Gravity/Gravity.hpp
Date: 2025-06-02 15:19:59
Exec Total Coverage
Lines: 48 65 73.8%
Functions: 3 6 50.0%
Branches: 38 88 43.2%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 /// @file Gravity.hpp
10 /// @brief Different Gravity Models
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @author M. Maier (marcel.maier@ins.uni-stuttgart.de)
13 /// @date 2020-09-15
14
15 #pragma once
16
17 #include <cstdint>
18 #include <Eigen/Dense>
19 #include <Eigen/Core>
20
21 #include "Navigation/Transformations/CoordinateFrames.hpp"
22 #include "Navigation/Constants.hpp"
23 #include "internal/AssociatedLegendre.hpp"
24 #include "internal/egm96Coeffs.hpp"
25
26 namespace NAV
27 {
28 /// Available Gravitation Models
29 enum class GravitationModel : uint8_t
30 {
31 None, ///< Gravity Model turned off
32 WGS84, ///< World Geodetic System 1984
33 WGS84_Skydel, ///< World Geodetic System 1984 implemented by the Skydel Simulator // FIXME: Remove after Skydel uses the same as Instinct
34 Somigliana, ///< Somigliana gravity model
35 EGM96, ///< Earth Gravitational Model 1996
36 COUNT, ///< Amount of items in the enum
37 };
38
39 /// @brief Converts the enum to a string
40 /// @param[in] gravitationModel Enum value to convert into text
41 /// @return String representation of the enum
42 const char* to_string(GravitationModel gravitationModel);
43
44 /// @brief Shows a ComboBox to select the gravitation model
45 /// @param[in] label Label to show beside the combo box. This has to be a unique id for ImGui.
46 /// @param[in] gravitationModel Reference to the gravitation model to select
47 bool ComboGravitationModel(const char* label, GravitationModel& gravitationModel);
48
49 /// @brief Calculates the gravitation (acceleration due to mass attraction of the Earth) at the WGS84 reference ellipsoid
50 /// using the Somigliana model and makes corrections for altitude
51 /// @param[in] latitude Latitude in [rad]
52 /// @param[in] altitude Altitude in [m]
53 /// @return Gravitation vector in local-navigation frame coordinates in [m/s^2]
54 ///
55 /// @note See S. Gleason (2009) - GNSS Applications and Methods (Chapter 6.2.3.2 - eq. 6.16)
56 template<typename T>
57 2888924 [[nodiscard]] Eigen::Vector3<T> n_calcGravitation_SomiglianaAltitude(const T& latitude, const T& altitude)
58 {
59 // eq 6.16 has a fault in the denominator, it should be a sin^2(latitude)
60 2888924 auto g_0 = 9.7803253359 * (1.0 + 1.931853e-3 * std::pow(std::sin(latitude), 2))
61 2890171 / std::sqrt(1.0 - InsConst::WGS84::e_squared * std::pow(std::sin(latitude), 2));
62
63 // Altitude compensation (Matlab example from Chapter 6_GNSS_INS_1 - glocal.m)
64 2873167 auto k = 1.0
65 5758747 - (2.0 * altitude / InsConst::WGS84::a)
66 2873167 * (1.0 + InsConst::WGS84::f
67 2885580 + (std::pow(InsConst::omega_ie * InsConst::WGS84::a, 2))
68 2873167 * (InsConst::WGS84::b / InsConst::WGS84::MU))
69 2873167 + 3.0 * std::pow(altitude / InsConst::WGS84::a, 2.0);
70
71
1/2
✓ Branch 1 taken 2883533 times.
✗ Branch 2 not taken.
5756700 return { T(0.0), T(0.0), k * g_0 };
72 }
73
74 /// @brief Calculates the gravitation (acceleration due to mass attraction of the Earth) at the WGS84 reference ellipsoid
75 /// using gravity as derived from the gravity potential. However, the north component of the centrifugal acceleration is neglected in order to match the implementation of Skydel's 'ImuPlugin'
76 /// @param[in] latitude Latitude in [rad]
77 /// @param[in] altitude Altitude in [m]
78 /// @return Gravitation vector in local-navigation frame coordinates in [m/s^2]
79 ///
80 /// @note See Skydel API plug-in 'skydel_plugin/source/library/inertial_math/Sources/source/gravity.cpp'
81 template<typename T>
82 [[nodiscard]] Eigen::Vector3<T> n_calcGravitation_WGS84_Skydel(const T& latitude, const T& altitude)
83 {
84 // geocentric latitude determination from geographic latitude
85 auto latitudeGeocentric = std::atan((std::pow(InsConst::WGS84::b, 2.0) / std::pow(InsConst::WGS84::a, 2.0)) * std::tan(latitude));
86 // effective radius determination, i.e. earth radius on WGS84 spheroid plus local altitude --> possible error!! altitude in lla should be added rather than subtracted!
87 auto radiusSpheroid = InsConst::WGS84::a * (1.0 - InsConst::WGS84::f * std::pow(std::sin(latitudeGeocentric), 2.0)) - altitude;
88
89 // Derivation of gravity, i.e. gravitational potential derived after effective radius
90 auto gravitationMagnitude = InsConst::WGS84::MU * std::pow(radiusSpheroid, -2.0)
91 - 3.0 * InsConst::WGS84::MU * InsConst::WGS84::J2 * std::pow(InsConst::WGS84::a, 2.0) * 0.5 * std::pow(radiusSpheroid, -4.0) * (3.0 * std::pow(std::sin(latitudeGeocentric), 2.0) - 1.0)
92 - std::pow(InsConst::omega_ie_Skydel, 2.0) * radiusSpheroid * std::pow(std::cos(latitudeGeocentric), 2.0);
93
94 return { T(0.0), T(0.0), gravitationMagnitude };
95 }
96
97 /// @brief Calculates the gravitation (acceleration due to mass attraction of the Earth) at the WGS84 reference ellipsoid
98 /// using gravity as derived from the gravity potential.
99 /// @param[in] latitude Latitude in [rad]
100 /// @param[in] altitude Altitude in [m]
101 /// @return Gravitation vector in local-navigation frame coordinates in [m/s^2]
102 ///
103 /// @note See 'INS-Projects/INSTINCT/SpecificLiterature/GravityPotentialWGS84' in NC folder (eq. (3) derived after 'r')
104 template<typename T>
105 [[nodiscard]] Eigen::Vector3<T> n_calcGravitation_WGS84(const T& latitude, const T& altitude)
106 {
107 // Geocentric latitude determination from geographic latitude
108 auto latitudeGeocentric = std::atan((std::pow(InsConst::WGS84::b, 2.0) / std::pow(InsConst::WGS84::a, 2.0)) * std::tan(latitude));
109 // Radius of spheroid determination
110 auto radiusSpheroid = InsConst::WGS84::a * (1.0 - InsConst::WGS84::f * std::pow(std::sin(latitudeGeocentric), 2.0)) + altitude;
111
112 // Magnitude of the gravity, i.e. without orientation
113 auto gravitationMagnitude = InsConst::WGS84::MU * std::pow(radiusSpheroid, -2.0)
114 - 3.0 * InsConst::WGS84::MU * InsConst::WGS84::J2 * std::pow(InsConst::WGS84::a, 2.0) * 0.5 * std::pow(radiusSpheroid, -4.0) * (3.0 * std::pow(std::sin(latitudeGeocentric), 2.0) - 1.0)
115 - std::pow(InsConst::omega_ie, 2.0) * radiusSpheroid * std::pow(std::cos(latitudeGeocentric), 2.0);
116
117 return { T(0.0), T(0.0), gravitationMagnitude };
118 }
119
120 /// @brief Calculates the gravitation (acceleration due to mass attraction of the Earth) at the WGS84 reference ellipsoid
121 /// using the EGM96 spherical harmonic model (up to order 10)
122 /// @param[in] lla_position [ϕ, λ, h] Latitude, Longitude, Altitude in [rad, rad, m]
123 /// @param[in] ndegree Degree of the EGM96 (1 <= ndegree <= 10)
124 /// @return Gravitation vector in local-navigation frame coordinates in [m/s^2]
125 ///
126 /// @note See Groves (2013) Chapter 2.4.3 and 'GUT User Guide' (2018) Chapter 7.4
127 template<typename Derived>
128 295054 [[nodiscard]] Eigen::Vector3<typename Derived::Scalar> n_calcGravitation_EGM96(const Eigen::MatrixBase<Derived>& lla_position, size_t ndegree = 10)
129 {
130 using internal::egm96Coeffs;
131 using internal::associatedLegendre;
132
133
1/2
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
295054 auto e_position = trafo::lla2ecef_WGS84(lla_position);
134
135 // Geocentric latitude determination from Groves (2013) - eq. (2.114)
136
5/10
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 147527 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 147527 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 147527 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 147527 times.
✗ Branch 14 not taken.
295054 auto latitudeGeocentric = std::atan(e_position(2) / std::sqrt(e_position(0) * e_position(0) + e_position(1) * e_position(1)));
137
138 // Spherical coordinates
139
6/12
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 147527 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 147527 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 147527 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 147527 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 147527 times.
✗ Branch 17 not taken.
295054 auto radius = std::sqrt(e_position(0) * e_position(0) + e_position(1) * e_position(1) + e_position(2) * e_position(2));
140 295054 auto elevation = M_PI_2 - latitudeGeocentric; // [rad]
141
1/2
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
295054 const auto& azimuth = lla_position(1); // [rad]
142
143 // Gravitation vector in local-navigation frame coordinates in [m/s^2]
144
2/4
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 147527 times.
✗ Branch 5 not taken.
295054 Eigen::Vector3<typename Derived::Scalar> n_gravitation = Eigen::Vector3<typename Derived::Scalar>::Zero();
145
146 295054 typename Derived::Scalar Pnm(0.0);
147 295054 typename Derived::Scalar Pnmd(0.0);
148
149 295054 auto coeffsRows = egm96Coeffs.size();
150
151 // Associated Legendre Polynomial Coefficients 'P' and their derivatives 'Pd'
152
1/2
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
295054 auto [P, Pd] = associatedLegendre(elevation, ndegree);
153
154
2/2
✓ Branch 0 taken 9441728 times.
✓ Branch 1 taken 147527 times.
19178510 for (size_t i = 0; i < coeffsRows; i++) // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult) // FIXME: Wrong error message about Eigen (error: The left operand of '*' is a garbage value)
155 {
156 // Retrieving EGM96 coefficients
157
2/4
✓ Branch 1 taken 9441728 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9441728 times.
✗ Branch 5 not taken.
18883456 auto n = static_cast<int>(egm96Coeffs.at(i).at(0)); // Degree of the Associated Legendre Polynomial
158
2/4
✓ Branch 1 taken 9441728 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9441728 times.
✗ Branch 5 not taken.
18883456 auto m = static_cast<int>(egm96Coeffs.at(i).at(1)); // Order of the Associated Legendre Polynomial
159
2/4
✓ Branch 1 taken 9441728 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9441728 times.
✗ Branch 5 not taken.
18883456 auto C = egm96Coeffs.at(i).at(2);
160
2/4
✓ Branch 1 taken 9441728 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9441728 times.
✗ Branch 5 not taken.
18883456 auto S = egm96Coeffs.at(i).at(3);
161
162
2/2
✓ Branch 0 taken 147527 times.
✓ Branch 1 taken 9294201 times.
18883456 if (static_cast<size_t>(n) == ndegree + 1)
163 {
164 // Ending of the for-loop once the iterated 'n' becomes larger than the user-defined 'ndegree'
165 295054 i = coeffsRows;
166 }
167 else
168 {
169 // Retrieving the parameters of the associated Legendre Polynomials
170
1/2
✓ Branch 1 taken 9294201 times.
✗ Branch 2 not taken.
18588402 Pnm = P(n, m);
171
1/2
✓ Branch 1 taken 9294201 times.
✗ Branch 2 not taken.
18588402 Pnmd = Pd(n, m);
172
173 18588402 auto nd = static_cast<double>(n);
174 18588402 auto md = static_cast<double>(m);
175
176 // Gravity vector from differentiation of the gravity potential in spherical coordinates (see 'GUT User Guide' eq. 7.4.2)
177
1/2
✓ Branch 1 taken 9294201 times.
✗ Branch 2 not taken.
18588402 n_gravitation(0) += std::pow((InsConst::WGS84::a / radius), nd) * (C * std::cos(md * azimuth) + S * std::sin(md * azimuth)) * Pnmd;
178
1/2
✓ Branch 1 taken 9294201 times.
✗ Branch 2 not taken.
18588402 n_gravitation(1) += std::pow((InsConst::WGS84::a / radius), nd) * md * (C * std::sin(md * azimuth) - S * std::cos(md * azimuth)) * Pnm;
179
1/2
✓ Branch 1 taken 9294201 times.
✗ Branch 2 not taken.
18588402 n_gravitation(2) += (nd + 1.0) * std::pow((InsConst::WGS84::a / radius), nd) * (C * std::cos(md * azimuth) + S * std::sin(md * azimuth)) * Pnm;
180 }
181 }
182
183
1/2
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
295054 return { -InsConst::WGS84::MU / (radius * radius) * n_gravitation(0),
184
1/2
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
295054 (1.0 / std::sin(elevation)) * (-InsConst::WGS84::MU / (radius * radius)) * n_gravitation(1),
185
2/4
✓ Branch 1 taken 147527 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 147527 times.
✗ Branch 5 not taken.
590108 InsConst::WGS84::MU / (radius * radius) * (1.0 + n_gravitation(2)) };
186 295054 }
187
188 /// @brief Calculates the gravitation (acceleration due to mass attraction of the Earth)
189 /// @param[in] lla_position [ϕ, λ, h] Latitude, Longitude, Altitude in [rad, rad, m]
190 /// @param[in] gravitationModel Gravitation model to use
191 /// @return Gravitation vector in local-navigation frame coordinates in [m/s^2]
192 template<typename Derived>
193 2979459 [[nodiscard]] Eigen::Vector3<typename Derived::Scalar> n_calcGravitation(const Eigen::MatrixBase<Derived>& lla_position,
194 GravitationModel gravitationModel = GravitationModel::EGM96)
195 {
196 2979459 const typename Derived::Scalar& latitude = lla_position(0);
197 2979175 const typename Derived::Scalar& altitude = lla_position(2);
198
199
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2981665 if (gravitationModel == GravitationModel::WGS84)
200 {
201 return n_calcGravitation_WGS84(latitude, altitude);
202 }
203
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2981665 if (gravitationModel == GravitationModel::WGS84_Skydel) // TODO: This function becomes obsolete, once the ImuStream is deactivated due to the 'InstinctDataStream'
204 {
205 return n_calcGravitation_WGS84_Skydel(latitude, altitude);
206 }
207
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2981665 if (gravitationModel == GravitationModel::Somigliana)
208 {
209 2884235 return n_calcGravitation_SomiglianaAltitude(latitude, altitude);
210 }
211
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
97430 if (gravitationModel == GravitationModel::EGM96)
212 {
213 97530 return n_calcGravitation_EGM96(lla_position);
214 }
215 return Eigen::Vector3<typename Derived::Scalar>::Zero();
216 }
217
218 } // namespace NAV
219