/** * \file MagneticModel.cpp * \brief Implementation for GeographicLib::MagneticModel class * * Copyright (c) Charles Karney (2011-2021) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #include #include #include #include #include #if !defined(GEOGRAPHICLIB_DATA) # if defined(_WIN32) # define GEOGRAPHICLIB_DATA "C:/ProgramData/GeographicLib" # else # define GEOGRAPHICLIB_DATA "/usr/local/share/GeographicLib" # endif #endif #if !defined(GEOGRAPHICLIB_MAGNETIC_DEFAULT_NAME) # define GEOGRAPHICLIB_MAGNETIC_DEFAULT_NAME "wmm2020" #endif #if defined(_MSC_VER) // Squelch warnings about unsafe use of getenv # pragma warning (disable: 4996) #endif namespace GeographicLib { using namespace std; MagneticModel::MagneticModel(const std::string& name, const std::string& path, const Geocentric& earth, int Nmax, int Mmax) : _name(name) , _dir(path) , _description("NONE") , _date("UNKNOWN") , _t0(Math::NaN()) , _dt0(1) , _tmin(Math::NaN()) , _tmax(Math::NaN()) , _a(Math::NaN()) , _hmin(Math::NaN()) , _hmax(Math::NaN()) , _nNmodels(1) , _nNconstants(0) , _nmx(-1) , _mmx(-1) , _norm(SphericalHarmonic::SCHMIDT) , _earth(earth) { if (_dir.empty()) _dir = DefaultMagneticPath(); bool truncate = Nmax >= 0 || Mmax >= 0; if (truncate) { if (Nmax >= 0 && Mmax < 0) Mmax = Nmax; if (Nmax < 0) Nmax = numeric_limits::max(); if (Mmax < 0) Mmax = numeric_limits::max(); } ReadMetadata(_name); _gG.resize(_nNmodels + 1 + _nNconstants); _hH.resize(_nNmodels + 1 + _nNconstants); { string coeff = _filename + ".cof"; ifstream coeffstr(coeff.c_str(), ios::binary); if (!coeffstr.good()) throw GeographicErr("Error opening " + coeff); char id[idlength_ + 1]; coeffstr.read(id, idlength_); if (!coeffstr.good()) throw GeographicErr("No header in " + coeff); id[idlength_] = '\0'; if (_id != string(id)) throw GeographicErr("ID mismatch: " + _id + " vs " + id); for (int i = 0; i < _nNmodels + 1 + _nNconstants; ++i) { int N, M; if (truncate) { N = Nmax; M = Mmax; } SphericalEngine::coeff::readcoeffs(coeffstr, N, M, _gG[i], _hH[i], truncate); if (!(M < 0 || _gG[i][0] == 0)) throw GeographicErr("A degree 0 term is not permitted"); _harm.push_back(SphericalHarmonic(_gG[i], _hH[i], N, N, M, _a, _norm)); _nmx = max(_nmx, _harm.back().Coefficients().nmx()); _mmx = max(_mmx, _harm.back().Coefficients().mmx()); } int pos = int(coeffstr.tellg()); coeffstr.seekg(0, ios::end); if (pos != coeffstr.tellg()) throw GeographicErr("Extra data in " + coeff); } } void MagneticModel::ReadMetadata(const string& name) { const char* spaces = " \t\n\v\f\r"; _filename = _dir + "/" + name + ".wmm"; ifstream metastr(_filename.c_str()); if (!metastr.good()) throw GeographicErr("Cannot open " + _filename); string line; getline(metastr, line); if (!(line.size() >= 6 && line.substr(0,5) == "WMMF-")) throw GeographicErr(_filename + " does not contain WMMF-n signature"); string::size_type n = line.find_first_of(spaces, 5); if (n != string::npos) n -= 5; string version(line, 5, n); if (!(version == "1" || version == "2")) throw GeographicErr("Unknown version in " + _filename + ": " + version); string key, val; while (getline(metastr, line)) { if (!Utility::ParseLine(line, key, val)) continue; // Process key words if (key == "Name") _name = val; else if (key == "Description") _description = val; else if (key == "ReleaseDate") _date = val; else if (key == "Radius") _a = Utility::val(val); else if (key == "Type") { if (!(val == "Linear" || val == "linear")) throw GeographicErr("Only linear models are supported"); } else if (key == "Epoch") _t0 = Utility::val(val); else if (key == "DeltaEpoch") _dt0 = Utility::val(val); else if (key == "NumModels") _nNmodels = Utility::val(val); else if (key == "NumConstants") _nNconstants = Utility::val(val); else if (key == "MinTime") _tmin = Utility::val(val); else if (key == "MaxTime") _tmax = Utility::val(val); else if (key == "MinHeight") _hmin = Utility::val(val); else if (key == "MaxHeight") _hmax = Utility::val(val); else if (key == "Normalization") { if (val == "FULL" || val == "Full" || val == "full") _norm = SphericalHarmonic::FULL; else if (val == "SCHMIDT" || val == "Schmidt" || val == "schmidt") _norm = SphericalHarmonic::SCHMIDT; else throw GeographicErr("Unknown normalization " + val); } else if (key == "ByteOrder") { if (val == "Big" || val == "big") throw GeographicErr("Only little-endian ordering is supported"); else if (!(val == "Little" || val == "little")) throw GeographicErr("Unknown byte ordering " + val); } else if (key == "ID") _id = val; // else unrecognized keywords are skipped } // Check values if (!(isfinite(_a) && _a > 0)) throw GeographicErr("Reference radius must be positive"); if (!(_t0 > 0)) throw GeographicErr("Epoch time not defined"); if (_tmin >= _tmax) throw GeographicErr("Min time exceeds max time"); if (_hmin >= _hmax) throw GeographicErr("Min height exceeds max height"); if (int(_id.size()) != idlength_) throw GeographicErr("Invalid ID"); if (_nNmodels < 1) throw GeographicErr("NumModels must be positive"); if (!(_nNconstants == 0 || _nNconstants == 1)) throw GeographicErr("NumConstants must be 0 or 1"); if (!(_dt0 > 0)) { if (_nNmodels > 1) throw GeographicErr("DeltaEpoch must be positive"); else _dt0 = 1; } } void MagneticModel::FieldGeocentric(real t, real X, real Y, real Z, real& BX, real& BY, real& BZ, real& BXt, real& BYt, real& BZt) const { t -= _t0; int n = max(min(int(floor(t / _dt0)), _nNmodels - 1), 0); bool interpolate = n + 1 < _nNmodels; t -= n * _dt0; // Components in geocentric basis // initial values to suppress warning real BXc = 0, BYc = 0, BZc = 0; _harm[n](X, Y, Z, BX, BY, BZ); _harm[n + 1](X, Y, Z, BXt, BYt, BZt); if (_nNconstants) _harm[_nNmodels + 1](X, Y, Z, BXc, BYc, BZc); if (interpolate) { // Convert to a time derivative BXt = (BXt - BX) / _dt0; BYt = (BYt - BY) / _dt0; BZt = (BZt - BZ) / _dt0; } BX += t * BXt + BXc; BY += t * BYt + BYc; BZ += t * BZt + BZc; BXt = BXt * - _a; BYt = BYt * - _a; BZt = BZt * - _a; BX *= - _a; BY *= - _a; BZ *= - _a; } void MagneticModel::Field(real t, real lat, real lon, real h, bool diffp, real& Bx, real& By, real& Bz, real& Bxt, real& Byt, real& Bzt) const { real X, Y, Z; real M[Geocentric::dim2_]; _earth.IntForward(lat, lon, h, X, Y, Z, M); // Components in geocentric basis // initial values to suppress warning real BX = 0, BY = 0, BZ = 0, BXt = 0, BYt = 0, BZt = 0; FieldGeocentric(t, X, Y, Z, BX, BY, BZ, BXt, BYt, BZt); if (diffp) Geocentric::Unrotate(M, BXt, BYt, BZt, Bxt, Byt, Bzt); Geocentric::Unrotate(M, BX, BY, BZ, Bx, By, Bz); } MagneticCircle MagneticModel::Circle(real t, real lat, real h) const { real t1 = t - _t0; int n = max(min(int(floor(t1 / _dt0)), _nNmodels - 1), 0); bool interpolate = n + 1 < _nNmodels; t1 -= n * _dt0; real X, Y, Z, M[Geocentric::dim2_]; _earth.IntForward(lat, 0, h, X, Y, Z, M); // Y = 0, cphi = M[7], sphi = M[8]; return (_nNconstants == 0 ? MagneticCircle(_a, _earth._f, lat, h, t, M[7], M[8], t1, _dt0, interpolate, _harm[n].Circle(X, Z, true), _harm[n + 1].Circle(X, Z, true)) : MagneticCircle(_a, _earth._f, lat, h, t, M[7], M[8], t1, _dt0, interpolate, _harm[n].Circle(X, Z, true), _harm[n + 1].Circle(X, Z, true), _harm[_nNmodels + 1].Circle(X, Z, true))); } void MagneticModel::FieldComponents(real Bx, real By, real Bz, real Bxt, real Byt, real Bzt, real& H, real& F, real& D, real& I, real& Ht, real& Ft, real& Dt, real& It) { H = hypot(Bx, By); Ht = H != 0 ? (Bx * Bxt + By * Byt) / H : hypot(Bxt, Byt); D = H != 0 ? Math::atan2d(Bx, By) : Math::atan2d(Bxt, Byt); Dt = (H != 0 ? (By * Bxt - Bx * Byt) / Math::sq(H) : 0) / Math::degree(); F = hypot(H, Bz); Ft = F != 0 ? (H * Ht + Bz * Bzt) / F : hypot(Ht, Bzt); I = F != 0 ? Math::atan2d(-Bz, H) : Math::atan2d(-Bzt, Ht); It = (F != 0 ? (Bz * Ht - H * Bzt) / Math::sq(F) : 0) / Math::degree(); } string MagneticModel::DefaultMagneticPath() { string path; char* magneticpath = getenv("GEOGRAPHICLIB_MAGNETIC_PATH"); if (magneticpath) path = string(magneticpath); if (!path.empty()) return path; char* datapath = getenv("GEOGRAPHICLIB_DATA"); if (datapath) path = string(datapath); return (!path.empty() ? path : string(GEOGRAPHICLIB_DATA)) + "/magnetic"; } string MagneticModel::DefaultMagneticName() { string name; char* magneticname = getenv("GEOGRAPHICLIB_MAGNETIC_NAME"); if (magneticname) name = string(magneticname); return !name.empty() ? name : string(GEOGRAPHICLIB_MAGNETIC_DEFAULT_NAME); } } // namespace GeographicLib