Skip to content

Commit

Permalink
feat: add orbit pass computation logic (#318)
Browse files Browse the repository at this point in the history
* feat: add orbit pass computation logic

* feat: add more tests

* feat: fix tests

* chore: remove unnecessary variable

* feat: rename some fields, add ascending/descending node fields
  • Loading branch information
vishwa2710 authored Jan 19, 2024
1 parent 6e89a7f commit 4ec6f95
Show file tree
Hide file tree
Showing 10 changed files with 1,147 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,23 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Orbit(pybind11::module& a
)doc"
)

.def_static(
"generate_pass_map",
&Orbit::GeneratePassMap,
arg("states"),
arg("initial_revolution_number"),
R"doc(
Generate a pass map from a set of states.
Args:
states (Array<State>): The states.
initial_revolution_number (Integer): The initial revolution number.
Returns:
PassMap: The pass map.
)doc"
)

;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,23 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Orbit_Pass(pybind11::modu
pass_class

.def(
init<const Pass::Type&, const Integer&, const Interval&>(),
init<const Pass::Type&, const Integer&, const Interval&, const Instant&, const Instant&, const Instant&>(),
arg("type"),
arg("revolution_number"),
arg("interval"),
arg("descending_node_instant"),
arg("north_point_instant"),
arg("south_point_instant"),
R"doc(
Constructor.
Args:
type (Pass.Type): The type of the pass.
revolution_number (int): The revolution number of the pass.
interval (Interval): The interval of the pass.
descending_node_instant (Instant): The instant at the descending node of the pass.
north_point_instant (Instant): The instant at the north point of the pass.
south_point_instant (Instant): The instant at the south point of the pass.
)doc"
)
Expand Down Expand Up @@ -147,6 +153,50 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Orbit_Pass(pybind11::modu
)doc"
)
.def(
"get_instant_at_ascending_node",
&Pass::accessInstantAtAscendingNode,
R"doc(
Get the instant at the ascending node of the pass.
Returns:
Instant: The instant at the ascending node of the pass.
)doc"
)
.def(
"get_instant_at_descending_node",
&Pass::accessInstantAtDescendingNode,
R"doc(
Get the instant at the descending node of the pass.
Returns:
Instant: The instant at the descending node of the pass.
)doc"
)
.def(
"get_instant_at_north_point",
&Pass::accessInstantAtNorthPoint,
R"doc(
Get the instant at the north point of the pass.
Returns:
Instant: The instant at the north point of the pass.
)doc"
)
.def(
"get_instant_at_south_point",
&Pass::accessInstantAtSouthPoint,
R"doc(
Get the instant at the south point of the pass.
Returns:
Instant: The instant at the south point of the pass.
)doc"
)

.def_static(
"undefined",
Expand Down
100 changes: 57 additions & 43 deletions bindings/python/test/trajectory/orbit/test_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,68 @@

import pytest

import ostk.physics as physics

import ostk.astrodynamics as astrodynamics

Length = physics.units.Length
Angle = physics.units.Angle
Scale = physics.time.Scale
Instant = physics.time.Instant
Interval = physics.time.Interval
DateTime = physics.time.DateTime
Position = physics.coordinate.Position
Velocity = physics.coordinate.Velocity
Frame = physics.coordinate.Frame
Environment = physics.Environment

Trajectory = astrodynamics.Trajectory
Model = astrodynamics.trajectory.Model
Orbit = astrodynamics.trajectory.Orbit
Pass = astrodynamics.trajectory.orbit.Pass
Kepler = astrodynamics.trajectory.orbit.models.Kepler
COE = astrodynamics.trajectory.orbit.models.kepler.COE
SGP4 = astrodynamics.trajectory.orbit.models.SGP4
TLE = astrodynamics.trajectory.orbit.models.sgp4.TLE
State = astrodynamics.trajectory.State
Access = astrodynamics.Access
from ostk.physics.time import Scale
from ostk.physics.time import Instant
from ostk.physics.time import Interval
from ostk.physics.time import DateTime
from ostk.physics import Environment

from ostk.astrodynamics.trajectory.orbit import Pass

earth = Environment.default().access_celestial_object_with_name("Earth")


def test_trajectory_orbit_pass():
pass_type = Pass.Type.Partial
pass_revolution_number = 123
pass_start_instant = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
pass_end_instant = Instant.date_time(DateTime(2018, 1, 1, 1, 0, 0), Scale.UTC)
pass_interval = Interval.closed(pass_start_instant, pass_end_instant)
@pytest.fixture
def pass_() -> Pass:
return Pass(
Pass.Type.Partial,
123,
Interval.closed(
Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC),
Instant.date_time(DateTime(2018, 1, 1, 1, 0, 0), Scale.UTC),
),
Instant.date_time(DateTime(2018, 1, 1, 0, 30, 0), Scale.UTC),
Instant.date_time(DateTime(2018, 1, 1, 0, 15, 0), Scale.UTC),
Instant.date_time(DateTime(2018, 1, 1, 0, 45, 0), Scale.UTC),
)


