/// <summary> /// Evaluate the gravity at an arbitrary point above (or below) the ellipsoid. /// </summary> /// <param name="lat">the geographic latitude (degrees).</param> /// <param name="lon">the geographic longitude (degrees).</param> /// <param name="h">the height above the ellipsoid (meters).</param> /// <returns> /// <list type="bullet"> /// <item><i>W</i>, the sum of the gravitational and centrifugal potentials (m^2 s^−2).</item> /// <item><i>gx</i>, the easterly component of the acceleration (m s^−2).</item> /// <item><i>gy</i>, the northerly component of the acceleration (m s^−2).</item> /// <item><i>gz</i>, the upward component of the acceleration (m s^−2); this is usually negative.</item> /// </list> /// </returns> /// <remarks> /// The function includes the effects of the earth's rotation. /// </remarks> public (double W, double gx, double gy, double gz) Gravity(double lat, double lon, double h) { Span <double> M = stackalloc double[Geocentric.dim2_]; var(X, Y, Z) = _earth.Earth.IntForward(lat, lon, h, M); var(Wres, gx, gy, gz) = W(X, Y, Z); Geocentric.Unrotate(M, gx, gy, gz, out gx, out gy, out gz); return(Wres, gx, gy, gz); }
/// <summary> /// Evaluate the gravity disturbance vector at an arbitrary point above (or below) the ellipsoid. /// </summary> /// <param name="lat">the geographic latitude (degrees).</param> /// <param name="lon">the geographic longitude (degrees).</param> /// <param name="h">the height above the ellipsoid (meters).</param> /// <returns> /// <list type="bullet"> /// <item><i>T</i>, the corresponding disturbing potential (m2 s−2).</item> /// <item><i>deltax</i>, the easterly component of the disturbance vector (m s^−2).</item> /// <item><i>deltay</i>, the northerly component of the disturbance vector (m s^−2).</item> /// <item><i>deltaz</i>, the upward component of the disturbance vector (m s^−2).</item> /// </list> /// </returns> public (double T, double deltax, double deltay, double deltaz) Disturbance(double lat, double lon, double h) { Span <double> M = stackalloc double[Geocentric.dim2_]; var(X, Y, Z) = _earth.Earth.IntForward(lat, lon, h, M); var Tres = InternalT(X, Y, Z, out var deltax, out var deltay, out var deltaz, true, true); Geocentric.Unrotate(M, deltax, deltay, deltaz, out deltax, out deltay, out deltaz); return(Tres, deltax, deltay, deltaz); }
/// <summary> /// Evaluate the gravity. /// </summary> /// <param name="lon">the geographic longitude (degrees).</param> /// <returns> /// <list type="bullet"> /// <item><i>W</i>, the sum of the gravitational and centrifugal potentials (m^2 s^−2).</item> /// <item><i>gx</i>, the easterly component of the acceleration (m s^−2).</item> /// <item><i>gy</i>, the northerly component of the acceleration (m s^−2).</item> /// <item><i>gz</i>, the upward component of the acceleration (m s^−2); this is usually negative.</item> /// </list> /// </returns> /// <remarks>The function includes the effects of the earth's rotation.</remarks> public (double W, double gx, double gy, double gz) Gravity(double lon) { Span <double> M = stackalloc double[Geocentric.dim2_]; SinCosd(lon, out var slam, out var clam); var(Wres, gx, gy, gz) = W(slam, clam); Geocentric.Rotation(_sphi, _cphi, slam, clam, M); Geocentric.Unrotate(M, gx, gy, gz, out gx, out gy, out gz); return(Wres, gx, gy, gz); }
/// <summary> /// Evaluate the gravity disturbance vector. /// </summary> /// <param name="lon">the geographic longitude (degrees).</param> /// <returns> /// <list type="bullet"> /// <item><i>T</i>, the corresponding disturbing potential (m2 s−2).</item> /// <item><i>deltax</i>, the easterly component of the disturbance vector (m s^−2).</item> /// <item><i>deltay</i>, the northerly component of the disturbance vector (m s^−2).</item> /// <item><i>deltaz</i>, the upward component of the disturbance vector (m s^−2).</item> /// </list> /// </returns> public (double T, double deltax, double deltay, double deltaz) Disturbance(double lon) { Span <double> M = stackalloc double[Geocentric.dim2_]; SinCosd(lon, out var slam, out var clam); var Tres = InternalT(slam, clam, out var deltax, out var deltay, out var deltaz, true, true); Geocentric.Rotation(_sphi, _cphi, slam, clam, M); Geocentric.Unrotate(M, deltax, deltay, deltaz, out deltax, out deltay, out deltaz); return(Tres, deltax, deltay, deltaz); }
/// <summary> /// Reset the origin. /// </summary> /// <param name="lat0">latitude at origin (degrees).</param> /// <param name="lon0">longitude at origin (degrees).</param> /// <param name="h0">height above ellipsoid at origin (meters); default 0.</param> /// <remarks> /// <paramref name="lat0"/> should be in the range [−90°, 90°]. /// </remarks> public void Reset(double lat0, double lon0, double h0 = 0) { _lat0 = LatFix(lat0); _lon0 = AngNormalize(lon0); _h0 = h0; (_x0, _y0, _z0) = _earth.Forward(_lat0, _lon0, _h0); SinCosd(_lat0, out var sphi, out var cphi); SinCosd(_lon0, out var slam, out var clam); Geocentric.Rotation(sphi, cphi, slam, clam, _r.Span); }
/// <summary> /// Constructor for the normal gravity. /// </summary> /// <param name="a">equatorial radius (meters).</param> /// <param name="GM">mass constant of the ellipsoid (meters^3/seconds^2); /// this is the product of <i>G</i> the gravitational constant and <i>M</i> the mass of the earth /// (usually including the mass of the earth's atmosphere).</param> /// <param name="omega">the angular velocity (rad s^−1).</param> /// <param name="f_J2">either the flattening of the ellipsoid <i>f</i> or the the dynamical form factor <i>J2</i>.</param> /// <param name="geometricp"> /// if <see langword="true"/> (the default), then <paramref name="f_J2"/> denotes the flattening, /// else it denotes the dynamical form factor <i>J2</i>. /// </param> /// <remarks> /// The shape of the ellipsoid can be given in one of two ways: /// <list type="bullet"> /// <item>geometrically (<paramref name="geometricp"/> = <see langword="true"/>), the ellipsoid is defined by /// the flattening <i>f</i> = (<i>a</i> − <i>b</i>) / <i>a</i>, /// where <i>a</i> and <i>b</i> are the equatorial radius and the polar semi-axis. /// The parameters should obey <i>a</i> > 0, <i>f</i> < 1. /// There are no restrictions on <paramref name="GM"/> or <paramref name="omega"/>, in particular, <paramref name="GM"/> need not be positive.</item> /// <item>physically (<paramref name="geometricp"/> = <see langword="false"/>), /// the ellipsoid is defined by the dynamical form factor <i>J2</i> = (<i>C</i> − <i>A</i>) / <i>Ma</i>^2, /// where <i>A</i> and <i>C</i> are the equatorial and polar moments of inertia and <i>M</i> is the mass of the earth. /// The parameters should obey <paramref name="a"/> > 0, /// <paramref name="GM"/> > 0 and <i>J2</i> < 1/3 − (<paramref name="omega"/>^2 <paramref name="a"/>^3/<paramref name="GM"/>) 8/(45π). /// There is no restriction on omega.</item> /// </list> /// </remarks> public NormalGravity(double a, double GM, double omega, double f_J2, bool geometricp = true) { _a = a; if (!(IsFinite(_a) && _a > 0)) { throw new GeographicException("Equatorial radius is not positive"); } _GM = GM; if (!IsFinite(_GM)) { throw new GeographicException("Gravitational constant is not finite"); } _omega = omega; _omega2 = Sq(_omega); _aomega2 = Sq(_omega * _a); if (!(IsFinite(_omega2) && IsFinite(_aomega2))) { throw new GeographicException("Rotation velocity is not finite"); } _f = geometricp ? f_J2 : J2ToFlattening(_a, _GM, _omega, f_J2); _b = _a * (1 - _f); if (!(IsFinite(_b) && _b > 0)) { throw new GeographicException("Polar semi-axis is not positive"); } _J2 = geometricp ? FlatteningToJ2(_a, _GM, _omega, f_J2) : f_J2; _e2 = _f * (2 - _f); _ep2 = _e2 / (1 - _e2); var ex2 = _f < 0 ? -_e2 : _ep2; _Q0 = Qf(ex2, _f < 0); _earth = new Geocentric(_a, _f); _E = _a * Sqrt(Abs(_e2)); // H+M, Eq 2-54 // H+M, Eq 2-61 _U0 = _GM * Atanzz(ex2, _f < 0) / _b + _aomega2 / 3; var P = Hf(ex2, _f < 0) / (6 * _Q0); // H+M, Eq 2-73 _gammae = _GM / (_a * _b) - (1 + P) * _a * _omega2; // H+M, Eq 2-74 _gammap = _GM / (_a * _a) + 2 * P * _b * _omega2; // k = gammae * (b * gammap / (a * gammae) - 1) // = (b * gammap - a * gammae) / a _k = -_e2 * _GM / (_a * _b) + _omega2 * (P * (_a + 2 * _b * (1 - _f)) + _a); // f* = (gammap - gammae) / gammae _fstar = (-_f * _GM / (_a * _b) + _omega2 * (P * (_a + 2 * _b) + _a)) / _gammae; }
private (double Bx, double By, double Bz) Field(double t, double lat, double lon, double h, bool diffp, out double Bxt, out double Byt, out double Bzt) { Bxt = Byt = Bzt = default; Span <double> M = stackalloc double[Geocentric.dim2_]; var(X, Y, Z) = _earth.IntForward(lat, lon, h, M); // Components in geocentric basis // initial values to suppress warning var(BX, BY, BZ) = FieldGeocentric(t, X, Y, Z, out var BXt, out var BYt, out var BZt); if (diffp) { Geocentric.Unrotate(M, BXt, BYt, BZt, out Bxt, out Byt, out Bzt); } Geocentric.Unrotate(M, BX, BY, BZ, out var Bx, out var By, out var Bz); return(Bx, By, Bz); }
private (double Bx, double By, double Bz) Field(double lon, bool diffp, out double Bxt, out double Byt, out double Bzt) { Bxt = Byt = Bzt = default; SinCosd(lon, out var slam, out var clam); Span <double> M = stackalloc double[Geocentric.dim2_]; Geocentric.Rotation(_sphi, _cphi, slam, clam, M); var(BX, BY, BZ) = FieldGeocentric(slam, clam, out var BXt, out var BYt, out var BZt); if (diffp) { Geocentric.Unrotate(M, BXt, BYt, BZt, out Bxt, out Byt, out Bzt); } Geocentric.Unrotate(M, BX, BY, BZ, out var Bx, out var By, out var Bz); return(Bx, By, Bz); }
/// <summary> /// Evaluate the components of the gravity anomaly vector using the spherical approximation. /// </summary> /// <param name="lon">the geographic longitude (degrees).</param> w /// <returns> /// <list type="bullet"> /// <item><i>Dg01</i>, the gravity anomaly (m s^−2).</item> /// <item><i>xi</i>, the northerly component of the deflection of the vertical (degrees).</item> /// <item><i>eta</i>, the easterly component of the deflection of the vertical (degrees).</item> /// </list> /// </returns> /// <remarks> /// The spherical approximation (see Heiskanen and Moritz, Sec 2-14) is used so that the results of the NGA codes are /// reproduced accurately. approximations used here. /// Details are given in /// <a href="https://geographiclib.sourceforge.io/html/gravity.html#gravitygeoid">Details of the geoid height and anomaly calculations</a>. /// </remarks> public (double Dg01, double xi, double eta) SphericalAnomaly(double lon) { if (_caps.HasFlag(GravityFlags.SphericalAnomaly)) { return(double.NaN, double.NaN, double.NaN); } SinCosd(lon, out var slam, out var clam); double deltax, deltay, deltaz, T = InternalT(slam, clam, out deltax, out deltay, out deltaz, true, false); // Rotate cartesian into spherical coordinates Span <double> MC = stackalloc double[Geocentric.dim2_]; Geocentric.Rotation(_spsi, _cpsi, slam, clam, MC); Geocentric.Unrotate(MC, deltax, deltay, deltaz, out deltax, out deltay, out deltaz); // H+M, Eq 2-151c var Dg01 = -deltaz - 2 * T * _invR; var xi = -(deltay / _gamma) / Degree; var eta = -(deltax / _gamma) / Degree; return(Dg01, xi, eta); }
/// <summary> /// Construct a magnetic model. /// </summary> /// <param name="name">the name of the model</param> /// <param name="path">(optional) directory for data file.</param> /// <param name="earth">(optional) <see cref="Geocentric"/> object for converting coordinates; default <see cref="Geocentric.WGS84"/>.</param> /// <param name="Nmax">(optional) if non-negative, truncate the degree of the model this value.</param> /// <param name="Mmax">(optional) if non-negative, truncate the order of the model this value.</param> /// <remarks> /// A filename is formed by appending ".wmm" (World Magnetic Model) to the name. /// If path is specified (and is non-empty), then the file is loaded from directory, <paramref name="path"/>. /// Otherwise the path is given by the <see cref="DefaultMagneticPath"/>. /// <para> /// This file contains the metadata which specifies the properties of the model. /// The coefficients for the spherical harmonic sums are obtained from a file obtained by appending ".cof" to metadata file (so the filename ends in ".wwm.cof"). /// </para> /// <para> /// The model is not tied to a particular ellipsoidal model of the earth. /// The final earth argument to the constructor specifies an ellipsoid to allow geodetic coordinates to the /// transformed into the spherical coordinates used in the spherical harmonic sum. /// </para> /// <para> /// If <paramref name="Nmax"/> ≥ 0 and <paramref name="Mmax"/> < 0, then <paramref name="Mmax"/> is set to <paramref name="Nmax"/>. /// After the model is loaded, the maximum degree and order of the model can be found by the <see cref="Degree"/> and <see cref="Order"/> methods. /// </para> /// </remarks> public MagneticModel(string name, string path = "", Geocentric earth = null, int Nmax = -1, int Mmax = -1) { earth = earth ?? Geocentric.WGS84; _name = name; _dir = path; _description = "NONE"; _t0 = double.NaN; _dt0 = 1; _tmin = double.NaN; _tmax = double.NaN; _a = double.NaN; _hmin = double.NaN; _hmax = double.NaN; _Nmodels = 1; _Nconstants = 0; _nmx = -1; _mmx = -1; _norm = Normalization.Schmidt; _earth = earth; if (string.IsNullOrEmpty(path)) { _dir = DefaultMagneticPath; } bool truncate = Nmax >= 0 || Mmax >= 0; if (truncate) { if (Nmax >= 0 && Mmax < 0) { Mmax = Nmax; } if (Nmax < 0) { Nmax = int.MaxValue; } if (Mmax < 0) { Mmax = int.MaxValue; } } ReadMetadata(_name, ref _filename, ref _id, ref _name, ref _description, ref _date, ref _a, ref _t0, ref _dt0, ref _tmin, ref _tmax, ref _hmin, ref _hmax, ref _Nmodels, ref _Nconstants, ref _norm); string coeff = _filename + ".cof"; using (var stream = File.OpenRead(coeff)) { Span <byte> id = stackalloc byte[idlength_]; if (stream.Read(id) != idlength_) { throw new GeographicException("No header in " + coeff); } if (MemoryMarshal.Cast <char, byte>(_id.AsSpan()).SequenceEqual(id)) { throw new GeographicException($"ID mismatch: {_id} vs {Encoding.ASCII.GetString(id.ToArray())}"); } for (int i = 0; i < _Nmodels + 1 + _Nconstants; ++i) { int N = 0, M = 0; if (truncate) { N = Nmax; M = Mmax; } var c = SphericalEngine.Coeff.FromStream(stream, ref N, ref M, truncate); if (!(M < 0 || c.Cv(0) == 0)) { throw new GeographicException("A degree 0 term is not permitted"); } var sh = new SphericalHarmonic(c, _a, _norm); _harm.Add(sh); _nmx = Max(_nmx, sh.Coefficients.Nmx); _mmx = Max(_mmx, sh.Coefficients.Mmx); } if (stream.Position != stream.Length) { throw new GeographicException("Extra data in " + coeff); } } }
/// <summary> /// Initialize a new <see cref="LocalCartesian"/> instance. /// </summary> /// <param name="earth"><see cref="Geocentric"/> object for the transformation; default <see cref="Geocentric.WGS84"/>.</param> public LocalCartesian(Geocentric earth = null) : this(0, 0, earth : earth) { }
/// <summary> /// Initialize a new <see cref="LocalCartesian"/> instance with specified origin. /// </summary> /// <param name="lat0">latitude at origin (degrees).</param> /// <param name="lon0">longitude at origin (degrees).</param> /// <param name="h0">height above ellipsoid at origin (meters); default 0.</param> /// <param name="earth"><see cref="Geocentric"/> object for the transformation; default <see cref="Geocentric.WGS84"/>.</param> /// <remarks> /// <paramref name="lat0"/> should be in the range [−90°, 90°]. /// </remarks> public LocalCartesian(double lat0, double lon0, double h0 = 0, Geocentric earth = null) { _earth = earth ?? Geocentric.WGS84; Reset(lat0, lon0, h0); }