LP-311 Remove basic/advanced stabilization tab auto-switch (autotune/txpid lock issues)
[librepilot.git] / ground / gcs / src / libs / utils / worldmagmodel.cpp
blob37a6f356f71f2d37b781992f25f4295409f32dc8
1 /**
2 ******************************************************************************
4 * @file worldmagmodel.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * @brief Utilities to find the location of openpilot GCS files:
7 * - Plugins Share directory path
9 * @brief Source file for the World Magnetic Model
10 * This is a port of code available from the US NOAA.
12 * The hard coded coefficients should be valid until 2015.
14 * Updated coeffs from ..
15 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_ddownload.shtml
17 * NASA C source code ..
18 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_wdownload.shtml
20 * Major changes include:
21 * - No geoid model (altitude must be geodetic WGS-84)
22 * - Floating point calculation (not double precision)
23 * - Hard coded coefficients for model
24 * - Elimination of user interface
25 * - Elimination of dynamic memory allocation
27 * @see The GNU Public License (GPL) Version 3
29 *****************************************************************************/
31 * This program is free software; you can redistribute it and/or modify
32 * it under the terms of the GNU General Public License as published by
33 * the Free Software Foundation; either version 3 of the License, or
34 * (at your option) any later version.
36 * This program is distributed in the hope that it will be useful, but
37 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
38 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
39 * for more details.
41 * You should have received a copy of the GNU General Public License along
42 * with this program; if not, write to the Free Software Foundation, Inc.,
43 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
46 #include "worldmagmodel.h"
48 #include <stdint.h>
49 #include <QDebug>
50 #include <math.h>
52 #define RAD2DEG(rad) ((rad) * (180.0 / M_PI))
53 #define DEG2RAD(deg) ((deg) * (M_PI / 180.0))
55 // updated coeffs available from http://www.ngdc.noaa.gov/geomag/WMM/wmm_ddownload.shtml
56 const double CoeffFile[91][6] = {
57 { 0, 0, 0, 0, 0, 0 },
58 { 1, 0, -29496.6, 0.0, 11.6, 0.0 },
59 { 1, 1, -1586.3, 4944.4, 16.5, -25.9 },
60 { 2, 0, -2396.6, 0.0, -12.1, 0.0 },
61 { 2, 1, 3026.1, -2707.7, -4.4, -22.5 },
62 { 2, 2, 1668.6, -576.1, 1.9, -11.8 },
63 { 3, 0, 1340.1, 0.0, 0.4, 0.0 },
64 { 3, 1, -2326.2, -160.2, -4.1, 7.3 },
65 { 3, 2, 1231.9, 251.9, -2.9, -3.9 },
66 { 3, 3, 634.0, -536.6, -7.7, -2.6 },
67 { 4, 0, 912.6, 0.0, -1.8, 0.0 },
68 { 4, 1, 808.9, 286.4, 2.3, 1.1 },
69 { 4, 2, 166.7, -211.2, -8.7, 2.7 },
70 { 4, 3, -357.1, 164.3, 4.6, 3.9 },
71 { 4, 4, 89.4, -309.1, -2.1, -0.8 },
72 { 5, 0, -230.9, 0.0, -1.0, 0.0 },
73 { 5, 1, 357.2, 44.6, 0.6, 0.4 },
74 { 5, 2, 200.3, 188.9, -1.8, 1.8 },
75 { 5, 3, -141.1, -118.2, -1.0, 1.2 },
76 { 5, 4, -163.0, 0.0, 0.9, 4.0 },
77 { 5, 5, -7.8, 100.9, 1.0, -0.6 },
78 { 6, 0, 72.8, 0.0, -0.2, 0.0 },
79 { 6, 1, 68.6, -20.8, -0.2, -0.2 },
80 { 6, 2, 76.0, 44.1, -0.1, -2.1 },
81 { 6, 3, -141.4, 61.5, 2.0, -0.4 },
82 { 6, 4, -22.8, -66.3, -1.7, -0.6 },
83 { 6, 5, 13.2, 3.1, -0.3, 0.5 },
84 { 6, 6, -77.9, 55.0, 1.7, 0.9 },
85 { 7, 0, 80.5, 0.0, 0.1, 0.0 },
86 { 7, 1, -75.1, -57.9, -0.1, 0.7 },
87 { 7, 2, -4.7, -21.1, -0.6, 0.3 },
88 { 7, 3, 45.3, 6.5, 1.3, -0.1 },
89 { 7, 4, 13.9, 24.9, 0.4, -0.1 },
90 { 7, 5, 10.4, 7.0, 0.3, -0.8 },
91 { 7, 6, 1.7, -27.7, -0.7, -0.3 },
92 { 7, 7, 4.9, -3.3, 0.6, 0.3 },
93 { 8, 0, 24.4, 0.0, -0.1, 0.0 },
94 { 8, 1, 8.1, 11.0, 0.1, -0.1 },
95 { 8, 2, -14.5, -20.0, -0.6, 0.2 },
96 { 8, 3, -5.6, 11.9, 0.2, 0.4 },
97 { 8, 4, -19.3, -17.4, -0.2, 0.4 },
98 { 8, 5, 11.5, 16.7, 0.3, 0.1 },
99 { 8, 6, 10.9, 7.0, 0.3, -0.1 },
100 { 8, 7, -14.1, -10.8, -0.6, 0.4 },
101 { 8, 8, -3.7, 1.7, 0.2, 0.3 },
102 { 9, 0, 5.4, 0.0, 0.0, 0.0 },
103 { 9, 1, 9.4, -20.5, -0.1, 0.0 },
104 { 9, 2, 3.4, 11.5, 0.0, -0.2 },
105 { 9, 3, -5.2, 12.8, 0.3, 0.0 },
106 { 9, 4, 3.1, -7.2, -0.4, -0.1 },
107 { 9, 5, -12.4, -7.4, -0.3, 0.1 },
108 { 9, 6, -0.7, 8.0, 0.1, 0.0 },
109 { 9, 7, 8.4, 2.1, -0.1, -0.2 },
110 { 9, 8, -8.5, -6.1, -0.4, 0.3 },
111 { 9, 9, -10.1, 7.0, -0.2, 0.2 },
112 { 10, 0, -2.0, 0.0, 0.0, 0.0 },
113 { 10, 1, -6.3, 2.8, 0.0, 0.1 },
114 { 10, 2, 0.9, -0.1, -0.1, -0.1 },
115 { 10, 3, -1.1, 4.7, 0.2, 0.0 },
116 { 10, 4, -0.2, 4.4, 0.0, -0.1 },
117 { 10, 5, 2.5, -7.2, -0.1, -0.1 },
118 { 10, 6, -0.3, -1.0, -0.2, 0.0 },
119 { 10, 7, 2.2, -3.9, 0.0, -0.1 },
120 { 10, 8, 3.1, -2.0, -0.1, -0.2 },
121 { 10, 9, -1.0, -2.0, -0.2, 0.0 },
122 { 10, 10, -2.8, -8.3, -0.2, -0.1 },
123 { 11, 0, 3.0, 0.0, 0.0, 0.0 },
124 { 11, 1, -1.5, 0.2, 0.0, 0.0 },
125 { 11, 2, -2.1, 1.7, 0.0, 0.1 },
126 { 11, 3, 1.7, -0.6, 0.1, 0.0 },
127 { 11, 4, -0.5, -1.8, 0.0, 0.1 },
128 { 11, 5, 0.5, 0.9, 0.0, 0.0 },
129 { 11, 6, -0.8, -0.4, 0.0, 0.1 },
130 { 11, 7, 0.4, -2.5, 0.0, 0.0 },
131 { 11, 8, 1.8, -1.3, 0.0, -0.1 },
132 { 11, 9, 0.1, -2.1, 0.0, -0.1 },
133 { 11, 10, 0.7, -1.9, -0.1, 0.0 },
134 { 11, 11, 3.8, -1.8, 0.0, -0.1 },
135 { 12, 0, -2.2, 0.0, 0.0, 0.0 },
136 { 12, 1, -0.2, -0.9, 0.0, 0.0 },
137 { 12, 2, 0.3, 0.3, 0.1, 0.0 },
138 { 12, 3, 1.0, 2.1, 0.1, 0.0 },
139 { 12, 4, -0.6, -2.5, -0.1, 0.0 },
140 { 12, 5, 0.9, 0.5, 0.0, 0.0 },
141 { 12, 6, -0.1, 0.6, 0.0, 0.1 },
142 { 12, 7, 0.5, 0.0, 0.0, 0.0 },
143 { 12, 8, -0.4, 0.1, 0.0, 0.0 },
144 { 12, 9, -0.4, 0.3, 0.0, 0.0 },
145 { 12, 10, 0.2, -0.9, 0.0, 0.0 },
146 { 12, 11, -0.8, -0.2, -0.1, 0.0 },
147 { 12, 12, 0.0, 0.9, 0.1, 0.0 }
150 namespace Utils {
151 WorldMagModel::WorldMagModel()
153 Initialize();
157 * @brief
158 * @param[in] LLA The longitude-latitude-altitude coordinate to compute the magnetic field at
159 * @param[in] Month
160 * @param[in] Day
161 * @param[in] Year
162 * @param[out] Be The resulting magnetic field at that location and time in [mGau](?)
163 * @returns 0 if successful, negative otherwise.
165 int WorldMagModel::GetMagVector(double LLA[3], int Month, int Day, int Year, double Be[3])
167 double Lat = LLA[0];
168 double Lon = LLA[1];
169 double AltEllipsoid = LLA[2] / 1000.0; // convert to km
171 // ***********
172 // range check supplied params
174 if (Lat < -90) {
175 return -1; // error
177 if (Lat > 90) {
178 return -2; // error
180 if (Lon < -180) {
181 return -3; // error
183 if (Lon > 180) {
184 return -4; // error
186 // ***********
188 WMMtype_CoordSpherical CoordSpherical;
189 WMMtype_CoordGeodetic CoordGeodetic;
190 WMMtype_GeoMagneticElements GeoMagneticElements;
192 Initialize();
194 CoordGeodetic.lambda = Lon;
195 CoordGeodetic.phi = Lat;
196 CoordGeodetic.HeightAboveEllipsoid = AltEllipsoid;
198 // Convert from geodeitic to Spherical Equations: 17-18, WMM Technical report
199 GeodeticToSpherical(&CoordGeodetic, &CoordSpherical);
201 if (DateToYear(Month, Day, Year) < 0) {
202 return -5; // error
204 // Compute the geoMagnetic field elements and their time change
205 if (Geomag(&CoordSpherical, &CoordGeodetic, &GeoMagneticElements) < 0) {
206 return -6; // error
208 // set the returned values
209 Be[0] = GeoMagneticElements.X * 1e-2;
210 Be[1] = GeoMagneticElements.Y * 1e-2;
211 Be[2] = GeoMagneticElements.Z * 1e-2;
213 // ***********
215 return 0; // OK
218 void WorldMagModel::Initialize()
219 { // Sets default values for WMM subroutines.
220 // UPDATES : Ellip and MagneticModel
222 // Sets WGS-84 parameters
223 Ellip.a = 6378.137; // semi-major axis of the ellipsoid in km
224 Ellip.b = 6356.7523142; // semi-minor axis of the ellipsoid in km
225 Ellip.fla = 1 / 298.257223563; // flattening
226 Ellip.eps = sqrt(1 - (Ellip.b * Ellip.b) / (Ellip.a * Ellip.a)); // first eccentricity
227 Ellip.epssq = (Ellip.eps * Ellip.eps); // first eccentricity squared
228 Ellip.re = 6371.2; // Earth's radius in km
230 // Sets Magnetic Model parameters
231 MagneticModel.nMax = WMM_MAX_MODEL_DEGREES;
232 MagneticModel.nMaxSecVar = WMM_MAX_SECULAR_VARIATION_MODEL_DEGREES;
233 MagneticModel.SecularVariationUsed = 0;
235 // Really, Really needs to be read from a file - out of date in 2015 at latest
236 MagneticModel.EditionDate = 5.7863328170559505e-307;
237 MagneticModel.epoch = 2010.0;
238 sprintf(MagneticModel.ModelName, "WMM-2010");
242 int WorldMagModel::Geomag(WMMtype_CoordSpherical *CoordSpherical, WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_GeoMagneticElements *GeoMagneticElements)
244 The main subroutine that calls a sequence of WMM sub-functions to calculate the magnetic field elements for a single point.
245 The function expects the model coefficients and point coordinates as input and returns the magnetic field elements and
246 their rate of change. Though, this subroutine can be called successively to calculate a time series, profile or grid
247 of magnetic field, these are better achieved by the subroutine WMM_Grid.
249 INPUT: Ellip
250 CoordSpherical
251 CoordGeodetic
252 TimedMagneticModel
254 OUTPUT : GeoMagneticElements
257 WMMtype_MagneticResults MagneticResultsSph;
258 WMMtype_MagneticResults MagneticResultsGeo;
259 WMMtype_MagneticResults MagneticResultsSphVar;
260 WMMtype_MagneticResults MagneticResultsGeoVar;
261 WMMtype_LegendreFunction LegendreFunction;
262 WMMtype_SphericalHarmonicVariables SphVariables;
264 // Compute Spherical Harmonic variables
265 ComputeSphericalHarmonicVariables(CoordSpherical, MagneticModel.nMax, &SphVariables);
267 // Compute ALF
268 if (AssociatedLegendreFunction(CoordSpherical, MagneticModel.nMax, &LegendreFunction) < 0) {
269 return -1; // error
271 // Accumulate the spherical harmonic coefficients
272 Summation(&LegendreFunction, &SphVariables, CoordSpherical, &MagneticResultsSph);
274 // Sum the Secular Variation Coefficients
275 SecVarSummation(&LegendreFunction, &SphVariables, CoordSpherical, &MagneticResultsSphVar);
277 // Map the computed Magnetic fields to Geodeitic coordinates
278 RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSph, &MagneticResultsGeo);
280 // Map the secular variation field components to Geodetic coordinates
281 RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSphVar, &MagneticResultsGeoVar);
283 // Calculate the Geomagnetic elements, Equation 18 , WMM Technical report
284 CalculateGeoMagneticElements(&MagneticResultsGeo, GeoMagneticElements);
286 // Calculate the secular variation of each of the Geomagnetic elements
287 CalculateSecularVariation(&MagneticResultsGeoVar, GeoMagneticElements);
289 return 0; // OK
292 void WorldMagModel::ComputeSphericalHarmonicVariables(WMMtype_CoordSpherical *CoordSpherical, int nMax, WMMtype_SphericalHarmonicVariables *SphVariables)
294 /* Computes Spherical variables
295 Variables computed are (a/r)^(n+2), cos_m(lamda) and sin_m(lambda) for spherical harmonic
296 summations. (Equations 10-12 in the WMM Technical Report)
297 INPUT Ellip data structure with the following elements
298 float a; semi-major axis of the ellipsoid
299 float b; semi-minor axis of the ellipsoid
300 float fla; flattening
301 float epssq; first eccentricity squared
302 float eps; first eccentricity
303 float re; mean radius of ellipsoid
304 CoordSpherical A data structure with the following elements
305 float lambda; ( longitude)
306 float phig; ( geocentric latitude )
307 float r; ( distance from the center of the ellipsoid)
308 nMax integer ( Maxumum degree of spherical harmonic secular model)\
310 OUTPUT SphVariables Pointer to the data structure with the following elements
311 float RelativeRadiusPower[WMM_MAX_MODEL_DEGREES+1]; [earth_reference_radius_km sph. radius ]^n
312 float cos_mlambda[WMM_MAX_MODEL_DEGREES+1]; cp(m) - cosine of (mspherical coord. longitude)
313 float sin_mlambda[WMM_MAX_MODEL_DEGREES+1]; sp(m) - sine of (mspherical coord. longitude)
315 double cos_lambda = cos(DEG2RAD(CoordSpherical->lambda));
316 double sin_lambda = sin(DEG2RAD(CoordSpherical->lambda));
318 /* for n = 0 ... model_order, compute (Radius of Earth / Spherica radius r)^(n+2)
319 for n 1..nMax-1 (this is much faster than calling pow MAX_N+1 times). */
321 SphVariables->RelativeRadiusPower[0] = (Ellip.re / CoordSpherical->r) * (Ellip.re / CoordSpherical->r);
322 for (int n = 1; n <= nMax; n++) {
323 SphVariables->RelativeRadiusPower[n] = SphVariables->RelativeRadiusPower[n - 1] * (Ellip.re / CoordSpherical->r);
327 Compute cos(m*lambda), sin(m*lambda) for m = 0 ... nMax
328 cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)
329 sin(a + b) = cos(a)*sin(b) + sin(a)*cos(b)
331 SphVariables->cos_mlambda[0] = 1.0;
332 SphVariables->sin_mlambda[0] = 0.0;
334 SphVariables->cos_mlambda[1] = cos_lambda;
335 SphVariables->sin_mlambda[1] = sin_lambda;
336 for (int m = 2; m <= nMax; m++) {
337 SphVariables->cos_mlambda[m] = SphVariables->cos_mlambda[m - 1] * cos_lambda - SphVariables->sin_mlambda[m - 1] * sin_lambda;
338 SphVariables->sin_mlambda[m] = SphVariables->cos_mlambda[m - 1] * sin_lambda + SphVariables->sin_mlambda[m - 1] * cos_lambda;
342 int WorldMagModel::AssociatedLegendreFunction(WMMtype_CoordSpherical *CoordSpherical, int nMax, WMMtype_LegendreFunction *LegendreFunction)
344 /* Computes all of the Schmidt-semi normalized associated Legendre
345 functions up to degree nMax. If nMax <= 16, function WMM_PcupLow is used.
346 Otherwise WMM_PcupHigh is called.
347 INPUT CoordSpherical A data structure with the following elements
348 float lambda; ( longitude)
349 float phig; ( geocentric latitude )
350 float r; ( distance from the center of the ellipsoid)
351 nMax integer ( Maxumum degree of spherical harmonic secular model)
352 LegendreFunction Pointer to data structure with the following elements
353 float *Pcup; ( pointer to store Legendre Function )
354 float *dPcup; ( pointer to store Derivative of Lagendre function )
356 OUTPUT LegendreFunction Calculated Legendre variables in the data structure
359 double sin_phi = sin(DEG2RAD(CoordSpherical->phig)); // sin (geocentric latitude)
361 if (nMax <= 16 || (1 - fabs(sin_phi)) < 1.0e-10) { /* If nMax is less tha 16 or at the poles */
362 PcupLow(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax);
363 } else {
364 if (PcupHigh(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax) < 0) {
365 return -1; // error
369 return 0; // OK
372 void WorldMagModel::Summation(WMMtype_LegendreFunction *LegendreFunction,
373 WMMtype_SphericalHarmonicVariables *SphVariables,
374 WMMtype_CoordSpherical *CoordSpherical,
375 WMMtype_MagneticResults *MagneticResults)
377 /* Computes Geomagnetic Field Elements X, Y and Z in Spherical coordinate system using spherical harmonic summation.
379 The vector Magnetic field is given by -grad V, where V is Geomagnetic scalar potential
380 The gradient in spherical coordinates is given by:
382 dV ^ 1 dV ^ 1 dV ^
383 grad V = -- r + - -- t + -------- -- p
384 dr r dt r sin(t) dp
386 INPUT : LegendreFunction
387 MagneticModel
388 SphVariables
389 CoordSpherical
390 OUTPUT : MagneticResults
392 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
395 MagneticResults->Bz = 0.0;
396 MagneticResults->By = 0.0;
397 MagneticResults->Bx = 0.0;
399 for (int n = 1; n <= MagneticModel.nMax; n++) {
400 for (int m = 0; m <= n; m++) {
401 int index = (n * (n + 1) / 2 + m);
403 /* nMax (n+2) n m m m
404 Bz = -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi))
405 n=1 m=0 n n n */
406 /* Equation 12 in the WMM Technical report. Derivative with respect to radius.*/
407 MagneticResults->Bz -=
408 SphVariables->RelativeRadiusPower[n] *
409 (get_main_field_coeff_g(index) *
410 SphVariables->cos_mlambda[m] + get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
411 * (double)(n + 1) * LegendreFunction->Pcup[index];
413 /* 1 nMax (n+2) n m m m
414 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
415 n=1 m=0 n n n */
416 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
417 MagneticResults->By +=
418 SphVariables->RelativeRadiusPower[n] *
419 (get_main_field_coeff_g(index) *
420 SphVariables->sin_mlambda[m] - get_main_field_coeff_h(index) * SphVariables->cos_mlambda[m])
421 * (double)(m) * LegendreFunction->Pcup[index];
422 /* nMax (n+2) n m m m
423 Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
424 n=1 m=0 n n n */
425 /* Equation 10 in the WMM Technical report. Derivative with respect to latitude, divided by radius. */
427 MagneticResults->Bx -=
428 SphVariables->RelativeRadiusPower[n] *
429 (get_main_field_coeff_g(index) *
430 SphVariables->cos_mlambda[m] + get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
431 * LegendreFunction->dPcup[index];
435 double cos_phi = cos(DEG2RAD(CoordSpherical->phig));
436 if (fabs(cos_phi) > 1.0e-10) {
437 MagneticResults->By = MagneticResults->By / cos_phi;
438 } else {
439 /* Special calculation for component - By - at Geographic poles.
440 * If the user wants to avoid using this function, please make sure that
441 * the latitude is not exactly +/-90. An option is to make use the function
442 * WMM_CheckGeographicPoles.
444 SummationSpecial(SphVariables, CoordSpherical, MagneticResults);
448 void WorldMagModel::SecVarSummation(WMMtype_LegendreFunction *LegendreFunction,
449 WMMtype_SphericalHarmonicVariables *SphVariables,
450 WMMtype_CoordSpherical *CoordSpherical,
451 WMMtype_MagneticResults *MagneticResults)
453 /*This Function sums the secular variation coefficients to get the secular variation of the Magnetic vector.
454 INPUT : LegendreFunction
455 MagneticModel
456 SphVariables
457 CoordSpherical
458 OUTPUT : MagneticResults
461 MagneticModel.SecularVariationUsed = true;
463 MagneticResults->Bz = 0.0;
464 MagneticResults->By = 0.0;
465 MagneticResults->Bx = 0.0;
467 for (int n = 1; n <= MagneticModel.nMaxSecVar; n++) {
468 for (int m = 0; m <= n; m++) {
469 int index = (n * (n + 1) / 2 + m);
471 /* nMax (n+2) n m m m
472 Bz = -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi))
473 n=1 m=0 n n n */
474 /* Derivative with respect to radius.*/
475 MagneticResults->Bz -=
476 SphVariables->RelativeRadiusPower[n] *
477 (get_secular_var_coeff_g(index) *
478 SphVariables->cos_mlambda[m] + get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
479 * (double)(n + 1) * LegendreFunction->Pcup[index];
481 /* 1 nMax (n+2) n m m m
482 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
483 n=1 m=0 n n n */
484 /* Derivative with respect to longitude, divided by radius. */
485 MagneticResults->By +=
486 SphVariables->RelativeRadiusPower[n] *
487 (get_secular_var_coeff_g(index) *
488 SphVariables->sin_mlambda[m] - get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[m])
489 * (double)(m) * LegendreFunction->Pcup[index];
490 /* nMax (n+2) n m m m
491 Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
492 n=1 m=0 n n n */
493 /* Derivative with respect to latitude, divided by radius. */
495 MagneticResults->Bx -=
496 SphVariables->RelativeRadiusPower[n] *
497 (get_secular_var_coeff_g(index) *
498 SphVariables->cos_mlambda[m] + get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
499 * LegendreFunction->dPcup[index];
503 double cos_phi = cos(DEG2RAD(CoordSpherical->phig));
504 if (fabs(cos_phi) > 1.0e-10) {
505 MagneticResults->By = MagneticResults->By / cos_phi;
506 } else { /* Special calculation for component By at Geographic poles */
507 SecVarSummationSpecial(SphVariables, CoordSpherical, MagneticResults);
511 void WorldMagModel::RotateMagneticVector(WMMtype_CoordSpherical *CoordSpherical,
512 WMMtype_CoordGeodetic *CoordGeodetic,
513 WMMtype_MagneticResults *MagneticResultsSph,
514 WMMtype_MagneticResults *MagneticResultsGeo)
516 /* Rotate the Magnetic Vectors to Geodetic Coordinates
517 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
518 Equation 16, WMM Technical report
520 INPUT : CoordSpherical : Data structure WMMtype_CoordSpherical with the following elements
521 float lambda; ( longitude)
522 float phig; ( geocentric latitude )
523 float r; ( distance from the center of the ellipsoid)
525 CoordGeodetic : Data structure WMMtype_CoordGeodetic with the following elements
526 float lambda; (longitude)
527 float phi; ( geodetic latitude)
528 float HeightAboveEllipsoid; (height above the ellipsoid (HaE) )
529 float HeightAboveGeoid;(height above the Geoid )
531 MagneticResultsSph : Data structure WMMtype_MagneticResults with the following elements
532 float Bx; North
533 float By; East
534 float Bz; Down
536 OUTPUT: MagneticResultsGeo Pointer to the data structure WMMtype_MagneticResults, with the following elements
537 float Bx; North
538 float By; East
539 float Bz; Down
542 /* Difference between the spherical and Geodetic latitudes */
543 double Psi = DEG2RAD(CoordSpherical->phig - CoordGeodetic->phi);
545 /* Rotate spherical field components to the Geodeitic system */
546 MagneticResultsGeo->Bz = MagneticResultsSph->Bx * sin(Psi) + MagneticResultsSph->Bz * cos(Psi);
547 MagneticResultsGeo->Bx = MagneticResultsSph->Bx * cos(Psi) - MagneticResultsSph->Bz * sin(Psi);
548 MagneticResultsGeo->By = MagneticResultsSph->By;
551 void WorldMagModel::CalculateGeoMagneticElements(WMMtype_MagneticResults *MagneticResultsGeo, WMMtype_GeoMagneticElements *GeoMagneticElements)
553 /* Calculate all the Geomagnetic elements from X,Y and Z components
554 INPUT MagneticResultsGeo Pointer to data structure with the following elements
555 float Bx; ( North )
556 float By; ( East )
557 float Bz; ( Down )
558 OUTPUT GeoMagneticElements Pointer to data structure with the following elements
559 float Decl; (Angle between the magnetic field vector and true north, positive east)
560 float Incl; Angle between the magnetic field vector and the horizontal plane, positive down
561 float F; Magnetic Field Strength
562 float H; Horizontal Magnetic Field Strength
563 float X; Northern component of the magnetic field vector
564 float Y; Eastern component of the magnetic field vector
565 float Z; Downward component of the magnetic field vector
568 GeoMagneticElements->X = MagneticResultsGeo->Bx;
569 GeoMagneticElements->Y = MagneticResultsGeo->By;
570 GeoMagneticElements->Z = MagneticResultsGeo->Bz;
572 GeoMagneticElements->H = sqrt(MagneticResultsGeo->Bx * MagneticResultsGeo->Bx + MagneticResultsGeo->By * MagneticResultsGeo->By);
573 GeoMagneticElements->F = sqrt(GeoMagneticElements->H * GeoMagneticElements->H + MagneticResultsGeo->Bz * MagneticResultsGeo->Bz);
574 GeoMagneticElements->Decl = RAD2DEG(atan2(GeoMagneticElements->Y, GeoMagneticElements->X));
575 GeoMagneticElements->Incl = RAD2DEG(atan2(GeoMagneticElements->Z, GeoMagneticElements->H));
578 void WorldMagModel::CalculateSecularVariation(WMMtype_MagneticResults *MagneticVariation, WMMtype_GeoMagneticElements *MagneticElements)
580 /* This takes the Magnetic Variation in x, y, and z and uses it to calculate the secular variation of each of the Geomagnetic elements.
581 INPUT MagneticVariation Data structure with the following elements
582 float Bx; ( North )
583 float By; ( East )
584 float Bz; ( Down )
585 OUTPUT MagneticElements Pointer to the data structure with the following elements updated
586 float Decldot; Yearly Rate of change in declination
587 float Incldot; Yearly Rate of change in inclination
588 float Fdot; Yearly rate of change in Magnetic field strength
589 float Hdot; Yearly rate of change in horizontal field strength
590 float Xdot; Yearly rate of change in the northern component
591 float Ydot; Yearly rate of change in the eastern component
592 float Zdot; Yearly rate of change in the downward component
593 float GVdot;Yearly rate of chnage in grid variation
596 MagneticElements->Xdot = MagneticVariation->Bx;
597 MagneticElements->Ydot = MagneticVariation->By;
598 MagneticElements->Zdot = MagneticVariation->Bz;
599 MagneticElements->Hdot = (MagneticElements->X * MagneticElements->Xdot + MagneticElements->Y * MagneticElements->Ydot) / MagneticElements->H; // See equation 19 in the WMM technical report
600 MagneticElements->Fdot =
601 (MagneticElements->X * MagneticElements->Xdot +
602 MagneticElements->Y * MagneticElements->Ydot + MagneticElements->Z * MagneticElements->Zdot) / MagneticElements->F;
603 MagneticElements->Decldot =
604 180.0 / M_PI * (MagneticElements->X * MagneticElements->Ydot -
605 MagneticElements->Y * MagneticElements->Xdot) / (MagneticElements->H * MagneticElements->H);
606 MagneticElements->Incldot =
607 180.0 / M_PI * (MagneticElements->H * MagneticElements->Zdot -
608 MagneticElements->Z * MagneticElements->Hdot) / (MagneticElements->F * MagneticElements->F);
609 MagneticElements->GVdot = MagneticElements->Decldot;
612 int WorldMagModel::PcupHigh(double *Pcup, double *dPcup, double x, int nMax)
614 /* This function evaluates all of the Schmidt-semi normalized associated Legendre
615 functions up to degree nMax. The functions are initially scaled by
616 10^280 sin^m in order to minimize the effects of underflow at large m
617 near the poles (see Holmes and Featherstone 2002, J. Geodesy, 76, 279-299).
618 Note that this function performs the same operation as WMM_PcupLow.
619 However this function also can be used for high degree (large nMax) models.
621 Calling Parameters:
622 INPUT
623 nMax: Maximum spherical harmonic degree to compute.
624 x: cos(colatitude) or sin(latitude).
626 OUTPUT
627 Pcup: A vector of all associated Legendgre polynomials evaluated at
628 x up to nMax. The lenght must by greater or equal to (nMax+1)*(nMax+2)/2.
629 dPcup: Derivative of Pcup(x) with respect to latitude
630 Notes:
632 Adopted from the FORTRAN code written by Mark Wieczorek September 25, 2005.
634 Manoj Nair, Nov, 2009 Manoj.C.Nair@Noaa.Gov
636 Change from the previous version
637 The prevous version computes the derivatives as
638 dP(n,m)(x)/dx, where x = sin(latitude) (or cos(colatitude) ).
639 However, the WMM Geomagnetic routines requires dP(n,m)(x)/dlatitude.
640 Hence the derivatives are multiplied by sin(latitude).
641 Removed the options for CS phase and normalizations.
643 Note: In geomagnetism, the derivatives of ALF are usually found with
644 respect to the colatitudes. Here the derivatives are found with respect
645 to the latitude. The difference is a sign reversal for the derivative of
646 the Associated Legendre Functions.
648 The derivates can't be computed for latitude = |90| degrees.
650 double f1[WMM_NUMPCUP];
651 double f2[WMM_NUMPCUP];
652 double PreSqr[WMM_NUMPCUP];
653 int m;
655 if (fabs(x) == 1.0) {
656 // printf("Error in PcupHigh: derivative cannot be calculated at poles\n");
657 return -2;
660 double scalef = 1.0e-280;
662 for (int n = 0; n <= 2 * nMax + 1; ++n) {
663 PreSqr[n] = sqrt((double)(n));
666 int k = 2;
668 for (int n = 2; n <= nMax; n++) {
669 k = k + 1;
670 f1[k] = (double)(2 * n - 1) / n;
671 f2[k] = (double)(n - 1) / n;
672 for (int m = 1; m <= n - 2; m++) {
673 k = k + 1;
674 f1[k] = (double)(2 * n - 1) / PreSqr[n + m] / PreSqr[n - m];
675 f2[k] = PreSqr[n - m - 1] * PreSqr[n + m - 1] / PreSqr[n + m] / PreSqr[n - m];
677 k = k + 2;
680 /*z = sin (geocentric latitude) */
681 double z = sqrt((1.0 - x) * (1.0 + x));
682 double pm2 = 1.0;
683 Pcup[0] = 1.0;
684 dPcup[0] = 0.0;
685 if (nMax == 0) {
686 return -3;
688 double pm1 = x;
689 Pcup[1] = pm1;
690 dPcup[1] = z;
691 k = 1;
693 for (int n = 2; n <= nMax; n++) {
694 k = k + n;
695 double plm = f1[k] * x * pm1 - f2[k] * pm2;
696 Pcup[k] = plm;
697 dPcup[k] = (double)(n) * (pm1 - x * plm) / z;
698 pm2 = pm1;
699 pm1 = plm;
702 double pmm = PreSqr[2] * scalef;
703 double rescalem = 1.0 / scalef;
704 int kstart = 0;
706 for (m = 1; m <= nMax - 1; ++m) {
707 rescalem = rescalem * z;
709 /* Calculate Pcup(m,m) */
710 kstart = kstart + m + 1;
711 pmm = pmm * PreSqr[2 * m + 1] / PreSqr[2 * m];
712 Pcup[kstart] = pmm * rescalem / PreSqr[2 * m + 1];
713 dPcup[kstart] = -((double)(m) * x * Pcup[kstart] / z);
714 pm2 = pmm / PreSqr[2 * m + 1];
715 /* Calculate Pcup(m+1,m) */
716 k = kstart + m + 1;
717 pm1 = x * PreSqr[2 * m + 1] * pm2;
718 Pcup[k] = pm1 * rescalem;
719 dPcup[k] = ((pm2 * rescalem) * PreSqr[2 * m + 1] - x * (double)(m + 1) * Pcup[k]) / z;
720 /* Calculate Pcup(n,m) */
721 for (int n = m + 2; n <= nMax; ++n) {
722 k = k + n;
723 double plm = x * f1[k] * pm1 - f2[k] * pm2;
724 Pcup[k] = plm * rescalem;
725 dPcup[k] = (PreSqr[n + m] * PreSqr[n - m] * (pm1 * rescalem) - (double)(n) * x * Pcup[k]) / z;
726 pm2 = pm1;
727 pm1 = plm;
731 /* Calculate Pcup(nMax,nMax) */
732 rescalem = rescalem * z;
733 kstart = kstart + m + 1;
734 pmm = pmm / PreSqr[2 * nMax];
735 Pcup[kstart] = pmm * rescalem;
736 dPcup[kstart] = -(double)(nMax) * x * Pcup[kstart] / z;
738 // *********
740 return 0; // OK
743 void WorldMagModel::PcupLow(double *Pcup, double *dPcup, double x, int nMax)
745 /* This function evaluates all of the Schmidt-semi normalized associated Legendre functions up to degree nMax.
747 Calling Parameters:
748 INPUT
749 nMax: Maximum spherical harmonic degree to compute.
750 x: cos(colatitude) or sin(latitude).
752 OUTPUT
753 Pcup: A vector of all associated Legendgre polynomials evaluated at
754 x up to nMax.
755 dPcup: Derivative of Pcup(x) with respect to latitude
757 Notes: Overflow may occur if nMax > 20 , especially for high-latitudes.
758 Use WMM_PcupHigh for large nMax.
760 Writted by Manoj Nair, June, 2009 . Manoj.C.Nair@Noaa.Gov.
762 Note: In geomagnetism, the derivatives of ALF are usually found with
763 respect to the colatitudes. Here the derivatives are found with respect
764 to the latitude. The difference is a sign reversal for the derivative of
765 the Associated Legendre Functions.
768 double schmidtQuasiNorm[WMM_NUMPCUP];
770 Pcup[0] = 1.0;
771 dPcup[0] = 0.0;
773 /*sin (geocentric latitude) - sin_phi */
774 double z = sqrt((1.0 - x) * (1.0 + x));
776 /* First, Compute the Gauss-normalized associated Legendre functions */
777 for (int n = 1; n <= nMax; n++) {
778 for (int m = 0; m <= n; m++) {
779 int index = (n * (n + 1) / 2 + m);
780 if (n == m) {
781 int index1 = (n - 1) * n / 2 + m - 1;
782 Pcup[index] = z * Pcup[index1];
783 dPcup[index] = z * dPcup[index1] + x * Pcup[index1];
784 } else if (n == 1 && m == 0) {
785 int index1 = (n - 1) * n / 2 + m;
786 Pcup[index] = x * Pcup[index1];
787 dPcup[index] = x * dPcup[index1] - z * Pcup[index1];
788 } else if (n > 1 && n != m) {
789 int index1 = (n - 2) * (n - 1) / 2 + m;
790 int index2 = (n - 1) * n / 2 + m;
791 if (m > n - 2) {
792 Pcup[index] = x * Pcup[index2];
793 dPcup[index] = x * dPcup[index2] - z * Pcup[index2];
794 } else {
795 double k = (double)(((n - 1) * (n - 1)) - (m * m)) / (double)((2 * n - 1) * (2 * n - 3));
796 Pcup[index] = x * Pcup[index2] - k * Pcup[index1];
797 dPcup[index] = x * dPcup[index2] - z * Pcup[index2] - k * dPcup[index1];
803 /*Compute the ration between the Gauss-normalized associated Legendre
804 functions and the Schmidt quasi-normalized version. This is equivalent to
805 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
807 schmidtQuasiNorm[0] = 1.0;
808 for (int n = 1; n <= nMax; n++) {
809 int index = (n * (n + 1) / 2);
810 int index1 = (n - 1) * n / 2;
811 /* for m = 0 */
812 schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * (double)(2 * n - 1) / (double)n;
814 for (int m = 1; m <= n; m++) {
815 index = (n * (n + 1) / 2 + m);
816 index1 = (n * (n + 1) / 2 + m - 1);
817 schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * sqrt((double)((n - m + 1) * (m == 1 ? 2 : 1)) / (double)(n + m));
821 /* Converts the Gauss-normalized associated Legendre
822 functions to the Schmidt quasi-normalized version using pre-computed
823 relation stored in the variable schmidtQuasiNorm */
825 for (int n = 1; n <= nMax; n++) {
826 for (int m = 0; m <= n; m++) {
827 int index = (n * (n + 1) / 2 + m);
828 Pcup[index] = Pcup[index] * schmidtQuasiNorm[index];
829 dPcup[index] = -dPcup[index] * schmidtQuasiNorm[index];
830 /* The sign is changed since the new WMM routines use derivative with respect to latitude insted of co-latitude */
835 void WorldMagModel::SummationSpecial(WMMtype_SphericalHarmonicVariables *SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
837 /* Special calculation for the component By at Geographic poles.
838 Manoj Nair, June, 2009 manoj.c.nair@noaa.gov
839 INPUT: MagneticModel
840 SphVariables
841 CoordSpherical
842 OUTPUT: MagneticResults
843 CALLS : none
844 See Section 1.4, "SINGULARITIES AT THE GEOGRAPHIC POLES", WMM Technical report
847 double PcupS[WMM_NUMPCUPS];
849 PcupS[0] = 1;
850 double schmidtQuasiNorm1 = 1.0;
852 MagneticResults->By = 0.0;
853 double sin_phi = sin(DEG2RAD(CoordSpherical->phig));
855 for (int n = 1; n <= MagneticModel.nMax; n++) {
856 /*Compute the ration between the Gauss-normalized associated Legendre
857 functions and the Schmidt quasi-normalized version. This is equivalent to
858 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
860 int index = (n * (n + 1) / 2 + 1);
861 double schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
862 double schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
863 schmidtQuasiNorm1 = schmidtQuasiNorm2;
864 if (n == 1) {
865 PcupS[n] = PcupS[n - 1];
866 } else {
867 double k = (double)(((n - 1) * (n - 1)) - 1) / (double)((2 * n - 1) * (2 * n - 3));
868 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
871 /* 1 nMax (n+2) n m m m
872 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
873 n=1 m=0 n n n */
874 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
876 MagneticResults->By +=
877 SphVariables->RelativeRadiusPower[n] *
878 (get_main_field_coeff_g(index) *
879 SphVariables->sin_mlambda[1] - get_main_field_coeff_h(index) * SphVariables->cos_mlambda[1])
880 * PcupS[n] * schmidtQuasiNorm3;
884 void WorldMagModel::SecVarSummationSpecial(WMMtype_SphericalHarmonicVariables *SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
886 /*Special calculation for the secular variation summation at the poles.
888 INPUT: MagneticModel
889 SphVariables
890 CoordSpherical
891 OUTPUT: MagneticResults
894 double PcupS[WMM_NUMPCUPS];
896 PcupS[0] = 1;
897 double schmidtQuasiNorm1 = 1.0;
899 MagneticResults->By = 0.0;
900 double sin_phi = sin(DEG2RAD(CoordSpherical->phig));
902 for (int n = 1; n <= MagneticModel.nMaxSecVar; n++) {
903 int index = (n * (n + 1) / 2 + 1);
904 double schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
905 double schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
906 schmidtQuasiNorm1 = schmidtQuasiNorm2;
907 if (n == 1) {
908 PcupS[n] = PcupS[n - 1];
909 } else {
910 double k = (double)(((n - 1) * (n - 1)) - 1) / (double)((2 * n - 1) * (2 * n - 3));
911 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
914 /* 1 nMax (n+2) n m m m
915 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
916 n=1 m=0 n n n */
917 /* Derivative with respect to longitude, divided by radius. */
919 MagneticResults->By +=
920 SphVariables->RelativeRadiusPower[n] *
921 (get_secular_var_coeff_g(index) *
922 SphVariables->sin_mlambda[1] - get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[1])
923 * PcupS[n] * schmidtQuasiNorm3;
927 // brief Comput the MainFieldCoeffH accounting for the date
928 double WorldMagModel::get_main_field_coeff_g(int index)
930 if (index >= WMM_NUMTERMS) {
931 return 0;
934 double coeff = CoeffFile[index][2];
936 int a = MagneticModel.nMaxSecVar;
937 int b = (a * (a + 1) / 2 + a);
938 for (int n = 1; n <= MagneticModel.nMax; n++) {
939 for (int m = 0; m <= n; m++) {
940 int sum_index = (n * (n + 1) / 2 + m);
942 /* Hacky for now, will solve for which conditions need summing analytically */
943 if (sum_index != index) {
944 continue;
947 if (index <= b) {
948 coeff += (decimal_date - MagneticModel.epoch) * get_secular_var_coeff_g(sum_index);
953 return coeff;
956 double WorldMagModel::get_main_field_coeff_h(int index)
958 if (index >= WMM_NUMTERMS) {
959 return 0;
962 double coeff = CoeffFile[index][3];
964 int a = MagneticModel.nMaxSecVar;
965 int b = (a * (a + 1) / 2 + a);
966 for (int n = 1; n <= MagneticModel.nMax; n++) {
967 for (int m = 0; m <= n; m++) {
968 int sum_index = (n * (n + 1) / 2 + m);
970 /* Hacky for now, will solve for which conditions need summing analytically */
971 if (sum_index != index) {
972 continue;
975 if (index <= b) {
976 coeff += (decimal_date - MagneticModel.epoch) * get_secular_var_coeff_h(sum_index);
981 return coeff;
984 double WorldMagModel::get_secular_var_coeff_g(int index)
986 if (index >= WMM_NUMTERMS) {
987 return 0;
990 return CoeffFile[index][4];
993 double WorldMagModel::get_secular_var_coeff_h(int index)
995 if (index >= WMM_NUMTERMS) {
996 return 0;
999 return CoeffFile[index][5];
1002 int WorldMagModel::DateToYear(int month, int day, int year)
1004 // Converts a given calendar date into a decimal year
1006 int temp = 0; // Total number of days
1007 int MonthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
1008 int ExtraDay = 0;
1010 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
1011 ExtraDay = 1;
1013 MonthDays[2] += ExtraDay;
1015 /******************Validation********************************/
1017 if (month <= 0 || month > 12) {
1018 return -1; // error
1020 if (day <= 0 || day > MonthDays[month]) {
1021 return -2; // error
1023 /****************Calculation of t***************************/
1024 for (int i = 1; i <= month; i++) {
1025 temp += MonthDays[i - 1];
1027 temp += day;
1029 decimal_date = year + (temp - 1) / (365.0 + ExtraDay);
1031 return 0; // OK
1034 void WorldMagModel::GeodeticToSpherical(WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_CoordSpherical *CoordSpherical)
1036 // Converts Geodetic coordinates to Spherical coordinates
1037 // Convert geodetic coordinates, (defined by the WGS-84
1038 // reference ellipsoid), to Earth Centered Earth Fixed Cartesian
1039 // coordinates, and then to spherical coordinates.
1041 double CosLat = cos(DEG2RAD(CoordGeodetic->phi));
1042 double SinLat = sin(DEG2RAD(CoordGeodetic->phi));
1044 // compute the local radius of curvature on the WGS-84 reference ellipsoid
1045 double rc = Ellip.a / sqrt(1.0 - Ellip.epssq * SinLat * SinLat);
1047 // compute ECEF Cartesian coordinates of specified point (for longitude=0)
1048 double xp = (rc + CoordGeodetic->HeightAboveEllipsoid) * CosLat;
1049 double zp = (rc * (1.0 - Ellip.epssq) + CoordGeodetic->HeightAboveEllipsoid) * SinLat;
1051 // compute spherical radius and angle lambda and phi of specified point
1052 CoordSpherical->r = sqrt(xp * xp + zp * zp);
1053 CoordSpherical->phig = RAD2DEG(asin(zp / CoordSpherical->r)); // geocentric latitude
1054 CoordSpherical->lambda = CoordGeodetic->lambda; // longitude