├── MagDec ├── css │ └── MagDec.css ├── templates │ └── MagDec.html └── WMM.html ├── LICENSE ├── README.md └── MagDec.js /MagDec/css/MagDec.css: -------------------------------------------------------------------------------- 1 | 2 | .magDecWidget { 3 | white-space: nowrap; 4 | min-width: 310px; 5 | } 6 | 7 | .magDecWidget .valuesDiv { 8 | font-weight: normal; 9 | } 10 | 11 | .magDecWidget .attribute { 12 | display: inline-block; 13 | width: 130px; 14 | height: 15px; 15 | font-weight: bold; 16 | } 17 | 18 | .magDecWidget .value { 19 | font-weight: normal; 20 | } 21 | 22 | .magDecWidget .warning { 23 | color: red; 24 | font-weight: bold; 25 | } 26 | .magDecWidget .smallText { 27 | color: black; 28 | font-weight: normal; 29 | font-size: 7px; 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 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 | -------------------------------------------------------------------------------- /MagDec/templates/MagDec.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MagneticDeclination-Widget 2 | 3 | ## Features 4 | An ArcGIS for JavaScript API widget for calculating magnetic declination. 5 | 6 | Model: World Magnetic Model 2015 (WMM-2015) Current until December 2019. 7 | Datum: WGS84 8 | 9 | Press spacebar to pause/resume updating. 10 | 11 | [View it live](http://joerogan.ca/maps/joegis/) 12 | 13 | ## Helpful Links 14 | * [Magnetic Declination](http://geomag.nrcan.gc.ca/mag_fld/magdec-en.php) 15 | * [World Magnetic Model](https://www.ngdc.noaa.gov/geomag/WMM/) 16 | 17 | ## Quickstart 18 | ```javascript 19 | on(mainMap, "load", function() { 20 | var magDec = new MagDec({ 21 | map: mainMap 22 | }, "MagDecWindow"); 23 | magDec.startup(); 24 | }); 25 | ``` 26 | 27 | ## Requirements 28 | * Notepad or HTML editor 29 | * A little background with JavaScript 30 | * Experience with the [ArcGIS API for JavaScript](https://developers.arcgis.com/javascript/) would help. 31 | 32 | ## Setup 33 | Set your dojo config to load the module. 34 | 35 | ```javascript 36 | var package_path = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')); 37 | var dojoConfig = { 38 | // The locationPath logic below may look confusing but all its doing is 39 | // enabling us to load the api from a CDN and load local modules from the correct location. 40 | packages: [{ 41 | name: "application", 42 | location: package_path + '/js' 43 | }] 44 | }; 45 | ``` 46 | 47 | ## Require module 48 | Include the module for the MagDec widget. 49 | 50 | ```javascript 51 | require(["application/MagDec", ... ], function(MagDec, ... ){ ... }); 52 | ``` 53 | 54 | ## Constructor 55 | MagDec(options, srcNode); 56 | 57 | ### Options (Object) 58 | |property|required|type|value|description| 59 | |---|---|---|---|---| 60 | |map|x|Map|null|ArcGIS JS Map.| 61 | |theme||string|magDecWidget|CSS Class for uniquely styling the widget.| 62 | 63 | ## Methods 64 | ### startup 65 | startup(): Start the widget. Map object must be loaded first. 66 | 67 | ## Issues 68 | Find a bug or want to request a new feature? Please let us know by submitting an issue. 69 | 70 | ## Contributing 71 | Anyone and everyone is welcome to contribute. 72 | 73 | ## Credits 74 | Modified from code create by Christopher Weiss (cmweiss@gmail.com) Copyright 2012 75 | Adapted from the geomagc software and World Magnetic Model of the NOAA 76 | Satellite and Information Service, National Geophysical Data Center 77 | [Source](http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml) 78 | 79 | ## Licensing 80 | The MIT License (MIT) 81 | 82 | Copyright (c) 2016 Joseph Rogan 83 | 84 | Permission is hereby granted, free of charge, to any person obtaining a copy 85 | of this software and associated documentation files (the "Software"), to deal 86 | in the Software without restriction, including without limitation the rights 87 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 88 | copies of the Software, and to permit persons to whom the Software is 89 | furnished to do so, subject to the following conditions: 90 | 91 | The above copyright notice and this permission notice shall be included in all 92 | copies or substantial portions of the Software. 93 | 94 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 95 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 96 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 97 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 98 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 99 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 100 | SOFTWARE. 101 | 102 | -------------------------------------------------------------------------------- /MagDec/WMM.html: -------------------------------------------------------------------------------- 1 | 2015.0 WMM-2015 12/15/2014 2 | 1 0 -29438.5 0.0 10.7 0.0 3 | 1 1 -1501.1 4796.2 17.9 -26.8 4 | 2 0 -2445.3 0.0 -8.6 0.0 5 | 2 1 3012.5 -2845.6 -3.3 -27.1 6 | 2 2 1676.6 -642.0 2.4 -13.3 7 | 3 0 1351.1 0.0 3.1 0.0 8 | 3 1 -2352.3 -115.3 -6.2 8.4 9 | 3 2 1225.6 245.0 -0.4 -0.4 10 | 3 3 581.9 -538.3 -10.4 2.3 11 | 4 0 907.2 0.0 -0.4 0.0 12 | 4 1 813.7 283.4 0.8 -0.6 13 | 4 2 120.3 -188.6 -9.2 5.3 14 | 4 3 -335.0 180.9 4.0 3.0 15 | 4 4 70.3 -329.5 -4.2 -5.3 16 | 5 0 -232.6 0.0 -0.2 0.0 17 | 5 1 360.1 47.4 0.1 0.4 18 | 5 2 192.4 196.9 -1.4 1.6 19 | 5 3 -141.0 -119.4 0.0 -1.1 20 | 5 4 -157.4 16.1 1.3 3.3 21 | 5 5 4.3 100.1 3.8 0.1 22 | 6 0 69.5 0.0 -0.5 0.0 23 | 6 1 67.4 -20.7 -0.2 0.0 24 | 6 2 72.8 33.2 -0.6 -2.2 25 | 6 3 -129.8 58.8 2.4 -0.7 26 | 6 4 -29.0 -66.5 -1.1 0.1 27 | 6 5 13.2 7.3 0.3 1.0 28 | 6 6 -70.9 62.5 1.5 1.3 29 | 7 0 81.6 0.0 0.2 0.0 30 | 7 1 -76.1 -54.1 -0.2 0.7 31 | 7 2 -6.8 -19.4 -0.4 0.5 32 | 7 3 51.9 5.6 1.3 -0.2 33 | 7 4 15.0 24.4 0.2 -0.1 34 | 7 5 9.3 3.3 -0.4 -0.7 35 | 7 6 -2.8 -27.5 -0.9 0.1 36 | 7 7 6.7 -2.3 0.3 0.1 37 | 8 0 24.0 0.0 0.0 0.0 38 | 8 1 8.6 10.2 0.1 -0.3 39 | 8 2 -16.9 -18.1 -0.5 0.3 40 | 8 3 -3.2 13.2 0.5 0.3 41 | 8 4 -20.6 -14.6 -0.2 0.6 42 | 8 5 13.3 16.2 0.4 -0.1 43 | 8 6 11.7 5.7 0.2 -0.2 44 | 8 7 -16.0 -9.1 -0.4 0.3 45 | 8 8 -2.0 2.2 0.3 0.0 46 | 9 0 5.4 0.0 0.0 0.0 47 | 9 1 8.8 -21.6 -0.1 -0.2 48 | 9 2 3.1 10.8 -0.1 -0.1 49 | 9 3 -3.1 11.7 0.4 -0.2 50 | 9 4 0.6 -6.8 -0.5 0.1 51 | 9 5 -13.3 -6.9 -0.2 0.1 52 | 9 6 -0.1 7.8 0.1 0.0 53 | 9 7 8.7 1.0 0.0 -0.2 54 | 9 8 -9.1 -3.9 -0.2 0.4 55 | 9 9 -10.5 8.5 -0.1 0.3 56 | 10 0 -1.9 0.0 0.0 0.0 57 | 10 1 -6.5 3.3 0.0 0.1 58 | 10 2 0.2 -0.3 -0.1 -0.1 59 | 10 3 0.6 4.6 0.3 0.0 60 | 10 4 -0.6 4.4 -0.1 0.0 61 | 10 5 1.7 -7.9 -0.1 -0.2 62 | 10 6 -0.7 -0.6 -0.1 0.1 63 | 10 7 2.1 -4.1 0.0 -0.1 64 | 10 8 2.3 -2.8 -0.2 -0.2 65 | 10 9 -1.8 -1.1 -0.1 0.1 66 | 10 10 -3.6 -8.7 -0.2 -0.1 67 | 11 0 3.1 0.0 0.0 0.0 68 | 11 1 -1.5 -0.1 0.0 0.0 69 | 11 2 -2.3 2.1 -0.1 0.1 70 | 11 3 2.1 -0.7 0.1 0.0 71 | 11 4 -0.9 -1.1 0.0 0.1 72 | 11 5 0.6 0.7 0.0 0.0 73 | 11 6 -0.7 -0.2 0.0 0.0 74 | 11 7 0.2 -2.1 0.0 0.1 75 | 11 8 1.7 -1.5 0.0 0.0 76 | 11 9 -0.2 -2.5 0.0 -0.1 77 | 11 10 0.4 -2.0 -0.1 0.0 78 | 11 11 3.5 -2.3 -0.1 -0.1 79 | 12 0 -2.0 0.0 0.1 0.0 80 | 12 1 -0.3 -1.0 0.0 0.0 81 | 12 2 0.4 0.5 0.0 0.0 82 | 12 3 1.3 1.8 0.1 -0.1 83 | 12 4 -0.9 -2.2 -0.1 0.0 84 | 12 5 0.9 0.3 0.0 0.0 85 | 12 6 0.1 0.7 0.1 0.0 86 | 12 7 0.5 -0.1 0.0 0.0 87 | 12 8 -0.4 0.3 0.0 0.0 88 | 12 9 -0.4 0.2 0.0 0.0 89 | 12 10 0.2 -0.9 0.0 0.0 90 | 12 11 -0.9 -0.2 0.0 0.0 91 | 12 12 0.0 0.7 0.0 0.0 92 | 999999999999999999999999999999999999999999999999 93 | 999999999999999999999999999999999999999999999999 94 | -------------------------------------------------------------------------------- /MagDec.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////// 2 | ///////////////////////////////--------MagDec.js-------////////////////////////////// 3 | ///////////////////////////////////////////////////////////////////////////////////// 4 | // 5 | // Version: 1.2 6 | // Author: Joseph Rogan (joseph.rogan@forces.gc.ca canadajebus@gmail.com) 7 | // 8 | // Credits: Modified from code create by Christopher Weiss (cmweiss@gmail.com) Copyright 2012 9 | // Adapted from the geomagc software and World Magnetic Model of the NOAA 10 | // Satellite and Information Service, National Geophysical Data Center 11 | // http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml 12 | // 13 | // This reusable widget allows the user to calculate magnetic declination at the point 14 | // of the mouse hover. 15 | // 16 | // on(mainMap, "load", function() { 17 | // //MagDec widget Example 18 | // var magDec = new MagDec({ 19 | // map: mainMap 20 | // }, "MagDecWindow"); 21 | // magDec.startup(); 22 | // }); 23 | // 24 | // Changes: 25 | // Version 1.2 26 | // -Minor tweak to size and warning text format. 27 | // Version 1.1 28 | // -Added .magDecWidget { white-space: nowrap; min-width: 300px; } to css file 29 | ///////////////////////////////////////////////////////////////////////////////////// 30 | ///////////////////////////////////////////////////////////////////////////////////// 31 | 32 | define([ 33 | "dijit/_WidgetBase", 34 | "dijit/_TemplatedMixin", 35 | "dijit/_WidgetsInTemplateMixin", 36 | 37 | "dojo/_base/declare", 38 | "dojo/_base/lang", 39 | "dojo/on", 40 | "require", 41 | 42 | "esri/geometry/webMercatorUtils", 43 | 44 | "dojo/text!./MagDec/WMM.html", 45 | 46 | "dojo/text!./MagDec/templates/MagDec.html", 47 | 48 | "dojo/domReady!" 49 | 50 | ], function(_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, 51 | declare, lang, on, require, 52 | webMercatorUtils, 53 | cof, 54 | dijitTemplate) 55 | { 56 | 57 | return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], { 58 | 59 | // Set the template .html file 60 | templateString: dijitTemplate, 61 | 62 | // Path to the templates .css file 63 | css_path: require.toUrl("./MagDec/css/MagDec.css"), 64 | 65 | // Vars 66 | cof: cof, 67 | wmm: null, 68 | 69 | maxord: null, 70 | tc: null, 71 | tc: null, 72 | sp: null, 73 | cp: null, 74 | pp: null, 75 | p: null, 76 | dp: null, 77 | a: null, 78 | b: null, 79 | re: null, 80 | a2: null, 81 | b2: null, 82 | c2: null, 83 | a4: null, 84 | b4: null, 85 | c4: null, 86 | c: null, 87 | cd: null, 88 | k: null, 89 | fn: null, 90 | fm: null, 91 | 92 | 93 | 94 | 95 | // The defaults 96 | defaults: { 97 | map: null, 98 | theme: "magDecWidget", 99 | autoUpdate: true 100 | }, 101 | 102 | 103 | // Called when the widget is declared as new object 104 | constructor: function(options) { 105 | // Mix in the given options with the defaults 106 | var properties = lang.mixin({}, this.defaults, options); 107 | this.set(properties); 108 | 109 | this.css = { 110 | valuesDiv: "valuesDiv", 111 | attribute: "attribute", 112 | value: "value", 113 | smallText: "smallText", 114 | warning: "warning" 115 | }; 116 | 117 | 118 | }, 119 | 120 | 121 | // Called after the widget is created 122 | postCreate: function() { 123 | this.inherited(arguments); 124 | 125 | }, 126 | 127 | 128 | // Called when the widget.startup() is used to view the widget 129 | startup: function() { 130 | this.inherited(arguments); 131 | 132 | // Calculate the model and initial math 133 | this.wmm = this._cof2Obj(this.cof) 134 | this._geoMagFactory(this.wmm); 135 | 136 | 137 | // Wire events for the mouse move 138 | var _this = this; 139 | on(this.map, "mouse-move", function (evt, $_this) { 140 | if (_this.autoUpdate) _this._mapMoveCalc(evt, _this); 141 | }); 142 | on(this.map, "mouse-drag", function (evt, $_this) { 143 | if (_this.autoUpdate) _this._mapMoveCalc(evt, _this); 144 | }); 145 | 146 | // Wire event for pausing updates with the keyboard 147 | on(this.map, "key-down", function (evt, $_this) { 148 | if (evt.keyCode == 32) 149 | { 150 | if (_this.autoUpdate) _this.autoUpdate = false; 151 | else _this.autoUpdate = true; 152 | } 153 | }); 154 | 155 | 156 | }, 157 | 158 | 159 | 160 | // Calculates geo mag on a map move event 161 | _mapMoveCalc: function(evt, _this) { 162 | 163 | // Get the map coordinates in lat/lon 164 | var mp = webMercatorUtils.webMercatorToGeographic(evt.mapPoint); 165 | 166 | // Get the inputs 167 | var latitude = mp.y.toFixed(5); 168 | var longitude = mp.x.toFixed(5); 169 | var altitude = 0; 170 | var time = new Date(); 171 | 172 | // Calculate geo mag 173 | var myGeoMag = _this.geoMag(latitude, longitude, altitude, time) 174 | 175 | // Calculate DMS values of the input lat lon 176 | var lat = this.DD2DMS(mp.y); 177 | var lon = this.DD2DMS(mp.x); 178 | var dec = this.DD2DMS(myGeoMag.dec); 179 | 180 | // Update the html 181 | _this.MagDecLatitude.innerHTML = latitude + " (" + lat.d + "°" + lat.m + "'" + lat.s + "\" " + lat.hemiLat + ")"; 182 | _this.MagDecLongitude.innerHTML = longitude + " (" + lon.d + "°" + lon.m + "'" + lon.s + "\" " + lon.hemiLon + ")"; 183 | 184 | var months = ["January","February","March","April","May","June","July","August","September","October","November","December"]; 185 | _this.MagDecDate.innerHTML = time.getDate() + " " + months[time.getMonth()] + " " + time.getFullYear(); 186 | 187 | _this.MagDecDeclination.innerHTML = Math.round(100*myGeoMag.dec)/100 + " (" + dec.d + "°" + dec.m + "' " + dec.hemiLon + ")"; 188 | _this.MagDecHorizontalIntensity.innerHTML = Math.round(myGeoMag.bh) + " nT"; 189 | 190 | // Warning message if required 191 | if (myGeoMag.bh < 5000 & myGeoMag.bh > 1000) 192 | { 193 | this.Warning.innerHTML = "Warning: The horizontal field strength at this
location is only " + Math.round(myGeoMag.bh) + " nT (Compass readings
have large uncertainties in areas where H is
smaller than 5000 nT)" 194 | } 195 | else if (myGeoMag.bh < 1000) 196 | { 197 | this.Warning.innerHTML = "Warning: The horizontal field strength at this
location is only " + Math.round(myGeoMag.bh) + " nT (Compass readings
have VERY LARGE uncertainties in areas where
H is smaller than 1000 nT)" 198 | } 199 | else 200 | { 201 | this.Warning.innerHTML = "
Press spacebar to pause/resume updating.

"; 202 | } 203 | 204 | 205 | }, 206 | 207 | 208 | // Calculates and returns DMS values from a Decimal Degree value 209 | DD2DMS: function(value) { 210 | 211 | var Abs = Math.abs(value) 212 | var D = Math.floor(Abs); 213 | var M = Math.floor( ( Abs - D ) * 60 ); 214 | var S = Math.floor( ( Abs - D - (M/60)) * 60 * 60 ); 215 | var H1 = "North"; 216 | if (value<0) H1 = "South"; 217 | var H2 = "East"; 218 | if (value<0) H2 = "West"; 219 | 220 | return {d: D, m: M, s: S, hemiLat: H1, hemiLon: H2} 221 | }, 222 | 223 | 224 | // Calculates initial math on the model 225 | _geoMagFactory: function(wmm) { 226 | 227 | var i, model, epoch = wmm.epoch, 228 | z = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 229 | this.maxord = 12; 230 | 231 | this.tc = [z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 232 | z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 233 | z.slice()]; 234 | this.sp = z.slice(); 235 | this.cp = z.slice(); 236 | this.pp = z.slice(); 237 | this.p = [z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 238 | z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 239 | z.slice()]; 240 | this.dp = [z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 241 | z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 242 | z.slice()]; 243 | 244 | this.a = 6378.137; 245 | this.b = 6356.7523142; 246 | this.re = 6371.2; 247 | this.a2 = this.a * this.a; 248 | this.b2 = this.b * this.b; 249 | this.c2 = this.a2 - this.b2; 250 | this.a4 = this.a2 * this.a2; 251 | this.b4 = this.b2 * this.b2; 252 | this.c4 = this.a4 - this.b4; 253 | 254 | this.c = [z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 255 | z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 256 | z.slice()]; 257 | this.cd = [z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 258 | z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 259 | z.slice()]; 260 | 261 | var n, m; 262 | var snorm = [z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 263 | z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 264 | z.slice(), z.slice()]; 265 | var j; 266 | this.k = [z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 267 | z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), z.slice(), 268 | z.slice()]; 269 | 270 | var flnmj; 271 | 272 | this.fn = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; 273 | this.fm = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; 274 | 275 | var D2; 276 | 277 | this.tc[0][0] = 0; 278 | this.sp[0] = 0.0; 279 | this.cp[0] = 1.0; 280 | this.pp[0] = 1.0; 281 | this.p[0][0] = 1; 282 | 283 | model = wmm.wmm; 284 | for (i in model) { 285 | if (model.hasOwnProperty(i)) { 286 | if (model[i].m <= model[i].n) { 287 | this.c[model[i].m][model[i].n] = model[i].gnm; 288 | this.cd[model[i].m][model[i].n] = model[i].dgnm; 289 | if (model[i].m !== 0) { 290 | this.c[model[i].n][model[i].m - 1] = model[i].hnm; 291 | this.cd[model[i].n][model[i].m - 1] = model[i].dhnm; 292 | } 293 | } 294 | } 295 | } 296 | wmm = null; 297 | model = null; 298 | 299 | /* CONVERT SCHMIDT NORMALIZED GAUSS COEFFICIENTS TO UNNORMALIZED */ 300 | snorm[0][0] = 1; 301 | 302 | for (n = 1; n <= this.maxord; n++) { 303 | snorm[0][n] = snorm[0][n - 1] * (2 * n - 1) / n; 304 | j = 2; 305 | 306 | for (m = 0, D2 = (n - m + 1); D2 > 0; D2--, m++) { 307 | this.k[m][n] = (((n - 1) * (n - 1)) - (m * m)) / 308 | ((2 * n - 1) * (2 * n - 3)); 309 | if (m > 0) { 310 | flnmj = ((n - m + 1) * j) / (n + m); 311 | snorm[m][n] = snorm[m - 1][n] * Math.sqrt(flnmj); 312 | j = 1; 313 | this.c[n][m - 1] = snorm[m][n] * this.c[n][m - 1]; 314 | this.cd[n][m - 1] = snorm[m][n] * this.cd[n][m - 1]; 315 | } 316 | this.c[m][n] = snorm[m][n] * this.c[m][n]; 317 | this.cd[m][n] = snorm[m][n] * this.cd[m][n]; 318 | } 319 | } 320 | this.k[1][1] = 0.0; 321 | }, 322 | 323 | 324 | // Calculates geo mag properties 325 | geoMag: function(glat, glon, h, date) { 326 | 327 | var alt = (h / 3280.8399) || 0, // convert h (in feet) to kilometers or set default of 0 328 | time = this._decimalDate(date), 329 | dt = time - this.wmm.epoch, 330 | rlat = this._deg2rad(glat), 331 | rlon = this._deg2rad(glon), 332 | srlon = Math.sin(rlon), 333 | srlat = Math.sin(rlat), 334 | crlon = Math.cos(rlon), 335 | crlat = Math.cos(rlat), 336 | srlat2 = srlat * srlat, 337 | crlat2 = crlat * crlat, 338 | q, 339 | q1, 340 | q2, 341 | ct, 342 | st, 343 | r2, 344 | r, 345 | d, 346 | ca, 347 | sa, 348 | aor, 349 | ar, 350 | br = 0.0, 351 | bt = 0.0, 352 | bp = 0.0, 353 | bpp = 0.0, 354 | par, 355 | temp1, 356 | temp2, 357 | parp, 358 | D4, 359 | bx, 360 | by, 361 | bz, 362 | bh, 363 | ti, 364 | dec, 365 | dip, 366 | gv; 367 | this.sp[1] = srlon; 368 | this.cp[1] = crlon; 369 | 370 | /* CONVERT FROM GEODETIC COORDS. TO SPHERICAL COORDS. */ 371 | q = Math.sqrt(this.a2 - this.c2 * srlat2); 372 | q1 = alt * q; 373 | q2 = ((q1 + this.a2) / (q1 + this.b2)) * ((q1 + this.a2) / (q1 + this.b2)); 374 | ct = srlat / Math.sqrt(q2 * crlat2 + srlat2); 375 | st = Math.sqrt(1.0 - (ct * ct)); 376 | r2 = (alt * alt) + 2.0 * q1 + (this.a4 - this.c4 * srlat2) / (q * q); 377 | r = Math.sqrt(r2); 378 | d = Math.sqrt(this.a2 * crlat2 + this.b2 * srlat2); 379 | ca = (alt + d) / r; 380 | sa = this.c2 * crlat * srlat / (r * d); 381 | 382 | for (m = 2; m <= this.maxord; m++) { 383 | this.sp[m] = this.sp[1] * this.cp[m - 1] + this.cp[1] * this.sp[m - 1]; 384 | this.cp[m] = this.cp[1] * this.cp[m - 1] - this.sp[1] * this.sp[m - 1]; 385 | } 386 | 387 | aor = this.re / r; 388 | ar = aor * aor; 389 | 390 | for (n = 1; n <= this.maxord; n++) { 391 | ar = ar * aor; 392 | for (m = 0, D4 = (n + m + 1); D4 > 0; D4--, m++) { 393 | 394 | /* 395 | COMPUTE UNNORMALIZED ASSOCIATED LEGENDRE POLYNOMIALS 396 | AND DERIVATIVES VIA RECURSION RELATIONS 397 | */ 398 | if (n === m) { 399 | this.p[m][n] = st * this.p[m - 1][n - 1]; 400 | this.dp[m][n] = st * this.dp[m - 1][n - 1] + ct * 401 | this.p[m - 1][n - 1]; 402 | } else if (n === 1 && m === 0) { 403 | this.p[m][n] = ct * this.p[m][n - 1]; 404 | this.dp[m][n] = ct * this.dp[m][n - 1] - st * this.p[m][n - 1]; 405 | } else if (n > 1 && n !== m) { 406 | if (m > n - 2) { this.p[m][n - 2] = 0; } 407 | if (m > n - 2) { this.dp[m][n - 2] = 0.0; } 408 | this.p[m][n] = ct * this.p[m][n - 1] - this.k[m][n] * this.p[m][n - 2]; 409 | this.dp[m][n] = ct * this.dp[m][n - 1] - st * this.p[m][n - 1] - 410 | this.k[m][n] * this.dp[m][n - 2]; 411 | } 412 | 413 | /* 414 | TIME ADJUST THE GAUSS COEFFICIENTS 415 | */ 416 | 417 | this.tc[m][n] = this.c[m][n] + dt * this.cd[m][n]; 418 | if (m !== 0) { 419 | this.tc[n][m - 1] = this.c[n][m - 1] + dt * this.cd[n][m - 1]; 420 | } 421 | 422 | /* 423 | ACCUMULATE TERMS OF THE SPHERICAL HARMONIC EXPANSIONS 424 | */ 425 | par = ar * this.p[m][n]; 426 | if (m === 0) { 427 | temp1 = this.tc[m][n] * this.cp[m]; 428 | temp2 = this.tc[m][n] * this.sp[m]; 429 | } else { 430 | temp1 = this.tc[m][n] * this.cp[m] + this.tc[n][m - 1] * this.sp[m]; 431 | temp2 = this.tc[m][n] * this.sp[m] - this.tc[n][m - 1] * this.cp[m]; 432 | } 433 | bt = bt - ar * temp1 * this.dp[m][n]; 434 | bp += (this.fm[m] * temp2 * par); 435 | br += (this.fn[n] * temp1 * par); 436 | /* 437 | SPECIAL CASE: NORTH/SOUTH GEOGRAPHIC POLES 438 | */ 439 | if (st === 0.0 && m === 1) { 440 | if (n === 1) { 441 | pp[n] = pp[n - 1]; 442 | } else { 443 | pp[n] = ct * pp[n - 1] - k[m][n] * pp[n - 2]; 444 | } 445 | parp = ar * pp[n]; 446 | bpp += (this.fm[m] * temp2 * parp); 447 | } 448 | } 449 | } 450 | 451 | bp = (st === 0.0 ? bpp : bp / st); 452 | /* 453 | ROTATE MAGNETIC VECTOR COMPONENTS FROM SPHERICAL TO 454 | GEODETIC COORDINATES 455 | */ 456 | bx = -bt * ca - br * sa; 457 | by = bp; 458 | bz = bt * sa - br * ca; 459 | 460 | /* 461 | COMPUTE DECLINATION (DEC), INCLINATION (DIP) AND 462 | TOTAL INTENSITY (TI) 463 | */ 464 | bh = Math.sqrt((bx * bx) + (by * by)); 465 | ti = Math.sqrt((bh * bh) + (bz * bz)); 466 | dec = this._rad2deg(Math.atan2(by, bx)); 467 | dip = this._rad2deg(Math.atan2(bz, bh)); 468 | 469 | /* 470 | COMPUTE MAGNETIC GRID VARIATION IF THE CURRENT 471 | GEODETIC POSITION IS IN THE ARCTIC OR ANTARCTIC 472 | (I.E. GLAT > +55 DEGREES OR GLAT < -55 DEGREES) 473 | OTHERWISE, SET MAGNETIC GRID VARIATION TO -999.0 474 | */ 475 | 476 | if (Math.abs(glat) >= 55.0) { 477 | if (glat > 0.0 && glon >= 0.0) { 478 | gv = dec - glon; 479 | } else if (glat > 0.0 && glon < 0.0) { 480 | gv = dec + Math.abs(glon); 481 | } else if (glat < 0.0 && glon >= 0.0) { 482 | gv = dec + glon; 483 | } else if (glat < 0.0 && glon < 0.0) { 484 | gv = dec - Math.abs(glon); 485 | } 486 | if (gv > 180.0) { 487 | gv -= 360.0; 488 | } else if (gv < -180.0) { gv += 360.0; } 489 | } 490 | 491 | return {dec: dec, dip: dip, ti: ti, bh: bh, bx: bx, by: by, bz: bz, lat: glat, lon: glon, gv: gv, epoch: this.wmm.epoch}; 492 | }, 493 | 494 | 495 | // Converts a date to decimal years 496 | _decimalDate: function(date) { 497 | date = date || new Date(); 498 | var year = date.getFullYear(), 499 | daysInYear = 365 + 500 | (((year % 400 === 0) || (year % 4 === 0 && (year % 100 > 0))) ? 1 : 0), 501 | msInYear = daysInYear * 24 * 60 * 60 * 1000; 502 | 503 | return date.getFullYear() + (date.valueOf() - (new Date(year, 0)).valueOf()) / msInYear; 504 | }, 505 | // Radian to degrees 506 | _rad2deg: function(rad) { 507 | return rad * (180 / Math.PI); 508 | }, 509 | // Degrees to radians 510 | _deg2rad: function(deg) { 511 | return deg * (Math.PI / 180); 512 | }, 513 | 514 | 515 | // Converts the WMM.COF text to a JSON object usable by geoMagFactory() 516 | _cof2Obj: function(cof) { 517 | 'use strict'; 518 | var modelLines = cof.split('\n'), 519 | wmm = [], 520 | i, vals, epoch, model, modelDate; 521 | for (i in modelLines) { 522 | if (modelLines.hasOwnProperty(i)) { 523 | vals = modelLines[i].replace(/^\s+|\s+$/g, "").split(/\s+/); 524 | if (vals.length === 3) { 525 | epoch = parseFloat(vals[0]); 526 | model = vals[1]; 527 | modelDate = vals[2]; 528 | } else if (vals.length === 6) { 529 | wmm.push({ 530 | n: parseInt(vals[0], 10), 531 | m: parseInt(vals[1], 10), 532 | gnm: parseFloat(vals[2]), 533 | hnm: parseFloat(vals[3]), 534 | dgnm: parseFloat(vals[4]), 535 | dhnm: parseFloat(vals[5]) 536 | }); 537 | } 538 | } 539 | } 540 | 541 | return {epoch: epoch, model: model, modelDate: modelDate, wmm: wmm}; 542 | } 543 | 544 | 545 | }); 546 | 547 | }); --------------------------------------------------------------------------------