OpenCPN Partial API docs
Loading...
Searching...
No Matches
GeomagnetismLibrary.c
1
2#include <stdio.h>
3#include <string.h>
4#include <math.h>
5#include <stdlib.h>
6#include <ctype.h>
7#include <assert.h>
8#include <time.h>
9#include "GeomagnetismHeader.h"
10
11/* $Id: GeomagnetismLibrary.c 1521 2017-01-24 17:52:41Z awoods $
12 *
13 * ABSTRACT
14 *
15 * The purpose of Geomagnetism Library is primarily to support the World
16 Magnetic Model (WMM) 2015-2020.
17 * It however is built to be used for spherical harmonic models of the Earth's
18 magnetic field
19 * generally and supports models even with a large (>>12) number of degrees. It
20 is also used in many
21 * other geomagnetic models distributed by NCEI.
22 *
23 * REUSE NOTES
24 *
25 * Geomagnetism Library is intended for reuse by any application that requires
26 * Computation of Geomagnetic field from a spherical harmonic model.
27 *
28 * REFERENCES
29 *
30 * Further information on Geoid can be found in the WMM Technical Documents.
31 *
32 *
33 * LICENSES
34 *
35 * The WMM source code is in the public domain and not licensed or under
36 copyright. * The information and software may be used freely by the public.
37 As required by 17 U.S.C. 403, * third parties producing copyrighted
38 works consisting predominantly of the material produced by * U.S. government
39 agencies must provide notice with such work(s) identifying the U.S. Government
40 material * incorporated and stating that such material is not subject to
41 copyright protection.
42 *
43 * RESTRICTIONS
44 *
45 * Geomagnetism library has no restrictions.
46 *
47 * ENVIRONMENT
48 *
49 * Geomagnetism library was tested in the following environments
50 *
51 * 1. Red Hat Linux with GCC Compiler
52 * 2. MS Windows 7 with MinGW compiler
53 * 3. Sun Solaris with GCC Compiler
54 *
55 *
56
57
58 * National Centers for Environmental Information
59 * NOAA E/NE42, 325 Broadway
60 * Boulder, CO 80305 USA
61 * Attn: Arnaud Chulliat
62 * Phone: (303) 497-6522
63 * Email: Arnaud.Chulliat@noaa.gov
64
65 * Software and Model Support
66 * National Centers for Environmental Information
67 * NOAA E/NE42
68 * 325 Broadway
69 * Boulder, CO 80305 USA
70 * Attn: Adam Woods or Manoj Nair
71 * Phone: (303) 497-6640 or -4642
72 * Email: geomag.models@noaa.gov
73 * URL: http://www.ngdc.noaa.gov/Geomagnetic/WMM/DoDWMM.shtml
74
75
76 * For more details on the subroutines, please consult the WMM
77 * Technical Documentations at
78 * http://www.ngdc.noaa.gov/Geomagnetic/WMM/DoDWMM.shtml
79
80 * Nov 23, 2009
81 * Written by Manoj C Nair and Adam Woods
82 * Manoj.C.Nair@noaa.Gov
83 * Adam.Woods@noaa.gov
84 */
85
86double MAG_dtstr_to_dyear(char *edit_date) {
87 /* Parse the date string format as mm/dd/yyyy*/
88
89 int day, month, year;
90 double extra_day = 0;
91 int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
92 int total_days = 0;
93 double dyear;
94 struct tm tm = {0};
95
96 if (sscanf(edit_date, "%d/%d/%d", &month, &day, &year) != 3) {
97 printf(
98 "Failed to parse the date string. Please use the format mm/dd/yyyy\n");
99 return -1;
100 }
101
102 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
103 extra_day = 1;
104 }
105
106 double total_year_days = 365.0 + extra_day;
107 months[2] = months[2] + extra_day;
108
109 for (int i = 0; i < month; i++) {
110 total_days += months[i];
111 }
112 total_days += day;
113
114 dyear = (double)year + (double)(total_days - 1) / total_year_days;
115
116 return dyear;
117}
118
119/******************************************************************************
120 ************************************Wrapper***********************************
121 * This grouping consists of functions call groups of other functions to do a
122 * complete calculation of some sort. For example, the MAG_Geomag function
123 * does everything necessary to compute the geomagnetic elements from a given
124 * geodetic point in space and magnetic model adjusted for the appropriate
125 * date. These functions are the external functions necessary to create a
126 * program that uses or calculates the magnetic field.
127 ******************************************************************************
128 ******************************************************************************/
129
130int MAG_Geomag(MAGtype_Ellipsoid Ellip, MAGtype_CoordSpherical CoordSpherical,
131 MAGtype_CoordGeodetic CoordGeodetic,
132 MAGtype_MagneticModel *TimedMagneticModel,
133 MAGtype_GeoMagneticElements *GeoMagneticElements)
134/*
135The main subroutine that calls a sequence of WMM sub-functions to calculate the
136magnetic field elements for a single point. The function expects the model
137coefficients and point coordinates as input and returns the magnetic field
138elements and their rate of change. Though, this subroutine can be called
139successively to calculate a time series, profile or grid of magnetic field,
140these are better achieved by the subroutine MAG_Grid.
141
142INPUT: Ellip
143 CoordSpherical
144 CoordGeodetic
145 TimedMagneticModel
146
147OUTPUT : GeoMagneticElements
148
149CALLS: MAG_AllocateLegendreFunctionMemory(NumTerms); ( For storing the
150ALF functions ) MAG_ComputeSphericalHarmonicVariables( Ellip, CoordSpherical,
151TimedMagneticModel->nMax, &SphVariables); (Compute Spherical Harmonic variables
152) MAG_AssociatedLegendreFunction(CoordSpherical, TimedMagneticModel->nMax,
153LegendreFunction); Compute ALF MAG_Summation(LegendreFunction,
154TimedMagneticModel, SphVariables, CoordSpherical, &MagneticResultsSph);
155Accumulate the spherical harmonic coefficients
156 MAG_SecVarSummation(LegendreFunction, TimedMagneticModel,
157SphVariables, CoordSpherical, &MagneticResultsSphVar); Sum the Secular Variation
158Coefficients MAG_RotateMagneticVector(CoordSpherical, CoordGeodetic,
159MagneticResultsSph, &MagneticResultsGeo); Map the computed Magnetic fields to
160Geodetic coordinates MAG_CalculateGeoMagneticElements(&MagneticResultsGeo,
161GeoMagneticElements); Calculate the Geomagnetic elements
162 MAG_CalculateSecularVariationElements(MagneticResultsGeoVar,
163GeoMagneticElements); Calculate the secular variation of each of the Geomagnetic
164elements
165
166 */
167{
168 MAGtype_LegendreFunction *LegendreFunction;
170 int NumTerms;
171 MAGtype_MagneticResults MagneticResultsSph, MagneticResultsGeo,
172 MagneticResultsSphVar, MagneticResultsGeoVar;
173
174 NumTerms =
175 ((TimedMagneticModel->nMax + 1) * (TimedMagneticModel->nMax + 2) / 2);
176 LegendreFunction = MAG_AllocateLegendreFunctionMemory(
177 NumTerms); /* For storing the ALF functions */
178 SphVariables = MAG_AllocateSphVarMemory(TimedMagneticModel->nMax);
179 MAG_ComputeSphericalHarmonicVariables(
180 Ellip, CoordSpherical, TimedMagneticModel->nMax,
181 SphVariables); /* Compute Spherical Harmonic variables */
182 MAG_AssociatedLegendreFunction(CoordSpherical, TimedMagneticModel->nMax,
183 LegendreFunction); /* Compute ALF */
184 MAG_Summation(
185 LegendreFunction, TimedMagneticModel, *SphVariables, CoordSpherical,
186 &MagneticResultsSph); /* Accumulate the spherical harmonic coefficients*/
187 MAG_SecVarSummation(
188 LegendreFunction, TimedMagneticModel, *SphVariables, CoordSpherical,
189 &MagneticResultsSphVar); /*Sum the Secular Variation Coefficients */
190 MAG_RotateMagneticVector(
191 CoordSpherical, CoordGeodetic, MagneticResultsSph,
192 &MagneticResultsGeo); /* Map the computed Magnetic fields to Geodeitic
193 coordinates */
194 MAG_RotateMagneticVector(
195 CoordSpherical, CoordGeodetic, MagneticResultsSphVar,
196 &MagneticResultsGeoVar); /* Map the secular variation field components to
197 Geodetic coordinates*/
198 MAG_CalculateGeoMagneticElements(
199 &MagneticResultsGeo,
200 GeoMagneticElements); /* Calculate the Geomagnetic elements, Equation 19 ,
201 WMM Technical report */
202 MAG_CalculateSecularVariationElements(
203 MagneticResultsGeoVar,
204 GeoMagneticElements); /*Calculate the secular variation of each of the
205 Geomagnetic elements*/
206
207 MAG_FreeLegendreMemory(LegendreFunction);
208 MAG_FreeSphVarMemory(SphVariables);
209
210 return TRUE;
211} /*MAG_Geomag*/
212
213void MAG_Gradient(MAGtype_Ellipsoid Ellip, MAGtype_CoordGeodetic CoordGeodetic,
214 MAGtype_MagneticModel *TimedMagneticModel,
215 MAGtype_Gradient *Gradient) {
216 /*It should be noted that the x[2], y[2], and z[2] variables are NOT the same
217 coordinate system as the directions in which the gradients are taken. These
218 variables represent a Cartesian coordinate system where the Earth's center is
219 the origin, 'z' points up toward the North (rotational) pole and 'x' points
220 toward the prime meridian. 'y' points toward longitude = 90 degrees East.
221 The gradient is preformed along a local Cartesian coordinate system with the
222 origin at CoordGeodetic. 'z' points down toward the Earth's core, x points
223 North, tangent to the local longitude line, and 'y' points East, tangent to
224 the local latitude line.*/
225 double phiDelta = 0.01, /*DeltaY = 0.01,*/ hDelta = -1, x[2], y[2], z[2],
226 distance;
227
228 MAGtype_CoordSpherical AdjCoordSpherical;
229 MAGtype_CoordGeodetic AdjCoordGeodetic;
230 MAGtype_GeoMagneticElements GeomagneticElements, AdjGeoMagneticElements[2];
231
232 /*Initialization*/
233 MAG_GeodeticToSpherical(Ellip, CoordGeodetic, &AdjCoordSpherical);
234 MAG_Geomag(Ellip, AdjCoordSpherical, CoordGeodetic, TimedMagneticModel,
235 &GeomagneticElements);
236 AdjCoordGeodetic = MAG_CoordGeodeticAssign(CoordGeodetic);
237
238 /*Gradient along x*/
239
240 AdjCoordGeodetic.phi = CoordGeodetic.phi + phiDelta;
241 MAG_GeodeticToSpherical(Ellip, AdjCoordGeodetic, &AdjCoordSpherical);
242 MAG_Geomag(Ellip, AdjCoordSpherical, AdjCoordGeodetic, TimedMagneticModel,
243 &AdjGeoMagneticElements[0]);
244 MAG_SphericalToCartesian(AdjCoordSpherical, &x[0], &y[0], &z[0]);
245 AdjCoordGeodetic.phi = CoordGeodetic.phi - phiDelta;
246 MAG_GeodeticToSpherical(Ellip, AdjCoordGeodetic, &AdjCoordSpherical);
247 MAG_Geomag(Ellip, AdjCoordSpherical, AdjCoordGeodetic, TimedMagneticModel,
248 &AdjGeoMagneticElements[1]);
249 MAG_SphericalToCartesian(AdjCoordSpherical, &x[1], &y[1], &z[1]);
250
251 distance =
252 sqrt((x[0] - x[1]) * (x[0] - x[1]) + (y[0] - y[1]) * (y[0] - y[1]) +
253 (z[0] - z[1]) * (z[0] - z[1]));
254 Gradient->GradPhi = MAG_GeoMagneticElementsSubtract(
255 AdjGeoMagneticElements[0], AdjGeoMagneticElements[1]);
256 Gradient->GradPhi =
257 MAG_GeoMagneticElementsScale(Gradient->GradPhi, 1 / distance);
258 AdjCoordGeodetic = MAG_CoordGeodeticAssign(CoordGeodetic);
259
260 /*Gradient along y*/
261
262 /*It is perhaps noticeable that the method here for calculation is
263 substantially different than that for the gradient along x. As we near the
264 North pole the longitude lines approach each other, and the calculation that
265 works well for latitude lines becomes unstable when 0.01 degrees represents
266 sufficiently small numbers, and fails to function correctly at all at the
267 North Pole*/
268
269 MAG_GeodeticToSpherical(Ellip, CoordGeodetic, &AdjCoordSpherical);
270 MAG_GradY(Ellip, AdjCoordSpherical, CoordGeodetic, TimedMagneticModel,
271 GeomagneticElements, &(Gradient->GradLambda));
272
273 /*Gradient along z*/
274 AdjCoordGeodetic.HeightAboveEllipsoid =
275 CoordGeodetic.HeightAboveEllipsoid + hDelta;
276 AdjCoordGeodetic.HeightAboveGeoid = CoordGeodetic.HeightAboveGeoid + hDelta;
277 MAG_GeodeticToSpherical(Ellip, AdjCoordGeodetic, &AdjCoordSpherical);
278 MAG_Geomag(Ellip, AdjCoordSpherical, AdjCoordGeodetic, TimedMagneticModel,
279 &AdjGeoMagneticElements[0]);
280 MAG_SphericalToCartesian(AdjCoordSpherical, &x[0], &y[0], &z[0]);
281 AdjCoordGeodetic.HeightAboveEllipsoid =
282 CoordGeodetic.HeightAboveEllipsoid - hDelta;
283 AdjCoordGeodetic.HeightAboveGeoid = CoordGeodetic.HeightAboveGeoid - hDelta;
284 MAG_GeodeticToSpherical(Ellip, AdjCoordGeodetic, &AdjCoordSpherical);
285 MAG_Geomag(Ellip, AdjCoordSpherical, AdjCoordGeodetic, TimedMagneticModel,
286 &AdjGeoMagneticElements[1]);
287 MAG_SphericalToCartesian(AdjCoordSpherical, &x[1], &y[1], &z[1]);
288
289 distance =
290 sqrt((x[0] - x[1]) * (x[0] - x[1]) + (y[0] - y[1]) * (y[0] - y[1]) +
291 (z[0] - z[1]) * (z[0] - z[1]));
292 Gradient->GradZ = MAG_GeoMagneticElementsSubtract(AdjGeoMagneticElements[0],
293 AdjGeoMagneticElements[1]);
294 Gradient->GradZ = MAG_GeoMagneticElementsScale(Gradient->GradZ, 1 / distance);
295 AdjCoordGeodetic = MAG_CoordGeodeticAssign(CoordGeodetic);
296}
297
298int MAG_SetDefaults(MAGtype_Ellipsoid *Ellip, MAGtype_Geoid *Geoid)
299
300/*
301 Sets default values for WMM subroutines.
302
303 UPDATES : Ellip
304 Geoid
305
306 CALLS : none
307 */
308{
309 /* Sets WGS-84 parameters */
310 Ellip->a = 6378.137; /*semi-major axis of the ellipsoid in */
311 Ellip->b = 6356.7523142; /*semi-minor axis of the ellipsoid in */
312 Ellip->fla = 1 / 298.257223563; /* flattening */
313 Ellip->eps = sqrt(1 - (Ellip->b * Ellip->b) /
314 (Ellip->a * Ellip->a)); /*first eccentricity */
315 Ellip->epssq = (Ellip->eps * Ellip->eps); /*first eccentricity squared */
316 Ellip->re = 6371.2; /* Earth's radius */
317
318 /* Sets EGM-96 model file parameters */
319 Geoid->NumbGeoidCols =
320 1441; /* 360 degrees of longitude at 15 minute spacing */
321 Geoid->NumbGeoidRows =
322 721; /* 180 degrees of latitude at 15 minute spacing */
323 Geoid->NumbHeaderItems =
324 6; /* min, max lat, min, max long, lat, long spacing*/
325 Geoid->ScaleFactor = 4; /* 4 grid cells per degree at 15 minute spacing */
326 Geoid->NumbGeoidElevs = Geoid->NumbGeoidCols * Geoid->NumbGeoidRows;
327 Geoid->Geoid_Initialized =
328 0; /* Geoid will be initialized only if this is set to zero */
329 Geoid->UseGeoid = MAG_USE_GEOID;
330
331 return TRUE;
332} /*MAG_SetDefaults */
333
334int MAG_robustReadMagModels(char *filename,
335 MAGtype_MagneticModel *(*magneticmodels)[],
336 int array_size) {
337 char *line = malloc(sizeof(char) * MAXLINELENGTH);
338 int n, nMax = 0, num_terms, a;
339 FILE *MODELFILE;
340 MODELFILE = fopen(filename, "r");
341 if (MODELFILE == 0) {
342 return 0;
343 }
344 if (NULL == fgets(line, MAXLINELENGTH, MODELFILE)) {
345 return 0;
346 }
347
348 if (array_size == 1) {
349 do {
350 if (NULL == fgets(line, MAXLINELENGTH, MODELFILE)) break;
351 a = sscanf(line, "%d", &n);
352 if (n > nMax && (n < 99999 && a == 1 && n > 0)) nMax = n;
353 } while (n < 99999 && a == 1);
354 num_terms = CALCULATE_NUMTERMS(nMax);
355 (*magneticmodels)[0] = MAG_AllocateModelMemory(num_terms);
356 (*magneticmodels)[0]->nMax = nMax;
357 (*magneticmodels)[0]->nMaxSecVar = nMax;
358 MAG_readMagneticModel(filename, (*magneticmodels)[0]);
359 (*magneticmodels)[0]->CoefficientFileEndDate =
360 (*magneticmodels)[0]->epoch + 5;
361
362 } else {
363 fclose(MODELFILE);
364 free(line);
365 return 0;
366 }
367 free(line);
368 fclose(MODELFILE);
369
370 return 1;
371} /*MAG_robustReadMagModels*/
372
373/*End of Wrapper Functions*/
374
375/******************************************************************************
376 ********************************User Interface********************************
377 * This grouping consists of functions which interact with the directly with
378 * the user and are generally specific to the XXX_point.c, XXX_grid.c, and
379 * XXX_file.c programs. They deal with input from and output to the user.
380 ******************************************************************************/
381
382void MAG_Error(int control)
383
384/*This prints WMM errors.
385INPUT control Error look up number
386OUTPUT none
387CALLS : none
388
389 */
390{
391 switch (control) {
392 case 1:
393 printf("\nError allocating in MAG_LegendreFunctionMemory.\n");
394 break;
395 case 2:
396 printf("\nError allocating in MAG_AllocateModelMemory.\n");
397 break;
398 case 3:
399 printf("\nError allocating in MAG_InitializeGeoid\n");
400 break;
401 case 4:
402 printf("\nError in setting default values.\n");
403 break;
404 case 5:
405 printf("\nError initializing Geoid.\n");
406 break;
407 case 6:
408 printf("\nError opening wmmhr.cof\n.");
409 break;
410 case 7:
411 printf("\nError opening WMMSV.COF\n.");
412 break;
413 case 8:
414 printf("\nError reading Magnetic Model.\n");
415 break;
416 case 9:
417 printf("\nError printing Command Prompt introduction.\n");
418 break;
419 case 10:
420 printf(
421 "\nError converting from geodetic co-ordinates to spherical "
422 "co-ordinates.\n");
423 break;
424 case 11:
425 printf("\nError in time modifying the Magnetic model\n");
426 break;
427 case 12:
428 printf("\nError in Geomagnetic\n");
429 break;
430 case 13:
431 printf("\nError printing user data\n");
432 break;
433 case 14:
434 printf("\nError allocating in MAG_SummationSpecial\n");
435 break;
436 case 15:
437 printf("\nError allocating in MAG_SecVarSummationSpecial\n");
438 break;
439 case 16:
440 printf("\nError in opening EGM9615.BIN file\n");
441 break;
442 case 17:
443 printf(
444 "\nError: Latitude OR Longitude out of range in "
445 "MAG_GetGeoidHeight\n");
446 break;
447 case 18:
448 printf("\nError allocating in MAG_PcupHigh\n");
449 break;
450 case 19:
451 printf("\nError allocating in MAG_PcupLow\n");
452 break;
453 case 20:
454 printf("\nError opening coefficient file\n");
455 break;
456 case 21:
457 printf("\nError: UnitDepth too large\n");
458 break;
459 case 22:
460 printf("\nYour system needs Big endian version of EGM9615.BIN. \n");
461 printf(
462 "Please download this file from "
463 "http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml. \n");
464 printf("Replace the existing EGM9615.BIN file with the downloaded one\n");
465 break;
466 }
467} /*MAG_Error*/
468
469void MAG_PrintGradient(MAGtype_Gradient Gradient) {
470 printf("\nGradient\n");
471 printf("\n Northward Eastward Downward\n");
472 printf("X: %7.1f nT/km %9.1f nT/km %9.1f nT/km \n",
473 Gradient.GradPhi.X, Gradient.GradLambda.X, Gradient.GradZ.X);
474 printf("Y: %7.1f nT/km %9.1f nT/km %9.1f nT/km \n",
475 Gradient.GradPhi.Y, Gradient.GradLambda.Y, Gradient.GradZ.Y);
476 printf("Z: %7.1f nT/km %9.1f nT/km %9.1f nT/km \n",
477 Gradient.GradPhi.Z, Gradient.GradLambda.Z, Gradient.GradZ.Z);
478 printf("H: %7.1f nT/km %9.1f nT/km %9.1f nT/km \n",
479 Gradient.GradPhi.H, Gradient.GradLambda.H, Gradient.GradZ.H);
480 printf("F: %7.1f nT/km %9.1f nT/km %9.1f nT/km \n",
481 Gradient.GradPhi.F, Gradient.GradLambda.F, Gradient.GradZ.F);
482 printf("Declination: %7.2f min/km %8.2f min/km %8.2f min/km \n",
483 Gradient.GradPhi.Decl * 60, Gradient.GradLambda.Decl * 60,
484 Gradient.GradZ.Decl * 60);
485 printf("Inclination: %7.2f min/km %8.2f min/km %8.2f min/km \n",
486 Gradient.GradPhi.Incl * 60, Gradient.GradLambda.Incl * 60,
487 Gradient.GradZ.Incl * 60);
488}
489
490void MAG_PrintUserData(MAGtype_GeoMagneticElements GeomagElements,
491 MAGtype_CoordGeodetic SpaceInput, MAGtype_Date TimeInput,
492 MAGtype_MagneticModel *MagneticModel,
493 MAGtype_Geoid *Geoid)
494/* This function prints the results in Geomagnetic Elements for a point
495calculation. It takes the calculated
496 * Geomagnetic elements "GeomagElements" as input.
497 * As well as the coordinates, date, and Magnetic Model.
498INPUT : GeomagElements : Data structure MAGtype_GeoMagneticElements with the
499following elements double Decl; (Angle between the magnetic field vector and
500true north, positive east) double Incl; Angle between the magnetic field vector
501and the horizontal plane, positive down double F; Magnetic Field Strength double
502H; Horizontal Magnetic Field Strength double X; Northern component of the
503magnetic field vector double Y; Eastern component of the magnetic field vector
504 double Z; Downward component of the magnetic field
505vector4 double Decldot; Yearly Rate of change in declination double Incldot;
506Yearly Rate of change in inclination double Fdot; Yearly rate of change in
507Magnetic field strength double Hdot; Yearly rate of change in horizontal field
508strength double Xdot; Yearly rate of change in the northern component double
509Ydot; Yearly rate of change in the eastern component double Zdot; Yearly rate of
510change in the downward component double GVdot;Yearly rate of chnage in grid
511variation CoordGeodetic Pointer to the data structure with the following
512elements double lambda; (longitude) double phi; ( geodetic latitude) double
513HeightAboveEllipsoid; (height above the ellipsoid (HaE) ) double
514HeightAboveGeoid;(height above the Geoid ) TimeInput : data structure
515MAGtype_Date with the following elements int Year; int Month; int
516Day; double DecimalYear; decimal years MagneticModel : data structure
517with the following elements double EditionDate; double epoch; Base time of
518Geomagnetic model epoch (yrs) char ModelName[20]; double *Main_Field_Coeff_G;
519C - Gauss coefficients of main geomagnetic model (nT) double
520*Main_Field_Coeff_H; C - Gauss coefficients of main geomagnetic model
521(nT) double *Secular_Var_Coeff_G; CD - Gauss coefficients of secular
522geomagnetic model (nT/yr) double *Secular_Var_Coeff_H; CD - Gauss coefficients
523of secular geomagnetic model (nT/yr) int nMax; Maximum degree of spherical
524harmonic model int nMaxSecVar; Maxumum degree of spherical harmonic secular
525model int SecularVariationUsed; Whether or not the magnetic secular variation
526vector will be needed by program OUTPUT : none
527 */
528{
529 char DeclString[100];
530 char InclString[100];
531 MAG_DegreeToDMSstring(GeomagElements.Incl, 2, InclString);
532 if (GeomagElements.H < 6000 && GeomagElements.H > 2000)
533 MAG_Warnings(1, GeomagElements.H, MagneticModel);
534 if (GeomagElements.H < 2000) MAG_Warnings(2, GeomagElements.H, MagneticModel);
535 if (MagneticModel->SecularVariationUsed == TRUE) {
536 MAG_DegreeToDMSstring(GeomagElements.Decl, 2, DeclString);
537 printf("\n Results For \n\n");
538 if (SpaceInput.phi < 0)
539 printf("Latitude %.2fS\n", -SpaceInput.phi);
540 else
541 printf("Latitude %.2fN\n", SpaceInput.phi);
542 if (SpaceInput.lambda < 0)
543 printf("Longitude %.2fW\n", -SpaceInput.lambda);
544 else
545 printf("Longitude %.2fE\n", SpaceInput.lambda);
546 if (Geoid->UseGeoid == 1)
547 printf("Altitude: %.2f Kilometers above mean sea level\n",
548 SpaceInput.HeightAboveGeoid);
549 else
550 printf("Altitude: %.2f Kilometers above the WGS-84 ellipsoid\n",
551 SpaceInput.HeightAboveEllipsoid);
552 printf("Date: %.1f\n", TimeInput.DecimalYear);
553 printf("\n Main Field\t\t\tSecular Change\n");
554 printf("F = %-9.1f nT\t\t Fdot = %.1f\tnT/yr\n", GeomagElements.F,
555 GeomagElements.Fdot);
556 printf("H = %-9.1f nT\t\t Hdot = %.1f\tnT/yr\n", GeomagElements.H,
557 GeomagElements.Hdot);
558 printf("X = %-9.1f nT\t\t Xdot = %.1f\tnT/yr\n", GeomagElements.X,
559 GeomagElements.Xdot);
560 printf("Y = %-9.1f nT\t\t Ydot = %.1f\tnT/yr\n", GeomagElements.Y,
561 GeomagElements.Ydot);
562 printf("Z = %-9.1f nT\t\t Zdot = %.1f\tnT/yr\n", GeomagElements.Z,
563 GeomagElements.Zdot);
564 if (GeomagElements.Decl < 0)
565 printf("Decl =%20s (WEST)\t Ddot = %.1f\tMin/yr\n", DeclString,
566 60 * GeomagElements.Decldot);
567 else
568 printf("Decl =%20s (EAST)\t Ddot = %.1f\tMin/yr\n", DeclString,
569 60 * GeomagElements.Decldot);
570 if (GeomagElements.Incl < 0)
571 printf("Incl =%20s (UP)\t Idot = %.1f\tMin/yr\n", InclString,
572 60 * GeomagElements.Incldot);
573 else
574 printf("Incl =%20s (DOWN)\t Idot = %.1f\tMin/yr\n", InclString,
575 60 * GeomagElements.Incldot);
576 } else {
577 MAG_DegreeToDMSstring(GeomagElements.Decl, 2, DeclString);
578 printf("\n Results For \n\n");
579 if (SpaceInput.phi < 0)
580 printf("Latitude %.2fS\n", -SpaceInput.phi);
581 else
582 printf("Latitude %.2fN\n", SpaceInput.phi);
583 if (SpaceInput.lambda < 0)
584 printf("Longitude %.2fW\n", -SpaceInput.lambda);
585 else
586 printf("Longitude %.2fE\n", SpaceInput.lambda);
587 if (Geoid->UseGeoid == 1)
588 printf("Altitude: %.2f Kilometers above MSL\n",
589 SpaceInput.HeightAboveGeoid);
590 else
591 printf("Altitude: %.2f Kilometers above WGS-84 Ellipsoid\n",
592 SpaceInput.HeightAboveEllipsoid);
593 printf("Date: %.1f\n", TimeInput.DecimalYear);
594 printf("\n Main Field\n");
595 printf("F = %-9.1f nT\n", GeomagElements.F);
596 printf("H = %-9.1f nT\n", GeomagElements.H);
597 printf("X = %-9.1f nT\n", GeomagElements.X);
598 printf("Y = %-9.1f nT\n", GeomagElements.Y);
599 printf("Z = %-9.1f nT\n", GeomagElements.Z);
600 if (GeomagElements.Decl < 0)
601 printf("Decl =%20s (WEST)\n", DeclString);
602 else
603 printf("Decl =%20s (EAST)\n", DeclString);
604 if (GeomagElements.Incl < 0)
605 printf("Incl =%20s (UP)\n", InclString);
606 else
607 printf("Incl =%20s (DOWN)\n", InclString);
608 }
609
610 if (SpaceInput.phi <= -55 || SpaceInput.phi >= 55)
611 /* Print Grid Variation */
612 {
613 MAG_DegreeToDMSstring(GeomagElements.GV, 2, InclString);
614 printf("\n\n Grid variation =%20s\n", InclString);
615 }
616
617} /*MAG_PrintUserData*/
618
619int MAG_Warnings(int control, double value,
620 MAGtype_MagneticModel *MagneticModel)
621
622/*Return value 0 means end program, Return value 1 means get new data, Return
623value 2 means continue. This prints a warning to the screen determined by the
624control integer. It also takes the value of the parameter causing the warning as
625a double. This is unnecessary for some warnings. It requires the MagneticModel
626to determine the current epoch.
627
628 INPUT control :int : (Warning number)
629 value : double: Magnetic field strength
630 MagneticModel
631OUTPUT : none
632CALLS : none
633
634 */
635{
636 char ans[20];
637 MAG_strlcpy_equivalent(ans, "", sizeof(ans));
638
639 switch (control) {
640 case 1: /* Horizontal Field strength low */
641 do {
642 printf(
643 "\nCaution: location is approaching the blackout zone around the "
644 "magnetic pole as\n");
645 printf(" defined by the WMM military specification \n");
646 printf(
647 " "
648 "(https://www.ngdc.noaa.gov/geomag/WMM/data/MIL-PRF-89500B.pdf). "
649 "Compass\n");
650 printf(" accuracy may be degraded in this region.\n");
651 printf("Press enter to continue...\n");
652 } while (NULL == fgets(ans, sizeof(ans), stdin));
653 break;
654 case 2: /* Horizontal Field strength very low */
655 do {
656 printf(
657 "\nWarning: location is in the blackout zone around the magnetic "
658 "pole as defined\n");
659 printf(" by the WMM military specification \n");
660 printf(
661 " "
662 "(https://www.ngdc.noaa.gov/geomag/WMM/data/MIL-PRF-89500B.pdf). "
663 "Compass\n");
664 printf(" accuracy is highly degraded in this region.\n");
665 } while (NULL == fgets(ans, sizeof(ans), stdin));
666 break;
667 case 3: /* Elevation outside the recommended range */
668 printf(
669 "\nWarning: The value you have entered of %.1f km for the elevation "
670 "is outside of the recommended range.\n Elevations above -10.0 km "
671 "are recommended for accurate results. \n",
672 value);
673 while (1) {
674 printf(
675 "\nPlease press 'C' to continue, 'G' to get new data or 'X' to "
676 "exit...\n");
677 while (NULL == fgets(ans, sizeof(ans), stdin)) {
678 printf("\nInvalid input\n");
679 }
680 switch (ans[0]) {
681 case 'X':
682 case 'x':
683 return 0;
684 case 'G':
685 case 'g':
686 return 1;
687 case 'C':
688 case 'c':
689 return 2;
690 default:
691 printf("\nInvalid input %c\n", ans[0]);
692 break;
693 }
694 }
695 break;
696
697 case 4: /*Date outside the recommended range*/
698 printf(
699 "\nWARNING - TIME EXTENDS BEYOND INTENDED USAGE RANGE\n CONTACT NCEI "
700 "FOR PRODUCT UPDATES:\n");
701 printf(" National Centers for Environmental Information\n");
702 printf(" NOAA E/NE42\n");
703 printf(" 325 Broadway\n");
704 printf("\n Boulder, CO 80305 USA");
705 printf(" Attn: Manoj Nair or Arnaud Chulliat\n");
706 printf(" Phone: (303) 497-4642 or -6522\n");
707 printf(" Email: geomag.models@noaa.gov\n");
708 printf(" Web: http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml\n");
709 printf("\n VALID RANGE = %d - %d\n", (int)MagneticModel->min_year,
710 (int)MagneticModel->CoefficientFileEndDate);
711 printf(" TIME = %f\n", value);
712 while (1) {
713 printf(
714 "\nPlease press 'C' to continue, 'N' to enter new data or 'X' to "
715 "exit...\n");
716 while (NULL == fgets(ans, sizeof(ans), stdin)) {
717 printf("\nInvalid input\n");
718 }
719 switch (ans[0]) {
720 case 'X':
721 case 'x':
722 return 0;
723 case 'N':
724 case 'n':
725 return 1;
726 case 'C':
727 case 'c':
728 return 2;
729 default:
730 printf("\nInvalid input %c\n", ans[0]);
731 break;
732 }
733 }
734 break;
735 case 5: /*Elevation outside the allowable range*/
736 printf(
737 "\nError: The value you have entered of %f km for the elevation is "
738 "outside of the recommended range.\n Elevations above -10.0 km are "
739 "recommended for accurate results. \n",
740 value);
741 while (1) {
742 printf(
743 "\nPlease press 'C' to continue, 'G' to get new data or 'X' to "
744 "exit...\n");
745 while (NULL == fgets(ans, sizeof(ans), stdin)) {
746 printf("\nInvalid input\n");
747 }
748 switch (ans[0]) {
749 case 'X':
750 case 'x':
751 return 0;
752 case 'G':
753 case 'g':
754 return 1;
755 case 'C':
756 case 'c':
757 return 2;
758 default:
759 printf("\nInvalid input %c\n", ans[0]);
760 break;
761 }
762 }
763 break;
764 }
765 return 2;
766} /*MAG_Warnings*/
767
768/*End of User Interface functions*/
769
770/******************************************************************************
771 ********************************Memory and File Processing********************
772 * This grouping consists of functions that read coefficient files into the
773 * memory, allocate memory, free memory or print models into coefficient files.
774 ******************************************************************************/
775
776MAGtype_LegendreFunction *MAG_AllocateLegendreFunctionMemory(int NumTerms)
777
778/* Allocate memory for Associated Legendre Function data types.
779 Should be called before computing Associated Legendre Functions.
780
781 INPUT: NumTerms : int : Total number of spherical harmonic coefficients in the
782model
783
784
785 OUTPUT: Pointer to data structure MAGtype_LegendreFunction with the
786following elements double *Pcup; ( pointer to store Legendre Function )
787 double *dPcup; ( pointer to store Derivative of
788Legendre function )
789
790 FALSE: Failed to allocate memory
791
792CALLS : none
793
794 */
795{
796 MAGtype_LegendreFunction *LegendreFunction;
797
798 LegendreFunction =
800
801 if (!LegendreFunction) {
802 MAG_Error(1);
803 return NULL;
804 }
805 LegendreFunction->Pcup = (double *)malloc((NumTerms + 1) * sizeof(double));
806 if (LegendreFunction->Pcup == 0) {
807 MAG_Error(1);
808 return NULL;
809 }
810 LegendreFunction->dPcup = (double *)malloc((NumTerms + 1) * sizeof(double));
811 if (LegendreFunction->dPcup == 0) {
812 MAG_Error(1);
813 return NULL;
814 }
815 return LegendreFunction;
816} /*MAGtype_LegendreFunction*/
817
818MAGtype_MagneticModel *MAG_AllocateModelMemory(int NumTerms)
819
820/* Allocate memory for WMM Coefficients
821 * Should be called before reading the model file *
822
823 INPUT: NumTerms : int : Total number of spherical harmonic coefficients in the
824model
825
826
827 OUTPUT: Pointer to data structure MAGtype_MagneticModel with the following
828elements double EditionDate; double epoch; Base time of Geomagnetic model
829epoch (yrs) char ModelName[20]; double *Main_Field_Coeff_G; C - Gauss
830coefficients of main geomagnetic model (nT) double *Main_Field_Coeff_H; C -
831Gauss coefficients of main geomagnetic model (nT) double *Secular_Var_Coeff_G;
832CD - Gauss coefficients of secular geomagnetic model (nT/yr) double
833*Secular_Var_Coeff_H; CD - Gauss coefficients of secular geomagnetic model
834(nT/yr) int nMax; Maximum degree of spherical harmonic model int nMaxSecVar;
835Maxumum degree of spherical harmonic secular model int SecularVariationUsed;
836Whether or not the magnetic secular variation vector will be needed by program
837
838 FALSE: Failed to allocate memory
839CALLS : none
840 */
841{
842 MAGtype_MagneticModel *MagneticModel;
843 int i;
844
845 MagneticModel =
846 (MAGtype_MagneticModel *)calloc(1, sizeof(MAGtype_MagneticModel));
847
848 if (MagneticModel == NULL) {
849 MAG_Error(2);
850 return NULL;
851 }
852
853 MagneticModel->Main_Field_Coeff_G =
854 (double *)malloc((NumTerms + 1) * sizeof(double));
855
856 if (MagneticModel->Main_Field_Coeff_G == NULL) {
857 MAG_Error(2);
858 return NULL;
859 }
860
861 MagneticModel->Main_Field_Coeff_H =
862 (double *)malloc((NumTerms + 1) * sizeof(double));
863
864 if (MagneticModel->Main_Field_Coeff_H == NULL) {
865 MAG_Error(2);
866 return NULL;
867 }
868 MagneticModel->Secular_Var_Coeff_G =
869 (double *)malloc((NumTerms + 1) * sizeof(double));
870 if (MagneticModel->Secular_Var_Coeff_G == NULL) {
871 MAG_Error(2);
872 return NULL;
873 }
874 MagneticModel->Secular_Var_Coeff_H =
875 (double *)malloc((NumTerms + 1) * sizeof(double));
876 if (MagneticModel->Secular_Var_Coeff_H == NULL) {
877 MAG_Error(2);
878 return NULL;
879 }
880 MagneticModel->CoefficientFileEndDate = 0;
881 MagneticModel->EditionDate = 0;
882 MAG_strlcpy_equivalent(MagneticModel->ModelName, "",
883 sizeof(MagneticModel->ModelName));
884 MagneticModel->SecularVariationUsed = 0;
885 MagneticModel->epoch = 0;
886 MagneticModel->nMax = 0;
887 MagneticModel->nMaxSecVar = 0;
888
889 for (i = 0; i < NumTerms; i++) {
890 MagneticModel->Main_Field_Coeff_G[i] = 0;
891 MagneticModel->Main_Field_Coeff_H[i] = 0;
892 MagneticModel->Secular_Var_Coeff_G[i] = 0;
893 MagneticModel->Secular_Var_Coeff_H[i] = 0;
894 }
895
896 return MagneticModel;
897
898} /*MAG_AllocateModelMemory*/
899
900MAGtype_SphericalHarmonicVariables *MAG_AllocateSphVarMemory(int nMax) {
902 SphVariables = (MAGtype_SphericalHarmonicVariables *)calloc(
904 SphVariables->RelativeRadiusPower =
905 (double *)malloc((nMax + 1) * sizeof(double));
906 SphVariables->cos_mlambda = (double *)malloc((nMax + 1) * sizeof(double));
907 SphVariables->sin_mlambda = (double *)malloc((nMax + 1) * sizeof(double));
908 return SphVariables;
909} /*MAG_AllocateSphVarMemory*/
910
911void MAG_AssignHeaderValues(MAGtype_MagneticModel *model,
912 char values[][MAXLINELENGTH]) {
913 /* MAGtype_Date releasedate; */
914 MAG_strlcpy_equivalent(model->ModelName, values[MODELNAME],
915 sizeof(model->ModelName));
916 /* releasedate.Year = 0;
917 releasedate.Day = 0;
918 releasedate.Month = 0;
919 releasedate.DecimalYear = 0;
920 sscanf(values[RELEASEDATE],"%d-%d-%d",&releasedate.Year,&releasedate.Month,&releasedate.Day);
921 if(MAG_DateToYear (&releasedate, NULL))
922 model->EditionDate = releasedate.DecimalYear;*/
923 model->epoch = atof(values[MODELSTARTYEAR]);
924 model->nMax = atoi(values[INTSTATICDEG]);
925 model->nMaxSecVar = atoi(values[INTSECVARDEG]);
926 model->CoefficientFileEndDate = atof(values[MODELENDYEAR]);
927 if (model->nMaxSecVar > 0)
928 model->SecularVariationUsed = 1;
929 else
930 model->SecularVariationUsed = 0;
931}
932
933void MAG_AssignMagneticModelCoeffs(MAGtype_MagneticModel *Assignee,
934 MAGtype_MagneticModel *Source, int nMax,
935 int nMaxSecVar)
936/* This function assigns the first nMax degrees of the Source model to the
937 Assignee model, leaving the other coefficients untouched*/
938{
939 int n, m, index;
940 assert(nMax <= Source->nMax);
941 assert(nMax <= Assignee->nMax);
942 assert(nMaxSecVar <= Source->nMaxSecVar);
943 assert(nMaxSecVar <= Assignee->nMaxSecVar);
944 for (n = 1; n <= nMaxSecVar; n++) {
945 for (m = 0; m <= n; m++) {
946 index = (n * (n + 1) / 2 + m);
947 Assignee->Main_Field_Coeff_G[index] = Source->Main_Field_Coeff_G[index];
948 Assignee->Main_Field_Coeff_H[index] = Source->Main_Field_Coeff_H[index];
949 Assignee->Secular_Var_Coeff_G[index] = Source->Secular_Var_Coeff_G[index];
950 Assignee->Secular_Var_Coeff_H[index] = Source->Secular_Var_Coeff_H[index];
951 }
952 }
953 for (n = nMaxSecVar + 1; n <= nMax; n++) {
954 for (m = 0; m <= n; m++) {
955 index = (n * (n + 1) / 2 + m);
956 Assignee->Main_Field_Coeff_G[index] = Source->Main_Field_Coeff_G[index];
957 Assignee->Main_Field_Coeff_H[index] = Source->Main_Field_Coeff_H[index];
958 }
959 }
960 return;
961} /*MAG_AssignMagneticModelCoeffs*/
962
963int MAG_FreeMemory(MAGtype_MagneticModel *MagneticModel,
964 MAGtype_MagneticModel *TimedMagneticModel,
965 MAGtype_LegendreFunction *LegendreFunction)
966
967/* Free memory used by WMM functions. Only to be called at the end of the main
968function. INPUT : MagneticModel pointer to data structure with the
969following elements
970
971 double EditionDate;
972 double epoch; Base time of Geomagnetic model epoch
973(yrs) char ModelName[20]; double *Main_Field_Coeff_G; C - Gauss
974coefficients of main geomagnetic model (nT) double *Main_Field_Coeff_H; C -
975Gauss coefficients of main geomagnetic model (nT) double *Secular_Var_Coeff_G;
976CD - Gauss coefficients of secular geomagnetic model (nT/yr) double
977*Secular_Var_Coeff_H; CD - Gauss coefficients of secular geomagnetic model
978(nT/yr) int nMax; Maximum degree of spherical harmonic model int nMaxSecVar;
979Maxumum degree of spherical harmonic secular model int SecularVariationUsed;
980Whether or not the magnetic secular variation vector will be needed by program
981
982 TimedMagneticModel Pointer to data structure similar to the
983first input. LegendreFunction Pointer to data structure with the following
984elements double *Pcup; ( pointer to store Legendre Function ) double *dPcup;
985( pointer to store Derivative of Lagendre function )
986
987OUTPUT none
988CALLS : none
989
990 */
991{
992 if (MagneticModel->Main_Field_Coeff_G) {
993 free(MagneticModel->Main_Field_Coeff_G);
994 MagneticModel->Main_Field_Coeff_G = NULL;
995 }
996 if (MagneticModel->Main_Field_Coeff_H) {
997 free(MagneticModel->Main_Field_Coeff_H);
998 MagneticModel->Main_Field_Coeff_H = NULL;
999 }
1000 if (MagneticModel->Secular_Var_Coeff_G) {
1001 free(MagneticModel->Secular_Var_Coeff_G);
1002 MagneticModel->Secular_Var_Coeff_G = NULL;
1003 }
1004 if (MagneticModel->Secular_Var_Coeff_H) {
1005 free(MagneticModel->Secular_Var_Coeff_H);
1006 MagneticModel->Secular_Var_Coeff_H = NULL;
1007 }
1008 if (MagneticModel) {
1009 free(MagneticModel);
1010 MagneticModel = NULL;
1011 }
1012
1013 if (TimedMagneticModel->Main_Field_Coeff_G) {
1014 free(TimedMagneticModel->Main_Field_Coeff_G);
1015 TimedMagneticModel->Main_Field_Coeff_G = NULL;
1016 }
1017 if (TimedMagneticModel->Main_Field_Coeff_H) {
1018 free(TimedMagneticModel->Main_Field_Coeff_H);
1019 TimedMagneticModel->Main_Field_Coeff_H = NULL;
1020 }
1021 if (TimedMagneticModel->Secular_Var_Coeff_G) {
1022 free(TimedMagneticModel->Secular_Var_Coeff_G);
1023 TimedMagneticModel->Secular_Var_Coeff_G = NULL;
1024 }
1025 if (TimedMagneticModel->Secular_Var_Coeff_H) {
1026 free(TimedMagneticModel->Secular_Var_Coeff_H);
1027 TimedMagneticModel->Secular_Var_Coeff_H = NULL;
1028 }
1029
1030 if (TimedMagneticModel) {
1031 free(TimedMagneticModel);
1032 TimedMagneticModel = NULL;
1033 }
1034
1035 if (LegendreFunction->Pcup) {
1036 free(LegendreFunction->Pcup);
1037 LegendreFunction->Pcup = NULL;
1038 }
1039 if (LegendreFunction->dPcup) {
1040 free(LegendreFunction->dPcup);
1041 LegendreFunction->dPcup = NULL;
1042 }
1043 if (LegendreFunction) {
1044 free(LegendreFunction);
1045 LegendreFunction = NULL;
1046 }
1047
1048 return TRUE;
1049} /*MAG_FreeMemory */
1050
1051int MAG_FreeMagneticModelMemory(MAGtype_MagneticModel *MagneticModel)
1052
1053/* Free the magnetic model memory used by WMM functions.
1054INPUT : MagneticModel pointer to data structure with the following elements
1055
1056 double EditionDate;
1057 double epoch; Base time of Geomagnetic model epoch
1058(yrs) char ModelName[20]; double *Main_Field_Coeff_G; C - Gauss
1059coefficients of main geomagnetic model (nT) double *Main_Field_Coeff_H; C -
1060Gauss coefficients of main geomagnetic model (nT) double *Secular_Var_Coeff_G;
1061CD - Gauss coefficients of secular geomagnetic model (nT/yr) double
1062*Secular_Var_Coeff_H; CD - Gauss coefficients of secular geomagnetic model
1063(nT/yr) int nMax; Maximum degree of spherical harmonic model int nMaxSecVar;
1064Maxumum degree of spherical harmonic secular model int SecularVariationUsed;
1065Whether or not the magnetic secular variation vector will be needed by program
1066
1067OUTPUT none
1068CALLS : none
1069
1070 */
1071{
1072 if (MagneticModel->Main_Field_Coeff_G) {
1073 free(MagneticModel->Main_Field_Coeff_G);
1074 MagneticModel->Main_Field_Coeff_G = NULL;
1075 }
1076 if (MagneticModel->Main_Field_Coeff_H) {
1077 free(MagneticModel->Main_Field_Coeff_H);
1078 MagneticModel->Main_Field_Coeff_H = NULL;
1079 }
1080 if (MagneticModel->Secular_Var_Coeff_G) {
1081 free(MagneticModel->Secular_Var_Coeff_G);
1082 MagneticModel->Secular_Var_Coeff_G = NULL;
1083 }
1084 if (MagneticModel->Secular_Var_Coeff_H) {
1085 free(MagneticModel->Secular_Var_Coeff_H);
1086 MagneticModel->Secular_Var_Coeff_H = NULL;
1087 }
1088 if (MagneticModel) {
1089 free(MagneticModel);
1090 MagneticModel = NULL;
1091 }
1092
1093 return TRUE;
1094} /*MAG_FreeMagneticModelMemory */
1095
1096int MAG_FreeLegendreMemory(MAGtype_LegendreFunction *LegendreFunction)
1097
1098/* Free the Legendre Coefficients memory used by the WMM functions.
1099INPUT : LegendreFunction Pointer to data structure with the following elements
1100 double *Pcup; ( pointer to
1101store Legendre Function ) double *dPcup; ( pointer to store Derivative of
1102Lagendre function )
1103
1104OUTPUT: none
1105CALLS : none
1106
1107 */
1108{
1109 if (LegendreFunction->Pcup) {
1110 free(LegendreFunction->Pcup);
1111 LegendreFunction->Pcup = NULL;
1112 }
1113 if (LegendreFunction->dPcup) {
1114 free(LegendreFunction->dPcup);
1115 LegendreFunction->dPcup = NULL;
1116 }
1117 if (LegendreFunction) {
1118 free(LegendreFunction);
1119 LegendreFunction = NULL;
1120 }
1121
1122 return TRUE;
1123} /*MAG_FreeLegendreMemory */
1124
1125int MAG_FreeSphVarMemory(MAGtype_SphericalHarmonicVariables *SphVar)
1126
1127/* Free the Spherical Harmonic Variable memory used by the WMM functions.
1128INPUT : LegendreFunction Pointer to data structure with the following elements
1129 double *RelativeRadiusPower
1130 double *cos_mlambda
1131 double *sin_mlambda
1132 OUTPUT: none
1133 CALLS : none
1134 */
1135{
1136 if (SphVar->RelativeRadiusPower) {
1137 free(SphVar->RelativeRadiusPower);
1138 SphVar->RelativeRadiusPower = NULL;
1139 }
1140 if (SphVar->cos_mlambda) {
1141 free(SphVar->cos_mlambda);
1142 SphVar->cos_mlambda = NULL;
1143 }
1144 if (SphVar->sin_mlambda) {
1145 free(SphVar->sin_mlambda);
1146 SphVar->sin_mlambda = NULL;
1147 }
1148 if (SphVar) {
1149 free(SphVar);
1150 SphVar = NULL;
1151 }
1152
1153 return TRUE;
1154} /*MAG_FreeSphVarMemory*/
1155
1156void MAG_PrintWMMFormat(char *filename, MAGtype_MagneticModel *MagneticModel) {
1157 int index, n, m;
1158 FILE *OUT;
1159 MAGtype_Date Date;
1160 char Datestring[11];
1161
1162 Date.DecimalYear = MagneticModel->EditionDate;
1163 MAG_YearToDate(&Date);
1164 sprintf(Datestring, "%d/%d/%d", Date.Month, Date.Day, Date.Year);
1165 OUT = fopen(filename, "w");
1166 fprintf(OUT, " %.1f %s %s\n",
1167 MagneticModel->epoch, MagneticModel->ModelName, Datestring);
1168 for (n = 1; n <= MagneticModel->nMax; n++) {
1169 for (m = 0; m <= n; m++) {
1170 index = (n * (n + 1) / 2 + m);
1171 if (m != 0)
1172 fprintf(OUT, " %2d %2d %9.4f %9.4f %9.4f %9.4f\n", n, m,
1173 MagneticModel->Main_Field_Coeff_G[index],
1174 MagneticModel->Main_Field_Coeff_H[index],
1175 MagneticModel->Secular_Var_Coeff_G[index],
1176 MagneticModel->Secular_Var_Coeff_H[index]);
1177 else
1178 fprintf(OUT, " %2d %2d %9.4f %9.4f %9.4f %9.4f\n", n, m,
1179 MagneticModel->Main_Field_Coeff_G[index], 0.0,
1180 MagneticModel->Secular_Var_Coeff_G[index], 0.0);
1181 }
1182 }
1183 fclose(OUT);
1184} /*MAG_PrintWMMFormat*/
1185
1186void MAG_PrintEMMFormat(char *filename, char *filenameSV,
1187 MAGtype_MagneticModel *MagneticModel) {
1188 int index, n, m;
1189 FILE *OUT;
1190 MAGtype_Date Date;
1191 char Datestring[11];
1192
1193 Date.DecimalYear = MagneticModel->EditionDate;
1194 MAG_YearToDate(&Date);
1195 sprintf(Datestring, "%d/%d/%d", Date.Month, Date.Day, Date.Year);
1196 OUT = fopen(filename, "w");
1197 fprintf(OUT, " %.1f %s %s\n",
1198 MagneticModel->epoch, MagneticModel->ModelName, Datestring);
1199 for (n = 1; n <= MagneticModel->nMax; n++) {
1200 for (m = 0; m <= n; m++) {
1201 index = (n * (n + 1) / 2 + m);
1202 if (m != 0)
1203 fprintf(OUT, " %2d %2d %9.4f %9.4f\n", n, m,
1204 MagneticModel->Main_Field_Coeff_G[index],
1205 MagneticModel->Main_Field_Coeff_H[index]);
1206 else
1207 fprintf(OUT, " %2d %2d %9.4f %9.4f\n", n, m,
1208 MagneticModel->Main_Field_Coeff_G[index], 0.0);
1209 }
1210 }
1211 fclose(OUT);
1212 OUT = fopen(filenameSV, "w");
1213 for (n = 1; n <= MagneticModel->nMaxSecVar; n++) {
1214 for (m = 0; m <= n; m++) {
1215 index = (n * (n + 1) / 2 + m);
1216 if (m != 0)
1217 fprintf(OUT, " %2d %2d %9.4f %9.4f\n", n, m,
1218 MagneticModel->Secular_Var_Coeff_G[index],
1219 MagneticModel->Secular_Var_Coeff_H[index]);
1220 else
1221 fprintf(OUT, " %2d %2d %9.4f %9.4f\n", n, m,
1222 MagneticModel->Secular_Var_Coeff_G[index], 0.0);
1223 }
1224 }
1225 fclose(OUT);
1226 return;
1227} /*MAG_PrintEMMFormat*/
1228
1229void MAG_PrintSHDFFormat(char *filename,
1230 MAGtype_MagneticModel *(*MagneticModel)[],
1231 int epochs) {
1232 int i, n, m, index, epochRange;
1233 FILE *SHDF_file;
1234 SHDF_file = fopen(filename, "w");
1235 /*lines = (int)(UFM_DEGREE / 2.0 * (UFM_DEGREE + 3));*/
1236 for (i = 0; i < epochs; i++) {
1237 if (i < epochs - 1)
1238 epochRange = (*MagneticModel)[i + 1]->epoch - (*MagneticModel)[i]->epoch;
1239 else
1240 epochRange = (*MagneticModel)[i]->epoch - (*MagneticModel)[i - 1]->epoch;
1241 fprintf(SHDF_file,
1242 "%%SHDF 16695 Definitive Geomagnetic Reference Field Model "
1243 "Coefficient File\n");
1244 fprintf(SHDF_file, "%%ModelName: %s\n", (*MagneticModel)[i]->ModelName);
1245 fprintf(SHDF_file,
1246 "%%Publisher: International Association of Geomagnetism and "
1247 "Aeronomy (IAGA), Working Group V-Mod\n");
1248 fprintf(SHDF_file, "%%ReleaseDate: Some Number\n");
1249 fprintf(SHDF_file, "%%DataCutOFF: Some Other Number\n");
1250 fprintf(SHDF_file, "%%ModelStartYear: %d\n",
1251 (int)(*MagneticModel)[i]->epoch);
1252 fprintf(SHDF_file, "%%ModelEndYear: %d\n",
1253 (int)(*MagneticModel)[i]->epoch + epochRange);
1254 fprintf(SHDF_file, "%%Epoch: %.0f\n", (*MagneticModel)[i]->epoch);
1255 fprintf(SHDF_file, "%%IntStaticDeg: %d\n", (*MagneticModel)[i]->nMax);
1256 fprintf(SHDF_file, "%%IntSecVarDeg: %d\n", (*MagneticModel)[i]->nMaxSecVar);
1257 fprintf(SHDF_file, "%%ExtStaticDeg: 0\n");
1258 fprintf(SHDF_file, "%%ExtSecVarDeg: 0\n");
1259 fprintf(SHDF_file, "%%Normalization: Schmidt semi-normailized\n");
1260 fprintf(SHDF_file, "%%SpatBasFunc: spherical harmonics\n");
1261 fprintf(SHDF_file, "# To synthesize the field for a given date:\n");
1262 fprintf(SHDF_file,
1263 "# Use the sub-model of the epoch corresponding to each date\n");
1264 fprintf(SHDF_file,
1265 "#\n#\n#\n#\n# I/E, n, m, Gnm, Hnm, SV-Gnm, SV-Hnm\n#\n");
1266 n = 1;
1267 m = 0;
1268 for (n = 1; n <= (*MagneticModel)[i]->nMax; n++) {
1269 for (m = 0; m <= n; m++) {
1270 index = (n * (n + 1)) / 2 + m;
1271 if (i < epochs - 1) {
1272 if (m != 0)
1273 fprintf(SHDF_file, "I,%d,%d,%f,%f,%f,%f\n", n, m,
1274 (*MagneticModel)[i]->Main_Field_Coeff_G[index],
1275 (*MagneticModel)[i]->Main_Field_Coeff_H[index],
1276 (*MagneticModel)[i]->Secular_Var_Coeff_G[index],
1277 (*MagneticModel)[i]->Secular_Var_Coeff_H[index]);
1278 else
1279 fprintf(SHDF_file, "I,%d,%d,%f,,%f,\n", n, m,
1280 (*MagneticModel)[i]->Main_Field_Coeff_G[index],
1281 (*MagneticModel)[i]->Secular_Var_Coeff_G[index]);
1282 } else {
1283 if (m != 0)
1284 fprintf(SHDF_file, "I,%d,%d,%f,%f,%f,%f\n", n, m,
1285 (*MagneticModel)[i]->Main_Field_Coeff_G[index],
1286 (*MagneticModel)[i]->Main_Field_Coeff_H[index],
1287 (*MagneticModel)[i]->Secular_Var_Coeff_G[index],
1288 (*MagneticModel)[i]->Secular_Var_Coeff_H[index]);
1289 else
1290 fprintf(SHDF_file, "I,%d,%d,%f,,%f,\n", n, m,
1291 (*MagneticModel)[i]->Main_Field_Coeff_G[index],
1292 (*MagneticModel)[i]->Secular_Var_Coeff_G[index]);
1293 }
1294 }
1295 }
1296 }
1297} /*MAG_PrintSHDFFormat*/
1298
1299int MAG_readMagneticModel(char *filename,
1300 MAGtype_MagneticModel *MagneticModel) {
1301 /* READ WORLD Magnetic MODEL SPHERICAL HARMONIC COEFFICIENTS (WMM.cof)
1302 INPUT : filename
1303 MagneticModel : Pointer to the data structure with the following
1304 fields required as inputs nMax : Number of static coefficients UPDATES :
1305 MagneticModel : Pointer to the data structure with the following fields
1306 populated char *ModelName; double epoch; Base time of Geomagnetic
1307 model epoch (yrs) double *Main_Field_Coeff_G; C - Gauss
1308 coefficients of main geomagnetic model (nT) double *Main_Field_Coeff_H; C -
1309 Gauss coefficients of main geomagnetic model (nT) double
1310 *Secular_Var_Coeff_G; CD - Gauss coefficients of secular geomagnetic model
1311 (nT/yr) double *Secular_Var_Coeff_H; CD - Gauss coefficients of secular
1312 geomagnetic model (nT/yr) CALLS : none
1313
1314 */
1315
1316 FILE *MAG_COF_File;
1317 char c_str[150],
1318 c_new[5]; /*these strings are used to read a line from coefficient file*/
1319 int i, icomp, m, n, EOF_Flag = 0, index;
1320 double epoch, gnm, hnm, dgnm, dhnm;
1321 MAG_COF_File = fopen(filename, "r");
1322 int date_size = 20;
1323 char *edit_date = (char *)malloc(sizeof(char) * date_size);
1324 if (MAG_COF_File == NULL) {
1325 MAG_Error(20);
1326 return FALSE;
1327 /* should we have a standard error printing routine ?*/
1328 }
1329 MagneticModel->Main_Field_Coeff_H[0] = 0.0;
1330 MagneticModel->Main_Field_Coeff_G[0] = 0.0;
1331 MagneticModel->Secular_Var_Coeff_H[0] = 0.0;
1332 MagneticModel->Secular_Var_Coeff_G[0] = 0.0;
1333
1334 char header_fmt[sizeof(c_str)];
1335 snprintf(header_fmt, sizeof(header_fmt), "%%lf %%%lus %%%ds",
1336 (unsigned long)sizeof(MagneticModel->ModelName), date_size);
1337
1338 fgets(c_str, sizeof(c_str), MAG_COF_File);
1339 sscanf(c_str, header_fmt, &epoch, MagneticModel->ModelName, edit_date);
1340
1341 MagneticModel->min_year = MAG_dtstr_to_dyear(edit_date);
1342 if (MagneticModel->min_year == -1) {
1343 MagneticModel->min_year = epoch;
1344 }
1345
1346 MagneticModel->epoch = epoch;
1347 while (EOF_Flag == 0) {
1348 if (NULL == fgets(c_str, sizeof(c_str), MAG_COF_File)) {
1349 break;
1350 }
1351 /* CHECK FOR LAST LINE IN FILE */
1352 for (i = 0; i < 4 && (c_str[i] != '\0'); i++) {
1353 c_new[i] = c_str[i];
1354 c_new[i + 1] = '\0';
1355 }
1356 icomp = strcmp("9999", c_new);
1357 if (icomp == 0) {
1358 EOF_Flag = 1;
1359 break;
1360 }
1361 /* END OF FILE NOT ENCOUNTERED, GET VALUES */
1362 sscanf(c_str, "%d%d%lf%lf%lf%lf", &n, &m, &gnm, &hnm, &dgnm, &dhnm);
1363 if (m <= n) {
1364 index = (n * (n + 1) / 2 + m);
1365 MagneticModel->Main_Field_Coeff_G[index] = gnm;
1366 MagneticModel->Secular_Var_Coeff_G[index] = dgnm;
1367 MagneticModel->Main_Field_Coeff_H[index] = hnm;
1368 MagneticModel->Secular_Var_Coeff_H[index] = dhnm;
1369 }
1370 }
1371
1372 free(edit_date);
1373 fclose(MAG_COF_File);
1374 return TRUE;
1375} /*MAG_readMagneticModel*/
1376
1377int MAG_readMagneticModel_SHDF(char *filename,
1378 MAGtype_MagneticModel *(*magneticmodels)[],
1379 int array_size)
1380/*
1381 * MAG_readMagneticModels - Read the Magnetic Models from an SHDF format file
1382 *
1383 * Input:
1384 * filename - Path to the SHDF format model file to be read
1385 * array_size - Max No of models to be read from the file
1386 *
1387 * Output:
1388 * magneticmodels[] - Array of magnetic models read from the file
1389 *
1390 * Return value:
1391 * Returns the number of models read from the file.
1392 * -2 implies that internal or external static degree was not found in the
1393 * file, hence memory cannot be allocated -1 implies some error during file
1394 * processing (I/O) 0 implies no models were read from the file if ReturnValue >
1395 * array_size then there were too many models in model file but only
1396 * <array_size> number were read . if ReturnValue <= array_size then the
1397 * function execution was successful.
1398 */
1399{
1400 char paramkeys[NOOFPARAMS][MAXLINELENGTH] = {
1401 "SHDF ", "ModelName: ", "Publisher: ", "ReleaseDate: ",
1402 "DataCutOff: ", "ModelStartYear: ", "ModelEndYear: ", "Epoch: ",
1403 "IntStaticDeg: ", "IntSecVarDeg: ", "ExtStaticDeg: ", "ExtSecVarDeg: ",
1404 "GeoMagRefRad: ", "Normalization: ", "SpatBasFunc: "};
1405
1406 char paramvalues[NOOFPARAMS][MAXLINELENGTH];
1407 char *line = (char *)malloc(MAXLINELENGTH);
1408 char *ptrreset;
1409 char paramvalue[MAXLINELENGTH];
1410 int paramvaluelength = 0;
1411 int paramkeylength = 0;
1412 int i = 0, j = 0;
1413 int newrecord = 1;
1414 int header_index = -1;
1415 int numterms;
1416 int tempint;
1417 int allocationflag = 0;
1418 char coefftype; /* Internal or External (I/E) */
1419
1420 /* For reading coefficients */
1421 int n, m;
1422 double gnm, hnm, dgnm, dhnm, cutoff;
1423 int index;
1424
1425 FILE *stream;
1426 ptrreset = line;
1427 stream = fopen(filename, READONLYMODE);
1428 if (stream == NULL) {
1429 perror("File open error");
1430 return header_index;
1431 }
1432
1433 /* Read records from the model file and store header information. */
1434 while (fgets(line, MAXLINELENGTH, stream) != NULL) {
1435 j++;
1436 if (strlen(MAG_Trim(line)) == 0) continue;
1437 if (*line == '%') {
1438 line++;
1439 if (newrecord) {
1440 if (header_index > -1) {
1441 MAG_AssignHeaderValues((*magneticmodels)[header_index], paramvalues);
1442 }
1443 header_index++;
1444 if (header_index >= array_size) {
1445 fprintf(
1446 stderr,
1447 "Header limit exceeded - too many models in model file. (%d)\n",
1448 header_index);
1449 return array_size + 1;
1450 }
1451 newrecord = 0;
1452 allocationflag = 0;
1453 }
1454 for (i = 0; i < NOOFPARAMS; i++) {
1455 paramkeylength = strlen(paramkeys[i]);
1456 if (!strncmp(line, paramkeys[i], paramkeylength)) {
1457 paramvaluelength = strlen(line) - paramkeylength;
1458 memset(paramvalues, '\0', paramvaluelength);
1459 MAG_strlcpy_equivalent(paramvalue, line + paramkeylength,
1460 paramvaluelength);
1461 paramvalue[paramvaluelength] = '\0';
1462 MAG_strlcpy_equivalent(paramvalues[i], paramvalue, 1);
1463 if (!strcmp(paramkeys[i], paramkeys[INTSTATICDEG]) ||
1464 !strcmp(paramkeys[i], paramkeys[EXTSTATICDEG])) {
1465 tempint = atoi(paramvalues[i]);
1466 if (tempint > 0 && allocationflag == 0) {
1467 numterms = CALCULATE_NUMTERMS(tempint);
1468 (*magneticmodels)[header_index] =
1469 MAG_AllocateModelMemory(numterms);
1470 /* model = (*magneticmodels)[header_index]; */
1471 allocationflag = 1;
1472 }
1473 }
1474 break;
1475 }
1476 }
1477 line--;
1478 } else if (*line == '#') {
1479 /* process comments */
1480
1481 } else if (sscanf(line, "%c,%d,%d", &coefftype, &n, &m) == 3) {
1482 if (m == 0) {
1483 sscanf(line, "%c,%d,%d,%lf,,%lf,", &coefftype, &n, &m, &gnm, &dgnm);
1484 hnm = 0;
1485 dhnm = 0;
1486 } else
1487 sscanf(line, "%c,%d,%d,%lf,%lf,%lf,%lf", &coefftype, &n, &m, &gnm, &hnm,
1488 &dgnm, &dhnm);
1489 newrecord = 1;
1490 if (!allocationflag) {
1491 fprintf(stderr,
1492 "Degree not found in model. Memory cannot be allocated.\n");
1493 return _DEGREE_NOT_FOUND;
1494 }
1495 if (m <= n) {
1496 index = (n * (n + 1) / 2 + m);
1497 (*magneticmodels)[header_index]->Main_Field_Coeff_G[index] = gnm;
1498 (*magneticmodels)[header_index]->Secular_Var_Coeff_G[index] = dgnm;
1499 (*magneticmodels)[header_index]->Main_Field_Coeff_H[index] = hnm;
1500 (*magneticmodels)[header_index]->Secular_Var_Coeff_H[index] = dhnm;
1501 }
1502 }
1503 }
1504 if (header_index > -1)
1505 MAG_AssignHeaderValues((*magneticmodels)[header_index], paramvalues);
1506 fclose(stream);
1507
1508 cutoff = (*magneticmodels)[array_size - 1]->CoefficientFileEndDate;
1509
1510 for (i = 0; i < array_size; i++)
1511 (*magneticmodels)[i]->CoefficientFileEndDate = cutoff;
1512
1513 free(ptrreset);
1514 line = NULL;
1515 ptrreset = NULL;
1516 return header_index + 1;
1517} /*MAG_readMagneticModel_SHDF*/
1518
1519char *MAG_Trim(char *str) {
1520 char *end;
1521
1522 while (isspace(*str)) str++;
1523
1524 if (*str == 0) return str;
1525
1526 end = str + strlen(str) - 1;
1527 while (end > str && isspace(*end)) end--;
1528
1529 *(end + 1) = 0;
1530
1531 return str;
1532}
1533
1534/*End of Memory and File Processing functions*/
1535
1536/******************************************************************************
1537 *************Conversions, Transformations, and other Calculations**************
1538 * This grouping consists of functions that perform unit conversions, coordinate
1539 * transformations and other simple or straightforward calculations that are
1540 * usually easily replicable with a typical scientific calculator.
1541 ******************************************************************************/
1542
1543void MAG_BaseErrors(double DeclCoef, double DeclBaseline, double InclOffset,
1544 double FOffset, double Multiplier, double H,
1545 double *DeclErr, double *InclErr, double *FErr) {
1546 double declHorizontalAdjustmentSq;
1547 declHorizontalAdjustmentSq = (DeclCoef / H) * (DeclCoef / H);
1548 *DeclErr = sqrt(declHorizontalAdjustmentSq + DeclBaseline * DeclBaseline) *
1549 Multiplier;
1550 *InclErr = InclOffset * Multiplier;
1551 *FErr = FOffset * Multiplier;
1552}
1553
1554int MAG_CalculateGeoMagneticElements(
1555 MAGtype_MagneticResults *MagneticResultsGeo,
1556 MAGtype_GeoMagneticElements *GeoMagneticElements)
1557
1558/* Calculate all the Geomagnetic elements from X,Y and Z components
1559INPUT MagneticResultsGeo Pointer to data structure with the following
1560elements double Bx; ( North ) double By; ( East ) double Bz; ( Down
1561) OUTPUT GeoMagneticElements Pointer to data structure with the following
1562elements double Decl; (Angle between the magnetic field vector and true north,
1563positive east) double Incl; Angle between the magnetic field vector and the
1564horizontal plane, positive down double F; Magnetic Field Strength double H;
1565Horizontal Magnetic Field Strength double X; Northern component of the magnetic
1566field vector double Y; Eastern component of the magnetic field vector double Z;
1567Downward component of the magnetic field vector CALLS : none
1568 */
1569{
1570 GeoMagneticElements->X = MagneticResultsGeo->Bx;
1571 GeoMagneticElements->Y = MagneticResultsGeo->By;
1572 GeoMagneticElements->Z = MagneticResultsGeo->Bz;
1573
1574 GeoMagneticElements->H =
1575 sqrt(MagneticResultsGeo->Bx * MagneticResultsGeo->Bx +
1576 MagneticResultsGeo->By * MagneticResultsGeo->By);
1577 GeoMagneticElements->F =
1578 sqrt(GeoMagneticElements->H * GeoMagneticElements->H +
1579 MagneticResultsGeo->Bz * MagneticResultsGeo->Bz);
1580 GeoMagneticElements->Decl =
1581 RAD2DEG(atan2(GeoMagneticElements->Y, GeoMagneticElements->X));
1582 GeoMagneticElements->Incl =
1583 RAD2DEG(atan2(GeoMagneticElements->Z, GeoMagneticElements->H));
1584
1585 return TRUE;
1586} /*MAG_CalculateGeoMagneticElements */
1587
1588int MAG_CalculateGridVariation(MAGtype_CoordGeodetic location,
1590
1591/*Computes the grid variation for |latitudes| > MAG_MAX_LAT_DEGREE
1592
1593Grivation (or grid variation) is the angle between grid north and
1594magnetic north. This routine calculates Grivation for the Polar Stereographic
1595projection for polar locations (Latitude => |55| deg). Otherwise, it computes
1596the grid variation in UTM projection system. However, the UTM projection codes
1597may be used to compute the grid variation at any latitudes.
1598
1599INPUT location Data structure with the following elements
1600 double lambda; (longitude)
1601 double phi; ( geodetic latitude)
1602 double HeightAboveEllipsoid; (height above the ellipsoid (HaE) )
1603 double HeightAboveGeoid;(height above the Geoid )
1604OUTPUT elements Data structure with the following elements updated
1605 double GV; ( The Grid Variation )
1606CALLS : MAG_GetTransverseMercator
1607
1608 */
1609{
1610 MAGtype_UTMParameters UTMParameters;
1611 if (location.phi >= MAG_PS_MAX_LAT_DEGREE) {
1612 elements->GV = elements->Decl - location.lambda;
1613 return 1;
1614 } else if (location.phi <= MAG_PS_MIN_LAT_DEGREE) {
1615 elements->GV = elements->Decl + location.lambda;
1616 return 1;
1617 } else {
1618 MAG_GetTransverseMercator(location, &UTMParameters);
1619 elements->GV = elements->Decl - UTMParameters.ConvergenceOfMeridians;
1620 }
1621 return 0;
1622} /*MAG_CalculateGridVariation*/
1623
1624void MAG_CalculateGradientElements(MAGtype_MagneticResults GradResults,
1625 MAGtype_GeoMagneticElements MagneticElements,
1626 MAGtype_GeoMagneticElements *GradElements) {
1627 GradElements->X = GradResults.Bx;
1628 GradElements->Y = GradResults.By;
1629 GradElements->Z = GradResults.Bz;
1630
1631 GradElements->H = (GradElements->X * MagneticElements.X +
1632 GradElements->Y * MagneticElements.Y) /
1633 MagneticElements.H;
1634 GradElements->F = (GradElements->X * MagneticElements.X +
1635 GradElements->Y * MagneticElements.Y +
1636 GradElements->Z * MagneticElements.Z) /
1637 MagneticElements.F;
1638 GradElements->Decl = 180.0 / M_PI *
1639 (MagneticElements.X * GradElements->Y -
1640 MagneticElements.Y * GradElements->X) /
1641 (MagneticElements.H * MagneticElements.H);
1642 GradElements->Incl = 180.0 / M_PI *
1643 (MagneticElements.H * GradElements->Z -
1644 MagneticElements.Z * GradElements->H) /
1645 (MagneticElements.F * MagneticElements.F);
1646 GradElements->GV = GradElements->Decl;
1647}
1648
1649int MAG_CalculateSecularVariationElements(
1650 MAGtype_MagneticResults MagneticVariation,
1651 MAGtype_GeoMagneticElements *MagneticElements)
1652/*This takes the Magnetic Variation in x, y, and z and uses it to calculate the
1653 secular variation of each of the Geomagnetic elements. INPUT
1654 MagneticVariation Data structure with the following elements double Bx; (
1655 North ) double By; ( East ) double Bz; ( Down ) OUTPUT
1656 MagneticElements Pointer to the data structure with the following elements
1657 updated double Decldot; Yearly Rate of change in declination double Incldot;
1658 Yearly Rate of change in inclination double Fdot; Yearly rate of change in
1659 Magnetic field strength double Hdot; Yearly rate of change in horizontal
1660 field strength double Xdot; Yearly rate of change in the northern component
1661 double Ydot; Yearly rate of change in the eastern
1662 component double Zdot; Yearly rate of change in the downward component double
1663 GVdot;Yearly rate of chnage in grid variation CALLS : none
1664
1665 */
1666{
1667 MagneticElements->Xdot = MagneticVariation.Bx;
1668 MagneticElements->Ydot = MagneticVariation.By;
1669 MagneticElements->Zdot = MagneticVariation.Bz;
1670 MagneticElements->Hdot =
1671 (MagneticElements->X * MagneticElements->Xdot +
1672 MagneticElements->Y * MagneticElements->Ydot) /
1673 MagneticElements->H; /* See equation 19 in the WMM technical report */
1674 MagneticElements->Fdot = (MagneticElements->X * MagneticElements->Xdot +
1675 MagneticElements->Y * MagneticElements->Ydot +
1676 MagneticElements->Z * MagneticElements->Zdot) /
1677 MagneticElements->F;
1678 MagneticElements->Decldot = 180.0 / M_PI *
1679 (MagneticElements->X * MagneticElements->Ydot -
1680 MagneticElements->Y * MagneticElements->Xdot) /
1681 (MagneticElements->H * MagneticElements->H);
1682 MagneticElements->Incldot = 180.0 / M_PI *
1683 (MagneticElements->H * MagneticElements->Zdot -
1684 MagneticElements->Z * MagneticElements->Hdot) /
1685 (MagneticElements->F * MagneticElements->F);
1686 MagneticElements->GVdot = MagneticElements->Decldot;
1687 return TRUE;
1688} /*MAG_CalculateSecularVariationElements*/
1689
1690void MAG_CartesianToGeodetic(MAGtype_Ellipsoid Ellip, double x, double y,
1691 double z, MAGtype_CoordGeodetic *CoordGeodetic) {
1692 /*This converts the Cartesian x, y, and z coordinates to Geodetic Coordinates
1693 x is defined as the direction pointing out of the core toward the point
1694 defined
1695 * by 0 degrees latitude and longitude.
1696 y is defined as the direction from the core toward 90 degrees east longitude
1697 along
1698 * the equator
1699 z is defined as the direction from the core out the geographic north pole*/
1700
1701 double modified_b, r, e, f, p, q, d, v, g, t, zlong, rlat;
1702
1703 /*
1704 * 1.0 compute semi-minor axis and set sign to that of z in order
1705 * to get sign of Phi correct
1706 */
1707
1708 if (z < 0.0)
1709 modified_b = -Ellip.b;
1710 else
1711 modified_b = Ellip.b;
1712
1713 /*
1714 * 2.0 compute intermediate values for latitude
1715 */
1716 r = sqrt(x * x + y * y);
1717 e = (modified_b * z - (Ellip.a * Ellip.a - modified_b * modified_b)) /
1718 (Ellip.a * r);
1719 f = (modified_b * z + (Ellip.a * Ellip.a - modified_b * modified_b)) /
1720 (Ellip.a * r);
1721 /*
1722 * 3.0 find solution to:
1723 * t^4 + 2*E*t^3 + 2*F*t - 1 = 0
1724 */
1725 p = (4.0 / 3.0) * (e * f + 1.0);
1726 q = 2.0 * (e * e - f * f);
1727 d = p * p * p + q * q;
1728
1729 if (d >= 0.0) {
1730 v = pow((sqrt(d) - q), (1.0 / 3.0)) - pow((sqrt(d) + q), (1.0 / 3.0));
1731 } else {
1732 v = 2.0 * sqrt(-p) * cos(acos(q / (p * sqrt(-p))) / 3.0);
1733 }
1734 /*
1735 * 4.0 improve v
1736 * NOTE: not really necessary unless point is near pole
1737 */
1738 if (v * v < fabs(p)) {
1739 v = -(v * v * v + 2.0 * q) / (3.0 * p);
1740 }
1741 g = (sqrt(e * e + v) + e) / 2.0;
1742 t = sqrt(g * g + (f - v * g) / (2.0 * g - e)) - g;
1743
1744 rlat = atan((Ellip.a * (1.0 - t * t)) / (2.0 * modified_b * t));
1745 CoordGeodetic->phi = RAD2DEG(rlat);
1746
1747 /*
1748 * 5.0 compute height above ellipsoid
1749 */
1750 CoordGeodetic->HeightAboveEllipsoid =
1751 (r - Ellip.a * t) * cos(rlat) + (z - modified_b) * sin(rlat);
1752 /*
1753 * 6.0 compute longitude east of Greenwich
1754 */
1755 zlong = atan2(y, x);
1756 if (zlong < 0.0) zlong = zlong + 2 * M_PI;
1757
1758 CoordGeodetic->lambda = RAD2DEG(zlong);
1759 while (CoordGeodetic->lambda > 180) {
1760 CoordGeodetic->lambda -= 360;
1761 }
1762}
1763
1764MAGtype_CoordGeodetic MAG_CoordGeodeticAssign(
1765 MAGtype_CoordGeodetic CoordGeodetic) {
1766 MAGtype_CoordGeodetic Assignee;
1767 Assignee.phi = CoordGeodetic.phi;
1768 Assignee.lambda = CoordGeodetic.lambda;
1769 Assignee.HeightAboveEllipsoid = CoordGeodetic.HeightAboveEllipsoid;
1770 Assignee.HeightAboveGeoid = CoordGeodetic.HeightAboveGeoid;
1771 Assignee.UseGeoid = CoordGeodetic.UseGeoid;
1772 return Assignee;
1773}
1774
1775int MAG_DateToYear(MAGtype_Date *CalendarDate, char *Error)
1776
1777/* Converts a given calendar date into a decimal year,
1778it also outputs an error string if there is a problem
1779INPUT CalendarDate Pointer to the data structure with the following elements
1780 int Year;
1781 int Month;
1782 int Day;
1783 double DecimalYear; decimal years
1784OUTPUT CalendarDate Pointer to the data structure with the following
1785elements updated double DecimalYear; decimal years Error pointer to an
1786error string CALLS : none
1787
1788 */
1789{
1790 int temp = 0; /*Total number of days */
1791 int MonthDays[13];
1792 int ExtraDay = 0;
1793 int i;
1794 int Error_size = 255;
1795 if (CalendarDate->Month == 0) {
1796 CalendarDate->DecimalYear = CalendarDate->Year;
1797 return TRUE;
1798 }
1799 if ((CalendarDate->Year % 4 == 0 && CalendarDate->Year % 100 != 0) ||
1800 CalendarDate->Year % 400 == 0)
1801 ExtraDay = 1;
1802 MonthDays[0] = 0;
1803 MonthDays[1] = 31;
1804 MonthDays[2] = 28 + ExtraDay;
1805 MonthDays[3] = 31;
1806 MonthDays[4] = 30;
1807 MonthDays[5] = 31;
1808 MonthDays[6] = 30;
1809 MonthDays[7] = 31;
1810 MonthDays[8] = 31;
1811 MonthDays[9] = 30;
1812 MonthDays[10] = 31;
1813 MonthDays[11] = 30;
1814 MonthDays[12] = 31;
1815
1816 /******************Validation********************************/
1817 if (CalendarDate->Month <= 0 || CalendarDate->Month > 12) {
1818 // The Error is passed as pointer and its size if defined outside of the
1819 // function. The functions used to pass Error to MAG_DateToYear() defines
1820 // the size of DMSstring are all 255.
1821 MAG_strlcpy_equivalent(
1822 Error,
1823 "\nError: The Month entered is invalid, valid months are '1 to 12'\n",
1824 Error_size);
1825 return 0;
1826 }
1827 if (CalendarDate->Day <= 0 ||
1828 CalendarDate->Day > MonthDays[CalendarDate->Month]) {
1829 printf("\nThe number of days in month %d is %d\n", CalendarDate->Month,
1830 MonthDays[CalendarDate->Month]);
1831 MAG_strlcpy_equivalent(Error, "\nError: The day entered is invalid\n",
1832 Error_size);
1833 return 0;
1834 }
1835 /****************Calculation of t***************************/
1836 for (i = 1; i <= CalendarDate->Month; i++) temp += MonthDays[i - 1];
1837 temp += CalendarDate->Day;
1838 CalendarDate->DecimalYear =
1839 CalendarDate->Year + (temp - 1) / (365.0 + ExtraDay);
1840 return TRUE;
1841
1842} /*MAG_DateToYear*/
1843
1844void MAG_DegreeToDMSstring(double DegreesOfArc, int UnitDepth, char *DMSstring)
1845
1846/*This converts a given decimal degree into a DMS string.
1847INPUT DegreesOfArc decimal degree
1848 UnitDepth How many iterations should be printed,
1849 1 = Degrees
1850 2 = Degrees, Minutes
1851 3 = Degrees, Minutes, Seconds
1852OUPUT DMSstring pointer to DMSString. Must be at least 30 characters.
1853CALLS : none
1854 */
1855{
1856 int DMS[3], i;
1857 double temp = DegreesOfArc;
1858 int tmp_size = 36;
1859 int tmp2_size = 32;
1860 char *tempstring = malloc(sizeof(char) * tmp_size);
1861 char *tempstring2 = malloc(sizeof(char) * tmp2_size);
1862 int DMSstring_size = 100;
1863
1864 memset(tempstring, '\0', tmp_size);
1865 memset(tempstring2, '\0', tmp2_size);
1866
1867 // The DMSstring is passed as pointer and its size if defined outside of the
1868 // function. The functions used to pass DMSstring to MAG_DegreeToDMSstring()
1869 // define the size of DMSstring are all 100.
1870 // MAG_strlcpy_equivalent(DMSstring, "", DMSstring_size);
1871 if (UnitDepth > 3) MAG_Error(21);
1872 for (i = 0; i < UnitDepth; i++) {
1873 DMS[i] = (int)temp;
1874 switch (i) {
1875 case 0:
1876 MAG_strlcpy_equivalent(tempstring2, "Deg", tmp2_size);
1877 break;
1878 case 1:
1879 MAG_strlcpy_equivalent(tempstring2, "Min", tmp2_size);
1880 break;
1881 case 2:
1882 MAG_strlcpy_equivalent(tempstring2, "Sec", tmp2_size);
1883 break;
1884 }
1885 temp = (temp - DMS[i]) * 60;
1886 if (i == UnitDepth - 1 && temp >= 30)
1887 DMS[i]++;
1888 else if (i == UnitDepth - 1 && temp <= -30)
1889 DMS[i]--;
1890 snprintf(tempstring, tmp_size, "%4d%4s", DMS[i], tempstring2);
1891 strcat(DMSstring, tempstring);
1892 }
1893
1894 free(tempstring);
1895 free(tempstring2);
1896
1897} /*MAG_DegreeToDMSstring*/
1898
1899void MAG_DMSstringToDegree(char *DMSstring, double *DegreesOfArc)
1900
1901/*This converts a given DMS string into decimal degrees.
1902INPUT DMSstring pointer to DMSString
1903OUTPUT DegreesOfArc decimal degree
1904CALLS : none
1905 */
1906{
1907 int second, minute, degree, sign = 1, j = 0;
1908 j = sscanf(DMSstring, "%d, %d, %d", &degree, &minute, &second);
1909 if (j != 3) sscanf(DMSstring, "%d %d %d", &degree, &minute, &second);
1910 if (degree < 0) sign = -1;
1911 degree = degree * sign;
1912 *DegreesOfArc = sign * (degree + minute / 60.0 + second / 3600.0);
1913} /*MAG_DMSstringToDegree*/
1914
1915void MAG_ErrorCalc(MAGtype_GeoMagneticElements B,
1917 /*Errors.Decl, Errors.Incl, Errors.F are all assumed to exist*/
1918 double cos2D, cos2I, sin2D, sin2I, EDSq, EISq, eD, eI;
1919 cos2D = cos(DEG2RAD(B.Decl)) * cos(DEG2RAD(B.Decl));
1920 cos2I = cos(DEG2RAD(B.Incl)) * cos(DEG2RAD(B.Incl));
1921 sin2D = sin(DEG2RAD(B.Decl)) * sin(DEG2RAD(B.Decl));
1922 sin2I = sin(DEG2RAD(B.Incl)) * sin(DEG2RAD(B.Incl));
1923 eD = DEG2RAD(Errors->Decl);
1924 eI = DEG2RAD(Errors->Incl);
1925 EDSq = eD * eD;
1926 EISq = eI * eI;
1927 Errors->X =
1928 sqrt(cos2D * cos2I * Errors->F * Errors->F +
1929 B.F * B.F * sin2D * cos2I * EDSq + B.F * B.F * cos2D * sin2I * EISq);
1930 Errors->Y =
1931 sqrt(sin2D * cos2I * Errors->F * Errors->F +
1932 B.F * B.F * cos2D * cos2I * EDSq + B.F * B.F * sin2D * sin2I * EISq);
1933 Errors->Z = sqrt(sin2I * Errors->F * Errors->F + B.F * B.F * cos2I * EISq);
1934 Errors->H = sqrt(cos2I * Errors->F * Errors->F + B.F * B.F * sin2I * EISq);
1935}
1936
1937int MAG_GeodeticToSpherical(MAGtype_Ellipsoid Ellip,
1938 MAGtype_CoordGeodetic CoordGeodetic,
1939 MAGtype_CoordSpherical *CoordSpherical)
1940
1941/* Converts Geodetic coordinates to Spherical coordinates
1942
1943 INPUT Ellip data structure with the following elements
1944 double a; semi-major axis of the ellipsoid
1945 double b; semi-minor axis of the ellipsoid
1946 double fla; flattening
1947 double epssq; first eccentricity squared
1948 double eps; first eccentricity
1949 double re; mean radius of ellipsoid
1950
1951 CoordGeodetic Pointer to the data structure with the
1952following elements updates double lambda; ( longitude ) double phi; ( geodetic
1953latitude ) double HeightAboveEllipsoid; ( height above the WGS84 ellipsoid (HaE)
1954) double HeightAboveGeoid; (height above the EGM96 Geoid model )
1955
1956 OUTPUT CoordSpherical Pointer to the data structure with the following
1957elements double lambda; ( longitude) double phig; ( geocentric latitude ) double
1958r; ( distance from the center of the ellipsoid)
1959
1960CALLS : none
1961
1962 */
1963{
1964 double CosLat, SinLat, rc, xp, zp; /*all local variables */
1965
1966 /*
1967 ** Convert geodetic coordinates, (defined by the WGS-84
1968 ** reference ellipsoid), to Earth Centered Earth Fixed Cartesian
1969 ** coordinates, and then to spherical coordinates.
1970 */
1971
1972 CosLat = cos(DEG2RAD(CoordGeodetic.phi));
1973 SinLat = sin(DEG2RAD(CoordGeodetic.phi));
1974
1975 /* compute the local radius of curvature on the WGS-84 reference ellipsoid */
1976
1977 rc = Ellip.a / sqrt(1.0 - Ellip.epssq * SinLat * SinLat);
1978
1979 /* compute ECEF Cartesian coordinates of specified point (for longitude=0) */
1980
1981 xp = (rc + CoordGeodetic.HeightAboveEllipsoid) * CosLat;
1982 zp = (rc * (1.0 - Ellip.epssq) + CoordGeodetic.HeightAboveEllipsoid) * SinLat;
1983
1984 /* compute spherical radius and angle lambda and phi of specified point */
1985
1986 CoordSpherical->r = sqrt(xp * xp + zp * zp);
1987 CoordSpherical->phig =
1988 RAD2DEG(asin(zp / CoordSpherical->r)); /* geocentric latitude */
1989 CoordSpherical->lambda = CoordGeodetic.lambda; /* longitude */
1990
1991 return TRUE;
1992} /*MAG_GeodeticToSpherical*/
1993
1994MAGtype_GeoMagneticElements MAG_GeoMagneticElementsAssign(
1995 MAGtype_GeoMagneticElements Elements) {
1997 Assignee.X = Elements.X;
1998 Assignee.Y = Elements.Y;
1999 Assignee.Z = Elements.Z;
2000 Assignee.H = Elements.H;
2001 Assignee.F = Elements.F;
2002 Assignee.Decl = Elements.Decl;
2003 Assignee.Incl = Elements.Incl;
2004 Assignee.GV = Elements.GV;
2005 Assignee.Xdot = Elements.Xdot;
2006 Assignee.Ydot = Elements.Ydot;
2007 Assignee.Zdot = Elements.Zdot;
2008 Assignee.Hdot = Elements.Hdot;
2009 Assignee.Fdot = Elements.Fdot;
2010 Assignee.Decldot = Elements.Decldot;
2011 Assignee.Incldot = Elements.Incldot;
2012 Assignee.GVdot = Elements.GVdot;
2013 return Assignee;
2014}
2015
2016MAGtype_GeoMagneticElements MAG_GeoMagneticElementsScale(
2017 MAGtype_GeoMagneticElements Elements, double factor) {
2018 /*This function scales all the geomagnetic elements to scale a vector use
2019 MAG_MagneticResultsScale*/
2021 product.X = Elements.X * factor;
2022 product.Y = Elements.Y * factor;
2023 product.Z = Elements.Z * factor;
2024 product.H = Elements.H * factor;
2025 product.F = Elements.F * factor;
2026 product.Incl = Elements.Incl * factor;
2027 product.Decl = Elements.Decl * factor;
2028 product.GV = Elements.GV * factor;
2029 product.Xdot = Elements.Xdot * factor;
2030 product.Ydot = Elements.Ydot * factor;
2031 product.Zdot = Elements.Zdot * factor;
2032 product.Hdot = Elements.Hdot * factor;
2033 product.Fdot = Elements.Fdot * factor;
2034 product.Incldot = Elements.Incldot * factor;
2035 product.Decldot = Elements.Decldot * factor;
2036 product.GVdot = Elements.GVdot * factor;
2037 return product;
2038}
2039
2040MAGtype_GeoMagneticElements MAG_GeoMagneticElementsSubtract(
2042 MAGtype_GeoMagneticElements subtrahend) {
2043 /*This algorithm does not result in the difference of F being derived from
2044 the Pythagorean theorem. This function should be used for computing
2045 residuals or changes in elements.*/
2046 MAGtype_GeoMagneticElements difference;
2047 difference.X = minuend.X - subtrahend.X;
2048 difference.Y = minuend.Y - subtrahend.Y;
2049 difference.Z = minuend.Z - subtrahend.Z;
2050
2051 difference.H = minuend.H - subtrahend.H;
2052 difference.F = minuend.F - subtrahend.F;
2053 difference.Decl = minuend.Decl - subtrahend.Decl;
2054 difference.Incl = minuend.Incl - subtrahend.Incl;
2055
2056 difference.Xdot = minuend.Xdot - subtrahend.Xdot;
2057 difference.Ydot = minuend.Ydot - subtrahend.Ydot;
2058 difference.Zdot = minuend.Zdot - subtrahend.Zdot;
2059
2060 difference.Hdot = minuend.Hdot - subtrahend.Hdot;
2061 difference.Fdot = minuend.Fdot - subtrahend.Fdot;
2062 difference.Decldot = minuend.Decldot - subtrahend.Decldot;
2063 difference.Incldot = minuend.Incldot - subtrahend.Incldot;
2064
2065 difference.GV = minuend.GV - subtrahend.GV;
2066 difference.GVdot = minuend.GVdot - subtrahend.GVdot;
2067
2068 return difference;
2069}
2070
2071int MAG_GetTransverseMercator(MAGtype_CoordGeodetic CoordGeodetic,
2072 MAGtype_UTMParameters *UTMParameters)
2073/* Gets the UTM Parameters for a given Latitude and Longitude.
2074
2075INPUT: CoordGeodetic : Data structure MAGtype_CoordGeodetic.
2076OUTPUT : UTMParameters : Pointer to data structure MAGtype_UTMParameters with
2077the following elements double Easting; (X) in meters double Northing; (Y) in
2078meters int Zone; UTM Zone char HemiSphere ; double CentralMeridian; Longitude of
2079the Central Meridian of the UTM Zone double ConvergenceOfMeridians; Convergence
2080of Meridians double PointScale;
2081 */
2082{
2083 double Eps, Epssq;
2084 double Acoeff[8];
2085 double Lam0, K0, falseE, falseN;
2086 double K0R4, K0R4oa;
2087 double Lambda, Phi;
2088 int XYonly;
2089 double X, Y, pscale, CoM;
2090 int Zone;
2091 char Hemisphere;
2092
2093 /* Get the map projection parameters */
2094
2095 Lambda = DEG2RAD(CoordGeodetic.lambda);
2096 Phi = DEG2RAD(CoordGeodetic.phi);
2097
2098 MAG_GetUTMParameters(Phi, Lambda, &Zone, &Hemisphere, &Lam0);
2099 K0 = 0.9996;
2100
2101 if (Hemisphere == 'n' || Hemisphere == 'N') {
2102 falseN = 0;
2103 }
2104 if (Hemisphere == 's' || Hemisphere == 'S') {
2105 falseN = 10000000;
2106 }
2107
2108 falseE = 500000;
2109
2110 /* WGS84 ellipsoid */
2111
2112 Eps = 0.081819190842621494335;
2113 Epssq = 0.0066943799901413169961;
2114 K0R4 = 6367449.1458234153093 * K0;
2115 K0R4oa = K0R4 / 6378137;
2116
2117 Acoeff[0] = 8.37731820624469723600E-04;
2118 Acoeff[1] = 7.60852777357248641400E-07;
2119 Acoeff[2] = 1.19764550324249124400E-09;
2120 Acoeff[3] = 2.42917068039708917100E-12;
2121 Acoeff[4] = 5.71181837042801392800E-15;
2122 Acoeff[5] = 1.47999793137966169400E-17;
2123 Acoeff[6] = 4.10762410937071532000E-20;
2124 Acoeff[7] = 1.21078503892257704200E-22;
2125
2126 /* WGS84 ellipsoid */
2127
2128 /* Execution of the forward T.M. algorithm */
2129
2130 XYonly = 0;
2131
2132 MAG_TMfwd4(Eps, Epssq, K0R4, K0R4oa, Acoeff, Lam0, K0, falseE, falseN, XYonly,
2133 Lambda, Phi, &X, &Y, &pscale, &CoM);
2134
2135 /* Report results */
2136
2137 UTMParameters->Easting = X; /* UTM Easting (X) in meters*/
2138 UTMParameters->Northing = Y; /* UTM Northing (Y) in meters */
2139 UTMParameters->Zone = Zone; /*UTM Zone*/
2140 UTMParameters->HemiSphere = Hemisphere;
2141 UTMParameters->CentralMeridian =
2142 RAD2DEG(Lam0); /* Central Meridian of the UTM Zone */
2143 UTMParameters->ConvergenceOfMeridians =
2144 RAD2DEG(CoM); /* Convergence of meridians of the UTM Zone and location */
2145 UTMParameters->PointScale = pscale;
2146
2147 return 0;
2148} /*MAG_GetTransverseMercator*/
2149
2150int MAG_GetUTMParameters(double Latitude, double Longitude, int *Zone,
2151 char *Hemisphere, double *CentralMeridian) {
2152 /*
2153 * The function MAG_GetUTMParameters converts geodetic (latitude and
2154 * longitude) coordinates to UTM projection parameters (zone, hemisphere and
2155 * central meridian) If any errors occur, the error code(s) are returned by
2156 * the function, otherwise TRUE is returned.
2157 *
2158 * Latitude : Latitude in radians (input)
2159 * Longitude : Longitude in radians (input)
2160 * Zone : UTM zone (output)
2161 * Hemisphere : North or South hemisphere (output)
2162 * CentralMeridian : Central Meridian of the UTM Zone in radians (output)
2163 */
2164
2165 long Lat_Degrees;
2166 long Long_Degrees;
2167 long temp_zone;
2168 int Error_Code = 0;
2169
2170 if ((Latitude < DEG2RAD(MAG_UTM_MIN_LAT_DEGREE)) ||
2171 (Latitude >
2172 DEG2RAD(MAG_UTM_MAX_LAT_DEGREE))) { /* Latitude out of range */
2173 MAG_Error(23);
2174 Error_Code = 1;
2175 }
2176 if ((Longitude < -M_PI) ||
2177 (Longitude > (2 * M_PI))) { /* Longitude out of range */
2178 MAG_Error(24);
2179 Error_Code = 1;
2180 }
2181 if (!Error_Code) { /* no errors */
2182 if (Longitude < 0) Longitude += (2 * M_PI) + 1.0e-10;
2183 Lat_Degrees = (long)(Latitude * 180.0 / M_PI);
2184 Long_Degrees = (long)(Longitude * 180.0 / M_PI);
2185
2186 if (Longitude < M_PI)
2187 temp_zone = (long)(31 + ((Longitude * 180.0 / M_PI) / 6.0));
2188 else
2189 temp_zone = (long)(((Longitude * 180.0 / M_PI) / 6.0) - 29);
2190 if (temp_zone > 60) temp_zone = 1;
2191 /* UTM special cases */
2192 if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > -1) &&
2193 (Long_Degrees < 3))
2194 temp_zone = 31;
2195 if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > 2) &&
2196 (Long_Degrees < 12))
2197 temp_zone = 32;
2198 if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 9))
2199 temp_zone = 31;
2200 if ((Lat_Degrees > 71) && (Long_Degrees > 8) && (Long_Degrees < 21))
2201 temp_zone = 33;
2202 if ((Lat_Degrees > 71) && (Long_Degrees > 20) && (Long_Degrees < 33))
2203 temp_zone = 35;
2204 if ((Lat_Degrees > 71) && (Long_Degrees > 32) && (Long_Degrees < 42))
2205 temp_zone = 37;
2206
2207 if (!Error_Code) {
2208 if (temp_zone >= 31)
2209 *CentralMeridian = (6 * temp_zone - 183) * M_PI / 180.0;
2210 else
2211 *CentralMeridian = (6 * temp_zone + 177) * M_PI / 180.0;
2212 *Zone = temp_zone;
2213 if (Latitude < 0)
2214 *Hemisphere = 'S';
2215 else
2216 *Hemisphere = 'N';
2217 }
2218 } /* END OF if (!Error_Code) */
2219 return (Error_Code);
2220} /* MAG_GetUTMParameters */
2221
2222int MAG_isNaN(double d) { return d != d; }
2223
2224int MAG_RotateMagneticVector(MAGtype_CoordSpherical CoordSpherical,
2225 MAGtype_CoordGeodetic CoordGeodetic,
2226 MAGtype_MagneticResults MagneticResultsSph,
2227 MAGtype_MagneticResults *MagneticResultsGeo)
2228/* Rotate the Magnetic Vectors to Geodetic Coordinates
2229Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
2230Equation 16, WMM Technical report
2231
2232INPUT : CoordSpherical : Data structure MAGtype_CoordSpherical with the
2233following elements double lambda; ( longitude) double phig; ( geocentric
2234latitude ) double r; ( distance from the center of the ellipsoid)
2235
2236 CoordGeodetic : Data structure MAGtype_CoordGeodetic with the
2237following elements double lambda; (longitude) double phi; ( geodetic latitude)
2238 double HeightAboveEllipsoid; (height above the ellipsoid
2239(HaE) ) double HeightAboveGeoid;(height above the Geoid )
2240
2241 MagneticResultsSph : Data structure MAGtype_MagneticResults with
2242the following elements double Bx; North double By; East double Bz;
2243Down
2244
2245OUTPUT: MagneticResultsGeo Pointer to the data structure
2246MAGtype_MagneticResults, with the following elements double Bx; North
2247 double By; East
2248 double Bz; Down
2249
2250CALLS : none
2251
2252 */
2253{
2254 double Psi;
2255 /* Difference between the spherical and Geodetic latitudes */
2256 Psi = (M_PI / 180) * (CoordSpherical.phig - CoordGeodetic.phi);
2257
2258 /* Rotate spherical field components to the Geodetic system */
2259 MagneticResultsGeo->Bz =
2260 MagneticResultsSph.Bx * sin(Psi) + MagneticResultsSph.Bz * cos(Psi);
2261 MagneticResultsGeo->Bx =
2262 MagneticResultsSph.Bx * cos(Psi) - MagneticResultsSph.Bz * sin(Psi);
2263 MagneticResultsGeo->By = MagneticResultsSph.By;
2264 return TRUE;
2265} /*MAG_RotateMagneticVector*/
2266
2267void MAG_SphericalToCartesian(MAGtype_CoordSpherical CoordSpherical, double *x,
2268 double *y, double *z) {
2269 double radphi;
2270 double radlambda;
2271
2272 radphi = CoordSpherical.phig * (M_PI / 180);
2273 radlambda = CoordSpherical.lambda * (M_PI / 180);
2274
2275 *x = CoordSpherical.r * cos(radphi) * cos(radlambda);
2276 *y = CoordSpherical.r * cos(radphi) * sin(radlambda);
2277 *z = CoordSpherical.r * sin(radphi);
2278 return;
2279}
2280
2281void MAG_SphericalToGeodetic(MAGtype_Ellipsoid Ellip,
2282 MAGtype_CoordSpherical CoordSpherical,
2283 MAGtype_CoordGeodetic *CoordGeodetic) {
2284 /*This converts spherical coordinates back to geodetic coordinates. It is not
2285 used in the WMM but may be necessary for some applications, such as
2286 geomagnetic coordinates*/
2287 double x, y, z;
2288
2289 MAG_SphericalToCartesian(CoordSpherical, &x, &y, &z);
2290 MAG_CartesianToGeodetic(Ellip, x, y, z, CoordGeodetic);
2291}
2292
2293void MAG_TMfwd4(double Eps, double Epssq, double K0R4, double K0R4oa,
2294 double Acoeff[], double Lam0, double K0, double falseE,
2295 double falseN, int XYonly, double Lambda, double Phi, double *X,
2296 double *Y, double *pscale, double *CoM) {
2297 /* Transverse Mercator forward equations including point-scale and CoM
2298 =--------- =------- =--=--= ---------
2299
2300 Algorithm developed by: C. Rollins August 7, 2006
2301 C software written by: K. Robins
2302
2303
2304 Constants fixed by choice of ellipsoid and choice of projection
2305 parameters
2306 ---------------
2307
2308 Eps Eccentricity (epsilon) of the ellipsoid
2309 Epssq Eccentricity squared
2310 ( R4 Meridional isoperimetric radius )
2311 ( K0 Central scale factor )
2312 K0R4 K0 times R4
2313 K0R4oa K0 times Ratio of R4 over semi-major axis
2314 Acoeff Trig series coefficients, omega as a function of chi
2315 Lam0 Longitude of the central meridian in radians
2316 K0 Central scale factor, for example, 0.9996 for UTM
2317 falseE False easting, for example, 500000 for UTM
2318 falseN False northing
2319
2320 Processing option
2321 ---------- ------
2322
2323 XYonly If one (1), then only X and Y will be properly
2324 computed. Values returned for point-scale
2325 and CoM will merely be the trivial values
2326 for points on the central meridian
2327
2328 Input items that identify the point to be converted
2329 ----- -----
2330
2331 Lambda Longitude (from Greenwich) in radians
2332 Phi Latitude in radians
2333
2334 Output items
2335 ------ -----
2336
2337 X X coordinate (Easting) in meters
2338 Y Y coordinate (Northing) in meters
2339 pscale point-scale (dimensionless)
2340 CoM Convergence-of-meridians in radians
2341 */
2342
2343 double Lam, CLam, SLam, CPhi, SPhi;
2344 double P, part1, part2, denom, CChi, SChi;
2345 double U, V;
2346 double T, Tsq, denom2;
2347 double c2u, s2u, c4u, s4u, c6u, s6u, c8u, s8u;
2348 double c2v, s2v, c4v, s4v, c6v, s6v, c8v, s8v;
2349 double Xstar, Ystar;
2350 double sig1, sig2, comroo;
2351
2352 /*
2353 Ellipsoid to sphere
2354 --------- -- ------
2355
2356 Convert longitude (Greenwhich) to longitude from the central meridian
2357 It is unnecessary to find the (-Pi, Pi] equivalent of the result.
2358 Compute its cosine and sine.
2359 */
2360
2361 Lam = Lambda - Lam0;
2362 CLam = cos(Lam);
2363 SLam = sin(Lam);
2364
2365 /* Latitude */
2366
2367 CPhi = cos(Phi);
2368 SPhi = sin(Phi);
2369
2370 /* Convert geodetic latitude, Phi, to conformal latitude, Chi
2371 Only the cosine and sine of Chi are actually needed. */
2372
2373 P = exp(Eps * ATanH(Eps * SPhi));
2374 part1 = (1 + SPhi) / P;
2375 part2 = (1 - SPhi) * P;
2376 denom = 1 / (part1 + part2);
2377 CChi = 2 * CPhi * denom;
2378 SChi = (part1 - part2) * denom;
2379
2380 /*
2381 Sphere to first plane
2382 ------ -- ----- -----
2383
2384 Apply spherical theory of transverse Mercator to get (u,v) coordinates
2385 Note the order of the arguments in Fortran's version of ArcTan, i.e.
2386 atan2(y, x) = ATan(y/x)
2387 The two argument form of ArcTan is needed here.
2388 */
2389
2390 T = CChi * SLam;
2391 U = ATanH(T);
2392 V = atan2(SChi, CChi * CLam);
2393
2394 /*
2395 Trigonometric multiple angles
2396 ------------- -------- ------
2397
2398 Compute Cosh of even multiples of U
2399 Compute Sinh of even multiples of U
2400 Compute Cos of even multiples of V
2401 Compute Sin of even multiples of V
2402 */
2403
2404 Tsq = T * T;
2405 denom2 = 1 / (1 - Tsq);
2406 c2u = (1 + Tsq) * denom2;
2407 s2u = 2 * T * denom2;
2408 c2v = (-1 + CChi * CChi * (1 + CLam * CLam)) * denom2;
2409 s2v = 2 * CLam * CChi * SChi * denom2;
2410
2411 c4u = 1 + 2 * s2u * s2u;
2412 s4u = 2 * c2u * s2u;
2413 c4v = 1 - 2 * s2v * s2v;
2414 s4v = 2 * c2v * s2v;
2415
2416 c6u = c4u * c2u + s4u * s2u;
2417 s6u = s4u * c2u + c4u * s2u;
2418 c6v = c4v * c2v - s4v * s2v;
2419 s6v = s4v * c2v + c4v * s2v;
2420
2421 c8u = 1 + 2 * s4u * s4u;
2422 s8u = 2 * c4u * s4u;
2423 c8v = 1 - 2 * s4v * s4v;
2424 s8v = 2 * c4v * s4v;
2425
2426 /* First plane to second plane
2427 ----- ----- -- ------ -----
2428
2429 Accumulate terms for X and Y
2430 */
2431
2432 Xstar = Acoeff[3] * s8u * c8v;
2433 Xstar = Xstar + Acoeff[2] * s6u * c6v;
2434 Xstar = Xstar + Acoeff[1] * s4u * c4v;
2435 Xstar = Xstar + Acoeff[0] * s2u * c2v;
2436 Xstar = Xstar + U;
2437
2438 Ystar = Acoeff[3] * c8u * s8v;
2439 Ystar = Ystar + Acoeff[2] * c6u * s6v;
2440 Ystar = Ystar + Acoeff[1] * c4u * s4v;
2441 Ystar = Ystar + Acoeff[0] * c2u * s2v;
2442 Ystar = Ystar + V;
2443
2444 /* Apply isoperimetric radius, scale adjustment, and offsets */
2445
2446 *X = K0R4 * Xstar + falseE;
2447 *Y = K0R4 * Ystar + falseN;
2448
2449 /* Point-scale and CoM
2450 ----- ----- --- --- */
2451
2452 if (XYonly == 1) {
2453 *pscale = K0;
2454 *CoM = 0;
2455 } else {
2456 sig1 = 8 * Acoeff[3] * c8u * c8v;
2457 sig1 = sig1 + 6 * Acoeff[2] * c6u * c6v;
2458 sig1 = sig1 + 4 * Acoeff[1] * c4u * c4v;
2459 sig1 = sig1 + 2 * Acoeff[0] * c2u * c2v;
2460 sig1 = sig1 + 1;
2461
2462 sig2 = 8 * Acoeff[3] * s8u * s8v;
2463 sig2 = sig2 + 6 * Acoeff[2] * s6u * s6v;
2464 sig2 = sig2 + 4 * Acoeff[1] * s4u * s4v;
2465 sig2 = sig2 + 2 * Acoeff[0] * s2u * s2v;
2466
2467 /* Combined square roots */
2468 comroo =
2469 sqrt((1 - Epssq * SPhi * SPhi) * denom2 * (sig1 * sig1 + sig2 * sig2));
2470
2471 *pscale = K0R4oa * 2 * denom * comroo;
2472 *CoM = atan2(SChi * SLam, CLam) + atan2(sig2, sig1);
2473 }
2474} /*MAG_TMfwd4*/
2475
2476int MAG_YearToDate(MAGtype_Date *CalendarDate)
2477
2478/* Converts a given Decimal year into a Year, Month and Date
2479it also outputs an error string if there is a problem
2480INPUT CalendarDate Pointer to the data structure with the following elements
2481 double DecimalYear; decimal years
2482OUTPUT CalendarDate Pointer to the data structure with the following
2483elements updated
2484 * int Year
2485 * int Month
2486 * int Day
2487 Error pointer to an error string
2488CALLS : none
2489
2490 */
2491{
2492 int MonthDays[13], CumulativeDays = 0;
2493 int ExtraDay = 0;
2494 int i, DayOfTheYear;
2495
2496 if (CalendarDate->DecimalYear == 0) {
2497 CalendarDate->Year = 0;
2498 CalendarDate->Month = 0;
2499 CalendarDate->Day = 0;
2500 return FALSE;
2501 }
2502
2503 CalendarDate->Year = (int)floor(CalendarDate->DecimalYear);
2504
2505 if ((CalendarDate->Year % 4 == 0 && CalendarDate->Year % 100 != 0) ||
2506 CalendarDate->Year % 400 == 0)
2507 ExtraDay = 1;
2508
2509 DayOfTheYear =
2510 floor((CalendarDate->DecimalYear - (double)CalendarDate->Year) *
2511 (365.0 + (double)ExtraDay) +
2512 0.5) +
2513 1;
2514 /*The above floor is used for rounding, this only works for positive
2515 * integers*/
2516
2517 MonthDays[0] = 0;
2518 MonthDays[1] = 31;
2519 MonthDays[2] = 28 + ExtraDay;
2520 MonthDays[3] = 31;
2521 MonthDays[4] = 30;
2522 MonthDays[5] = 31;
2523 MonthDays[6] = 30;
2524 MonthDays[7] = 31;
2525 MonthDays[8] = 31;
2526 MonthDays[9] = 30;
2527 MonthDays[10] = 31;
2528 MonthDays[11] = 30;
2529 MonthDays[12] = 31;
2530
2531 for (i = 1; i <= 12; i++) {
2532 CumulativeDays = CumulativeDays + MonthDays[i];
2533
2534 if (DayOfTheYear <= CumulativeDays) {
2535 CalendarDate->Month = i;
2536 CalendarDate->Day = MonthDays[i] - (CumulativeDays - DayOfTheYear);
2537 break;
2538 }
2539 }
2540
2541 return TRUE;
2542
2543} /*MAG_YearToDate*/
2544
2545/******************************************************************************
2546 ********************************Spherical Harmonics***************************
2547 * This grouping consists of functions that together take gauss coefficients
2548 * and return a magnetic vector for an input location in spherical coordinates
2549 ******************************************************************************/
2550
2551int MAG_AssociatedLegendreFunction(MAGtype_CoordSpherical CoordSpherical,
2552 int nMax,
2553 MAGtype_LegendreFunction *LegendreFunction)
2554
2555/* Computes all of the Schmidt-semi normalized associated Legendre
2556functions up to degree nMax. If nMax <= 16, function MAG_PcupLow is used.
2557Otherwise MAG_PcupHigh is called.
2558INPUT CoordSpherical A data structure with the following elements
2559 double lambda; ( longitude)
2560 double phig; ( geocentric
2561latitude ) double r; ( distance from the center of the ellipsoid) nMax
2562integer ( Maxumum degree of spherical harmonic secular model)
2563 LegendreFunction Pointer to data structure with the following
2564elements double *Pcup; ( pointer to store Legendre Function ) double *dPcup;
2565( pointer to store Derivative of Lagendre function )
2566
2567OUTPUT LegendreFunction Calculated Legendre variables in the data structure
2568
2569 */
2570{
2571 double sin_phi;
2572 int FLAG = 1;
2573
2574 sin_phi = sin(DEG2RAD(CoordSpherical.phig)); /* sin (geocentric latitude) */
2575
2576 if (nMax <= 16 || (1 - fabs(sin_phi)) <
2577 1.0e-10) /* If nMax is less tha 16 or at the poles */
2578 FLAG = MAG_PcupLow(LegendreFunction->Pcup, LegendreFunction->dPcup, sin_phi,
2579 nMax);
2580 else
2581 FLAG = MAG_PcupHigh(LegendreFunction->Pcup, LegendreFunction->dPcup,
2582 sin_phi, nMax);
2583 if (FLAG == 0) /* Error while computing Legendre variables*/
2584 return FALSE;
2585
2586 return TRUE;
2587} /*MAG_AssociatedLegendreFunction */
2588
2589int MAG_CheckGeographicPole(MAGtype_CoordGeodetic *CoordGeodetic)
2590
2591/* Check if the latitude is equal to -90 or 90. If it is,
2592offset it by 1e-5 to avoid division by zero. This is not currently used in the
2593Geomagnetic main function. This may be used to avoid calling
2594MAG_SummationSpecial. The function updates the input data structure.
2595
2596INPUT CoordGeodetic Pointer to the data structure with the following
2597elements double lambda; (longitude) double phi; ( geodetic latitude) double
2598HeightAboveEllipsoid; (height above the ellipsoid (HaE) ) double
2599HeightAboveGeoid;(height above the Geoid ) OUTPUT CoordGeodetic Pointer to the
2600data structure with the following elements updates double phi; ( geodetic
2601latitude) CALLS : none
2602
2603 */
2604{
2605 CoordGeodetic->phi = CoordGeodetic->phi < (-90.0 + MAG_GEO_POLE_TOLERANCE)
2606 ? (-90.0 + MAG_GEO_POLE_TOLERANCE)
2607 : CoordGeodetic->phi;
2608 CoordGeodetic->phi = CoordGeodetic->phi > (90.0 - MAG_GEO_POLE_TOLERANCE)
2609 ? (90.0 - MAG_GEO_POLE_TOLERANCE)
2610 : CoordGeodetic->phi;
2611 return TRUE;
2612} /*MAG_CheckGeographicPole*/
2613
2614int MAG_ComputeSphericalHarmonicVariables(
2615 MAGtype_Ellipsoid Ellip, MAGtype_CoordSpherical CoordSpherical, int nMax,
2617
2618/* Computes Spherical variables
2619 Variables computed are (a/r)^(n+2), cos_m(lamda) and sin_m(lambda) for
2620 spherical harmonic summations. (Equations 10-12 in the WMM Technical Report)
2621 INPUT Ellip data structure with the following elements
2622 double a; semi-major axis of the ellipsoid
2623 double b; semi-minor axis of the ellipsoid
2624 double fla; flattening
2625 double epssq; first eccentricity squared
2626 double eps; first eccentricity
2627 double re; mean radius of ellipsoid
2628 CoordSpherical A data structure with the following
2629 elements double lambda; ( longitude) double phig; ( geocentric latitude )
2630 double r; ( distance from the center of
2631 the ellipsoid)
2632 nMax integer ( Maxumum degree of spherical harmonic
2633 secular model)\
2634
2635 OUTPUT SphVariables Pointer to the data structure with the following
2636 elements double RelativeRadiusPower[MAG_MAX_MODEL_DEGREES+1];
2637 [earth_reference_radius_km sph. radius ]^n double
2638 cos_mlambda[MAG_MAX_MODEL_DEGREES+1]; cp(m) - cosine of (mspherical coord.
2639 longitude) double sin_mlambda[MAG_MAX_MODEL_DEGREES+1]; sp(m) - sine of
2640 (mspherical coord. longitude) CALLS : none
2641 */
2642{
2643 double cos_lambda, sin_lambda;
2644 int m, n;
2645 cos_lambda = cos(DEG2RAD(CoordSpherical.lambda));
2646 sin_lambda = sin(DEG2RAD(CoordSpherical.lambda));
2647 /* for n = 0 ... model_order, compute (Radius of Earth / Spherical radius
2648 r)^(n+2) for n 1..nMax-1 (this is much faster than calling pow MAX_N+1
2649 times). */
2650 SphVariables->RelativeRadiusPower[0] =
2651 (Ellip.re / CoordSpherical.r) * (Ellip.re / CoordSpherical.r);
2652 for (n = 1; n <= nMax; n++) {
2653 SphVariables->RelativeRadiusPower[n] =
2654 SphVariables->RelativeRadiusPower[n - 1] *
2655 (Ellip.re / CoordSpherical.r);
2656 }
2657
2658 /*
2659 Compute cos(m*lambda), sin(m*lambda) for m = 0 ... nMax
2660 cos(a + b) = cos(a)*cos(b) - sin(a)*sin(b)
2661 sin(a + b) = cos(a)*sin(b) + sin(a)*cos(b)
2662 */
2663
2664 SphVariables->cos_mlambda[0] =
2665 1.0; // The size if cos_mlambda and sin_mlambda is nMax+1
2666 SphVariables->sin_mlambda[0] = 0.0;
2667 if (nMax + 1 >= 2) {
2668 SphVariables->cos_mlambda[1] = cos_lambda;
2669 SphVariables->sin_mlambda[1] = sin_lambda;
2670 }
2671
2672 if (nMax + 1 >= 3) {
2673 for (m = 2; m <= nMax; m++) {
2674 SphVariables->cos_mlambda[m] =
2675 SphVariables->cos_mlambda[m - 1] * cos_lambda -
2676 SphVariables->sin_mlambda[m - 1] * sin_lambda;
2677 SphVariables->sin_mlambda[m] =
2678 SphVariables->cos_mlambda[m - 1] * sin_lambda +
2679 SphVariables->sin_mlambda[m - 1] * cos_lambda;
2680 }
2681 }
2682 return TRUE;
2683} /*MAG_ComputeSphericalHarmonicVariables*/
2684
2685void MAG_GradY(MAGtype_Ellipsoid Ellip, MAGtype_CoordSpherical CoordSpherical,
2686 MAGtype_CoordGeodetic CoordGeodetic,
2687 MAGtype_MagneticModel *TimedMagneticModel,
2688 MAGtype_GeoMagneticElements GeoMagneticElements,
2689 MAGtype_GeoMagneticElements *GradYElements) {
2690 MAGtype_LegendreFunction *LegendreFunction;
2692 int NumTerms;
2693 MAGtype_MagneticResults GradYResultsSph, GradYResultsGeo;
2694
2695 NumTerms =
2696 ((TimedMagneticModel->nMax + 1) * (TimedMagneticModel->nMax + 2) / 2);
2697 LegendreFunction = MAG_AllocateLegendreFunctionMemory(
2698 NumTerms); /* For storing the ALF functions */
2699 SphVariables = MAG_AllocateSphVarMemory(TimedMagneticModel->nMax);
2700 MAG_ComputeSphericalHarmonicVariables(
2701 Ellip, CoordSpherical, TimedMagneticModel->nMax,
2702 SphVariables); /* Compute Spherical Harmonic variables */
2703 MAG_AssociatedLegendreFunction(CoordSpherical, TimedMagneticModel->nMax,
2704 LegendreFunction); /* Compute ALF */
2705 MAG_GradYSummation(
2706 LegendreFunction, TimedMagneticModel, *SphVariables, CoordSpherical,
2707 &GradYResultsSph); /* Accumulate the spherical harmonic coefficients*/
2708 MAG_RotateMagneticVector(
2709 CoordSpherical, CoordGeodetic, GradYResultsSph,
2710 &GradYResultsGeo); /* Map the computed Magnetic fields to Geodetic
2711 coordinates */
2712 MAG_CalculateGradientElements(
2713 GradYResultsGeo, GeoMagneticElements,
2714 GradYElements); /* Calculate the Geomagnetic elements, Equation 18 , WMM
2715 Technical report */
2716
2717 MAG_FreeLegendreMemory(LegendreFunction);
2718 MAG_FreeSphVarMemory(SphVariables);
2719}
2720
2721void MAG_GradYSummation(MAGtype_LegendreFunction *LegendreFunction,
2722 MAGtype_MagneticModel *MagneticModel,
2724 MAGtype_CoordSpherical CoordSpherical,
2725 MAGtype_MagneticResults *GradY) {
2726 int m, n, index;
2727 double cos_phi;
2728 GradY->Bz = 0.0;
2729 GradY->By = 0.0;
2730 GradY->Bx = 0.0;
2731 for (n = 1; n <= MagneticModel->nMax; n++) {
2732 for (m = 0; m <= n; m++) {
2733 index = (n * (n + 1) / 2 + m);
2734
2735 GradY->Bz -= SphVariables.RelativeRadiusPower[n] *
2736 (-1 * MagneticModel->Main_Field_Coeff_G[index] *
2737 SphVariables.sin_mlambda[m] +
2738 MagneticModel->Main_Field_Coeff_H[index] *
2739 SphVariables.cos_mlambda[m]) *
2740 (double)(n + 1) * (double)(m)*LegendreFunction->Pcup[index] *
2741 (1 / CoordSpherical.r);
2742 GradY->By += SphVariables.RelativeRadiusPower[n] *
2743 (MagneticModel->Main_Field_Coeff_G[index] *
2744 SphVariables.cos_mlambda[m] +
2745 MagneticModel->Main_Field_Coeff_H[index] *
2746 SphVariables.sin_mlambda[m]) *
2747 (double)(m * m) * LegendreFunction->Pcup[index] *
2748 (1 / CoordSpherical.r);
2749 GradY->Bx -= SphVariables.RelativeRadiusPower[n] *
2750 (-1 * MagneticModel->Main_Field_Coeff_G[index] *
2751 SphVariables.sin_mlambda[m] +
2752 MagneticModel->Main_Field_Coeff_H[index] *
2753 SphVariables.cos_mlambda[m]) *
2754 (double)(m)*LegendreFunction->dPcup[index] *
2755 (1 / CoordSpherical.r);
2756 }
2757 }
2758
2759 cos_phi = cos(DEG2RAD(CoordSpherical.phig));
2760 if (fabs(cos_phi) > 1.0e-10) {
2761 GradY->By = GradY->By / (cos_phi * cos_phi);
2762 GradY->Bx = GradY->Bx / (cos_phi);
2763 GradY->Bz = GradY->Bz / (cos_phi);
2764 } else
2765 /* Special calculation for component - By - at Geographic poles.
2766 * If the user wants to avoid using this function, please make sure that
2767 * the latitude is not exactly +/-90. An option is to make use the function
2768 * MAG_CheckGeographicPoles.
2769 */
2770 {
2771 /* MAG_SummationSpecial(MagneticModel, SphVariables, CoordSpherical, GradY);
2772 */
2773 }
2774}
2775
2776int MAG_PcupHigh(double *Pcup, double *dPcup, double x, int nMax)
2777
2778/* This function evaluates all of the Schmidt-semi normalized associated
2779 Legendre functions up to degree nMax. The functions are initially scaled by
2780 10^280 sin^m in order to minimize the effects of underflow at large m
2781 near the poles (see Holmes and Featherstone 2002, J. Geodesy, 76,
2782 279-299). Note that this function performs the same operation as MAG_PcupLow.
2783 However this function also can be used for high degree (large nMax)
2784 models.
2785
2786 Calling Parameters:
2787 INPUT
2788 nMax: Maximum spherical harmonic degree to compute.
2789 x: cos(colatitude) or sin(latitude).
2790
2791 OUTPUT
2792 Pcup: A vector of all associated Legendgre polynomials
2793 evaluated at x up to nMax. The lenght must by greater or equal to
2794 (nMax+1)*(nMax+2)/2. dPcup: Derivative of Pcup(x) with respect to latitude
2795
2796 CALLS : none
2797 Notes:
2798
2799
2800
2801 Adopted from the FORTRAN code written by Mark Wieczorek September 25, 2005.
2802
2803 Manoj Nair, Nov, 2009 Manoj.C.Nair@Noaa.Gov
2804
2805 Change from the previous version
2806 The prevous version computes the derivatives as
2807 dP(n,m)(x)/dx, where x = sin(latitude) (or cos(colatitude) ).
2808 However, the WMM Geomagnetic routines requires dP(n,m)(x)/dlatitude.
2809 Hence the derivatives are multiplied by sin(latitude).
2810 Removed the options for CS phase and normalizations.
2811
2812 Note: In geomagnetism, the derivatives of ALF are usually found with
2813 respect to the colatitudes. Here the derivatives are found with respect
2814 to the latitude. The difference is a sign reversal for the derivative of
2815 the Associated Legendre Functions.
2816
2817 The derivatives can't be computed for latitude = |90| degrees.
2818 */
2819{
2820 double pm2, pm1, pmm, plm, rescalem, z, scalef;
2821 double *f1, *f2, *PreSqr;
2822 int k, kstart, m, n, NumTerms;
2823
2824 NumTerms = ((nMax + 1) * (nMax + 2) / 2);
2825
2826 z = sqrt((1.0 - x) * (1.0 + x));
2827
2828 if (z == 0) {
2829 return 0;
2830 }
2831
2832 if (fabs(x) == 1.0) {
2833 printf("Error in PcupHigh: derivative cannot be calculated at poles\n");
2834 return FALSE;
2835 }
2836
2837 f1 = (double *)malloc((NumTerms + 1) * sizeof(double));
2838 if (f1 == NULL) {
2839 MAG_Error(18);
2840 return FALSE;
2841 }
2842
2843 PreSqr = (double *)malloc((NumTerms + 1) * sizeof(double));
2844
2845 if (PreSqr == NULL) {
2846 MAG_Error(18);
2847 return FALSE;
2848 }
2849
2850 f2 = (double *)malloc((NumTerms + 1) * sizeof(double));
2851
2852 if (f2 == NULL) {
2853 MAG_Error(18);
2854 return FALSE;
2855 }
2856
2857 scalef = 1.0e-280;
2858
2859 for (n = 0; n <= 2 * nMax + 1; ++n) {
2860 PreSqr[n] = sqrt((double)(n));
2861 }
2862
2863 k = 2;
2864
2865 for (n = 2; n <= nMax; n++) {
2866 k = k + 1;
2867 f1[k] = (double)(2 * n - 1) / (double)(n);
2868 f2[k] = (double)(n - 1) / (double)(n);
2869 for (m = 1; m <= n - 2; m++) {
2870 k = k + 1;
2871 f1[k] = (double)(2 * n - 1) / PreSqr[n + m] / PreSqr[n - m];
2872 f2[k] =
2873 PreSqr[n - m - 1] * PreSqr[n + m - 1] / PreSqr[n + m] / PreSqr[n - m];
2874 }
2875 k = k + 2;
2876 }
2877
2878 /*z = sin (geocentric latitude) */
2879
2880 pm2 = 1.0;
2881 Pcup[0] = 1.0;
2882 dPcup[0] = 0.0;
2883 if (nMax == 0) return FALSE;
2884 pm1 = x;
2885 Pcup[1] = pm1;
2886 dPcup[1] = z;
2887 k = 1;
2888
2889 for (n = 2; n <= nMax; n++) {
2890 k = k + n;
2891 plm = f1[k] * x * pm1 - f2[k] * pm2;
2892 Pcup[k] = plm;
2893 dPcup[k] = (double)(n) * (pm1 - x * plm) / z;
2894 pm2 = pm1;
2895 pm1 = plm;
2896 }
2897#pragma GCC diagnostic push
2898#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
2899 // Compiler sees PreSqr as not initialized. However, it is.
2900 pmm = PreSqr[2] * scalef;
2901 rescalem = 1.0 / scalef;
2902 kstart = 0;
2903
2904 for (m = 1; m <= nMax - 1; ++m) {
2905 rescalem = rescalem * z;
2906
2907 /* Calculate Pcup(m,m)*/
2908 kstart = kstart + m + 1;
2909 pmm = pmm * PreSqr[2 * m + 1] / PreSqr[2 * m];
2910 Pcup[kstart] = pmm * rescalem / PreSqr[2 * m + 1];
2911 dPcup[kstart] = -((double)(m)*x * Pcup[kstart] / z);
2912 pm2 = pmm / PreSqr[2 * m + 1];
2913 /* Calculate Pcup(m+1,m)*/
2914 k = kstart + m + 1;
2915 pm1 = x * PreSqr[2 * m + 1] * pm2;
2916 Pcup[k] = pm1 * rescalem;
2917 dPcup[k] =
2918 ((pm2 * rescalem) * PreSqr[2 * m + 1] - x * (double)(m + 1) * Pcup[k]) /
2919 z;
2920 /* Calculate Pcup(n,m)*/
2921 for (n = m + 2; n <= nMax; ++n) {
2922 k = k + n;
2923 plm = x * f1[k] * pm1 - f2[k] * pm2;
2924 Pcup[k] = plm * rescalem;
2925 dPcup[k] = (PreSqr[n + m] * PreSqr[n - m] * (pm1 * rescalem) -
2926 (double)(n)*x * Pcup[k]) /
2927 z;
2928 pm2 = pm1;
2929 pm1 = plm;
2930 }
2931#pragma GCC diagnostic pop
2932 }
2933
2934 /* Calculate Pcup(nMax,nMax)*/
2935 rescalem = rescalem * z;
2936 kstart = kstart + m + 1;
2937 pmm = pmm / PreSqr[2 * nMax];
2938 Pcup[kstart] = pmm * rescalem;
2939 dPcup[kstart] = -(double)(nMax)*x * Pcup[kstart] / z;
2940 free(f1);
2941 free(PreSqr);
2942 free(f2);
2943
2944 return TRUE;
2945} /* MAG_PcupHigh */
2946
2947int MAG_PcupLow(double *Pcup, double *dPcup, double x, int nMax)
2948
2949/* This function evaluates all of the Schmidt-semi normalized associated
2950 Legendre functions up to degree nMax.
2951
2952 Calling Parameters:
2953 INPUT
2954 nMax: Maximum spherical harmonic degree to compute.
2955 x: cos(colatitude) or sin(latitude).
2956
2957 OUTPUT
2958 Pcup: A vector of all associated Legendgre polynomials
2959 evaluated at x up to nMax. dPcup: Derivative of Pcup(x) with respect to
2960 latitude
2961
2962 Notes: Overflow may occur if nMax > 20 , especially for high-latitudes.
2963 Use MAG_PcupHigh for large nMax.
2964
2965 Written by Manoj Nair, June, 2009 . Manoj.C.Nair@Noaa.Gov.
2966
2967 Note: In geomagnetism, the derivatives of ALF are usually found with
2968 respect to the colatitudes. Here the derivatives are found with respect
2969 to the latitude. The difference is a sign reversal for the derivative of
2970 the Associated Legendre Functions.
2971 */
2972{
2973 int n, m, index, index1, index2, NumTerms;
2974 double k, z, *schmidtQuasiNorm;
2975 Pcup[0] = 1.0;
2976 dPcup[0] = 0.0;
2977 /*sin (geocentric latitude) - sin_phi */
2978 z = sqrt((1.0 - x) * (1.0 + x));
2979
2980 NumTerms = ((nMax + 1) * (nMax + 2) / 2);
2981 schmidtQuasiNorm = (double *)malloc((NumTerms + 1) * sizeof(double));
2982
2983 if (schmidtQuasiNorm == NULL) {
2984 MAG_Error(19);
2985 return FALSE;
2986 }
2987
2988 /* First, Compute the Gauss-normalized associated Legendre functions*/
2989 for (n = 1; n <= nMax; n++) {
2990 for (m = 0; m <= n; m++) {
2991 index = (n * (n + 1) / 2 + m);
2992 if (n == m) {
2993 index1 = (n - 1) * n / 2 + m - 1;
2994 Pcup[index] = z * Pcup[index1];
2995 dPcup[index] = z * dPcup[index1] + x * Pcup[index1];
2996 } else if (n == 1 && m == 0) {
2997 index1 = (n - 1) * n / 2 + m;
2998 Pcup[index] = x * Pcup[index1];
2999 dPcup[index] = x * dPcup[index1] - z * Pcup[index1];
3000 } else if (n > 1 && n != m) {
3001 index1 = (n - 2) * (n - 1) / 2 + m;
3002 index2 = (n - 1) * n / 2 + m;
3003 if (m > n - 2) {
3004 Pcup[index] = x * Pcup[index2];
3005 dPcup[index] = x * dPcup[index2] - z * Pcup[index2];
3006 } else {
3007 k = (double)(((n - 1) * (n - 1)) - (m * m)) /
3008 (double)((2 * n - 1) * (2 * n - 3));
3009 Pcup[index] = x * Pcup[index2] - k * Pcup[index1];
3010 dPcup[index] =
3011 x * dPcup[index2] - z * Pcup[index2] - k * dPcup[index1];
3012 }
3013 }
3014 }
3015 }
3016 /* Compute the ration between the the Schmidt quasi-normalized associated
3017 * Legendre functions and the Gauss-normalized version. */
3018
3019 schmidtQuasiNorm[0] = 1.0;
3020 for (n = 1; n <= nMax; n++) {
3021 index = (n * (n + 1) / 2);
3022 index1 = (n - 1) * n / 2;
3023 /* for m = 0 */
3024 schmidtQuasiNorm[index] =
3025 schmidtQuasiNorm[index1] * (double)(2 * n - 1) / (double)n;
3026
3027 for (m = 1; m <= n; m++) {
3028 index = (n * (n + 1) / 2 + m);
3029 index1 = (n * (n + 1) / 2 + m - 1);
3030 schmidtQuasiNorm[index] =
3031 schmidtQuasiNorm[index1] *
3032 sqrt((double)((n - m + 1) * (m == 1 ? 2 : 1)) / (double)(n + m));
3033 }
3034 }
3035
3036 /* Converts the Gauss-normalized associated Legendre
3037 functions to the Schmidt quasi-normalized version using pre-computed
3038 relation stored in the variable schmidtQuasiNorm */
3039
3040 for (n = 1; n <= nMax; n++) {
3041 for (m = 0; m <= n; m++) {
3042 index = (n * (n + 1) / 2 + m);
3043 Pcup[index] = Pcup[index] * schmidtQuasiNorm[index];
3044 dPcup[index] = -dPcup[index] * schmidtQuasiNorm[index];
3045 /* The sign is changed since the new WMM routines use derivative with
3046 respect to latitude insted of co-latitude */
3047 }
3048 }
3049
3050 if (schmidtQuasiNorm) free(schmidtQuasiNorm);
3051 return TRUE;
3052} /*MAG_PcupLow */
3053
3054int MAG_SecVarSummation(MAGtype_LegendreFunction *LegendreFunction,
3055 MAGtype_MagneticModel *MagneticModel,
3057 MAGtype_CoordSpherical CoordSpherical,
3058 MAGtype_MagneticResults *MagneticResults) {
3059 /*This Function sums the secular variation coefficients to get the secular
3060 variation of the Magnetic vector. INPUT : LegendreFunction MagneticModel
3061 SphVariables
3062 CoordSpherical
3063 OUTPUT : MagneticResults
3064
3065 CALLS : MAG_SecVarSummationSpecial
3066
3067 */
3068 int m, n, index;
3069 double cos_phi;
3070 MagneticModel->SecularVariationUsed = TRUE;
3071 MagneticResults->Bz = 0.0;
3072 MagneticResults->By = 0.0;
3073 MagneticResults->Bx = 0.0;
3074 for (n = 1; n <= MagneticModel->nMaxSecVar; n++) {
3075 for (m = 0; m <= n; m++) {
3076 index = (n * (n + 1) / 2 + m);
3077
3078 /* nMax (n+2) n m m m Bz =
3079 -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi)) n=1
3080 m=0 n n n */
3081 /* Derivative with respect to radius.*/
3082 MagneticResults->Bz -= SphVariables.RelativeRadiusPower[n] *
3083 (MagneticModel->Secular_Var_Coeff_G[index] *
3084 SphVariables.cos_mlambda[m] +
3085 MagneticModel->Secular_Var_Coeff_H[index] *
3086 SphVariables.sin_mlambda[m]) *
3087 (double)(n + 1) * LegendreFunction->Pcup[index];
3088
3089 /* 1 nMax (n+2) n m m m
3090 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP
3091 (sin(phi)) n=1 m=0 n n n */
3092 /* Derivative with respect to longitude, divided by radius. */
3093 MagneticResults->By += SphVariables.RelativeRadiusPower[n] *
3094 (MagneticModel->Secular_Var_Coeff_G[index] *
3095 SphVariables.sin_mlambda[m] -
3096 MagneticModel->Secular_Var_Coeff_H[index] *
3097 SphVariables.cos_mlambda[m]) *
3098 (double)(m)*LegendreFunction->Pcup[index];
3099 /* nMax (n+2) n m m m
3100 Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
3101 n=1 m=0 n n n */
3102 /* Derivative with respect to latitude, divided by radius. */
3103
3104 MagneticResults->Bx -= SphVariables.RelativeRadiusPower[n] *
3105 (MagneticModel->Secular_Var_Coeff_G[index] *
3106 SphVariables.cos_mlambda[m] +
3107 MagneticModel->Secular_Var_Coeff_H[index] *
3108 SphVariables.sin_mlambda[m]) *
3109 LegendreFunction->dPcup[index];
3110 }
3111 }
3112 cos_phi = cos(DEG2RAD(CoordSpherical.phig));
3113 if (fabs(cos_phi) > 1.0e-10) {
3114 MagneticResults->By = MagneticResults->By / cos_phi;
3115 } else
3116 /* Special calculation for component By at Geographic poles */
3117 {
3118 MAG_SecVarSummationSpecial(MagneticModel, SphVariables, CoordSpherical,
3119 MagneticResults);
3120 }
3121 return TRUE;
3122} /*MAG_SecVarSummation*/
3123
3124int MAG_SecVarSummationSpecial(MAGtype_MagneticModel *MagneticModel,
3126 MAGtype_CoordSpherical CoordSpherical,
3127 MAGtype_MagneticResults *MagneticResults) {
3128 /*Special calculation for the secular variation summation at the poles.
3129
3130
3131 INPUT: MagneticModel
3132 SphVariables
3133 CoordSpherical
3134 OUTPUT: MagneticResults
3135 CALLS : none
3136
3137
3138 */
3139 int n, index;
3140 double k, sin_phi, *PcupS, schmidtQuasiNorm1, schmidtQuasiNorm2,
3141 schmidtQuasiNorm3;
3142
3143 PcupS = (double *)malloc((MagneticModel->nMaxSecVar + 1) * sizeof(double));
3144
3145 if (PcupS == NULL) {
3146 MAG_Error(15);
3147 return FALSE;
3148 }
3149
3150 PcupS[0] = 1;
3151 schmidtQuasiNorm1 = 1.0;
3152
3153 MagneticResults->By = 0.0;
3154 sin_phi = sin(DEG2RAD(CoordSpherical.phig));
3155
3156 for (n = 1; n <= MagneticModel->nMaxSecVar; n++) {
3157 index = (n * (n + 1) / 2 + 1);
3158 schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
3159 schmidtQuasiNorm3 =
3160 schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
3161 schmidtQuasiNorm1 = schmidtQuasiNorm2;
3162 if (n == 1) {
3163 PcupS[n] = PcupS[n - 1];
3164 } else {
3165 k = (double)(((n - 1) * (n - 1)) - 1) /
3166 (double)((2 * n - 1) * (2 * n - 3));
3167 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
3168 }
3169
3170 /* 1 nMax (n+2) n m m m
3171 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
3172 n=1 m=0 n n n */
3173 /* Derivative with respect to longitude, divided by radius. */
3174
3175 MagneticResults->By += SphVariables.RelativeRadiusPower[n] *
3176 (MagneticModel->Secular_Var_Coeff_G[index] *
3177 SphVariables.sin_mlambda[1] -
3178 MagneticModel->Secular_Var_Coeff_H[index] *
3179 SphVariables.cos_mlambda[1]) *
3180 PcupS[n] * schmidtQuasiNorm3;
3181 }
3182
3183 if (PcupS) free(PcupS);
3184 return TRUE;
3185} /*SecVarSummationSpecial*/
3186
3187int MAG_Summation(MAGtype_LegendreFunction *LegendreFunction,
3188 MAGtype_MagneticModel *MagneticModel,
3190 MAGtype_CoordSpherical CoordSpherical,
3191 MAGtype_MagneticResults *MagneticResults) {
3192 /* Computes Geomagnetic Field Elements X, Y and Z in Spherical coordinate
3193 system using spherical harmonic summation.
3194
3195
3196 The vector Magnetic field is given by -grad V, where V is Geomagnetic scalar
3197 potential The gradient in spherical coordinates is given by:
3198
3199 dV ^ 1 dV ^ 1 dV ^
3200 grad V = -- r + - -- t + -------- -- p
3201 dr r dt r sin(t) dp
3202
3203
3204 INPUT : LegendreFunction
3205 MagneticModel
3206 SphVariables
3207 CoordSpherical
3208 OUTPUT : MagneticResults
3209
3210 CALLS : MAG_SummationSpecial
3211
3212
3213
3214 Manoj Nair, June, 2009 Manoj.C.Nair@Noaa.Gov
3215 */
3216 int m, n, index;
3217 double cos_phi;
3218 MagneticResults->Bz = 0.0;
3219 MagneticResults->By = 0.0;
3220 MagneticResults->Bx = 0.0;
3221 for (n = 1; n <= MagneticModel->nMax; n++) {
3222 for (m = 0; m <= n; m++) {
3223 index = (n * (n + 1) / 2 + m);
3224
3225 /* nMax (n+2) n m m m Bz =
3226 -SUM (a/r) (n+1) SUM [g cos(m p) + h sin(m p)] P (sin(phi)) n=1
3227 m=0 n n n */
3228 /* Equation 12 in the WMM Technical report. Derivative with respect to
3229 * radius.*/
3230 MagneticResults->Bz -= SphVariables.RelativeRadiusPower[n] *
3231 (MagneticModel->Main_Field_Coeff_G[index] *
3232 SphVariables.cos_mlambda[m] +
3233 MagneticModel->Main_Field_Coeff_H[index] *
3234 SphVariables.sin_mlambda[m]) *
3235 (double)(n + 1) * LegendreFunction->Pcup[index];
3236
3237 /* 1 nMax (n+2) n m m m
3238 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP
3239 (sin(phi)) n=1 m=0 n n n */
3240 /* Equation 11 in the WMM Technical report. Derivative with respect to
3241 * longitude, divided by radius. */
3242 MagneticResults->By += SphVariables.RelativeRadiusPower[n] *
3243 (MagneticModel->Main_Field_Coeff_G[index] *
3244 SphVariables.sin_mlambda[m] -
3245 MagneticModel->Main_Field_Coeff_H[index] *
3246 SphVariables.cos_mlambda[m]) *
3247 (double)(m)*LegendreFunction->Pcup[index];
3248 /* nMax (n+2) n m m m
3249 Bx = - SUM (a/r) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
3250 n=1 m=0 n n n */
3251 /* Equation 10 in the WMM Technical report. Derivative with respect to
3252 * latitude, divided by radius. */
3253
3254 MagneticResults->Bx -= SphVariables.RelativeRadiusPower[n] *
3255 (MagneticModel->Main_Field_Coeff_G[index] *
3256 SphVariables.cos_mlambda[m] +
3257 MagneticModel->Main_Field_Coeff_H[index] *
3258 SphVariables.sin_mlambda[m]) *
3259 LegendreFunction->dPcup[index];
3260 }
3261 }
3262
3263 cos_phi = cos(DEG2RAD(CoordSpherical.phig));
3264 if (fabs(cos_phi) > 1.0e-10) {
3265 MagneticResults->By = MagneticResults->By / cos_phi;
3266 } else
3267 /* Special calculation for component - By - at Geographic poles.
3268 * If the user wants to avoid using this function, please make sure that
3269 * the latitude is not exactly +/-90. An option is to make use the function
3270 * MAG_CheckGeographicPoles.
3271 */
3272 {
3273 MAG_SummationSpecial(MagneticModel, SphVariables, CoordSpherical,
3274 MagneticResults);
3275 }
3276 return TRUE;
3277} /*MAG_Summation */
3278
3279int MAG_SummationSpecial(MAGtype_MagneticModel *MagneticModel,
3281 MAGtype_CoordSpherical CoordSpherical,
3282 MAGtype_MagneticResults *MagneticResults)
3283/* Special calculation for the component By at Geographic poles.
3284Manoj Nair, June, 2009 manoj.c.nair@noaa.gov
3285INPUT: MagneticModel
3286 SphVariables
3287 CoordSpherical
3288OUTPUT: MagneticResults
3289CALLS : none
3290See Section 1.4, "SINGULARITIES AT THE GEOGRAPHIC POLES", WMM Technical report
3291
3292 */
3293{
3294 int n, index;
3295 double k, sin_phi, *PcupS, schmidtQuasiNorm1, schmidtQuasiNorm2,
3296 schmidtQuasiNorm3;
3297
3298 PcupS = (double *)malloc((MagneticModel->nMax + 1) * sizeof(double));
3299 if (PcupS == 0) {
3300 MAG_Error(14);
3301 return FALSE;
3302 }
3303
3304 PcupS[0] = 1;
3305 schmidtQuasiNorm1 = 1.0;
3306
3307 MagneticResults->By = 0.0;
3308 sin_phi = sin(DEG2RAD(CoordSpherical.phig));
3309
3310 for (n = 1; n <= MagneticModel->nMax; n++) {
3311 /*Compute the ration between the Gauss-normalized associated Legendre
3312functions and the Schmidt quasi-normalized version. This is equivalent to
3313sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! */
3314
3315 index = (n * (n + 1) / 2 + 1);
3316 schmidtQuasiNorm2 = schmidtQuasiNorm1 * (double)(2 * n - 1) / (double)n;
3317 schmidtQuasiNorm3 =
3318 schmidtQuasiNorm2 * sqrt((double)(n * 2) / (double)(n + 1));
3319 schmidtQuasiNorm1 = schmidtQuasiNorm2;
3320 if (n == 1) {
3321 PcupS[n] = PcupS[n - 1];
3322 } else {
3323 k = (double)(((n - 1) * (n - 1)) - 1) /
3324 (double)((2 * n - 1) * (2 * n - 3));
3325 PcupS[n] = sin_phi * PcupS[n - 1] - k * PcupS[n - 2];
3326 }
3327
3328 /* 1 nMax (n+2) n m m m
3329 By = SUM (a/r) (m) SUM [g cos(m p) + h sin(m p)] dP (sin(phi))
3330 n=1 m=0 n n n */
3331 /* Equation 11 in the WMM Technical report. Derivative with respect to
3332 * longitude, divided by radius. */
3333
3334 MagneticResults->By += SphVariables.RelativeRadiusPower[n] *
3335 (MagneticModel->Main_Field_Coeff_G[index] *
3336 SphVariables.sin_mlambda[1] -
3337 MagneticModel->Main_Field_Coeff_H[index] *
3338 SphVariables.cos_mlambda[1]) *
3339 PcupS[n] * schmidtQuasiNorm3;
3340 }
3341
3342 if (PcupS) free(PcupS);
3343 return TRUE;
3344} /*MAG_SummationSpecial */
3345
3346int MAG_TimelyModifyMagneticModel(MAGtype_Date UserDate,
3347 MAGtype_MagneticModel *MagneticModel,
3348 MAGtype_MagneticModel *TimedMagneticModel)
3349
3350/* Time change the Model coefficients from the base year of the model using
3351secular variation coefficients. Store the coefficients of the static model with
3352their values advanced from epoch t0 to epoch t. Copy the SV coefficients. If
3353input "t�" is the same as "t0", then this is merely a copy operation. If the
3354address of "TimedMagneticModel" is the same as the address of "MagneticModel",
3355then this procedure overwrites the given item "MagneticModel".
3356
3357INPUT: UserDate
3358 MagneticModel
3359OUTPUT:TimedMagneticModel
3360CALLS : none
3361 */
3362{
3363 int n, m, index, a, b;
3364 TimedMagneticModel->EditionDate = MagneticModel->EditionDate;
3365 TimedMagneticModel->epoch = MagneticModel->epoch;
3366 TimedMagneticModel->nMax = MagneticModel->nMax;
3367 TimedMagneticModel->nMaxSecVar = MagneticModel->nMaxSecVar;
3368 a = TimedMagneticModel->nMaxSecVar;
3369 b = (a * (a + 1) / 2 + a);
3370 MAG_strlcpy_equivalent(TimedMagneticModel->ModelName,
3371 MagneticModel->ModelName,
3372 sizeof(TimedMagneticModel->ModelName));
3373 for (n = 1; n <= MagneticModel->nMax; n++) {
3374 for (m = 0; m <= n; m++) {
3375 index = (n * (n + 1) / 2 + m);
3376 if (index <= b) {
3377 TimedMagneticModel->Main_Field_Coeff_H[index] =
3378 MagneticModel->Main_Field_Coeff_H[index] +
3379 (UserDate.DecimalYear - MagneticModel->epoch) *
3380 MagneticModel->Secular_Var_Coeff_H[index];
3381 TimedMagneticModel->Main_Field_Coeff_G[index] =
3382 MagneticModel->Main_Field_Coeff_G[index] +
3383 (UserDate.DecimalYear - MagneticModel->epoch) *
3384 MagneticModel->Secular_Var_Coeff_G[index];
3385 TimedMagneticModel->Secular_Var_Coeff_H[index] =
3386 MagneticModel
3387 ->Secular_Var_Coeff_H[index]; /* We need a copy of the secular
3388 var coef to calculate secular
3389 change */
3390 TimedMagneticModel->Secular_Var_Coeff_G[index] =
3391 MagneticModel->Secular_Var_Coeff_G[index];
3392 } else {
3393 TimedMagneticModel->Main_Field_Coeff_H[index] =
3394 MagneticModel->Main_Field_Coeff_H[index];
3395 TimedMagneticModel->Main_Field_Coeff_G[index] =
3396 MagneticModel->Main_Field_Coeff_G[index];
3397 }
3398 }
3399 }
3400 return TRUE;
3401} /* MAG_TimelyModifyMagneticModel */
3402
3403/*End of Spherical Harmonic Functions*/
3404
3405/******************************************************************************
3406 *************************************Geoid************************************
3407 * This grouping consists of functions that make calculations to adjust
3408 * ellipsoid height to height above the geoid (Height above MSL).
3409 ******************************************************************************
3410 ******************************************************************************/
3411
3412int MAG_ConvertGeoidToEllipsoidHeight(MAGtype_CoordGeodetic *CoordGeodetic,
3413 MAGtype_Geoid *Geoid)
3414
3415/*
3416 * The function Convert_Geoid_To_Ellipsoid_Height converts the specified WGS84
3417 * Geoid height at the specified geodetic coordinates to the equivalent
3418 * ellipsoid height, using the EGM96 gravity model.
3419 *
3420 * CoordGeodetic->phi : Geodetic latitude in degress (input)
3421 * CoordGeodetic->lambda : Geodetic longitude in degrees (input)
3422 * CoordGeodetic->HeightAboveEllipsoid : Ellipsoid height, in
3423 kilometers (output)
3424 * CoordGeodetic->HeightAboveGeoid: Geoid height, in kilometers (input)
3425 *
3426 CALLS : MAG_GetGeoidHeight (
3427
3428 */
3429{
3430 double DeltaHeight;
3431 int Error_Code;
3432 double lat, lon;
3433
3434 if (Geoid->UseGeoid == 1) { /* Geoid correction required */
3435 /* To ensure that latitude is less than 90 call MAG_EquivalentLatLon() */
3436 MAG_EquivalentLatLon(CoordGeodetic->phi, CoordGeodetic->lambda, &lat, &lon);
3437 Error_Code = MAG_GetGeoidHeight(lat, lon, &DeltaHeight, Geoid);
3438 CoordGeodetic->HeightAboveEllipsoid =
3439 CoordGeodetic->HeightAboveGeoid + DeltaHeight / 1000; /* Input and
3440output should be kilometers, However MAG_GetGeoidHeight returns Geoid height in
3441meters - Hence division by 1000 */
3442 } else /* Geoid correction not required, copy the MSL height to Ellipsoid
3443 height */
3444 {
3445 CoordGeodetic->HeightAboveEllipsoid = CoordGeodetic->HeightAboveGeoid;
3446 Error_Code = TRUE;
3447 }
3448 return (Error_Code);
3449} /* MAG_ConvertGeoidToEllipsoidHeight*/
3450
3451int MAG_GetGeoidHeight(double Latitude, double Longitude, double *DeltaHeight,
3452 MAGtype_Geoid *Geoid)
3453/*
3454 * The function MAG_GetGeoidHeight returns the height of the
3455 * EGM96 geiod above or below the WGS84 ellipsoid,
3456 * at the specified geodetic coordinates,
3457 * using a grid of height adjustments from the EGM96 gravity model.
3458 *
3459 * Latitude : Geodetic latitude in radians (input)
3460 * Longitude : Geodetic longitude in radians (input)
3461 * DeltaHeight : Height Adjustment, in meters. (output)
3462 * Geoid : MAGtype_Geoid with Geoid grid
3463 (input) CALLS : none
3464 */
3465{
3466 long Index;
3467 double DeltaX, DeltaY;
3468 double ElevationSE, ElevationSW, ElevationNE, ElevationNW;
3469 double OffsetX, OffsetY;
3470 double PostX, PostY;
3471 double UpperY, LowerY;
3472 int Error_Code = 0;
3473
3474 if (!Geoid->Geoid_Initialized) {
3475 MAG_Error(5);
3476 return (FALSE);
3477 }
3478 if ((Latitude < -90) || (Latitude > 90)) { /* Latitude out of range */
3479 Error_Code |= 1;
3480 }
3481 if ((Longitude < -180) || (Longitude > 360)) { /* Longitude out of range */
3482 Error_Code |= 1;
3483 }
3484
3485 if (!Error_Code) { /* no errors */
3486 /* Compute X and Y Offsets into Geoid Height Array: */
3487
3488 if (Longitude < 0.0) {
3489 OffsetX = (Longitude + 360.0) * Geoid->ScaleFactor;
3490 } else {
3491 OffsetX = Longitude * Geoid->ScaleFactor;
3492 }
3493 OffsetY = (90.0 - Latitude) * Geoid->ScaleFactor;
3494
3495 /* Find Four Nearest Geoid Height Cells for specified Latitude, Longitude;
3496 */
3497 /* Assumes that (0,0) of Geoid Height Array is at Northwest corner: */
3498
3499 PostX = floor(OffsetX);
3500 if ((PostX + 1) == Geoid->NumbGeoidCols) PostX--;
3501 PostY = floor(OffsetY);
3502 if ((PostY + 1) == Geoid->NumbGeoidRows) PostY--;
3503
3504 Index = (long)(PostY * Geoid->NumbGeoidCols + PostX);
3505 ElevationNW = (double)Geoid->GeoidHeightBuffer[Index];
3506 ElevationNE = (double)Geoid->GeoidHeightBuffer[Index + 1];
3507
3508 Index = (long)((PostY + 1) * Geoid->NumbGeoidCols + PostX);
3509 ElevationSW = (double)Geoid->GeoidHeightBuffer[Index];
3510 ElevationSE = (double)Geoid->GeoidHeightBuffer[Index + 1];
3511
3512 /* Perform Bi-Linear Interpolation to compute Height above Ellipsoid: */
3513
3514 DeltaX = OffsetX - PostX;
3515 DeltaY = OffsetY - PostY;
3516
3517 UpperY = ElevationNW + DeltaX * (ElevationNE - ElevationNW);
3518 LowerY = ElevationSW + DeltaX * (ElevationSE - ElevationSW);
3519
3520 *DeltaHeight = UpperY + DeltaY * (LowerY - UpperY);
3521 } else {
3522 MAG_Error(17);
3523 return (FALSE);
3524 }
3525 return TRUE;
3526} /*MAG_GetGeoidHeight*/
3527
3528void MAG_EquivalentLatLon(double lat, double lon, double *repairedLat,
3529 double *repairedLon)
3530/*This function takes a latitude and longitude that are ordinarily out of range
3531 and gives in range values that are equivalent on the Earth's surface. This is
3532 required to get correct values for the geoid function.*/
3533{
3534 double colat;
3535 colat = 90 - lat;
3536 *repairedLon = lon;
3537 if (colat < 0) colat = -colat;
3538 while (colat > 360) {
3539 colat -= 360;
3540 }
3541 if (colat > 180) {
3542 colat -= 180;
3543 *repairedLon = *repairedLon + 180;
3544 }
3545 *repairedLat = 90 - colat;
3546 if (*repairedLon > 360) *repairedLon -= 360;
3547 if (*repairedLon < -180) *repairedLon += 360;
3548}
3549
3550/*End of Geoid Functions*/
3551
3552/*New Error Functions*/
3553
3554void MAG_WMMErrorCalc(double H, MAGtype_GeoMagneticElements *Uncertainty) {
3555 double decl_variable, decl_constant;
3556 Uncertainty->F = WMM_UNCERTAINTY_F;
3557 Uncertainty->H = WMM_UNCERTAINTY_H;
3558 Uncertainty->X = WMM_UNCERTAINTY_X;
3559 Uncertainty->Z = WMM_UNCERTAINTY_Z;
3560 Uncertainty->Incl = WMM_UNCERTAINTY_I;
3561 Uncertainty->Y = WMM_UNCERTAINTY_Y;
3562 decl_variable = (WMM_UNCERTAINTY_D_COEF / H);
3563 decl_constant = (WMM_UNCERTAINTY_D_OFFSET);
3564 Uncertainty->Decl =
3565 sqrt(decl_constant * decl_constant + decl_variable * decl_variable);
3566 if (Uncertainty->Decl > 180) {
3567 Uncertainty->Decl = 180;
3568 }
3569}
3570
3571void MAG_WMMHRErrorCalc(double H, MAGtype_GeoMagneticElements *Uncertainty) {
3572 double decl_variable, decl_constant;
3573 Uncertainty->F = WMMHR_UNCERTAINTY_F;
3574 Uncertainty->H = WMMHR_UNCERTAINTY_H;
3575 Uncertainty->X = WMMHR_UNCERTAINTY_X;
3576 Uncertainty->Z = WMMHR_UNCERTAINTY_Z;
3577 Uncertainty->Incl = WMMHR_UNCERTAINTY_I;
3578 Uncertainty->Y = WMMHR_UNCERTAINTY_Y;
3579 decl_variable = (WMMHR_UNCERTAINTY_D_COEF / H);
3580 decl_constant = (WMMHR_UNCERTAINTY_D_OFFSET);
3581 Uncertainty->Decl =
3582 sqrt(decl_constant * decl_constant + decl_variable * decl_variable);
3583 if (Uncertainty->Decl > 180) {
3584 Uncertainty->Decl = 180;
3585 }
3586}
3587
3588void MAG_PrintUserDataWithUncertainty(
3589 MAGtype_GeoMagneticElements GeomagElements,
3591 MAGtype_Date TimeInput, MAGtype_MagneticModel *MagneticModel,
3592 MAGtype_Geoid *Geoid) {
3593 int dms_size = 150;
3594 char *DeclString = malloc(sizeof(char) * dms_size);
3595 char *InclString = malloc(sizeof(char) * dms_size);
3596 char *GVString = malloc(sizeof(char) * dms_size);
3597 memset(DeclString, '\0', dms_size);
3598 memset(InclString, '\0', dms_size);
3599 memset(GVString, '\0', dms_size);
3600 MAG_DegreeToDMSstring(GeomagElements.Incl, 2, InclString);
3601 if (GeomagElements.H < 6000 && GeomagElements.H > 2000)
3602 MAG_Warnings(1, GeomagElements.H, MagneticModel);
3603 if (GeomagElements.H < 2000) MAG_Warnings(2, GeomagElements.H, MagneticModel);
3604 if (MagneticModel->SecularVariationUsed == TRUE) {
3605 MAG_DegreeToDMSstring(GeomagElements.Decl, 2, DeclString);
3606 printf("\n Results For \n\n");
3607 if (SpaceInput.phi < 0)
3608 printf("Latitude %.2fS\n", -SpaceInput.phi);
3609 else
3610 printf("Latitude %.2fN\n", SpaceInput.phi);
3611 if (SpaceInput.lambda < 0)
3612 printf("Longitude %.2fW\n", -SpaceInput.lambda);
3613 else
3614 printf("Longitude %.2fE\n", SpaceInput.lambda);
3615 if (Geoid->UseGeoid == 1)
3616 printf("Altitude: %.2f Kilometers above mean sea level\n",
3617 SpaceInput.HeightAboveGeoid);
3618 else
3619 printf("Altitude: %.2f Kilometers above the WGS-84 ellipsoid\n",
3620 SpaceInput.HeightAboveEllipsoid);
3621 printf("Date: %.1f\n", TimeInput.DecimalYear);
3622 printf("\n Main Field\t\t\tSecular Change\n");
3623 printf("F = %9.1f +/- %5.1f nT\t\t Fdot = %5.1f\tnT/yr\n",
3624 GeomagElements.F, Errors.F, GeomagElements.Fdot);
3625 printf("H = %9.1f +/- %5.1f nT\t\t Hdot = %5.1f\tnT/yr\n",
3626 GeomagElements.H, Errors.H, GeomagElements.Hdot);
3627 printf("X = %9.1f +/- %5.1f nT\t\t Xdot = %5.1f\tnT/yr\n",
3628 GeomagElements.X, Errors.X, GeomagElements.Xdot);
3629 printf("Y = %9.1f +/- %5.1f nT\t\t Ydot = %5.1f\tnT/yr\n",
3630 GeomagElements.Y, Errors.Y, GeomagElements.Ydot);
3631 printf("Z = %9.1f +/- %5.1f nT\t\t Zdot = %5.1f\tnT/yr\n",
3632 GeomagElements.Z, Errors.Z, GeomagElements.Zdot);
3633 if (GeomagElements.Decl < 0)
3634 printf("Decl =%20s (WEST) +/-%3.0f Min Ddot = %.1f\tMin/yr\n",
3635 DeclString, 60 * Errors.Decl, 60 * GeomagElements.Decldot);
3636 else
3637 printf("Decl =%20s (EAST) +/-%3.0f Min Ddot = %.1f\tMin/yr\n",
3638 DeclString, 60 * Errors.Decl, 60 * GeomagElements.Decldot);
3639 if (GeomagElements.Incl < 0)
3640 printf("Incl =%20s (UP) +/-%3.0f Min Idot = %.1f\tMin/yr\n",
3641 InclString, 60 * Errors.Incl, 60 * GeomagElements.Incldot);
3642 else
3643 printf("Incl =%20s (DOWN) +/-%3.0f Min Idot = %.1f\tMin/yr\n",
3644 InclString, 60 * Errors.Incl, 60 * GeomagElements.Incldot);
3645 } else {
3646 MAG_DegreeToDMSstring(GeomagElements.Decl, 2, DeclString);
3647 printf("\n Results For \n\n");
3648 if (SpaceInput.phi < 0)
3649 printf("Latitude %.2fS\n", -SpaceInput.phi);
3650 else
3651 printf("Latitude %.2fN\n", SpaceInput.phi);
3652 if (SpaceInput.lambda < 0)
3653 printf("Longitude %.2fW\n", -SpaceInput.lambda);
3654 else
3655 printf("Longitude %.2fE\n", SpaceInput.lambda);
3656 if (Geoid->UseGeoid == 1)
3657 printf("Altitude: %.2f Kilometers above MSL\n",
3658 SpaceInput.HeightAboveGeoid);
3659 else
3660 printf("Altitude: %.2f Kilometers above WGS-84 Ellipsoid\n",
3661 SpaceInput.HeightAboveEllipsoid);
3662 printf("Date: %.1f\n", TimeInput.DecimalYear);
3663 printf("\n Main Field\n");
3664 printf("F = %-9.1f +/-%5.1f nT\n", GeomagElements.F, Errors.F);
3665 printf("H = %-9.1f +/-%5.1f nT\n", GeomagElements.H, Errors.H);
3666 printf("X = %-9.1f +/-%5.1f nT\n", GeomagElements.X, Errors.X);
3667 printf("Y = %-9.1f +/-%5.1f nT\n", GeomagElements.Y, Errors.Y);
3668 printf("Z = %-9.1f +/-%5.1f nT\n", GeomagElements.Z, Errors.Z);
3669 if (GeomagElements.Decl < 0)
3670 printf("Decl =%20s (WEST)+/-%4f\n", DeclString, 60 * Errors.Decl);
3671 else
3672 printf("Decl =%20s (EAST)+/-%4f\n", DeclString, 60 * Errors.Decl);
3673 if (GeomagElements.Incl < 0)
3674 printf("Incl =%20s (UP)+/-%4f\n", InclString, 60 * Errors.Incl);
3675 else
3676 printf("Incl =%20s (DOWN)+/-%4f\n", InclString, 60 * Errors.Incl);
3677 }
3678
3679 MAG_DegreeToDMSstring(GeomagElements.GV, 2, GVString);
3680
3681 /* Print Grid Variation */
3682
3683 if (SpaceInput.phi < -55) {
3684 printf("\n\n Grid variation (SOUTH) =%20s\n", GVString);
3685 } else if (SpaceInput.phi > 55) {
3686 printf("\n\n Grid variation (NORTH) =%20s \n", GVString);
3687 }
3688 free(DeclString);
3689 free(InclString);
3690 free(GVString);
3691} /*MAG_PrintUserDataWithUncertainty*/
3692
3693size_t MAG_strlcpy_equivalent(char *dst, char *src, size_t dstlen) {
3694 /*The strlcpy is not the standard C library on Linux*/
3695 size_t srclen;
3696 if (src != NULL) {
3697 srclen = strlen(src);
3698 } else {
3699 return 0;
3700 }
3701 if (dstlen > 0) {
3702 int copy_size = (srclen >= dstlen) ? dstlen - 1 : srclen;
3703 memcpy(dst, src, copy_size);
3704 dst[dstlen - 1] = '\0';
3705 }
3706
3707 return srclen;
3708}