LP-89 - Port OP_15.05.01 fixes. Release notes:
[librepilot.git] / flight / libraries / WorldMagModel.c
blob262740e1b755235a2638669952fa498b708987f3
1 /**
2 ******************************************************************************
4 * @file WorldMagModel.c
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * @brief Source file for the World Magnetic Model
7 * This is a port of code available from the US NOAA.
9 * The hard coded coefficients should be valid until 2015.
11 * Updated coeffs from ..
12 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_ddownload.shtml
14 * NASA C source code ..
15 * http://www.ngdc.noaa.gov/geomag/WMM/wmm_wdownload.shtml
17 * Major changes include:
18 * - No geoid model (altitude must be geodetic WGS-84)
19 * - Floating point calculation (not double precision)
20 * - Hard coded coefficients for model
21 * - Elimination of user interface
22 * - Elimination of dynamic memory allocation
24 * @see The GNU Public License (GPL) Version 3
26 *****************************************************************************/
28 * This program is free software; you can redistribute it and/or modify
29 * it under the terms of the GNU General Public License as published by
30 * the Free Software Foundation; either version 3 of the License, or
31 * (at your option) any later version.
33 * This program is distributed in the hope that it will be useful, but
34 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
35 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
36 * for more details.
38 * You should have received a copy of the GNU General Public License along
39 * with this program; if not, write to the Free Software Foundation, Inc.,
40 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
43 #include "openpilot.h"
45 #include <stdio.h>
46 #include <string.h>
47 #include <math.h>
48 #include <stdlib.h>
49 #include <stdint.h>
51 #include "WorldMagModel.h"
52 #include "WMMInternal.h"
54 #define MALLOC(x) pios_malloc(x)
55 #define FREE(x) vPortFree(x)
56 // #define MALLOC(x) malloc(x)
57 // #define FREE(x) free(x)
59 // http://reviews.openpilot.org/cru/OPReview-436#c6476 :
60 // first column not used but it will be optimized out by compiler
61 static const float CoeffFile[91][6] = {
62 { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
63 { 1.0f, 0.0f, -29496.6f, 0.0f, 11.6f, 0.0f },
64 { 1.0f, 1.0f, -1586.3f, 4944.4f, 16.5f, -25.9f },
65 { 2.0f, 0.0f, -2396.6f, 0.0f, -12.1f, 0.0f },
66 { 2.0f, 1.0f, 3026.1f, -2707.7f, -4.4f, -22.5f },
67 { 2.0f, 2.0f, 1668.6f, -576.1f, 1.9f, -11.8f },
68 { 3.0f, 0.0f, 1340.1f, 0.0f, 0.4f, 0.0f },
69 { 3.0f, 1.0f, -2326.2f, -160.2f, -4.1f, 7.3f },
70 { 3.0f, 2.0f, 1231.9f, 251.9f, -2.9f, -3.9f },
71 { 3.0f, 3.0f, 634.0f, -536.6f, -7.7f, -2.6f },
72 { 4.0f, 0.0f, 912.6f, 0.0f, -1.8f, 0.0f },
73 { 4.0f, 1.0f, 808.9f, 286.4f, 2.3f, 1.1f },
74 { 4.0f, 2.0f, 166.7f, -211.2f, -8.7f, 2.7f },
75 { 4.0f, 3.0f, -357.1f, 164.3f, 4.6f, 3.9f },
76 { 4.0f, 4.0f, 89.4f, -309.1f, -2.1f, -0.8f },
77 { 5.0f, 0.0f, -230.9f, 0.0f, -1.0f, 0.0f },
78 { 5.0f, 1.0f, 357.2f, 44.6f, 0.6f, 0.4f },
79 { 5.0f, 2.0f, 200.3f, 188.9f, -1.8f, 1.8f },
80 { 5.0f, 3.0f, -141.1f, -118.2f, -1.0f, 1.2f },
81 { 5.0f, 4.0f, -163.0f, 0.0f, 0.9f, 4.0f },
82 { 5.0f, 5.0f, -7.8f, 100.9f, 1.0f, -0.6f },
83 { 6.0f, 0.0f, 72.8f, 0.0f, -0.2f, 0.0f },
84 { 6.0f, 1.0f, 68.6f, -20.8f, -0.2f, -0.2f },
85 { 6.0f, 2.0f, 76.0f, 44.1f, -0.1f, -2.1f },
86 { 6.0f, 3.0f, -141.4f, 61.5f, 2.0f, -0.4f },
87 { 6.0f, 4.0f, -22.8f, -66.3f, -1.7f, -0.6f },
88 { 6.0f, 5.0f, 13.2f, 3.1f, -0.3f, 0.5f },
89 { 6.0f, 6.0f, -77.9f, 55.0f, 1.7f, 0.9f },
90 { 7.0f, 0.0f, 80.5f, 0.0f, 0.1f, 0.0f },
91 { 7.0f, 1.0f, -75.1f, -57.9f, -0.1f, 0.7f },
92 { 7.0f, 2.0f, -4.7f, -21.1f, -0.6f, 0.3f },
93 { 7.0f, 3.0f, 45.3f, 6.5f, 1.3f, -0.1f },
94 { 7.0f, 4.0f, 13.9f, 24.9f, 0.4f, -0.1f },
95 { 7.0f, 5.0f, 10.4f, 7.0f, 0.3f, -0.8f },
96 { 7.0f, 6.0f, 1.7f, -27.7f, -0.7f, -0.3f },
97 { 7.0f, 7.0f, 4.9f, -3.3f, 0.6f, 0.3f },
98 { 8.0f, 0.0f, 24.4f, 0.0f, -0.1f, 0.0f },
99 { 8.0f, 1.0f, 8.1f, 11.0f, 0.1f, -0.1f },
100 { 8.0f, 2.0f, -14.5f, -20.0f, -0.6f, 0.2f },
101 { 8.0f, 3.0f, -5.6f, 11.9f, 0.2f, 0.4f },
102 { 8.0f, 4.0f, -19.3f, -17.4f, -0.2f, 0.4f },
103 { 8.0f, 5.0f, 11.5f, 16.7f, 0.3f, 0.1f },
104 { 8.0f, 6.0f, 10.9f, 7.0f, 0.3f, -0.1f },
105 { 8.0f, 7.0f, -14.1f, -10.8f, -0.6f, 0.4f },
106 { 8.0f, 8.0f, -3.7f, 1.7f, 0.2f, 0.3f },
107 { 9.0f, 0.0f, 5.4f, 0.0f, 0.0f, 0.0f },
108 { 9.0f, 1.0f, 9.4f, -20.5f, -0.1f, 0.0f },
109 { 9.0f, 2.0f, 3.4f, 11.5f, 0.0f, -0.2f },
110 { 9.0f, 3.0f, -5.2f, 12.8f, 0.3f, 0.0f },
111 { 9.0f, 4.0f, 3.1f, -7.2f, -0.4f, -0.1f },
112 { 9.0f, 5.0f, -12.4f, -7.4f, -0.3f, 0.1f },
113 { 9.0f, 6.0f, -0.7f, 8.0f, 0.1f, 0.0f },
114 { 9.0f, 7.0f, 8.4f, 2.1f, -0.1f, -0.2f },
115 { 9.0f, 8.0f, -8.5f, -6.1f, -0.4f, 0.3f },
116 { 9.0f, 9.0f, -10.1f, 7.0f, -0.2f, 0.2f },
117 { 10.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f },
118 { 10.0f, 1.0f, -6.3f, 2.8f, 0.0f, 0.1f },
119 { 10.0f, 2.0f, 0.9f, -0.1f, -0.1f, -0.1f },
120 { 10.0f, 3.0f, -1.1f, 4.7f, 0.2f, 0.0f },
121 { 10.0f, 4.0f, -0.2f, 4.4f, 0.0f, -0.1f },
122 { 10.0f, 5.0f, 2.5f, -7.2f, -0.1f, -0.1f },
123 { 10.0f, 6.0f, -0.3f, -1.0f, -0.2f, 0.0f },
124 { 10.0f, 7.0f, 2.2f, -3.9f, 0.0f, -0.1f },
125 { 10.0f, 8.0f, 3.1f, -2.0f, -0.1f, -0.2f },
126 { 10.0f, 9.0f, -1.0f, -2.0f, -0.2f, 0.0f },
127 { 10.0f, 10.0f, -2.8f, -8.3f, -0.2f, -0.1f },
128 { 11.0f, 0.0f, 3.0f, 0.0f, 0.0f, 0.0f },
129 { 11.0f, 1.0f, -1.5f, 0.2f, 0.0f, 0.0f },
130 { 11.0f, 2.0f, -2.1f, 1.7f, 0.0f, 0.1f },
131 { 11.0f, 3.0f, 1.7f, -0.6f, 0.1f, 0.0f },
132 { 11.0f, 4.0f, -0.5f, -1.8f, 0.0f, 0.1f },
133 { 11.0f, 5.0f, 0.5f, 0.9f, 0.0f, 0.0f },
134 { 11.0f, 6.0f, -0.8f, -0.4f, 0.0f, 0.1f },
135 { 11.0f, 7.0f, 0.4f, -2.5f, 0.0f, 0.0f },
136 { 11.0f, 8.0f, 1.8f, -1.3f, 0.0f, -0.1f },
137 { 11.0f, 9.0f, 0.1f, -2.1f, 0.0f, -0.1f },
138 { 11.0f, 10.0f, 0.7f, -1.9f, -0.1f, 0.0f },
139 { 11.0f, 11.0f, 3.8f, -1.8f, 0.0f, -0.1f },
140 { 12.0f, 0.0f, -2.2f, 0.0f, 0.0f, 0.0f },
141 { 12.0f, 1.0f, -0.2f, -0.9f, 0.0f, 0.0f },
142 { 12.0f, 2.0f, 0.3f, 0.3f, 0.1f, 0.0f },
143 { 12.0f, 3.0f, 1.0f, 2.1f, 0.1f, 0.0f },
144 { 12.0f, 4.0f, -0.6f, -2.5f, -0.1f, 0.0f },
145 { 12.0f, 5.0f, 0.9f, 0.5f, 0.0f, 0.0f },
146 { 12.0f, 6.0f, -0.1f, 0.6f, 0.0f, 0.1f },
147 { 12.0f, 7.0f, 0.5f, 0.0f, 0.0f, 0.0f },
148 { 12.0f, 8.0f, -0.4f, 0.1f, 0.0f, 0.0f },
149 { 12.0f, 9.0f, -0.4f, 0.3f, 0.0f, 0.0f },
150 { 12.0f, 10.0f, 0.2f, -0.9f, 0.0f, 0.0f },
151 { 12.0f, 11.0f, -0.8f, -0.2f, -0.1f, 0.0f },
152 { 12.0f, 12.0f, 0.0f, 0.9f, 0.1f, 0.0f }
155 static WMMtype_Ellipsoid *Ellip = NULL;
156 static WMMtype_MagneticModel *MagneticModel = NULL;
157 static float decimal_date;
159 /**************************************************************************************
160 * Example use - very simple - only two exposed functions
162 * WMM_Initialize(); // Set default values and constants
164 * WMM_GetMagVector(float Lat, float Lon, float Alt, uint16_t Month, uint16_t Day, uint16_t Year, float B[3]);
165 * e.g. Iceland in may of 2012 = WMM_GetMagVector(65.0, -20.0, 0.0, 5, 5, 2012, B);
166 * Alt is above the WGS-84 Ellipsoid
167 * B is the NED (XYZ) magnetic vector in nTesla
168 **************************************************************************************/
170 int WMM_Initialize()
171 // Sets default values for WMM subroutines.
172 // UPDATES : Ellip and MagneticModel
174 if (!Ellip) {
175 return -1; // invalid pointer
177 if (!MagneticModel) {
178 return -2; // invalid pointer
180 // Sets WGS-84 parameters
181 Ellip->a = 6378.137f; // semi-major axis of the ellipsoid in km
182 Ellip->b = 6356.7523142f; // semi-minor axis of the ellipsoid in km
183 Ellip->fla = 1.0f / 298.257223563f; // flattening
184 Ellip->eps = sqrt(1 - (Ellip->b * Ellip->b) / (Ellip->a * Ellip->a)); // first eccentricity
185 Ellip->epssq = (Ellip->eps * Ellip->eps); // first eccentricity squared
186 Ellip->re = 6371.2f; // Earth's radius in km
188 // Sets Magnetic Model parameters
189 MagneticModel->nMax = WMM_MAX_MODEL_DEGREES;
190 MagneticModel->nMaxSecVar = WMM_MAX_SECULAR_VARIATION_MODEL_DEGREES;
191 MagneticModel->SecularVariationUsed = 0;
193 // Really, Really needs to be read from a file - out of date in 2015 at latest
194 MagneticModel->EditionDate = 0.0f; /* OP change. Originally 5.7863328170559505e-307, truncates to 0.0f */
195 MagneticModel->epoch = 2010.0f;
196 sprintf(MagneticModel->ModelName, "WMM-2010");
198 return 0; // OK
201 int WMM_GetMagVector(float Lat, float Lon, float AltEllipsoid, uint16_t Month, uint16_t Day, uint16_t Year, float B[3])
203 // return '0' if all appears to be OK
204 // return < 0 if error
206 int returned = 0; // default to OK
208 // ***********
209 // range check supplied params
211 if (Lat < -90.0f) {
212 return -1; // error
214 if (Lat > 90.0f) {
215 return -2; // error
217 if (Lon < -180.0f) {
218 return -3; // error
220 if (Lon > 180.0f) {
221 return -4; // error
223 // ***********
224 // allocated required memory
226 // Ellip = NULL;
227 // MagneticModel = NULL;
229 // MagneticModel = NULL;
230 // CoordGeodetic = NULL;
231 // GeoMagneticElements = NULL;
233 Ellip = (WMMtype_Ellipsoid *)MALLOC(sizeof(WMMtype_Ellipsoid));
234 MagneticModel = (WMMtype_MagneticModel *)MALLOC(sizeof(WMMtype_MagneticModel));
236 WMMtype_CoordSpherical *CoordSpherical = (WMMtype_CoordSpherical *)MALLOC(sizeof(WMMtype_CoordSpherical));
237 WMMtype_CoordGeodetic *CoordGeodetic = (WMMtype_CoordGeodetic *)MALLOC(sizeof(WMMtype_CoordGeodetic));
238 WMMtype_GeoMagneticElements *GeoMagneticElements = (WMMtype_GeoMagneticElements *)MALLOC(sizeof(WMMtype_GeoMagneticElements));
240 if (!Ellip || !MagneticModel || !CoordSpherical || !CoordGeodetic || !GeoMagneticElements) {
241 returned = -5; // error
243 // ***********
245 if (returned >= 0) {
246 if (WMM_Initialize() < 0) {
247 returned = -6; // error
251 if (returned >= 0) {
252 CoordGeodetic->lambda = Lon;
253 CoordGeodetic->phi = Lat;
254 CoordGeodetic->HeightAboveEllipsoid = AltEllipsoid / 1000.0f; // convert to km
256 // Convert from geodetic to Spherical Equations: 17-18, WMM Technical report
257 if (WMM_GeodeticToSpherical(CoordGeodetic, CoordSpherical) < 0) {
258 returned = -7; // error
263 if (returned >= 0) {
264 if (WMM_DateToYear(Month, Day, Year) < 0) {
265 returned = -8; // error
269 if (returned >= 0) {
270 // Compute the geoMagnetic field elements and their time change
271 if (WMM_Geomag(CoordSpherical, CoordGeodetic, GeoMagneticElements) < 0) {
272 returned = -9; // error
273 } else { // set the returned values
274 B[0] = GeoMagneticElements->X;
275 B[1] = GeoMagneticElements->Y;
276 B[2] = GeoMagneticElements->Z;
280 // ***********
281 // free allocated memory
283 if (GeoMagneticElements) {
284 FREE(GeoMagneticElements);
287 if (CoordGeodetic) {
288 FREE(CoordGeodetic);
291 if (CoordSpherical) {
292 FREE(CoordSpherical);
295 if (MagneticModel) {
296 FREE(MagneticModel);
297 MagneticModel = NULL;
300 if (Ellip) {
301 FREE(Ellip);
302 Ellip = NULL;
305 B[0] = GeoMagneticElements->X * 1e-2f;
306 B[1] = GeoMagneticElements->Y * 1e-2f;
307 B[2] = GeoMagneticElements->Z * 1e-2f;
309 return returned;
312 int WMM_Geomag(WMMtype_CoordSpherical *CoordSpherical, WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_GeoMagneticElements *GeoMagneticElements)
314 The main subroutine that calls a sequence of WMM sub-functions to calculate the magnetic field elements for a single point.
315 The function expects the model coefficients and point coordinates as input and returns the magnetic field elements and
316 their rate of change. Though, this subroutine can be called successively to calculate a time series, profile or grid
317 of magnetic field, these are better achieved by the subroutine WMM_Grid.
319 INPUT: Ellip
320 CoordSpherical
321 CoordGeodetic
322 TimedMagneticModel
324 OUTPUT : GeoMagneticElements
326 CALLS: WMM_ComputeSphericalHarmonicVariables( Ellip, CoordSpherical, TimedMagneticModel->nMax, &SphVariables); (Compute Spherical Harmonic variables )
327 WMM_AssociatedLegendreFunction(CoordSpherical, TimedMagneticModel->nMax, LegendreFunction); Compute ALF
328 WMM_Summation(LegendreFunction, TimedMagneticModel, SphVariables, CoordSpherical, &MagneticResultsSph); Accumulate the spherical harmonic coefficients
329 WMM_SecVarSummation(LegendreFunction, TimedMagneticModel, SphVariables, CoordSpherical, &MagneticResultsSphVar); Sum the Secular Variation Coefficients
330 WMM_RotateMagneticVector(CoordSpherical, CoordGeodetic, MagneticResultsSph, &MagneticResultsGeo); Map the computed Magnetic fields to Geodeitic coordinates
331 WMM_RotateMagneticVector(CoordSpherical, CoordGeodetic, MagneticResultsSphVar, &MagneticResultsGeoVar); Map the secular variation field components to Geodetic coordinates
332 WMM_CalculateGeoMagneticElements(&MagneticResultsGeo, GeoMagneticElements); Calculate the Geomagnetic elements
333 WMM_CalculateSecularVariation(MagneticResultsGeoVar, GeoMagneticElements); Calculate the secular variation of each of the Geomagnetic elements
337 int returned = 0; // default to OK
339 WMMtype_MagneticResults MagneticResultsSph;
340 WMMtype_MagneticResults MagneticResultsGeo;
341 WMMtype_MagneticResults MagneticResultsSphVar;
342 WMMtype_MagneticResults MagneticResultsGeoVar;
344 // ********
345 // allocate required memory
347 WMMtype_LegendreFunction *LegendreFunction = (WMMtype_LegendreFunction *)MALLOC(sizeof(WMMtype_LegendreFunction));
348 WMMtype_SphericalHarmonicVariables *SphVariables = (WMMtype_SphericalHarmonicVariables *)MALLOC(sizeof(WMMtype_SphericalHarmonicVariables));
350 if (!LegendreFunction || !SphVariables) {
351 returned = -1; // memory allocation error
353 // ********
355 if (returned >= 0) { // Compute Spherical Harmonic variables
356 if (WMM_ComputeSphericalHarmonicVariables(CoordSpherical, MagneticModel->nMax, SphVariables) < 0) {
357 returned = -2; // error
361 if (returned >= 0) { // Compute ALF
362 if (WMM_AssociatedLegendreFunction(CoordSpherical, MagneticModel->nMax, LegendreFunction) < 0) {
363 returned = -3; // error
367 if (returned >= 0) { // Accumulate the spherical harmonic coefficients
368 if (WMM_Summation(LegendreFunction, SphVariables, CoordSpherical, &MagneticResultsSph) < 0) {
369 returned = -4; // error
373 if (returned >= 0) { // Sum the Secular Variation Coefficients
374 if (WMM_SecVarSummation(LegendreFunction, SphVariables, CoordSpherical, &MagneticResultsSphVar) < 0) {
375 returned = -5; // error
379 if (returned >= 0) { // Map the computed Magnetic fields to Geodeitic coordinates
380 if (WMM_RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSph, &MagneticResultsGeo) < 0) {
381 returned = -6; // error
385 if (returned >= 0) { // Map the secular variation field components to Geodetic coordinates
386 if (WMM_RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSphVar, &MagneticResultsGeoVar) < 0) {
387 returned = -7; // error
391 if (returned >= 0) { // Calculate the Geomagnetic elements, Equation 18 , WMM Technical report
392 if (WMM_CalculateGeoMagneticElements(&MagneticResultsGeo, GeoMagneticElements) < 0) {
393 returned = -8; // error
397 if (returned >= 0) { // Calculate the secular variation of each of the Geomagnetic elements
398 if (WMM_CalculateSecularVariation(&MagneticResultsGeoVar, GeoMagneticElements) < 0) {
399 returned = -9; // error
403 // ********
404 // free allocated memory
406 if (SphVariables) {
407 FREE(SphVariables);
410 if (LegendreFunction) {
411 FREE(LegendreFunction);
414 // ********
416 return returned;
419 int WMM_ComputeSphericalHarmonicVariables(WMMtype_CoordSpherical *CoordSpherical, uint16_t nMax, WMMtype_SphericalHarmonicVariables *SphVariables)
420 /* Computes Spherical variables
421 Variables computed are (a/r)^(n+2), cos_m(lamda) and sin_m(lambda) for spherical harmonic
422 summations. (Equations 10-12 in the WMM Technical Report)
423 INPUT Ellip data structure with the following elements
424 float a; semi-major axis of the ellipsoid
425 float b; semi-minor axis of the ellipsoid
426 float fla; flattening
427 float epssq; first eccentricity squared
428 float eps; first eccentricity
429 float re; mean radius of ellipsoid
430 CoordSpherical A data structure with the following elements
431 float lambda; ( longitude)
432 float phig; ( geocentric latitude )
433 float r; ( distance from the center of the ellipsoid)
434 nMax integer ( Maxumum degree of spherical harmonic secular model)\
436 OUTPUT SphVariables Pointer to the data structure with the following elements
437 float RelativeRadiusPower[WMM_MAX_MODEL_DEGREES+1]; [earth_reference_radius_km sph. radius ]^n
438 float cos_mlambda[WMM_MAX_MODEL_DEGREES+1]; cp(m) - cosine of (mspherical coord. longitude)
439 float sin_mlambda[WMM_MAX_MODEL_DEGREES+1]; sp(m) - sine of (mspherical coord. longitude)
440 CALLS : none
443 float cos_lambda, sin_lambda;
444 uint16_t m, n;
446 cos_lambda = cosf(DEG2RAD(CoordSpherical->lambda));
447 sin_lambda = sinf(DEG2RAD(CoordSpherical->lambda));
449 /* for n = 0 ... model_order, compute (Radius of Earth / Spherica radius r)^(n+2)
450 for n 1..nMax-1 (this is much faster than calling pow MAX_N+1 times). */
452 SphVariables->RelativeRadiusPower[0] = (Ellip->re / CoordSpherical->r) * (Ellip->re / CoordSpherical->r);
453 for (n = 1; n <= nMax; n++) {
454 SphVariables->RelativeRadiusPower[n] = SphVariables->RelativeRadiusPower[n - 1] * (Ellip->re / CoordSpherical->r);
458 Compute cosf(m*lambda), sinf(m*lambda) for m = 0 ... nMax
459 cosf(a + b) = cosf(a)*cosf(b) - sinf(a)*sinf(b)
460 sinf(a + b) = cosf(a)*sinf(b) + sinf(a)*cosf(b)
462 SphVariables->cos_mlambda[0] = 1.0f;
463 SphVariables->sin_mlambda[0] = 0.0f;
465 SphVariables->cos_mlambda[1] = cos_lambda;
466 SphVariables->sin_mlambda[1] = sin_lambda;
467 for (m = 2; m <= nMax; m++) {
468 SphVariables->cos_mlambda[m] = SphVariables->cos_mlambda[m - 1] * cos_lambda - SphVariables->sin_mlambda[m - 1] * sin_lambda;
469 SphVariables->sin_mlambda[m] = SphVariables->cos_mlambda[m - 1] * sin_lambda + SphVariables->sin_mlambda[m - 1] * cos_lambda;
472 return 0; // OK
475 int WMM_AssociatedLegendreFunction(WMMtype_CoordSpherical *CoordSpherical, uint16_t nMax, WMMtype_LegendreFunction *LegendreFunction)
476 /* Computes all of the Schmidt-semi normalized associated Legendre
477 functions up to degree nMax. If nMax <= 16, function WMM_PcupLow is used.
478 Otherwise WMM_PcupHigh is called.
479 INPUT CoordSpherical A data structure with the following elements
480 float lambda; ( longitude)
481 float phig; ( geocentric latitude )
482 float r; ( distance from the center of the ellipsoid)
483 nMax integer ( Maxumum degree of spherical harmonic secular model)
484 LegendreFunction Pointer to data structure with the following elements
485 float *Pcup; ( pointer to store Legendre Function )
486 float *dPcup; ( pointer to store Derivative of Lagendre function )
488 OUTPUT LegendreFunction Calculated Legendre variables in the data structure
492 float sin_phi = sinf(DEG2RAD(CoordSpherical->phig)); /* sinf (geocentric latitude) */
494 if (nMax <= 16 || (1 - fabsf(sin_phi)) < 1.0e-10f) { /* If nMax is less tha 16 or at the poles */
495 if (WMM_PcupLow(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax) < 0) {
496 return -1; // error
498 } else {
499 if (WMM_PcupHigh(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax) < 0) {
500 return -2; // error
504 return 0; // OK
507 int WMM_Summation(WMMtype_LegendreFunction *LegendreFunction,
508 WMMtype_SphericalHarmonicVariables *SphVariables,
509 WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
511 /* Computes Geomagnetic Field Elements X, Y and Z in Spherical coordinate system using
512 spherical harmonic summation.
514 The vector Magnetic field is given by -grad V, where V is Geomagnetic scalar potential
515 The gradient in spherical coordinates is given by:
517 dV ^ 1 dV ^ 1 dV ^
518 grad V = -- r + - -- t + -------- -- p
519 dr r dt r sinf(t) dp
521 INPUT : LegendreFunction
522 MagneticModel
523 SphVariables
524 CoordSpherical
525 OUTPUT : MagneticResults
527 CALLS : WMM_SummationSpecial
529 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
532 uint16_t m, n, index;
533 float cos_phi;
535 MagneticResults->Bz = 0.0f;
536 MagneticResults->By = 0.0f;
537 MagneticResults->Bx = 0.0f;
539 for (n = 1; n <= MagneticModel->nMax; n++) {
540 for (m = 0; m <= n; m++) {
541 index = (n * (n + 1) / 2 + m);
543 /* nMax (n+2) n m m m
544 Bz = -SUM (a/r) (n+1) SUM [g cosf(m p) + h sinf(m p)] P (sinf(phi))
545 n=1 m=0 n n n */
546 /* Equation 12 in the WMM Technical report. Derivative with respect to radius.*/
547 MagneticResults->Bz -=
548 SphVariables->RelativeRadiusPower[n] *
549 (WMM_get_main_field_coeff_g(index) *
550 SphVariables->cos_mlambda[m] + WMM_get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
551 * (float)(n + 1) * LegendreFunction->Pcup[index];
553 /* 1 nMax (n+2) n m m m
554 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
555 n=1 m=0 n n n */
556 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
557 MagneticResults->By +=
558 SphVariables->RelativeRadiusPower[n] *
559 (WMM_get_main_field_coeff_g(index) *
560 SphVariables->sin_mlambda[m] - WMM_get_main_field_coeff_h(index) * SphVariables->cos_mlambda[m])
561 * (float)(m) * LegendreFunction->Pcup[index];
562 /* nMax (n+2) n m m m
563 Bx = - SUM (a/r) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
564 n=1 m=0 n n n */
565 /* Equation 10 in the WMM Technical report. Derivative with respect to latitude, divided by radius. */
567 MagneticResults->Bx -=
568 SphVariables->RelativeRadiusPower[n] *
569 (WMM_get_main_field_coeff_g(index) *
570 SphVariables->cos_mlambda[m] + WMM_get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
571 * LegendreFunction->dPcup[index];
575 cos_phi = cosf(DEG2RAD(CoordSpherical->phig));
576 if (fabsf(cos_phi) > 1.0e-10f) {
577 MagneticResults->By = MagneticResults->By / cos_phi;
578 } else {
579 /* Special calculation for component - By - at Geographic poles.
580 * If the user wants to avoid using this function, please make sure that
581 * the latitude is not exactly +/-90. An option is to make use the function
582 * WMM_CheckGeographicPoles.
584 if (WMM_SummationSpecial(SphVariables, CoordSpherical, MagneticResults) < 0) {
585 return -1; // error
589 return 0; // OK
592 int WMM_SecVarSummation(WMMtype_LegendreFunction *LegendreFunction,
593 WMMtype_SphericalHarmonicVariables *
594 SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
596 /*This Function sums the secular variation coefficients to get the secular variation of the Magnetic vector.
597 INPUT : LegendreFunction
598 MagneticModel
599 SphVariables
600 CoordSpherical
601 OUTPUT : MagneticResults
603 CALLS : WMM_SecVarSummationSpecial
607 uint16_t m, n, index;
608 float cos_phi;
610 MagneticModel->SecularVariationUsed = TRUE;
612 MagneticResults->Bz = 0.0f;
613 MagneticResults->By = 0.0f;
614 MagneticResults->Bx = 0.0f;
616 for (n = 1; n <= MagneticModel->nMaxSecVar; n++) {
617 for (m = 0; m <= n; m++) {
618 index = (n * (n + 1) / 2 + m);
620 /* nMax (n+2) n m m m
621 Bz = -SUM (a/r) (n+1) SUM [g cosf(m p) + h sinf(m p)] P (sinf(phi))
622 n=1 m=0 n n n */
623 /* Derivative with respect to radius.*/
624 MagneticResults->Bz -=
625 SphVariables->RelativeRadiusPower[n] *
626 (WMM_get_secular_var_coeff_g(index) *
627 SphVariables->cos_mlambda[m] + WMM_get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
628 * (float)(n + 1) * LegendreFunction->Pcup[index];
630 /* 1 nMax (n+2) n m m m
631 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
632 n=1 m=0 n n n */
633 /* Derivative with respect to longitude, divided by radius. */
634 MagneticResults->By +=
635 SphVariables->RelativeRadiusPower[n] *
636 (WMM_get_secular_var_coeff_g(index) *
637 SphVariables->sin_mlambda[m] - WMM_get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[m])
638 * (float)(m) * LegendreFunction->Pcup[index];
639 /* nMax (n+2) n m m m
640 Bx = - SUM (a/r) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
641 n=1 m=0 n n n */
642 /* Derivative with respect to latitude, divided by radius. */
644 MagneticResults->Bx -=
645 SphVariables->RelativeRadiusPower[n] *
646 (WMM_get_secular_var_coeff_g(index) *
647 SphVariables->cos_mlambda[m] + WMM_get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
648 * LegendreFunction->dPcup[index];
651 cos_phi = cosf(DEG2RAD(CoordSpherical->phig));
652 if (fabsf(cos_phi) > 1.0e-10f) {
653 MagneticResults->By = MagneticResults->By / cos_phi;
654 } else {
655 /* Special calculation for component By at Geographic poles */
656 if (WMM_SecVarSummationSpecial(SphVariables, CoordSpherical, MagneticResults) < 0) {
657 return -1; // error
661 return 0; // OK
664 int WMM_RotateMagneticVector(WMMtype_CoordSpherical *CoordSpherical,
665 WMMtype_CoordGeodetic *CoordGeodetic,
666 WMMtype_MagneticResults *MagneticResultsSph, WMMtype_MagneticResults *MagneticResultsGeo)
667 /* Rotate the Magnetic Vectors to Geodetic Coordinates
668 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
669 Equation 16, WMM Technical report
671 INPUT : CoordSpherical : Data structure WMMtype_CoordSpherical with the following elements
672 float lambda; ( longitude)
673 float phig; ( geocentric latitude )
674 float r; ( distance from the center of the ellipsoid)
676 CoordGeodetic : Data structure WMMtype_CoordGeodetic with the following elements
677 float lambda; (longitude)
678 float phi; ( geodetic latitude)
679 float HeightAboveEllipsoid; (height above the ellipsoid (HaE) )
680 float HeightAboveGeoid;(height above the Geoid )
682 MagneticResultsSph : Data structure WMMtype_MagneticResults with the following elements
683 float Bx; North
684 float By; East
685 float Bz; Down
687 OUTPUT: MagneticResultsGeo Pointer to the data structure WMMtype_MagneticResults, with the following elements
688 float Bx; North
689 float By; East
690 float Bz; Down
692 CALLS : none
696 /* Difference between the spherical and Geodetic latitudes */
697 float Psi = DEG2RAD(CoordSpherical->phig - CoordGeodetic->phi);
699 /* Rotate spherical field components to the Geodeitic system */
700 MagneticResultsGeo->Bz = MagneticResultsSph->Bx * sinf(Psi) + MagneticResultsSph->Bz * cosf(Psi);
701 MagneticResultsGeo->Bx = MagneticResultsSph->Bx * cosf(Psi) - MagneticResultsSph->Bz * sinf(Psi);
702 MagneticResultsGeo->By = MagneticResultsSph->By;
704 return 0;
707 int WMM_CalculateGeoMagneticElements(WMMtype_MagneticResults *MagneticResultsGeo, WMMtype_GeoMagneticElements *GeoMagneticElements)
708 /* Calculate all the Geomagnetic elements from X,Y and Z components
709 INPUT MagneticResultsGeo Pointer to data structure with the following elements
710 float Bx; ( North )
711 float By; ( East )
712 float Bz; ( Down )
713 OUTPUT GeoMagneticElements Pointer to data structure with the following elements
714 float Decl; (Angle between the magnetic field vector and true north, positive east)
715 float Incl; Angle between the magnetic field vector and the horizontal plane, positive down
716 float F; Magnetic Field Strength
717 float H; Horizontal Magnetic Field Strength
718 float X; Northern component of the magnetic field vector
719 float Y; Eastern component of the magnetic field vector
720 float Z; Downward component of the magnetic field vector
721 CALLS : none
724 GeoMagneticElements->X = MagneticResultsGeo->Bx;
725 GeoMagneticElements->Y = MagneticResultsGeo->By;
726 GeoMagneticElements->Z = MagneticResultsGeo->Bz;
728 GeoMagneticElements->H = sqrtf(MagneticResultsGeo->Bx * MagneticResultsGeo->Bx + MagneticResultsGeo->By * MagneticResultsGeo->By);
729 GeoMagneticElements->F = sqrtf(GeoMagneticElements->H * GeoMagneticElements->H + MagneticResultsGeo->Bz * MagneticResultsGeo->Bz);
730 GeoMagneticElements->Decl = RAD2DEG(atan2f(GeoMagneticElements->Y, GeoMagneticElements->X));
731 GeoMagneticElements->Incl = RAD2DEG(atan2f(GeoMagneticElements->Z, GeoMagneticElements->H));
733 return 0; // OK
736 int WMM_CalculateSecularVariation(WMMtype_MagneticResults *MagneticVariation, WMMtype_GeoMagneticElements *MagneticElements)
737 /*This takes the Magnetic Variation in x, y, and z and uses it to calculate the secular variation of each of the Geomagnetic elements.
738 INPUT MagneticVariation Data structure with the following elements
739 float Bx; ( North )
740 float By; ( East )
741 float Bz; ( Down )
742 OUTPUT MagneticElements Pointer to the data structure with the following elements updated
743 float Decldot; Yearly Rate of change in declination
744 float Incldot; Yearly Rate of change in inclination
745 float Fdot; Yearly rate of change in Magnetic field strength
746 float Hdot; Yearly rate of change in horizontal field strength
747 float Xdot; Yearly rate of change in the northern component
748 float Ydot; Yearly rate of change in the eastern component
749 float Zdot; Yearly rate of change in the downward component
750 float GVdot;Yearly rate of chnage in grid variation
751 CALLS : none
755 MagneticElements->Xdot = MagneticVariation->Bx;
756 MagneticElements->Ydot = MagneticVariation->By;
757 MagneticElements->Zdot = MagneticVariation->Bz;
758 MagneticElements->Hdot = (MagneticElements->X * MagneticElements->Xdot + MagneticElements->Y * MagneticElements->Ydot) / MagneticElements->H; // See equation 19 in the WMM technical report
759 MagneticElements->Fdot =
760 (MagneticElements->X * MagneticElements->Xdot +
761 MagneticElements->Y * MagneticElements->Ydot + MagneticElements->Z * MagneticElements->Zdot) / MagneticElements->F;
762 MagneticElements->Decldot =
763 180.0f / M_PI_F * (MagneticElements->X * MagneticElements->Ydot -
764 MagneticElements->Y * MagneticElements->Xdot) / (MagneticElements->H * MagneticElements->H);
765 MagneticElements->Incldot =
766 180.0f / M_PI_F * (MagneticElements->H * MagneticElements->Zdot -
767 MagneticElements->Z * MagneticElements->Hdot) / (MagneticElements->F * MagneticElements->F);
768 MagneticElements->GVdot = MagneticElements->Decldot;
770 return 0; // OK
773 int WMM_PcupHigh(float *Pcup, float *dPcup, float x, uint16_t nMax)
774 /* This function evaluates all of the Schmidt-semi normalized associated Legendre
775 functions up to degree nMax. The functions are initially scaled by
776 10^280 sinf^m in order to minimize the effects of underflow at large m
777 near the poles (see Holmes and Featherstone 2002, J. Geodesy, 76, 279-299).
778 Note that this function performs the same operation as WMM_PcupLow.
779 However this function also can be used for high degree (large nMax) models.
781 Calling Parameters:
782 INPUT
783 nMax: Maximum spherical harmonic degree to compute.
784 x: cosf(colatitude) or sinf(latitude).
786 OUTPUT
787 Pcup: A vector of all associated Legendgre polynomials evaluated at
788 x up to nMax. The lenght must by greater or equal to (nMax+1)*(nMax+2)/2.
789 dPcup: Derivative of Pcup(x) with respect to latitude
791 CALLS : none
792 Notes:
794 Adopted from the FORTRAN code written by Mark Wieczorek September 25, 2005.
796 Manoj Nair, Nov, 2009 Manoj.C.Nair@Noaa.Gov
798 Change from the previous version
799 The prevous version computes the derivatives as
800 dP(n,m)(x)/dx, where x = sinf(latitude) (or cosf(colatitude) ).
801 However, the WMM Geomagnetic routines requires dP(n,m)(x)/dlatitude.
802 Hence the derivatives are multiplied by sinf(latitude).
803 Removed the options for CS phase and normalizations.
805 Note: In geomagnetism, the derivatives of ALF are usually found with
806 respect to the colatitudes. Here the derivatives are found with respect
807 to the latitude. The difference is a sign reversal for the derivative of
808 the Associated Legendre Functions.
810 The derivates can't be computed for latitude = |90| degrees.
813 uint16_t k, kstart, m, n;
814 float pm2, pm1, pmm, plm, rescalem, z, scalef;
816 float *f1 = (float *)MALLOC(sizeof(float) * NUMPCUP);
817 float *f2 = (float *)MALLOC(sizeof(float) * NUMPCUP);
818 float *PreSqr = (float *)MALLOC(sizeof(float) * NUMPCUP);
820 if (!PreSqr || !f2 || !f1) { // memory allocation error
821 if (PreSqr) {
822 FREE(PreSqr);
824 if (f2) {
825 FREE(f2);
827 if (f1) {
828 FREE(f1);
831 return -1;
835 * Note: OP code change to avoid floating point equality test.
836 * Was: if (fabs(x) == 1.0)
838 if (fabsf(x) - 1.0f < 1e-9f) {
839 FREE(PreSqr);
840 FREE(f2);
841 FREE(f1);
843 // printf("Error in PcupHigh: derivative cannot be calculated at poles\n");
844 return -2;
847 /* OP Change: 1.0e-280 is too small to store in a float - the compiler truncates
848 * it to 0.0f, which is bad as the code below divides by scalef. */
849 scalef = 1.0e-20f;
851 for (n = 0; n <= 2 * nMax + 1; ++n) {
852 PreSqr[n] = sqrtf((float)(n));
855 k = 2;
857 for (n = 2; n <= nMax; n++) {
858 k = k + 1;
859 f1[k] = (float)(2 * n - 1) / (float)(n);
860 f2[k] = (float)(n - 1) / (float)(n);
861 for (m = 1; m <= n - 2; m++) {
862 k = k + 1;
863 f1[k] = (float)(2 * n - 1) / PreSqr[n + m] / PreSqr[n - m];
864 f2[k] = PreSqr[n - m - 1] * PreSqr[n + m - 1] / PreSqr[n + m] / PreSqr[n - m];
866 k = k + 2;
869 /*z = sinf (geocentric latitude) */
870 z = sqrtf((1.0f - x) * (1.0f + x));
871 pm2 = 1.0f;
872 Pcup[0] = 1.0f;
873 dPcup[0] = 0.0f;
874 if (nMax == 0) {
875 FREE(PreSqr);
876 FREE(f2);
877 FREE(f1);
878 return -3;
880 pm1 = x;
881 Pcup[1] = pm1;
882 dPcup[1] = z;
883 k = 1;
885 for (n = 2; n <= nMax; n++) {
886 k = k + n;
887 plm = f1[k] * x * pm1 - f2[k] * pm2;
888 Pcup[k] = plm;
889 dPcup[k] = (float)(n) * (pm1 - x * plm) / z;
890 pm2 = pm1;
891 pm1 = plm;
894 pmm = PreSqr[2] * scalef;
895 rescalem = 1.0f / scalef;
896 kstart = 0;
898 for (m = 1; m <= nMax - 1; ++m) {
899 rescalem = rescalem * z;
901 /* Calculate Pcup(m,m) */
902 kstart = kstart + m + 1;
903 pmm = pmm * PreSqr[2 * m + 1] / PreSqr[2 * m];
904 Pcup[kstart] = pmm * rescalem / PreSqr[2 * m + 1];
905 dPcup[kstart] = -((float)(m) * x * Pcup[kstart] / z);
906 pm2 = pmm / PreSqr[2 * m + 1];
907 /* Calculate Pcup(m+1,m) */
908 k = kstart + m + 1;
909 pm1 = x * PreSqr[2 * m + 1] * pm2;
910 Pcup[k] = pm1 * rescalem;
911 dPcup[k] = ((pm2 * rescalem) * PreSqr[2 * m + 1] - x * (float)(m + 1) * Pcup[k]) / z;
912 /* Calculate Pcup(n,m) */
913 for (n = m + 2; n <= nMax; ++n) {
914 k = k + n;
915 plm = x * f1[k] * pm1 - f2[k] * pm2;
916 Pcup[k] = plm * rescalem;
917 dPcup[k] = (PreSqr[n + m] * PreSqr[n - m] * (pm1 * rescalem) - (float)(n) * x * Pcup[k]) / z;
918 pm2 = pm1;
919 pm1 = plm;
923 /* Calculate Pcup(nMax,nMax) */
924 rescalem = rescalem * z;
925 kstart = kstart + m + 1;
926 pmm = pmm / PreSqr[2 * nMax];
927 Pcup[kstart] = pmm * rescalem;
928 dPcup[kstart] = -(float)(nMax) * x * Pcup[kstart] / z;
930 // *********
931 // free allocated memory
933 FREE(PreSqr);
934 FREE(f2);
935 FREE(f1);
937 // *********
939 return 0; // OK
942 int WMM_PcupLow(float *Pcup, float *dPcup, float x, uint16_t nMax)
943 /* This function evaluates all of the Schmidt-semi normalized associated Legendre
944 functions up to degree nMax.
946 Calling Parameters:
947 INPUT
948 nMax: Maximum spherical harmonic degree to compute.
949 x: cosf(colatitude) or sinf(latitude).
951 OUTPUT
952 Pcup: A vector of all associated Legendgre polynomials evaluated at
953 x up to nMax.
954 dPcup: Derivative of Pcup(x) with respect to latitude
956 Notes: Overflow may occur if nMax > 20 , especially for high-latitudes.
957 Use WMM_PcupHigh for large nMax.
959 Writted by Manoj Nair, June, 2009 . Manoj.C.Nair@Noaa.Gov.
961 Note: In geomagnetism, the derivatives of ALF are usually found with
962 respect to the colatitudes. Here the derivatives are found with respect
963 to the latitude. The difference is a sign reversal for the derivative of
964 the Associated Legendre Functions.
967 uint16_t n, m, index, index1, index2;
968 float k, z;
970 float *schmidtQuasiNorm = (float *)MALLOC(sizeof(float) * NUMPCUP);
972 if (!schmidtQuasiNorm) { // memory allocation error
973 return -1;
976 Pcup[0] = 1.0f;
977 dPcup[0] = 0.0f;
979 /*sinf (geocentric latitude) - sin_phi */
980 z = sqrtf((1.0f - x) * (1.0f + x));
982 /* First, Compute the Gauss-normalized associated Legendre functions */
983 for (n = 1; n <= nMax; n++) {
984 for (m = 0; m <= n; m++) {
985 index = (n * (n + 1) / 2 + m);
986 if (n == m) {
987 index1 = (n - 1) * n / 2 + m - 1;
988 Pcup[index] = z * Pcup[index1];
989 dPcup[index] = z * dPcup[index1] + x * Pcup[index1];
990 } else if (n == 1 && m == 0) {
991 index1 = (n - 1) * n / 2 + m;
992 Pcup[index] = x * Pcup[index1];
993 dPcup[index] = x * dPcup[index1] - z * Pcup[index1];
994 } else if (n > 1 && n != m) {
995 index1 = (n - 2) * (n - 1) / 2 + m;
996 index2 = (n - 1) * n / 2 + m;
997 if (m > n - 2) {
998 Pcup[index] = x * Pcup[index2];
999 dPcup[index] = x * dPcup[index2] - z * Pcup[index2];
1000 } else {
1001 k = (float)(((n - 1) * (n - 1)) - (m * m)) / (float)((2 * n - 1)
1002 * (2 * n - 3));
1003 Pcup[index] = x * Pcup[index2] - k * Pcup[index1];
1004 dPcup[index] = x * dPcup[index2] - z * Pcup[index2] - k * dPcup[index1];
1009 /*Compute the ration between the Gauss-normalized associated Legendre
1010 functions and the Schmidt quasi-normalized version. This is equivalent to
1011 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
1013 schmidtQuasiNorm[0] = 1.0f;
1014 for (n = 1; n <= nMax; n++) {
1015 index = (n * (n + 1) / 2);
1016 index1 = (n - 1) * n / 2;
1017 /* for m = 0 */
1018 schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * (float)(2 * n - 1) / (float)n;
1020 for (m = 1; m <= n; m++) {
1021 index = (n * (n + 1) / 2 + m);
1022 index1 = (n * (n + 1) / 2 + m - 1);
1023 schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * sqrtf((float)((n - m + 1) * (m == 1 ? 2 : 1)) / (float)(n + m));
1027 /* Converts the Gauss-normalized associated Legendre
1028 functions to the Schmidt quasi-normalized version using pre-computed
1029 relation stored in the variable schmidtQuasiNorm */
1031 for (n = 1; n <= nMax; n++) {
1032 for (m = 0; m <= n; m++) {
1033 index = (n * (n + 1) / 2 + m);
1034 Pcup[index] = Pcup[index] * schmidtQuasiNorm[index];
1035 dPcup[index] = -dPcup[index] * schmidtQuasiNorm[index];
1036 /* The sign is changed since the new WMM routines use derivative with respect to latitude
1037 insted of co-latitude */
1041 FREE(schmidtQuasiNorm);
1043 return 0; // OK
1046 int WMM_SummationSpecial(WMMtype_SphericalHarmonicVariables *
1047 SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
1048 /* Special calculation for the component By at Geographic poles.
1049 Manoj Nair, June, 2009 manoj.c.nair@noaa.gov
1050 INPUT: MagneticModel
1051 SphVariables
1052 CoordSpherical
1053 OUTPUT: MagneticResults
1054 CALLS : none
1055 See Section 1.4, "SINGULARITIES AT THE GEOGRAPHIC POLES", WMM Technical report
1059 uint16_t n, index;
1060 float k, sin_phi;
1061 float schmidtQuasiNorm1;
1062 float schmidtQuasiNorm2;
1063 float schmidtQuasiNorm3;
1065 float *PcupS = (float *)MALLOC(sizeof(float) * NUMPCUPS);
1067 if (!PcupS) {
1068 return -1; // memory allocation error
1070 PcupS[0] = 1;
1071 schmidtQuasiNorm1 = 1.0f;
1073 MagneticResults->By = 0.0f;
1074 sin_phi = sinf(DEG2RAD(CoordSpherical->phig));
1076 for (n = 1; n <= MagneticModel->nMax; n++) {
1077 /*Compute the ration between the Gauss-normalized associated Legendre
1078 functions and the Schmidt quasi-normalized version. This is equivalent to
1079 sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
1081 index = (n * (n + 1) / 2 + 1);
1082 schmidtQuasiNorm2 = schmidtQuasiNorm1 * (float)(2 * n - 1) / (float)n;
1083 schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrtf((float)(n * 2) / (float)(n + 1));
1084 schmidtQuasiNorm1 = schmidtQuasiNorm2;
1085 if (n == 1) {
1086 PcupS[n] = PcupS[n - 1];
1087 } else {
1088 k = (float)(((n - 1) * (n - 1)) - 1) / (float)((2 * n - 1) * (2 * n - 3));
1089 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
1092 /* 1 nMax (n+2) n m m m
1093 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
1094 n=1 m=0 n n n */
1095 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
1097 MagneticResults->By +=
1098 SphVariables->RelativeRadiusPower[n] *
1099 (WMM_get_main_field_coeff_g(index) *
1100 SphVariables->sin_mlambda[1] - WMM_get_main_field_coeff_h(index) * SphVariables->cos_mlambda[1])
1101 * PcupS[n] * schmidtQuasiNorm3;
1104 FREE(PcupS);
1106 return 0; // OK
1109 int WMM_SecVarSummationSpecial(WMMtype_SphericalHarmonicVariables *
1110 SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
1112 /*Special calculation for the secular variation summation at the poles.
1114 INPUT: MagneticModel
1115 SphVariables
1116 CoordSpherical
1117 OUTPUT: MagneticResults
1118 CALLS : none
1121 uint16_t n, index;
1122 float k, sin_phi;
1123 float schmidtQuasiNorm1;
1124 float schmidtQuasiNorm2;
1125 float schmidtQuasiNorm3;
1127 float *PcupS = (float *)MALLOC(sizeof(float) * NUMPCUPS);
1129 if (!PcupS) {
1130 return -1; // memory allocation error
1132 PcupS[0] = 1;
1133 schmidtQuasiNorm1 = 1.0f;
1135 MagneticResults->By = 0.0f;
1136 sin_phi = sinf(DEG2RAD(CoordSpherical->phig));
1138 for (n = 1; n <= MagneticModel->nMaxSecVar; n++) {
1139 index = (n * (n + 1) / 2 + 1);
1140 schmidtQuasiNorm2 = schmidtQuasiNorm1 * (float)(2 * n - 1) / (float)n;
1141 schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrtf((float)(n * 2) / (float)(n + 1));
1142 schmidtQuasiNorm1 = schmidtQuasiNorm2;
1143 if (n == 1) {
1144 PcupS[n] = PcupS[n - 1];
1145 } else {
1146 k = (float)(((n - 1) * (n - 1)) - 1) / (float)((2 * n - 1) * (2 * n - 3));
1147 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
1150 /* 1 nMax (n+2) n m m m
1151 By = SUM (a/r) (m) SUM [g cosf(m p) + h sinf(m p)] dP (sinf(phi))
1152 n=1 m=0 n n n */
1153 /* Derivative with respect to longitude, divided by radius. */
1155 MagneticResults->By +=
1156 SphVariables->RelativeRadiusPower[n] *
1157 (WMM_get_secular_var_coeff_g(index) *
1158 SphVariables->sin_mlambda[1] - WMM_get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[1])
1159 * PcupS[n] * schmidtQuasiNorm3;
1162 FREE(PcupS);
1164 return 0; // OK
1168 * @brief Comput the MainFieldCoeffH accounting for the date
1170 float WMM_get_main_field_coeff_g(uint16_t index)
1172 if (index >= NUMTERMS) {
1173 return 0;
1176 uint16_t n, m, sum_index, a, b;
1178 float coeff = CoeffFile[index][2];
1180 a = MagneticModel->nMaxSecVar;
1181 b = (a * (a + 1) / 2 + a);
1182 for (n = 1; n <= MagneticModel->nMax; n++) {
1183 for (m = 0; m <= n; m++) {
1184 sum_index = (n * (n + 1) / 2 + m);
1186 /* Hacky for now, will solve for which conditions need summing analytically */
1187 if (sum_index != index) {
1188 continue;
1191 if (index <= b) {
1192 coeff += (decimal_date - MagneticModel->epoch) * WMM_get_secular_var_coeff_g(sum_index);
1197 return coeff;
1200 float WMM_get_main_field_coeff_h(uint16_t index)
1202 if (index >= NUMTERMS) {
1203 return 0;
1206 uint16_t n, m, sum_index, a, b;
1207 float coeff = CoeffFile[index][3];
1209 a = MagneticModel->nMaxSecVar;
1210 b = (a * (a + 1) / 2 + a);
1211 for (n = 1; n <= MagneticModel->nMax; n++) {
1212 for (m = 0; m <= n; m++) {
1213 sum_index = (n * (n + 1) / 2 + m);
1215 /* Hacky for now, will solve for which conditions need summing analytically */
1216 if (sum_index != index) {
1217 continue;
1220 if (index <= b) {
1221 coeff += (decimal_date - MagneticModel->epoch) * WMM_get_secular_var_coeff_h(sum_index);
1226 return coeff;
1229 float WMM_get_secular_var_coeff_g(uint16_t index)
1231 if (index >= NUMTERMS) {
1232 return 0;
1235 return CoeffFile[index][4];
1238 float WMM_get_secular_var_coeff_h(uint16_t index)
1240 if (index >= NUMTERMS) {
1241 return 0;
1244 return CoeffFile[index][5];
1247 int WMM_DateToYear(uint16_t month, uint16_t day, uint16_t year)
1248 // Converts a given calendar date into a decimal year
1250 uint16_t temp = 0; // Total number of days
1251 uint16_t MonthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
1252 uint16_t ExtraDay = 0;
1253 uint16_t i;
1255 if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
1256 ExtraDay = 1;
1258 MonthDays[2] += ExtraDay;
1260 /******************Validation********************************/
1262 if (month <= 0 || month > 12) {
1263 return -1; // error
1265 if (day <= 0 || day > MonthDays[month]) {
1266 return -2; // error
1268 /****************Calculation of t***************************/
1269 for (i = 1; i <= month; i++) {
1270 temp += MonthDays[i - 1];
1272 temp += day;
1274 decimal_date = year + (temp - 1) / (365.0f + ExtraDay);
1276 return 0; // OK
1279 int WMM_GeodeticToSpherical(WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_CoordSpherical *CoordSpherical)
1280 // Converts Geodetic coordinates to Spherical coordinates
1281 // Convert geodetic coordinates, (defined by the WGS-84
1282 // reference ellipsoid), to Earth Centered Earth Fixed Cartesian
1283 // coordinates, and then to spherical coordinates.
1285 float CosLat, SinLat, rc, xp, zp; // all local variables
1287 CosLat = cosf(DEG2RAD(CoordGeodetic->phi));
1288 SinLat = sinf(DEG2RAD(CoordGeodetic->phi));
1290 // compute the local radius of curvature on the WGS-84 reference ellipsoid
1291 rc = Ellip->a / sqrtf(1.0f - Ellip->epssq * SinLat * SinLat);
1293 // compute ECEF Cartesian coordinates of specified point (for longitude=0)
1295 xp = (rc + CoordGeodetic->HeightAboveEllipsoid) * CosLat;
1296 zp = (rc * (1.0f - Ellip->epssq) + CoordGeodetic->HeightAboveEllipsoid) * SinLat;
1298 // compute spherical radius and angle lambda and phi of specified point
1300 CoordSpherical->r = sqrtf(xp * xp + zp * zp);
1301 CoordSpherical->phig = RAD2DEG(asinf(zp / CoordSpherical->r)); // geocentric latitude
1302 CoordSpherical->lambda = CoordGeodetic->lambda; // longitude
1304 return 0; // OK