class TestPass:
def test_is_defined(self, pass_: Pass):
assert pass_.is_defined()

def test_is_complete(self, pass_: Pass):
assert pass_.is_complete() is not None

def test_get_type(self, pass_: Pass):
assert pass_.get_type() is not None

def test_get_revolution_number(self, pass_: Pass):
assert pass_.get_revolution_number() is not None

def test_get_interval(self, pass_: Pass):
assert pass_.get_interval() is not None

def test_get_instant_at_ascending_node(self, pass_: Pass):
assert pass_.get_instant_at_ascending_node() is not None

def test_get_instant_at_descending_node(self, pass_: Pass):
assert pass_.get_instant_at_descending_node() is not None

def test_get_instant_at_north_point(self, pass_: Pass):
assert pass_.get_instant_at_north_point() is not None

def test_get_instant_at_south_point(self, pass_: Pass):
assert pass_.get_instant_at_south_point() is not None

pass_ = Pass(pass_type, pass_revolution_number, pass_interval)
def test_undefined(self):
assert Pass.undefined().is_defined() is False

assert pass_ is not None
assert isinstance(pass_, Pass)
def test_string_from_type(self):
assert Pass.string_from_type(Pass.Type.Complete) is not None

assert pass_.is_defined()
assert pass_.is_complete() is not None
assert pass_.get_type() is not None
assert pass_.get_revolution_number() is not None
# Interval conversion to Python type of issue
# assert pass_.get_interval() is not None
def test_string_from_phase(self):
assert Pass.string_from_phase(Pass.Phase.Ascending) is not None

assert Pass.string_from_type(Pass.Type.Complete) is not None
assert Pass.string_from_phase(Pass.Phase.Ascending) is not None
assert Pass.string_from_quarter(Pass.Quarter.First) is not None
def test_string_from_quarter(self):
assert Pass.string_from_quarter(Pass.Quarter.First) is not None
76 changes: 66 additions & 10 deletions bindings/python/test/trajectory/test_orbit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,43 @@

import pytest

from ostk.physics import Environment
from ostk.physics.environment.objects.celestial_bodies import Earth
from ostk.physics.units import Length, Angle
from ostk.physics.time import Scale, Instant, DateTime, Time
from ostk.physics.time import Scale, Instant, DateTime, Time, Duration, Interval

from ostk.astrodynamics.trajectory import Orbit, State
from ostk.astrodynamics.trajectory.orbit import Pass
from ostk.astrodynamics.trajectory.orbit import Pass
from ostk.astrodynamics.trajectory.orbit.models import SGP4
from ostk.astrodynamics.trajectory.orbit.models.sgp4 import TLE


@pytest.fixture
def earth():
return Environment.default().access_celestial_object_with_name("Earth")
def earth() -> Earth:
return Earth.default()


@pytest.fixture
def epoch() -> Instant:
return Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)


@pytest.fixture
def orbit(earth: Earth, epoch: Instant):
return Orbit.sun_synchronous(epoch, Length.kilometers(500.0), Time.midnight(), earth)


@pytest.fixture
def states(orbit: Orbit, epoch: Instant) -> list[State]:
instants: list[Instant] = Interval.closed(
epoch, epoch + Duration.days(1.0)
).generate_grid(Duration.seconds(20.0))

