dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
worldmagmodel.cpp
Go to the documentation of this file.
1 
31 /*
32  * This program is free software; you can redistribute it and/or modify
33  * it under the terms of the GNU General Public License as published by
34  * the Free Software Foundation; either version 3 of the License, or
35  * (at your option) any later version.
36  *
37  * This program is distributed in the hope that it will be useful, but
38  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
39  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
40  * for more details.
41  *
42  * You should have received a copy of the GNU General Public License along
43  * with this program; if not, see <http://www.gnu.org/licenses/>
44  */
45 
46 #define _USE_MATH_DEFINES
47 #include <cmath>
48 #include "worldmagmodel.h"
49 
50 #include <qglobal.h>
51 #include <QDebug>
52 #include <math.h>
53 #include "physical_constants.h"
54 
55 // Must be updated periodically. Last update expires in 2015
56 // updated coeffs available from http://www.ngdc.noaa.gov/geomag/WMM/wmm_ddownload.shtml
57 // Must update WorldMagneticModel.c at same time
58 const double CoeffFile[91][6] = COEFFS_FROM_NASA;
59 
60 namespace Utils {
61 
63  {
64  Initialize();
65  }
66 
67  int WorldMagModel::GetMagVector(double LLA[3], int Month, int Day, int Year, double Be[3])
68  {
69  double Lat = LLA[0];
70  double Lon = LLA[1];
71  double AltEllipsoid = LLA[2]/1000.0; // convert to km
72 
73  // ***********
74  // range check supplied params
75 
76  if (Lat < -90) return -1; // error
77  if (Lat > 90) return -2; // error
78 
79  if (Lon < -180) return -3; // error
80  if (Lon > 180) return -4; // error
81 
82  // ***********
83 
84  WMMtype_CoordSpherical CoordSpherical;
85  WMMtype_CoordGeodetic CoordGeodetic;
86  WMMtype_GeoMagneticElements GeoMagneticElements;
87 
88  Initialize();
89 
90  CoordGeodetic.lambda = Lon;
91  CoordGeodetic.phi = Lat;
92  CoordGeodetic.HeightAboveEllipsoid = AltEllipsoid;
93 
94  // Convert from geodeitic to Spherical Equations: 17-18, WMM Technical report
95  GeodeticToSpherical(&CoordGeodetic, &CoordSpherical);
96 
97  if (DateToYear(Month, Day, Year) < 0)
98  return -5; // error
99 
100  // Compute the geoMagnetic field elements and their time change
101  if (Geomag(&CoordSpherical, &CoordGeodetic, &GeoMagneticElements) < 0)
102  return -6; // error
103 
104  // set the returned values
105  Be[0] = GeoMagneticElements.X * 1e-2;
106  Be[1] = GeoMagneticElements.Y * 1e-2;
107  Be[2] = GeoMagneticElements.Z * 1e-2;
108 
109  // ***********
110 
111  return 0; // OK
112  }
113 
114  void WorldMagModel::Initialize()
115  { // Sets default values for WMM subroutines.
116  // UPDATES : Ellip and MagneticModel
117 
118  // Sets WGS-84 parameters
119  Ellip.a = WGS84_A; // semi-major axis of the ellipsoid in km
120  Ellip.b = WGS84_B; // semi-minor axis of the ellipsoid in km
121  Ellip.fla = WGS84_FLATTENING; // flattening
122  Ellip.eps = WGS84_EPS; // first eccentricity
123  Ellip.epssq = WGS84_EPS2; // first eccentricity squared
124  Ellip.re = WGS84_RADIUS_EARTH_KM; // Earth's radius in km
125 
126  // Sets Magnetic Model parameters
127  MagneticModel.nMax = WMM_MAX_MODEL_DEGREES;
128  MagneticModel.nMaxSecVar = WMM_MAX_SECULAR_VARIATION_MODEL_DEGREES;
129  MagneticModel.SecularVariationUsed = 0;
130 
131  // Must be updated periodically. Last update expires in 2020
132  MagneticModel.EditionDate = MAGNETIC_MODEL_EDITION_DATE;
133  MagneticModel.epoch = MAGNETIC_MODEL_EPOCH;
134  sprintf(MagneticModel.ModelName, MAGNETIC_MODEL_NAME);
135  }
136 
137 
138  int WorldMagModel::Geomag(WMMtype_CoordSpherical *CoordSpherical, WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_GeoMagneticElements *GeoMagneticElements)
139  /*
140  The main subroutine that calls a sequence of WMM sub-functions to calculate the magnetic field elements for a single point.
141  The function expects the model coefficients and point coordinates as input and returns the magnetic field elements and
142  their rate of change. Though, this subroutine can be called successively to calculate a time series, profile or grid
143  of magnetic field, these are better achieved by the subroutine WMM_Grid.
144 
145  INPUT: Ellip
146  CoordSpherical
147  CoordGeodetic
148  TimedMagneticModel
149 
150  OUTPUT : GeoMagneticElements
151  */
152  {
153  WMMtype_MagneticResults MagneticResultsSph;
154  WMMtype_MagneticResults MagneticResultsGeo;
155  WMMtype_MagneticResults MagneticResultsSphVar;
156  WMMtype_MagneticResults MagneticResultsGeoVar;
157  WMMtype_LegendreFunction LegendreFunction;
159 
160  // Compute Spherical Harmonic variables
161  ComputeSphericalHarmonicVariables(CoordSpherical, MagneticModel.nMax, &SphVariables);
162 
163  // Compute ALF
164  if (AssociatedLegendreFunction(CoordSpherical, MagneticModel.nMax, &LegendreFunction) < 0)
165  return -1; // error
166 
167  // Accumulate the spherical harmonic coefficients
168  Summation(&LegendreFunction, &SphVariables, CoordSpherical, &MagneticResultsSph);
169 
170  // Sum the Secular Variation Coefficients
171  SecVarSummation(&LegendreFunction, &SphVariables, CoordSpherical, &MagneticResultsSphVar);
172 
173  // Map the computed Magnetic fields to Geodeitic coordinates
174  RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSph, &MagneticResultsGeo);
175 
176  // Map the secular variation field components to Geodetic coordinates
177  RotateMagneticVector(CoordSpherical, CoordGeodetic, &MagneticResultsSphVar, &MagneticResultsGeoVar);
178 
179  // Calculate the Geomagnetic elements, Equation 18 , WMM Technical report
180  CalculateGeoMagneticElements(&MagneticResultsGeo, GeoMagneticElements);
181 
182  // Calculate the secular variation of each of the Geomagnetic elements
183  CalculateSecularVariation(&MagneticResultsGeoVar, GeoMagneticElements);
184 
185  return 0; // OK
186  }
187 
188  void WorldMagModel::ComputeSphericalHarmonicVariables(WMMtype_CoordSpherical *CoordSpherical, int nMax, WMMtype_SphericalHarmonicVariables *SphVariables)
189  {
190  /* Computes Spherical variables
191  Variables computed are (a/r)^(n+2), cos_m(lamda) and sin_m(lambda) for spherical harmonic
192  summations. (Equations 10-12 in the WMM Technical Report)
193  INPUT Ellip data structure with the following elements
194  float a; semi-major axis of the ellipsoid
195  float b; semi-minor axis of the ellipsoid
196  float fla; flattening
197  float epssq; first eccentricity squared
198  float eps; first eccentricity
199  float re; mean radius of ellipsoid
200  CoordSpherical A data structure with the following elements
201  float lambda; ( longitude)
202  float phig; ( geocentric latitude )
203  float r; ( distance from the center of the ellipsoid)
204  nMax integer ( Maxumum degree of spherical harmonic secular model)\
205 
206  OUTPUT SphVariables Pointer to the data structure with the following elements
207  float RelativeRadiusPower[WMM_MAX_MODEL_DEGREES+1]; [earth_reference_radius_km sph. radius ]^n
208  float cos_mlambda[WMM_MAX_MODEL_DEGREES+1]; cp(m) - cosine of (mspherical coord. longitude)
209  float sin_mlambda[WMM_MAX_MODEL_DEGREES+1]; sp(m) - sine of (mspherical coord. longitude)
210  */
211  double cos_lambda = cos(CoordSpherical->lambda * DEG2RAD);
212  double sin_lambda = sin(CoordSpherical->lambda * DEG2RAD);
213 
214  /* for n = 0 ... model_order, compute (Radius of Earth / Spherica radius r)^(n+2)
215  for n 1..nMax-1 (this is much faster than calling pow MAX_N+1 times). */
216 
217  SphVariables->RelativeRadiusPower[0] = (Ellip.re / CoordSpherical->r) * (Ellip.re / CoordSpherical->r);
218  for (int n = 1; n <= nMax; n++)
219  SphVariables->RelativeRadiusPower[n] = SphVariables->RelativeRadiusPower[n - 1] * (Ellip.re / CoordSpherical->r);
220 
221  /*
222  Compute cos(m*lambda), sin(m*lambda) for m = 0 ... nMax
223  cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)
224  sin(a + b) = cos(a)*sin(b) + sin(a)*cos(b)
225  */
226  SphVariables->cos_mlambda[0] = 1.0;
227  SphVariables->sin_mlambda[0] = 0.0;
228 
229  SphVariables->cos_mlambda[1] = cos_lambda;
230  SphVariables->sin_mlambda[1] = sin_lambda;
231  for (int m = 2; m <= nMax; m++)
232  {
233  SphVariables->cos_mlambda[m] = SphVariables->cos_mlambda[m - 1] * cos_lambda - SphVariables->sin_mlambda[m - 1] * sin_lambda;
234  SphVariables->sin_mlambda[m] = SphVariables->cos_mlambda[m - 1] * sin_lambda + SphVariables->sin_mlambda[m - 1] * cos_lambda;
235  }
236  }
237 
238  int WorldMagModel::AssociatedLegendreFunction(WMMtype_CoordSpherical *CoordSpherical, int nMax, WMMtype_LegendreFunction *LegendreFunction)
239  {
240  /* Computes all of the Schmidt-semi normalized associated Legendre
241  functions up to degree nMax. If nMax <= 16, function WMM_PcupLow is used.
242  Otherwise WMM_PcupHigh is called.
243  INPUT CoordSpherical A data structure with the following elements
244  float lambda; ( longitude)
245  float phig; ( geocentric latitude )
246  float r; ( distance from the center of the ellipsoid)
247  nMax integer ( Maxumum degree of spherical harmonic secular model)
248  LegendreFunction Pointer to data structure with the following elements
249  float *Pcup; ( pointer to store Legendre Function )
250  float *dPcup; ( pointer to store Derivative of Lagendre function )
251 
252  OUTPUT LegendreFunction Calculated Legendre variables in the data structure
253  */
254 
255  double sin_phi = sin(CoordSpherical->phig * DEG2RAD); // sin (geocentric latitude)
256 
257  if (nMax <= 16 || (1 - fabs(sin_phi)) < 1.0e-10) /* If nMax is less tha 16 or at the poles */
258  PcupLow(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax);
259  else
260  {
261  if (PcupHigh(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi, nMax) < 0)
262  return -1; // error
263  }
264 
265  return 0; // OK
266  }
267 
268  void WorldMagModel::Summation( WMMtype_LegendreFunction *LegendreFunction,
270  WMMtype_CoordSpherical *CoordSpherical,
271  WMMtype_MagneticResults *MagneticResults)
272  {
273  /* Computes Geomagnetic Field Elements X, Y and Z in Spherical coordinate system using spherical harmonic summation.
274 
275  The vector Magnetic field is given by -grad V, where V is Geomagnetic scalar potential
276  The gradient in spherical coordinates is given by:
277 
278  dV ^ 1 dV ^ 1 dV ^
279  grad V = -- r + - -- t + -------- -- p
280  dr r dt r sin(t) dp
281 
282  INPUT : LegendreFunction
283  MagneticModel
284  SphVariables
285  CoordSpherical
286  OUTPUT : MagneticResults
287 
288  Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
289  */
290 
291  MagneticResults->Bz = 0.0;
292  MagneticResults->By = 0.0;
293  MagneticResults->Bx = 0.0;
294 
295  for (int n = 1; n <= MagneticModel.nMax; n++)
296  {
297  for (int m = 0; m <= n; m++)
298  {
299  int index = (n * (n + 1) / 2 + m);
300 
301 /* nMax (n+2) n m m m
302  Bz = -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi))
303  n=1 m=0 n n n */
304 /* Equation 12 in the WMM Technical report. Derivative with respect to radius.*/
305  MagneticResults->Bz -=
306  SphVariables->RelativeRadiusPower[n] *
307  (get_main_field_coeff_g(index) *
308  SphVariables->cos_mlambda[m] + get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
309  * (double)(n + 1) * LegendreFunction->Pcup[index];
310 
311 /* 1 nMax (n+2) n m m m
312  By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
313  n=1 m=0 n n n */
314 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
315  MagneticResults->By +=
316  SphVariables->RelativeRadiusPower[n] *
317  (get_main_field_coeff_g(index) *
318  SphVariables->sin_mlambda[m] - get_main_field_coeff_h(index) * SphVariables->cos_mlambda[m])
319  * (double)(m) * LegendreFunction->Pcup[index];
320 /* nMax (n+2) n m m m
321  Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
322  n=1 m=0 n n n */
323 /* Equation 10 in the WMM Technical report. Derivative with respect to latitude, divided by radius. */
324 
325  MagneticResults->Bx -=
326  SphVariables->RelativeRadiusPower[n] *
327  (get_main_field_coeff_g(index) *
328  SphVariables->cos_mlambda[m] + get_main_field_coeff_h(index) * SphVariables->sin_mlambda[m])
329  * LegendreFunction->dPcup[index];
330 
331  }
332  }
333 
334  double cos_phi = cos(CoordSpherical->phig * DEG2RAD);
335  if (fabs(cos_phi) > 1.0e-10)
336  {
337  MagneticResults->By = MagneticResults->By / cos_phi;
338  }
339  else
340  {
341  /* Special calculation for component - By - at Geographic poles.
342  * If the user wants to avoid using this function, please make sure that
343  * the latitude is not exactly +/-90. An option is to make use the function
344  * WMM_CheckGeographicPoles.
345  */
346  SummationSpecial(SphVariables, CoordSpherical, MagneticResults);
347  }
348  }
349 
350  void WorldMagModel::SecVarSummation( WMMtype_LegendreFunction *LegendreFunction,
352  WMMtype_CoordSpherical *CoordSpherical,
353  WMMtype_MagneticResults *MagneticResults)
354  {
355  /*This Function sums the secular variation coefficients to get the secular variation of the Magnetic vector.
356  INPUT : LegendreFunction
357  MagneticModel
358  SphVariables
359  CoordSpherical
360  OUTPUT : MagneticResults
361  */
362 
363  MagneticModel.SecularVariationUsed = true;
364 
365  MagneticResults->Bz = 0.0;
366  MagneticResults->By = 0.0;
367  MagneticResults->Bx = 0.0;
368 
369  for (int n = 1; n <= MagneticModel.nMaxSecVar; n++)
370  {
371  for (int m = 0; m <= n; m++)
372  {
373  int index = (n * (n + 1) / 2 + m);
374 
375 /* nMax (n+2) n m m m
376  Bz = -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi))
377  n=1 m=0 n n n */
378 /* Derivative with respect to radius.*/
379  MagneticResults->Bz -=
380  SphVariables->RelativeRadiusPower[n] *
381  (get_secular_var_coeff_g(index) *
382  SphVariables->cos_mlambda[m] + get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
383  * (double)(n + 1) * LegendreFunction->Pcup[index];
384 
385 /* 1 nMax (n+2) n m m m
386  By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
387  n=1 m=0 n n n */
388 /* Derivative with respect to longitude, divided by radius. */
389  MagneticResults->By +=
390  SphVariables->RelativeRadiusPower[n] *
391  (get_secular_var_coeff_g(index) *
392  SphVariables->sin_mlambda[m] - get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[m])
393  * (double)(m) * LegendreFunction->Pcup[index];
394 /* nMax (n+2) n m m m
395  Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
396  n=1 m=0 n n n */
397 /* Derivative with respect to latitude, divided by radius. */
398 
399  MagneticResults->Bx -=
400  SphVariables->RelativeRadiusPower[n] *
401  (get_secular_var_coeff_g(index) *
402  SphVariables->cos_mlambda[m] + get_secular_var_coeff_h(index) * SphVariables->sin_mlambda[m])
403  * LegendreFunction->dPcup[index];
404  }
405  }
406 
407  double cos_phi = cos(CoordSpherical->phig * DEG2RAD);
408  if (fabs(cos_phi) > 1.0e-10)
409  {
410  MagneticResults->By = MagneticResults->By / cos_phi;
411  }
412  else
413  { /* Special calculation for component By at Geographic poles */
414  SecVarSummationSpecial(SphVariables, CoordSpherical, MagneticResults);
415  }
416  }
417 
418  void WorldMagModel::RotateMagneticVector( WMMtype_CoordSpherical *CoordSpherical,
419  WMMtype_CoordGeodetic *CoordGeodetic,
420  WMMtype_MagneticResults *MagneticResultsSph,
421  WMMtype_MagneticResults *MagneticResultsGeo)
422  {
423  /* Rotate the Magnetic Vectors to Geodetic Coordinates
424  Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
425  Equation 16, WMM Technical report
426 
427  INPUT : CoordSpherical : Data structure WMMtype_CoordSpherical with the following elements
428  float lambda; ( longitude)
429  float phig; ( geocentric latitude )
430  float r; ( distance from the center of the ellipsoid)
431 
432  CoordGeodetic : Data structure WMMtype_CoordGeodetic with the following elements
433  float lambda; (longitude)
434  float phi; ( geodetic latitude)
435  float HeightAboveEllipsoid; (height above the ellipsoid (HaE) )
436  float HeightAboveGeoid;(height above the Geoid )
437 
438  MagneticResultsSph : Data structure WMMtype_MagneticResults with the following elements
439  float Bx; North
440  float By; East
441  float Bz; Down
442 
443  OUTPUT: MagneticResultsGeo Pointer to the data structure WMMtype_MagneticResults, with the following elements
444  float Bx; North
445  float By; East
446  float Bz; Down
447  */
448 
449  /* Difference between the spherical and Geodetic latitudes */
450  double Psi = (CoordSpherical->phig - CoordGeodetic->phi) * DEG2RAD;
451 
452  /* Rotate spherical field components to the Geodeitic system */
453  MagneticResultsGeo->Bz = MagneticResultsSph->Bx * sin(Psi) + MagneticResultsSph->Bz * cos(Psi);
454  MagneticResultsGeo->Bx = MagneticResultsSph->Bx * cos(Psi) - MagneticResultsSph->Bz * sin(Psi);
455  MagneticResultsGeo->By = MagneticResultsSph->By;
456  }
457 
458  void WorldMagModel::CalculateGeoMagneticElements(WMMtype_MagneticResults *MagneticResultsGeo, WMMtype_GeoMagneticElements *GeoMagneticElements)
459  {
460  /* Calculate all the Geomagnetic elements from X,Y and Z components
461  INPUT MagneticResultsGeo Pointer to data structure with the following elements
462  float Bx; ( North )
463  float By; ( East )
464  float Bz; ( Down )
465  OUTPUT GeoMagneticElements Pointer to data structure with the following elements
466  float Decl; (Angle between the magnetic field vector and true north, positive east)
467  float Incl; Angle between the magnetic field vector and the horizontal plane, positive down
468  float F; Magnetic Field Strength
469  float H; Horizontal Magnetic Field Strength
470  float X; Northern component of the magnetic field vector
471  float Y; Eastern component of the magnetic field vector
472  float Z; Downward component of the magnetic field vector
473  */
474 
475  GeoMagneticElements->X = MagneticResultsGeo->Bx;
476  GeoMagneticElements->Y = MagneticResultsGeo->By;
477  GeoMagneticElements->Z = MagneticResultsGeo->Bz;
478 
479  GeoMagneticElements->H = sqrt(MagneticResultsGeo->Bx * MagneticResultsGeo->Bx + MagneticResultsGeo->By * MagneticResultsGeo->By);
480  GeoMagneticElements->F = sqrt(GeoMagneticElements->H * GeoMagneticElements->H + MagneticResultsGeo->Bz * MagneticResultsGeo->Bz);
481  GeoMagneticElements->Decl = atan2(GeoMagneticElements->Y, GeoMagneticElements->X) * RAD2DEG;
482  GeoMagneticElements->Incl = atan2(GeoMagneticElements->Z, GeoMagneticElements->H) * RAD2DEG;
483  }
484 
485  void WorldMagModel::CalculateSecularVariation(WMMtype_MagneticResults *MagneticVariation, WMMtype_GeoMagneticElements *MagneticElements)
486  {
487  /* This takes the Magnetic Variation in x, y, and z and uses it to calculate the secular variation of each of the Geomagnetic elements.
488  INPUT MagneticVariation Data structure with the following elements
489  float Bx; ( North )
490  float By; ( East )
491  float Bz; ( Down )
492  OUTPUT MagneticElements Pointer to the data structure with the following elements updated
493  float Decldot; Yearly Rate of change in declination
494  float Incldot; Yearly Rate of change in inclination
495  float Fdot; Yearly rate of change in Magnetic field strength
496  float Hdot; Yearly rate of change in horizontal field strength
497  float Xdot; Yearly rate of change in the northern component
498  float Ydot; Yearly rate of change in the eastern component
499  float Zdot; Yearly rate of change in the downward component
500  float GVdot;Yearly rate of chnage in grid variation
501  */
502 
503  MagneticElements->Xdot = MagneticVariation->Bx;
504  MagneticElements->Ydot = MagneticVariation->By;
505  MagneticElements->Zdot = MagneticVariation->Bz;
506  MagneticElements->Hdot = (MagneticElements->X * MagneticElements->Xdot + MagneticElements->Y * MagneticElements->Ydot) / MagneticElements->H; //See equation 19 in the WMM technical report
507  MagneticElements->Fdot =
508  (MagneticElements->X * MagneticElements->Xdot +
509  MagneticElements->Y * MagneticElements->Ydot + MagneticElements->Z * MagneticElements->Zdot) / MagneticElements->F;
510  MagneticElements->Decldot =
511  180.0 / M_PI * (MagneticElements->X * MagneticElements->Ydot -
512  MagneticElements->Y * MagneticElements->Xdot) / (MagneticElements->H * MagneticElements->H);
513  MagneticElements->Incldot =
514  180.0 / M_PI * (MagneticElements->H * MagneticElements->Zdot -
515  MagneticElements->Z * MagneticElements->Hdot) / (MagneticElements->F * MagneticElements->F);
516  MagneticElements->GVdot = MagneticElements->Decldot;
517  }
518 
519  int WorldMagModel::PcupHigh(double *Pcup, double *dPcup, double x, int nMax)
520  {
521  /* This function evaluates all of the Schmidt-semi normalized associated Legendre
522  functions up to degree nMax. The functions are initially scaled by
523  10^280 sin^m in order to minimize the effects of underflow at large m
524  near the poles (see Holmes and Featherstone 2002, J. Geodesy, 76, 279-299).
525  Note that this function performs the same operation as WMM_PcupLow.
526  However this function also can be used for high degree (large nMax) models.
527 
528  Calling Parameters:
529  INPUT
530  nMax: Maximum spherical harmonic degree to compute.
531  x: cos(colatitude) or sin(latitude).
532 
533  OUTPUT
534  Pcup: A vector of all associated Legendgre polynomials evaluated at
535  x up to nMax. The lenght must by greater or equal to (nMax+1)*(nMax+2)/2.
536  dPcup: Derivative of Pcup(x) with respect to latitude
537  Notes:
538 
539  Adopted from the FORTRAN code written by Mark Wieczorek September 25, 2005.
540 
541  Manoj Nair, Nov, 2009 Manoj.C.Nair@Noaa.Gov
542 
543  Change from the previous version
544  The prevous version computes the derivatives as
545  dP(n,m)(x)/dx, where x = sin(latitude) (or cos(colatitude) ).
546  However, the WMM Geomagnetic routines requires dP(n,m)(x)/dlatitude.
547  Hence the derivatives are multiplied by sin(latitude).
548  Removed the options for CS phase and normalizations.
549 
550  Note: In geomagnetism, the derivatives of ALF are usually found with
551  respect to the colatitudes. Here the derivatives are found with respect
552  to the latitude. The difference is a sign reversal for the derivative of
553  the Associated Legendre Functions.
554 
555  The derivates can't be computed for latitude = |90| degrees.
556  */
557  double f1[WMM_NUMPCUP];
558  double f2[WMM_NUMPCUP];
559  double PreSqr[WMM_NUMPCUP];
560  int m;
561 
562  if (fabs(x) == 1.0)
563  {
564  // printf("Error in PcupHigh: derivative cannot be calculated at poles\n");
565  return -2;
566  }
567 
568  double scalef = 1.0e-280;
569 
570  for (int n = 0; n <= 2 * nMax + 1; ++n)
571  PreSqr[n] = sqrt((double)(n));
572 
573  int k = 2;
574 
575  for (int n = 2; n <= nMax; n++)
576  {
577  k = k + 1;
578  f1[k] = (double)(2 * n - 1) / n;
579  f2[k] = (double)(n - 1) / n;
580  for (int m = 1; m <= n - 2; m++)
581  {
582  k = k + 1;
583  f1[k] = (double)(2 * n - 1) / PreSqr[n + m] / PreSqr[n - m];
584  f2[k] = PreSqr[n - m - 1] * PreSqr[n + m - 1] / PreSqr[n + m] / PreSqr[n - m];
585  }
586  k = k + 2;
587  }
588 
589  /*z = sin (geocentric latitude) */
590  double z = sqrt((1.0 - x) * (1.0 + x));
591  double pm2 = 1.0;
592  Pcup[0] = 1.0;
593  dPcup[0] = 0.0;
594  if (nMax == 0)
595  return -3;
596  double pm1 = x;
597  Pcup[1] = pm1;
598  dPcup[1] = z;
599  k = 1;
600 
601  for (int n = 2; n <= nMax; n++)
602  {
603  k = k + n;
604  double plm = f1[k] * x * pm1 - f2[k] * pm2;
605  Pcup[k] = plm;
606  dPcup[k] = (double)(n) * (pm1 - x * plm) / z;
607  pm2 = pm1;
608  pm1 = plm;
609  }
610 
611  double pmm = PreSqr[2] * scalef;
612  double rescalem = 1.0 / scalef;
613  int kstart = 0;
614 
615  for (m = 1; m <= nMax - 1; ++m)
616  {
617  rescalem = rescalem * z;
618 
619  /* Calculate Pcup(m,m) */
620  kstart = kstart + m + 1;
621  pmm = pmm * PreSqr[2 * m + 1] / PreSqr[2 * m];
622  Pcup[kstart] = pmm * rescalem / PreSqr[2 * m + 1];
623  dPcup[kstart] = -((double)(m) * x * Pcup[kstart] / z);
624  pm2 = pmm / PreSqr[2 * m + 1];
625  /* Calculate Pcup(m+1,m) */
626  k = kstart + m + 1;
627  pm1 = x * PreSqr[2 * m + 1] * pm2;
628  Pcup[k] = pm1 * rescalem;
629  dPcup[k] = ((pm2 * rescalem) * PreSqr[2 * m + 1] - x * (double)(m + 1) * Pcup[k]) / z;
630  /* Calculate Pcup(n,m) */
631  for (int n = m + 2; n <= nMax; ++n)
632  {
633  k = k + n;
634  double plm = x * f1[k] * pm1 - f2[k] * pm2;
635  Pcup[k] = plm * rescalem;
636  dPcup[k] = (PreSqr[n + m] * PreSqr[n - m] * (pm1 * rescalem) - (double)(n) * x * Pcup[k]) / z;
637  pm2 = pm1;
638  pm1 = plm;
639  }
640  }
641 
642  /* Calculate Pcup(nMax,nMax) */
643  rescalem = rescalem * z;
644  kstart = kstart + m + 1;
645  pmm = pmm / PreSqr[2 * nMax];
646  Pcup[kstart] = pmm * rescalem;
647  dPcup[kstart] = -(double)(nMax) * x * Pcup[kstart] / z;
648 
649  // *********
650 
651  return 0; // OK
652  }
653 
654  void WorldMagModel::PcupLow(double *Pcup, double *dPcup, double x, int nMax)
655  {
656  /* This function evaluates all of the Schmidt-semi normalized associated Legendre functions up to degree nMax.
657 
658  Calling Parameters:
659  INPUT
660  nMax: Maximum spherical harmonic degree to compute.
661  x: cos(colatitude) or sin(latitude).
662 
663  OUTPUT
664  Pcup: A vector of all associated Legendgre polynomials evaluated at
665  x up to nMax.
666  dPcup: Derivative of Pcup(x) with respect to latitude
667 
668  Notes: Overflow may occur if nMax > 20 , especially for high-latitudes.
669  Use WMM_PcupHigh for large nMax.
670 
671  Writted by Manoj Nair, June, 2009 . Manoj.C.Nair@Noaa.Gov.
672 
673  Note: In geomagnetism, the derivatives of ALF are usually found with
674  respect to the colatitudes. Here the derivatives are found with respect
675  to the latitude. The difference is a sign reversal for the derivative of
676  the Associated Legendre Functions.
677  */
678 
679  double schmidtQuasiNorm[WMM_NUMPCUP];
680 
681  Pcup[0] = 1.0;
682  dPcup[0] = 0.0;
683 
684  /*sin (geocentric latitude) - sin_phi */
685  double z = sqrt((1.0 - x) * (1.0 + x));
686 
687  /* First, Compute the Gauss-normalized associated Legendre functions */
688  for (int n = 1; n <= nMax; n++)
689  {
690  for (int m = 0; m <= n; m++)
691  {
692  int index = (n * (n + 1) / 2 + m);
693  if (n == m)
694  {
695  int index1 = (n - 1) * n / 2 + m - 1;
696  Pcup[index] = z * Pcup[index1];
697  dPcup[index] = z * dPcup[index1] + x * Pcup[index1];
698  }
699  else
700  if (n == 1 && m == 0)
701  {
702  int index1 = (n - 1) * n / 2 + m;
703  Pcup[index] = x * Pcup[index1];
704  dPcup[index] = x * dPcup[index1] - z * Pcup[index1];
705  }
706  else
707  if (n > 1 && n != m)
708  {
709  int index1 = (n - 2) * (n - 1) / 2 + m;
710  int index2 = (n - 1) * n / 2 + m;
711  if (m > n - 2)
712  {
713  Pcup[index] = x * Pcup[index2];
714  dPcup[index] = x * dPcup[index2] - z * Pcup[index2];
715  }
716  else
717  {
718  double k = (double)(((n - 1) * (n - 1)) - (m * m)) / (double)((2 * n - 1) * (2 * n - 3));
719  Pcup[index] = x * Pcup[index2] - k * Pcup[index1];
720  dPcup[index] = x * dPcup[index2] - z * Pcup[index2] - k * dPcup[index1];
721  }
722  }
723  }
724  }
725 
726  /*Compute the ration between the Gauss-normalized associated Legendre
727  functions and the Schmidt quasi-normalized version. This is equivalent to
728  sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
729 
730  schmidtQuasiNorm[0] = 1.0;
731  for (int n = 1; n <= nMax; n++)
732  {
733  int index = (n * (n + 1) / 2);
734  int index1 = (n - 1) * n / 2;
735  /* for m = 0 */
736  schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * (double)(2 * n - 1) / (double)n;
737 
738  for (int m = 1; m <= n; m++)
739  {
740  index = (n * (n + 1) / 2 + m);
741  index1 = (n * (n + 1) / 2 + m - 1);
742  schmidtQuasiNorm[index] = schmidtQuasiNorm[index1] * sqrt((double)((n - m + 1) * (m == 1 ? 2 : 1)) / (double)(n + m));
743  }
744  }
745 
746  /* Converts the Gauss-normalized associated Legendre
747  functions to the Schmidt quasi-normalized version using pre-computed
748  relation stored in the variable schmidtQuasiNorm */
749 
750  for (int n = 1; n <= nMax; n++)
751  {
752  for (int m = 0; m <= n; m++)
753  {
754  int index = (n * (n + 1) / 2 + m);
755  Pcup[index] = Pcup[index] * schmidtQuasiNorm[index];
756  dPcup[index] = -dPcup[index] * schmidtQuasiNorm[index];
757  /* The sign is changed since the new WMM routines use derivative with respect to latitude insted of co-latitude */
758  }
759  }
760  }
761 
762  void WorldMagModel::SummationSpecial(WMMtype_SphericalHarmonicVariables *SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
763  {
764  /* Special calculation for the component By at Geographic poles.
765  Manoj Nair, June, 2009 manoj.c.nair@noaa.gov
766  INPUT: MagneticModel
767  SphVariables
768  CoordSpherical
769  OUTPUT: MagneticResults
770  CALLS : none
771  See Section 1.4, "SINGULARITIES AT THE GEOGRAPHIC POLES", WMM Technical report
772  */
773 
774  double PcupS[WMM_NUMPCUPS];
775 
776  PcupS[0] = 1;
777  double schmidtQuasiNorm1 = 1.0;
778 
779  MagneticResults->By = 0.0;
780  double sin_phi = sin(CoordSpherical->phig * DEG2RAD);
781 
782  for (int n = 1; n <= MagneticModel.nMax; n++)
783  {
784  /*Compute the ration between the Gauss-normalized associated Legendre
785  functions and the Schmidt quasi-normalized version. This is equivalent to
786  sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
787 
788  int index = (n * (n + 1) / 2 + 1);
789  double schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
790  double schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
791  schmidtQuasiNorm1 = schmidtQuasiNorm2;
792  if (n == 1)
793  {
794  PcupS[n] = PcupS[n - 1];
795  }
796  else
797  {
798  double k = (double)(((n - 1) * (n - 1)) - 1) / (double)((2 * n - 1) * (2 * n - 3));
799  PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
800  }
801 
802 /* 1 nMax (n+2) n m m m
803  By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
804  n=1 m=0 n n n */
805 /* Equation 11 in the WMM Technical report. Derivative with respect to longitude, divided by radius. */
806 
807  MagneticResults->By +=
808  SphVariables->RelativeRadiusPower[n] *
809  (get_main_field_coeff_g(index) *
810  SphVariables->sin_mlambda[1] - get_main_field_coeff_h(index) * SphVariables->cos_mlambda[1])
811  * PcupS[n] * schmidtQuasiNorm3;
812  }
813  }
814 
815  void WorldMagModel::SecVarSummationSpecial(WMMtype_SphericalHarmonicVariables *SphVariables, WMMtype_CoordSpherical *CoordSpherical, WMMtype_MagneticResults *MagneticResults)
816  {
817  /*Special calculation for the secular variation summation at the poles.
818 
819  INPUT: MagneticModel
820  SphVariables
821  CoordSpherical
822  OUTPUT: MagneticResults
823  */
824 
825  double PcupS[WMM_NUMPCUPS];
826 
827  PcupS[0] = 1;
828  double schmidtQuasiNorm1 = 1.0;
829 
830  MagneticResults->By = 0.0;
831  double sin_phi = sin(CoordSpherical->phig * DEG2RAD);
832 
833  for (int n = 1; n <= MagneticModel.nMaxSecVar; n++)
834  {
835  int index = (n * (n + 1) / 2 + 1);
836  double schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
837  double schmidtQuasiNorm3 = schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
838  schmidtQuasiNorm1 = schmidtQuasiNorm2;
839  if (n == 1)
840  {
841  PcupS[n] = PcupS[n - 1];
842  }
843  else
844  {
845  double k = (double)(((n - 1) * (n - 1)) - 1) / (double)((2 * n - 1) * (2 * n - 3));
846  PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
847  }
848 
849 /* 1 nMax (n+2) n m m m
850  By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
851  n=1 m=0 n n n */
852 /* Derivative with respect to longitude, divided by radius. */
853 
854  MagneticResults->By +=
855  SphVariables->RelativeRadiusPower[n] *
856  (get_secular_var_coeff_g(index) *
857  SphVariables->sin_mlambda[1] - get_secular_var_coeff_h(index) * SphVariables->cos_mlambda[1])
858  * PcupS[n] * schmidtQuasiNorm3;
859  }
860  }
861 
862  // brief Comput the MainFieldCoeffH accounting for the date
863  double WorldMagModel::get_main_field_coeff_g(int index)
864  {
865  if (index >= WMM_NUMTERMS)
866  return 0;
867 
868  double coeff = CoeffFile[index][2];
869 
870  int a = MagneticModel.nMaxSecVar;
871  int b = (a * (a + 1) / 2 + a);
872  for (int n = 1; n <= MagneticModel.nMax; n++)
873  {
874  for (int m = 0; m <= n; m++)
875  {
876  int sum_index = (n * (n + 1) / 2 + m);
877 
878  /* Hacky for now, will solve for which conditions need summing analytically */
879  if (sum_index != index)
880  continue;
881 
882  if (index <= b)
883  coeff += (decimal_date - MagneticModel.epoch) * get_secular_var_coeff_g(sum_index);
884  }
885  }
886 
887  return coeff;
888  }
889 
890  double WorldMagModel::get_main_field_coeff_h(int index)
891  {
892  if (index >= WMM_NUMTERMS)
893  return 0;
894 
895  double coeff = CoeffFile[index][3];
896 
897  int a = MagneticModel.nMaxSecVar;
898  int b = (a * (a + 1) / 2 + a);
899  for (int n = 1; n <= MagneticModel.nMax; n++)
900  {
901  for (int m = 0; m <= n; m++)
902  {
903  int sum_index = (n * (n + 1) / 2 + m);
904 
905  /* Hacky for now, will solve for which conditions need summing analytically */
906  if (sum_index != index)
907  continue;
908 
909  if (index <= b)
910  coeff += (decimal_date - MagneticModel.epoch) * get_secular_var_coeff_h(sum_index);
911  }
912  }
913 
914  return coeff;
915  }
916 
917  double WorldMagModel::get_secular_var_coeff_g(int index)
918  {
919  if (index >= WMM_NUMTERMS)
920  return 0;
921 
922  return CoeffFile[index][4];
923  }
924 
925  double WorldMagModel::get_secular_var_coeff_h(int index)
926  {
927  if (index >= WMM_NUMTERMS)
928  return 0;
929 
930  return CoeffFile[index][5];
931  }
932 
933  int WorldMagModel::DateToYear(int month, int day, int year)
934  {
935  // Converts a given calendar date into a decimal year
936 
937  int temp = 0; // Total number of days
938  int MonthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
939  int ExtraDay = 0;
940 
941  if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
942  ExtraDay = 1;
943  MonthDays[2] += ExtraDay;
944 
945  /******************Validation********************************/
946 
947  if (month <= 0 || month > 12)
948  return -1; // error
949 
950  if (day <= 0 || day > MonthDays[month])
951  return -2; // error
952 
953  /****************Calculation of t***************************/
954  for (int i = 1; i <= month; i++)
955  temp += MonthDays[i - 1];
956  temp += day;
957 
958  decimal_date = year + (temp - 1) / (365.0 + ExtraDay);
959 
960  return 0; // OK
961  }
962 
963  void WorldMagModel::GeodeticToSpherical(WMMtype_CoordGeodetic *CoordGeodetic, WMMtype_CoordSpherical *CoordSpherical)
964  {
965  // Converts Geodetic coordinates to Spherical coordinates
966  // Convert geodetic coordinates, (defined by the WGS-84
967  // reference ellipsoid), to Earth Centered Earth Fixed Cartesian
968  // coordinates, and then to spherical coordinates.
969 
970  double CosLat = cos(CoordGeodetic->phi * DEG2RAD);
971  double SinLat = sin(CoordGeodetic->phi * DEG2RAD);
972 
973  // compute the local radius of curvature on the WGS-84 reference ellipsoid
974  double rc = Ellip.a / sqrt(1.0 - Ellip.epssq * SinLat * SinLat);
975 
976  // compute ECEF Cartesian coordinates of specified point (for longitude=0)
977  double xp = (rc + CoordGeodetic->HeightAboveEllipsoid) * CosLat;
978  double zp = (rc * (1.0 - Ellip.epssq) + CoordGeodetic->HeightAboveEllipsoid) * SinLat;
979 
980  // compute spherical radius and angle lambda and phi of specified point
981  CoordSpherical->r = sqrt(xp * xp + zp * zp);
982  CoordSpherical->phig = asin(zp / CoordSpherical->r) * RAD2DEG; // geocentric latitude
983  CoordSpherical->lambda = CoordGeodetic->lambda; // longitude
984  }
985 
986 }
int GetMagVector(double LLA[3], int Month, int Day, int Year, double Be[3])
DEG2RAD
Definition: OPPlots.m:107
double cos_mlambda[WMM_MAX_MODEL_DEGREES+1]
for i
Definition: OPPlots.m:140
double dPcup[WMM_NUMPCUP]
Definition: worldmagmodel.h:91
end a
Definition: OPPlots.m:98
Lat
Definition: OPPlots.m:110
double RelativeRadiusPower[WMM_MAX_MODEL_DEGREES+1]
Eccentricity n
Definition: OPPlots.m:137
z
Definition: OPPlots.m:102
RAD2DEG
Definition: OPPlots.m:106
const double CoeffFile[91][6]
double sin_mlambda[WMM_MAX_MODEL_DEGREES+1]
LLA
Definition: OPPlots.m:34
x
Definition: OPPlots.m:100
e
Definition: OPPlots.m:99
double Pcup[WMM_NUMPCUP]
Definition: worldmagmodel.h:90