open-space-toolkit-astrodynamics 9.4.1__py38-none-manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. open_space_toolkit_astrodynamics-9.4.1.dist-info/METADATA +30 -0
  2. open_space_toolkit_astrodynamics-9.4.1.dist-info/RECORD +98 -0
  3. open_space_toolkit_astrodynamics-9.4.1.dist-info/WHEEL +5 -0
  4. open_space_toolkit_astrodynamics-9.4.1.dist-info/top_level.txt +1 -0
  5. open_space_toolkit_astrodynamics-9.4.1.dist-info/zip-safe +1 -0
  6. ostk/__init__.py +1 -0
  7. ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-38-x86_64-linux-gnu.so +0 -0
  8. ostk/astrodynamics/__init__.py +11 -0
  9. ostk/astrodynamics/converters.py +185 -0
  10. ostk/astrodynamics/display.py +220 -0
  11. ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.9 +0 -0
  12. ostk/astrodynamics/pytrajectory/__init__.py +1 -0
  13. ostk/astrodynamics/pytrajectory/pystate.py +36 -0
  14. ostk/astrodynamics/test/__init__.py +1 -0
  15. ostk/astrodynamics/test/access/__init__.py +1 -0
  16. ostk/astrodynamics/test/access/test_generator.py +248 -0
  17. ostk/astrodynamics/test/conftest.py +119 -0
  18. ostk/astrodynamics/test/conjunction/message/ccsds/__init__.py +1 -0
  19. ostk/astrodynamics/test/conjunction/message/ccsds/conftest.py +325 -0
  20. ostk/astrodynamics/test/conjunction/message/ccsds/data/cdm.json +303 -0
  21. ostk/astrodynamics/test/conjunction/message/ccsds/test_cdm.py +416 -0
  22. ostk/astrodynamics/test/dynamics/__init__.py +1 -0
  23. ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity.csv +565 -0
  24. ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity_Truth.csv +100 -0
  25. ostk/astrodynamics/test/dynamics/test_atmospheric_drag.py +128 -0
  26. ostk/astrodynamics/test/dynamics/test_central_body_gravity.py +58 -0
  27. ostk/astrodynamics/test/dynamics/test_dynamics.py +50 -0
  28. ostk/astrodynamics/test/dynamics/test_position_derivative.py +51 -0
  29. ostk/astrodynamics/test/dynamics/test_tabulated.py +138 -0
  30. ostk/astrodynamics/test/dynamics/test_third_body_gravity.py +67 -0
  31. ostk/astrodynamics/test/dynamics/test_thruster.py +142 -0
  32. ostk/astrodynamics/test/event_condition/test_angular_condition.py +113 -0
  33. ostk/astrodynamics/test/event_condition/test_boolean_condition.py +55 -0
  34. ostk/astrodynamics/test/event_condition/test_coe_condition.py +87 -0
  35. ostk/astrodynamics/test/event_condition/test_instant_condition.py +48 -0
  36. ostk/astrodynamics/test/event_condition/test_logical_condition.py +120 -0
  37. ostk/astrodynamics/test/event_condition/test_real_condition.py +50 -0
  38. ostk/astrodynamics/test/flight/__init__.py +1 -0
  39. ostk/astrodynamics/test/flight/system/__init__.py +1 -0
  40. ostk/astrodynamics/test/flight/system/test_propulsion_system.py +73 -0
  41. ostk/astrodynamics/test/flight/system/test_satellite_system.py +91 -0
  42. ostk/astrodynamics/test/flight/system/test_satellite_system_builder.py +71 -0
  43. ostk/astrodynamics/test/flight/test_maneuver.py +212 -0
  44. ostk/astrodynamics/test/flight/test_profile.py +153 -0
  45. ostk/astrodynamics/test/flight/test_system.py +55 -0
  46. ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +91 -0
  47. ostk/astrodynamics/test/guidance_law/test_qlaw.py +138 -0
  48. ostk/astrodynamics/test/solvers/__init__.py +1 -0
  49. ostk/astrodynamics/test/solvers/test_finite_difference_solver.py +181 -0
  50. ostk/astrodynamics/test/solvers/test_temporal_condition_solver.py +153 -0
  51. ostk/astrodynamics/test/test_access.py +128 -0
  52. ostk/astrodynamics/test/test_converters.py +387 -0
  53. ostk/astrodynamics/test/test_display.py +115 -0
  54. ostk/astrodynamics/test/test_event_condition.py +58 -0
  55. ostk/astrodynamics/test/test_import.py +26 -0
  56. ostk/astrodynamics/test/test_root_solver.py +70 -0
  57. ostk/astrodynamics/test/test_trajectory.py +40 -0
  58. ostk/astrodynamics/test/test_utilities.py +121 -0
  59. ostk/astrodynamics/test/test_viewer.py +129 -0
  60. ostk/astrodynamics/test/trajectory/__init__.py +1 -0
  61. ostk/astrodynamics/test/trajectory/orbit/__init__.py +1 -0
  62. ostk/astrodynamics/test/trajectory/orbit/message/__init__.py +1 -0
  63. ostk/astrodynamics/test/trajectory/orbit/message/spacex/__init__.py +1 -0
  64. ostk/astrodynamics/test/trajectory/orbit/message/spacex/conftest.py +18 -0
  65. ostk/astrodynamics/test/trajectory/orbit/message/spacex/data/opm_1.yaml +44 -0
  66. ostk/astrodynamics/test/trajectory/orbit/message/spacex/test_opm.py +108 -0
  67. ostk/astrodynamics/test/trajectory/orbit/models/__init__.py +1 -0
  68. ostk/astrodynamics/test/trajectory/orbit/models/kepler/__init__.py +1 -0
  69. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean.py +65 -0
  70. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_long.py +102 -0
  71. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_short.py +102 -0
  72. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +167 -0
  73. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/__init__.py +1 -0
  74. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/test_tle.py +331 -0
  75. ostk/astrodynamics/test/trajectory/orbit/models/test_kepler.py +130 -0
  76. ostk/astrodynamics/test/trajectory/orbit/models/test_propagated.py +234 -0
  77. ostk/astrodynamics/test/trajectory/orbit/models/test_sgp4.py +1 -0
  78. ostk/astrodynamics/test/trajectory/orbit/models/test_tabulated.py +380 -0
  79. ostk/astrodynamics/test/trajectory/orbit/test_model.py +1 -0
  80. ostk/astrodynamics/test/trajectory/orbit/test_pass.py +72 -0
  81. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_angular_velocity.py +30 -0
  82. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quaternion.py +18 -0
  83. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py +107 -0
  84. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py +115 -0
  85. ostk/astrodynamics/test/trajectory/state/test_coordinate_broker.py +84 -0
  86. ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +46 -0
  87. ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +314 -0
  88. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_direction.py +81 -0
  89. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +76 -0
  90. ostk/astrodynamics/test/trajectory/test_model.py +1 -0
  91. ostk/astrodynamics/test/trajectory/test_orbit.py +174 -0
  92. ostk/astrodynamics/test/trajectory/test_propagator.py +458 -0
  93. ostk/astrodynamics/test/trajectory/test_segment.py +305 -0
  94. ostk/astrodynamics/test/trajectory/test_sequence.py +477 -0
  95. ostk/astrodynamics/test/trajectory/test_state.py +237 -0
  96. ostk/astrodynamics/test/trajectory/test_state_builder.py +171 -0
  97. ostk/astrodynamics/utilities.py +158 -0
  98. ostk/astrodynamics/viewer.py +392 -0
