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,247 @@
|
|
1
|
+
# Apache License 2.0
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from datetime import datetime
|
6
|
+
from datetime import timezone
|
7
|
+
|
8
|
+
from .OpenSpaceToolkitAstrodynamicsPy import *
|
9
|
+
|
10
|
+
from ostk.physics import Environment
|
11
|
+
from ostk.physics.time import Scale
|
12
|
+
from ostk.physics.time import Instant
|
13
|
+
from ostk.physics.time import Duration
|
14
|
+
from ostk.physics.time import Interval
|
15
|
+
from ostk.physics.unit import Length
|
16
|
+
from ostk.physics.coordinate.spherical import LLA
|
17
|
+
from ostk.physics.coordinate.spherical import AER
|
18
|
+
from ostk.physics.coordinate import Position
|
19
|
+
from ostk.physics.coordinate import Frame
|
20
|
+
from ostk.physics.environment.object.celestial import Earth
|
21
|
+
from ostk.physics.environment.gravitational import Earth as EarthGravitationalModel
|
22
|
+
|
23
|
+
|
24
|
+
def lla_from_state(state: trajectory.State) -> LLA:
|
25
|
+
"""
|
26
|
+
Return latitude (degrees), longitude (degrees), altitude (meters) float list from a state.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
state (trajectory.State): A state.
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
LLA: The LLA.
|
33
|
+
"""
|
34
|
+
|
35
|
+
return lla_from_position(state.get_position(), state.get_instant())
|
36
|
+
|
37
|
+
|
38
|
+
def lla_from_position(
|
39
|
+
position: Position,
|
40
|
+
instant: Instant | None = None,
|
41
|
+
) -> LLA:
|
42
|
+
"""
|
43
|
+
Return LLA from position and instant.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
position (Position): A position.
|
47
|
+
instant (Instant): An instant.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
LLA: The LLA.
|
51
|
+
"""
|
52
|
+
|
53
|
+
if position.access_frame() != Frame.ITRF():
|
54
|
+
if instant is None:
|
55
|
+
raise ValueError(
|
56
|
+
"Instant must be provided if position is not expressed in ECEF."
|
57
|
+
)
|
58
|
+
position: Position = position.in_frame(Frame.ITRF(), instant)
|
59
|
+
|
60
|
+
return LLA.cartesian(
|
61
|
+
position.get_coordinates(),
|
62
|
+
EarthGravitationalModel.EGM2008.equatorial_radius,
|
63
|
+
EarthGravitationalModel.EGM2008.flattening,
|
64
|
+
)
|
65
|
+
|
66
|
+
|
67
|
+
def position_from_lla(lla: LLA) -> Position:
|
68
|
+
"""
|
69
|
+
Return position from lla.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
lla (LLA): A LLA.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Position: The position.
|
76
|
+
"""
|
77
|
+
|
78
|
+
return Position.meters(
|
79
|
+
lla.to_cartesian(
|
80
|
+
EarthGravitationalModel.EGM2008.equatorial_radius,
|
81
|
+
EarthGravitationalModel.EGM2008.flattening,
|
82
|
+
),
|
83
|
+
Frame.ITRF(),
|
84
|
+
)
|
85
|
+
|
86
|
+
|
87
|
+
def compute_aer(
|
88
|
+
instant: Instant,
|
89
|
+
from_position: Position,
|
90
|
+
to_position: Position,
|
91
|
+
environment: Environment,
|
92
|
+
) -> AER:
|
93
|
+
"""
|
94
|
+
Return AER from Instant and Positions (observer, target).
|
95
|
+
|
96
|
+
Args:
|
97
|
+
instant (Instant): An instant.
|
98
|
+
from_position (Position): An observer position.
|
99
|
+
to_position (Position): A target position.
|
100
|
+
environment (Environment): An environment.
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
AER: The AER.
|
104
|
+
"""
|
105
|
+
|
106
|
+
from_lla: LLA = lla_from_position(from_position, instant)
|
107
|
+
|
108
|
+
earth: Earth = environment.access_celestial_object_with_name("Earth")
|
109
|
+
ned_frame: Frame = earth.get_frame_at(from_lla, Earth.FrameType.NED)
|
110
|
+
|
111
|
+
from_position_NED: Position = from_position.in_frame(ned_frame, instant)
|
112
|
+
to_position_NED: Position = to_position.in_frame(ned_frame, instant)
|
113
|
+
|
114
|
+
return AER.from_position_to_position(from_position_NED, to_position_NED, True)
|
115
|
+
|
116
|
+
|
117
|
+
def compute_time_lla_aer_coordinates(
|
118
|
+
state: trajectory.State,
|
119
|
+
from_position: Position,
|
120
|
+
environment: Environment,
|
121
|
+
) -> tuple[datetime, float, float, float, float, float, float]:
|
122
|
+
"""
|
123
|
+
Return [datetime, latitude, longitude, altitude, azimuth, elevation, range] from State and observer Position.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
state (trajectory.State): A state.
|
127
|
+
from_position (Position): An observer position.
|
128
|
+
environment (Environment): An environment.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
tuple[datetime, float, float, float, float, float, float]: The [datetime, latitude, longitude, altitude, azimuth, elevation, range].
|
132
|
+
"""
|
133
|
+
|
134
|
+
instant: Instant = state.get_instant()
|
135
|
+
|
136
|
+
lla: LLA = lla_from_state(state)
|
137
|
+
|
138
|
+
aer: AER = compute_aer(
|
139
|
+
instant,
|
140
|
+
from_position,
|
141
|
+
state.get_position().in_frame(Frame.ITRF(), state.get_instant()),
|
142
|
+
environment,
|
143
|
+
)
|
144
|
+
|
145
|
+
return (
|
146
|
+
instant.get_date_time(Scale.UTC).replace(tzinfo=timezone.utc),
|
147
|
+
float(lla.get_latitude().in_degrees()),
|
148
|
+
float(lla.get_longitude().in_degrees()),
|
149
|
+
float(lla.get_altitude().in_meters()),
|
150
|
+
float(aer.get_azimuth().in_degrees()),
|
151
|
+
float(aer.get_elevation().in_degrees()),
|
152
|
+
float(aer.get_range().in_meters()),
|
153
|
+
)
|
154
|
+
|
155
|
+
|
156
|
+
def compute_trajectory_geometry(
|
157
|
+
trajectory: Trajectory,
|
158
|
+
interval: Interval,
|
159
|
+
step: Duration = Duration.minutes(1.0),
|
160
|
+
) -> list[LLA]:
|
161
|
+
"""
|
162
|
+
Return list of LLA along a Trajectory during the Interval at the provided step.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
trajectory (Trajectory): A trajectory.
|
166
|
+
interval (Interval): An interval.
|
167
|
+
step (Duration): A step. Defaults to 1.0 minutes.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
list[LLA]: The list of LLA.
|
171
|
+
"""
|
172
|
+
|
173
|
+
return [
|
174
|
+
lla_from_state(state)
|
175
|
+
for state in trajectory.get_states_at(interval.generate_grid(step))
|
176
|
+
]
|
177
|
+
|
178
|
+
|
179
|
+
def compute_ground_track(
|
180
|
+
trajectory: Trajectory,
|
181
|
+
interval: Interval,
|
182
|
+
step: Duration = Duration.minutes(1.0),
|
183
|
+
) -> list[LLA]:
|
184
|
+
"""
|
185
|
+
Return list of LLA along a Trajectory ground track (altitude set to 10.0 meters) during the Interval at the provided step.
|
186
|
+
|
187
|
+
Args:
|
188
|
+
trajectory (Trajectory): A trajectory.
|
189
|
+
interval (Interval): An interval.
|
190
|
+
step (Duration): A step. Defaults to 1.0 minutes.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
list[LLA]: The list of LLA.
|
194
|
+
"""
|
195
|
+
|
196
|
+
ground_llas: list[LLA] = []
|
197
|
+
for state in trajectory.get_states_at(interval.generate_grid(step)):
|
198
|
+
lla: LLA = lla_from_state(state)
|
199
|
+
|
200
|
+
ground_lla: LLA = LLA(
|
201
|
+
lla.get_latitude(),
|
202
|
+
lla.get_longitude(),
|
203
|
+
Length.meters(10.0),
|
204
|
+
)
|
205
|
+
|
206
|
+
ground_llas.append(ground_lla)
|
207
|
+
|
208
|
+
return ground_llas
|
209
|
+
|
210
|
+
|
211
|
+
def convert_state(
|
212
|
+
state: trajectory.State,
|
213
|
+
) -> tuple[str, float, float, float, float, float, float, float, float, float]:
|
214
|
+
"""
|
215
|
+
Convert a State into dataframe-ready values.
|
216
|
+
- Instant (str)
|
217
|
+
- Modified Julian Date (float)
|
218
|
+
- Position X [meters] (float)
|
219
|
+
- Position Y [meters] (float)
|
220
|
+
- Position Z [meters] (float)
|
221
|
+
- Velocity X [meters/second] (float)
|
222
|
+
- Velocity Y [meters/second] (float)
|
223
|
+
- Velocity Z [meters/second] (float)
|
224
|
+
- Latitude [degrees] (float)
|
225
|
+
- Longitude [degrees] (float)
|
226
|
+
- Altitude [meters] (float)
|
227
|
+
|
228
|
+
Args:
|
229
|
+
state (trajectory.State): A state.
|
230
|
+
|
231
|
+
Returns:
|
232
|
+
tuple[str, float, float, float, float, float, float, float, float, float]: The dataframe-ready values.
|
233
|
+
"""
|
234
|
+
|
235
|
+
lla: LLA = lla_from_state(state)
|
236
|
+
|
237
|
+
instant: Instant = state.get_instant()
|
238
|
+
|
239
|
+
return (
|
240
|
+
str(instant.to_string()),
|
241
|
+
float(instant.get_modified_julian_date(Scale.UTC)),
|
242
|
+
*state.get_position().get_coordinates().transpose().tolist(),
|
243
|
+
*state.get_velocity().get_coordinates().transpose().tolist(),
|
244
|
+
float(lla.get_latitude().in_degrees()),
|
245
|
+
float(lla.get_longitude().in_degrees()),
|
246
|
+
float(lla.get_altitude().in_meters()),
|
247
|
+
)
|
@@ -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.")
|