0.3.0
Loading...
Searching...
No Matches
EGM96.cpp
Go to the documentation of this file.
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/// @file EGM96.hpp
6/// @brief EGM96 geoid model
7/// @author T. Topp (topp@ins.uni-stuttgart.de)
8/// @date 2024-07-05
9///
10/// Source code: https://github.com/emericg/EGM96/blob/master/EGM96.c
11/// Copyright (c) 2006 D.Ineiev <ineiev@yahoo.co.uk>
12/// Copyright (c) 2020 Emeric Grange <emeric.grange@gmail.com>
13///
14/// This software is provided 'as-is', without any express or implied warranty.
15/// In no event will the authors be held liable for any damages arising from
16/// the use of this software.
17///
18/// Permission is granted to anyone to use this software for any purpose,
19/// including commercial applications, and to alter it and redistribute it
20/// freely, subject to the following restrictions:
21///
22/// 1. The origin of this software must not be misrepresented; you must not
23/// claim that you wrote the original software. If you use this software
24/// in a product, an acknowledgment in the product documentation would be
25/// appreciated but is not required.
26/// 2. Altered source versions must be plainly marked as such, and must not be
27/// misrepresented as being the original software.
28/// 3. This notice may not be removed or altered from any source distribution.
29
30/*
31 * This program is designed for the calculation of a geoid undulation at a point
32 * whose latitude and longitude is specified.
33 *
34 * This program is designed to be used with the constants of EGM96 and those of
35 * the WGS84(g873) system. The undulation will refer to the WGS84 ellipsoid.
36 *
37 * It's designed to use the potential coefficient model EGM96 and a set of
38 * spherical harmonic coefficients of a correction term.
39 * The correction term is composed of several different components, the primary
40 * one being the conversion of a height anomaly to a geoid undulation.
41 * The principles of this procedure were initially described in the paper:
42 * - use of potential coefficient models for geoid undulation determination using
43 * a spherical harmonic representation of the height anomaly/geoid undulation
44 * difference by R.H. Rapp, Journal of Geodesy, 1996.
45 *
46 * This program is a modification of the program described in the following report:
47 * - a fortran program for the computation of gravimetric quantities from high
48 * degree spherical harmonic expansions, Richard H. Rapp, report 334, Department
49 * of Geodetic Science and Surveying, the Ohio State University, Columbus, 1982
50 */
51/*
52 * Changes:
53 * - Auto-formatting
54 * - Namespace NAV added
55 * - Added "NOLINT" instructions to disable clang-tidy
56 */
57
58#include "EGM96.hpp"
60#include <cmath>
61
62/* ************************************************************************** */
63namespace NAV
64{
65#define _coeffs (65341) //!< Size of correction and harmonic coefficients arrays (361*181)
66#define _nmax (360) //!< Maximum degree and orders of harmonic coefficients.
67#define _361 (361)
68
69/* ************************************************************************** */
70
71double hundu(double p[_coeffs + 1], // NOLINT
72 double sinml[_361 + 1], double cosml[_361 + 1], // NOLINT
73 double gr, double re)
74{
75 // WGS 84 gravitational constant in m³/s² (mass of Earth’s atmosphere included)
76 const double GM = 0.3986004418e15;
77 // WGS 84 datum surface equatorial radius
78 const double ae = 6378137.0;
79
80 double ar = ae / re;
81 double arn = ar;
82 double ac = 0;
83 double a = 0;
84
85 unsigned k = 3;
86 for (unsigned n = 2; n <= _nmax; n++)
87 {
88 arn *= ar;
89 k++;
90 double sum = p[k] * Geoid::EGM96::internal::egm96_data[k][2]; // NOLINT
91 double sumc = p[k] * Geoid::EGM96::internal::egm96_data[k][0]; // NOLINT
92
93 for (unsigned m = 1; m <= n; m++)
94 {
95 k++;
96 double tempc = Geoid::EGM96::internal::egm96_data[k][0] * cosml[m] + Geoid::EGM96::internal::egm96_data[k][1] * sinml[m]; // NOLINT
97 double temp = Geoid::EGM96::internal::egm96_data[k][2] * cosml[m] + Geoid::EGM96::internal::egm96_data[k][3] * sinml[m]; // NOLINT
98 sumc += p[k] * tempc;
99 sum += p[k] * temp;
100 }
101 ac += sumc;
102 a += sum * arn;
103 }
105
106 // Add haco = ac/100 to convert height anomaly on the ellipsoid to the undulation
107 // Add -0.53m to make undulation refer to the WGS84 ellipsoid
108
109 return ((a * GM) / (gr * re)) + (ac / 100.0) - 0.53;
110}
111
112void dscml(double rlon, double sinml[_361 + 1], double cosml[_361 + 1]) // NOLINT
113{
114 double a = sin(rlon);
115 double b = cos(rlon);
116
117 sinml[1] = a;
118 cosml[1] = b;
119 sinml[2] = 2 * b * a;
120 cosml[2] = 2 * b * b - 1;
121
122 for (unsigned m = 3; m <= _nmax; m++)
123 {
124 sinml[m] = 2 * b * sinml[m - 1] - sinml[m - 2];
125 cosml[m] = 2 * b * cosml[m - 1] - cosml[m - 2];
126 }
127}
128
129/*!
130 * \param m : order.
131 * \param theta : Colatitude (radians).
132 * \param rleg : Normalized legendre function.
133 *
134 * This subroutine computes all normalized legendre function in 'rleg'.
135 * The dimensions of array 'rleg' must be at least equal to nmax+1.
136 * All calculations are in double precision.
137 *
138 * Original programmer: Oscar L. Colombo, Dept. of Geodetic Science the Ohio State University, August 1980.
139 * ineiev: I removed the derivatives, for they are never computed here.
140 */
141void legfdn(unsigned m, double theta, double rleg[_361 + 1]) // NOLINT
142{
143 static double drts[1301], dirt[1301], cothet, sithet, rlnn[_361 + 1]; // NOLINT
144 static int ir; // TODO 'ir' must be set to zero before the first call to this sub.
145
146 unsigned nmax1 = _nmax + 1;
147 unsigned nmax2p = (2 * _nmax) + 1;
148 unsigned m1 = m + 1;
149 unsigned m2 = m + 2;
150 unsigned m3 = m + 3;
151 unsigned n, n1, n2; // NOLINT
152
153 if (!ir)
154 {
155 ir = 1;
156 for (n = 1; n <= nmax2p; n++)
157 {
158 drts[n] = sqrt(n); // NOLINT
159 dirt[n] = 1 / drts[n]; // NOLINT
160 }
161 }
162
163 cothet = cos(theta);
164 sithet = sin(theta);
165
166 // compute the legendre functions
167 rlnn[1] = 1;
168 rlnn[2] = sithet * drts[3];
169 for (n1 = 3; n1 <= m1; n1++)
170 {
171 n = n1 - 1;
172 n2 = 2 * n;
173 rlnn[n1] = drts[n2 + 1] * dirt[n2] * sithet * rlnn[n]; // NOLINT
174 }
175
176 switch (m) // NOLINT
177 {
178 case 1:
179 rleg[2] = rlnn[2];
180 rleg[3] = drts[5] * cothet * rleg[2];
181 break;
182 case 0:
183 rleg[1] = 1;
184 rleg[2] = cothet * drts[3];
185 break;
186 }
187 rleg[m1] = rlnn[m1]; // NOLINT
188
189 if (m2 <= nmax1)
190 {
191 rleg[m2] = drts[m1 * 2 + 1] * cothet * rleg[m1]; // NOLINT
192 if (m3 <= nmax1)
193 {
194 for (n1 = m3; n1 <= nmax1; n1++)
195 {
196 n = n1 - 1;
197 if ((!m && n < 2) || (m == 1 && n < 3)) continue; // NOLINT
198 n2 = 2 * n;
199 rleg[n1] = drts[n2 + 1] * dirt[n + m] * dirt[n - m] * (drts[n2 - 1] * cothet * rleg[n1 - 1] - drts[n + m - 1] * drts[n - m - 1] * dirt[n2 - 3] * rleg[n1 - 2]); // NOLINT
200 }
201 }
202 }
203}
204
205namespace
206{
207
208/*!
209 * \param lat : Latitude in radians.
210 * \param lon : Longitude in radians.
211 * \param re : Geocentric radius.
212 * \param rlat : Geocentric latitude.
213 * \param gr : Normal gravity (m/sec²).
214 *
215 * This subroutine computes geocentric distance to the point, the geocentric
216 * latitude, and an approximate value of normal gravity at the point based the
217 * constants of the WGS84(g873) system are used.
218 */
219void radgra(double lat, double lon, double* rlat, double* gr, double* re)
220{
221 const double a = 6378137.0;
222 const double e2 = 0.00669437999013;
223 const double geqt = 9.7803253359;
224 const double k = 0.00193185265246;
225 double t1 = sin(lat) * sin(lat);
226 double n = a / sqrt(1.0 - (e2 * t1));
227 double t2 = n * cos(lat);
228 double x = t2 * cos(lon);
229 double y = t2 * sin(lon);
230 double z = (n * (1 - e2)) * sin(lat);
231
232 *re = sqrt((x * x) + (y * y) + (z * z)); // compute the geocentric radius
233 *rlat = atan(z / sqrt((x * x) + (y * y))); // compute the geocentric latitude
234 *gr = geqt * (1 + (k * t1)) / sqrt(1 - (e2 * t1)); // compute normal gravity (m/sec²)
235}
236
237/*!
238 * \brief Compute the geoid undulation from the EGM96 potential coefficient model, for a given latitude and longitude.
239 * \param lat : Latitude in radians.
240 * \param lon : Longitude in radians.
241 * \return The geoid undulation / altitude offset (in meters).
242 */
243double undulation(double lat, double lon)
244{
245 double p[_coeffs + 1], sinml[_361 + 1], cosml[_361 + 1], rleg[_361 + 1]; // NOLINT
246
247 double rlat, gr, re; // NOLINT
248 unsigned nmax1 = _nmax + 1;
249
250 // compute the geocentric latitude, geocentric radius, normal gravity
251 radgra(lat, lon, &rlat, &gr, &re);
252 rlat = (M_PI / 2) - rlat;
253
254 for (unsigned j = 1; j <= nmax1; j++)
255 {
256 unsigned m = j - 1;
257 legfdn(m, rlat, rleg);
258 for (unsigned i = j; i <= nmax1; i++)
259 {
260 p[(((i - 1) * i) / 2) + m + 1] = rleg[i]; // NOLINT
261 }
262 }
263 dscml(lon, sinml, cosml);
264
265 return hundu(p, sinml, cosml, gr, re);
266}
267
268} // namespace
269
270/* ************************************************************************** */
271
272double egm96_compute_altitude_offset(double lat, double lon)
273{
274 return undulation(lat, lon);
275}
276
277/* ************************************************************************** */
278} // namespace NAV
#define _coeffs
Size of correction and harmonic coefficients arrays (361*181)
Definition EGM96.cpp:65
#define _361
Definition EGM96.cpp:67
#define _nmax
Maximum degree and orders of harmonic coefficients.
Definition EGM96.cpp:66
EGM96 geoid model.
static const double egm96_data[65342][4]
Precomputed EGM96 correction and harmonic coefficients.
double egm96_compute_altitude_offset(double lat, double lon)
Compute the geoid undulation from the EGM96 potential coefficient model to The WGS84 ellipsoid.
Definition EGM96.cpp:272
double hundu(double p[_coeffs+1], double sinml[_361+1], double cosml[_361+1], double gr, double re)
Definition EGM96.cpp:71
void dscml(double rlon, double sinml[_361+1], double cosml[_361+1])
Definition EGM96.cpp:112
void legfdn(unsigned m, double theta, double rleg[_361+1])
Definition EGM96.cpp:141