Merged in f5soh/librepilot/update_credits (pull request #529)
[librepilot.git] / ground / gcs / src / libs / utils / worldmagmodel.cpp
blob6b31a759b49162c460d263729124feb797504e20
1 /**
2 ******************************************************************************
4 * @file worldmagmodel.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2019.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * @brief Utilities to find the location of openpilot GCS files:
8 * - Plugins Share directory path
10 * @brief Source file for the World Magnetic Model
11 * This is a port of code available from the US NOAA.
13 * The hard coded coefficients should be valid until 2020.
15 * Updated coeffs from ..
16 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_ddownload.shtml
18 * NASA C source code ..
19 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_wdownload.shtml
21 * Major changes include:
22 * - No geoid model (altitude must be geodetic WGS-84)
23 * - Floating point calculation (not double precision)
24 * - Hard coded coefficients for model
25 * - Elimination of user interface
26 * - Elimination of dynamic memory allocation
28 * @see The GNU Public License (GPL) Version 3
30 *****************************************************************************/
32 * This program is free software; you can redistribute it and/or modify
33 * it under the terms of the GNU General Public License as published by
34 * the Free Software Foundation; either version 3 of the License, or
35 * (at your option) any later version.
37 * This program is distributed in the hope that it will be useful, but
38 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
39 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
40 * for more details.
42 * You should have received a copy of the GNU General Public License along
43 * with this program; if not, write to the Free Software Foundation, Inc.,
44 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
47 #include "worldmagmodel.h"
49 #include <stdint.h>
50 #include <QDebug>
51 #include <math.h>
53 #define RAD2DEG(rad) ((rad) * (180.0 / M_PI))
54 #define DEG2RAD(deg) ((deg) * (M_PI / 180.0))
56 // updated coeffs available from http://www.ngdc.noaa.gov/geomag/WMM/wmm_ddownload.shtml
57 const double CoeffFile[91][6] = {
58 { 0, 0, 0, 0, 0, 0 },
59 { 1, 0, -29438.2, 0.0, 7.0, 0.0 },
60 { 1, 1, -1493.5, 4796.3, 9.0, -30.2 },
61 { 2, 0, -2444.5, 0.0, -11.0, 0.0 },
62 { 2, 1, 3014.7, -2842.4, -6.2, -29.6 },
63 { 2, 2, 1679.0, -638.8, 0.3, -17.3 },
64 { 3, 0, 1351.8, 0.0, 2.4, 0.0 },
65 { 3, 1, -2351.6, -113.7, -5.7, 6.5 },
66 { 3, 2, 1223.6, 246.5, 2.0, -0.8 },
67 { 3, 3, 582.3, -537.4, -11.0, -2.0 },
68 { 4, 0, 907.5, 0.0, -0.8, 0.0 },
69 { 4, 1, 814.8, 283.3, -0.9, -0.4 },
70 { 4, 2, 117.8, -188.6, -6.5, 5.8 },
71 { 4, 3, -335.6, 180.7, 5.2, 3.8 },
72 { 4, 4, 69.7, -330.0, -4.0, -3.5 },
73 { 5, 0, -232.9, 0.0, -0.3, 0.0 },
74 { 5, 1, 360.1, 46.9, 0.6, 0.2 },
75 { 5, 2, 191.7, 196.5, -0.8, 2.3 },
76 { 5, 3, -141.3, -119.9, 0.1, -0.0 },
77 { 5, 4, -157.2, 16.0, 1.2, 3.3 },
78 { 5, 5, 7.7, 100.6, 1.4, -0.6 },
79 { 6, 0, 69.4, 0.0, -0.8, 0.0 },
80 { 6, 1, 67.7, -20.1, -0.5, 0.3 },
81 { 6, 2, 72.3, 32.8, -0.1, -1.5 },
82 { 6, 3, -129.1, 59.1, 1.6, -1.2 },
83 { 6, 4, -28.4, -67.1, -1.6, 0.4 },
84 { 6, 5, 13.6, 8.1, 0.0, 0.2 },
85 { 6, 6, -70.3, 61.9, 1.2, 1.3 },
86 { 7, 0, 81.7, 0.0, -0.3, 0.0 },
87 { 7, 1, -75.9, -54.3, -0.2, 0.6 },
88 { 7, 2, -7.1, -19.5, -0.3, 0.5 },
89 { 7, 3, 52.2, 6.0, 0.9, -0.8 },
90 { 7, 4, 15.0, 24.5, 0.1, -0.2 },
91 { 7, 5, 9.1, 3.5, -0.6, -1.1 },
92 { 7, 6, -3.0, -27.7, -0.9, 0.1 },
93 { 7, 7, 5.9, -2.9, 0.7, 0.2 },
94 { 8, 0, 24.2, 0.0, -0.1, 0.0 },
95 { 8, 1, 8.9, 10.1, 0.2, -0.4 },
96 { 8, 2, -16.9, -18.3, -0.2, 0.6 },
97 { 8, 3, -3.1, 13.3, 0.5, -0.1 },
98 { 8, 4, -20.7, -14.5, -0.1, 0.6 },
99 { 8, 5, 13.3, 16.2, 0.4, -0.2 },
100 { 8, 6, 11.6, 6.0, 0.4, -0.5 },
101 { 8, 7, -16.3, -9.2, -0.1, 0.5 },
102 { 8, 8, -2.1, 2.4, 0.4, 0.1 },
103 { 9, 0, 5.5, 0.0, -0.1, 0.0 },
104 { 9, 1, 8.8, -21.8, -0.1, -0.3 },
105 { 9, 2, 3.0, 10.7, -0.0, 0.1 },
106 { 9, 3, -3.2, 11.8, 0.4, -0.4 },
107 { 9, 4, 0.6, -6.8, -0.4, 0.3 },
108 { 9, 5, -13.2, -6.9, 0.0, 0.1 },
109 { 9, 6, -0.1, 7.9, 0.3, -0.0 },
110 { 9, 7, 8.7, 1.0, 0.0, -0.1 },
111 { 9, 8, -9.1, -3.9, -0.0, 0.5 },
112 { 9, 9, -10.4, 8.5, -0.3, 0.2 },
113 { 10, 0, -2.0, 0.0, 0.0, 0.0 },
114 { 10, 1, -6.1, 3.3, -0.0, 0.0 },
115 { 10, 2, 0.2, -0.4, -0.1, 0.1 },
116 { 10, 3, 0.6, 4.6, 0.2, -0.2 },
117 { 10, 4, -0.5, 4.4, -0.1, 0.1 },
118 { 10, 5, 1.8, -7.9, -0.2, -0.1 },
119 { 10, 6, -0.7, -0.6, -0.0, 0.1 },
120 { 10, 7, 2.2, -4.2, -0.1, -0.0 },
121 { 10, 8, 2.4, -2.9, -0.2, -0.1 },
122 { 10, 9, -1.8, -1.1, -0.1, 0.2 },
123 { 10, 10, -3.6, -8.8, -0.0, -0.0 },
124 { 11, 0, 3.0, 0.0, -0.0, 0.0 },
125 { 11, 1, -1.4, -0.0, 0.0, 0.0 },
126 { 11, 2, -2.3, 2.1, -0.0, 0.1 },
127 { 11, 3, 2.1, -0.6, 0.0, 0.0 },
128 { 11, 4, -0.8, -1.1, -0.0, 0.1 },
129 { 11, 5, 0.6, 0.7, -0.1, -0.0 },
130 { 11, 6, -0.7, -0.2, 0.0, -0.0 },
131 { 11, 7, 0.1, -2.1, -0.0, 0.1 },
132 { 11, 8, 1.7, -1.5, -0.0, -0.0 },
133 { 11, 9, -0.2, -2.6, -0.1, -0.1 },
134 { 11, 10, 0.4, -2.0, -0.0, -0.0 },
135 { 11, 11, 3.5, -2.3, -0.1, -0.1 },
136 { 12, 0, -2.0, 0.0, 0.0, 0.0 },
137 { 12, 1, -0.1, -1.0, 0.0, -0.0 },
138 { 12, 2, 0.5, 0.3, -0.0, 0.0 },
139 { 12, 3, 1.2, 1.8, 0.0, -0.1 },
140 { 12, 4, -0.9, -2.2, -0.1, 0.1 },
141 { 12, 5, 0.9, 0.3, -0.0, -0.0 },
142 { 12, 6, 0.1, 0.7, 0.0, 0.0 },
143 { 12, 7, 0.6, -0.1, -0.0, -0.0 },
144 { 12, 8, -0.4, 0.3, 0.0, 0.0 },
145 { 12, 9, -0.5, 0.2, -0.0, 0.0 },
146 { 12, 10, 0.2, -0.9, -0.0, -0.0 },
147 { 12, 11, -0.9, -0.2, -0.0, 0.0 },
148 { 12, 12, -0.0, 0.8, -0.1, -0.1 }
151 namespace Utils {
152 WorldMagModel::WorldMagModel()
154 Initialize();
158 * @brief
159 * @param[in] LLA The longitude-latitude-altitude coordinate to compute the magnetic field at
160 * @param[in] Month
161 * @param[in] Day
162 * @param[in] Year
163 * @param[out] Be The resulting magnetic field at that location and time in [mGau](?)
164 * @returns 0 if successful, negative otherwise.
166 int WorldMagModel::GetMagVector(double LLA[3], int Month, int Day, int Year, double Be[3])
168 double Lat = LLA[0];
169 double Lon = LLA[1];
170 double AltEllipsoid = LLA[2] / 1000.0; // convert to km
172 // ***********
173 // range check supplied params
175 if (Lat < -90) {
176 return -1; // error
178 if (Lat > 90) {
179 return -2; // error
181 if (Lon < -180) {
182 return -3; // error
184 if (Lon > 180) {
185 return -4; // error
187 // ***********
189 WMMtype_CoordSpherical CoordSpherical;
190 WMMtype_CoordGeodetic CoordGeodetic;
191 WMMtype_GeoMagneticElements GeoMagneticElements;
193 Initialize();
195 CoordGeodetic.lambda = Lon;
196 CoordGeodetic.phi = Lat;
197 CoordGeodetic.HeightAboveEllipsoid = AltEllipsoid;
199 // Convert from geodeitic to Spherical Equations: 17-18, WMM Technical report
200 GeodeticToSpherical(&CoordGeodetic, &CoordSpherical);
202 if (DateToYear(Month, Day, Year) < 0) {
203 return -5; // error
205 // Compute the geoMagnetic field elements and their time change
206 if (Geomag(&CoordSpherical, &CoordGeodetic, &GeoMagneticElements) < 0) {
207 return -6; // error
209 // set the returned values
210 Be[0] = GeoMagneticElements.X * 1e-2;
211 Be[1] = GeoMagneticElements.Y * 1e-2;
212 Be[2] = GeoMagneticElements.Z * 1e-2;
214 // ***********
216 return 0; // OK
219 void WorldMagModel::Initialize()
220 { // Sets default values for WMM subroutines.
221 // UPDATES : Ellip and MagneticModel
223 // Sets WGS-84 parameters
224 Ellip.a = 6378.137; // semi-major axis of the ellipsoid in km
225 Ellip.b = 6356.7523142; // semi-minor axis of the ellipsoid in km
226 Ellip.fla = 1 / 298.257223563; // flattening
227 Ellip.eps = sqrt(1 - (Ellip.b * Ellip.b) / (Ellip.a * Ellip.a)); // first eccentricity
228 Ellip.epssq = (Ellip.eps * Ellip.eps); // first eccentricity squared
229 Ellip.re = 6371.2; // Earth's radius in km
231 // Sets Magnetic Model parameters
232 MagneticModel.nMax = WMM_MAX_MODEL_DEGREES;
233 MagneticModel.nMaxSecVar = WMM_MAX_SECULAR_VARIATION_MODEL_DEGREES;
234 MagneticModel.SecularVariationUsed = 0;
236 // Really, Really needs to be read from a file - out of date in 2020 at latest
237 MagneticModel.EditionDate = 5.7863328170559505e-307;
238 MagneticModel.epoch = 2015.0;
239 sprintf(MagneticModel.ModelName, "WMM-2015v2");
243 int WorldMagModel::Geomag(WMMtype_CoordSpherical *CoordSpherical, WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_GeoMagneticElements *GeoMagneticElements)
245 The main subroutine that calls a sequence of WMM sub-functions to calculate the magnetic field elements for a single point.
246 The function expects the model coefficients and point coordinates as input and returns the magnetic field elements and
247 their rate of change. Though, this subroutine can be called successively to calculate a time series, profile or grid
248 of magnetic field, these are better achieved by the subroutine WMM_Grid.
250 INPUT: Ellip
251 CoordSpherical
252 CoordGeodetic
253 TimedMagneticModel
255 OUTPUT : GeoMagneticElements
258 WMMtype_MagneticResults MagneticResultsSph;
259 WMMtype_MagneticResults MagneticResultsGeo;
260 WMMtype_MagneticResults MagneticResultsSphVar;
261 WMMtype_MagneticResults MagneticResultsGeoVar;
262 WMMtype_LegendreFunction LegendreFunction;
263 WMMtype_SphericalHarmonicVariables SphVariables;
265 // Compute Spherical Harmonic variables
266 ComputeSphericalHarmonicVariables(CoordSpherical, MagneticModel.nMax, &SphVariables);
268 // Compute ALF
269 if (AssociatedLegendreFunction(CoordSpherical, MagneticModel.nMax, &LegendreFunction) < 0) {
270 return -1; // error
272 // Accumulate the spherical harmonic coefficients
273 Summation(&LegendreFunction, &SphVariables, CoordSpherical, &MagneticResultsSph);
275 // Sum the Secular Variation Coefficients
276 SecVarSummation(&LegendreFunction, &SphVariables, CoordSpherical, &MagneticResultsSphVar);
278 // Map the computed Magnetic fields to Geodeitic coordinates
279 RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSph, &MagneticResultsGeo);
281 // Map the secular variation field components to Geodetic coordinates
282 RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSphVar, &MagneticResultsGeoVar);
284 // Calculate the Geomagnetic elements, Equation 18 , WMM Technical report
285 CalculateGeoMagneticElements(&MagneticResultsGeo, GeoMagneticElements);
287 // Calculate the secular variation of each of the Geomagnetic elements
288 CalculateSecularVariation(&MagneticResultsGeoVar, GeoMagneticElements);
290 return 0; // OK
293 void WorldMagModel::ComputeSphericalHarmonicVariables(WMMtype_CoordSpherical *CoordSpherical, int nMax, WMMtype_SphericalHarmonicVariables *SphVariables)
295 /* Computes Spherical variables
296 Variables computed are (a/r)^(n+2), cos_m(lamda) and sin_m(lambda) for spherical harmonic
297 summations. (Equations 10-12 in the WMM Technical Report)
298 INPUT Ellip data structure with the following elements
299 float a; semi-major axis of the ellipsoid
300 float b; semi-minor axis of the ellipsoid
301 float fla; flattening
302 float epssq; first eccentricity squared
303 float eps; first eccentricity
304 float re; mean radius of ellipsoid
305 CoordSpherical A data structure with the following elements
306 float lambda; ( longitude)
307 float phig; ( geocentric latitude )
308 float r; ( distance from the center of the ellipsoid)
309 nMax integer ( Maxumum degree of spherical harmonic secular model)\
311 OUTPUT SphVariables Pointer to the data structure with the following elements
312 float RelativeRadiusPower[WMM_MAX_MODEL_DEGREES+1]; [earth_reference_radius_km sph. radius ]^n
313 float cos_mlambda[WMM_MAX_MODEL_DEGREES+1]; cp(m) - cosine of (mspherical coord. longitude)
314 float sin_mlambda[WMM_MAX_MODEL_DEGREES+1]; sp(m) - sine of (mspherical coord. longitude)
316 double cos_lambda = cos(DEG2RAD(CoordSpherical->lambda));
317 double sin_lambda = sin(DEG2RAD(CoordSpherical->lambda));
319 /* for n = 0 ... model_order, compute (Radius of Earth / Spherica radius r)^(n+2)
320 for n 1..nMax-1 (this is much faster than calling pow MAX_N+1 times). */
322 SphVariables->RelativeRadiusPower[0] = (Ellip.re / CoordSpherical->r) * (Ellip.re / CoordSpherical->r);
323 for (int n = 1; n <= nMax; n++) {
324 SphVariables->RelativeRadiusPower[n] = SphVariables->RelativeRadiusPower[n - 1] * (Ellip.re / CoordSpherical->r);
328 Compute cos(m*lambda), sin(m*lambda) for m = 0 ... nMax
329 cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)
330 sin(a + b) = cos(a)*sin(b) + sin(a)*cos(b)
332 SphVariables->cos_mlambda[0] = 1.0;
333 SphVariables->sin_mlambda[0] = 0.0;
335 SphVariables->cos_mlambda[1] = cos_lambda;
336 SphVariables->sin_mlambda[1] = sin_lambda;
337 for (int m = 2; m <= nMax; m++) {
338 SphVariables->cos_mlambda[m] = SphVariables->cos_mlambda[m - 1] * cos_lambda - SphVariables->sin_mlambda[m - 1] * sin_lambda;
339 SphVariables->sin_mlambda[m] = SphVariables->cos_mlambda[m - 1] * sin_lambda + SphVariables->sin_mlambda[m - 1] * cos_lambda;
343 int WorldMagModel::AssociatedLegendreFunction(WMMtype_CoordSpherical *CoordSpherical, int nMax, WMMtype_LegendreFunction *LegendreFunction)
345 /* Computes all of the Schmidt-semi normalized associated Legendre
346 functions up to degree nMax. If nMax <= 16, function WMM_PcupLow is used.
347 Otherwise WMM_PcupHigh is called.
348 INPUT CoordSpherical A data structure with the following elements
349 float lambda; ( longitude)
350 float phig; ( geocentric latitude )
351 float r; ( distance from the center of the ellipsoid)
352 nMax integer ( Maxumum degree of spherical harmonic secular model)
353 LegendreFunction Pointer to data structure with the following elements
354 float *Pcup; ( pointer to store Legendre Function )
355 float *dPcup; ( pointer to store Derivative of Lagendre function )
357 OUTPUT LegendreFunction Calculated Legendre variables in the data structure
360 double sin_phi = sin(DEG2RAD(CoordSpherical->phig)); // sin (geocentric latitude)
362 if (nMax <= 16 || (1 - fabs(sin_phi)) < 1.0e-10) { /* If nMax is less tha 16 or at the poles */
363 PcupLow(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax);
364 } else {
365 if (PcupHigh(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax) < 0) {
366 return -1; // error
370 return 0; // OK
373 void WorldMagModel::Summation(WMMtype_LegendreFunction *LegendreFunction,
374 WMMtype_SphericalHarmonicVariables *SphVariables,
375 WMMtype_CoordSpherical *CoordSpherical,
376 WMMtype_MagneticResults *MagneticResults)
378 /* Computes Geomagnetic Field Elements X, Y and Z in Spherical coordinate system using spherical harmonic summation.
380 The vector Magnetic field is given by -grad V, where V is Geomagnetic scalar potential
381 The gradient in spherical coordinates is given by:
383 dV ^ 1 dV ^ 1 dV ^
384 grad V = -- r + - -- t + -------- -- p
385 dr r dt r sin(t) dp
387 INPUT : LegendreFunction
388 MagneticModel
389 SphVariables
390 CoordSpherical
391 OUTPUT : MagneticResults
393 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
396 MagneticResults->Bz = 0.0;
397 MagneticResults->By = 0.0;
398 MagneticResults->Bx = 0.0;
400 for (int n = 1; n <= MagneticModel.nMax; n++) {
401 for (int m = 0; m <= n; m++) {
402 int index = (n * (n + 1) / 2 + m);
404 /* nMax (n+2) n m m m
405 Bz = -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi))
406 n=1 m=0 n n n */
407 /* Equation 12 in the WMM Technical report. Derivative with respect to radius.*/
408 MagneticResults->Bz -=
409 SphVariables->RelativeRadiusPower[n] *
410 (get_main_field_coeff_g(index) *
411 SphVariables->cos_mlambda[m] + get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
412 * (double)(n + 1) * LegendreFunction->Pcup[index];
414 /* 1 nMax (n+2) n m m m
415 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
416 n=1 m=0 n n n */
417 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
418 MagneticResults->By +=
419 SphVariables->RelativeRadiusPower[n] *
420 (get_main_field_coeff_g(index) *
421 SphVariables->sin_mlambda[m] - get_main_field_coeff_h(index) * SphVariables->cos_mlambda[m])
422 * (double)(m) * LegendreFunction->Pcup[index];
423 /* nMax (n+2) n m m m
424 Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
425 n=1 m=0 n n n */
426 /* Equation 10 in the WMM Technical report. Derivative with respect to latitude, divided by radius. */
428 MagneticResults->Bx -=
429 SphVariables->RelativeRadiusPower[n] *
430 (get_main_field_coeff_g(index) *
431 SphVariables->cos_mlambda[m] + get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
432 * LegendreFunction->dPcup[index];
436 double cos_phi = cos(DEG2RAD(CoordSpherical->phig));
437 if (fabs(cos_phi) > 1.0e-10) {
438 MagneticResults->By = MagneticResults->By / cos_phi;
439 } else {
440 /* Special calculation for component - By - at Geographic poles.
441 * If the user wants to avoid using this function, please make sure that
442 * the latitude is not exactly +/-90. An option is to make use the function
443 * WMM_CheckGeographicPoles.
445 SummationSpecial(SphVariables, CoordSpherical, MagneticResults);
449 void WorldMagModel::SecVarSummation(WMMtype_LegendreFunction *LegendreFunction,
450 WMMtype_SphericalHarmonicVariables *SphVariables,
451 WMMtype_CoordSpherical *CoordSpherical,
452 WMMtype_MagneticResults *MagneticResults)
454 /*This Function sums the secular variation coefficients to get the secular variation of the Magnetic vector.
455 INPUT : LegendreFunction
456 MagneticModel
457 SphVariables
458 CoordSpherical
459 OUTPUT : MagneticResults
462 MagneticModel.SecularVariationUsed = true;
464 MagneticResults->Bz = 0.0;
465 MagneticResults->By = 0.0;
466 MagneticResults->Bx = 0.0;
468 for (int n = 1; n <= MagneticModel.nMaxSecVar; n++) {
469 for (int m = 0; m <= n; m++) {
470 int index = (n * (n + 1) / 2 + m);
472 /* nMax (n+2) n m m m
473 Bz = -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi))
474 n=1 m=0 n n n */
475 /* Derivative with respect to radius.*/
476 MagneticResults->Bz -=
477 SphVariables->RelativeRadiusPower[n] *
478 (get_secular_var_coeff_g(index) *
479 SphVariables->cos_mlambda[m] + get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
480 * (double)(n + 1) * LegendreFunction->Pcup[index];
482 /* 1 nMax (n+2) n m m m
483 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
484 n=1 m=0 n n n */
485 /* Derivative with respect to longitude, divided by radius. */
486 MagneticResults->By +=
487 SphVariables->RelativeRadiusPower[n] *
488 (get_secular_var_coeff_g(index) *
489 SphVariables->sin_mlambda[m] - get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[m])
490 * (double)(m) * LegendreFunction->Pcup[index];
491 /* nMax (n+2) n m m m
492 Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
493 n=1 m=0 n n n */
494 /* Derivative with respect to latitude, divided by radius. */
496 MagneticResults->Bx -=
497 SphVariables->RelativeRadiusPower[n] *
498 (get_secular_var_coeff_g(index) *
499 SphVariables->cos_mlambda[m] + get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
500 * LegendreFunction->dPcup[index];
504 double cos_phi = cos(DEG2RAD(CoordSpherical->phig));
505 if (fabs(cos_phi) > 1.0e-10) {
506 MagneticResults->By = MagneticResults->By / cos_phi;
507 } else { /* Special calculation for component By at Geographic poles */
508 SecVarSummationSpecial(SphVariables, CoordSpherical, MagneticResults);
512 void WorldMagModel::RotateMagneticVector(WMMtype_CoordSpherical *CoordSpherical,
513 WMMtype_CoordGeodetic *CoordGeodetic,
514 WMMtype_MagneticResults *MagneticResultsSph,
515 WMMtype_MagneticResults *MagneticResultsGeo)
517 /* Rotate the Magnetic Vectors to Geodetic Coordinates
518 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
519 Equation 16, WMM Technical report
521 INPUT : CoordSpherical : Data structure WMMtype_CoordSpherical with the following elements
522 float lambda; ( longitude)
523 float phig; ( geocentric latitude )
524 float r; ( distance from the center of the ellipsoid)
526 CoordGeodetic : Data structure WMMtype_CoordGeodetic with the following elements
527 float lambda; (longitude)
528 float phi; ( geodetic latitude)
529 float HeightAboveEllipsoid; (height above the ellipsoid (HaE) )
530 float HeightAboveGeoid;(height above the Geoid )
532 MagneticResultsSph : Data structure WMMtype_MagneticResults with the following elements
533 float Bx; North
534 float By; East
535 float Bz; Down
537 OUTPUT: MagneticResultsGeo Pointer to the data structure WMMtype_MagneticResults, with the following elements
538 float Bx; North
539 float By; East
540 float Bz; Down
543 /* Difference between the spherical and Geodetic latitudes */
544 double Psi = DEG2RAD(CoordSpherical->phig - CoordGeodetic->phi);
546 /* Rotate spherical field components to the Geodeitic system */
547 MagneticResultsGeo->Bz = MagneticResultsSph->Bx * sin(Psi) + MagneticResultsSph->Bz * cos(Psi);
548 MagneticResultsGeo->Bx = MagneticResultsSph->Bx * cos(Psi) - MagneticResultsSph->Bz * sin(Psi);
549 MagneticResultsGeo->By = MagneticResultsSph->By;
552 void WorldMagModel::CalculateGeoMagneticElements(WMMtype_MagneticResults *MagneticResultsGeo, WMMtype_GeoMagneticElements *GeoMagneticElements)
554 /* Calculate all the Geomagnetic elements from X,Y and Z components
555 INPUT MagneticResultsGeo Pointer to data structure with the following elements
556 float Bx; ( North )
557 float By; ( East )
558 float Bz; ( Down )
559 OUTPUT GeoMagneticElements Pointer to data structure with the following elements
560 float Decl; (Angle between the magnetic field vector and true north, positive east)
561 float Incl; Angle between the magnetic field vector and the horizontal plane, positive down
562 float F; Magnetic Field Strength
563 float H; Horizontal Magnetic Field Strength
564 float X; Northern component of the magnetic field vector
565 float Y; Eastern component of the magnetic field vector
566 float Z; Downward component of the magnetic field vector
569 GeoMagneticElements->X = MagneticResultsGeo->Bx;
570 GeoMagneticElements->Y = MagneticResultsGeo->By;
571 GeoMagneticElements->Z = MagneticResultsGeo->Bz;
573 GeoMagneticElements->H = sqrt(MagneticResultsGeo->Bx * MagneticResultsGeo->Bx + MagneticResultsGeo->By * MagneticResultsGeo->By);
574 GeoMagneticElements->F = sqrt(GeoMagneticElements->H * GeoMagneticElements->H + MagneticResultsGeo->Bz * MagneticResultsGeo->Bz);
575 GeoMagneticElements->Decl = RAD2DEG(atan2(GeoMagneticElements->Y, GeoMagneticElements->X));
576 GeoMagneticElements->Incl = RAD2DEG(atan2(GeoMagneticElements->Z, GeoMagneticElements->H));
579 void WorldMagModel::CalculateSecularVariation(WMMtype_MagneticResults *MagneticVariation, WMMtype_GeoMagneticElements *MagneticElements)
581 /* This takes the Magnetic Variation in x, y, and z and uses it to calculate the secular variation of each of the Geomagnetic elements.
582 INPUT MagneticVariation Data structure with the following elements
583 float Bx; ( North )
584 float By; ( East )
585 float Bz; ( Down )
586 OUTPUT MagneticElements Pointer to the data structure with the following elements updated
587 float Decldot; Yearly Rate of change in declination
588 float Incldot; Yearly Rate of change in inclination
589 float Fdot; Yearly rate of change in Magnetic field strength
590 float Hdot; Yearly rate of change in horizontal field strength
591 float Xdot; Yearly rate of change in the northern component
592 float Ydot; Yearly rate of change in the eastern component
593 float Zdot; Yearly rate of change in the downward component
594 float GVdot;Yearly rate of chnage in grid variation
597 MagneticElements->Xdot = MagneticVariation->Bx;
598 MagneticElements->Ydot = MagneticVariation->By;
599 MagneticElements->Zdot = MagneticVariation->Bz;
600 MagneticElements->Hdot = (MagneticElements->X * MagneticElements->Xdot + MagneticElements->Y * MagneticElements->Ydot) / MagneticElements->H; // See equation 19 in the WMM technical report
601 MagneticElements->Fdot =
602 (MagneticElements->X * MagneticElements->Xdot +
603 MagneticElements->Y * MagneticElements->Ydot + MagneticElements->Z * MagneticElements->Zdot) / MagneticElements->F;
604 MagneticElements->Decldot =
605 180.0 / M_PI * (MagneticElements->X * MagneticElements->Ydot -
606 MagneticElements->Y * MagneticElements->Xdot) / (MagneticElements->H * MagneticElements->H);
607 MagneticElements->Incldot =
608 180.0 / M_PI * (MagneticElements->H * MagneticElements->Zdot -
609 MagneticElements->Z * MagneticElements->Hdot) / (MagneticElements->F * MagneticElements->F);
610 MagneticElements->GVdot = MagneticElements->Decldot;
613 int WorldMagModel::PcupHigh(double *Pcup, double *dPcup, double x, int nMax)
615 /* This function evaluates all of the Schmidt-semi normalized associated Legendre
616 functions up to degree nMax. The functions are initially scaled by
617 10^280 sin^m in order to minimize the effects of underflow at large m
618 near the poles (see Holmes and Featherstone 2002, J. Geodesy, 76, 279-299).
619 Note that this function performs the same operation as WMM_PcupLow.
620 However this function also can be used for high degree (large nMax) models.
622 Calling Parameters:
623 INPUT
624 nMax: Maximum spherical harmonic degree to compute.
625 x: cos(colatitude) or sin(latitude).
627 OUTPUT
628 Pcup: A vector of all associated Legendgre polynomials evaluated at
629 x up to nMax. The lenght must by greater or equal to (nMax+1)*(nMax+2)/2.
630 dPcup: Derivative of Pcup(x) with respect to latitude
631 Notes:
633 Adopted from the FORTRAN code written by Mark Wieczorek September 25, 2005.
635 Manoj Nair, Nov, 2009 Manoj.C.Nair@Noaa.Gov
637 Change from the previous version
638 The prevous version computes the derivatives as
639 dP(n,m)(x)/dx, where x = sin(latitude) (or cos(colatitude) ).
640 However, the WMM Geomagnetic routines requires dP(n,m)(x)/dlatitude.
641 Hence the derivatives are multiplied by sin(latitude).
642 Removed the options for CS phase and normalizations.
644 Note: In geomagnetism, the derivatives of ALF are usually found with
645 respect to the colatitudes. Here the derivatives are found with respect
646 to the latitude. The difference is a sign reversal for the derivative of
647 the Associated Legendre Functions.
649 The derivates can't be computed for latitude = |90| degrees.
651 double f1[WMM_NUMPCUP];
652 double f2[WMM_NUMPCUP];
653 double PreSqr[WMM_NUMPCUP];
654 int m;
656 if (fabs(x) == 1.0) {
657 // printf("Error in PcupHigh: derivative cannot be calculated at poles\n");
658 return -2;
661 double scalef = 1.0e-280;
663 for (int n = 0; n <= 2 * nMax + 1; ++n) {
664 PreSqr[n] = sqrt((double)(n));
667 int k = 2;
669 for (int n = 2; n <= nMax; n++) {
670 k = k + 1;
671 f1[k] = (double)(2 * n - 1) / n;
672 f2[k] = (double)(n - 1) / n;
673 for (int m = 1; m <= n - 2; m++) {
674 k = k + 1;
675 f1[k] = (double)(2 * n - 1) / PreSqr[n + m] / PreSqr[n - m];
676 f2[k] = PreSqr[n - m - 1] * PreSqr[n + m - 1] / PreSqr[n + m] / PreSqr[n - m];
678 k = k + 2;
681 /*z = sin (geocentric latitude) */
682 double z = sqrt((1.0 - x) * (1.0 + x));
683 double pm2 = 1.0;
684 Pcup[0] = 1.0;
685 dPcup[0] = 0.0;
686 if (nMax == 0) {
687 return -3;
689 double pm1 = x;
690 Pcup[1] = pm1;
691 dPcup[1] = z;
692 k = 1;
694 for (int n = 2; n <= nMax; n++) {
695 k = k + n;
696 double plm = f1[k] * x * pm1 - f2[k] * pm2;
697 Pcup[k] = plm;
698 dPcup[k] = (double)(n) * (pm1 - x * plm) / z;
699 pm2 = pm1;
700 pm1 = plm;
703 double pmm = PreSqr[2] * scalef;
704 double rescalem = 1.0 / scalef;
705 int kstart = 0;
707 for (m = 1; m <= nMax - 1; ++m) {
708 rescalem = rescalem * z;
710 /* Calculate Pcup(m,m) */
711 kstart = kstart + m + 1;
712 pmm = pmm * PreSqr[2 * m + 1] / PreSqr[2 * m];
713 Pcup[kstart] = pmm * rescalem / PreSqr[2 * m + 1];
714 dPcup[kstart] = -((double)(m) * x * Pcup[kstart] / z);
715 pm2 = pmm / PreSqr[2 * m + 1];
716 /* Calculate Pcup(m+1,m) */
717 k = kstart + m + 1;
718 pm1 = x * PreSqr[2 * m + 1] * pm2;
719 Pcup[k] = pm1 * rescalem;
720 dPcup[k] = ((pm2 * rescalem) * PreSqr[2 * m + 1] - x * (double)(m + 1) * Pcup[k]) / z;
721 /* Calculate Pcup(n,m) */
722 for (int n = m + 2; n <= nMax; ++n) {
723 k = k + n;
724 double plm = x * f1[k] * pm1 - f2[k] * pm2;
725 Pcup[k] = plm * rescalem;
726 dPcup[k] = (PreSqr[n + m] * PreSqr[n - m] * (pm1 * rescalem) - (double)(n) * x * Pcup[k]) / z;
727 pm2 = pm1;
728 pm1 = plm;
732 /* Calculate Pcup(nMax,nMax) */
733 rescalem = rescalem * z;
734 kstart = kstart + m + 1;
735 pmm = pmm / PreSqr[2 * nMax];
736 Pcup[kstart] = pmm * rescalem;
737 dPcup[kstart] = -(double)(nMax) * x * Pcup[kstart] / z;
739 // *********
741 return 0; // OK
744 void WorldMagModel::PcupLow(double *Pcup, double *dPcup, double x, int nMax)
746 /* This function evaluates all of the Schmidt-semi normalized associated Legendre functions up to degree nMax.
748 Calling Parameters:
749 INPUT
750 nMax: Maximum spherical harmonic degree to compute.
751 x: cos(colatitude) or sin(latitude).
753 OUTPUT
754 Pcup: A vector of all associated Legendgre polynomials evaluated at
755 x up to nMax.
756 dPcup: Derivative of Pcup(x) with respect to latitude
758 Notes: Overflow may occur if nMax > 20 , especially for high-latitudes.
759 Use WMM_PcupHigh for large nMax.
761 Writted by Manoj Nair, June, 2009 . Manoj.C.Nair@Noaa.Gov.
763 Note: In geomagnetism, the derivatives of ALF are usually found with
764 respect to the colatitudes. Here the derivatives are found with respect
765 to the latitude. The difference is a sign reversal for the derivative of
766 the Associated Legendre Functions.
769 double schmidtQuasiNorm[WMM_NUMPCUP];
771 Pcup[0] = 1.0;
772 dPcup[0] = 0.0;
774 /*sin (geocentric latitude) - sin_phi */
775 double z = sqrt((1.0 - x) * (1.0 + x));
777 /* First, Compute the Gauss-normalized associated Legendre functions */
778 for (int n = 1; n <= nMax; n++) {
779 for (int m = 0; m <= n; m++) {
780 int index = (n * (n + 1) / 2 + m);
781 if (n == m) {
782 int index1 = (n - 1) * n / 2 + m - 1;
783 Pcup[index] = z * Pcup[index1];
784 dPcup[index] = z * dPcup[index1] + x * Pcup[index1];
785 } else if (n == 1 && m == 0) {
786 int index1 = (n - 1) * n / 2 + m;
787 Pcup[index] = x * Pcup[index1];
788 dPcup[index] = x * dPcup[index1] - z * Pcup[index1];
789 } else if (n > 1 && n != m) {
790 int index1 = (n - 2) * (n - 1) / 2 + m;
791 int index2 = (n - 1) * n / 2 + m;
792 if (m > n - 2) {
793 Pcup[index] = x * Pcup[index2];
794 dPcup[index] = x * dPcup[index2] - z * Pcup[index2];
795 } else {
796 double k = (double)(((n - 1) * (n - 1)) - (m * m)) / (double)((2 * n - 1) * (2 * n - 3));
797 Pcup[index] = x * Pcup[index2] - k * Pcup[index1];
798 dPcup[index] = x * dPcup[index2] - z * Pcup[index2] - k * dPcup[index1];
804 /*Compute the ration between the Gauss-normalized associated Legendre
805 functions and the Schmidt quasi-normalized version. This is equivalent to
806 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
808 schmidtQuasiNorm[0] = 1.0;
809 for (int n = 1; n <= nMax; n++) {
810 int index = (n * (n + 1) / 2);
811 int index1 = (n - 1) * n / 2;
812 /* for m = 0 */
813 schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * (double)(2 * n - 1) / (double)n;
815 for (int m = 1; m <= n; m++) {
816 index = (n * (n + 1) / 2 + m);
817 index1 = (n * (n + 1) / 2 + m - 1);
818 schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * sqrt((double)((n - m + 1) * (m == 1 ? 2 : 1)) / (double)(n + m));
822 /* Converts the Gauss-normalized associated Legendre
823 functions to the Schmidt quasi-normalized version using pre-computed
824 relation stored in the variable schmidtQuasiNorm */
826 for (int n = 1; n <= nMax; n++) {
827 for (int m = 0; m <= n; m++) {
828 int index = (n * (n + 1) / 2 + m);
829 Pcup[index] = Pcup[index] * schmidtQuasiNorm[index];
830 dPcup[index] = -dPcup[index] * schmidtQuasiNorm[index];
831 /* The sign is changed since the new WMM routines use derivative with respect to latitude insted of co-latitude */
836 void WorldMagModel::SummationSpecial(WMMtype_SphericalHarmonicVariables *SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
838 /* Special calculation for the component By at Geographic poles.
839 Manoj Nair, June, 2009 manoj.c.nair@noaa.gov
840 INPUT: MagneticModel
841 SphVariables
842 CoordSpherical
843 OUTPUT: MagneticResults
844 CALLS : none
845 See Section 1.4, "SINGULARITIES AT THE GEOGRAPHIC POLES", WMM Technical report
848 double PcupS[WMM_NUMPCUPS];
850 PcupS[0] = 1;
851 double schmidtQuasiNorm1 = 1.0;
853 MagneticResults->By = 0.0;
854 double sin_phi = sin(DEG2RAD(CoordSpherical->phig));
856 for (int n = 1; n <= MagneticModel.nMax; n++) {
857 /*Compute the ration between the Gauss-normalized associated Legendre
858 functions and the Schmidt quasi-normalized version. This is equivalent to
859 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
861 int index = (n * (n + 1) / 2 + 1);
862 double schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
863 double schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
864 schmidtQuasiNorm1 = schmidtQuasiNorm2;
865 if (n == 1) {
866 PcupS[n] = PcupS[n - 1];
867 } else {
868 double k = (double)(((n - 1) * (n - 1)) - 1) / (double)((2 * n - 1) * (2 * n - 3));
869 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
872 /* 1 nMax (n+2) n m m m
873 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
874 n=1 m=0 n n n */
875 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
877 MagneticResults->By +=
878 SphVariables->RelativeRadiusPower[n] *
879 (get_main_field_coeff_g(index) *
880 SphVariables->sin_mlambda[1] - get_main_field_coeff_h(index) * SphVariables->cos_mlambda[1])
881 * PcupS[n] * schmidtQuasiNorm3;
885 void WorldMagModel::SecVarSummationSpecial(WMMtype_SphericalHarmonicVariables *SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
887 /*Special calculation for the secular variation summation at the poles.
889 INPUT: MagneticModel
890 SphVariables
891 CoordSpherical
892 OUTPUT: MagneticResults
895 double PcupS[WMM_NUMPCUPS];
897 PcupS[0] = 1;
898 double schmidtQuasiNorm1 = 1.0;
900 MagneticResults->By = 0.0;
901 double sin_phi = sin(DEG2RAD(CoordSpherical->phig));
903 for (int n = 1; n <= MagneticModel.nMaxSecVar; n++) {
904 int index = (n * (n + 1) / 2 + 1);
905 double schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
906 double schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
907 schmidtQuasiNorm1 = schmidtQuasiNorm2;
908 if (n == 1) {
909 PcupS[n] = PcupS[n - 1];
910 } else {
911 double k = (double)(((n - 1) * (n - 1)) - 1) / (double)((2 * n - 1) * (2 * n - 3));
912 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
915 /* 1 nMax (n+2) n m m m
916 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
917 n=1 m=0 n n n */
918 /* Derivative with respect to longitude, divided by radius. */
920 MagneticResults->By +=
921 SphVariables->RelativeRadiusPower[n] *
922 (get_secular_var_coeff_g(index) *
923 SphVariables->sin_mlambda[1] - get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[1])
924 * PcupS[n] * schmidtQuasiNorm3;
928 // brief Comput the MainFieldCoeffH accounting for the date
929 double WorldMagModel::get_main_field_coeff_g(int index)
931 if (index >= WMM_NUMTERMS) {
932 return 0;
935 double coeff = CoeffFile[index][2];
937 int a = MagneticModel.nMaxSecVar;
938 int b = (a * (a + 1) / 2 + a);
939 for (int n = 1; n <= MagneticModel.nMax; n++) {
940 for (int m = 0; m <= n; m++) {
941 int sum_index = (n * (n + 1) / 2 + m);
943 /* Hacky for now, will solve for which conditions need summing analytically */
944 if (sum_index != index) {
945 continue;
948 if (index <= b) {
949 coeff += (decimal_date - MagneticModel.epoch) * get_secular_var_coeff_g(sum_index);
954 return coeff;
957 double WorldMagModel::get_main_field_coeff_h(int index)
959 if (index >= WMM_NUMTERMS) {
960 return 0;
963 double coeff = CoeffFile[index][3];
965 int a = MagneticModel.nMaxSecVar;
966 int b = (a * (a + 1) / 2 + a);
967 for (int n = 1; n <= MagneticModel.nMax; n++) {
968 for (int m = 0; m <= n; m++) {
969 int sum_index = (n * (n + 1) / 2 + m);
971 /* Hacky for now, will solve for which conditions need summing analytically */
972 if (sum_index != index) {
973 continue;
976 if (index <= b) {
977 coeff += (decimal_date - MagneticModel.epoch) * get_secular_var_coeff_h(sum_index);
982 return coeff;
985 double WorldMagModel::get_secular_var_coeff_g(int index)
987 if (index >= WMM_NUMTERMS) {
988 return 0;
991 return CoeffFile[index][4];
994 double WorldMagModel::get_secular_var_coeff_h(int index)
996 if (index >= WMM_NUMTERMS) {
997 return 0;
1000 return CoeffFile[index][5];
1003 int WorldMagModel::DateToYear(int month, int day, int year)
1005 // Converts a given calendar date into a decimal year
1007 int temp = 0; // Total number of days
1008 int MonthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
1009 int ExtraDay = 0;
1011 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
1012 ExtraDay = 1;
1014 MonthDays[2] += ExtraDay;
1016 /******************Validation********************************/
1018 if (month <= 0 || month > 12) {
1019 return -1; // error
1021 if (day <= 0 || day > MonthDays[month]) {
1022 return -2; // error
1024 /****************Calculation of t***************************/
1025 for (int i = 1; i <= month; i++) {
1026 temp += MonthDays[i - 1];
1028 temp += day;
1030 decimal_date = year + (temp - 1) / (365.0 + ExtraDay);
1032 return 0; // OK
1035 void WorldMagModel::GeodeticToSpherical(WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_CoordSpherical *CoordSpherical)
1037 // Converts Geodetic coordinates to Spherical coordinates
1038 // Convert geodetic coordinates, (defined by the WGS-84
1039 // reference ellipsoid), to Earth Centered Earth Fixed Cartesian
1040 // coordinates, and then to spherical coordinates.
1042 double CosLat = cos(DEG2RAD(CoordGeodetic->phi));
1043 double SinLat = sin(DEG2RAD(CoordGeodetic->phi));
1045 // compute the local radius of curvature on the WGS-84 reference ellipsoid
1046 double rc = Ellip.a / sqrt(1.0 - Ellip.epssq * SinLat * SinLat);
1048 // compute ECEF Cartesian coordinates of specified point (for longitude=0)
1049 double xp = (rc + CoordGeodetic->HeightAboveEllipsoid) * CosLat;
1050 double zp = (rc * (1.0 - Ellip.epssq) + CoordGeodetic->HeightAboveEllipsoid) * SinLat;
1052 // compute spherical radius and angle lambda and phi of specified point
1053 CoordSpherical->r = sqrt(xp * xp + zp * zp);
1054 CoordSpherical->phig = RAD2DEG(asin(zp / CoordSpherical->r)); // geocentric latitude
1055 CoordSpherical->lambda = CoordGeodetic->lambda; // longitude