diff --git a/IO.Astrodynamics.Tests/APITest.cs b/IO.Astrodynamics.Tests/APITest.cs index 3278807..8c7ff53 100644 --- a/IO.Astrodynamics.Tests/APITest.cs +++ b/IO.Astrodynamics.Tests/APITest.cs @@ -259,7 +259,7 @@ public void PropagateScenario2() Assert.Equal("2021-03-04T04:18:01.4656137 (TDB)", maneuver.ManeuverWindow.EndDate.ToFormattedString()); Assert.Equal("2021-03-04T04:18:00.5620061 (TDB)", maneuver.ThrustWindow.StartDate.ToFormattedString()); Assert.Equal("2021-03-04T04:18:01.4656137 (TDB)", maneuver.ThrustWindow.EndDate.ToFormattedString()); - Assert.Equal(0.90360759999999996, maneuver.ThrustWindow.Length.TotalSeconds,3); + Assert.Equal(0.90360759999999996, maneuver.ThrustWindow.Length.TotalSeconds, 3); Assert.Equal(new Vector3(0.6113331463337078, 10.731242700644234, 16.97261190194155), ((ImpulseManeuver)maneuver).DeltaV); Assert.Equal(45.180377988723926, maneuver.FuelBurned); } @@ -663,6 +663,29 @@ void GetCelestialBodyInformation() Assert.Equal(6378136.5999999998, res.Radii.X); Assert.Equal(6378136.5999999998, res.Radii.Y); Assert.Equal(6356751.9000000002, res.Radii.Z); + Assert.Equal(0.001082616, res.J2); + Assert.Equal(-2.5388099999999996E-06, res.J3); + Assert.Equal(-1.6559699999999999E-06, res.J4); + } + + [Fact] + void GetCelestialBodyInformationWithoutJ() + { + //Read celestial celestialItem information from spice kernels + var res = API.Instance.GetCelestialBodyInfo(TestHelpers.MoonAtJ2000.NaifId); + Assert.Equal(PlanetsAndMoons.MOON.NaifId, res.Id); + Assert.Equal(PlanetsAndMoons.EARTH.NaifId, res.CenterOfMotionId); + Assert.Equal(Barycenters.EARTH_BARYCENTER.NaifId, res.BarycenterOfMotionId); + Assert.Equal(PlanetsAndMoons.MOON.Name, res.Name); + Assert.Equal(31001, res.FrameId); + Assert.Equal("MOON_ME", res.FrameName); + Assert.Equal(4902800066163.7959, res.GM); + Assert.Equal(1737400.0, res.Radii.X); + Assert.Equal(1737400.0, res.Radii.Y); + Assert.Equal(1737400.0, res.Radii.Z); + Assert.Equal(double.NaN, res.J2); + Assert.Equal(double.NaN, res.J3); + Assert.Equal(double.NaN, res.J4); } [Fact] @@ -749,7 +772,7 @@ void LoadKernelException() [Fact] void CelestialBody() { - DTO.CelestialBody celestialBody = new CelestialBody(1, 2, 3, "celestialItem", new Vector3D(1.0, 2.0, 3.0), 123, "frame", 147); + DTO.CelestialBody celestialBody = new CelestialBody(1, 2, 3, "celestialItem", new Vector3D(1.0, 2.0, 3.0), 123, "frame", 147, 1.0, 2.0, 3.0); Assert.Equal(1, celestialBody.Id); Assert.Equal(2, celestialBody.CenterOfMotionId); Assert.Equal(3, celestialBody.BarycenterOfMotionId); @@ -758,6 +781,9 @@ void CelestialBody() Assert.Equal(147, celestialBody.FrameId); Assert.Equal("frame", celestialBody.FrameName); Assert.Equal(123, celestialBody.GM); + Assert.Equal(1.0, celestialBody.J2); + Assert.Equal(2.0, celestialBody.J3); + Assert.Equal(3.0, celestialBody.J4); } [Fact] diff --git a/IO.Astrodynamics.Tests/Body/CelestialBodyTests.cs b/IO.Astrodynamics.Tests/Body/CelestialBodyTests.cs index 255f617..8d11d62 100644 --- a/IO.Astrodynamics.Tests/Body/CelestialBodyTests.cs +++ b/IO.Astrodynamics.Tests/Body/CelestialBodyTests.cs @@ -209,4 +209,103 @@ public void GetOrientation() Assert.Equal(DateTimeExtension.J2000, orientation.Epoch); Assert.Equal(Frames.Frame.ICRF, orientation.ReferenceFrame); } + + [Fact] + public void EarthSideralRotationPerdiod() + { + var duration = TestHelpers.EarthAtJ2000.SideralRotationPeriod(DateTimeExtension.J2000); + Assert.Equal(TimeSpan.FromTicks(861640998130), duration); + } + + [Fact] + public void MoonSideralRotationPerdiod() + { + var duration = TestHelpers.MoonAtJ2000.SideralRotationPeriod(DateTimeExtension.J2000); + Assert.Equal(TimeSpan.FromTicks(23603596749416), duration); + } + + [Fact] + public void GeosynchronousOrbit() + { + var orbit = TestHelpers.EarthAtJ2000.GeosynchronousOrbit(0.0, 0.0, new DateTime(2021, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)); + Assert.Equal(42164171.958719358, orbit.ToStateVector().Position.Magnitude()); + Assert.Equal(3074.6599898500758, orbit.ToStateVector().Velocity.Magnitude()); + Assert.Equal(Frames.Frame.ICRF, orbit.Frame); + } + + [Fact] + public void GeosynchronousOrbit2() + { + var orbit = TestHelpers.EarthAtJ2000.GeosynchronousOrbit(1.0, 1.0, new DateTime(2021, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)); + Assert.Equal(42164171.95871935, orbit.ToStateVector().Position.Magnitude(), 3); + Assert.Equal(3074.6599898500763, orbit.ToStateVector().Velocity.Magnitude(), 3); + Assert.Equal(Frames.Frame.ICRF, orbit.Frame); + Assert.Equal(42164171.95871935, orbit.SemiMajorAxis()); + Assert.Equal(0.0, orbit.Eccentricity()); + Assert.Equal(1.0, orbit.Inclination(), 2); + Assert.Equal(1.1804318466570587, orbit.AscendingNode(), 2); + Assert.Equal(1.569, orbit.ArgumentOfPeriapsis(), 2); + Assert.Equal(0.0, orbit.MeanAnomaly(), 2); + Assert.Equal(new Vector3(-20992029.30827995, 8679264.319395786, 35522140.607779175), orbit.ToStateVector().Position, TestHelpers.VectorComparer); + Assert.Equal(new Vector3(-1171.3783810266016, -2842.7805399479103, 2.354430257176734), orbit.ToStateVector().Velocity, TestHelpers.VectorComparer); + } + + + [Fact] + public void TrueSolarDayJan() + { + var res1 = TestHelpers.Earth.TrueSolarDay(new DateTime(2021, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)); + Assert.Equal(86407.306035452566, res1.TotalSeconds, 3); + } + + [Fact] + public void TrueSolarDayJMar() + { + var res1 = TestHelpers.Earth.TrueSolarDay(new DateTime(2021, 3, 26, 0, 0, 0, DateTimeKind.Unspecified)); + Assert.Equal(86400.359514701879, res1.TotalSeconds, 3); + } + + [Fact] + public void TrueSolarDayJul() + { + var res1 = TestHelpers.Earth.TrueSolarDay(new DateTime(2021, 7, 25, 0, 0, 0, DateTimeKind.Unspecified)); + Assert.Equal(86392.011764653842, res1.TotalSeconds, 3); + } + + [Fact] + public void TrueSolarDayDec() + { + var res1 = TestHelpers.Earth.TrueSolarDay(new DateTime(2021, 12, 22, 0, 0, 0, DateTimeKind.Unspecified)); + Assert.Equal(86407.114275442393, res1.TotalSeconds, 3); + } + + [Fact] + public void HelioSynchronousOrbit() + { + var epoch = new DateTime(2021, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); + var res = TestHelpers.Earth.HelioSynchronousOrbit(7080636.3, 0.0001724, epoch); + Assert.Equal(7080636.3, res.A); + Assert.Equal(0.0001724, res.E, 6); + Assert.Equal(98.208156353447507, res.I * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(11.457000000000001, res.RAAN * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(270.0, res.AOP * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(270.0, res.TrueAnomaly() * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(270.01999999999998, res.MeanAnomaly() * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(epoch, res.Epoch); + } + + [Fact] + public void PhaseHelioSynchronousOrbit() + { + var epoch = new DateTime(2021, 11, 22, 0, 0, 0, DateTimeKind.Unspecified); + var res = TestHelpers.Earth.HelioSynchronousOrbit(0.0001724, epoch, 14); + Assert.Equal(7272221.8761325106, res.A, 3); + Assert.Equal(0.0001724, res.E, 6); + Assert.Equal(99.018, res.I * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(327.43000000000001, res.RAAN * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(270.0, res.AOP * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(270.0, res.TrueAnomaly() * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(270.01999999999998, res.MeanAnomaly() * Astrodynamics.Constants.Rad2Deg, 3); + Assert.Equal(epoch, res.Epoch); + } } \ No newline at end of file diff --git a/IO.Astrodynamics.Tests/OrbitalParameters/KeplerianElementsTests.cs b/IO.Astrodynamics.Tests/OrbitalParameters/KeplerianElementsTests.cs index e0e42f9..fcf8057 100644 --- a/IO.Astrodynamics.Tests/OrbitalParameters/KeplerianElementsTests.cs +++ b/IO.Astrodynamics.Tests/OrbitalParameters/KeplerianElementsTests.cs @@ -1,4 +1,5 @@ using System; +using System.Data; using IO.Astrodynamics.Body; using IO.Astrodynamics.Math; using IO.Astrodynamics.OrbitalParameters; @@ -33,35 +34,35 @@ public void Create() Assert.Equal(earth, ke.Observer); Assert.Equal(epoch, ke.Epoch); Assert.Equal(Frames.Frame.ICRF, ke.Frame); - Assert.Throws(()=>new KeplerianElements(-20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(-20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, 40.0 * IO.Astrodynamics.Constants.Deg2Rad, 50.0 * IO.Astrodynamics.Constants.Deg2Rad, 10.0 * IO.Astrodynamics.Constants.Deg2Rad, earth, epoch, Frames.Frame.ICRF)); - Assert.Throws(()=>new KeplerianElements(20000, -0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(20000, -0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, 40.0 * IO.Astrodynamics.Constants.Deg2Rad, 50.0 * IO.Astrodynamics.Constants.Deg2Rad, 10.0 * IO.Astrodynamics.Constants.Deg2Rad, earth, epoch, Frames.Frame.ICRF)); - Assert.Throws(()=>new KeplerianElements(20000, 0.5, 181 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(20000, 0.5, 181 * IO.Astrodynamics.Constants.Deg2Rad, 40.0 * IO.Astrodynamics.Constants.Deg2Rad, 50.0 * IO.Astrodynamics.Constants.Deg2Rad, 10.0 * IO.Astrodynamics.Constants.Deg2Rad, earth, epoch, Frames.Frame.ICRF)); - Assert.Throws(()=>new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, -40.0 * IO.Astrodynamics.Constants.Deg2Rad, 50.0 * IO.Astrodynamics.Constants.Deg2Rad, 10.0 * IO.Astrodynamics.Constants.Deg2Rad, earth, epoch, Frames.Frame.ICRF)); - Assert.Throws(()=>new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, 40.0 * IO.Astrodynamics.Constants.Deg2Rad, -50.0 * IO.Astrodynamics.Constants.Deg2Rad, 10.0 * IO.Astrodynamics.Constants.Deg2Rad, earth, epoch, Frames.Frame.ICRF)); - Assert.Throws(()=>new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, 40.0 * IO.Astrodynamics.Constants.Deg2Rad, 50.0 * IO.Astrodynamics.Constants.Deg2Rad, -10.0 * IO.Astrodynamics.Constants.Deg2Rad, earth, epoch, Frames.Frame.ICRF)); - Assert.Throws(()=>new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, 40.0 * IO.Astrodynamics.Constants.Deg2Rad, 50.0 * IO.Astrodynamics.Constants.Deg2Rad, 10.0 * IO.Astrodynamics.Constants.Deg2Rad, null, epoch, Frames.Frame.ICRF)); - Assert.Throws(()=>new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, + Assert.Throws(() => new KeplerianElements(20000, 0.5, 30.0 * IO.Astrodynamics.Constants.Deg2Rad, 40.0 * IO.Astrodynamics.Constants.Deg2Rad, 50.0 * IO.Astrodynamics.Constants.Deg2Rad, 10.0 * IO.Astrodynamics.Constants.Deg2Rad, earth, epoch, null)); @@ -99,7 +100,7 @@ public void ToStateVector() Assert.Equal(-5477.646280596454, sv.Velocity.Y, 6); Assert.Equal(-5297.633402454895, sv.Velocity.Z, 6); } - + [Fact] public void ToStateVector2() { @@ -120,6 +121,31 @@ public void ToStateVector2() Assert.Equal(-5297.633402454895, sv.Velocity.Z, 6); } + [Fact] + public void SingularityZeroEccentricity() + { + KeplerianElements original = new KeplerianElements(42000000, 0.0, 1.0, 2.0, 1.0, 0.5, TestHelpers.EarthAtJ2000, DateTimeExtension.J2000, Frames.Frame.ICRF); + KeplerianElements transformed = original.ToStateVector().ToKeplerianElements(); + Assert.Equal(original.A, transformed.A); + Assert.Equal(original.E, transformed.E, 6); + Assert.Equal(original.I, transformed.I, 6); + Assert.Equal(original.RAAN, transformed.RAAN); + Assert.Equal(original.MeanLongitude(), transformed.MeanLongitude()); + Assert.Equal(original.AOP + original.M, transformed.AOP + transformed.M); + } + + [Fact] + public void SingularityZeroEccentricityZeroInclination() + { + KeplerianElements original = new KeplerianElements(42000000, 0.0, 0.0, 2.0, 1.0, 0.5, TestHelpers.EarthAtJ2000, DateTimeExtension.J2000, Frames.Frame.ICRF); + var originalSv = original.ToStateVector(); + KeplerianElements transformed = originalSv.ToKeplerianElements(); + Assert.Equal(original.A, transformed.A); + Assert.Equal(original.E, transformed.E, 6); + Assert.Equal(original.I, transformed.I, 6); + Assert.Equal(original.MeanLongitude(), transformed.MeanLongitude(),6); + } + [Fact] public void TrueAnomaly10() { diff --git a/IO.Astrodynamics.Tests/TestHelpers.cs b/IO.Astrodynamics.Tests/TestHelpers.cs index 63182ff..8a25beb 100644 --- a/IO.Astrodynamics.Tests/TestHelpers.cs +++ b/IO.Astrodynamics.Tests/TestHelpers.cs @@ -1,5 +1,6 @@ using System; using IO.Astrodynamics.Body; +using IO.Astrodynamics.Math; using IO.Astrodynamics.SolarSystemObjects; namespace IO.Astrodynamics.Tests @@ -17,5 +18,14 @@ internal static class TestHelpers internal static CelestialBody MoonAtJ2000 => new(PlanetsAndMoons.MOON, Frames.Frame.ICRF, new DateTime(2000, 1, 1, 12, 0, 0)); internal static CelestialBody MoonAt20011214 => new(PlanetsAndMoons.MOON, Frames.Frame.ICRF, new DateTime(2001, 12, 14, 0, 0, 0)); + private static object LockObj = new object(); + + internal static bool VectorComparer(Vector3 v1, Vector3 v2) + { + lock (LockObj) + { + return System.Math.Abs(v1.X - v2.X) < 1E-03 && System.Math.Abs(v1.Y - v2.Y) < 1E-03 && System.Math.Abs(v1.Z - v2.Z) < 1E-03; + } + } } } \ No newline at end of file diff --git a/IO.Astrodynamics/Body/CelestialBody.cs b/IO.Astrodynamics/Body/CelestialBody.cs index 26c67ca..b8441f3 100644 --- a/IO.Astrodynamics/Body/CelestialBody.cs +++ b/IO.Astrodynamics/Body/CelestialBody.cs @@ -1,8 +1,14 @@ using System; +using System.Linq; +using System.Numerics; +using IO.Astrodynamics.Coordinates; using IO.Astrodynamics.Frames; +using IO.Astrodynamics.Math; using IO.Astrodynamics.OrbitalParameters; using IO.Astrodynamics.SolarSystemObjects; +using IO.Astrodynamics.Surface; using IO.Astrodynamics.Time; +using Vector3 = IO.Astrodynamics.Math.Vector3; namespace IO.Astrodynamics.Body; @@ -16,6 +22,10 @@ public class CelestialBody : CelestialItem public double SphereOfInfluence { get; private set; } public Frame Frame { get; } + public double J2 { get; } + public double J3 { get; } + public double J4 { get; } + /// /// Instantiate celestial body from naif object with default parameters (Ecliptic J2000 at J2000 epoch) /// @@ -53,6 +63,9 @@ public CelestialBody(int naifId, Frame frame, DateTime epoch) : base(naifId, fra PolarRadius = ExtendedInformation.Radii.Z; EquatorialRadius = ExtendedInformation.Radii.X; Flattening = (EquatorialRadius - PolarRadius) / EquatorialRadius; + J2 = ExtendedInformation.J2; + J3 = ExtendedInformation.J3; + J4 = ExtendedInformation.J4; if (double.IsNaN(Flattening)) { Flattening = 0.0; @@ -128,4 +141,112 @@ public StateOrientation GetOrientation(Frame referenceFrame, in DateTime epoch) { return referenceFrame.ToFrame(Frame, epoch); } + + public TimeSpan SideralRotationPeriod(DateTime epoch) + { + return TimeSpan.FromSeconds(Constants._2PI / GetOrientation(Frame.ICRF, epoch).AngularVelocity.Magnitude()); + } + + public KeplerianElements GeosynchronousOrbit(double longitude, double latitude, DateTime epoch) + { + var sideralRotation2 = System.Math.Pow(SideralRotationPeriod(epoch).TotalSeconds, 2); + var radius = System.Math.Cbrt((GM * sideralRotation2) / (4 * Constants.PI * Constants.PI)); + var bodyfFixedCoordinates = new Planetocentric(longitude, latitude, radius).ToCartesianCoordinates(); + var icrfPos = bodyfFixedCoordinates.Rotate(Frame.ToFrame(Frame.ICRF, epoch).Rotation); + var icrfRot = Vector3.VectorZ.Rotate(Frame.ToFrame(Frame.ICRF, epoch).Rotation); + var inertialVelocity = icrfRot.Cross(icrfPos).Normalize() * System.Math.Sqrt(GM / radius); + var sv = new StateVector(icrfPos, inertialVelocity, this, epoch, Frame.ICRF); + return new KeplerianElements(radius, 0.0, sv.Inclination(), sv.AscendingNode(), (sv.ArgumentOfPeriapsis() + sv.MeanAnomaly()) % Constants._2PI, 0.0, this, epoch, sv.Frame); + } + + /// + /// Calculates the Keplerian elements for a heliosynchronous + /// orbit. + /// + /// The semi-major axis of the orbit. + /// The eccentricity of the orbit. + /// The epoch at the descending node. + /// The Keplerian elements for the heliosynchronous orbit. + /// Thrown when + /// the orbit perigee is lower than the equatorial radius. + public KeplerianElements HelioSynchronousOrbit(double semiMajorAxis, double eccentricity, DateTime epochAtDescendingNode) + { + CelestialBody sun = new CelestialBody(10); + double p = semiMajorAxis * (1 - eccentricity); + if (p < EquatorialRadius) + { + throw new ArgumentException("Orbit perigee is lower than equatorial radius"); + } + + double a72 = System.Math.Pow(semiMajorAxis, 3.5); + double e2 = eccentricity * eccentricity; + double e22 = (1 - e2) * (1 - e2); + double sqrtGM = System.Math.Sqrt(GM); + double re2 = EquatorialRadius * EquatorialRadius; + var ephemeris = GetEphemeris(epochAtDescendingNode, sun, Frame.ICRF, Aberration.LT); + double i = System.Math.Acos((2.0 * a72 * e22 * ephemeris.MeanMotion()) / (3.0 * sqrtGM * -J2 * re2)); + + var sunVector = ephemeris.ToStateVector().Position.Inverse(); + Math.Plane sunPlane = new Math.Plane(Vector3.VectorZ.Rotate(Frame.ToFrame(Frame.ICRF, epochAtDescendingNode).Rotation).Cross(sunVector), 0.0); + double raanLongitude = sunPlane.GetAngle(Vector3.VectorY); + + if (sunVector.Y > 0.0) + { + raanLongitude *= -1.0; + } + + //Make raan in range 0.0->2PI + if (raanLongitude < 0.0) + { + raanLongitude += Constants._2PI; + } + + double m = OrbitalParameters.OrbitalParameters.TrueAnomalyToMeanAnomaly(Constants.PI + Constants.PI2, eccentricity); + + return new KeplerianElements(semiMajorAxis, eccentricity, i, raanLongitude, Constants.PI + Constants.PI2, m, this, epochAtDescendingNode, Frame.ICRF); + } + + /// + /// Calculate the true solar day for a given epoch. + /// + /// The epoch for which to calculate the true solar day. + /// The duration of the true solar day. + /// + /// This method only works with planets. + /// It throws an if the current celestial body is not a planet. + /// It calculates the sideral rotation period and uses it to determine the angle of rotation between two points in the celestial body + /// 's ephemeris. + /// Finally, it returns the sideral rotation period plus the time it takes to rotate toward the sun using the angular velocity of + /// the celestial body at the given epoch. + /// + public TimeSpan TrueSolarDay(DateTime epoch) + { + if (!this.IsPlanet) + { + throw new InvalidOperationException("At this time, the computation of true solar day works only with planets"); + } + + CelestialBody sun = new CelestialBody(10); + var sideralRotation = SideralRotationPeriod(epoch); + var eph0 = this.GetEphemeris(epoch, sun, Frame.ECLIPTIC_J2000, Aberration.LT).ToStateVector().Position; + var eph1 = this.GetEphemeris(epoch + sideralRotation, sun, Frame.ECLIPTIC_J2000, Aberration.LT).ToStateVector().Position; + var angle = eph0.Angle(eph1); + return sideralRotation + TimeSpan.FromSeconds(angle / GetOrientation(Frame.ICRF, epoch).AngularVelocity.Magnitude()); + } + + /// + /// Calculates the Keplerian elements of a phased heliosynchronous + /// orbit + /// + /// The eccentricity of the orbit + /// The epoch at the descending node + /// The number of orbits per day + /// The calculated Keplerian elements + public KeplerianElements HelioSynchronousOrbit(double eccentricity, DateTime epochAtDescendingNode, int nbOrbitPerDay) + { + var trueSolarDay = TrueSolarDay(epochAtDescendingNode); + double t = trueSolarDay.TotalSeconds / nbOrbitPerDay; + double a = System.Math.Cbrt(((t * t) * GM) / (4 * Constants.PI * Constants.PI)); + return HelioSynchronousOrbit(a, eccentricity, epochAtDescendingNode); + } } \ No newline at end of file diff --git a/IO.Astrodynamics/Body/CelestialItem.cs b/IO.Astrodynamics/Body/CelestialItem.cs index 0f4c12c..f31e61b 100644 --- a/IO.Astrodynamics/Body/CelestialItem.cs +++ b/IO.Astrodynamics/Body/CelestialItem.cs @@ -213,9 +213,6 @@ public override string ToString() return Name; } - - - public bool Equals(CelestialItem other) { if (ReferenceEquals(null, other)) return false; diff --git a/IO.Astrodynamics/DTO/CelestialBody.cs b/IO.Astrodynamics/DTO/CelestialBody.cs index 8109468..11119cc 100644 --- a/IO.Astrodynamics/DTO/CelestialBody.cs +++ b/IO.Astrodynamics/DTO/CelestialBody.cs @@ -16,8 +16,11 @@ public readonly struct CelestialBody public string FrameName { get; } public int FrameId { get; } public string Error { get; } = string.Empty; + public double J2 { get; } + public double J3 { get; } + public double J4 { get; } - public CelestialBody(int id, int centerOfMotionId, int barycenterOfMotionId, string name, Vector3D radii, double gm, string frameName, int frameId) + public CelestialBody(int id, int centerOfMotionId, int barycenterOfMotionId, string name, Vector3D radii, double gm, string frameName, int frameId, double j2, double j3, double j4) { Id = id; CenterOfMotionId = centerOfMotionId; @@ -26,6 +29,9 @@ public CelestialBody(int id, int centerOfMotionId, int barycenterOfMotionId, str GM = gm; FrameName = frameName; FrameId = frameId; + J2 = j2; + J3 = j3; + J4 = j4; BarycenterOfMotionId = barycenterOfMotionId; } diff --git a/IO.Astrodynamics/Math/Plane.cs b/IO.Astrodynamics/Math/Plane.cs new file mode 100644 index 0000000..89bff80 --- /dev/null +++ b/IO.Astrodynamics/Math/Plane.cs @@ -0,0 +1,27 @@ +namespace IO.Astrodynamics.Math; + +public class Plane +{ + public Vector3 Normal { get; } + public double Distance { get; } + + public Plane(Vector3 normal) : this(normal, 0.0) + { + } + + public Plane(Vector3 normal, double distance) + { + Normal = normal; + Distance = distance; + } + + public double GetAngle(Plane plane) + { + return System.Math.Acos((Normal * plane.Normal) / (Normal.Magnitude() * plane.Normal.Magnitude())); + } + + public double GetAngle(Vector3 vector) + { + return System.Math.Asin((Normal * vector) / (Normal.Magnitude() * vector.Magnitude())); + } +} \ No newline at end of file diff --git a/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs b/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs index 22bf17a..27db536 100644 --- a/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs +++ b/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs @@ -133,6 +133,11 @@ public Vector3 DescendingNodeVector() /// /// public double MeanAnomaly(double trueAnomaly) + { + return TrueAnomalyToMeanAnomaly(trueAnomaly, Eccentricity()); + } + + public static double TrueAnomalyToMeanAnomaly(double trueAnomaly, double eccentricity) { if (trueAnomaly < 0.0) { @@ -140,9 +145,9 @@ public double MeanAnomaly(double trueAnomaly) } //X = cos E - double x = (Eccentricity() + System.Math.Cos(trueAnomaly)) / (1 + Eccentricity() * System.Math.Cos(trueAnomaly)); + double x = (eccentricity + System.Math.Cos(trueAnomaly)) / (1 + eccentricity * System.Math.Cos(trueAnomaly)); double eccAno = System.Math.Acos(x); - double M = eccAno - Eccentricity() * System.Math.Sin(eccAno); + double M = eccAno - eccentricity * System.Math.Sin(eccAno); if (trueAnomaly > Constants.PI) { @@ -198,7 +203,7 @@ public virtual StateVector ToStateVector() return new StateVector(new Vector3(finalPos[0], finalPos[1], finalPos[2]), new Vector3(finalV[0], finalV[1], finalV[2]), Observer, Epoch, Frame); } - + public virtual StateVector ToStateVector(DateTime epoch) { return AtEpoch(epoch).ToStateVector(); @@ -284,7 +289,7 @@ public double MeanLongitude() public bool IsCircular() { - return Eccentricity() == 0.0; + return Eccentricity() < 1E-03; } public bool IsParabolic() diff --git a/IO.Astrodynamics/OrbitalParameters/StateVector.cs b/IO.Astrodynamics/OrbitalParameters/StateVector.cs index 27fe849..eb65b12 100644 --- a/IO.Astrodynamics/OrbitalParameters/StateVector.cs +++ b/IO.Astrodynamics/OrbitalParameters/StateVector.cs @@ -94,6 +94,15 @@ public override double ArgumentOfPeriapsis() public override double TrueAnomaly() { + if (IsCircular()) + { + if (Inclination() < 1E-03) + { + return CircularNoInclinationTrueAnomaly(); + } + + return CircularTrueAnomaly(); + } var e = EccentricityVector(); var v = System.Math.Acos((e * Position) / (e.Magnitude() * Position.Magnitude())); if (Position * Velocity < 0.0) @@ -104,6 +113,41 @@ public override double TrueAnomaly() return v; } + private double CircularTrueAnomaly() + { + var omega = AscendingNodeVector(); + var v = System.Math.Acos((omega * Position) / (omega.Magnitude() * Position.Magnitude())); + if (Position.Z < 0.0) + { + v = Constants._2PI - v; + } + + v -= ArgumentOfPeriapsis(); + if (v < 0.0) + { + v += Constants._2PI; + } + + return v % Constants._2PI; + } + + private double CircularNoInclinationTrueAnomaly() + { + var l = System.Math.Acos(Position.X / Position.Magnitude()); + if (Velocity.X > 0) + { + l = Constants._2PI - l; + } + + l = l - ArgumentOfPeriapsis() - AscendingNode(); + if (l < 0.0) + { + l += Constants._2PI; + } + + return l % Constants._2PI; + } + public override double EccentricAnomaly() { double v = TrueAnomaly(); diff --git a/IO.Astrodynamics/resources/IO.Astrodynamics.dll b/IO.Astrodynamics/resources/IO.Astrodynamics.dll index 0044dda..ca952ff 100644 Binary files a/IO.Astrodynamics/resources/IO.Astrodynamics.dll and b/IO.Astrodynamics/resources/IO.Astrodynamics.dll differ diff --git a/IO.Astrodynamics/resources/IO.Astrodynamics.lib b/IO.Astrodynamics/resources/IO.Astrodynamics.lib index c77d8fc..6365687 100644 Binary files a/IO.Astrodynamics/resources/IO.Astrodynamics.lib and b/IO.Astrodynamics/resources/IO.Astrodynamics.lib differ diff --git a/IO.Astrodynamics/resources/libIO.Astrodynamics.so b/IO.Astrodynamics/resources/libIO.Astrodynamics.so old mode 100644 new mode 100755 index f87963f..d802f7f Binary files a/IO.Astrodynamics/resources/libIO.Astrodynamics.so and b/IO.Astrodynamics/resources/libIO.Astrodynamics.so differ