open-space-toolkit-astrodynamics 13.1.0__py313-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.
- open_space_toolkit_astrodynamics-13.1.0.dist-info/METADATA +30 -0
- open_space_toolkit_astrodynamics-13.1.0.dist-info/RECORD +100 -0
- open_space_toolkit_astrodynamics-13.1.0.dist-info/WHEEL +5 -0
- open_space_toolkit_astrodynamics-13.1.0.dist-info/top_level.txt +1 -0
- open_space_toolkit_astrodynamics-13.1.0.dist-info/zip-safe +1 -0
- ostk/__init__.py +1 -0
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-313-x86_64-linux-gnu.so +0 -0
- ostk/astrodynamics/__init__.py +11 -0
- ostk/astrodynamics/converters.py +130 -0
- ostk/astrodynamics/dataframe.py +479 -0
- ostk/astrodynamics/display.py +222 -0
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.13 +0 -0
- ostk/astrodynamics/pytrajectory/__init__.py +1 -0
- ostk/astrodynamics/pytrajectory/pystate.py +251 -0
- ostk/astrodynamics/test/__init__.py +1 -0
- ostk/astrodynamics/test/access/__init__.py +1 -0
- ostk/astrodynamics/test/access/test_generator.py +248 -0
- ostk/astrodynamics/test/conftest.py +119 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/__init__.py +1 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/conftest.py +325 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/data/cdm.json +303 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/test_cdm.py +416 -0
- ostk/astrodynamics/test/dynamics/__init__.py +1 -0
- ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity.csv +565 -0
- ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity_Truth.csv +100 -0
- ostk/astrodynamics/test/dynamics/test_atmospheric_drag.py +128 -0
- ostk/astrodynamics/test/dynamics/test_central_body_gravity.py +58 -0
- ostk/astrodynamics/test/dynamics/test_dynamics.py +50 -0
- ostk/astrodynamics/test/dynamics/test_position_derivative.py +51 -0
- ostk/astrodynamics/test/dynamics/test_tabulated.py +138 -0
- ostk/astrodynamics/test/dynamics/test_third_body_gravity.py +67 -0
- ostk/astrodynamics/test/dynamics/test_thruster.py +142 -0
- ostk/astrodynamics/test/event_condition/test_angular_condition.py +113 -0
- ostk/astrodynamics/test/event_condition/test_boolean_condition.py +55 -0
- ostk/astrodynamics/test/event_condition/test_coe_condition.py +87 -0
- ostk/astrodynamics/test/event_condition/test_instant_condition.py +48 -0
- ostk/astrodynamics/test/event_condition/test_logical_condition.py +120 -0
- ostk/astrodynamics/test/event_condition/test_real_condition.py +50 -0
- ostk/astrodynamics/test/flight/__init__.py +1 -0
- ostk/astrodynamics/test/flight/system/__init__.py +1 -0
- ostk/astrodynamics/test/flight/system/test_propulsion_system.py +73 -0
- ostk/astrodynamics/test/flight/system/test_satellite_system.py +91 -0
- ostk/astrodynamics/test/flight/system/test_satellite_system_builder.py +71 -0
- ostk/astrodynamics/test/flight/test_maneuver.py +212 -0
- ostk/astrodynamics/test/flight/test_profile.py +253 -0
- ostk/astrodynamics/test/flight/test_system.py +55 -0
- ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +91 -0
- ostk/astrodynamics/test/guidance_law/test_qlaw.py +138 -0
- ostk/astrodynamics/test/solvers/__init__.py +1 -0
- ostk/astrodynamics/test/solvers/test_finite_difference_solver.py +181 -0
- ostk/astrodynamics/test/solvers/test_temporal_condition_solver.py +153 -0
- ostk/astrodynamics/test/test_access.py +128 -0
- ostk/astrodynamics/test/test_converters.py +290 -0
- ostk/astrodynamics/test/test_dataframe.py +875 -0
- ostk/astrodynamics/test/test_display.py +114 -0
- ostk/astrodynamics/test/test_event_condition.py +78 -0
- ostk/astrodynamics/test/test_import.py +26 -0
- ostk/astrodynamics/test/test_root_solver.py +70 -0
- ostk/astrodynamics/test/test_trajectory.py +118 -0
- ostk/astrodynamics/test/test_utilities.py +106 -0
- ostk/astrodynamics/test/test_viewer.py +129 -0
- ostk/astrodynamics/test/trajectory/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/message/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/conftest.py +18 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/data/opm_1.yaml +44 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/test_opm.py +108 -0
- ostk/astrodynamics/test/trajectory/orbit/models/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean.py +65 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_long.py +102 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_short.py +102 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +201 -0
- ostk/astrodynamics/test/trajectory/orbit/models/sgp4/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/sgp4/test_tle.py +331 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_kepler.py +130 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_propagated.py +234 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_sgp4.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_tabulated.py +380 -0
- ostk/astrodynamics/test/trajectory/orbit/test_model.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/test_pass.py +75 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_angular_velocity.py +30 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quaternion.py +18 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py +107 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py +115 -0
- ostk/astrodynamics/test/trajectory/state/test_coordinate_broker.py +84 -0
- ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +46 -0
- ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +314 -0
- ostk/astrodynamics/test/trajectory/test_local_orbital_frame_direction.py +81 -0
- ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +108 -0
- ostk/astrodynamics/test/trajectory/test_model.py +1 -0
- ostk/astrodynamics/test/trajectory/test_orbit.py +205 -0
- ostk/astrodynamics/test/trajectory/test_propagator.py +458 -0
- ostk/astrodynamics/test/trajectory/test_segment.py +403 -0
- ostk/astrodynamics/test/trajectory/test_sequence.py +530 -0
- ostk/astrodynamics/test/trajectory/test_state.py +543 -0
- ostk/astrodynamics/test/trajectory/test_state_builder.py +171 -0
- ostk/astrodynamics/utilities.py +247 -0
- ostk/astrodynamics/viewer.py +392 -0
@@ -0,0 +1,251 @@
|
|
1
|
+
# Apache License 2.0
|
2
|
+
|
3
|
+
# Python-only State functionality
|
4
|
+
import re
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
|
8
|
+
from ostk.physics.coordinate import Frame
|
9
|
+
from ostk.physics.time import Instant
|
10
|
+
|
11
|
+
from ostk.astrodynamics.trajectory import State, StateBuilder
|
12
|
+
from ostk.astrodynamics.trajectory.state import CoordinateSubset
|
13
|
+
from ostk.astrodynamics.converters import coerce_to_instant
|
14
|
+
from ostk.astrodynamics.trajectory.state.coordinate_subset import (
|
15
|
+
CartesianPosition,
|
16
|
+
CartesianVelocity,
|
17
|
+
AttitudeQuaternion,
|
18
|
+
AngularVelocity,
|
19
|
+
)
|
20
|
+
|
21
|
+
CANONICAL_FORMAT: str = r"(r|v)_(.*?)_(x|y|z)"
|
22
|
+
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def custom_class_generator(frame: Frame, coordinate_subsets: list) -> type:
|
26
|
+
"""
|
27
|
+
Emit a custom class type for States. This is meta-programming syntactic sugar on top of the StateBuilder class.
|
28
|
+
|
29
|
+
StateType = State.template(frame, coordinate_subsets)
|
30
|
+
state = StateType(instant, coordinates)
|
31
|
+
|
32
|
+
is equivalent to
|
33
|
+
|
34
|
+
state_builder = StateBuilder(frame, coordinate_subsets)
|
35
|
+
state = state_builder.build(instant, coordinates)
|
36
|
+
"""
|
37
|
+
|
38
|
+
class StateTemplateType(State):
|
39
|
+
state_builder: StateBuilder = StateBuilder(frame, coordinate_subsets)
|
40
|
+
|
41
|
+
def __init__(self, instant: Instant, coordinates: np.ndarray):
|
42
|
+
super().__init__(StateTemplateType.state_builder.build(instant, coordinates))
|
43
|
+
|
44
|
+
return StateTemplateType
|
45
|
+
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def from_dict(data: dict) -> State:
|
49
|
+
"""
|
50
|
+
Create a State from a dictionary.
|
51
|
+
|
52
|
+
Note: Implicit assumption that ECEF = ITRF, and ECI = GCRF.
|
53
|
+
|
54
|
+
The dictionary must contain the following:
|
55
|
+
- 'timestamp': The timestamp of the state.
|
56
|
+
- 'r_ITRF_x'/'rx'/'rx_eci'/'rx_ecef': The x-coordinate of the position.
|
57
|
+
- 'r_ITRF_y'/'ry'/'ry_eci'/'ry_ecef': The y-coordinate of the position.
|
58
|
+
- 'r_ITRF_z'/'rz'/'rz_eci'/'rz_ecef': The z-coordinate of the position.
|
59
|
+
- 'v_ITRF_x'/'vx'/'vx_eci'/'vx_ecef': The x-coordinate of the velocity.
|
60
|
+
- 'v_ITRF_y'/'vy'/'vy_eci'/'vy_ecef': The y-coordinate of the velocity.
|
61
|
+
- 'v_ITRF_z'/'vz'/'vz_eci'/'vz_ecef': The z-coordinate of the velocity.
|
62
|
+
- 'frame': The frame of the state. Required if 'rx', 'ry', 'rz', 'vx', 'vy', 'vz' are provided.
|
63
|
+
- 'q_B_ECI_x': The x-coordinate of the quaternion. Optional.
|
64
|
+
- 'q_B_ECI_y': The y-coordinate of the quaternion. Optional.
|
65
|
+
- 'q_B_ECI_z': The z-coordinate of the quaternion. Optional.
|
66
|
+
- 'q_B_ECI_s': The s-coordinate of the quaternion. Optional.
|
67
|
+
- 'w_B_ECI_in_B_x': The x-coordinate of the angular velocity. Optional.
|
68
|
+
- 'w_B_ECI_in_B_y': The y-coordinate of the angular velocity. Optional.
|
69
|
+
- 'w_B_ECI_in_B_z': The z-coordinate of the angular velocity. Optional.
|
70
|
+
- 'drag_coefficient'/'cd': The drag coefficient. Optional.
|
71
|
+
- 'cross_sectional_area'/'surface_area': The cross-sectional area. Optional.
|
72
|
+
- 'mass': The mass. Optional.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
data (dict): The dictionary.
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
State: The State.
|
79
|
+
"""
|
80
|
+
|
81
|
+
instant: Instant = coerce_to_instant(data["timestamp"])
|
82
|
+
|
83
|
+
eci_columns: list[str] = [
|
84
|
+
"rx_eci",
|
85
|
+
"ry_eci",
|
86
|
+
"rz_eci",
|
87
|
+
"vx_eci",
|
88
|
+
"vy_eci",
|
89
|
+
"vz_eci",
|
90
|
+
]
|
91
|
+
ecef_columns: list[str] = [
|
92
|
+
"rx_ecef",
|
93
|
+
"ry_ecef",
|
94
|
+
"rz_ecef",
|
95
|
+
"vx_ecef",
|
96
|
+
"vy_ecef",
|
97
|
+
"vz_ecef",
|
98
|
+
]
|
99
|
+
generic_columns: list[str] = [
|
100
|
+
"rx",
|
101
|
+
"ry",
|
102
|
+
"rz",
|
103
|
+
"vx",
|
104
|
+
"vy",
|
105
|
+
"vz",
|
106
|
+
]
|
107
|
+
|
108
|
+
# Replace non-standard position keys with canonical representation
|
109
|
+
if all(key in data.keys() for key in ("x_eci", "y_eci", "z_eci")):
|
110
|
+
data["rx_eci"] = data["x_eci"]
|
111
|
+
data["ry_eci"] = data["y_eci"]
|
112
|
+
data["rz_eci"] = data["z_eci"]
|
113
|
+
|
114
|
+
if all(key in data.keys() for key in ("x_ecef", "y_ecef", "z_ecef")):
|
115
|
+
data["rx_ecef"] = data["x_ecef"]
|
116
|
+
data["ry_ecef"] = data["y_ecef"]
|
117
|
+
data["rz_ecef"] = data["z_ecef"]
|
118
|
+
|
119
|
+
frame: Frame
|
120
|
+
coordinates: np.ndarray
|
121
|
+
|
122
|
+
coordinate_subsets: list[CoordinateSubset] = [
|
123
|
+
CartesianPosition.default(),
|
124
|
+
CartesianVelocity.default(),
|
125
|
+
]
|
126
|
+
|
127
|
+
match_groups: list[re.Match] = [
|
128
|
+
re.match(CANONICAL_FORMAT, column) for column in data.keys()
|
129
|
+
]
|
130
|
+
|
131
|
+
if len(matches := [match for match in match_groups if match is not None]) == 6:
|
132
|
+
frame_name: str = matches[0].group(2)
|
133
|
+
try:
|
134
|
+
frame: Frame = Frame.with_name(frame_name) or getattr(Frame, frame_name)()
|
135
|
+
except Exception:
|
136
|
+
raise ValueError(f"No frame exists with name [{frame_name}].")
|
137
|
+
|
138
|
+
coordinates = np.array([data[match.group(0)] for match in matches])
|
139
|
+
|
140
|
+
elif all(column in data for column in eci_columns):
|
141
|
+
frame: Frame = Frame.GCRF()
|
142
|
+
coordinates = np.array(
|
143
|
+
[
|
144
|
+
data["rx_eci"],
|
145
|
+
data["ry_eci"],
|
146
|
+
data["rz_eci"],
|
147
|
+
data["vx_eci"],
|
148
|
+
data["vy_eci"],
|
149
|
+
data["vz_eci"],
|
150
|
+
]
|
151
|
+
)
|
152
|
+
|
153
|
+
elif all(column in data for column in ecef_columns):
|
154
|
+
frame = Frame.ITRF()
|
155
|
+
coordinates = np.array(
|
156
|
+
[
|
157
|
+
data["rx_ecef"],
|
158
|
+
data["ry_ecef"],
|
159
|
+
data["rz_ecef"],
|
160
|
+
data["vx_ecef"],
|
161
|
+
data["vy_ecef"],
|
162
|
+
data["vz_ecef"],
|
163
|
+
]
|
164
|
+
)
|
165
|
+
|
166
|
+
elif all(column in data for column in generic_columns):
|
167
|
+
if "frame" not in data:
|
168
|
+
raise ValueError("Frame must be provided for generic columns.")
|
169
|
+
|
170
|
+
if isinstance(data["frame"], str):
|
171
|
+
if Frame.exists(data["frame"]):
|
172
|
+
frame = Frame.with_name(data["frame"])
|
173
|
+
else:
|
174
|
+
raise ValueError(f"No frame exists with name [{data['frame']}].")
|
175
|
+
elif isinstance(data["frame"], Frame):
|
176
|
+
frame = data["frame"]
|
177
|
+
else:
|
178
|
+
raise ValueError(f"Invalid frame data [{data['frame']}].")
|
179
|
+
|
180
|
+
coordinates = np.array(
|
181
|
+
[
|
182
|
+
data["rx"],
|
183
|
+
data["ry"],
|
184
|
+
data["rz"],
|
185
|
+
data["vx"],
|
186
|
+
data["vy"],
|
187
|
+
data["vz"],
|
188
|
+
]
|
189
|
+
)
|
190
|
+
else:
|
191
|
+
raise ValueError("Invalid state data.")
|
192
|
+
|
193
|
+
if all(
|
194
|
+
column in data for column in ["q_B_ECI_x", "q_B_ECI_y", "q_B_ECI_z", "q_B_ECI_s"]
|
195
|
+
):
|
196
|
+
coordinate_subsets.append(AttitudeQuaternion.default())
|
197
|
+
coordinates = np.append(
|
198
|
+
coordinates,
|
199
|
+
[
|
200
|
+
data["q_B_ECI_x"],
|
201
|
+
data["q_B_ECI_y"],
|
202
|
+
data["q_B_ECI_z"],
|
203
|
+
data["q_B_ECI_s"],
|
204
|
+
],
|
205
|
+
)
|
206
|
+
|
207
|
+
if all(
|
208
|
+
column in data
|
209
|
+
for column in ["w_B_ECI_in_B_x", "w_B_ECI_in_B_y", "w_B_ECI_in_B_z"]
|
210
|
+
):
|
211
|
+
coordinate_subsets.append(AngularVelocity.default())
|
212
|
+
coordinates = np.append(
|
213
|
+
coordinates,
|
214
|
+
[
|
215
|
+
data["w_B_ECI_in_B_x"],
|
216
|
+
data["w_B_ECI_in_B_y"],
|
217
|
+
data["w_B_ECI_in_B_z"],
|
218
|
+
],
|
219
|
+
)
|
220
|
+
|
221
|
+
if "drag_coefficient" in data or "cd" in data:
|
222
|
+
coordinate_subsets.append(CoordinateSubset.drag_coefficient())
|
223
|
+
coordinates = np.append(
|
224
|
+
coordinates,
|
225
|
+
data.get("drag_coefficient", data.get("cd")),
|
226
|
+
)
|
227
|
+
|
228
|
+
if "cross_sectional_area" in data or "surface_area" in data:
|
229
|
+
coordinate_subsets.append(CoordinateSubset.surface_area())
|
230
|
+
coordinates = np.append(
|
231
|
+
coordinates,
|
232
|
+
data.get("cross_sectional_area", data.get("surface_area")),
|
233
|
+
)
|
234
|
+
|
235
|
+
if "mass" in data:
|
236
|
+
coordinate_subsets.append(CoordinateSubset.mass())
|
237
|
+
coordinates = np.append(
|
238
|
+
coordinates,
|
239
|
+
data["mass"],
|
240
|
+
)
|
241
|
+
|
242
|
+
return State(
|
243
|
+
instant=instant,
|
244
|
+
coordinates=coordinates,
|
245
|
+
frame=frame,
|
246
|
+
coordinate_subsets=coordinate_subsets,
|
247
|
+
)
|
248
|
+
|
249
|
+
|
250
|
+
State.from_dict = from_dict
|
251
|
+
State.template = custom_class_generator
|
@@ -0,0 +1 @@
|
|
1
|
+
# Apache License 2.0
|
@@ -0,0 +1 @@
|
|
1
|
+
# Apache License 2.0
|
@@ -0,0 +1,248 @@
|
|
1
|
+
# Apache License 2.0
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from ostk.mathematics.object import RealInterval
|
6
|
+
|
7
|
+
from ostk.physics.unit import Length
|
8
|
+
from ostk.physics.unit import Angle
|
9
|
+
from ostk.physics.time import DateTime
|
10
|
+
from ostk.physics.time import Scale
|
11
|
+
from ostk.physics.time import Duration
|
12
|
+
from ostk.physics.time import Instant
|
13
|
+
from ostk.physics.time import Interval
|
14
|
+
from ostk.physics import Environment
|
15
|
+
from ostk.physics.environment.object import Celestial
|
16
|
+
|
17
|
+
from ostk.astrodynamics import Trajectory
|
18
|
+
from ostk.astrodynamics.trajectory import Orbit
|
19
|
+
from ostk.astrodynamics.trajectory.orbit.model import Kepler
|
20
|
+
from ostk.astrodynamics.trajectory.orbit.model.kepler import COE
|
21
|
+
from ostk.astrodynamics import Access
|
22
|
+
from ostk.astrodynamics.access import Generator
|
23
|
+
|
24
|
+
|
25
|
+
@pytest.fixture
|
26
|
+
def environment() -> Environment:
|
27
|
+
return Environment.default()
|
28
|
+
|
29
|
+
|
30
|
+
@pytest.fixture
|
31
|
+
def earth(environment: Environment) -> Celestial:
|
32
|
+
return environment.access_celestial_object_with_name("Earth")
|
33
|
+
|
34
|
+
|
35
|
+
@pytest.fixture
|
36
|
+
def generator(environment: Environment) -> Generator:
|
37
|
+
return Generator(
|
38
|
+
environment=environment,
|
39
|
+
aer_filter=lambda aer: True,
|
40
|
+
access_filter=lambda access: True,
|
41
|
+
state_filter=lambda state_1, state_2: True,
|
42
|
+
)
|
43
|
+
|
44
|
+
|
45
|
+
@pytest.fixture
|
46
|
+
def from_trajectory(earth: Celestial) -> Trajectory:
|
47
|
+
return Orbit(
|
48
|
+
model=Kepler(
|
49
|
+
coe=COE(
|
50
|
+
semi_major_axis=Length.kilometers(7000.0),
|
51
|
+
eccentricity=0.0,
|
52
|
+
inclination=Angle.degrees(45.0),
|
53
|
+
raan=Angle.degrees(0.0),
|
54
|
+
aop=Angle.degrees(0.0),
|
55
|
+
true_anomaly=Angle.degrees(0.0),
|
56
|
+
),
|
57
|
+
epoch=Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC),
|
58
|
+
celestial_object=earth,
|
59
|
+
perturbation_type=Kepler.PerturbationType.No,
|
60
|
+
),
|
61
|
+
celestial_object=earth,
|
62
|
+
)
|
63
|
+
|
64
|
+
|
65
|
+
@pytest.fixture
|
66
|
+
def to_trajectory(earth: Celestial) -> Trajectory:
|
67
|
+
return Orbit(
|
68
|
+
model=Kepler(
|
69
|
+
coe=COE(
|
70
|
+
semi_major_axis=Length.kilometers(7000.0),
|
71
|
+
eccentricity=0.0,
|
72
|
+
inclination=Angle.degrees(45.0),
|
73
|
+
raan=Angle.degrees(180.0),
|
74
|
+
aop=Angle.degrees(0.0),
|
75
|
+
true_anomaly=Angle.degrees(180.0),
|
76
|
+
),
|
77
|
+
epoch=Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC),
|
78
|
+
celestial_object=earth,
|
79
|
+
perturbation_type=Kepler.PerturbationType.No,
|
80
|
+
),
|
81
|
+
celestial_object=earth,
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
class TestGenerator:
|
86
|
+
def test_constructor_success_environment(self, environment: Environment):
|
87
|
+
generator = Generator(
|
88
|
+
environment=environment,
|
89
|
+
)
|
90
|
+
|
91
|
+
assert generator is not None
|
92
|
+
assert isinstance(generator, Generator)
|
93
|
+
|
94
|
+
def test_constructor_success_environment_aer_filter(self, environment: Environment):
|
95
|
+
generator = Generator(
|
96
|
+
environment=environment,
|
97
|
+
aer_filter=lambda aer: True,
|
98
|
+
)
|
99
|
+
|
100
|
+
assert generator is not None
|
101
|
+
assert isinstance(generator, Generator)
|
102
|
+
|
103
|
+
def test_constructor_success_environment_access_filter(
|
104
|
+
self,
|
105
|
+
environment: Environment,
|
106
|
+
):
|
107
|
+
generator = Generator(
|
108
|
+
environment=environment,
|
109
|
+
access_filter=lambda access: True,
|
110
|
+
)
|
111
|
+
|
112
|
+
assert generator is not None
|
113
|
+
assert isinstance(generator, Generator)
|
114
|
+
|
115
|
+
def test_constructor_success_environment_state_filter(
|
116
|
+
self,
|
117
|
+
environment: Environment,
|
118
|
+
):
|
119
|
+
generator = Generator(
|
120
|
+
environment=environment,
|
121
|
+
state_filter=lambda state_1, state_2: True,
|
122
|
+
)
|
123
|
+
|
124
|
+
assert generator is not None
|
125
|
+
assert isinstance(generator, Generator)
|
126
|
+
|
127
|
+
def test_constructor_success_environment_step_tolerance(
|
128
|
+
self,
|
129
|
+
environment: Environment,
|
130
|
+
):
|
131
|
+
generator = Generator(
|
132
|
+
environment=environment,
|
133
|
+
step=Duration.seconds(1.0),
|
134
|
+
tolerance=Duration.minutes(1.0),
|
135
|
+
)
|
136
|
+
|
137
|
+
assert generator is not None
|
138
|
+
assert isinstance(generator, Generator)
|
139
|
+
assert generator.get_step() == Duration.seconds(1.0)
|
140
|
+
assert generator.get_tolerance() == Duration.minutes(1.0)
|
141
|
+
|
142
|
+
def test_getters_success(self, generator: Generator):
|
143
|
+
assert generator.get_step() == Duration.minutes(1.0)
|
144
|
+
assert generator.get_tolerance() == Duration.microseconds(1.0)
|
145
|
+
assert generator.get_aer_filter() is not None
|
146
|
+
assert generator.get_access_filter() is not None
|
147
|
+
assert generator.get_state_filter() is not None
|
148
|
+
|
149
|
+
def test_get_condition_function_success(
|
150
|
+
self,
|
151
|
+
generator: Generator,
|
152
|
+
from_trajectory: Trajectory,
|
153
|
+
to_trajectory: Trajectory,
|
154
|
+
):
|
155
|
+
condition_function = generator.get_condition_function(
|
156
|
+
from_trajectory=from_trajectory,
|
157
|
+
to_trajectory=to_trajectory,
|
158
|
+
)
|
159
|
+
|
160
|
+
assert condition_function is not None
|
161
|
+
assert (
|
162
|
+
condition_function(
|
163
|
+
Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
|
164
|
+
)
|
165
|
+
is True
|
166
|
+
)
|
167
|
+
|
168
|
+
def test_compute_accesses_success(
|
169
|
+
self,
|
170
|
+
generator: Generator,
|
171
|
+
from_trajectory: Trajectory,
|
172
|
+
to_trajectory: Trajectory,
|
173
|
+
):
|
174
|
+
accesses = generator.compute_accesses(
|
175
|
+
interval=Interval.closed(
|
176
|
+
Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC),
|
177
|
+
Instant.date_time(DateTime(2018, 1, 1, 2, 0, 0), Scale.UTC),
|
178
|
+
),
|
179
|
+
from_trajectory=from_trajectory,
|
180
|
+
to_trajectory=to_trajectory,
|
181
|
+
)
|
182
|
+
|
183
|
+
assert accesses is not None
|
184
|
+
assert isinstance(accesses, list)
|
185
|
+
assert accesses[0] is not None
|
186
|
+
assert isinstance(accesses[0], Access)
|
187
|
+
|
188
|
+
def test_set_step_success(self, generator: Generator):
|
189
|
+
generator.set_step(Duration.seconds(1.0))
|
190
|
+
|
191
|
+
def test_set_tolerance_success(self, generator: Generator):
|
192
|
+
generator.set_tolerance(Duration.seconds(1.0))
|
193
|
+
|
194
|
+
def test_set_aer_filter_success(self, generator: Generator):
|
195
|
+
generator.set_aer_filter(aer_filter=lambda aer: True)
|
196
|
+
|
197
|
+
def test_set_access_filter_success(self, generator: Generator):
|
198
|
+
generator.set_access_filter(access_filter=lambda access: True)
|
199
|
+
|
200
|
+
def test_set_state_filter_success(self, generator: Generator):
|
201
|
+
generator.set_state_filter(state_filter=lambda state_1, state_2: True)
|
202
|
+
|
203
|
+
def test_undefined_success(self):
|
204
|
+
generator = Generator.undefined()
|
205
|
+
|
206
|
+
assert generator is not None
|
207
|
+
assert isinstance(generator, Generator)
|
208
|
+
assert generator.is_defined() is False
|
209
|
+
|
210
|
+
def test_aer_ranges_success(self, environment: Environment):
|
211
|
+
# Construct arbitrary AER ranges
|
212
|
+
azimuth_interval = RealInterval.closed(0.0, 360.0)
|
213
|
+
elevation_interval = RealInterval.closed(0.0, 90.0)
|
214
|
+
range_interval = RealInterval.closed(0.0, 7000e3)
|
215
|
+
|
216
|
+
generator = Generator.aer_ranges(
|
217
|
+
azimuth_range=azimuth_interval,
|
218
|
+
elevation_range=elevation_interval,
|
219
|
+
range_range=range_interval,
|
220
|
+
environment=environment,
|
221
|
+
)
|
222
|
+
|
223
|
+
assert generator is not None
|
224
|
+
assert isinstance(generator, Generator)
|
225
|
+
assert generator.is_defined()
|
226
|
+
|
227
|
+
def test_aer_mask_success(self, environment: Environment):
|
228
|
+
# Construct arbitrary anAzimuthElevationMask using python dict
|
229
|
+
an_azimuth_elevation_mask = {
|
230
|
+
0.0: 30.0,
|
231
|
+
90.0: 60.0,
|
232
|
+
180.0: 60.0,
|
233
|
+
270.0: 30.0,
|
234
|
+
359.0: 30.0,
|
235
|
+
}
|
236
|
+
|
237
|
+
# Construct arbitrary aRangerange
|
238
|
+
a_range_range = RealInterval(0.0, 10e4, RealInterval.Type.Closed)
|
239
|
+
|
240
|
+
generator = Generator.aer_mask(
|
241
|
+
azimuth_elevation_mask=an_azimuth_elevation_mask,
|
242
|
+
range_range=a_range_range,
|
243
|
+
environment=environment,
|
244
|
+
)
|
245
|
+
|
246
|
+
assert generator is not None
|
247
|
+
assert isinstance(generator, Generator)
|
248
|
+
assert generator.is_defined()
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Apache License 2.0
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from ostk.physics import Environment
|
6
|
+
from ostk.physics.unit import Angle
|
7
|
+
from ostk.physics.unit import Length
|
8
|
+
from ostk.physics.time import Instant
|
9
|
+
from ostk.physics.time import DateTime
|
10
|
+
from ostk.physics.time import Scale
|
11
|
+
from ostk.physics.time import Interval
|
12
|
+
from ostk.physics.coordinate import Position
|
13
|
+
from ostk.physics.coordinate import Velocity
|
14
|
+
from ostk.physics.coordinate import Frame
|
15
|
+
from ostk.physics.coordinate.spherical import LLA
|
16
|
+
from ostk.physics.coordinate.spherical import AER
|
17
|
+
|
18
|
+
from ostk.astrodynamics import Trajectory
|
19
|
+
from ostk.astrodynamics.trajectory import State
|
20
|
+
|
21
|
+
|
22
|
+
@pytest.fixture
|
23
|
+
def environment() -> Environment:
|
24
|
+
return Environment.default()
|
25
|
+
|
26
|
+
|
27
|
+
@pytest.fixture
|
28
|
+
def scale() -> Scale:
|
29
|
+
return Scale.UTC
|
30
|
+
|
31
|
+
|
32
|
+
@pytest.fixture
|
33
|
+
def instant_1(scale: Scale) -> Instant:
|
34
|
+
return Instant.date_time(DateTime(2023, 1, 8, 13, 45, 34), scale)
|
35
|
+
|
36
|
+
|
37
|
+
@pytest.fixture
|
38
|
+
def instant_2(scale: Scale) -> Instant:
|
39
|
+
return Instant.date_time(DateTime(2023, 1, 8, 13, 46, 34), scale)
|
40
|
+
|
41
|
+
|
42
|
+
@pytest.fixture
|
43
|
+
def instant_3(scale: Scale) -> Instant:
|
44
|
+
return Instant.date_time(DateTime(2023, 1, 8, 13, 47, 34), scale)
|
45
|
+
|
46
|
+
|
47
|
+
@pytest.fixture
|
48
|
+
def instant(instant_1: Instant) -> Instant:
|
49
|
+
return instant_1
|
50
|
+
|
51
|
+
|
52
|
+
@pytest.fixture
|
53
|
+
def interval(instant_1: Instant, instant_2: Instant) -> Interval:
|
54
|
+
return Interval(instant_1, instant_2, Interval.Type.Closed)
|
55
|
+
|
56
|
+
|
57
|
+
@pytest.fixture
|
58
|
+
def frame() -> Frame:
|
59
|
+
return Frame.GCRF()
|
60
|
+
|
61
|
+
|
62
|
+
@pytest.fixture
|
63
|
+
def position(frame: Frame) -> Position:
|
64
|
+
return Position.meters([7000000.0, 0.0, 0.0], frame)
|
65
|
+
|
66
|
+
|
67
|
+
@pytest.fixture
|
68
|
+
def velocity(frame: Frame) -> Velocity:
|
69
|
+
return Velocity.meters_per_second([7600.0, 0.0, 0.0], frame)
|
70
|
+
|
71
|
+
|
72
|
+
@pytest.fixture
|
73
|
+
def state(instant: Instant, position: Position, velocity: Velocity) -> State:
|
74
|
+
return State(instant, position, velocity)
|
75
|
+
|
76
|
+
|
77
|
+
@pytest.fixture
|
78
|
+
def latitude() -> Angle:
|
79
|
+
return Angle(30.0, Angle.Unit.Degree)
|
80
|
+
|
81
|
+
|
82
|
+
@pytest.fixture
|
83
|
+
def longitude() -> Angle:
|
84
|
+
return Angle(-111.0, Angle.Unit.Degree)
|
85
|
+
|
86
|
+
|
87
|
+
@pytest.fixture
|
88
|
+
def altitude() -> Length:
|
89
|
+
return Length(10000.0, Length.Unit.Meter)
|
90
|
+
|
91
|
+
|
92
|
+
@pytest.fixture
|
93
|
+
def lla(latitude: Angle, longitude: Angle, altitude: Length) -> LLA:
|
94
|
+
return LLA(latitude, longitude, altitude)
|
95
|
+
|
96
|
+
|
97
|
+
@pytest.fixture
|
98
|
+
def azimuth() -> Angle:
|
99
|
+
return Angle(270.0, Angle.Unit.Degree)
|
100
|
+
|
101
|
+
|
102
|
+
@pytest.fixture
|
103
|
+
def elevation() -> Angle:
|
104
|
+
return Angle(10.45, Angle.Unit.Degree)
|
105
|
+
|
106
|
+
|
107
|
+
@pytest.fixture
|
108
|
+
def range() -> Length:
|
109
|
+
return Length(100000.0, Length.Unit.Meter)
|
110
|
+
|
111
|
+
|
112
|
+
@pytest.fixture
|
113
|
+
def aer(azimuth: Angle, elevation: Angle, range: Length) -> LLA:
|
114
|
+
return AER(azimuth, elevation, range)
|
115
|
+
|
116
|
+
|
117
|
+
@pytest.fixture
|
118
|
+
def trajectory(position: Position, instant_1: Instant) -> Trajectory:
|
119
|
+
return Trajectory.position(position.in_frame(Frame.ITRF(), instant_1))
|
@@ -0,0 +1 @@
|
|
1
|
+
# Apache License 2.0
|