diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj b/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj
index 36b9ad97..f928d93c 100644
--- a/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj
@@ -9,7 +9,7 @@
0.0.1
true
astro
- 0.6.2.1
+ 0.6.3.1
Astrodynamics command line interface
Sylvain Guillet
This CLI allows end user to exploit IO.Astrodynamics framework
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/OrbitalParameters/FindOrbitalParametersFromObservations.cs b/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/OrbitalParameters/FindOrbitalParametersFromObservations.cs
new file mode 100644
index 00000000..adc2508b
--- /dev/null
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/OrbitalParameters/FindOrbitalParametersFromObservations.cs
@@ -0,0 +1,101 @@
+using System;
+using IO.Astrodynamics.Body;
+using IO.Astrodynamics.Coordinates;
+using IO.Astrodynamics.Math;
+using IO.Astrodynamics.OrbitalParameters;
+using IO.Astrodynamics.SolarSystemObjects;
+using IO.Astrodynamics.Surface;
+using Xunit;
+
+namespace IO.Astrodynamics.Tests.OrbitalParameters;
+
+public class InitialOrbitDeterminationTests
+{
+ public InitialOrbitDeterminationTests()
+ {
+ API.Instance.LoadKernels(Constants.SolarSystemKernelPath);
+ }
+
+ [Fact]
+ public void GeosynchronousObject()
+ {
+ for (int i = 1; i <= 5; i++)
+ {
+ var timespan = TimeSpan.FromMinutes(i * 2);
+ Site site = new Site(80, "MyStation", TestHelpers.EarthAtJ2000, new Planetodetic(0.0, 45.0 * Constants.DEG_RAD, 0.0));
+
+ var e2 = new TimeSystem.Time(2024, 1, 2);
+
+ var e1 = e2 - timespan;
+ var e3 = e2 + timespan;
+ var referenceOrbit = new KeplerianElements(
+ semiMajorAxis: 42164000.0, // m
+ eccentricity: 0.0,
+ inclination: 15.0 * Constants.DEG_RAD,
+ rigthAscendingNode: 45.0 * Constants.DEG_RAD,
+ argumentOfPeriapsis: 0.0,
+ meanAnomaly: 45.0 * Constants.DEG_RAD, // Décalage de 45 degrés
+ observer: TestHelpers.EarthAtJ2000,
+ frame: Frames.Frame.ICRF,
+ epoch: e2
+ );
+ var obs1 = referenceOrbit.AtEpoch(e1).RelativeTo(site, Aberration.LT);
+ var obs2 = referenceOrbit.AtEpoch(e2).RelativeTo(site, Aberration.LT);
+ var obs3 = referenceOrbit.AtEpoch(e3).RelativeTo(site, Aberration.LT);
+ var orbitalParams =
+ InitialOrbitDetermination.CreateFromObservation_Gauss(obs1.ToEquatorial(), obs2.ToEquatorial(), obs3.ToEquatorial(), site,
+ PlanetsAndMoons.EARTH_BODY, 42000000.0);
+ var deltaRange = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Position - referenceOrbit.ToStateVector().Position).Magnitude() /
+ referenceOrbit.ToStateVector().Position.Magnitude();
+ var deltaVelocity = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Velocity - referenceOrbit.ToStateVector().Velocity).Magnitude() /
+ referenceOrbit.ToStateVector().Velocity.Magnitude();
+ Assert.True(deltaRange < 0.02);
+ Assert.True(deltaVelocity < 0.02);
+ }
+ }
+
+ [Fact]
+ public void PlanetObject()
+ {
+ var timespan = TimeSpan.FromDays(10);
+ Site site = new Site(13, "DSS-13", TestHelpers.EarthAtJ2000);
+
+ var e2 = new TimeSystem.Time(2023, 3, 1);
+ var e1 = e2 - timespan;
+ var e3 = e2 + timespan;
+ var target = new Barycenter(4);
+ var obs1 = target.GetEphemeris(e1, site, Frames.Frame.ICRF, Aberration.LT);
+ var obs2 = target.GetEphemeris(e2, site, Frames.Frame.ICRF, Aberration.LT);
+ var obs3 = target.GetEphemeris(e3, site, Frames.Frame.ICRF, Aberration.LT);
+ var referenceOrbit = target.GetEphemeris(e2, TestHelpers.Sun, Frames.Frame.ICRF, Aberration.LT);
+ var orbitalParams =
+ InitialOrbitDetermination.CreateFromObservation_Gauss(obs1.ToEquatorial(), obs2.ToEquatorial(), obs3.ToEquatorial(), site,
+ Stars.SUN_BODY, 350000000000.58063);
+
+ var deltaRange = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Position - referenceOrbit.ToStateVector().Position).Magnitude() /
+ referenceOrbit.ToStateVector().Position.Magnitude();
+ var deltaVelocity = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Velocity - referenceOrbit.ToStateVector().Velocity).Magnitude() /
+ referenceOrbit.ToStateVector().Velocity.Magnitude();
+
+ Assert.True(deltaRange < 0.06);
+ Assert.True(deltaVelocity < 0.15);
+ }
+
+ [Fact]
+ public void COBE()
+ {
+ var ut1 = new TimeSystem.Time(2000, 11, 6, 22, 31, 29, 0, 0, TimeSystem.TimeFrame.UTCFrame);
+ var ut2 = new TimeSystem.Time(2000, 11, 6, 22, 34, 30, 0, 0, TimeSystem.TimeFrame.UTCFrame);
+ var ut3 = new TimeSystem.Time(2000, 11, 6, 22, 37, 30, 0, 0, TimeSystem.TimeFrame.UTCFrame);
+ var site = new Site(99, "Maryland university", TestHelpers.EarthAtJ2000, new Planetodetic(-76.95667 * Constants.DEG_RAD, 39.00167 * Constants.DEG_RAD, 53.0));
+ var obs1 = new Equatorial(-16.3 * Astrodynamics.Constants.Deg2Rad, 327.0 * Astrodynamics.Constants.Deg2Rad, 7250000.0, ut1);
+ var obs2 = new Equatorial(46.9 * Astrodynamics.Constants.Deg2Rad, 318.5 * Astrodynamics.Constants.Deg2Rad, 7250000.0, ut2);
+ var obs3 = new Equatorial(76.1 * Astrodynamics.Constants.Deg2Rad, 165.75 * Astrodynamics.Constants.Deg2Rad, 7250000.0, ut3);
+ var orbitalParams =
+ InitialOrbitDetermination.CreateFromObservation_Gauss(obs1, obs2, obs3, site, PlanetsAndMoons.EARTH_BODY, 7_250_000.0);
+ StateVector referenceOrbit = new(new Vector3(3520477.0118993367, -4310404.2221997585, 4645118.5764311915),
+ new Vector3(-4026.2486947722973, 2615.67401249017, 5468.113004203227),
+ TestHelpers.EarthAtJ2000, ut2, Frames.Frame.ICRF);
+ Assert.Equal(referenceOrbit, orbitalParams.OrbitalParameters.ToStateVector(), TestHelpers.StateVectorComparer);
+ }
+}
\ No newline at end of file
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs
index c8f779a3..aac976d8 100644
--- a/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs
@@ -47,5 +47,13 @@ public Vector3 ToCartesian()
double z = Distance * System.Math.Sin(Declination);
return new Vector3(x, y, z);
}
+
+ public Vector3 ToDirection()
+ {
+ double x = System.Math.Cos(Declination) * System.Math.Cos(RightAscension);
+ double y = System.Math.Cos(Declination) * System.Math.Sin(RightAscension);
+ double z = System.Math.Sin(Declination);
+ return new Vector3(x, y, z);
+ }
}
}
\ No newline at end of file
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec b/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec
index c45c24e4..2e53cc16 100644
--- a/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec
@@ -4,7 +4,7 @@
IO.Astrodynamics
Sylvain Guillet
Sylvain Guillet
- 6.2.0
+ 6.3.0
Astrodynamics framework
images\dragonfly-dark-trans.png
docs\README.md
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Bissection.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Bissection.cs
new file mode 100644
index 00000000..d02f8721
--- /dev/null
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Bissection.cs
@@ -0,0 +1,47 @@
+using System;
+
+public class BisectionMethod
+{
+ public static double Solve(
+ Func f, // Your polynomial function
+ double a, // Left endpoint
+ double b, // Right endpoint
+ double tolerance = 1E-12, // Matching your tolerance
+ int maxIterations = 1000) // Matching your max iterations
+ {
+ if (f(a) * f(b) >= 0)
+ {
+ throw new ArgumentException("Function must have different signs at endpoints a and b");
+ }
+
+ double c = a;
+ int iterations = 0;
+
+ while ((b - a) > tolerance && iterations < maxIterations)
+ {
+ c = (a + b) / 2;
+ double fc = f(c);
+
+ if (Math.Abs(fc) < tolerance)
+ break;
+
+ if (f(a) * fc < 0)
+ {
+ b = c;
+ }
+ else
+ {
+ a = c;
+ }
+
+ iterations++;
+ }
+
+ if (iterations >= maxIterations)
+ {
+ throw new ArgumentException($"Failed to converge after {maxIterations} iterations");
+ }
+
+ return c;
+ }
+}
\ No newline at end of file
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/NewtonRaphson.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/NewtonRaphson.cs
new file mode 100644
index 00000000..fb7b33de
--- /dev/null
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/NewtonRaphson.cs
@@ -0,0 +1,85 @@
+using System;
+
+namespace IO.Astrodynamics.Math;
+
+///
+/// Newton-Raphson method for finding the root of a function.
+///
+public class NewtonRaphson
+{
+ ///
+ /// Solve the equation f(x) = 0 using the Newton-Raphson method.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static double Solve(
+ Func function,
+ Func derivative,
+ double initialGuess,
+ double tolerance = 1e-6,
+ int maxIterations = 100)
+ {
+ double x = initialGuess;
+
+ for (int i = 0; i < maxIterations; i++)
+ {
+ double fValue = function(x);
+ double fDerivative = derivative(x);
+
+ if (System.Math.Abs(fDerivative) < 1e-12) // Avoid division by zero or near-zero derivatives
+ throw new Exception($"Derivative too small (f'(x) = {fDerivative}) at iteration {i}. Adjust initial guess or problem scaling.");
+
+ // Update the root estimate using Newton-Raphson formula
+ double xNew = x - fValue / fDerivative;
+
+ // Check for convergence (absolute and relative tolerance)
+ if (System.Math.Abs(xNew - x) < tolerance || System.Math.Abs(xNew - x) < tolerance * System.Math.Abs(xNew))
+ return xNew;
+
+ if (double.IsNaN(xNew) || double.IsInfinity(xNew))
+ throw new Exception("Newton-Raphson method diverged.");
+
+ x = xNew;
+ }
+
+
+ throw new Exception("Newton-Raphson method did not converge within the maximum number of iterations.");
+ }
+
+ public static double BoundedNewtonRaphson(Func f, Func df,
+ double x0, double min, double max, double tolerance, int maxIterations)
+ {
+ double x = x0;
+ for (int i = 0; i < maxIterations; i++)
+ {
+ double fx = f(x);
+ if (System.Math.Abs(fx) < tolerance)
+ return x;
+
+ double dfx = df(x);
+ if (System.Math.Abs(dfx) < 1e-10)
+ break;
+
+ double step = fx / dfx;
+ double newX = x - step;
+
+ // Bound the solution
+ if (newX < min)
+ newX = min;
+ else if (newX > max)
+ newX = max;
+
+ if (System.Math.Abs(newX - x) < tolerance)
+ return newX;
+
+ x = newX;
+ }
+
+ throw new Exception("Failed to converge within maximum iterations");
+ }
+}
\ No newline at end of file
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Solver.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Solver.cs
new file mode 100644
index 00000000..bb4ac035
--- /dev/null
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Solver.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace IO.Astrodynamics.Math;
+
+public class Solver
+{
+ ///
+ /// Solve the quadratic equation ax^2 + bx + c = 0.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static double SolveQuadratic(double a, double b, double c)
+ {
+ double discriminant = b * b - 4 * a * c;
+ if (discriminant < 0)
+ throw new InvalidOperationException("No real roots for the polynomial.");
+
+ double root1 = (-b + System.Math.Sqrt(discriminant)) / (2 * a);
+ double root2 = (-b - System.Math.Sqrt(discriminant)) / (2 * a);
+
+ // Select the positive root (physically meaningful solution)
+ return System.Math.Max(root1, root2);
+ }
+}
\ No newline at end of file
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs
index c72bb595..fa9a12eb 100644
--- a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs
@@ -20,7 +20,12 @@ public Vector3(double x, double y, double z)
public double Magnitude()
{
- return System.Math.Sqrt(X * X + Y * Y + Z * Z);
+ return System.Math.Sqrt(MagnitudeSquared());
+ }
+
+ public double MagnitudeSquared()
+ {
+ return X * X + Y * Y + Z * Z;
}
public Vector3 Normalize()
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/InitialOrbitDetermination.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/InitialOrbitDetermination.cs
new file mode 100644
index 00000000..af3819fd
--- /dev/null
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/InitialOrbitDetermination.cs
@@ -0,0 +1,223 @@
+using System;
+using System.Linq;
+using IO.Astrodynamics.Body;
+using IO.Astrodynamics.Coordinates;
+using IO.Astrodynamics.Frames;
+using IO.Astrodynamics.Math;
+using IO.Astrodynamics.TimeSystem;
+
+namespace IO.Astrodynamics.OrbitalParameters;
+
+public class InitialOrbitDetermination
+{
+ ///
+ /// Create orbital parameters from observations
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static OrbitDetermination CreateFromObservation_Gauss(Equatorial observation1, Equatorial observation2, Equatorial observation3, ILocalizable observer,
+ CelestialItem expectedCenterOfMotion, double expectedRangeFromObserver)
+ {
+ var distanceScale = expectedCenterOfMotion.IsSun ? Constants.AU : 1E03;
+
+ double mu = expectedCenterOfMotion.GM / System.Math.Pow(distanceScale, 3);
+ // Step 1: Compute observer positions with improved scaling
+ Vector3 R1 = observer
+ .GetEphemeris(observation1.Epoch, expectedCenterOfMotion, Frame.ICRF, Aberration.LT)
+ .ToStateVector()
+ .Position / distanceScale;
+
+ Vector3 R2 = observer
+ .GetEphemeris(observation2.Epoch, expectedCenterOfMotion, Frame.ICRF, Aberration.LT)
+ .ToStateVector()
+ .Position / distanceScale;
+
+ Vector3 R3 = observer
+ .GetEphemeris(observation3.Epoch, expectedCenterOfMotion, Frame.ICRF, Aberration.LT)
+ .ToStateVector()
+ .Position / distanceScale;
+
+ // Step 2: Convert equatorial coordinates to unit direction vectors
+ Vector3 rhoHat1 = observation1.ToDirection();
+ Vector3 rhoHat2 = observation2.ToDirection();
+ Vector3 rhoHat3 = observation3.ToDirection();
+
+ var score = CheckObservationQuality(new[] { R1, R2, R3 }, new[] { rhoHat1, rhoHat2, rhoHat3 }, new[] { observation1.Epoch, observation2.Epoch, observation3.Epoch });
+
+ // Step 3: Time differences with improved precision
+ double tau1 = (observation1.Epoch - observation2.Epoch).TotalSeconds;
+ double tau3 = (observation3.Epoch - observation2.Epoch).TotalSeconds;
+ double tau = tau3 - tau1;
+
+ // Step 4: Compute determinants with improved numerical stability
+ Vector3 p1 = rhoHat2.Cross(rhoHat3);
+ Vector3 p2 = rhoHat1.Cross(rhoHat3);
+ Vector3 p3 = rhoHat1.Cross(rhoHat2);
+
+ double d0 = rhoHat1 * p1;
+ double d11 = R1 * p1;
+ double d12 = R1 * p2;
+ double d13 = R1 * p3;
+ double d21 = R2 * p1;
+ double d22 = R2 * p2;
+ double d23 = R2 * p3;
+ double d31 = R3 * p1;
+ double d32 = R3 * p2;
+ double d33 = R3 * p3;
+
+ // Step 5: Compute scaled coefficients with improved numerical stability
+ double A = (1.0 / d0) * (-d12 * (tau3 / tau) + d22 + d32 * (tau1 / tau));
+ double B = (1.0 / (6.0 * d0 * tau)) *
+ (-d12 * tau3 * (tau * tau - tau3 * tau3) + d32 * tau1 * (tau * tau - tau1 * tau1));
+
+ // Step 6: Solve eighth-degree polynomial with improved coefficients
+ double E = R2 * rhoHat2;
+
+ double polynomialA = -(A * A + 2.0 * A * E + R2.MagnitudeSquared());
+ double polynomialB = -2.0 * mu * B * (A + E);
+ double polynomialC = -(mu * mu) * (B * B);
+
+ Func function = r2 => System.Math.Pow(r2, 8) + polynomialA * System.Math.Pow(r2, 6) +
+ polynomialB * System.Math.Pow(r2, 3) + polynomialC;
+
+ Func derivative = r2 => 8.0 * System.Math.Pow(r2, 7) +
+ 6.0 * polynomialA * System.Math.Pow(r2, 5) +
+ 3.0 * polynomialB * System.Math.Pow(r2, 2);
+
+ // Use improved initial guess based on expected range
+ double scaledInitialGuess = expectedRangeFromObserver / distanceScale;
+ double root_distance = NewtonRaphson.Solve(function, derivative, scaledInitialGuess, 1E-12, 1000);
+
+ double r23 = System.Math.Pow(root_distance, 3);
+ double numerator = 6 * (d31 * (tau1 / tau3) + d21 * (tau / tau3)) * r23 +
+ mu * d31 * (tau * tau - tau1 * tau1) * (tau1 / tau3);
+
+ double denominator = 6 * r23 + mu * (tau * tau - tau3 * tau3);
+
+ double rho1 = (1 / d0) * ((numerator / denominator) - d11);
+
+ // Calculate ρ₃ (rho3)
+ double numerator3 = 6 * (d13 * (tau3 / tau1) - d23 * (tau / tau1)) * r23 + mu * d13 * (tau * tau - tau3 * tau3) * (tau3 / tau1);
+
+ double denominator3 = 6 * r23 + mu * (tau * tau - tau1 * tau1);
+
+ double rho3 = (1 / d0) * ((numerator3 / denominator3) - d33);
+
+ // Calculate ρ₂ (rho2)
+ double rho2 = A + ((mu * B) / r23);
+
+ // Step 7: Compute position vectors
+ Vector3 r1 = rhoHat1 * rho1 + R1;
+ Vector3 r2 = rhoHat2 * rho2 + R2;
+ Vector3 r3 = rhoHat3 * rho3 + R3;
+
+ // Calculate higher-order terms for f1 and f3
+ double r2Cubed = System.Math.Pow(r2.Magnitude(), 3);
+ double r2Sixth = System.Math.Pow(r2.Magnitude(), 6);
+
+ double f1 = 1 - (mu * System.Math.Pow(tau1, 2)) / (2 * r2Cubed) +
+ (mu * mu * System.Math.Pow(tau1, 4)) / (24 * r2Sixth);
+
+ double f3 = 1 - (mu * System.Math.Pow(tau3, 2)) / (2 * r2Cubed) +
+ (mu * mu * System.Math.Pow(tau3, 4)) / (24 * r2Sixth);
+
+// Calculate higher-order terms for g1 and g3
+ double g1 = tau1 - (mu * System.Math.Pow(tau1, 3)) / (6 * r2Cubed) +
+ (mu * mu * System.Math.Pow(tau1, 5)) / (120 * r2Sixth);
+
+ double g3 = tau3 - (mu * System.Math.Pow(tau3, 3)) / (6 * r2Cubed) +
+ (mu * mu * System.Math.Pow(tau3, 5)) / (120 * r2Sixth);
+
+
+ // Step 9: Compute velocity vector using full Lagrange formulation
+ Vector3 v2 = (r1 * -f3 + r3 * f1) * (1 / (f1 * g3 - f3 * g1));
+
+ // Step 10: Convert position and velocity to Keplerian elements
+ var sv = new StateVector(r2 * distanceScale, v2 * distanceScale, expectedCenterOfMotion, observation2.Epoch, Frame.ICRF);
+ return new OrbitDetermination(sv, score.GlobalReliability, score.GeometricReliability, score.TemporalSpacingReliability, score.CenterOfMotionReliability);
+ }
+
+ private static QualityMetrics CheckObservationQuality(Vector3[] observerPositions,
+ Vector3[] apparentDirections,
+ Time[] observationTimes)
+ {
+ if (observerPositions.Length != 3 || apparentDirections.Length != 3 || observationTimes.Length != 3)
+ throw new ArgumentException("Exactly 3 observations are required");
+
+ var sightLines = apparentDirections.Select(dir => dir.Normalize()).ToList();
+
+ // geometrics quality
+ var normal = sightLines[0].Cross(sightLines[1]).Normalize();
+ var coplanarity = 1 - System.Math.Abs(normal * sightLines[2]);
+
+ var angle1 = System.Math.Acos(sightLines[0] * sightLines[1]);
+ var angle2 = System.Math.Acos(sightLines[1] * sightLines[2]);
+
+ var normalizedAngle1 = angle1 / (System.Math.PI / 2);
+ var normalizedAngle2 = angle2 / (System.Math.PI / 2);
+
+ const double geometricOptimalAngle = 0.5; // 45° normalisé
+ var angleQuality1 = 1 - System.Math.Abs(normalizedAngle1 - geometricOptimalAngle);
+ var angleQuality2 = 1 - System.Math.Abs(normalizedAngle2 - geometricOptimalAngle);
+
+ var geometricQuality = coplanarity * System.Math.Min(angleQuality1, angleQuality2);
+
+ // temporal quality
+ TimeSpan totalDuration = observationTimes[2] - observationTimes[0];
+ TimeSpan idealSpacing = totalDuration / 2;
+ TimeSpan middleSpacing = observationTimes[1] - observationTimes[0];
+ double temporalQuality = 1 - System.Math.Abs(middleSpacing.TotalSeconds - idealSpacing.TotalSeconds) / totalDuration.TotalSeconds;
+
+ // center of motion quality
+ var centerOfMotionQuality = 0.0;
+ for (int i = 0; i < 3; i++)
+ {
+ var observerDirection = observerPositions[i].Normalize();
+ var angle = System.Math.Abs(sightLines[i] * observerDirection);
+ const double optimalAngle = 0.25;
+ var normalizedDistance = System.Math.Abs(angle - optimalAngle);
+ var quality = System.Math.Exp(-2 * normalizedDistance);
+
+ centerOfMotionQuality += quality;
+ }
+
+ centerOfMotionQuality /= 3;
+
+ double W_GEOMETRIC = 0.15;
+ double W_TEMPORAL = 0.05;
+ double W_CENTER = 0.8;
+
+ if (observerPositions[0].Magnitude() < 1E05)
+ {
+ W_GEOMETRIC = 0.4;
+ W_TEMPORAL = 0.5;
+ W_CENTER = 0.1;
+ }
+
+ var globalQuality =
+ W_GEOMETRIC * geometricQuality +
+ W_TEMPORAL * temporalQuality +
+ W_CENTER * centerOfMotionQuality;
+
+ return new QualityMetrics
+ {
+ GeometricReliability = geometricQuality,
+ TemporalSpacingReliability = temporalQuality,
+ CenterOfMotionReliability = centerOfMotionQuality,
+ GlobalReliability = globalQuality
+ };
+ }
+
+ public record QualityMetrics
+ {
+ public double GeometricReliability { get; init; }
+ public double TemporalSpacingReliability { get; init; }
+ public double CenterOfMotionReliability { get; init; }
+ public double GlobalReliability { get; init; }
+ }
+}
\ No newline at end of file
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs
index 72b6b817..394ef469 100644
--- a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs
@@ -200,7 +200,7 @@ public override int GetHashCode()
public override string ToString()
{
- return $"Epoch : {Epoch.ToString()} A : {A} Ecc. : {E} Inc. : {I} AN : {RAAN} AOP : {AOP} M : {M} Frame : {Frame.Name}";
+ return $"Epoch : {Epoch.ToString()} A : {A}, Ecc. : {E}, Inc. : {I}, AN : {RAAN}, AOP : {AOP}, M : {M}, Frame : {Frame.Name}";
}
#endregion
}
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitDetermination.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitDetermination.cs
new file mode 100644
index 00000000..68c49cf7
--- /dev/null
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitDetermination.cs
@@ -0,0 +1,54 @@
+using System;
+
+namespace IO.Astrodynamics.OrbitalParameters;
+
+public record OrbitDetermination
+{
+ public OrbitalParameters OrbitalParameters { get; }
+ public double Reliability { get; }
+ public double GeometricReliability { get; }
+ public double TemporalReliability { get; }
+ public double CenterOfMotionQualityReliability { get; }
+
+ public OrbitDetermination(OrbitalParameters orbitalParameters, double reliability, double geometricReliability, double temporalReliability, double centerOfMotionReliability)
+ {
+ if (orbitalParameters == null) throw new ArgumentNullException(nameof(orbitalParameters));
+ ArgumentOutOfRangeException.ThrowIfNegative(reliability);
+ if (reliability > 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(reliability), "Reliability must be between 0 and 1.");
+ }
+
+ ArgumentOutOfRangeException.ThrowIfNegative(centerOfMotionReliability);
+ if (centerOfMotionReliability > 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(centerOfMotionReliability), "Center of motion quality Reliability must be between 0 and 1.");
+ }
+
+ ArgumentOutOfRangeException.ThrowIfNegative(temporalReliability);
+ if (temporalReliability > 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(temporalReliability), "Temporal Reliability must be between 0 and 1.");
+ }
+
+ ArgumentOutOfRangeException.ThrowIfNegative(geometricReliability);
+ if (geometricReliability > 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(geometricReliability), "Geometric Reliability must be between 0 and 1.");
+ }
+
+ OrbitalParameters = orbitalParameters;
+ Reliability = reliability;
+ GeometricReliability = geometricReliability;
+ TemporalReliability = temporalReliability;
+ CenterOfMotionQualityReliability = centerOfMotionReliability;
+ }
+
+ public override string ToString()
+ {
+ return $"Reliability : {Reliability * 100}%\n" +
+ $"Geometric reliability : {GeometricReliability * 100}%\n" +
+ $"Temporal reliability : {TemporalReliability * 100}%\n" +
+ $"Center of motion reliability : {CenterOfMotionQualityReliability * 100}%";
+ }
+}
\ No newline at end of file
diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs
index 9ced1f74..f71476d0 100644
--- a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs
+++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs
@@ -4,6 +4,7 @@
using IO.Astrodynamics.Coordinates;
using IO.Astrodynamics.Frames;
using IO.Astrodynamics.Math;
+using IO.Astrodynamics.SolarSystemObjects;
using IO.Astrodynamics.TimeSystem;
namespace IO.Astrodynamics.OrbitalParameters;
@@ -934,6 +935,10 @@ public double SemiLatusRectum()
return hNorm * hNorm / Observer.GM;
}
+
+
+ #region Operators
+
public override bool Equals(object obj)
{
return Equals(obj as OrbitalParameters);
@@ -959,4 +964,6 @@ public override int GetHashCode()
{
return !(left == right);
}
+
+ #endregion
}
\ No newline at end of file