/// <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);
        }
Exemple #3
0
        /// <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);
        }
Exemple #4
0
        /// <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);
        }
Exemple #5
0
        /// <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> &lt; 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> &lt; 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);
        }
Exemple #9
0
        /// <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"/> &lt; 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);
                }
            }
        }
Exemple #11
0
 /// <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)
 {
 }
Exemple #12
0
 /// <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);
 }