├── .gitattributes ├── library.properties ├── keywords.txt ├── LICENSE ├── examples └── Example1_magneticDeclination │ └── Example1_magneticDeclination.ino ├── src ├── wmm.h ├── WMM_Tinier.h ├── WMM_Tinier.cpp ├── WMM_COF.c └── wmm.c └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=WMM_Tinier 2 | version=1.0.3 3 | author=David Armstrong 4 | maintainer=David Armstrong 5 | sentence=An adaptation of the miniwinwm/WMM_Tiny code for calculating magnetic variation. 6 | paragraph=A small embedded C99 implementation of the World Magnetic Model published by NOAA for calculating the magnetic field variation at any point on the world's surface for a given date in the years 2025 to 2030. 7 | category=Other 8 | url=https://github.com/DavidArmstrong/WMM_Tinier 9 | architectures=* 10 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ######################################################## 2 | # Syntax Coloring Map for WMM_Tinier Library # 3 | ######################################################## 4 | 5 | ####################################### 6 | # Class 7 | ####################################### 8 | 9 | WMM_Tinier KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | begin KEYWORD2 16 | decimalDegrees KEYWORD2 17 | printDegMinSecs KEYWORD2 18 | magneticDeclination KEYWORD2 19 | 20 | ####################################### 21 | # Instances (KEYWORD2) 22 | ####################################### 23 | 24 | ####################################### 25 | # Constants (LITERAL1) 26 | ####################################### 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2025 David Armstrong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/Example1_magneticDeclination/Example1_magneticDeclination.ino: -------------------------------------------------------------------------------- 1 | /* WMM_Tinier Library - Computes geomagnetic declination for date and location 2 | * David Armstrong 3 | * Version 1.0.2 - March 8, 2025 4 | * Example1_magneticDeclination 5 | */ 6 | 7 | #include 8 | 9 | // Need the following define for SAMD processors 10 | #if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL) 11 | #define Serial SERIAL_PORT_USBVIRTUAL 12 | #endif 13 | 14 | WMM_Tinier myDeclination; 15 | 16 | void setup() { 17 | Serial.begin(9600); 18 | delay(2000); //SAMD boards may need a long time to init SerialUSB 19 | Serial.println("WMM_Tinier Arduino IDE Library Example\n"); 20 | 21 | myDeclination.begin(); 22 | Serial.println("We use a sample Longitude = -100 degrees 30 minutes"); 23 | Serial.println("And a sample Latitude = 40 degrees 20 minutes"); 24 | Serial.println("On date = Sept 9, 2025"); 25 | 26 | float longitude = myDeclination.decimalDegrees(-100, 30, 0); 27 | float latitude = myDeclination.decimalDegrees(40, 20, 0); 28 | uint8_t year = 25; 29 | uint8_t month = 9; 30 | uint8_t day = 9; 31 | Serial.println("The calculated geomagnetic declination is: "); 32 | Serial.print(myDeclination.magneticDeclination(latitude, longitude, year, month, day)); 33 | Serial.println(" degrees"); 34 | } 35 | 36 | void loop() { 37 | while(1); //Freeze 38 | } 39 | -------------------------------------------------------------------------------- /src/wmm.h: -------------------------------------------------------------------------------- 1 | #ifndef WMM_H 2 | #define WMM_H 3 | 4 | #include 5 | 6 | #define WMM_EPOCH 2025.0f 7 | 8 | typedef struct 9 | { 10 | float gnm; 11 | float hnm; 12 | float dgnm; 13 | float dhnm; 14 | } wmm_cof_record_t; 15 | 16 | /** 17 | * Initialize the WMM. Needs calling only once. 18 | */ 19 | void wmm_init(void); 20 | 21 | /** 22 | * Get the date in WMM format 23 | * 24 | * @param year Year in 2 digit format of 21st centuary, i.e. 25 represents 2025 25 | * @param month Month, 1 to 12 26 | * @param date Date of month, 1 to 31 27 | * @return Date in WMM format 28 | * @note No checking of illegal dates is done 29 | */ 30 | float wmm_get_date(uint8_t year, uint8_t month, uint8_t date); 31 | 32 | /** 33 | * Get the magnetic variation at a point on the earth's surface 34 | * 35 | * @param glat Latitude in degrees and fractional degrees, negative south 36 | * @param glon Longitude in degrees and fractional degrees, negative west 37 | * @param time_years The date as returned from wmm_get_date 38 | * @param dec Pointer to float holding calculated magnetic variation (also known as declination). Negative is west. 39 | * @note The altitude used is the ellipsoid at the supplied latitude/longitude, not the earth's surface. This will 40 | * give very small errors in some parts of the world comapred to sea level. 41 | */ 42 | void E0000(float glat, float glon, float time_years, float *dec); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/WMM_Tinier.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | WMM_Tinier.h 3 | WMM_Tinier Arduino Library Header File 4 | David Armstrong 5 | Version 1.0.0 - August 6, 2021 6 | https://github.com/DavidArmstrong/WMM_Tinier 7 | 8 | This file prototypes the WMM_Tinier class, as implemented in WMM_Tinier.cpp 9 | 10 | Resources: 11 | Uses math.h for math functions 12 | Refer to https://github.com/miniwinwm/WMM_Tiny 13 | 14 | Development environment specifics: 15 | Arduino IDE 1.8.15 16 | 17 | This code is released under the [MIT License](http://opensource.org/licenses/MIT) 18 | Please review the LICENSE.md file included with this example. 19 | Distributed as-is; no warranty is given. 20 | 21 | ******************************************************************************/ 22 | 23 | // ensure this library description is only included once 24 | #ifndef __WMM_Tinier_h 25 | #define __WMM_Tinier_h 26 | 27 | //Uncomment the following line for debugging output 28 | //#define debug_WMM_Tinier 29 | 30 | #include 31 | #include 32 | #include "wmm.h" 33 | 34 | #if defined(ARDUINO) && ARDUINO >= 100 35 | #include "Arduino.h" 36 | #else 37 | #include "WProgram.h" 38 | #endif 39 | 40 | // Structure to hold data 41 | // We need to populate this when we calculate data 42 | struct WMM_TinierData { 43 | public: 44 | float longitude; 45 | float latitude; 46 | float date; 47 | float declination; 48 | }; 49 | 50 | // Sidereal_Planets library description 51 | class WMM_Tinier { 52 | // user-accessible "public" interface 53 | public: 54 | WMM_TinierData spData; 55 | boolean begin(void); 56 | float decimalDegrees(int degrees, int minutes, float seconds); 57 | void printDegMinSecs(float n); 58 | float magneticDeclination(float Latitude, float Longitude, uint8_t year, uint8_t month, uint8_t day); 59 | 60 | // library-accessible "private" interface 61 | private: 62 | 63 | }; 64 | #endif 65 | -------------------------------------------------------------------------------- /src/WMM_Tinier.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | WMM_Tinier.cpp 3 | WMM_Tinier Arduino Library C++ source 4 | David Armstrong 5 | Version 1.0.1 - October 15, 2022 6 | https://github.com/DavidArmstrong/WMM_Tinier 7 | 8 | Resources: 9 | Uses math.h for math function 10 | Refer to https://github.com/miniwinwm/WMM_Tiny 11 | 12 | Development environment specifics: 13 | Arduino IDE 1.8.15 14 | 15 | This code is released under the [MIT License](http://opensource.org/licenses/MIT). 16 | Please review the LICENSE.md file included with this example. 17 | Distributed as-is; no warranty is given. 18 | ******************************************************************************/ 19 | 20 | // include this library's description file 21 | #include "WMM_Tinier.h" 22 | #include "wmm.c" 23 | 24 | // Need the following define for SAMD processors 25 | #if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL) 26 | #define Serial SERIAL_PORT_USBVIRTUAL 27 | #endif 28 | 29 | // Public Methods ////////////////////////////////////////////////////////// 30 | // Start by doing any setup, and verifying that doubles are supported 31 | boolean WMM_Tinier::begin(void) { 32 | wmm_init(); 33 | return true; 34 | } 35 | 36 | float WMM_Tinier::decimalDegrees(int degrees, int minutes, float seconds) { 37 | int sign = 1; 38 | if (degrees < 0) { 39 | degrees = -degrees; 40 | sign = -1; 41 | } 42 | if (minutes < 0) { 43 | minutes = -minutes; 44 | sign = -1; 45 | } 46 | if (seconds < 0) { 47 | seconds = -seconds; 48 | sign = -1; 49 | } 50 | double decDeg = degrees + (minutes / 60.0) + (seconds / 3600.); 51 | return decDeg * sign; 52 | } 53 | 54 | void WMM_Tinier::printDegMinSecs(float n) { 55 | boolean sign = (n < 0.); 56 | if (sign) n = -n; 57 | long lsec = n * 360000.0; 58 | long deg = lsec / 360000; 59 | long min = (lsec - (deg * 360000)) / 6000; 60 | float secs = (lsec - (deg * 360000) - (min * 6000)) / 100.; 61 | if (sign) Serial.print("-"); 62 | Serial.print(deg); Serial.print(":"); 63 | Serial.print(min); Serial.print(":"); 64 | Serial.print(abs(secs)); Serial.print(" "); 65 | } 66 | 67 | float WMM_Tinier::magneticDeclination(float Latitude, float Longitude, uint8_t year, uint8_t month, uint8_t day) { 68 | //wmm_init(); 69 | float wmm_date = wmm_get_date(year, month, day); 70 | float variation; 71 | E0000(Latitude, Longitude, wmm_date, &variation); 72 | return variation; 73 | } 74 | -------------------------------------------------------------------------------- /src/WMM_COF.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const uint8_t wmm_cof_entries_encoded[] = 4 | {0xCE, 0xEA, 0x23, 0x00, 0xB8, 0x01, 0x00, 0xDC, 0xDC, 0x01, 0x8E, 0xC6, 0x05, 0xA1, 0x01, 0xD7, 0x03, 0xDE, 0x8F, 0x03, 0x00, 0xF4, 0x01, 0x00, 0x87, 0xCD, 0x03, 0xE8, 0xE9, 0x03, 0x74, 0xD5, 0x04, 0xAD, 0x81, 0x02, 0xD7, 0x7F, 0xD0, 0x01, 0xF9, 0x01, 0xAA, 0xD4, 0x01, 0x00, 0x4D, 0x00, 0xE9, 0xF7, 0x02, 0xF6, 0x08, 0x6A, 0x28, 0x96, 0xC2, 0x01, 0x87, 0x25, 0x04, 0x43, 0xB8, 0x46, 0xF7, 0x55, 0xDC, 0x02, 0x69, 0xB6, 0x8B, 0x01, 0x00, 0x50, 0x00, 0xBB, 0x7C, 0xA2, 0x2B, 0x58, 0x4B, 0xAD, 0x08, 0xFB, 0x14, 0x7C, 0x29, 0xFB, 0x2B, 0x88, 0x21, 0x38, 0x10, 0xB9, 0x01, 0xEC, 0x3A, 0xC6, 0x01, 0x6C, 0xDC, 0x24, 0x00, 0x06, 0x00, 0xA9, 0x39, 0x86, 0x07, 0x0E, 0x45, 0x90, 0x1D, 0x9A, 0x22, 0x00, 0x16, 0xEB, 0x15, 0xCD, 0x13, 0x06, 0x04, 0xCC, 0x16, 0xAE, 0x06, 0x16, 0x11, 0x91, 0x03, 0xA5, 0x10, 0x09, 0x13, 0x84, 0x0A, 0x00, 0x42, 0x00, 0xBE, 0x09, 0xF8, 0x02, 0x44, 0x03, 0x81, 0x0C, 0xA8, 0x02, 0x09, 0x50, 0xC5, 0x12, 0xA8, 0x07, 0x0C, 0x44, 0xD9, 0x06, 0xD6, 0x09, 0x49, 0x09, 0x95, 0x02, 0xAD, 0x01, 0x03, 0x07, 0xDF, 0x09, 0x97, 0x0B, 0x09, 0x09, 0x9B, 0x0C, 0x00, 0x00, 0x00, 0xC2, 0x0C, 0xE9, 0x07, 0x41, 0x06, 0xD8, 0x01, 0xD0, 0x02, 0x41, 0x05, 0x91, 0x09, 0x4A, 0x05, 0x48, 0x9E, 0x02, 0xAA, 0x03, 0x41, 0x00, 0x19, 0xCA, 0x01, 0x48, 0x4A, 0xEF, 0x01, 0xFB, 0x03, 0x48, 0x06, 0x8E, 0x02, 0x57, 0x08, 0x42, 0xA8, 0x03, 0x00, 0x41, 0x00, 0xAC, 0x01, 0x87, 0x01, 0x02, 0x42, 0xEF, 0x02, 0xFE, 0x01, 0x00, 0x05, 0x14, 0xB2, 0x01, 0x05, 0x44, 0xD9, 0x03, 0xE1, 0x01, 0x41, 0x04, 0xA9, 0x02, 0xBF, 0x01, 0x03, 0x45, 0x96, 0x02, 0x07, 0x02, 0x46, 0xE8, 0x02, 0x74, 0x00, 0x03, 0x09, 0x27, 0x02, 0x02, 0x2E, 0x00, 0x00, 0x00, 0x8E, 0x01, 0xF8, 0x03, 0x41, 0x43, 0x1E, 0xBA, 0x01, 0x01, 0x03, 0x42, 0x93, 0x01, 0x03, 0x43, 0x59, 0x61, 0x43, 0x03, 0xC3, 0x02, 0x74, 0x00, 0x02, 0x18, 0x88, 0x01, 0x03, 0x41, 0x96, 0x01, 0x46, 0x41, 0x42, 0xD7, 0x01, 0x08, 0x01, 0x04, 0xC1, 0x02, 0xA4, 0x01, 0x41, 0x01, 0x4D, 0x00, 0x01, 0x00, 0xC0, 0x01, 0x21, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x14, 0x18, 0x01, 0x42, 0x4A, 0x35, 0x00, 0x01, 0x46, 0xDB, 0x01, 0x43, 0x41, 0x49, 0x04, 0x00, 0x01, 0x0F, 0x6A, 0x41, 0x00, 0x09, 0x66, 0x41, 0x41, 0x5B, 0x09, 0x00, 0x02, 0x67, 0xDB, 0x01, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x59, 0x1D, 0x00, 0x01, 0x18, 0x46, 0x00, 0x00, 0x46, 0x02, 0x00, 0x01, 0x41, 0x05, 0x41, 0x00, 0x46, 0x43, 0x00, 0x00, 0x41, 0x4C, 0x00, 0x01, 0x0B, 0x51, 0x41, 0x00, 0x4A, 0x5D, 0x41, 0x00, 0x42, 0x52, 0x41, 0x00, 0x1A, 0x57, 0x41, 0x00, 0x54, 0x00, 0x00, 0x00, 0x42, 0x4D, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x0C, 0x0A, 0x00, 0x41, 0x4D, 0x4E, 0x00, 0x01, 0x06, 0x00, 0x00, 0x00, 0x06, 0x06, 0x01, 0x00, 0x05, 0x41, 0x00, 0x00, 0x41, 0x08, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x42, 0x4A, 0x41, 0x00, 0x4D, 0x01, 0x00, 0x00, 0x47, 0x02, 0x41, 0x41}; 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WMM_Tinier 2 | 3 | 4 | WMM_Tinier - Arduino library for calculating geomagnetic variation 5 | 6 | Version 1.0.3 - October 25, 2025 7 | 8 | By David Armstrong
9 | https://github.com/DavidArmstrong/WMM_Tinier
10 | See MIT LICENSE.md file 11 | 12 | 13 | This Arduino library is a small embedded C99 implementation of the World Magnetic Model published by NOAA for calculating the magnetic field declination, or variation, at any point on the world's surface for a given date in the years 2025 to 2030. Its core coding is copied, with permission, from https://github.com/miniwinwm/WMM_Tiny and it is recommended that internal code details be obtained at that URL. 14 | 15 | Geomagnetic variation is needed to account for the difference between the orientation of the earth's magnetic field in relation to true North. This value varies slowly over time according to the observer's position on the earth. Therefore, the value of variation is essential to know when using digital compass sensors so as to be able to determine a true heading. 16 | 17 | Note: NOAA provides the latest coefficients file at this URL: https://www.ngdc.noaa.gov/geomag/WMM/soft.shtml 18 | 19 | It is recommended that the user run the example sketch on the target Arduino board, as it shows how to use the library functions. 20 | 21 | Notes: 22 | 1) The library reduces memory usage by using a compressed coefficients file. Refer to https://github.com/miniwinwm/WMM_Tiny for more details. 23 | 2) Even with the compression used, the program is still much too large for a standard Arduino Uno board, with only 2K of RAM. The target Arduino board must have at least 8K of RAM available. Many of the newer processor boards can meet this requirement, such as the SAMD boards, ESP32, Teensy, and so forth. 24 | 25 | 26 | ====================================== 27 | 28 | Basic Library Functions:
29 | -- Specifics are explained with each function. 30 | 31 | boolean begin()
32 | This initializes the library. 33 | 34 | float decimalDegrees(int degrees, int minutes, float seconds)
35 | Convert a number that is specified in degrees, minutes, and seconds to decimal degrees. 36 | 37 | void printDegMinSecs(float n)
38 | Prints a float number of degrees to Serial in the form {deg}:{min}:{seconds}. The seconds may include a fractional part of two digits. If needed, a minus sign is printed in front of the number. The numbers printed are not further formatted in any way. 39 | 40 | float magneticDeclination(float Latitude, float Longitude, uint8_t year, uint8_t month, uint8_t day)
41 | Sets the Longitude, Latitude, and date for any position on the earth that will be used to calculate the magnetic declination. Coordinates are in degrees, and can range from -90. to +90. for Latitude, and -180. to +180. for Longitude. The Date must be input with a 2-digit year, representing a date between 2025.0 to 2030.0, inclusive. (So a year of 2025 must be entered as just 25.) The geomagnetic declination is returned as a float representing degrees. A positive value means that a compass will point East of North by that amount in degrees. 42 | -------------------------------------------------------------------------------- /src/wmm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "wmm.h" 6 | 7 | #define PI_CONST 3.14159265359f 8 | #define RADIANS_TO_DEGREES 0.017453292f 9 | #define DEGREES_TO_RADIANS (PI_CONST / 180.0f) 10 | #define A_CONST 6378.137f 11 | #define A2_CONST (A_CONST * A_CONST) 12 | #define B_CONST 6356.7523142f 13 | #define B2_CONST (B_CONST * B_CONST) 14 | #define RE_CONST 6371.2f 15 | #define A4_CONST (A2_CONST * A2_CONST) 16 | #define B4_CONST (B2_CONST * B2_CONST) 17 | #define C2_CONST (A2_CONST - B2_CONST) 18 | #define C4_CONST (A4_CONST - B4_CONST) 19 | #define COEFFICIENTS_COUNT 90U 20 | 21 | static float c[13][13]; 22 | static float cd[13][13]; 23 | static float k[13][13]; 24 | static float snorm[169]; 25 | static float fn[13]; 26 | static float fm[13]; 27 | extern const uint8_t wmm_cof_entries_encoded[]; 28 | static wmm_cof_record_t wmm_cof_entries[COEFFICIENTS_COUNT]; 29 | 30 | static float convert_varint_to_float(char **bytes); 31 | 32 | float wmm_get_date(uint8_t year, uint8_t month, uint8_t date) 33 | { 34 | float days_in_year = 365.0f; 35 | 36 | if (year % 4U == 0U) 37 | { 38 | days_in_year = 366.0f; 39 | } 40 | 41 | return (float)year + 2000.0f + (float)(month - 1U) / 12.0f + (float)(date - 1U) / (days_in_year); 42 | } 43 | 44 | static float convert_varint_to_float(char **bytes) 45 | { 46 | float result; 47 | int32_t result_int; 48 | bool negative = false; 49 | bool first_byte = true; 50 | uint8_t shift; 51 | 52 | do 53 | { 54 | if (first_byte) 55 | { 56 | if (**bytes & 0x40) 57 | { 58 | negative = true; 59 | } 60 | 61 | result_int = **bytes & 0x3f; 62 | shift = 6U; 63 | first_byte = false; 64 | } 65 | else 66 | { 67 | result_int += (uint32_t)(**bytes & 0x7f) << shift; 68 | shift += 7U; 69 | } 70 | 71 | if ((**bytes & 0x80) == 0U) 72 | { 73 | (*bytes)++; 74 | break; 75 | } 76 | 77 | (*bytes)++; 78 | 79 | } while (true); 80 | 81 | 82 | result = ((float)result_int) / 10.0f; 83 | if (negative) 84 | { 85 | result = -result; 86 | } 87 | 88 | return result; 89 | } 90 | 91 | void wmm_init(void) 92 | { 93 | uint8_t j; 94 | uint8_t m; 95 | uint8_t n; 96 | uint8_t D2wmm; 97 | float gnm; 98 | float hnm; 99 | float dgnm; 100 | float dhnm; 101 | float flnmj; 102 | uint8_t i; 103 | char *bytes = (char *)&wmm_cof_entries_encoded[0]; 104 | 105 | // unpack coefficients 106 | for (i = 0U; i < COEFFICIENTS_COUNT; i++) 107 | { 108 | wmm_cof_entries[i].gnm = convert_varint_to_float(&bytes); 109 | wmm_cof_entries[i].hnm = convert_varint_to_float(&bytes); 110 | wmm_cof_entries[i].dgnm = convert_varint_to_float(&bytes); 111 | wmm_cof_entries[i].dhnm = convert_varint_to_float(&bytes); 112 | } 113 | 114 | c[0][0] = 0.0f; 115 | cd[0][0] = 0.0f; 116 | 117 | j = 0U; 118 | for (n = 1U; n <= 12U; n++) 119 | { 120 | for (m = 0U; m <= n; m++) 121 | { 122 | gnm = wmm_cof_entries[j].gnm; 123 | hnm = wmm_cof_entries[j].hnm; 124 | dgnm = wmm_cof_entries[j].dgnm; 125 | dhnm = wmm_cof_entries[j].dhnm; 126 | j++; 127 | 128 | if (m <= n) 129 | { 130 | c[m][n] = gnm; 131 | cd[m][n] = dgnm; 132 | if (m != 0U) 133 | { 134 | c[n][m - 1U] = hnm; 135 | cd[n][m - 1U] = dhnm; 136 | } 137 | } 138 | } 139 | } 140 | 141 | // CONVERT SCHMIDT NORMALIZED GAUSS COEFFICIENTS TO UNNORMALIZED 142 | *snorm = 1.0f; 143 | for (n = 1U; n <= 12U; n++) 144 | { 145 | *(snorm + n) = *(snorm + n - 1U) * (float)(2U * n - 1U) / (float)n; 146 | j = 2U; 147 | m = 0U; 148 | for (D2wmm = n - m + 1U; D2wmm > 0U; D2wmm--) 149 | { 150 | k[m][n] = (float)(((n - 1U) * (n - 1U)) - (m * m)) / (float)((2U * n - 1U) * (2U * n - 3U)); 151 | if (m > 0U) 152 | { 153 | flnmj = (float)((n - m + 1U) * j) / (float)(n + m); 154 | *(snorm + n + m * 13U) = *(snorm + n + (m - 1U) * 13U) * sqrt(flnmj); 155 | j = 1U; 156 | c[n][m - 1U] = *(snorm + n + m * 13U) * c[n][m - 1U]; 157 | cd[n][m - 1U] = *(snorm + n + m * 13U) * cd[n][m - 1U]; 158 | } 159 | c[m][n] = *(snorm + n + m * 13U) * c[m][n]; 160 | cd[m][n] = *(snorm + n + m *13U) * cd[m][n]; 161 | m += 1U; 162 | } 163 | fn[n] = (float)(n + 1U); 164 | fm[n] = (float)n; 165 | } 166 | k[1][1] = 0.0f; 167 | } 168 | 169 | void E0000(float glat, float glon, float time_years, float *dec) 170 | { 171 | static float tc[13][13]; 172 | static float sp[13]; 173 | static float cp[13]; 174 | static float dp[13][13]; 175 | static float pp[13]; 176 | float dt = time_years - WMM_EPOCH; 177 | float rlon = glon * DEGREES_TO_RADIANS; 178 | float rlat = glat * DEGREES_TO_RADIANS; 179 | float srlon = sinf(rlon); 180 | float srlat = sinf(rlat); 181 | float crlon = cosf(rlon); 182 | float crlat = cosf(rlat); 183 | float srlat2 = srlat * srlat; 184 | float crlat2 = crlat * crlat; 185 | sp[0] = 0.0f; 186 | sp[1] = srlon; 187 | cp[0] = 1.0f; 188 | cp[1] = crlon; 189 | dp[0][0] = 0.0f; 190 | pp[0] = 1.0f; 191 | 192 | // CONVERT FROM GEODETIC COORDS. TO SPHERICAL COORDS 193 | float q = sqrtf(A2_CONST - C2_CONST * srlat2); 194 | float q2 = (A2_CONST / (B2_CONST)) * (A2_CONST / B2_CONST); 195 | float ct = srlat / sqrtf(q2 * crlat2 + srlat2); 196 | float st = sqrtf(1.0f - (ct * ct)); 197 | float r2 = (A4_CONST - C4_CONST * srlat2) / (q * q); 198 | float r = sqrtf(r2); 199 | float d = sqrtf(A2_CONST * crlat2 + B2_CONST * srlat2); 200 | float ca = d / r; 201 | float sa = C2_CONST * crlat * srlat / (r * d); 202 | for (uint8_t m = 2U; m <= 12U; m++) 203 | { 204 | sp[m] = sp[1] * cp[m - 1U] + cp[1] * sp[m - 1U]; 205 | cp[m] = cp[1] * cp[m - 1U] - sp[1] * sp[m - 1U]; 206 | } 207 | float aor = RE_CONST / r; 208 | float ar = aor * aor; 209 | float br = 0.0f; 210 | float bt = 0.0f; 211 | float bp = 0.0f; 212 | float bpp = 0.0f; 213 | 214 | for (uint16_t n = 1U; n <= 12U; n++) 215 | { 216 | ar = ar * aor; 217 | uint8_t m = 0U; 218 | for (uint8_t D4wmm = n + 1U; D4wmm > 0U; D4wmm--) 219 | { 220 | // COMPUTE UNNORMALIZED ASSOCIATED LEGENDRE POLYNOMIALS AND DERIVATIVES VIA RECURSION RELATIONS 221 | if (n == m) 222 | { 223 | *(snorm + n + m * 13U) = st * *(snorm + n - 1U + (m - 1U) * 13U); 224 | dp[m][n] = st * dp[m - 1U][n - 1U] + ct * *(snorm + n - 1U + (m - 1U) * 13U); 225 | goto S50; 226 | } 227 | if (n == 1U && m == 0U) 228 | { 229 | *(snorm + n + m * 13U) = ct * *(snorm + n - 1U + m * 13U); 230 | dp[m][n] = ct * dp[m][n - 1U] - st * *(snorm + n - 1U + m * 13U); 231 | goto S50; 232 | } 233 | if (n > 1U && n != m) 234 | { 235 | if (m > n - 2U) 236 | { 237 | *(snorm + n - 2U + m * 13U) = 0.0f; 238 | } 239 | if (m > n - 2U) 240 | { 241 | dp[m][n - 2U] = 0.0f; 242 | } 243 | *(snorm + n + m * 13U) = ct * *(snorm + n - 1U + m * 13U) - k[m][n] * *(snorm + n - 2U + m * 13U); 244 | dp[m][n] = ct * dp[m][n - 1U] - st * *(snorm + n - 1U + m * 13U) - k[m][n] * dp[m][n - 2U]; 245 | } 246 | S50: 247 | 248 | // TIME ADJUST THE GAUSS COEFFICIENTS 249 | tc[m][n] = c[m][n] + dt * cd[m][n]; 250 | if (m != 0U) 251 | { 252 | tc[n][m - 1U] = c[n][m - 1U] + dt * cd[n][m - 1U]; 253 | } 254 | 255 | // ACCUMULATE TERMS OF THE SPHERICAL HARMONIC EXPANSIONS 256 | float par = ar * *(snorm + n + m * 13U); 257 | float temp1; 258 | float temp2; 259 | 260 | if (m == 0) 261 | { 262 | temp1 = tc[m][n] * cp[m]; 263 | temp2 = tc[m][n] * sp[m]; 264 | } 265 | else 266 | { 267 | temp1 = tc[m][n] * cp[m] + tc[n][m - 1U] * sp[m]; 268 | temp2 = tc[m][n] * sp[m] - tc[n][m - 1U] * cp[m]; 269 | } 270 | 271 | bt = bt - ar * temp1 * dp[m][n]; 272 | bp += (fm[m] * temp2 * par); 273 | br += (fn[n] * temp1 * par); 274 | 275 | // SPECIAL CASE: NORTH/SOUTH GEOGRAPHIC POLES 276 | if (st == 0.0f && m == 1U) 277 | { 278 | if (n == 1U) 279 | { 280 | pp[n] = pp[n - 1U]; 281 | } 282 | else 283 | { 284 | pp[n] = ct * pp[n - 1U] - k[m][n] * pp[n - 2U]; 285 | } 286 | bpp += (fm[m] * temp2 * ar * pp[n]); 287 | } 288 | m += 1U; 289 | } 290 | } 291 | if (st == 0.0f) 292 | { 293 | bp = bpp; 294 | } 295 | else 296 | { 297 | bp /= st; 298 | } 299 | 300 | // ROTATE MAGNETIC VECTOR COMPONENTS FROM SPHERICAL TO GEODETIC COORDINATES 301 | float bx = -bt * ca - br * sa; 302 | float by = bp; 303 | 304 | // COMPUTE DECLINATION 305 | *dec = atan2f(by, bx) / DEGREES_TO_RADIANS; 306 | } 307 | --------------------------------------------------------------------------------