2 ******************************************************************************
4 * @file WorldMagModel.c
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2015-2019.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * @brief Source file for the World Magnetic Model
8 * This is a port of code available from the US NOAA.
10 * The hard coded coefficients should be valid until 2020.
12 * Updated coeffs from ..
13 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_ddownload.shtml
15 * NASA C source code ..
16 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_wdownload.shtml
18 * Major changes include:
19 * - No geoid model (altitude must be geodetic WGS-84)
20 * - Floating point calculation (not double precision)
21 * - Hard coded coefficients for model
22 * - Elimination of user interface
23 * - Elimination of dynamic memory allocation
25 * @see The GNU Public License (GPL) Version 3
27 *****************************************************************************/
29 * This program is free software; you can redistribute it and/or modify
30 * it under the terms of the GNU General Public License as published by
31 * the Free Software Foundation; either version 3 of the License, or
32 * (at your option) any later version.
34 * This program is distributed in the hope that it will be useful, but
35 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
36 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
39 * You should have received a copy of the GNU General Public License along
40 * with this program; if not, write to the Free Software Foundation, Inc.,
41 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
44 #include "openpilot.h"
52 #include "WorldMagModel.h"
53 #include "WMMInternal.h"
55 #define MALLOC(x) pios_malloc(x)
56 #define FREE(x) vPortFree(x)
57 // #define MALLOC(x) malloc(x)
58 // #define FREE(x) free(x)
60 // http://reviews.openpilot.org/cru/OPReview-436#c6476 :
61 // first column not used but it will be optimized out by compiler
62 static const float CoeffFile
[91][6] = {
63 { 0.0f
, 0.0f
, 0.0f
, 0.0f
, 0.0f
, 0.0f
},
64 { 1.0f
, 0.0f
, -29438.2f
, 0.0f
, 7.0f
, 0.0f
},
65 { 1.0f
, 1.0f
, -1493.5f
, 4796.3f
, 9.0f
, -30.2f
},
66 { 2.0f
, 0.0f
, -2444.5f
, 0.0f
, -11.0f
, 0.0f
},
67 { 2.0f
, 1.0f
, 3014.7f
, -2842.4f
, -6.2f
, -29.6f
},
68 { 2.0f
, 2.0f
, 1679.0f
, -638.8f
, 0.3f
, -17.3f
},
69 { 3.0f
, 0.0f
, 1351.8f
, 0.0f
, 2.4f
, 0.0f
},
70 { 3.0f
, 1.0f
, -2351.6f
, -113.7f
, -5.7f
, 6.5f
},
71 { 3.0f
, 2.0f
, 1223.6f
, 246.5f
, 2.0f
, -0.8f
},
72 { 3.0f
, 3.0f
, 582.3f
, -537.4f
, -11.0f
, -2.0f
},
73 { 4.0f
, 0.0f
, 907.5f
, 0.0f
, -0.8f
, 0.0f
},
74 { 4.0f
, 1.0f
, 814.8f
, 283.3f
, -0.9f
, -0.4f
},
75 { 4.0f
, 2.0f
, 117.8f
, -188.6f
, -6.5f
, 5.8f
},
76 { 4.0f
, 3.0f
, -335.6f
, 180.7f
, 5.2f
, 3.8f
},
77 { 4.0f
, 4.0f
, 69.7f
, -330.0f
, -4.0f
, -3.5f
},
78 { 5.0f
, 0.0f
, -232.9f
, 0.0f
, -0.3f
, 0.0f
},
79 { 5.0f
, 1.0f
, 360.1f
, 46.9f
, 0.6f
, 0.2f
},
80 { 5.0f
, 2.0f
, 191.7f
, 196.5f
, -0.8f
, 2.3f
},
81 { 5.0f
, 3.0f
, -141.3f
, -119.9f
, 0.1f
, -0.0f
},
82 { 5.0f
, 4.0f
, -157.2f
, 16.0f
, 1.2f
, 3.3f
},
83 { 5.0f
, 5.0f
, 7.7f
, 100.6f
, 1.4f
, -0.6f
},
84 { 6.0f
, 0.0f
, 69.4f
, 0.0f
, -0.8f
, 0.0f
},
85 { 6.0f
, 1.0f
, 67.7f
, -20.1f
, -0.5f
, 0.3f
},
86 { 6.0f
, 2.0f
, 72.3f
, 32.8f
, -0.1f
, -1.5f
},
87 { 6.0f
, 3.0f
, -129.1f
, 59.1f
, 1.6f
, -1.2f
},
88 { 6.0f
, 4.0f
, -28.4f
, -67.1f
, -1.6f
, 0.4f
},
89 { 6.0f
, 5.0f
, 13.6f
, 8.1f
, 0.0f
, 0.2f
},
90 { 6.0f
, 6.0f
, -70.3f
, 61.9f
, 1.2f
, 1.3f
},
91 { 7.0f
, 0.0f
, 81.7f
, 0.0f
, -0.3f
, 0.0f
},
92 { 7.0f
, 1.0f
, -75.9f
, -54.3f
, -0.2f
, 0.6f
},
93 { 7.0f
, 2.0f
, -7.1f
, -19.5f
, -0.3f
, 0.5f
},
94 { 7.0f
, 3.0f
, 52.2f
, 6.0f
, 0.9f
, -0.8f
},
95 { 7.0f
, 4.0f
, 15.0f
, 24.5f
, 0.1f
, -0.2f
},
96 { 7.0f
, 5.0f
, 9.1f
, 3.5f
, -0.6f
, -1.1f
},
97 { 7.0f
, 6.0f
, -3.0f
, -27.7f
, -0.9f
, 0.1f
},
98 { 7.0f
, 7.0f
, 5.9f
, -2.9f
, 0.7f
, 0.2f
},
99 { 8.0f
, 0.0f
, 24.2f
, 0.0f
, -0.1f
, 0.0f
},
100 { 8.0f
, 1.0f
, 8.9f
, 10.1f
, 0.2f
, -0.4f
},
101 { 8.0f
, 2.0f
, -16.9f
, -18.3f
, -0.2f
, 0.6f
},
102 { 8.0f
, 3.0f
, -3.1f
, 13.3f
, 0.5f
, -0.1f
},
103 { 8.0f
, 4.0f
, -20.7f
, -14.5f
, -0.1f
, 0.6f
},
104 { 8.0f
, 5.0f
, 13.3f
, 16.2f
, 0.4f
, -0.2f
},
105 { 8.0f
, 6.0f
, 11.6f
, 6.0f
, 0.4f
, -0.5f
},
106 { 8.0f
, 7.0f
, -16.3f
, -9.2f
, -0.1f
, 0.5f
},
107 { 8.0f
, 8.0f
, -2.1f
, 2.4f
, 0.4f
, 0.1f
},
108 { 9.0f
, 0.0f
, 5.5f
, 0.0f
, -0.1f
, 0.0f
},
109 { 9.0f
, 1.0f
, 8.8f
, -21.8f
, -0.1f
, -0.3f
},
110 { 9.0f
, 2.0f
, 3.0f
, 10.7f
, -0.0f
, 0.1f
},
111 { 9.0f
, 3.0f
, -3.2f
, 11.8f
, 0.4f
, -0.4f
},
112 { 9.0f
, 4.0f
, 0.6f
, -6.8f
, -0.4f
, 0.3f
},
113 { 9.0f
, 5.0f
, -13.2f
, -6.9f
, 0.0f
, 0.1f
},
114 { 9.0f
, 6.0f
, -0.1f
, 7.9f
, 0.3f
, -0.0f
},
115 { 9.0f
, 7.0f
, 8.7f
, 1.0f
, 0.0f
, -0.1f
},
116 { 9.0f
, 8.0f
, -9.1f
, -3.9f
, -0.0f
, 0.5f
},
117 { 9.0f
, 9.0f
, -10.4f
, 8.5f
, -0.3f
, 0.2f
},
118 { 10.0f
, 0.0f
, -2.0f
, 0.0f
, 0.0f
, 0.0f
},
119 { 10.0f
, 1.0f
, -6.1f
, 3.3f
, -0.0f
, 0.0f
},
120 { 10.0f
, 2.0f
, 0.2f
, -0.4f
, -0.1f
, 0.1f
},
121 { 10.0f
, 3.0f
, 0.6f
, 4.6f
, 0.2f
, -0.2f
},
122 { 10.0f
, 4.0f
, -0.5f
, 4.4f
, -0.1f
, 0.1f
},
123 { 10.0f
, 5.0f
, 1.8f
, -7.9f
, -0.2f
, -0.1f
},
124 { 10.0f
, 6.0f
, -0.7f
, -0.6f
, -0.0f
, 0.1f
},
125 { 10.0f
, 7.0f
, 2.2f
, -4.2f
, -0.1f
, -0.0f
},
126 { 10.0f
, 8.0f
, 2.4f
, -2.9f
, -0.2f
, -0.1f
},
127 { 10.0f
, 9.0f
, -1.8f
, -1.1f
, -0.1f
, 0.2f
},
128 { 10.0f
, 10.0f
, -3.6f
, -8.8f
, -0.0f
, -0.0f
},
129 { 11.0f
, 0.0f
, 3.0f
, 0.0f
, -0.0f
, 0.0f
},
130 { 11.0f
, 1.0f
, -1.4f
, -0.0f
, 0.0f
, 0.0f
},
131 { 11.0f
, 2.0f
, -2.3f
, 2.1f
, -0.0f
, 0.1f
},
132 { 11.0f
, 3.0f
, 2.1f
, -0.6f
, 0.0f
, 0.0f
},
133 { 11.0f
, 4.0f
, -0.8f
, -1.1f
, -0.0f
, 0.1f
},
134 { 11.0f
, 5.0f
, 0.6f
, 0.7f
, -0.1f
, -0.0f
},
135 { 11.0f
, 6.0f
, -0.7f
, -0.2f
, 0.0f
, -0.0f
},
136 { 11.0f
, 7.0f
, 0.1f
, -2.1f
, -0.0f
, 0.1f
},
137 { 11.0f
, 8.0f
, 1.7f
, -1.5f
, -0.0f
, -0.0f
},
138 { 11.0f
, 9.0f
, -0.2f
, -2.6f
, -0.1f
, -0.1f
},
139 { 11.0f
, 10.0f
, 0.4f
, -2.0f
, -0.0f
, -0.0f
},
140 { 11.0f
, 11.0f
, 3.5f
, -2.3f
, -0.1f
, -0.1f
},
141 { 12.0f
, 0.0f
, -2.0f
, 0.0f
, 0.0f
, 0.0f
},
142 { 12.0f
, 1.0f
, -0.1f
, -1.0f
, 0.0f
, -0.0f
},
143 { 12.0f
, 2.0f
, 0.5f
, 0.3f
, -0.0f
, 0.0f
},
144 { 12.0f
, 3.0f
, 1.2f
, 1.8f
, 0.0f
, -0.1f
},
145 { 12.0f
, 4.0f
, -0.9f
, -2.2f
, -0.1f
, 0.1f
},
146 { 12.0f
, 5.0f
, 0.9f
, 0.3f
, -0.0f
, -0.0f
},
147 { 12.0f
, 6.0f
, 0.1f
, 0.7f
, 0.0f
, 0.0f
},
148 { 12.0f
, 7.0f
, 0.6f
, -0.1f
, -0.0f
, -0.0f
},
149 { 12.0f
, 8.0f
, -0.4f
, 0.3f
, 0.0f
, 0.0f
},
150 { 12.0f
, 9.0f
, -0.5f
, 0.2f
, -0.0f
, 0.0f
},
151 { 12.0f
, 10.0f
, 0.2f
, -0.9f
, -0.0f
, -0.0f
},
152 { 12.0f
, 11.0f
, -0.9f
, -0.2f
, -0.0f
, 0.0f
},
153 { 12.0f
, 12.0f
, -0.0f
, 0.8f
, -0.1f
, -0.1f
}
156 static WMMtype_Ellipsoid
*Ellip
= NULL
;
157 static WMMtype_MagneticModel
*MagneticModel
= NULL
;
158 static float decimal_date
;
160 /**************************************************************************************
161 * Example use - very simple - only two exposed functions
163 * WMM_Initialize(); // Set default values and constants
165 * WMM_GetMagVector(float Lat, float Lon, float Alt, uint16_t Month, uint16_t Day, uint16_t Year, float B[3]);
166 * e.g. Iceland in may of 2012 = WMM_GetMagVector(65.0, -20.0, 0.0, 5, 5, 2012, B);
167 * Alt is above the WGS-84 Ellipsoid
168 * B is the NED (XYZ) magnetic vector in nTesla
169 **************************************************************************************/
172 // Sets default values for WMM subroutines.
173 // UPDATES : Ellip and MagneticModel
176 return -1; // invalid pointer
178 if (!MagneticModel
) {
179 return -2; // invalid pointer
181 // Sets WGS-84 parameters
182 Ellip
->a
= 6378.137f
; // semi-major axis of the ellipsoid in km
183 Ellip
->b
= 6356.7523142f
; // semi-minor axis of the ellipsoid in km
184 Ellip
->fla
= 1.0f
/ 298.257223563f
; // flattening
185 Ellip
->eps
= sqrt(1 - (Ellip
->b
* Ellip
->b
) / (Ellip
->a
* Ellip
->a
)); // first eccentricity
186 Ellip
->epssq
= (Ellip
->eps
* Ellip
->eps
); // first eccentricity squared
187 Ellip
->re
= 6371.2f
; // Earth's radius in km
189 // Sets Magnetic Model parameters
190 MagneticModel
->nMax
= WMM_MAX_MODEL_DEGREES
;
191 MagneticModel
->nMaxSecVar
= WMM_MAX_SECULAR_VARIATION_MODEL_DEGREES
;
192 MagneticModel
->SecularVariationUsed
= 0;
194 // Really, Really needs to be read from a file - out of date in 2020 at latest
195 MagneticModel
->EditionDate
= 0.0f
; /* OP change. Originally 5.7863328170559505e-307, truncates to 0.0f */
196 MagneticModel
->epoch
= 2015.0f
;
197 sprintf(MagneticModel
->ModelName
, "WMM-2015v2");
202 int WMM_GetMagVector(float Lat
, float Lon
, float AltEllipsoid
, uint16_t Month
, uint16_t Day
, uint16_t Year
, float B
[3])
204 // return '0' if all appears to be OK
205 // return < 0 if error
207 int returned
= 0; // default to OK
210 // range check supplied params
225 // allocated required memory
228 // MagneticModel = NULL;
230 // MagneticModel = NULL;
231 // CoordGeodetic = NULL;
232 // GeoMagneticElements = NULL;
234 Ellip
= (WMMtype_Ellipsoid
*)MALLOC(sizeof(WMMtype_Ellipsoid
));
235 MagneticModel
= (WMMtype_MagneticModel
*)MALLOC(sizeof(WMMtype_MagneticModel
));
237 WMMtype_CoordSpherical
*CoordSpherical
= (WMMtype_CoordSpherical
*)MALLOC(sizeof(WMMtype_CoordSpherical
));
238 WMMtype_CoordGeodetic
*CoordGeodetic
= (WMMtype_CoordGeodetic
*)MALLOC(sizeof(WMMtype_CoordGeodetic
));
239 WMMtype_GeoMagneticElements
*GeoMagneticElements
= (WMMtype_GeoMagneticElements
*)MALLOC(sizeof(WMMtype_GeoMagneticElements
));
241 if (!Ellip
|| !MagneticModel
|| !CoordSpherical
|| !CoordGeodetic
|| !GeoMagneticElements
) {
242 returned
= -5; // error
247 if (WMM_Initialize() < 0) {
248 returned
= -6; // error
253 CoordGeodetic
->lambda
= Lon
;
254 CoordGeodetic
->phi
= Lat
;
255 CoordGeodetic
->HeightAboveEllipsoid
= AltEllipsoid
/ 1000.0f
; // convert to km
257 // Convert from geodetic to Spherical Equations: 17-18, WMM Technical report
258 if (WMM_GeodeticToSpherical(CoordGeodetic
, CoordSpherical
) < 0) {
259 returned
= -7; // error
265 if (WMM_DateToYear(Month
, Day
, Year
) < 0) {
266 returned
= -8; // error
271 // Compute the geoMagnetic field elements and their time change
272 if (WMM_Geomag(CoordSpherical
, CoordGeodetic
, GeoMagneticElements
) < 0) {
273 returned
= -9; // error
274 } else { // set the returned values
275 B
[0] = GeoMagneticElements
->X
;
276 B
[1] = GeoMagneticElements
->Y
;
277 B
[2] = GeoMagneticElements
->Z
;
282 // free allocated memory
284 if (GeoMagneticElements
) {
285 FREE(GeoMagneticElements
);
292 if (CoordSpherical
) {
293 FREE(CoordSpherical
);
298 MagneticModel
= NULL
;
306 B
[0] = GeoMagneticElements
->X
* 1e-2f
;
307 B
[1] = GeoMagneticElements
->Y
* 1e-2f
;
308 B
[2] = GeoMagneticElements
->Z
* 1e-2f
;
313 int WMM_Geomag(WMMtype_CoordSpherical
*CoordSpherical
, WMMtype_CoordGeodetic
*CoordGeodetic
, WMMtype_GeoMagneticElements
*GeoMagneticElements
)
315 The main subroutine that calls a sequence of WMM sub-functions to calculate the magnetic field elements for a single point.
316 The function expects the model coefficients and point coordinates as input and returns the magnetic field elements and
317 their rate of change. Though, this subroutine can be called successively to calculate a time series, profile or grid
318 of magnetic field, these are better achieved by the subroutine WMM_Grid.
325 OUTPUT : GeoMagneticElements
327 CALLS: WMM_ComputeSphericalHarmonicVariables( Ellip, CoordSpherical, TimedMagneticModel->nMax, &SphVariables); (Compute Spherical Harmonic variables )
328 WMM_AssociatedLegendreFunction(CoordSpherical, TimedMagneticModel->nMax, LegendreFunction); Compute ALF
329 WMM_Summation(LegendreFunction, TimedMagneticModel, SphVariables, CoordSpherical, &MagneticResultsSph); Accumulate the spherical harmonic coefficients
330 WMM_SecVarSummation(LegendreFunction, TimedMagneticModel, SphVariables, CoordSpherical, &MagneticResultsSphVar); Sum the Secular Variation Coefficients
331 WMM_RotateMagneticVector(CoordSpherical, CoordGeodetic, MagneticResultsSph, &MagneticResultsGeo); Map the computed Magnetic fields to Geodeitic coordinates
332 WMM_RotateMagneticVector(CoordSpherical, CoordGeodetic, MagneticResultsSphVar, &MagneticResultsGeoVar); Map the secular variation field components to Geodetic coordinates
333 WMM_CalculateGeoMagneticElements(&MagneticResultsGeo, GeoMagneticElements); Calculate the Geomagnetic elements
334 WMM_CalculateSecularVariation(MagneticResultsGeoVar, GeoMagneticElements); Calculate the secular variation of each of the Geomagnetic elements
338 int returned
= 0; // default to OK
340 WMMtype_MagneticResults MagneticResultsSph
;
341 WMMtype_MagneticResults MagneticResultsGeo
;
342 WMMtype_MagneticResults MagneticResultsSphVar
;
343 WMMtype_MagneticResults MagneticResultsGeoVar
;
346 // allocate required memory
348 WMMtype_LegendreFunction
*LegendreFunction
= (WMMtype_LegendreFunction
*)MALLOC(sizeof(WMMtype_LegendreFunction
));
349 WMMtype_SphericalHarmonicVariables
*SphVariables
= (WMMtype_SphericalHarmonicVariables
*)MALLOC(sizeof(WMMtype_SphericalHarmonicVariables
));
351 if (!LegendreFunction
|| !SphVariables
) {
352 returned
= -1; // memory allocation error
356 if (returned
>= 0) { // Compute Spherical Harmonic variables
357 if (WMM_ComputeSphericalHarmonicVariables(CoordSpherical
, MagneticModel
->nMax
, SphVariables
) < 0) {
358 returned
= -2; // error
362 if (returned
>= 0) { // Compute ALF
363 if (WMM_AssociatedLegendreFunction(CoordSpherical
, MagneticModel
->nMax
, LegendreFunction
) < 0) {
364 returned
= -3; // error
368 if (returned
>= 0) { // Accumulate the spherical harmonic coefficients
369 if (WMM_Summation(LegendreFunction
, SphVariables
, CoordSpherical
, &MagneticResultsSph
) < 0) {
370 returned
= -4; // error
374 if (returned
>= 0) { // Sum the Secular Variation Coefficients
375 if (WMM_SecVarSummation(LegendreFunction
, SphVariables
, CoordSpherical
, &MagneticResultsSphVar
) < 0) {
376 returned
= -5; // error
380 if (returned
>= 0) { // Map the computed Magnetic fields to Geodeitic coordinates
381 if (WMM_RotateMagneticVector(CoordSpherical
, CoordGeodetic
, &MagneticResultsSph
, &MagneticResultsGeo
) < 0) {
382 returned
= -6; // error
386 if (returned
>= 0) { // Map the secular variation field components to Geodetic coordinates
387 if (WMM_RotateMagneticVector(CoordSpherical
, CoordGeodetic
, &MagneticResultsSphVar
, &MagneticResultsGeoVar
) < 0) {
388 returned
= -7; // error
392 if (returned
>= 0) { // Calculate the Geomagnetic elements, Equation 18 , WMM Technical report
393 if (WMM_CalculateGeoMagneticElements(&MagneticResultsGeo
, GeoMagneticElements
) < 0) {
394 returned
= -8; // error
398 if (returned
>= 0) { // Calculate the secular variation of each of the Geomagnetic elements
399 if (WMM_CalculateSecularVariation(&MagneticResultsGeoVar
, GeoMagneticElements
) < 0) {
400 returned
= -9; // error
405 // free allocated memory
411 if (LegendreFunction
) {
412 FREE(LegendreFunction
);
420 int WMM_ComputeSphericalHarmonicVariables(WMMtype_CoordSpherical
*CoordSpherical
, uint16_t nMax
, WMMtype_SphericalHarmonicVariables
*SphVariables
)
421 /* Computes Spherical variables
422 Variables computed are (a/r)^(n+2), cos_m(lamda) and sin_m(lambda) for spherical harmonic
423 summations. (Equations 10-12 in the WMM Technical Report)
424 INPUT Ellip data structure with the following elements
425 float a; semi-major axis of the ellipsoid
426 float b; semi-minor axis of the ellipsoid
427 float fla; flattening
428 float epssq; first eccentricity squared
429 float eps; first eccentricity
430 float re; mean radius of ellipsoid
431 CoordSpherical A data structure with the following elements
432 float lambda; ( longitude)
433 float phig; ( geocentric latitude )
434 float r; ( distance from the center of the ellipsoid)
435 nMax integer ( Maxumum degree of spherical harmonic secular model)\
437 OUTPUT SphVariables Pointer to the data structure with the following elements
438 float RelativeRadiusPower[WMM_MAX_MODEL_DEGREES+1]; [earth_reference_radius_km sph. radius ]^n
439 float cos_mlambda[WMM_MAX_MODEL_DEGREES+1]; cp(m) - cosine of (mspherical coord. longitude)
440 float sin_mlambda[WMM_MAX_MODEL_DEGREES+1]; sp(m) - sine of (mspherical coord. longitude)
444 float cos_lambda
, sin_lambda
;
447 cos_lambda
= cosf(DEG2RAD(CoordSpherical
->lambda
));
448 sin_lambda
= sinf(DEG2RAD(CoordSpherical
->lambda
));
450 /* for n = 0 ... model_order, compute (Radius of Earth / Spherica radius r)^(n+2)
451 for n 1..nMax-1 (this is much faster than calling pow MAX_N+1 times). */
453 SphVariables
->RelativeRadiusPower
[0] = (Ellip
->re
/ CoordSpherical
->r
) * (Ellip
->re
/ CoordSpherical
->r
);
454 for (n
= 1; n
<= nMax
; n
++) {
455 SphVariables
->RelativeRadiusPower
[n
] = SphVariables
->RelativeRadiusPower
[n
- 1] * (Ellip
->re
/ CoordSpherical
->r
);
459 Compute cosf(m*lambda), sinf(m*lambda) for m = 0 ... nMax
460 cosf(a + b) = cosf(a)*cosf(b) - sinf(a)*sinf(b)
461 sinf(a + b) = cosf(a)*sinf(b) + sinf(a)*cosf(b)
463 SphVariables
->cos_mlambda
[0] = 1.0f
;
464 SphVariables
->sin_mlambda
[0] = 0.0f
;
466 SphVariables
->cos_mlambda
[1] = cos_lambda
;
467 SphVariables
->sin_mlambda
[1] = sin_lambda
;
468 for (m
= 2; m
<= nMax
; m
++) {
469 SphVariables
->cos_mlambda
[m
] = SphVariables
->cos_mlambda
[m
- 1] * cos_lambda
- SphVariables
->sin_mlambda
[m
- 1] * sin_lambda
;
470 SphVariables
->sin_mlambda
[m
] = SphVariables
->cos_mlambda
[m
- 1] * sin_lambda
+ SphVariables
->sin_mlambda
[m
- 1] * cos_lambda
;
476 int WMM_AssociatedLegendreFunction(WMMtype_CoordSpherical
*CoordSpherical
, uint16_t nMax
, WMMtype_LegendreFunction
*LegendreFunction
)
477 /* Computes all of the Schmidt-semi normalized associated Legendre
478 functions up to degree nMax. If nMax <= 16, function WMM_PcupLow is used.
479 Otherwise WMM_PcupHigh is called.
480 INPUT CoordSpherical A data structure with the following elements
481 float lambda; ( longitude)
482 float phig; ( geocentric latitude )
483 float r; ( distance from the center of the ellipsoid)
484 nMax integer ( Maxumum degree of spherical harmonic secular model)
485 LegendreFunction Pointer to data structure with the following elements
486 float *Pcup; ( pointer to store Legendre Function )
487 float *dPcup; ( pointer to store Derivative of Lagendre function )
489 OUTPUT LegendreFunction Calculated Legendre variables in the data structure
493 float sin_phi
= sinf(DEG2RAD(CoordSpherical
->phig
)); /* sinf (geocentric latitude) */
495 if (nMax
<= 16 || (1 - fabsf(sin_phi
)) < 1.0e-10f
) { /* If nMax is less tha 16 or at the poles */
496 if (WMM_PcupLow(LegendreFunction
->Pcup
, LegendreFunction
->dPcup
, sin_phi
, nMax
) < 0) {
500 if (WMM_PcupHigh(LegendreFunction
->Pcup
, LegendreFunction
->dPcup
, sin_phi
, nMax
) < 0) {
508 int WMM_Summation(WMMtype_LegendreFunction
*LegendreFunction
,
509 WMMtype_SphericalHarmonicVariables
*SphVariables
,
510 WMMtype_CoordSpherical
*CoordSpherical
, WMMtype_MagneticResults
*MagneticResults
)
512 /* Computes Geomagnetic Field Elements X, Y and Z in Spherical coordinate system using
513 spherical harmonic summation.
515 The vector Magnetic field is given by -grad V, where V is Geomagnetic scalar potential
516 The gradient in spherical coordinates is given by:
519 grad V = -- r + - -- t + -------- -- p
522 INPUT : LegendreFunction
526 OUTPUT : MagneticResults
528 CALLS : WMM_SummationSpecial
530 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
533 uint16_t m
, n
, index
;
536 MagneticResults
->Bz
= 0.0f
;
537 MagneticResults
->By
= 0.0f
;
538 MagneticResults
->Bx
= 0.0f
;
540 for (n
= 1; n
<= MagneticModel
->nMax
; n
++) {
541 for (m
= 0; m
<= n
; m
++) {
542 index
= (n
* (n
+ 1) / 2 + m
);
544 /* nMax (n+2) n m m m
545 Bz = -SUM (a/r) (n+1) SUM [g cosf(m p) + h sinf(m p)] P (sinf(phi))
547 /* Equation 12 in the WMM Technical report. Derivative with respect to radius.*/
548 MagneticResults
->Bz
-=
549 SphVariables
->RelativeRadiusPower
[n
] *
550 (WMM_get_main_field_coeff_g(index
) *
551 SphVariables
->cos_mlambda
[m
] + WMM_get_main_field_coeff_h(index
) * SphVariables
->sin_mlambda
[m
])
552 * (float)(n
+ 1) * LegendreFunction
->Pcup
[index
];
554 /* 1 nMax (n+2) n m m m
555 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
557 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
558 MagneticResults
->By
+=
559 SphVariables
->RelativeRadiusPower
[n
] *
560 (WMM_get_main_field_coeff_g(index
) *
561 SphVariables
->sin_mlambda
[m
] - WMM_get_main_field_coeff_h(index
) * SphVariables
->cos_mlambda
[m
])
562 * (float)(m
) * LegendreFunction
->Pcup
[index
];
563 /* nMax (n+2) n m m m
564 Bx = - SUM (a/r) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
566 /* Equation 10 in the WMM Technical report. Derivative with respect to latitude, divided by radius. */
568 MagneticResults
->Bx
-=
569 SphVariables
->RelativeRadiusPower
[n
] *
570 (WMM_get_main_field_coeff_g(index
) *
571 SphVariables
->cos_mlambda
[m
] + WMM_get_main_field_coeff_h(index
) * SphVariables
->sin_mlambda
[m
])
572 * LegendreFunction
->dPcup
[index
];
576 cos_phi
= cosf(DEG2RAD(CoordSpherical
->phig
));
577 if (fabsf(cos_phi
) > 1.0e-10f
) {
578 MagneticResults
->By
= MagneticResults
->By
/ cos_phi
;
580 /* Special calculation for component - By - at Geographic poles.
581 * If the user wants to avoid using this function, please make sure that
582 * the latitude is not exactly +/-90. An option is to make use the function
583 * WMM_CheckGeographicPoles.
585 if (WMM_SummationSpecial(SphVariables
, CoordSpherical
, MagneticResults
) < 0) {
593 int WMM_SecVarSummation(WMMtype_LegendreFunction
*LegendreFunction
,
594 WMMtype_SphericalHarmonicVariables
*
595 SphVariables
, WMMtype_CoordSpherical
*CoordSpherical
, WMMtype_MagneticResults
*MagneticResults
)
597 /*This Function sums the secular variation coefficients to get the secular variation of the Magnetic vector.
598 INPUT : LegendreFunction
602 OUTPUT : MagneticResults
604 CALLS : WMM_SecVarSummationSpecial
608 uint16_t m
, n
, index
;
611 MagneticModel
->SecularVariationUsed
= TRUE
;
613 MagneticResults
->Bz
= 0.0f
;
614 MagneticResults
->By
= 0.0f
;
615 MagneticResults
->Bx
= 0.0f
;
617 for (n
= 1; n
<= MagneticModel
->nMaxSecVar
; n
++) {
618 for (m
= 0; m
<= n
; m
++) {
619 index
= (n
* (n
+ 1) / 2 + m
);
621 /* nMax (n+2) n m m m
622 Bz = -SUM (a/r) (n+1) SUM [g cosf(m p) + h sinf(m p)] P (sinf(phi))
624 /* Derivative with respect to radius.*/
625 MagneticResults
->Bz
-=
626 SphVariables
->RelativeRadiusPower
[n
] *
627 (WMM_get_secular_var_coeff_g(index
) *
628 SphVariables
->cos_mlambda
[m
] + WMM_get_secular_var_coeff_h(index
) * SphVariables
->sin_mlambda
[m
])
629 * (float)(n
+ 1) * LegendreFunction
->Pcup
[index
];
631 /* 1 nMax (n+2) n m m m
632 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
634 /* Derivative with respect to longitude, divided by radius. */
635 MagneticResults
->By
+=
636 SphVariables
->RelativeRadiusPower
[n
] *
637 (WMM_get_secular_var_coeff_g(index
) *
638 SphVariables
->sin_mlambda
[m
] - WMM_get_secular_var_coeff_h(index
) * SphVariables
->cos_mlambda
[m
])
639 * (float)(m
) * LegendreFunction
->Pcup
[index
];
640 /* nMax (n+2) n m m m
641 Bx = - SUM (a/r) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
643 /* Derivative with respect to latitude, divided by radius. */
645 MagneticResults
->Bx
-=
646 SphVariables
->RelativeRadiusPower
[n
] *
647 (WMM_get_secular_var_coeff_g(index
) *
648 SphVariables
->cos_mlambda
[m
] + WMM_get_secular_var_coeff_h(index
) * SphVariables
->sin_mlambda
[m
])
649 * LegendreFunction
->dPcup
[index
];
652 cos_phi
= cosf(DEG2RAD(CoordSpherical
->phig
));
653 if (fabsf(cos_phi
) > 1.0e-10f
) {
654 MagneticResults
->By
= MagneticResults
->By
/ cos_phi
;
656 /* Special calculation for component By at Geographic poles */
657 if (WMM_SecVarSummationSpecial(SphVariables
, CoordSpherical
, MagneticResults
) < 0) {
665 int WMM_RotateMagneticVector(WMMtype_CoordSpherical
*CoordSpherical
,
666 WMMtype_CoordGeodetic
*CoordGeodetic
,
667 WMMtype_MagneticResults
*MagneticResultsSph
, WMMtype_MagneticResults
*MagneticResultsGeo
)
668 /* Rotate the Magnetic Vectors to Geodetic Coordinates
669 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
670 Equation 16, WMM Technical report
672 INPUT : CoordSpherical : Data structure WMMtype_CoordSpherical with the following elements
673 float lambda; ( longitude)
674 float phig; ( geocentric latitude )
675 float r; ( distance from the center of the ellipsoid)
677 CoordGeodetic : Data structure WMMtype_CoordGeodetic with the following elements
678 float lambda; (longitude)
679 float phi; ( geodetic latitude)
680 float HeightAboveEllipsoid; (height above the ellipsoid (HaE) )
681 float HeightAboveGeoid;(height above the Geoid )
683 MagneticResultsSph : Data structure WMMtype_MagneticResults with the following elements
688 OUTPUT: MagneticResultsGeo Pointer to the data structure WMMtype_MagneticResults, with the following elements
697 /* Difference between the spherical and Geodetic latitudes */
698 float Psi
= DEG2RAD(CoordSpherical
->phig
- CoordGeodetic
->phi
);
700 /* Rotate spherical field components to the Geodeitic system */
701 MagneticResultsGeo
->Bz
= MagneticResultsSph
->Bx
* sinf(Psi
) + MagneticResultsSph
->Bz
* cosf(Psi
);
702 MagneticResultsGeo
->Bx
= MagneticResultsSph
->Bx
* cosf(Psi
) - MagneticResultsSph
->Bz
* sinf(Psi
);
703 MagneticResultsGeo
->By
= MagneticResultsSph
->By
;
708 int WMM_CalculateGeoMagneticElements(WMMtype_MagneticResults
*MagneticResultsGeo
, WMMtype_GeoMagneticElements
*GeoMagneticElements
)
709 /* Calculate all the Geomagnetic elements from X,Y and Z components
710 INPUT MagneticResultsGeo Pointer to data structure with the following elements
714 OUTPUT GeoMagneticElements Pointer to data structure with the following elements
715 float Decl; (Angle between the magnetic field vector and true north, positive east)
716 float Incl; Angle between the magnetic field vector and the horizontal plane, positive down
717 float F; Magnetic Field Strength
718 float H; Horizontal Magnetic Field Strength
719 float X; Northern component of the magnetic field vector
720 float Y; Eastern component of the magnetic field vector
721 float Z; Downward component of the magnetic field vector
725 GeoMagneticElements
->X
= MagneticResultsGeo
->Bx
;
726 GeoMagneticElements
->Y
= MagneticResultsGeo
->By
;
727 GeoMagneticElements
->Z
= MagneticResultsGeo
->Bz
;
729 GeoMagneticElements
->H
= sqrtf(MagneticResultsGeo
->Bx
* MagneticResultsGeo
->Bx
+ MagneticResultsGeo
->By
* MagneticResultsGeo
->By
);
730 GeoMagneticElements
->F
= sqrtf(GeoMagneticElements
->H
* GeoMagneticElements
->H
+ MagneticResultsGeo
->Bz
* MagneticResultsGeo
->Bz
);
731 GeoMagneticElements
->Decl
= RAD2DEG(atan2f(GeoMagneticElements
->Y
, GeoMagneticElements
->X
));
732 GeoMagneticElements
->Incl
= RAD2DEG(atan2f(GeoMagneticElements
->Z
, GeoMagneticElements
->H
));
737 int WMM_CalculateSecularVariation(WMMtype_MagneticResults
*MagneticVariation
, WMMtype_GeoMagneticElements
*MagneticElements
)
738 /*This takes the Magnetic Variation in x, y, and z and uses it to calculate the secular variation of each of the Geomagnetic elements.
739 INPUT MagneticVariation Data structure with the following elements
743 OUTPUT MagneticElements Pointer to the data structure with the following elements updated
744 float Decldot; Yearly Rate of change in declination
745 float Incldot; Yearly Rate of change in inclination
746 float Fdot; Yearly rate of change in Magnetic field strength
747 float Hdot; Yearly rate of change in horizontal field strength
748 float Xdot; Yearly rate of change in the northern component
749 float Ydot; Yearly rate of change in the eastern component
750 float Zdot; Yearly rate of change in the downward component
751 float GVdot;Yearly rate of chnage in grid variation
756 MagneticElements
->Xdot
= MagneticVariation
->Bx
;
757 MagneticElements
->Ydot
= MagneticVariation
->By
;
758 MagneticElements
->Zdot
= MagneticVariation
->Bz
;
759 MagneticElements
->Hdot
= (MagneticElements
->X
* MagneticElements
->Xdot
+ MagneticElements
->Y
* MagneticElements
->Ydot
) / MagneticElements
->H
; // See equation 19 in the WMM technical report
760 MagneticElements
->Fdot
=
761 (MagneticElements
->X
* MagneticElements
->Xdot
+
762 MagneticElements
->Y
* MagneticElements
->Ydot
+ MagneticElements
->Z
* MagneticElements
->Zdot
) / MagneticElements
->F
;
763 MagneticElements
->Decldot
=
764 180.0f
/ M_PI_F
* (MagneticElements
->X
* MagneticElements
->Ydot
-
765 MagneticElements
->Y
* MagneticElements
->Xdot
) / (MagneticElements
->H
* MagneticElements
->H
);
766 MagneticElements
->Incldot
=
767 180.0f
/ M_PI_F
* (MagneticElements
->H
* MagneticElements
->Zdot
-
768 MagneticElements
->Z
* MagneticElements
->Hdot
) / (MagneticElements
->F
* MagneticElements
->F
);
769 MagneticElements
->GVdot
= MagneticElements
->Decldot
;
774 int WMM_PcupHigh(float *Pcup
, float *dPcup
, float x
, uint16_t nMax
)
775 /* This function evaluates all of the Schmidt-semi normalized associated Legendre
776 functions up to degree nMax. The functions are initially scaled by
777 10^280 sinf^m in order to minimize the effects of underflow at large m
778 near the poles (see Holmes and Featherstone 2002, J. Geodesy, 76, 279-299).
779 Note that this function performs the same operation as WMM_PcupLow.
780 However this function also can be used for high degree (large nMax) models.
784 nMax: Maximum spherical harmonic degree to compute.
785 x: cosf(colatitude) or sinf(latitude).
788 Pcup: A vector of all associated Legendgre polynomials evaluated at
789 x up to nMax. The lenght must by greater or equal to (nMax+1)*(nMax+2)/2.
790 dPcup: Derivative of Pcup(x) with respect to latitude
795 Adopted from the FORTRAN code written by Mark Wieczorek September 25, 2005.
797 Manoj Nair, Nov, 2009 Manoj.C.Nair@Noaa.Gov
799 Change from the previous version
800 The prevous version computes the derivatives as
801 dP(n,m)(x)/dx, where x = sinf(latitude) (or cosf(colatitude) ).
802 However, the WMM Geomagnetic routines requires dP(n,m)(x)/dlatitude.
803 Hence the derivatives are multiplied by sinf(latitude).
804 Removed the options for CS phase and normalizations.
806 Note: In geomagnetism, the derivatives of ALF are usually found with
807 respect to the colatitudes. Here the derivatives are found with respect
808 to the latitude. The difference is a sign reversal for the derivative of
809 the Associated Legendre Functions.
811 The derivates can't be computed for latitude = |90| degrees.
814 uint16_t k
, kstart
, m
, n
;
815 float pm2
, pm1
, pmm
, plm
, rescalem
, z
, scalef
;
817 float *f1
= (float *)MALLOC(sizeof(float) * NUMPCUP
);
818 float *f2
= (float *)MALLOC(sizeof(float) * NUMPCUP
);
819 float *PreSqr
= (float *)MALLOC(sizeof(float) * NUMPCUP
);
821 if (!PreSqr
|| !f2
|| !f1
) { // memory allocation error
836 * Note: OP code change to avoid floating point equality test.
837 * Was: if (fabs(x) == 1.0)
839 if (fabsf(x
) - 1.0f
< 1e-9f
) {
844 // printf("Error in PcupHigh: derivative cannot be calculated at poles\n");
848 /* OP Change: 1.0e-280 is too small to store in a float - the compiler truncates
849 * it to 0.0f, which is bad as the code below divides by scalef. */
852 for (n
= 0; n
<= 2 * nMax
+ 1; ++n
) {
853 PreSqr
[n
] = sqrtf((float)(n
));
858 for (n
= 2; n
<= nMax
; n
++) {
860 f1
[k
] = (float)(2 * n
- 1) / (float)(n
);
861 f2
[k
] = (float)(n
- 1) / (float)(n
);
862 for (m
= 1; m
<= n
- 2; m
++) {
864 f1
[k
] = (float)(2 * n
- 1) / PreSqr
[n
+ m
] / PreSqr
[n
- m
];
865 f2
[k
] = PreSqr
[n
- m
- 1] * PreSqr
[n
+ m
- 1] / PreSqr
[n
+ m
] / PreSqr
[n
- m
];
870 /*z = sinf (geocentric latitude) */
871 z
= sqrtf((1.0f
- x
) * (1.0f
+ x
));
886 for (n
= 2; n
<= nMax
; n
++) {
888 plm
= f1
[k
] * x
* pm1
- f2
[k
] * pm2
;
890 dPcup
[k
] = (float)(n
) * (pm1
- x
* plm
) / z
;
895 pmm
= PreSqr
[2] * scalef
;
896 rescalem
= 1.0f
/ scalef
;
899 for (m
= 1; m
<= nMax
- 1; ++m
) {
900 rescalem
= rescalem
* z
;
902 /* Calculate Pcup(m,m) */
903 kstart
= kstart
+ m
+ 1;
904 pmm
= pmm
* PreSqr
[2 * m
+ 1] / PreSqr
[2 * m
];
905 Pcup
[kstart
] = pmm
* rescalem
/ PreSqr
[2 * m
+ 1];
906 dPcup
[kstart
] = -((float)(m
) * x
* Pcup
[kstart
] / z
);
907 pm2
= pmm
/ PreSqr
[2 * m
+ 1];
908 /* Calculate Pcup(m+1,m) */
910 pm1
= x
* PreSqr
[2 * m
+ 1] * pm2
;
911 Pcup
[k
] = pm1
* rescalem
;
912 dPcup
[k
] = ((pm2
* rescalem
) * PreSqr
[2 * m
+ 1] - x
* (float)(m
+ 1) * Pcup
[k
]) / z
;
913 /* Calculate Pcup(n,m) */
914 for (n
= m
+ 2; n
<= nMax
; ++n
) {
916 plm
= x
* f1
[k
] * pm1
- f2
[k
] * pm2
;
917 Pcup
[k
] = plm
* rescalem
;
918 dPcup
[k
] = (PreSqr
[n
+ m
] * PreSqr
[n
- m
] * (pm1
* rescalem
) - (float)(n
) * x
* Pcup
[k
]) / z
;
924 /* Calculate Pcup(nMax,nMax) */
925 rescalem
= rescalem
* z
;
926 kstart
= kstart
+ m
+ 1;
927 pmm
= pmm
/ PreSqr
[2 * nMax
];
928 Pcup
[kstart
] = pmm
* rescalem
;
929 dPcup
[kstart
] = -(float)(nMax
) * x
* Pcup
[kstart
] / z
;
932 // free allocated memory
943 int WMM_PcupLow(float *Pcup
, float *dPcup
, float x
, uint16_t nMax
)
944 /* This function evaluates all of the Schmidt-semi normalized associated Legendre
945 functions up to degree nMax.
949 nMax: Maximum spherical harmonic degree to compute.
950 x: cosf(colatitude) or sinf(latitude).
953 Pcup: A vector of all associated Legendgre polynomials evaluated at
955 dPcup: Derivative of Pcup(x) with respect to latitude
957 Notes: Overflow may occur if nMax > 20 , especially for high-latitudes.
958 Use WMM_PcupHigh for large nMax.
960 Writted by Manoj Nair, June, 2009 . Manoj.C.Nair@Noaa.Gov.
962 Note: In geomagnetism, the derivatives of ALF are usually found with
963 respect to the colatitudes. Here the derivatives are found with respect
964 to the latitude. The difference is a sign reversal for the derivative of
965 the Associated Legendre Functions.
968 uint16_t n
, m
, index
, index1
, index2
;
971 float *schmidtQuasiNorm
= (float *)MALLOC(sizeof(float) * NUMPCUP
);
973 if (!schmidtQuasiNorm
) { // memory allocation error
980 /*sinf (geocentric latitude) - sin_phi */
981 z
= sqrtf((1.0f
- x
) * (1.0f
+ x
));
983 /* First, Compute the Gauss-normalized associated Legendre functions */
984 for (n
= 1; n
<= nMax
; n
++) {
985 for (m
= 0; m
<= n
; m
++) {
986 index
= (n
* (n
+ 1) / 2 + m
);
988 index1
= (n
- 1) * n
/ 2 + m
- 1;
989 Pcup
[index
] = z
* Pcup
[index1
];
990 dPcup
[index
] = z
* dPcup
[index1
] + x
* Pcup
[index1
];
991 } else if (n
== 1 && m
== 0) {
992 index1
= (n
- 1) * n
/ 2 + m
;
993 Pcup
[index
] = x
* Pcup
[index1
];
994 dPcup
[index
] = x
* dPcup
[index1
] - z
* Pcup
[index1
];
995 } else if (n
> 1 && n
!= m
) {
996 index1
= (n
- 2) * (n
- 1) / 2 + m
;
997 index2
= (n
- 1) * n
/ 2 + m
;
999 Pcup
[index
] = x
* Pcup
[index2
];
1000 dPcup
[index
] = x
* dPcup
[index2
] - z
* Pcup
[index2
];
1002 k
= (float)(((n
- 1) * (n
- 1)) - (m
* m
)) / (float)((2 * n
- 1)
1004 Pcup
[index
] = x
* Pcup
[index2
] - k
* Pcup
[index1
];
1005 dPcup
[index
] = x
* dPcup
[index2
] - z
* Pcup
[index2
] - k
* dPcup
[index1
];
1010 /*Compute the ration between the Gauss-normalized associated Legendre
1011 functions and the Schmidt quasi-normalized version. This is equivalent to
1012 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
1014 schmidtQuasiNorm
[0] = 1.0f
;
1015 for (n
= 1; n
<= nMax
; n
++) {
1016 index
= (n
* (n
+ 1) / 2);
1017 index1
= (n
- 1) * n
/ 2;
1019 schmidtQuasiNorm
[index
] = schmidtQuasiNorm
[index1
] * (float)(2 * n
- 1) / (float)n
;
1021 for (m
= 1; m
<= n
; m
++) {
1022 index
= (n
* (n
+ 1) / 2 + m
);
1023 index1
= (n
* (n
+ 1) / 2 + m
- 1);
1024 schmidtQuasiNorm
[index
] = schmidtQuasiNorm
[index1
] * sqrtf((float)((n
- m
+ 1) * (m
== 1 ? 2 : 1)) / (float)(n
+ m
));
1028 /* Converts the Gauss-normalized associated Legendre
1029 functions to the Schmidt quasi-normalized version using pre-computed
1030 relation stored in the variable schmidtQuasiNorm */
1032 for (n
= 1; n
<= nMax
; n
++) {
1033 for (m
= 0; m
<= n
; m
++) {
1034 index
= (n
* (n
+ 1) / 2 + m
);
1035 Pcup
[index
] = Pcup
[index
] * schmidtQuasiNorm
[index
];
1036 dPcup
[index
] = -dPcup
[index
] * schmidtQuasiNorm
[index
];
1037 /* The sign is changed since the new WMM routines use derivative with respect to latitude
1038 insted of co-latitude */
1042 FREE(schmidtQuasiNorm
);
1047 int WMM_SummationSpecial(WMMtype_SphericalHarmonicVariables
*
1048 SphVariables
, WMMtype_CoordSpherical
*CoordSpherical
, WMMtype_MagneticResults
*MagneticResults
)
1049 /* Special calculation for the component By at Geographic poles.
1050 Manoj Nair, June, 2009 manoj.c.nair@noaa.gov
1051 INPUT: MagneticModel
1054 OUTPUT: MagneticResults
1056 See Section 1.4, "SINGULARITIES AT THE GEOGRAPHIC POLES", WMM Technical report
1062 float schmidtQuasiNorm1
;
1063 float schmidtQuasiNorm2
;
1064 float schmidtQuasiNorm3
;
1066 float *PcupS
= (float *)MALLOC(sizeof(float) * NUMPCUPS
);
1069 return -1; // memory allocation error
1072 schmidtQuasiNorm1
= 1.0f
;
1074 MagneticResults
->By
= 0.0f
;
1075 sin_phi
= sinf(DEG2RAD(CoordSpherical
->phig
));
1077 for (n
= 1; n
<= MagneticModel
->nMax
; n
++) {
1078 /*Compute the ration between the Gauss-normalized associated Legendre
1079 functions and the Schmidt quasi-normalized version. This is equivalent to
1080 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
1082 index
= (n
* (n
+ 1) / 2 + 1);
1083 schmidtQuasiNorm2
= schmidtQuasiNorm1
* (float)(2 * n
- 1) / (float)n
;
1084 schmidtQuasiNorm3
= schmidtQuasiNorm2
* sqrtf((float)(n
* 2) / (float)(n
+ 1));
1085 schmidtQuasiNorm1
= schmidtQuasiNorm2
;
1087 PcupS
[n
] = PcupS
[n
- 1];
1089 k
= (float)(((n
- 1) * (n
- 1)) - 1) / (float)((2 * n
- 1) * (2 * n
- 3));
1090 PcupS
[n
] = sin_phi
* PcupS
[n
- 1] - k
* PcupS
[n
- 2];
1093 /* 1 nMax (n+2) n m m m
1094 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
1096 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
1098 MagneticResults
->By
+=
1099 SphVariables
->RelativeRadiusPower
[n
] *
1100 (WMM_get_main_field_coeff_g(index
) *
1101 SphVariables
->sin_mlambda
[1] - WMM_get_main_field_coeff_h(index
) * SphVariables
->cos_mlambda
[1])
1102 * PcupS
[n
] * schmidtQuasiNorm3
;
1110 int WMM_SecVarSummationSpecial(WMMtype_SphericalHarmonicVariables
*
1111 SphVariables
, WMMtype_CoordSpherical
*CoordSpherical
, WMMtype_MagneticResults
*MagneticResults
)
1113 /*Special calculation for the secular variation summation at the poles.
1115 INPUT: MagneticModel
1118 OUTPUT: MagneticResults
1124 float schmidtQuasiNorm1
;
1125 float schmidtQuasiNorm2
;
1126 float schmidtQuasiNorm3
;
1128 float *PcupS
= (float *)MALLOC(sizeof(float) * NUMPCUPS
);
1131 return -1; // memory allocation error
1134 schmidtQuasiNorm1
= 1.0f
;
1136 MagneticResults
->By
= 0.0f
;
1137 sin_phi
= sinf(DEG2RAD(CoordSpherical
->phig
));
1139 for (n
= 1; n
<= MagneticModel
->nMaxSecVar
; n
++) {
1140 index
= (n
* (n
+ 1) / 2 + 1);
1141 schmidtQuasiNorm2
= schmidtQuasiNorm1
* (float)(2 * n
- 1) / (float)n
;
1142 schmidtQuasiNorm3
= schmidtQuasiNorm2
* sqrtf((float)(n
* 2) / (float)(n
+ 1));
1143 schmidtQuasiNorm1
= schmidtQuasiNorm2
;
1145 PcupS
[n
] = PcupS
[n
- 1];
1147 k
= (float)(((n
- 1) * (n
- 1)) - 1) / (float)((2 * n
- 1) * (2 * n
- 3));
1148 PcupS
[n
] = sin_phi
* PcupS
[n
- 1] - k
* PcupS
[n
- 2];
1151 /* 1 nMax (n+2) n m m m
1152 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
1154 /* Derivative with respect to longitude, divided by radius. */
1156 MagneticResults
->By
+=
1157 SphVariables
->RelativeRadiusPower
[n
] *
1158 (WMM_get_secular_var_coeff_g(index
) *
1159 SphVariables
->sin_mlambda
[1] - WMM_get_secular_var_coeff_h(index
) * SphVariables
->cos_mlambda
[1])
1160 * PcupS
[n
] * schmidtQuasiNorm3
;
1169 * @brief Comput the MainFieldCoeffH accounting for the date
1171 float WMM_get_main_field_coeff_g(uint16_t index
)
1173 if (index
>= NUMTERMS
) {
1177 uint16_t n
, m
, sum_index
, a
, b
;
1179 float coeff
= CoeffFile
[index
][2];
1181 a
= MagneticModel
->nMaxSecVar
;
1182 b
= (a
* (a
+ 1) / 2 + a
);
1183 for (n
= 1; n
<= MagneticModel
->nMax
; n
++) {
1184 for (m
= 0; m
<= n
; m
++) {
1185 sum_index
= (n
* (n
+ 1) / 2 + m
);
1187 /* Hacky for now, will solve for which conditions need summing analytically */
1188 if (sum_index
!= index
) {
1193 coeff
+= (decimal_date
- MagneticModel
->epoch
) * WMM_get_secular_var_coeff_g(sum_index
);
1201 float WMM_get_main_field_coeff_h(uint16_t index
)
1203 if (index
>= NUMTERMS
) {
1207 uint16_t n
, m
, sum_index
, a
, b
;
1208 float coeff
= CoeffFile
[index
][3];
1210 a
= MagneticModel
->nMaxSecVar
;
1211 b
= (a
* (a
+ 1) / 2 + a
);
1212 for (n
= 1; n
<= MagneticModel
->nMax
; n
++) {
1213 for (m
= 0; m
<= n
; m
++) {
1214 sum_index
= (n
* (n
+ 1) / 2 + m
);
1216 /* Hacky for now, will solve for which conditions need summing analytically */
1217 if (sum_index
!= index
) {
1222 coeff
+= (decimal_date
- MagneticModel
->epoch
) * WMM_get_secular_var_coeff_h(sum_index
);
1230 float WMM_get_secular_var_coeff_g(uint16_t index
)
1232 if (index
>= NUMTERMS
) {
1236 return CoeffFile
[index
][4];
1239 float WMM_get_secular_var_coeff_h(uint16_t index
)
1241 if (index
>= NUMTERMS
) {
1245 return CoeffFile
[index
][5];
1248 int WMM_DateToYear(uint16_t month
, uint16_t day
, uint16_t year
)
1249 // Converts a given calendar date into a decimal year
1251 uint16_t temp
= 0; // Total number of days
1252 uint16_t MonthDays
[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
1253 uint16_t ExtraDay
= 0;
1256 if ((year
% 4 == 0 && year
% 100 != 0) || (year
% 400 == 0)) {
1259 MonthDays
[2] += ExtraDay
;
1261 /******************Validation********************************/
1263 if (month
<= 0 || month
> 12) {
1266 if (day
<= 0 || day
> MonthDays
[month
]) {
1269 /****************Calculation of t***************************/
1270 for (i
= 1; i
<= month
; i
++) {
1271 temp
+= MonthDays
[i
- 1];
1275 decimal_date
= year
+ (temp
- 1) / (365.0f
+ ExtraDay
);
1280 int WMM_GeodeticToSpherical(WMMtype_CoordGeodetic
*CoordGeodetic
, WMMtype_CoordSpherical
*CoordSpherical
)
1281 // Converts Geodetic coordinates to Spherical coordinates
1282 // Convert geodetic coordinates, (defined by the WGS-84
1283 // reference ellipsoid), to Earth Centered Earth Fixed Cartesian
1284 // coordinates, and then to spherical coordinates.
1286 float CosLat
, SinLat
, rc
, xp
, zp
; // all local variables
1288 CosLat
= cosf(DEG2RAD(CoordGeodetic
->phi
));
1289 SinLat
= sinf(DEG2RAD(CoordGeodetic
->phi
));
1291 // compute the local radius of curvature on the WGS-84 reference ellipsoid
1292 rc
= Ellip
->a
/ sqrtf(1.0f
- Ellip
->epssq
* SinLat
* SinLat
);
1294 // compute ECEF Cartesian coordinates of specified point (for longitude=0)
1296 xp
= (rc
+ CoordGeodetic
->HeightAboveEllipsoid
) * CosLat
;
1297 zp
= (rc
* (1.0f
- Ellip
->epssq
) + CoordGeodetic
->HeightAboveEllipsoid
) * SinLat
;
1299 // compute spherical radius and angle lambda and phi of specified point
1301 CoordSpherical
->r
= sqrtf(xp
* xp
+ zp
* zp
);
1302 CoordSpherical
->phig
= RAD2DEG(asinf(zp
/ CoordSpherical
->r
)); // geocentric latitude
1303 CoordSpherical
->lambda
= CoordGeodetic
->lambda
; // longitude