@@ -0,0 +1,171 @@
1
+ # Apache License 2.0
2
+
3
+ import pytest
4
+
5
+ import numpy as np
6
+
7
+ from ostk.physics.time import Instant
8
+ from ostk.physics.time import DateTime
9
+ from ostk.physics.time import Scale
10
+ from ostk.physics.coordinate import Frame
11
+
12
+ from ostk.astrodynamics.trajectory import (
13
+ State,
14
+ StateBuilder,
15
+ )
16
+ from ostk.astrodynamics.trajectory.state import (
17
+ CoordinateBroker,
18
+ CoordinateSubset,
19
+ )
20
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import (
21
+ CartesianPosition,
22
+ CartesianVelocity,
23
+ )
24
+
25
+
26
+ @pytest.fixture()
27
+ def instant() -> Instant:
28
+ return Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
29
+
30
+
31
+ @pytest.fixture
32
+ def frame() -> Frame:
33
+ return Frame.GCRF()
34
+
35
+
36
+ @pytest.fixture
37
+ def coordinate_subsets() -> list[CoordinateSubset]:
38
+ return [CartesianPosition.default(), CartesianVelocity.default()]
39
+
40
+
41
+ @pytest.fixture
42
+ def coordinates() -> list[float]:
43
+ return [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
44
+
45
+
46
+ @pytest.fixture
47
+ def coordinate_broker(
48
+ coordinate_subsets: list[CoordinateSubset],
49
+ ) -> CoordinateBroker:
50
+ return CoordinateBroker(coordinate_subsets)
51
+
52
+
53
+ @pytest.fixture
54
+ def state(
55
+ instant: Instant,
56
+ coordinates: list[float],
57
+ frame: Frame,
58
+ coordinate_broker: CoordinateBroker,
59
+ ) -> State:
60
+ return State(instant, coordinates, frame, coordinate_broker)
61
+
62
+
63
+ @pytest.fixture
64
+ def state_builder(frame: Frame, coordinate_broker: CoordinateBroker) -> State:
65
+ return StateBuilder(frame, coordinate_broker)
66
+
67
+
68
+ class TestStateBuilder:
69
+ def test_broker_constructor(
70
+ self,
71
+ frame: Frame,
72
+ coordinate_broker: CoordinateBroker,
73
+ ):
74
+ builder = StateBuilder(frame, coordinate_broker)
75
+ assert builder is not None
76
+ assert isinstance(builder, StateBuilder)
77
+ assert builder.is_defined()
78
+
79
+ def test_subsets_constructor(
80
+ self,
81
+ frame: Frame,
82
+ coordinate_subsets: list[CoordinateSubset],
83
+ ):
84
+ builder = StateBuilder(frame, coordinate_subsets)
85
+ assert builder is not None
86
+ assert isinstance(builder, StateBuilder)
87
+ assert builder.is_defined()
88
+
89
+ def test_state_constructor(
90
+ self,
91
+ state: State,
92
+ ):
93
+ builder = StateBuilder(state)
94
+ assert builder is not None
95
+ assert isinstance(builder, StateBuilder)
96
+ assert builder.is_defined()
97
+
98
+ def test_comparators(self, state_builder: StateBuilder):
99
+ assert (state_builder == state_builder) is True
100
+ assert (state_builder != state_builder) is False
101
+
102
+ def test_operators(
103
+ self,
104
+ state_builder: StateBuilder,
105
+ ):
106
+ added_builder: StateBuilder = state_builder + CoordinateSubset.mass()
107
+ assert isinstance(added_builder, StateBuilder)
108
+ assert state_builder != added_builder
109
+
110
+ subtracted_builder: StateBuilder = state_builder - CartesianPosition.default()
111
+ assert isinstance(subtracted_builder, StateBuilder)
112
+ assert state_builder != subtracted_builder
113
+
114
+ def test_build(
115
+ self,
116
+ instant: Instant,
117
+ state_builder: StateBuilder,
118
+ ):
119
+ coordinates = [1, 2, 3, 1, 2, 3]
120
+ state: State = state_builder.build(instant, coordinates)
121
+
122
+ assert state is not None
123
+ assert isinstance(state, State)
124
+ assert state.is_defined()
125
+ assert state.get_instant() == instant
126
+ assert (state.get_coordinates() == coordinates).all()
127
+ assert state.get_frame() == state_builder.get_frame()
128
+ assert state.get_coordinate_subsets() == state_builder.get_coordinate_subsets()
129
+
130
+ def test_reduce(
131
+ self,
132
+ state: State,
133
+ ):
134
+ builder = StateBuilder(state.get_frame(), [CartesianPosition.default()])
135
+ reduced_state: State = builder.reduce(state)
136
+
137
+ assert isinstance(reduced_state, State)
138
+ assert state != reduced_state
139
+
140
+ def test_expand(
141
+ self,
142
+ state: State,
143
+ ):
144
+ builder = StateBuilder(
145
+ state.get_frame(),
146
+ [
147
+ CartesianPosition.default(),
148
+ CartesianVelocity.default(),
149
+ CoordinateSubset.mass(),
150
+ ],
151
+ )
152
+ default_state: State = State(
153
+ state.get_instant(),
154
+ [100],
155
+ state.get_frame(),
156
+ CoordinateBroker([CoordinateSubset.mass()]),
157
+ )
158
+ expanded_state: State = builder.expand(state, default_state)
159
+
160
+ assert isinstance(expanded_state, State)
161
+ assert state != expanded_state
162
+ assert default_state != expanded_state
163
+
164
+ def test_getters(
165
+ self,
166
+ state_builder: StateBuilder,
167
+ frame: Frame,
168
+ coordinate_broker: CoordinateBroker,
169
+ ):
170
+ assert state_builder.get_frame() == frame
171
+ assert state_builder.get_coordinate_subsets() == coordinate_broker.get_subsets()
@@ -0,0 +1,158 @@
1
+ # Apache License 2.0
2
+
3
+ from .OpenSpaceToolkitAstrodynamicsPy import *
4
+
5
+ from ostk.physics import Environment
6
+ from ostk.physics.time import Scale
7
+ from ostk.physics.time import Instant
8
+ from ostk.physics.time import Duration
9
+ from ostk.physics.time import Interval
10
+ from ostk.physics.coordinate.spherical import LLA
11
+ from ostk.physics.coordinate.spherical import AER
12
+ from ostk.physics.coordinate import Position
13
+ from ostk.physics.coordinate import Frame
14
+ from ostk.physics.environment.object.celestial import Earth
15
+ from ostk.physics.environment.gravitational import Earth as EarthGravitationalModel
16
+
17
+
18
+ def lla_from_state(state: trajectory.State) -> tuple[float, float, float]:
19
+ """
20
+ Return latitude (degrees), longitude (degrees), altitude (meters) float list from a state.
21
+ """
22
+
23
+ lla: LLA = lla_from_position(state.get_position(), state.get_instant())
24
+
25
+ return (
26
+ float(lla.get_latitude().in_degrees()),
27
+ float(lla.get_longitude().in_degrees()),
28
+ float(lla.get_altitude().in_meters()),
29
+ )
30
+
31
+
32
+ def lla_from_position(
33
+ position: Position,
34
+ instant: Instant | None = None,
35
+ ) -> LLA:
36
+ """
37
+ Return LLA from position and instant.
38
+ """
39
+
40
+ if position.access_frame() != Frame.ITRF():
41
+ if instant is None:
42
+ raise ValueError(
43
+ "Instant must be provided if position is not expressed in ECEF."
44
+ )
45
+ position: Position = position.in_frame(Frame.ITRF(), instant)
46
+
47
+ return LLA.cartesian(
48
+ position.get_coordinates(),
49
+ EarthGravitationalModel.EGM2008.equatorial_radius,
50
+ EarthGravitationalModel.EGM2008.flattening,
51
+ )
52
+
53
+
54
+ def position_from_lla(lla: LLA) -> Position:
55
+ """
56
+ Return position from lla.
57
+ """
58
+
59
+ return Position.meters(
60
+ lla.to_cartesian(
61
+ EarthGravitationalModel.EGM2008.equatorial_radius,
62
+ EarthGravitationalModel.EGM2008.flattening,
63
+ ),
64
+ Frame.ITRF(),
65
+ )
66
+
67
+
68
+ def compute_aer(
69
+ instant: Instant,
70
+ from_position: Position,
71
+ to_position: Position,
72
+ environment: Environment,
73
+ ) -> tuple[float, float, float]:
74
+ """
75
+ Return [azimuth (degrees), elevation (degrees), range (meters)] from Instant and Positions (observer, target).
76
+ """
77
+
78
+ from_lla: LLA = lla_from_position(from_position, instant)
79
+
80
+ earth: Earth = environment.access_celestial_object_with_name("Earth")
81
+ ned_frame: Frame = earth.get_frame_at(from_lla, Earth.FrameType.NED)
82
+
83
+ from_position_NED: Position = from_position.in_frame(ned_frame, instant)
84
+ to_position_NED: Position = to_position.in_frame(ned_frame, instant)
85
+
86
+ aer: AER = AER.from_position_to_position(from_position_NED, to_position_NED, True)
87
+
88
+ return (
89
+ float(aer.get_azimuth().in_degrees()),
90
+ float(aer.get_elevation().in_degrees()),
91
+ float(aer.get_range().in_meters()),
92
+ )
93
+
94
+
95
+ def compute_time_lla_aer_state(
96
+ state: trajectory.State,
97
+ from_position: Position,
98
+ environment: Environment,
99
+ ) -> tuple[Instant, float, float, float, float, float, float]:
100
+ """
101
+ Return [instant, latitude, longitude, altitude, azimuth, elevation, range] from State and observer Position.
102
+ """
103
+
104
+ instant: Instant = state.get_instant()
105
+
106
+ lla: tuple[float, float, float] = lla_from_state(state)
107
+ aer: AER = compute_aer(
108
+ instant,
109
+ from_position,
110
+ state.get_position().in_frame(Frame.ITRF(), state.get_instant()),
111
+ environment,
112
+ )
113
+
114
+ return (instant, lla[0], lla[1], lla[2], aer[0], aer[1], aer[2])
115
+
116
+
117
+ def compute_trajectory_geometry(
118
+ trajectory: Trajectory,
119
+ interval: Interval,
120
+ ) -> list[tuple[float, float, float]]:
121
+ """
122
+ Return [latitude (degrees), longitude (degrees), altitude (meters)] values along a Trajectory during Interval.
123
+ """
124
+
125
+ return [
126
+ lla_from_state(state)
127
+ for state in trajectory.get_states_at(
128
+ interval.generate_grid(Duration.minutes(1.0))
129
+ )
130
+ ]
131
+
132
+
133
+ def convert_state(
134
+ state: trajectory.State,
135
+ ) -> tuple[str, float, float, float, float, float, float, float, float, float]:
136
+ """
137
+ Convert an input (Instant, State) into dataframe-ready values.
138
+ """
139
+
140
+ lla: LLA = LLA.cartesian(
141
+ state.get_position()
142
+ .in_frame(Frame.ITRF(), state.get_instant())
143
+ .get_coordinates(),
144
+ EarthGravitationalModel.EGM2008.equatorial_radius,
145
+ EarthGravitationalModel.EGM2008.flattening,
146
+ )
147
+
148
+ instant: Instant = state.get_instant()
149
+
150
+ return (
151
+ repr(instant),
152
+ float(instant.get_modified_julian_date(Scale.UTC)),
153
+ *state.get_position().get_coordinates().transpose().tolist(),
154
+ *state.get_velocity().get_coordinates().transpose().tolist(),
155
+ float(lla.get_latitude().in_degrees()),
156
+ float(lla.get_longitude().in_degrees()),
157
+ float(lla.get_altitude().in_meters()),
158
+ )
@@ -0,0 +1,392 @@
1
+ # Apache License 2.0
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import operator
7
+ from dataclasses import dataclass
8
+
9
+ import numpy as np
10
+
11
+ try:
12
+ import cesiumpy
13
+
14
+ except ImportError:
15
+ ...
16
+
17
+ from ostk.mathematics.geometry.d3.transformation.rotation import Quaternion
18
+
19
+ from ostk.physics.unit import Length
20
+ from ostk.physics.unit import Angle
21
+ from ostk.physics.time import Instant, Interval, Duration
22
+ from ostk.physics.coordinate import Position
23
+ from ostk.physics.coordinate import Frame
24
+ from ostk.physics.coordinate.spherical import LLA
25
+
26
+ from ostk.astrodynamics.flight import Profile
27
+ from ostk.astrodynamics.trajectory import State
28
+
29
+ from .converters import coerce_to_datetime
30
+ from .utilities import lla_from_position
31
+
32
+
33
+ @dataclass
34
+ class Sensor:
35
+ name: str
36
+ direction: tuple[float, float, float] | np.ndarray
37
+ color: str
38
+
39
+
40
+ @dataclass
41
+ class ConicSensor(Sensor):
42
+ half_angle: Angle
43
+ length: Length
44
+
45
+
46
+ @dataclass
47
+ class RectangularSensor(Sensor):
48
+ x_half_angle: Angle
49
+ y_half_angle: Angle
50
+ radius: Length
51
+
52
+
53
+ class Viewer:
54
+ def __init__(
55
+ self,
56
+ interval: Interval,
57
+ cesium_token: str | None = None,
58
+ width: str = "1500px",
59
+ height: str = "800px",
60
+ ) -> None:
61
+ self._interval: Interval = interval
62
+
63
+ self._viewer: cesiumpy.Viewer = cesiumpy.Viewer(
64
+ width=width,
65
+ height=height,
66
+ clock_view_model=cesiumpy.ClockViewModel(
67
+ clock=cesiumpy.Clock(
68
+ start_time=coerce_to_datetime(interval.get_start()),
69
+ stop_time=coerce_to_datetime(interval.get_end()),
70
+ clock_range=cesiumpy.Clock.Range.CLAMPED,
71
+ can_animate=True,
72
+ should_animate=False,
73
+ )
74
+ ),
75
+ fullscreen_button=False,
76
+ home_button=False,
77
+ info_box=False,
78
+ timeline=True,
79
+ navigation_help_button=False,
80
+ navigation_instructions_initially_visible=False,
81
+ scene_mode_picker=False,
82
+ selection_indicator=False,
83
+ scene3d_only=True,
84
+ zoom_to_entity=True,
85
+ track_entity=True,
86
+ default_access_token=cesium_token,
87
+ )
88
+
89
+ @property
90
+ def interval(self) -> Interval:
91
+ return self._interval
92
+
93
+ def add_profile(
94
+ self,
95
+ profile: Profile,
96
+ step: Duration,
97
+ show_orbital_track: bool = False,
98
+ cesium_asset_id: int | None = None,
99
+ sensors: list[Sensor] | None = None,
100
+ show_xyz_axes: bool = False,
101
+ ) -> None:
102
+ """
103
+ Add Profile to Viewer.
104
+
105
+ Args:
106
+ profile (Profile): Profile to be added.
107
+ step (Duration): Step between two consecutive states.
108
+ show_orbital_track (bool, optional): Whether to show the orbital track. Defaults to False.
109
+ cesium_asset_id (int, optional): The Cesium asset ID. Defaults to None.
110
+ sensors (list[Sensor], optional): Sensors to be added to the asset. Defaults to None.
111
+ show_xyz_axes (bool, optional): Whether to show the XYZ axes. Defaults to False.
112
+ """
113
+
114
+ instants: list[Instant] = self._interval.generate_grid(step)
115
+ states: list[State] = profile.get_states_at(instants)
116
+ llas: list[LLA] = _generate_llas(states)
117
+
118
+ sensors = sensors or []
119
+ if show_xyz_axes:
120
+ sensors.extend(
121
+ [
122
+ ConicSensor(
123
+ name="x_axis",
124
+ direction=(+1.0, 0.0, 0.0),
125
+ half_angle=Angle.degrees(1.0),
126
+ length=Length.meters(2.0),
127
+ color="red",
128
+ ),
129
+ ConicSensor(
130
+ name="y_axis",
131
+ direction=(0.0, +1.0, 0.0),
132
+ half_angle=Angle.degrees(1.0),
133
+ length=Length.meters(2.0),
134
+ color="blue",
135
+ ),
136
+ ConicSensor(
137
+ name="z_axis",
138
+ direction=(0.0, 0.0, +1.0),
139
+ half_angle=Angle.degrees(1.0),
140
+ length=Length.meters(2.0),
141
+ color="green",
142
+ ),
143
+ ]
144
+ )
145
+
146
+ satellite = cesiumpy.Satellite(
147
+ position=_generate_sampled_position(instants, llas),
148
+ orientation=_generate_sampled_orientation(states),
149
+ availability=cesiumpy.TimeIntervalCollection(
150
+ intervals=[
151
+ cesiumpy.TimeInterval(
152
+ start=coerce_to_datetime(self._interval.get_start()),
153
+ stop=coerce_to_datetime(self._interval.get_end()),
154
+ ),
155
+ ],
156
+ ),
157
+ model=cesiumpy.IonResource(
158
+ asset_id=cesium_asset_id or 0
159
+ ), # TBM: Should be made more robust
160
+ sensors=[_cesium_from_ostk_sensor(sensor) for sensor in sensors or []],
161
+ )
162
+
163
+ satellite.render(self._viewer)
164
+
165
+ if show_orbital_track:
166
+ self._viewer.entities.add(
167
+ cesiumpy.Polyline(
168
+ positions=cesiumpy.entities.cartesian.Cartesian3Array(
169
+ functools.reduce(
170
+ operator.iconcat,
171
+ [
172
+ [
173
+ float(lla.get_longitude().in_degrees()),
174
+ float(lla.get_latitude().in_degrees()),
175
+ float(lla.get_altitude().in_meters()),
176
+ ]
177
+ for lla in _generate_llas(states)
178
+ ],
179
+ [],
180
+ )
181
+ ),
182
+ width=1,
183
+ )
184
+ )
185
+
186
+ def add_target(
187
+ self,
188
+ position: Position,
189
+ size: int | None = None,
190
+ color: str | None = None,
191
+ ) -> None:
192
+ """
193
+ Add target to Viewer.
194
+
195
+ Args:
196
+ position (Position): Target position.
197
+ size (int, optional): Target size. Defaults to None.
198
+ color (str, optional): Target color. Defaults to None.
199
+ """
200
+
201
+ self._viewer.entities.add(
202
+ cesiumpy.Point(
203
+ position=_cesium_from_ostk_position(position),
204
+ pixel_size=size or 10,
205
+ color=color or cesiumpy.color.YELLOW,
206
+ )
207
+ )
208
+
209
+ def add_line(
210
+ self,
211
+ positions: list[Position],
212
+ size: int | None = None,
213
+ color: str | None = None,
214
+ ) -> None:
215
+ """
216
+ Add line to Viewer.
217
+
218
+ Args:
219
+ positions (list[Position]): Line positions.
220
+ size (int, optional): Line size. Defaults to None.
221
+ color (str, optional): Line color. Defaults to None.
222
+ """
223
+
224
+ self._viewer.entities.add(
225
+ cesiumpy.Polyline(
226
+ positions=cesiumpy.entities.cartesian.Cartesian3Array(
227
+ functools.reduce(
228
+ operator.iconcat,
229
+ [
230
+ [
231
+ float(lla.get_longitude().in_degrees()),
232
+ float(lla.get_latitude().in_degrees()),
233
+ 10.0,
234
+ ]
235
+ for lla in map(lla_from_position, positions)
236
+ ],
237
+ [],
238
+ )
239
+ ),
240
+ width=size or 1,
241
+ material=color or cesiumpy.color.YELLOW,
242
+ )
243
+ )
244
+
245
+ def render(self) -> str:
246
+ """
247
+ Render Viewer as HTML string.
248
+
249
+ Returns:
250
+ str: Rendered HTML string.
251
+ """
252
+
253
+ return self._viewer.to_html()
254
+
255
+ def _repr_html_(self) -> str:
256
+ return self.render()
257
+
258
+
259
+ def _generate_llas(states: list[State]) -> list[LLA]:
260
+ return [
261
+ lla_from_position(state.get_position(), state.get_instant()) for state in states
262
+ ]
263
+
264
+
265
+ def _generate_sampled_position(
266
+ instants: list[Instant],
267
+ llas: list[LLA],
268
+ ) -> cesiumpy.SampledPositionProperty:
269
+ """
270
+ Generate a sampled position property from a list of OSTk LLAs.
271
+
272
+ Args:
273
+ states (list[LLA]): A list of OSTk LLAs.
274
+
275
+ Returns:
276
+ cesiumpy.SampledPositionProperty: Sampled position property.
277
+ """
278
+
279
+ return cesiumpy.SampledPositionProperty(
280
+ samples=[
281
+ (
282
+ coerce_to_datetime(instant),
283
+ _cesium_from_ostk_lla(lla),
284
+ None,
285
+ )
286
+ for instant, lla in zip(instants, llas)
287
+ ],
288
+ )
289
+
290
+
291
+ def _generate_sampled_orientation(states: list[State]) -> cesiumpy.SampledProperty:
292
+ """
293
+ Generate a sampled orientation property from a list of OSTk States.
294
+
295
+ Args:
296
+ states (list[State]): A list of OSTk States.
297
+
298
+ Returns:
299
+ cesiumpy.SampledProperty: Sampled orientation property.
300
+ """
301
+
302
+ return cesiumpy.SampledProperty(
303
+ type=cesiumpy.Quaternion,
304
+ samples=[
305
+ (
306
+ coerce_to_datetime(state.get_instant()),
307
+ _cesium_from_ostk_quaternion(state.in_frame(Frame.ITRF()).get_attitude()),
308
+ None,
309
+ )
310
+ for state in states
311
+ ],
312
+ )
313
+
314
+
315
+ def _cesium_from_ostk_position(
316
+ position: Position,
317
+ instant: Instant | None = None,
318
+ ) -> cesiumpy.Cartesian3:
319
+ """
320
+ Convert OSTk Position into Cesium Cartesian3.
321
+
322
+ Args:
323
+ position (Position): Position to convert.
324
+ instant (Instant, optional): Instant at which the position is valid (required if position is not expressed in ECEF).
325
+
326
+ Returns:
327
+ cesiumpy.Cartesian3: Converted position.
328
+ """
329
+
330
+ return _cesium_from_ostk_lla(lla_from_position(position, instant))
331
+
332
+
333
+ def _cesium_from_ostk_lla(
334
+ lla: LLA,
335
+ ) -> cesiumpy.Cartesian3:
336
+ """
337
+ Convert OSTk LLA into Cesium Cartesian3.
338
+
339
+ Args:
340
+ lla (LLA): LLA to convert.
341
+
342
+ Returns:
343
+ cesiumpy.Cartesian3: Converted LLA.
344
+ """
345
+
346
+ return cesiumpy.Cartesian3.fromDegrees(
347
+ float(lla.get_longitude().in_degrees()),
348
+ float(lla.get_latitude().in_degrees()),
349
+ float(lla.get_altitude().in_meters()),
350
+ )
351
+
352
+
353
+ def _cesium_from_ostk_quaternion(quaternion: Quaternion) -> cesiumpy.Quaternion:
354
+ """
355
+ Convert OSTk Quaternion into Cesium Quaternion.
356
+
357
+ Args:
358
+ quaternion (Quaternion): Quaternion to convert.
359
+
360
+ Returns:
361
+ cesiumpy.Quaternion: Converted quaternion.
362
+ """
363
+
364
+ return cesiumpy.Quaternion(
365
+ float(quaternion.x()),
366
+ float(quaternion.y()),
367
+ float(quaternion.z()),
368
+ float(quaternion.s()),
369
+ )
370
+
371
+
372
+ def _cesium_from_ostk_sensor(sensor: Sensor) -> cesiumpy.Sensor:
373
+ if isinstance(sensor, ConicSensor):
374
+ return cesiumpy.ConicSensor(
375
+ name=sensor.name,
376
+ direction=cesiumpy.Cartesian3(*sensor.direction),
377
+ half_angle=float(sensor.half_angle.in_radians()),
378
+ length=float(sensor.length.in_meters()),
379
+ material=sensor.color or cesiumpy.color.RED,
380
+ )
381
+
382
+ elif isinstance(sensor, RectangularSensor):
383
+ return cesiumpy.RectangularSensor(
384
+ name=sensor.name,
385
+ direction=cesiumpy.Cartesian3(*sensor.direction),
386
+ x_half_angle=float(sensor.x_half_angle.in_radians()),
387
+ y_half_angle=float(sensor.y_half_angle.in_radians()),
388
+ radius=float(sensor.radius.in_meters()),
389
+ material=sensor.color or cesiumpy.color.ORANGE,
390
+ )
391
+
392
+ raise NotImplementedError("{sensor.__name__} is not supported yet.")