open-space-toolkit-astrodynamics 11.1.0__py39-none-manylinux2014_aarch64.whl → 12.0.0__py39-none-manylinux2014_aarch64.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-11.1.0.dist-info → open_space_toolkit_astrodynamics-12.0.0.dist-info}/METADATA +1 -1
- {open_space_toolkit_astrodynamics-11.1.0.dist-info → open_space_toolkit_astrodynamics-12.0.0.dist-info}/RECORD +15 -15
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-39-aarch64-linux-gnu.so +0 -0
- ostk/astrodynamics/converters.py +28 -4
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.12 +0 -0
- ostk/astrodynamics/pytrajectory/pystate.py +160 -0
- ostk/astrodynamics/test/flight/test_profile.py +104 -9
- ostk/astrodynamics/test/test_converters.py +43 -46
- ostk/astrodynamics/test/test_utilities.py +31 -46
- ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +34 -2
- ostk/astrodynamics/test/trajectory/test_state.py +138 -0
- ostk/astrodynamics/utilities.py +122 -35
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.11 +0 -0
- {open_space_toolkit_astrodynamics-11.1.0.dist-info → open_space_toolkit_astrodynamics-12.0.0.dist-info}/WHEEL +0 -0
- {open_space_toolkit_astrodynamics-11.1.0.dist-info → open_space_toolkit_astrodynamics-12.0.0.dist-info}/top_level.txt +0 -0
- {open_space_toolkit_astrodynamics-11.1.0.dist-info → open_space_toolkit_astrodynamics-12.0.0.dist-info}/zip-safe +0 -0
@@ -1,25 +1,25 @@
|
|
1
1
|
ostk/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
2
|
-
ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-39-aarch64-linux-gnu.so,sha256=
|
2
|
+
ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-39-aarch64-linux-gnu.so,sha256=coucr-W6KWKFkpHhPtkvrLvZNUbV1ojJ9CfB3qUKXRU,2164512
|
3
3
|
ostk/astrodynamics/__init__.py,sha256=3gWyqFIbhAfcdeMhmfBPQPlPQTmaOzm-6flkJe745Zk,251
|
4
|
-
ostk/astrodynamics/converters.py,sha256=
|
4
|
+
ostk/astrodynamics/converters.py,sha256=Ld-DLTaqg7pBkIJAcYeoaA-sDOQbR_uksRR-lg9dfXo,3278
|
5
5
|
ostk/astrodynamics/dataframe.py,sha256=njimcfT2cvnSOp9eBR1Ej-LuYpXbVieAVEBk2tYW4Vg,18761
|
6
6
|
ostk/astrodynamics/display.py,sha256=KiEGsjNftQfoUe-HfZeYpQQQ4TkaEnxtEl6p5_LX5f0,6303
|
7
|
-
ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.
|
8
|
-
ostk/astrodynamics/utilities.py,sha256=
|
7
|
+
ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.12,sha256=fzy-MjP5eNUaVa_3RAdR6KMcoQne2dHtMao2M9-uW3Q,3411472
|
8
|
+
ostk/astrodynamics/utilities.py,sha256=f0ySFhD6X9jhAZW1hLnbLVEOuy3QgVq2C6k4m8MFGYA,6963
|
9
9
|
ostk/astrodynamics/viewer.py,sha256=8UxqsvU5wui7gPUwxXaKcIB-509NJiih9Df3mQgXyXY,11776
|
10
10
|
ostk/astrodynamics/pytrajectory/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
11
|
-
ostk/astrodynamics/pytrajectory/pystate.py,sha256=
|
11
|
+
ostk/astrodynamics/pytrajectory/pystate.py,sha256=qtGofajIODveyy_g_lEeBqJQ1GddqmzRBVNCExF8pdk,5734
|
12
12
|
ostk/astrodynamics/test/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
13
13
|
ostk/astrodynamics/test/conftest.py,sha256=fsdUQnLxyfvBzfaSCkgipkPIhkPHchQqbeAro1pF648,2717
|
14
14
|
ostk/astrodynamics/test/test_access.py,sha256=MCBsUPtuVm7NgHZR0z0DpWnPZ_qBu4aRhLI2PnRNUYs,3940
|
15
|
-
ostk/astrodynamics/test/test_converters.py,sha256=
|
15
|
+
ostk/astrodynamics/test/test_converters.py,sha256=mFpDD0YM8o356lj91GGIwYdMk2ut6xZFV3uYcgZepMY,8744
|
16
16
|
ostk/astrodynamics/test/test_dataframe.py,sha256=IJMOODzTVYtiPxekmltH6lOMArPXbHwSEAl-jg0Ab2g,28414
|
17
17
|
ostk/astrodynamics/test/test_display.py,sha256=Ykvw2rUWWw8jsX0Suy11X27bHjwm_ira__xj0ZyHHxw,3672
|
18
18
|
ostk/astrodynamics/test/test_event_condition.py,sha256=mhMTH7wAoYFWRYt_8l2d1vjNPrFhVjMAEET4INLCVXY,1472
|
19
19
|
ostk/astrodynamics/test/test_import.py,sha256=py_hALBR0IYuUzv9dfgQZzrrLHJIpnyKvt3Oi1XBqCg,1251
|
20
20
|
ostk/astrodynamics/test/test_root_solver.py,sha256=hQ8O6g-WP49gZH_H3Rdufv0F0gQorpzJyIcjBGGUQ34,1831
|
21
21
|
ostk/astrodynamics/test/test_trajectory.py,sha256=WC9X-O_prHeVLIZlmmYzgkskrs5ZwvmOfhz8yth2lgI,1345
|
22
|
-
ostk/astrodynamics/test/test_utilities.py,sha256=
|
22
|
+
ostk/astrodynamics/test/test_utilities.py,sha256=xmRt-5z9-FFYTMQxBpjGrcKP39trbOiH-NLxGcTWOJE,3048
|
23
23
|
ostk/astrodynamics/test/test_viewer.py,sha256=rbVAiclzBZ1bS4Zdh4xUNJ-STCN4G0O7Gr8suSPJrSc,3986
|
24
24
|
ostk/astrodynamics/test/access/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
25
25
|
ostk/astrodynamics/test/access/test_generator.py,sha256=7l_MJCZIhWIyPpZ-XouBXlSd5-BfqFDGVOMMxjoRIHA,8032
|
@@ -45,7 +45,7 @@ ostk/astrodynamics/test/event_condition/test_logical_condition.py,sha256=09h5TYW
|
|
45
45
|
ostk/astrodynamics/test/event_condition/test_real_condition.py,sha256=tle6HVzMFMIIkfRY7CuaA0mPtw3riJBG_JQkc1L0dpk,1374
|
46
46
|
ostk/astrodynamics/test/flight/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
47
47
|
ostk/astrodynamics/test/flight/test_maneuver.py,sha256=6LhnNhaiwId-IveUraHnhx8dYxeOgzntP_WPtv9GFjQ,6197
|
48
|
-
ostk/astrodynamics/test/flight/test_profile.py,sha256=
|
48
|
+
ostk/astrodynamics/test/flight/test_profile.py,sha256=ZuK2FWWnFzvnJsMgZYeQyG6G0yolUihL4TjGt0BMrZI,7518
|
49
49
|
ostk/astrodynamics/test/flight/test_system.py,sha256=MVaE7lJYisH4vmJPD-G-Hw4wNj-Xe8yMksgu8IXCLvg,1322
|
50
50
|
ostk/astrodynamics/test/flight/system/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
51
51
|
ostk/astrodynamics/test/flight/system/test_propulsion_system.py,sha256=jLUC74YjIQz7APr-kQcdYRYiKm8lfequjosptG29kmI,1964
|
@@ -58,13 +58,13 @@ ostk/astrodynamics/test/solvers/test_finite_difference_solver.py,sha256=49hS9EF7
|
|
58
58
|
ostk/astrodynamics/test/solvers/test_temporal_condition_solver.py,sha256=ukDuIiEbBBvfqbHfiMIsPk5eyeRsWywCvNT1A3YnmIQ,4862
|
59
59
|
ostk/astrodynamics/test/trajectory/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
60
60
|
ostk/astrodynamics/test/trajectory/test_local_orbital_frame_direction.py,sha256=d72J50UGG9m0Atei0UQ8sITU1WdJmws5xgUiacLZMbw,2515
|
61
|
-
ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py,sha256=
|
61
|
+
ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py,sha256=hXA23AFZ9RFroBZiGZMxTWGCFJ4fxQpYx50eml1WFjI,3116
|
62
62
|
ostk/astrodynamics/test/trajectory/test_model.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
63
63
|
ostk/astrodynamics/test/trajectory/test_orbit.py,sha256=iKVgScEo1osMIXjON4ToURCgp2LDZV18fKQuy49RktI,6484
|
64
64
|
ostk/astrodynamics/test/trajectory/test_propagator.py,sha256=dG31S5FY0NjJ5bnisqJ_HdL7amj_AZAWWLiOmdHfJAc,14233
|
65
65
|
ostk/astrodynamics/test/trajectory/test_segment.py,sha256=ACVMYvjlTyCQHZ2-hFEurK08-LQxt6VlcGTMOrgDHaE,9333
|
66
66
|
ostk/astrodynamics/test/trajectory/test_sequence.py,sha256=gswWqEBTYvbi5TQjF0DNPkAj5COo9Nb9IK7TdwJ_5C0,12882
|
67
|
-
ostk/astrodynamics/test/trajectory/test_state.py,sha256=
|
67
|
+
ostk/astrodynamics/test/trajectory/test_state.py,sha256=IRtdEyiYHCdBXSRmNEKZy355s7i8STYddOzQAMXh5EY,10805
|
68
68
|
ostk/astrodynamics/test/trajectory/test_state_builder.py,sha256=PSlXtmGURCrlf0w50tn3dEBRo6p0x4NmIF9gJsNOR3k,4782
|
69
69
|
ostk/astrodynamics/test/trajectory/orbit/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
70
70
|
ostk/astrodynamics/test/trajectory/orbit/test_model.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
|
@@ -93,8 +93,8 @@ ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_angular_velocity
|
|
93
93
|
ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quaternion.py,sha256=UEu9ApzQLmT87eeISw6_gcHTlX-4b2scIvHz-uE1p_c,393
|
94
94
|
ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py,sha256=XvHdk1KjacTwtkgx2jUAc9I9N3nvjPDv03FAanpv8jQ,2702
|
95
95
|
ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py,sha256=-kd5TZO5TICihbkqDTew2i_tDpggdpe3Yf23046FATM,3057
|
96
|
-
open_space_toolkit_astrodynamics-
|
97
|
-
open_space_toolkit_astrodynamics-
|
98
|
-
open_space_toolkit_astrodynamics-
|
99
|
-
open_space_toolkit_astrodynamics-
|
100
|
-
open_space_toolkit_astrodynamics-
|
96
|
+
open_space_toolkit_astrodynamics-12.0.0.dist-info/METADATA,sha256=CLVAoU2IU_TzyOATZkppzbeCyWXNA81cWvySMniQCa4,1916
|
97
|
+
open_space_toolkit_astrodynamics-12.0.0.dist-info/WHEEL,sha256=kYqHudTOMtjp4at7li7kQygMBvMc9voLd-PjCaTm7Ps,110
|
98
|
+
open_space_toolkit_astrodynamics-12.0.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
|
99
|
+
open_space_toolkit_astrodynamics-12.0.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
100
|
+
open_space_toolkit_astrodynamics-12.0.0.dist-info/RECORD,,
|
Binary file
|
ostk/astrodynamics/converters.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Apache License 2.0
|
2
2
|
|
3
|
+
import re
|
4
|
+
|
3
5
|
from datetime import datetime, timezone
|
4
6
|
|
5
7
|
from ostk.physics.time import Instant
|
@@ -30,7 +32,9 @@ def coerce_to_datetime(value: Instant | datetime | str) -> datetime:
|
|
30
32
|
|
31
33
|
return datetime.fromisoformat(value)
|
32
34
|
|
33
|
-
raise TypeError(
|
35
|
+
raise TypeError(
|
36
|
+
f"Argument is of type [{type(value)}]. Must be a datetime, an Instant, or a str."
|
37
|
+
)
|
34
38
|
|
35
39
|
|
36
40
|
def coerce_to_instant(value: Instant | datetime | str) -> Instant:
|
@@ -53,7 +57,9 @@ def coerce_to_instant(value: Instant | datetime | str) -> Instant:
|
|
53
57
|
if isinstance(value, str):
|
54
58
|
return coerce_to_instant(coerce_to_datetime(value))
|
55
59
|
|
56
|
-
raise TypeError(
|
60
|
+
raise TypeError(
|
61
|
+
f"Argument is of type [{type(value)}]. Must be a datetime, an Instant, or a str."
|
62
|
+
)
|
57
63
|
|
58
64
|
|
59
65
|
def coerce_to_iso(value: Instant | datetime | str, timespec: str = "microseconds") -> str:
|
@@ -77,12 +83,18 @@ def coerce_to_iso(value: Instant | datetime | str, timespec: str = "microseconds
|
|
77
83
|
if isinstance(value, Instant):
|
78
84
|
return coerce_to_iso(coerce_to_datetime(value), timespec=timespec)
|
79
85
|
|
80
|
-
raise TypeError(
|
86
|
+
raise TypeError(
|
87
|
+
f"Argument is of type [{type(value)}]. Must be a datetime, an Instant, or a str."
|
88
|
+
)
|
81
89
|
|
82
90
|
|
83
91
|
def coerce_to_interval(
|
84
92
|
value: (
|
85
|
-
Interval
|
93
|
+
Interval
|
94
|
+
| tuple[Instant, Instant]
|
95
|
+
| tuple[datetime, datetime]
|
96
|
+
| tuple[str, str]
|
97
|
+
| str
|
86
98
|
),
|
87
99
|
) -> Interval:
|
88
100
|
"""
|
@@ -98,6 +110,18 @@ def coerce_to_interval(
|
|
98
110
|
if isinstance(value, Interval):
|
99
111
|
return value
|
100
112
|
|
113
|
+
if isinstance(value, str):
|
114
|
+
regex = r"\[(.*) - (.*)\] \[UTC\]"
|
115
|
+
matches = re.search(regex, value)
|
116
|
+
|
117
|
+
if matches:
|
118
|
+
return Interval.closed(
|
119
|
+
start_instant=coerce_to_instant(matches.group(1)),
|
120
|
+
end_instant=coerce_to_instant(matches.group(2)),
|
121
|
+
)
|
122
|
+
|
123
|
+
raise ValueError(f"String [{value}] does not match the expected format.")
|
124
|
+
|
101
125
|
return Interval.closed(
|
102
126
|
start_instant=coerce_to_instant(value[0]),
|
103
127
|
end_instant=coerce_to_instant(value[1]),
|
Binary file
|
@@ -8,6 +8,14 @@ from ostk.physics.coordinate import Frame
|
|
8
8
|
from ostk.physics.time import Instant
|
9
9
|
|
10
10
|
from ostk.astrodynamics.trajectory import State, StateBuilder
|
11
|
+
from ostk.astrodynamics.trajectory.state import CoordinateSubset
|
12
|
+
from ostk.astrodynamics.converters import coerce_to_instant
|
13
|
+
from ostk.astrodynamics.trajectory.state.coordinate_subset import (
|
14
|
+
CartesianPosition,
|
15
|
+
CartesianVelocity,
|
16
|
+
AttitudeQuaternion,
|
17
|
+
AngularVelocity,
|
18
|
+
)
|
11
19
|
|
12
20
|
|
13
21
|
@staticmethod
|
@@ -33,4 +41,156 @@ def custom_class_generator(frame: Frame, coordinate_subsets: list) -> type:
|
|
33
41
|
return StateTemplateType
|
34
42
|
|
35
43
|
|
44
|
+
@staticmethod
|
45
|
+
def from_dict(data: dict) -> State:
|
46
|
+
"""
|
47
|
+
Create a State from a dictionary.
|
48
|
+
The dictionary must contain the following:
|
49
|
+
- 'timestamp': The timestamp of the state.
|
50
|
+
- 'rx'/'x_eci'/'x_ecef': The x-coordinate of the position.
|
51
|
+
- 'ry'/'y_eci'/'y_ecef': The y-coordinate of the position.
|
52
|
+
- 'rz'/'z_eci'/'z_ecef': The z-coordinate of the position.
|
53
|
+
- 'vx'/'vx_eci'/'vx_ecef': The x-coordinate of the velocity.
|
54
|
+
- 'vy'/'vy_eci'/'vy_ecef': The y-coordinate of the velocity.
|
55
|
+
- 'vz'/'vz_eci'/'vz_ecef': The z-coordinate of the velocity.
|
56
|
+
- 'frame': The frame of the state. Required if 'rx', 'ry', 'rz', 'vx', 'vy', 'vz' are provided.
|
57
|
+
- 'q_B_ECI_x': The x-coordinate of the quaternion.
|
58
|
+
- 'q_B_ECI_y': The y-coordinate of the quaternion.
|
59
|
+
- 'q_B_ECI_z': The z-coordinate of the quaternion.
|
60
|
+
- 'q_B_ECI_s': The s-coordinate of the quaternion.
|
61
|
+
- 'w_B_ECI_in_B_x': The x-coordinate of the angular velocity.
|
62
|
+
- 'w_B_ECI_in_B_y': The y-coordinate of the angular velocity.
|
63
|
+
- 'w_B_ECI_in_B_z': The z-coordinate of the angular velocity.
|
64
|
+
|
65
|
+
|
66
|
+
Args:
|
67
|
+
data (dict): The dictionary.
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
State: The State.
|
71
|
+
"""
|
72
|
+
|
73
|
+
instant: Instant = coerce_to_instant(data["timestamp"])
|
74
|
+
|
75
|
+
eci_columns: list[str] = [
|
76
|
+
"rx_eci",
|
77
|
+
"ry_eci",
|
78
|
+
"rz_eci",
|
79
|
+
"vx_eci",
|
80
|
+
"vy_eci",
|
81
|
+
"vz_eci",
|
82
|
+
]
|
83
|
+
ecef_columns: list[str] = [
|
84
|
+
"rx_ecef",
|
85
|
+
"ry_ecef",
|
86
|
+
"rz_ecef",
|
87
|
+
"vx_ecef",
|
88
|
+
"vy_ecef",
|
89
|
+
"vz_ecef",
|
90
|
+
]
|
91
|
+
generic_columns: list[str] = [
|
92
|
+
"rx",
|
93
|
+
"ry",
|
94
|
+
"rz",
|
95
|
+
"vx",
|
96
|
+
"vy",
|
97
|
+
"vz",
|
98
|
+
]
|
99
|
+
|
100
|
+
frame: Frame
|
101
|
+
coordinates: np.ndarray
|
102
|
+
|
103
|
+
coordinate_subsets: list[CoordinateSubset] = [
|
104
|
+
CartesianPosition.default(),
|
105
|
+
CartesianVelocity.default(),
|
106
|
+
]
|
107
|
+
|
108
|
+
if all(column in data for column in eci_columns):
|
109
|
+
frame = Frame.GCRF()
|
110
|
+
coordinates = np.array(
|
111
|
+
[
|
112
|
+
data["rx_eci"],
|
113
|
+
data["ry_eci"],
|
114
|
+
data["rz_eci"],
|
115
|
+
data["vx_eci"],
|
116
|
+
data["vy_eci"],
|
117
|
+
data["vz_eci"],
|
118
|
+
]
|
119
|
+
)
|
120
|
+
elif all(column in data for column in ecef_columns):
|
121
|
+
frame = Frame.ITRF()
|
122
|
+
coordinates = np.array(
|
123
|
+
[
|
124
|
+
data["rx_ecef"],
|
125
|
+
data["ry_ecef"],
|
126
|
+
data["rz_ecef"],
|
127
|
+
data["vx_ecef"],
|
128
|
+
data["vy_ecef"],
|
129
|
+
data["vz_ecef"],
|
130
|
+
]
|
131
|
+
)
|
132
|
+
elif all(column in data for column in generic_columns):
|
133
|
+
if "frame" not in data:
|
134
|
+
raise ValueError("Frame must be provided for generic columns.")
|
135
|
+
|
136
|
+
if isinstance(data["frame"], str):
|
137
|
+
if Frame.exists(data["frame"]):
|
138
|
+
frame = Frame.with_name(data["frame"])
|
139
|
+
else:
|
140
|
+
raise ValueError(f"No frame exists with name [{data['frame']}].")
|
141
|
+
elif isinstance(data["frame"], Frame):
|
142
|
+
frame = data["frame"]
|
143
|
+
else:
|
144
|
+
raise ValueError(f"Invalid frame data [{data['frame']}].")
|
145
|
+
|
146
|
+
coordinates = np.array(
|
147
|
+
[
|
148
|
+
data["rx"],
|
149
|
+
data["ry"],
|
150
|
+
data["rz"],
|
151
|
+
data["vx"],
|
152
|
+
data["vy"],
|
153
|
+
data["vz"],
|
154
|
+
]
|
155
|
+
)
|
156
|
+
else:
|
157
|
+
raise ValueError("Invalid state data.")
|
158
|
+
|
159
|
+
if all(
|
160
|
+
column in data for column in ["q_B_ECI_x", "q_B_ECI_y", "q_B_ECI_z", "q_B_ECI_s"]
|
161
|
+
):
|
162
|
+
coordinate_subsets.append(AttitudeQuaternion.default())
|
163
|
+
coordinates = np.append(
|
164
|
+
coordinates,
|
165
|
+
[
|
166
|
+
data["q_B_ECI_x"],
|
167
|
+
data["q_B_ECI_y"],
|
168
|
+
data["q_B_ECI_z"],
|
169
|
+
data["q_B_ECI_s"],
|
170
|
+
],
|
171
|
+
)
|
172
|
+
|
173
|
+
if all(
|
174
|
+
column in data
|
175
|
+
for column in ["w_B_ECI_in_B_x", "w_B_ECI_in_B_y", "w_B_ECI_in_B_z"]
|
176
|
+
):
|
177
|
+
coordinate_subsets.append(AngularVelocity.default())
|
178
|
+
coordinates = np.append(
|
179
|
+
coordinates,
|
180
|
+
[
|
181
|
+
data["w_B_ECI_in_B_x"],
|
182
|
+
data["w_B_ECI_in_B_y"],
|
183
|
+
data["w_B_ECI_in_B_z"],
|
184
|
+
],
|
185
|
+
)
|
186
|
+
|
187
|
+
return State(
|
188
|
+
instant=instant,
|
189
|
+
coordinates=coordinates,
|
190
|
+
frame=frame,
|
191
|
+
coordinate_subsets=coordinate_subsets,
|
192
|
+
)
|
193
|
+
|
194
|
+
|
195
|
+
State.from_dict = from_dict
|
36
196
|
State.template = custom_class_generator
|
@@ -8,6 +8,7 @@ from ostk.mathematics.geometry.d3.transformation.rotation import Quaternion
|
|
8
8
|
|
9
9
|
from ostk.physics import Environment
|
10
10
|
from ostk.physics.time import DateTime
|
11
|
+
from ostk.physics.time import Duration
|
11
12
|
from ostk.physics.time import Time
|
12
13
|
from ostk.physics.time import Scale
|
13
14
|
from ostk.physics.time import Instant
|
@@ -32,6 +33,21 @@ def instant() -> Instant:
|
|
32
33
|
return Instant.date_time(DateTime(2020, 1, 1, 0, 0, 30), Scale.UTC)
|
33
34
|
|
34
35
|
|
36
|
+
@pytest.fixture
|
37
|
+
def environment() -> Environment:
|
38
|
+
return Environment.default()
|
39
|
+
|
40
|
+
|
41
|
+
@pytest.fixture
|
42
|
+
def orbit(instant: Instant, environment: Environment) -> Orbit:
|
43
|
+
return Orbit.sun_synchronous(
|
44
|
+
epoch=instant,
|
45
|
+
altitude=Length.kilometers(500.0),
|
46
|
+
local_time_at_descending_node=Time(14, 0, 0),
|
47
|
+
celestial_object=environment.access_celestial_object_with_name("Earth"),
|
48
|
+
)
|
49
|
+
|
50
|
+
|
35
51
|
@pytest.fixture
|
36
52
|
def transform_model() -> TransformModel:
|
37
53
|
def dynamic_provider_generator(instant: Instant):
|
@@ -78,11 +94,46 @@ def profile(request) -> Profile:
|
|
78
94
|
return Profile(model=model)
|
79
95
|
|
80
96
|
|
97
|
+
@pytest.fixture(
|
98
|
+
params=[
|
99
|
+
Profile.Target(Profile.TargetType.GeocentricNadir, Profile.Axis.X),
|
100
|
+
Profile.TrajectoryTarget(
|
101
|
+
Trajectory.position(Position.meters((0.0, 0.0, 0.0), Frame.ITRF())),
|
102
|
+
Profile.Axis.X,
|
103
|
+
),
|
104
|
+
Profile.OrientationProfileTarget(
|
105
|
+
[
|
106
|
+
(Instant.J2000(), [1.0, 0.0, 0.0]),
|
107
|
+
(Instant.J2000() + Duration.minutes(1.0), [1.0, 0.0, 0.0]),
|
108
|
+
(Instant.J2000() + Duration.minutes(2.0), [1.0, 0.0, 0.0]),
|
109
|
+
(Instant.J2000() + Duration.minutes(3.0), [1.0, 0.0, 0.0]),
|
110
|
+
],
|
111
|
+
Profile.Axis.X,
|
112
|
+
),
|
113
|
+
Profile.CustomTarget(
|
114
|
+
lambda state: [1.0, 0.0, 0.0],
|
115
|
+
Profile.Axis.X,
|
116
|
+
),
|
117
|
+
]
|
118
|
+
)
|
119
|
+
def alignment_target() -> Profile.Target:
|
120
|
+
return Profile.Target(Profile.TargetType.GeocentricNadir, Profile.Axis.X)
|
121
|
+
|
122
|
+
|
123
|
+
@pytest.fixture
|
124
|
+
def clocking_target() -> Profile.Target:
|
125
|
+
return Profile.Target(Profile.TargetType.VelocityECI, Profile.Axis.Y)
|
126
|
+
|
127
|
+
|
81
128
|
class TestProfile:
|
82
129
|
def test_constructors(self, profile: Profile):
|
83
130
|
assert profile is not None
|
84
131
|
assert isinstance(profile, Profile)
|
85
132
|
|
133
|
+
def test_profile_target(self, alignment_target: Profile.Target):
|
134
|
+
assert alignment_target is not None
|
135
|
+
assert isinstance(alignment_target, Profile.Target)
|
136
|
+
|
86
137
|
def test_get_state_at(self, profile: Profile, instant: Instant):
|
87
138
|
state: State = profile.get_state_at(instant)
|
88
139
|
|
@@ -130,18 +181,62 @@ class TestProfile:
|
|
130
181
|
assert isinstance(profile, Profile)
|
131
182
|
assert profile.is_defined()
|
132
183
|
|
133
|
-
def test_nadir_pointing(
|
134
|
-
|
184
|
+
def test_nadir_pointing(
|
185
|
+
self,
|
186
|
+
orbit: Orbit,
|
187
|
+
):
|
188
|
+
profile: Profile = Profile.nadir_pointing(orbit, Orbit.FrameType.VVLH)
|
135
189
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
190
|
+
assert profile is not None
|
191
|
+
assert isinstance(profile, Profile)
|
192
|
+
assert profile.is_defined()
|
193
|
+
|
194
|
+
def test_custom_pointing(
|
195
|
+
self,
|
196
|
+
orbit: Orbit,
|
197
|
+
alignment_target: Profile.Target,
|
198
|
+
clocking_target: Profile.Target,
|
199
|
+
):
|
200
|
+
profile = Profile.custom_pointing(
|
201
|
+
orbit, Profile.align_and_constrain(alignment_target, clocking_target)
|
141
202
|
)
|
142
203
|
|
143
|
-
profile
|
204
|
+
assert profile is not None
|
205
|
+
assert profile.is_defined()
|
206
|
+
|
207
|
+
def test_align_and_constrain(
|
208
|
+
self,
|
209
|
+
orbit: Orbit,
|
210
|
+
instant: Instant,
|
211
|
+
alignment_target: Profile.Target,
|
212
|
+
clocking_target: Profile.Target,
|
213
|
+
):
|
214
|
+
orientation = Profile.align_and_constrain(
|
215
|
+
alignment_target=alignment_target,
|
216
|
+
clocking_target=clocking_target,
|
217
|
+
)
|
218
|
+
|
219
|
+
assert orientation is not None
|
220
|
+
assert orientation(orbit.get_state_at(instant)) is not None
|
221
|
+
|
222
|
+
def test_custom_pointing(
|
223
|
+
self,
|
224
|
+
orbit: Orbit,
|
225
|
+
alignment_target: Profile.Target,
|
226
|
+
clocking_target: Profile.Target,
|
227
|
+
):
|
228
|
+
profile = Profile.custom_pointing(
|
229
|
+
orbit, Profile.align_and_constrain(alignment_target, clocking_target)
|
230
|
+
)
|
231
|
+
|
232
|
+
assert profile is not None
|
233
|
+
assert profile.is_defined()
|
234
|
+
|
235
|
+
profile = Profile.custom_pointing(
|
236
|
+
orbit,
|
237
|
+
alignment_target,
|
238
|
+
clocking_target,
|
239
|
+
)
|
144
240
|
|
145
241
|
assert profile is not None
|
146
|
-
assert isinstance(profile, Profile)
|
147
242
|
assert profile.is_defined()
|
@@ -241,52 +241,49 @@ def test_coerce_to_iso_failure():
|
|
241
241
|
coerce_to_iso("some_ill_formed_iso")
|
242
242
|
|
243
243
|
|
244
|
-
|
245
|
-
value
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
datetime(2020, 1, 1, tzinfo=timezone.utc),
|
288
|
-
datetime(2020, 1, 2, tzinfo=timezone.utc),
|
289
|
-
]
|
244
|
+
@pytest.mark.parametrize(
|
245
|
+
"value",
|
246
|
+
[
|
247
|
+
(
|
248
|
+
Interval.closed(
|
249
|
+
Instant.date_time(DateTime(2020, 1, 1), Scale.UTC),
|
250
|
+
Instant.date_time(DateTime(2020, 1, 2), Scale.UTC),
|
251
|
+
)
|
252
|
+
),
|
253
|
+
(
|
254
|
+
(
|
255
|
+
Instant.date_time(DateTime(2020, 1, 1), Scale.UTC),
|
256
|
+
Instant.date_time(DateTime(2020, 1, 2), Scale.UTC),
|
257
|
+
)
|
258
|
+
),
|
259
|
+
(
|
260
|
+
[
|
261
|
+
Instant.date_time(DateTime(2020, 1, 1), Scale.UTC),
|
262
|
+
Instant.date_time(DateTime(2020, 1, 2), Scale.UTC),
|
263
|
+
]
|
264
|
+
),
|
265
|
+
(
|
266
|
+
(
|
267
|
+
datetime(2020, 1, 1, tzinfo=timezone.utc),
|
268
|
+
datetime(2020, 1, 2, tzinfo=timezone.utc),
|
269
|
+
)
|
270
|
+
),
|
271
|
+
(
|
272
|
+
[
|
273
|
+
datetime(2020, 1, 1, tzinfo=timezone.utc),
|
274
|
+
datetime(2020, 1, 2, tzinfo=timezone.utc),
|
275
|
+
]
|
276
|
+
),
|
277
|
+
(
|
278
|
+
[
|
279
|
+
"2020-01-01T00:00:00Z",
|
280
|
+
"2020-01-02T00:00:00Z",
|
281
|
+
]
|
282
|
+
),
|
283
|
+
("[2020-01-01T00:00:00Z - 2020-01-02T00:00:00Z] [UTC]"),
|
284
|
+
],
|
285
|
+
)
|
286
|
+
def test_coerce_to_interval(value):
|
290
287
|
assert coerce_to_interval(value) == Interval.closed(
|
291
288
|
Instant.date_time(DateTime(2020, 1, 1), Scale.UTC),
|
292
289
|
Instant.date_time(DateTime(2020, 1, 2), Scale.UTC),
|
@@ -1,25 +1,15 @@
|
|
1
1
|
# Apache License 2.0
|
2
2
|
|
3
|
-
import
|
3
|
+
from datetime import datetime
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
from ostk.
|
5
|
+
from ostk.physics import Environment
|
6
|
+
from ostk.physics.time import Instant, Interval
|
7
|
+
from ostk.physics.coordinate import Position
|
8
|
+
from ostk.physics.coordinate.spherical import LLA, AER
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Scale = physics.time.Scale
|
13
|
-
Instant = physics.time.Instant
|
14
|
-
Interval = physics.time.Interval
|
15
|
-
Position = physics.coordinate.Position
|
16
|
-
Velocity = physics.coordinate.Velocity
|
17
|
-
Frame = physics.coordinate.Frame
|
18
|
-
LLA = physics.coordinate.spherical.LLA
|
19
|
-
AER = physics.coordinate.spherical.AER
|
20
|
-
Environment = physics.Environment
|
21
|
-
Trajectory = astrodynamics.Trajectory
|
22
|
-
State = astrodynamics.trajectory.State
|
10
|
+
from ostk.astrodynamics import utilities
|
11
|
+
from ostk.astrodynamics import Trajectory
|
12
|
+
from ostk.astrodynamics.trajectory import State
|
23
13
|
|
24
14
|
|
25
15
|
class TestUtility:
|
@@ -27,22 +17,16 @@ class TestUtility:
|
|
27
17
|
self,
|
28
18
|
state: State,
|
29
19
|
):
|
30
|
-
lla:
|
20
|
+
lla: LLA = utilities.lla_from_state(state)
|
31
21
|
|
32
22
|
assert lla is not None
|
33
|
-
assert
|
34
|
-
assert
|
35
|
-
assert isinstance(lla[0], float)
|
36
|
-
assert lla[0] >= -90.0 and lla[0] <= 90.0
|
37
|
-
assert isinstance(lla[1], float)
|
38
|
-
assert lla[0] >= -180.0 and lla[0] <= 180.0
|
39
|
-
assert isinstance(lla[2], float)
|
23
|
+
assert isinstance(lla, LLA)
|
24
|
+
assert lla.is_defined()
|
40
25
|
|
41
26
|
def test_lla_from_position(
|
42
27
|
self,
|
43
28
|
instant: Instant,
|
44
29
|
position: Position,
|
45
|
-
state: State,
|
46
30
|
):
|
47
31
|
lla: LLA = utilities.lla_from_position(position, instant)
|
48
32
|
|
@@ -50,12 +34,6 @@ class TestUtility:
|
|
50
34
|
assert isinstance(lla, LLA)
|
51
35
|
assert lla.is_defined()
|
52
36
|
|
53
|
-
lla_from_state: tuple[float, float, float] = utilities.lla_from_state(state)
|
54
|
-
|
55
|
-
assert lla.get_latitude().in_degrees() == lla_from_state[0]
|
56
|
-
assert lla.get_longitude().in_degrees() == lla_from_state[1]
|
57
|
-
assert lla.get_altitude().in_meters() == lla_from_state[2]
|
58
|
-
|
59
37
|
def test_position_from_lla(
|
60
38
|
self,
|
61
39
|
lla: LLA,
|
@@ -72,28 +50,24 @@ class TestUtility:
|
|
72
50
|
position: Position,
|
73
51
|
environment: Environment,
|
74
52
|
):
|
75
|
-
aer:
|
76
|
-
instant, position, position, environment
|
77
|
-
)
|
53
|
+
aer: AER = utilities.compute_aer(instant, position, position, environment)
|
78
54
|
|
79
55
|
assert aer is not None
|
80
|
-
assert
|
81
|
-
assert aer[2] == 0.0
|
56
|
+
assert isinstance(aer, AER)
|
82
57
|
|
83
|
-
def
|
58
|
+
def test_compute_time_lla_aer_coordinates(
|
84
59
|
self,
|
85
60
|
state: State,
|
86
61
|
position: Position,
|
87
62
|
environment: Environment,
|
88
63
|
):
|
89
|
-
time_lla_aer: float[
|
90
|
-
utilities.
|
64
|
+
time_lla_aer: float[datetime, float, float, float, float, float, float] = (
|
65
|
+
utilities.compute_time_lla_aer_coordinates(state, position, environment)
|
91
66
|
)
|
92
67
|
|
93
68
|
assert time_lla_aer is not None
|
94
69
|
assert len(time_lla_aer) == 7
|
95
|
-
assert isinstance(time_lla_aer[0],
|
96
|
-
assert time_lla_aer[0] == state.get_instant()
|
70
|
+
assert isinstance(time_lla_aer[0], datetime)
|
97
71
|
for i in range(1, 7):
|
98
72
|
assert isinstance(time_lla_aer[i], float)
|
99
73
|
|
@@ -102,12 +76,23 @@ class TestUtility:
|
|
102
76
|
interval: Interval,
|
103
77
|
trajectory: Trajectory,
|
104
78
|
):
|
105
|
-
output: list[
|
106
|
-
|
107
|
-
|
79
|
+
output: list[LLA] = utilities.compute_trajectory_geometry(trajectory, interval)
|
80
|
+
|
81
|
+
assert output is not None
|
82
|
+
assert len(output) == 2
|
83
|
+
assert isinstance(output[0], LLA)
|
84
|
+
|
85
|
+
def test_compute_ground_track(
|
86
|
+
self,
|
87
|
+
interval: Interval,
|
88
|
+
trajectory: Trajectory,
|
89
|
+
):
|
90
|
+
output: list[LLA] = utilities.compute_ground_track(trajectory, interval)
|
108
91
|
|
109
92
|
assert output is not None
|
110
93
|
assert len(output) == 2
|
94
|
+
assert isinstance(output[0], LLA)
|
95
|
+
assert output[0].get_altitude().in_meters() <= 15.0
|
111
96
|
|
112
97
|
def test_convert_state(self, state: State):
|
113
98
|
output: tuple[
|
@@ -5,11 +5,11 @@ import pytest
|
|
5
5
|
from ostk.physics.time import Instant
|
6
6
|
from ostk.physics.time import DateTime
|
7
7
|
from ostk.physics.time import Scale
|
8
|
-
from ostk.physics.coordinate import Position
|
9
|
-
from ostk.physics.coordinate import Velocity
|
10
8
|
from ostk.physics.coordinate import Frame
|
9
|
+
from ostk.physics.coordinate import Transform
|
11
10
|
|
12
11
|
from ostk.astrodynamics.trajectory import LocalOrbitalFrameFactory
|
12
|
+
from ostk.astrodynamics.trajectory import LocalOrbitalFrameTransformProvider
|
13
13
|
|
14
14
|
|
15
15
|
@pytest.fixture
|
@@ -17,6 +17,16 @@ def parent_frame() -> Frame:
|
|
17
17
|
return Frame.GCRF()
|
18
18
|
|
19
19
|
|
20
|
+
@pytest.fixture
|
21
|
+
def local_orbital_transform_provider_type() -> LocalOrbitalFrameTransformProvider.Type:
|
22
|
+
return LocalOrbitalFrameTransformProvider.Type.VNC
|
23
|
+
|
24
|
+
|
25
|
+
@pytest.fixture
|
26
|
+
def transform_generator() -> callable:
|
27
|
+
return lambda instant, position, velocity: Transform.identity(Transform.Type.passive)
|
28
|
+
|
29
|
+
|
20
30
|
@pytest.fixture
|
21
31
|
def local_orbital_frame_factory(parent_frame: Frame) -> LocalOrbitalFrameFactory:
|
22
32
|
return LocalOrbitalFrameFactory.VNC(parent_frame)
|
@@ -74,3 +84,25 @@ class TestLocalOrbitalFrameFactory:
|
|
74
84
|
local_orbital_frame_factory: LocalOrbitalFrameFactory,
|
75
85
|
):
|
76
86
|
assert local_orbital_frame_factory.is_defined()
|
87
|
+
|
88
|
+
def test_constructor(
|
89
|
+
self,
|
90
|
+
local_orbital_transform_provider_type: LocalOrbitalFrameTransformProvider.Type,
|
91
|
+
parent_frame: Frame,
|
92
|
+
):
|
93
|
+
assert (
|
94
|
+
LocalOrbitalFrameFactory.construct(
|
95
|
+
local_orbital_transform_provider_type, parent_frame
|
96
|
+
)
|
97
|
+
is not None
|
98
|
+
)
|
99
|
+
|
100
|
+
def test_custom_constructor(
|
101
|
+
self,
|
102
|
+
transform_generator: callable,
|
103
|
+
parent_frame: Frame,
|
104
|
+
):
|
105
|
+
assert (
|
106
|
+
LocalOrbitalFrameFactory.construct(transform_generator, parent_frame)
|
107
|
+
is not None
|
108
|
+
)
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
import pytest
|
4
4
|
|
5
|
+
from datetime import datetime, timezone
|
6
|
+
|
5
7
|
import numpy as np
|
6
8
|
|
7
9
|
from ostk.mathematics.geometry.d3.transformation.rotation import Quaternion
|
@@ -177,6 +179,142 @@ class TestState:
|
|
177
179
|
assert custom_state == state
|
178
180
|
assert custom_state is not state
|
179
181
|
|
182
|
+
def test_from_dict_with_eci_coordinates(self):
|
183
|
+
data = {
|
184
|
+
"timestamp": datetime.now(timezone.utc),
|
185
|
+
"rx_eci": 7000.0,
|
186
|
+
"ry_eci": 0.0,
|
187
|
+
"rz_eci": 0.0,
|
188
|
+
"vx_eci": 0.0,
|
189
|
+
"vy_eci": 7.5,
|
190
|
+
"vz_eci": 0.0,
|
191
|
+
}
|
192
|
+
|
193
|
+
state: State = State.from_dict(data)
|
194
|
+
|
195
|
+
assert state is not None
|
196
|
+
assert isinstance(state, State)
|
197
|
+
assert state.get_frame() == Frame.GCRF()
|
198
|
+
|
199
|
+
def test_from_dict_with_ecef_coordinates(self):
|
200
|
+
data = {
|
201
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
202
|
+
"rx_ecef": 7000.0,
|
203
|
+
"ry_ecef": 0.0,
|
204
|
+
"rz_ecef": 0.0,
|
205
|
+
"vx_ecef": 0.0,
|
206
|
+
"vy_ecef": 7.5,
|
207
|
+
"vz_ecef": 0.0,
|
208
|
+
}
|
209
|
+
|
210
|
+
state: State = State.from_dict(data)
|
211
|
+
|
212
|
+
assert state is not None
|
213
|
+
assert isinstance(state, State)
|
214
|
+
assert state.get_frame() == Frame.ITRF()
|
215
|
+
|
216
|
+
def test_from_dict_with_generic_coordinates(self):
|
217
|
+
data = {
|
218
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
219
|
+
"rx": 7000.0,
|
220
|
+
"ry": 0.0,
|
221
|
+
"rz": 0.0,
|
222
|
+
"vx": 0.0,
|
223
|
+
"vy": 7.5,
|
224
|
+
"vz": 0.0,
|
225
|
+
"frame": "GCRF",
|
226
|
+
}
|
227
|
+
|
228
|
+
state: State = State.from_dict(data)
|
229
|
+
|
230
|
+
assert state is not None
|
231
|
+
assert isinstance(state, State)
|
232
|
+
assert state.get_frame() == Frame.GCRF()
|
233
|
+
|
234
|
+
with pytest.raises(
|
235
|
+
ValueError, match="Frame must be provided for generic columns."
|
236
|
+
):
|
237
|
+
data = {
|
238
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
239
|
+
"rx": 7000.0,
|
240
|
+
"ry": 0.0,
|
241
|
+
"rz": 0.0,
|
242
|
+
"vx": 0.0,
|
243
|
+
"vy": 7.5,
|
244
|
+
"vz": 0.0,
|
245
|
+
}
|
246
|
+
|
247
|
+
State.from_dict(data)
|
248
|
+
|
249
|
+
with pytest.raises(ValueError, match="No frame exists with name \\[RANDOM\\]."):
|
250
|
+
data = {
|
251
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
252
|
+
"rx": 7000.0,
|
253
|
+
"ry": 0.0,
|
254
|
+
"rz": 0.0,
|
255
|
+
"vx": 0.0,
|
256
|
+
"vy": 7.5,
|
257
|
+
"vz": 0.0,
|
258
|
+
"frame": "RANDOM",
|
259
|
+
}
|
260
|
+
|
261
|
+
State.from_dict(data)
|
262
|
+
|
263
|
+
with pytest.raises(ValueError, match="Invalid frame data \\[123\\]"):
|
264
|
+
data = {
|
265
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
266
|
+
"rx": 7000.0,
|
267
|
+
"ry": 0.0,
|
268
|
+
"rz": 0.0,
|
269
|
+
"vx": 0.0,
|
270
|
+
"vy": 7.5,
|
271
|
+
"vz": 0.0,
|
272
|
+
"frame": 123,
|
273
|
+
}
|
274
|
+
|
275
|
+
State.from_dict(data)
|
276
|
+
|
277
|
+
def test_from_dict_with_attitude_quaternion(self):
|
278
|
+
data = {
|
279
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
280
|
+
"rx_eci": 7000.0,
|
281
|
+
"ry_eci": 0.0,
|
282
|
+
"rz_eci": 0.0,
|
283
|
+
"vx_eci": 0.0,
|
284
|
+
"vy_eci": 7.5,
|
285
|
+
"vz_eci": 0.0,
|
286
|
+
"q_B_ECI_x": 0.0,
|
287
|
+
"q_B_ECI_y": 0.0,
|
288
|
+
"q_B_ECI_z": 0.0,
|
289
|
+
"q_B_ECI_s": 1.0,
|
290
|
+
}
|
291
|
+
|
292
|
+
state: State = State.from_dict(data)
|
293
|
+
|
294
|
+
assert state is not None
|
295
|
+
assert isinstance(state, State)
|
296
|
+
assert state.get_frame() == Frame.GCRF()
|
297
|
+
|
298
|
+
def test_from_dict_with_angular_velocity(self):
|
299
|
+
data = {
|
300
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
301
|
+
"rx_eci": 7000.0,
|
302
|
+
"ry_eci": 0.0,
|
303
|
+
"rz_eci": 0.0,
|
304
|
+
"vx_eci": 0.0,
|
305
|
+
"vy_eci": 7.5,
|
306
|
+
"vz_eci": 0.0,
|
307
|
+
"w_B_ECI_in_B_x": 0.1,
|
308
|
+
"w_B_ECI_in_B_y": 0.2,
|
309
|
+
"w_B_ECI_in_B_z": 0.3,
|
310
|
+
}
|
311
|
+
|
312
|
+
state: State = State.from_dict(data)
|
313
|
+
|
314
|
+
assert state is not None
|
315
|
+
assert isinstance(state, State)
|
316
|
+
assert state.get_frame() == Frame.GCRF()
|
317
|
+
|
180
318
|
def test_comparators(self, state: State):
|
181
319
|
assert (state == state) is True
|
182
320
|
assert (state != state) is False
|
ostk/astrodynamics/utilities.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Apache License 2.0
|
2
2
|
|
3
|
+
from datetime import datetime
|
4
|
+
from datetime import timezone
|
5
|
+
|
3
6
|
from .OpenSpaceToolkitAstrodynamicsPy import *
|
4
7
|
|
5
8
|
from ostk.physics import Environment
|
@@ -7,6 +10,7 @@ from ostk.physics.time import Scale
|
|
7
10
|
from ostk.physics.time import Instant
|
8
11
|
from ostk.physics.time import Duration
|
9
12
|
from ostk.physics.time import Interval
|
13
|
+
from ostk.physics.unit import Length
|
10
14
|
from ostk.physics.coordinate.spherical import LLA
|
11
15
|
from ostk.physics.coordinate.spherical import AER
|
12
16
|
from ostk.physics.coordinate import Position
|
@@ -15,18 +19,18 @@ from ostk.physics.environment.object.celestial import Earth
|
|
15
19
|
from ostk.physics.environment.gravitational import Earth as EarthGravitationalModel
|
16
20
|
|
17
21
|
|
18
|
-
def lla_from_state(state: trajectory.State) ->
|
22
|
+
def lla_from_state(state: trajectory.State) -> LLA:
|
19
23
|
"""
|
20
24
|
Return latitude (degrees), longitude (degrees), altitude (meters) float list from a state.
|
21
|
-
"""
|
22
25
|
|
23
|
-
|
26
|
+
Args:
|
27
|
+
state (trajectory.State): A state.
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
)
|
29
|
+
Returns:
|
30
|
+
LLA: The LLA.
|
31
|
+
"""
|
32
|
+
|
33
|
+
return lla_from_position(state.get_position(), state.get_instant())
|
30
34
|
|
31
35
|
|
32
36
|
def lla_from_position(
|
@@ -35,6 +39,13 @@ def lla_from_position(
|
|
35
39
|
) -> LLA:
|
36
40
|
"""
|
37
41
|
Return LLA from position and instant.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
position (Position): A position.
|
45
|
+
instant (Instant): An instant.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
LLA: The LLA.
|
38
49
|
"""
|
39
50
|
|
40
51
|
if position.access_frame() != Frame.ITRF():
|
@@ -54,6 +65,12 @@ def lla_from_position(
|
|
54
65
|
def position_from_lla(lla: LLA) -> Position:
|
55
66
|
"""
|
56
67
|
Return position from lla.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
lla (LLA): A LLA.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
Position: The position.
|
57
74
|
"""
|
58
75
|
|
59
76
|
return Position.meters(
|
@@ -70,9 +87,18 @@ def compute_aer(
|
|
70
87
|
from_position: Position,
|
71
88
|
to_position: Position,
|
72
89
|
environment: Environment,
|
73
|
-
) ->
|
90
|
+
) -> AER:
|
74
91
|
"""
|
75
|
-
Return
|
92
|
+
Return AER from Instant and Positions (observer, target).
|
93
|
+
|
94
|
+
Args:
|
95
|
+
instant (Instant): An instant.
|
96
|
+
from_position (Position): An observer position.
|
97
|
+
to_position (Position): A target position.
|
98
|
+
environment (Environment): An environment.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
AER: The AER.
|
76
102
|
"""
|
77
103
|
|
78
104
|
from_lla: LLA = lla_from_position(from_position, instant)
|
@@ -83,27 +109,30 @@ def compute_aer(
|
|
83
109
|
from_position_NED: Position = from_position.in_frame(ned_frame, instant)
|
84
110
|
to_position_NED: Position = to_position.in_frame(ned_frame, instant)
|
85
111
|
|
86
|
-
|
112
|
+
return AER.from_position_to_position(from_position_NED, to_position_NED, True)
|
87
113
|
|
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
114
|
|
94
|
-
|
95
|
-
def compute_time_lla_aer_state(
|
115
|
+
def compute_time_lla_aer_coordinates(
|
96
116
|
state: trajectory.State,
|
97
117
|
from_position: Position,
|
98
118
|
environment: Environment,
|
99
|
-
) -> tuple[
|
119
|
+
) -> tuple[datetime, float, float, float, float, float, float]:
|
100
120
|
"""
|
101
|
-
Return [
|
121
|
+
Return [datetime, latitude, longitude, altitude, azimuth, elevation, range] from State and observer Position.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
state (trajectory.State): A state.
|
125
|
+
from_position (Position): An observer position.
|
126
|
+
environment (Environment): An environment.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
tuple[datetime, float, float, float, float, float, float]: The [datetime, latitude, longitude, altitude, azimuth, elevation, range].
|
102
130
|
"""
|
103
131
|
|
104
132
|
instant: Instant = state.get_instant()
|
105
133
|
|
106
|
-
lla:
|
134
|
+
lla: LLA = lla_from_state(state)
|
135
|
+
|
107
136
|
aer: AER = compute_aer(
|
108
137
|
instant,
|
109
138
|
from_position,
|
@@ -111,44 +140,102 @@ def compute_time_lla_aer_state(
|
|
111
140
|
environment,
|
112
141
|
)
|
113
142
|
|
114
|
-
return (
|
143
|
+
return (
|
144
|
+
instant.get_date_time(Scale.UTC).replace(tzinfo=timezone.utc),
|
145
|
+
float(lla.get_latitude().in_degrees()),
|
146
|
+
float(lla.get_longitude().in_degrees()),
|
147
|
+
float(lla.get_altitude().in_meters()),
|
148
|
+
float(aer.get_azimuth().in_degrees()),
|
149
|
+
float(aer.get_elevation().in_degrees()),
|
150
|
+
float(aer.get_range().in_meters()),
|
151
|
+
)
|
115
152
|
|
116
153
|
|
117
154
|
def compute_trajectory_geometry(
|
118
155
|
trajectory: Trajectory,
|
119
156
|
interval: Interval,
|
120
|
-
|
157
|
+
step: Duration = Duration.minutes(1.0),
|
158
|
+
) -> list[LLA]:
|
121
159
|
"""
|
122
|
-
Return
|
160
|
+
Return list of LLA along a Trajectory during the Interval at the provided step.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
trajectory (Trajectory): A trajectory.
|
164
|
+
interval (Interval): An interval.
|
165
|
+
step (Duration): A step. Defaults to 1.0 minutes.
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
list[LLA]: The list of LLA.
|
123
169
|
"""
|
124
170
|
|
125
171
|
return [
|
126
172
|
lla_from_state(state)
|
127
|
-
for state in trajectory.get_states_at(
|
128
|
-
interval.generate_grid(Duration.minutes(1.0))
|
129
|
-
)
|
173
|
+
for state in trajectory.get_states_at(interval.generate_grid(step))
|
130
174
|
]
|
131
175
|
|
132
176
|
|
177
|
+
def compute_ground_track(
|
178
|
+
trajectory: Trajectory,
|
179
|
+
interval: Interval,
|
180
|
+
step: Duration = Duration.minutes(1.0),
|
181
|
+
) -> list[LLA]:
|
182
|
+
"""
|
183
|
+
Return list of LLA along a Trajectory ground track (altitude set to 10.0 meters) during the Interval at the provided step.
|
184
|
+
|
185
|
+
Args:
|
186
|
+
trajectory (Trajectory): A trajectory.
|
187
|
+
interval (Interval): An interval.
|
188
|
+
step (Duration): A step. Defaults to 1.0 minutes.
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
list[LLA]: The list of LLA.
|
192
|
+
"""
|
193
|
+
|
194
|
+
ground_llas: list[LLA] = []
|
195
|
+
for state in trajectory.get_states_at(interval.generate_grid(step)):
|
196
|
+
lla: LLA = lla_from_state(state)
|
197
|
+
|
198
|
+
ground_lla: LLA = LLA(
|
199
|
+
lla.get_latitude(),
|
200
|
+
lla.get_longitude(),
|
201
|
+
Length.meters(10.0),
|
202
|
+
)
|
203
|
+
|
204
|
+
ground_llas.append(ground_lla)
|
205
|
+
|
206
|
+
return ground_llas
|
207
|
+
|
208
|
+
|
133
209
|
def convert_state(
|
134
210
|
state: trajectory.State,
|
135
211
|
) -> tuple[str, float, float, float, float, float, float, float, float, float]:
|
136
212
|
"""
|
137
213
|
Convert a State into dataframe-ready values.
|
214
|
+
- Instant (str)
|
215
|
+
- Modified Julian Date (float)
|
216
|
+
- Position X [meters] (float)
|
217
|
+
- Position Y [meters] (float)
|
218
|
+
- Position Z [meters] (float)
|
219
|
+
- Velocity X [meters/second] (float)
|
220
|
+
- Velocity Y [meters/second] (float)
|
221
|
+
- Velocity Z [meters/second] (float)
|
222
|
+
- Latitude [degrees] (float)
|
223
|
+
- Longitude [degrees] (float)
|
224
|
+
- Altitude [meters] (float)
|
225
|
+
|
226
|
+
Args:
|
227
|
+
state (trajectory.State): A state.
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
tuple[str, float, float, float, float, float, float, float, float, float]: The dataframe-ready values.
|
138
231
|
"""
|
139
232
|
|
140
|
-
lla: LLA =
|
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
|
-
)
|
233
|
+
lla: LLA = lla_from_state(state)
|
147
234
|
|
148
235
|
instant: Instant = state.get_instant()
|
149
236
|
|
150
237
|
return (
|
151
|
-
|
238
|
+
str(instant.to_string()),
|
152
239
|
float(instant.get_modified_julian_date(Scale.UTC)),
|
153
240
|
*state.get_position().get_coordinates().transpose().tolist(),
|
154
241
|
*state.get_velocity().get_coordinates().transpose().tolist(),
|
Binary file
|
File without changes
|
File without changes
|
File without changes
|