return orbit.get_states_at(instants)


class TestOrbit:
def test_trajectory_orbit_constructors(self, earth):
def test_constructors(self, earth):
# Construct Two-Line Element set
tle = TLE(
"1 25544U 98067A 18231.17878740 .00000187 00000-0 10196-4 0 9994",
Expand All @@ -37,14 +58,45 @@ def test_trajectory_orbit_constructors(self, earth):
assert state is not None
assert isinstance(state, State)

def test_trajectory_orbit_circular(self, earth):
def test_get_revolution_number_at(self, orbit: Orbit):
assert (
orbit.get_revolution_number_at(
Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
)
== 1
)

def test_get_pass_at(self, orbit: Orbit):
pass_ = orbit.get_pass_at(
Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
)

assert pass_ is not None
assert isinstance(pass_, Pass)
assert pass_.is_defined()

def test_get_pass_with_revolution_number(self, orbit: Orbit):
pass_ = orbit.get_pass_with_revolution_number(1)

assert pass_ is not None
assert isinstance(pass_, Pass)
assert pass_.is_defined()

def test_undefined(self):
assert Orbit.undefined().is_defined() is False

def test_circular(self, earth):
epoch = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
altitude = Length.kilometers(500.0)
inclination = Angle.degrees(45.0)

orbit: Orbit = Orbit.circular(epoch, altitude, inclination, earth)

def test_trajectory_orbit_equatorial(self, earth):
assert orbit is not None
assert isinstance(orbit, Orbit)
assert orbit.is_defined()

def test_equatorial(self, earth):
epoch = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
apoapsis_altitude = Length.kilometers(500.1)
periapsis_altitude = Length.kilometers(499.9)
Expand All @@ -57,7 +109,7 @@ def test_trajectory_orbit_equatorial(self, earth):
assert isinstance(orbit, Orbit)
assert orbit.is_defined()

def test_trajectory_orbit_circular_equatorial(self, earth):
def test_circular_equatorial(self, earth):
epoch = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
altitude = Length.kilometers(500.0)

Expand All @@ -67,7 +119,7 @@ def test_trajectory_orbit_circular_equatorial(self, earth):
assert isinstance(orbit, Orbit)
assert orbit.is_defined()

def test_trajectory_orbit_geo_synchronous(self, earth):
def test_geo_synchronous(self, earth):
epoch = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
inclination = Angle.degrees(45.0)
longitude = Angle.degrees(45.0)
Expand All @@ -78,7 +130,7 @@ def test_trajectory_orbit_geo_synchronous(self, earth):
assert isinstance(orbit, Orbit)
assert orbit.is_defined()

def test_trajectory_orbit_sun_synchronous(self, earth):
def test_sun_synchronous(self, earth):
epoch = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
altitude = Length.kilometers(500.0)
local_time_at_descending_node = Time.midnight()
Expand All @@ -98,3 +150,7 @@ def test_trajectory_orbit_sun_synchronous(self, earth):
celestial_object=earth,
argument_of_latitude=Angle.degrees(50.0),
).is_defined()

def test_generate_pass_map(self, orbit: Orbit, states: list[State]):
pass_map: dict[int, Pass] = orbit.generate_pass_map(states, 1)
assert pass_map is not None
6 changes: 5 additions & 1 deletion include/OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ class Orbit : public Trajectory

static String StringFromFrameType(const Orbit::FrameType& aFrameType);

static Map<Index, Pass> GeneratePassMap(const Array<State>& aStateArray, const Integer& anInitialRevolutionNumber);

private:
const orbit::Model* modelPtr_;

Expand All @@ -200,7 +202,9 @@ class Orbit : public Trajectory

String generateFrameName(const Orbit::FrameType& aFrameType) const;

static Map<Index, Pass> GeneratePassMap(const Array<State>& aStateArray, const Integer& anInitialRevolutionNumber);
static Instant GetCrossingInstant(
const Instant& previousInstant, const Instant& currentInstant, const std::function<double(double)>& getValue
);

static Array<State> GenerateStates(const Model& aModel, const Array<Instant>& anInstantGrid);
};
Expand Down
Loading

0 comments on commit 4ec6f95

Please sign in to comment.