open-space-toolkit-astrodynamics 16.1.0__py311-none-manylinux2014_aarch64.whl → 16.3.0__py311-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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: open-space-toolkit-astrodynamics
3
- Version: 16.1.0
3
+ Version: 16.3.0
4
4
  Summary: Orbit, attitude, access.
5
5
  Author: Open Space Collective
6
6
  Author-email: contact@open-space-collective.org
@@ -1,7 +1,7 @@
1
1
  ostk/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
2
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-311-aarch64-linux-gnu.so,sha256=9fmfraU2lPhefU_6TsuoOL8vzvlEUnjGy5KZhjTsYWA,2558128
2
+ ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-311-aarch64-linux-gnu.so,sha256=eYbHv3EMHzJjkOjnqeiFvy-Ln5zbSUyBnJUAeM851Pk,2537648
3
3
  ostk/astrodynamics/__init__.py,sha256=3gWyqFIbhAfcdeMhmfBPQPlPQTmaOzm-6flkJe745Zk,251
4
- ostk/astrodynamics/__init__.pyi,sha256=dK0Zxyqiz9NYFkU3JxpSxismYAss-xZvJv5M0NfwZIc,32193
4
+ ostk/astrodynamics/__init__.pyi,sha256=pbhgy4KSQj-3AOEK1MbKMC0TQdz9SNmrdvTIKFOVWkA,32193
5
5
  ostk/astrodynamics/access.pyi,sha256=t2CF0TU6_6ow_rkV_I4rVKap7ZIdC4jYKL3WkTDHRXg,25157
6
6
  ostk/astrodynamics/converters.py,sha256=luPh30qMp9bzEkN7hUccmxlLf7zRp_AzqmBe8IUjPhU,3314
7
7
  ostk/astrodynamics/converters.pyi,sha256=HrZFyizkc6Hv_K38ZKZ80fX_bAxd6keA_NFWNQygvbs,1745
@@ -11,16 +11,16 @@ ostk/astrodynamics/dynamics.pyi,sha256=gZ95KoGex4SB-1z6yMrngkZN1Ir9X6bEmrZgdLxq1
11
11
  ostk/astrodynamics/estimator.pyi,sha256=MnahWzp8aACxrNKWlYRsgQr5zpBxogNr-yPm7hJob5k,14000
12
12
  ostk/astrodynamics/event_condition.pyi,sha256=2c_1Sq7tkYKeAA_aRWBi43KDQXXxu6EMSmUpEWz_Fa4,45814
13
13
  ostk/astrodynamics/guidance_law.pyi,sha256=rVmbpV2Y5FsIXejaInxINS67nVHmTIxVEkhaDIn17SA,12171
14
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16,sha256=vYKniNkCP03xaob_yvSqt9X36oinswZudb4upz1lSWc,3831176
14
+ ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16,sha256=jKQApPJR22aaC1Tq6SZbSSmgk4s4XCR1s54f6cHq4dU,3866232
15
15
  ostk/astrodynamics/solver.pyi,sha256=sPqyYPXBfFGC24dzzYyFyt01VfMAlWQ5_gh_RpeaBFk,17734
16
16
  ostk/astrodynamics/utilities.py,sha256=y8mr3M46J5z-GhS1oIEnuEJA6otwcyJ9YDhvn_5JxHM,6976
17
- ostk/astrodynamics/viewer.py,sha256=PLznIOEArRlx-pfMHpGoszNRf2F8mf2SHe8CGvZrVnw,16740
17
+ ostk/astrodynamics/viewer.py,sha256=SlKyOWKjaF3V9HFB3I7ZgHy7n_GLeHTWM9q2wXkpxe8,27077
18
18
  ostk/astrodynamics/conjunction/__init__.pyi,sha256=HFvWl8bCmrq3cBkUh5X5RGIh8webmVGxaZdpsz3WN-E,79
19
19
  ostk/astrodynamics/conjunction/message/__init__.pyi,sha256=5H__sg_QUx7ybf9jtVWvXzrUHeK3ECotfhddAdHjJUc,75
20
20
  ostk/astrodynamics/conjunction/message/ccsds.pyi,sha256=1Peto19hRqlD7KHf1cyLP3CT4OAKzwtemqvO6_4FZ0g,28162
21
21
  ostk/astrodynamics/data/__init__.pyi,sha256=4l_mfVbnU_L7wImwgTCe8fVI81gK_tUmd0z7BY9lLi8,81
22
22
  ostk/astrodynamics/data/provider.pyi,sha256=O4Lg9FBq9itufgATnic5SMg90pn8vJJJMUdNcWP72NI,1492
23
- ostk/astrodynamics/flight/__init__.pyi,sha256=lcObS0XM7GvdJ_Oe-1GGQkSg3y_MYh-n5i5AwH6eREQ,21204
23
+ ostk/astrodynamics/flight/__init__.pyi,sha256=JEMXdq_Fkfc2GGMFZL3pta-Juu5sj9v5t3A_Tdc9SW8,22248
24
24
  ostk/astrodynamics/flight/system.pyi,sha256=WVxy6Oe4q3C81c0AOxSwAmnwUHcpXO7JCEvESjs2g4A,10508
25
25
  ostk/astrodynamics/flight/profile/__init__.pyi,sha256=WBTG17V59UwD-X1r6TOrXT_rA3WjKY-2ML1cWfji_4g,3688
26
26
  ostk/astrodynamics/flight/profile/model.pyi,sha256=g5Uy2ZLxImuETrMP4jqXr5FXl2dGAoKuNgzOH07EdBo,6807
@@ -39,7 +39,7 @@ ostk/astrodynamics/test/test_import.py,sha256=py_hALBR0IYuUzv9dfgQZzrrLHJIpnyKvt
39
39
  ostk/astrodynamics/test/test_root_solver.py,sha256=hQ8O6g-WP49gZH_H3Rdufv0F0gQorpzJyIcjBGGUQ34,1831
40
40
  ostk/astrodynamics/test/test_trajectory.py,sha256=GzH7RNRiL088nFeeMm1lhpG4-HEFz15cnWcbD-8VAow,3933
41
41
  ostk/astrodynamics/test/test_utilities.py,sha256=NNIyzqOxMdsNpK2z0wU0utX06iZNfbMJDE36Upard28,3048
42
- ostk/astrodynamics/test/test_viewer.py,sha256=kDstRH_WKufN_0JPSXttzLMk3Afv_ylcEBfHxc35yrA,8946
42
+ ostk/astrodynamics/test/test_viewer.py,sha256=6IxHjSrwnLkmLiTTzRcnwAdEeWdIF_b2Kjz5iCqwctw,10912
43
43
  ostk/astrodynamics/test/access/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
44
44
  ostk/astrodynamics/test/access/test_generator.py,sha256=i7TnM80kF0Q_9KmyoqKt5n1ufg3ZjAIEMPVVds8ZDdI,10315
45
45
  ostk/astrodynamics/test/access/test_visibility_criterion.py,sha256=VA6WDQTj3q-f2YGIIkrrNp8G23Nf_0g9nKmfZAgAlWQ,6568
@@ -69,7 +69,7 @@ ostk/astrodynamics/test/event_condition/test_logical_condition.py,sha256=09h5TYW
69
69
  ostk/astrodynamics/test/event_condition/test_real_condition.py,sha256=tle6HVzMFMIIkfRY7CuaA0mPtw3riJBG_JQkc1L0dpk,1374
70
70
  ostk/astrodynamics/test/flight/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
71
71
  ostk/astrodynamics/test/flight/test_maneuver.py,sha256=HonAvD9qtax72lizoDLR6ELI-poC3LWk_umg-WYygYA,5388
72
- ostk/astrodynamics/test/flight/test_profile.py,sha256=V1IekQv7Mju3fMlQZuY4vc1LPNVHsPMtjpT9zI2L_DE,7865
72
+ ostk/astrodynamics/test/flight/test_profile.py,sha256=ialv-BdeGq9WX_G8l9sva-C0TXfenA8ZOXEA_aiVtuA,8051
73
73
  ostk/astrodynamics/test/flight/test_system.py,sha256=5kJCULHdpkwAC7i6xLV7vdJnGfOdrOuhi0G22p_L160,1224
74
74
  ostk/astrodynamics/test/flight/system/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
75
75
  ostk/astrodynamics/test/flight/system/test_propulsion_system.py,sha256=SoxOt-fjHvs_86f6Xb3jAsHDOShJ6Puz7eO5wrqibaM,1835
@@ -129,8 +129,8 @@ ostk/astrodynamics/trajectory/orbit/model/kepler.pyi,sha256=OZMznHuU7e6m1rfqtOgX
129
129
  ostk/astrodynamics/trajectory/orbit/model/sgp4.pyi,sha256=OhFzoPPQHlYy7m3LiZ8TXF89M4uBTfNk6tGsBEp0sjI,14235
130
130
  ostk/astrodynamics/trajectory/state/__init__.pyi,sha256=bq__Fii35czVrTeNxc9eQVjXdqwbbQxUdNQWK3vLrMo,17649
131
131
  ostk/astrodynamics/trajectory/state/coordinate_subset.pyi,sha256=kYMfMwEjCqO1NepMYFT4QS6kIPBkVL6sGCLeLbogcMw,10176
132
- open_space_toolkit_astrodynamics-16.1.0.dist-info/METADATA,sha256=-oOEr23wSU5vy8jOZpmEflURSXpN7wm-K4nMa6S9JPc,1913
133
- open_space_toolkit_astrodynamics-16.1.0.dist-info/WHEEL,sha256=DkMZqRfK9fJpItD2rZUb6F-GgOse-1Hr87pC4CCxOQ8,111
134
- open_space_toolkit_astrodynamics-16.1.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
135
- open_space_toolkit_astrodynamics-16.1.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
136
- open_space_toolkit_astrodynamics-16.1.0.dist-info/RECORD,,
132
+ open_space_toolkit_astrodynamics-16.3.0.dist-info/METADATA,sha256=lMlaorcNreWP2h-7yOnWlQc2jlrPiVBcYNzSzPBv5cA,1913
133
+ open_space_toolkit_astrodynamics-16.3.0.dist-info/WHEEL,sha256=DkMZqRfK9fJpItD2rZUb6F-GgOse-1Hr87pC4CCxOQ8,111
134
+ open_space_toolkit_astrodynamics-16.3.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
135
+ open_space_toolkit_astrodynamics-16.3.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
136
+ open_space_toolkit_astrodynamics-16.3.0.dist-info/RECORD,,
@@ -5,8 +5,8 @@ from ostk.astrodynamics.trajectory import State as PyState
5
5
  from ostk import core as OpenSpaceToolkitCorePy
6
6
  from ostk.core import container
7
7
  from ostk.core import filesystem
8
- import ostk.core.type
9
8
  from ostk.core import type
9
+ import ostk.core.type
10
10
  from ostk import io as OpenSpaceToolkitIOPy
11
11
  from ostk.io import URL
12
12
  from ostk.io import ip
@@ -19,8 +19,8 @@ from ostk import physics as OpenSpaceToolkitPhysicsPy
19
19
  from ostk.physics import Environment
20
20
  from ostk.physics import Manager
21
21
  from ostk.physics import Unit
22
- from ostk.physics import coordinate
23
22
  import ostk.physics.coordinate
23
+ from ostk.physics import coordinate
24
24
  import ostk.physics.coordinate.spherical
25
25
  from ostk.physics import environment
26
26
  import ostk.physics.environment.object
@@ -186,7 +186,7 @@ class Profile:
186
186
  @property
187
187
  def orientation_generator(self) -> typing.Callable[[ostk.astrodynamics.trajectory.State], numpy.ndarray[numpy.float64[3, 1]]]:
188
188
  """
189
- The orientation generator of the target
189
+ The orientation generator of the target.
190
190
  """
191
191
  class OrientationProfileTarget(Profile.Target):
192
192
  """
@@ -207,7 +207,7 @@ class Profile:
207
207
  @property
208
208
  def orientation_profile(self) -> list[tuple[ostk.physics.time.Instant, numpy.ndarray[numpy.float64[3, 1]]]]:
209
209
  """
210
- The orientation profile of the target
210
+ The orientation profile of the target.
211
211
  """
212
212
  class Target:
213
213
  """
@@ -254,6 +254,10 @@ class Profile:
254
254
 
255
255
  Trajectory : Trajectory
256
256
 
257
+ TargetPosition : Target position
258
+
259
+ TargetVelocity : Target velocity
260
+
257
261
  Sun : Sun
258
262
 
259
263
  Moon : Moon
@@ -268,14 +272,16 @@ class Profile:
268
272
  """
269
273
  GeocentricNadir: typing.ClassVar[Profile.TargetType] # value = <TargetType.GeocentricNadir: 0>
270
274
  GeodeticNadir: typing.ClassVar[Profile.TargetType] # value = <TargetType.GeodeticNadir: 1>
271
- Moon: typing.ClassVar[Profile.TargetType] # value = <TargetType.Moon: 4>
272
- OrbitalMomentum: typing.ClassVar[Profile.TargetType] # value = <TargetType.OrbitalMomentum: 7>
273
- OrientationProfile: typing.ClassVar[Profile.TargetType] # value = <TargetType.OrientationProfile: 8>
274
- Sun: typing.ClassVar[Profile.TargetType] # value = <TargetType.Sun: 3>
275
+ Moon: typing.ClassVar[Profile.TargetType] # value = <TargetType.Moon: 6>
276
+ OrbitalMomentum: typing.ClassVar[Profile.TargetType] # value = <TargetType.OrbitalMomentum: 9>
277
+ OrientationProfile: typing.ClassVar[Profile.TargetType] # value = <TargetType.OrientationProfile: 10>
278
+ Sun: typing.ClassVar[Profile.TargetType] # value = <TargetType.Sun: 5>
279
+ TargetPosition: typing.ClassVar[Profile.TargetType] # value = <TargetType.TargetPosition: 3>
280
+ TargetVelocity: typing.ClassVar[Profile.TargetType] # value = <TargetType.TargetVelocity: 4>
275
281
  Trajectory: typing.ClassVar[Profile.TargetType] # value = <TargetType.Trajectory: 2>
276
- VelocityECEF: typing.ClassVar[Profile.TargetType] # value = <TargetType.VelocityECEF: 6>
277
- VelocityECI: typing.ClassVar[Profile.TargetType] # value = <TargetType.VelocityECI: 5>
278
- __members__: typing.ClassVar[dict[str, Profile.TargetType]] # value = {'GeocentricNadir': <TargetType.GeocentricNadir: 0>, 'GeodeticNadir': <TargetType.GeodeticNadir: 1>, 'Trajectory': <TargetType.Trajectory: 2>, 'Sun': <TargetType.Sun: 3>, 'Moon': <TargetType.Moon: 4>, 'VelocityECI': <TargetType.VelocityECI: 5>, 'VelocityECEF': <TargetType.VelocityECEF: 6>, 'OrbitalMomentum': <TargetType.OrbitalMomentum: 7>, 'OrientationProfile': <TargetType.OrientationProfile: 8>}
282
+ VelocityECEF: typing.ClassVar[Profile.TargetType] # value = <TargetType.VelocityECEF: 8>
283
+ VelocityECI: typing.ClassVar[Profile.TargetType] # value = <TargetType.VelocityECI: 7>
284
+ __members__: typing.ClassVar[dict[str, Profile.TargetType]] # value = {'GeocentricNadir': <TargetType.GeocentricNadir: 0>, 'GeodeticNadir': <TargetType.GeodeticNadir: 1>, 'Trajectory': <TargetType.Trajectory: 2>, 'TargetPosition': <TargetType.TargetPosition: 3>, 'TargetVelocity': <TargetType.TargetVelocity: 4>, 'Sun': <TargetType.Sun: 5>, 'Moon': <TargetType.Moon: 6>, 'VelocityECI': <TargetType.VelocityECI: 7>, 'VelocityECEF': <TargetType.VelocityECEF: 8>, 'OrbitalMomentum': <TargetType.OrbitalMomentum: 9>, 'OrientationProfile': <TargetType.OrientationProfile: 10>}
279
285
  def __eq__(self, other: typing.Any) -> bool:
280
286
  ...
281
287
  def __getstate__(self) -> int:
@@ -309,6 +315,16 @@ class Profile:
309
315
 
310
316
 
311
317
  """
318
+ @staticmethod
319
+ def target_position(trajectory: ostk.astrodynamics.Trajectory, axis: Profile.Axis, anti_direction: bool = False) -> Profile.TrajectoryTarget:
320
+ """
321
+ Create a target, which produces a vector pointing from the observer to the target position.
322
+ """
323
+ @staticmethod
324
+ def target_velocity(trajectory: ostk.astrodynamics.Trajectory, axis: Profile.Axis, anti_direction: bool = False) -> Profile.TrajectoryTarget:
325
+ """
326
+ Create a target, which produces a vector pointing along the target velocity.
327
+ """
312
328
  def __init__(self, trajectory: ostk.astrodynamics.Trajectory, axis: Profile.Axis, anti_direction: bool = False) -> None:
313
329
  """
314
330
  Constructor.
@@ -321,7 +337,7 @@ class Profile:
321
337
  @property
322
338
  def trajectory(self) -> ostk.astrodynamics.Trajectory:
323
339
  """
324
- The trajectory of the target. Required only if the target type is `Trajectory`.
340
+ The trajectory of the target. Used to compute the target position or velocity.
325
341
  """
326
342
  @staticmethod
327
343
  def align_and_constrain(alignment_target: Profile.Target, clocking_target: Profile.Target, angular_offset: ostk.physics.unit.Angle = ...) -> typing.Callable[[ostk.astrodynamics.trajectory.State], ostk.mathematics.geometry.d3.transformation.rotation.Quaternion]:
@@ -97,7 +97,11 @@ def profile(request) -> Profile:
97
97
  @pytest.fixture(
98
98
  params=[
99
99
  Profile.Target(Profile.TargetType.GeocentricNadir, Profile.Axis.X),
100
- Profile.TrajectoryTarget(
100
+ Profile.TrajectoryTarget.target_position(
101
+ Trajectory.position(Position.meters((0.0, 0.0, 0.0), Frame.ITRF())),
102
+ Profile.Axis.X,
103
+ ),
104
+ Profile.TrajectoryTarget.target_velocity(
101
105
  Trajectory.position(Position.meters((0.0, 0.0, 0.0), Frame.ITRF())),
102
106
  Profile.Axis.X,
103
107
  ),
@@ -19,6 +19,7 @@ from ostk.astrodynamics.trajectory import Orbit
19
19
  from ostk.astrodynamics.flight import Profile
20
20
  from ostk.astrodynamics.viewer import Viewer
21
21
  from ostk.astrodynamics.viewer import ConicSensor
22
+ from ostk.astrodynamics.viewer import _compute_celestial_angular_diameter_from_states
22
23
 
23
24
 
24
25
  @pytest.fixture
@@ -172,6 +173,48 @@ class TestViewer:
172
173
  )
173
174
  assert rendered_html.endswith("</script>")
174
175
 
176
+ @pytest.mark.parametrize(
177
+ "celestial_body_name",
178
+ ["Earth", "Moon", "Sun"],
179
+ )
180
+ def test_add_celestial_body_direction_success(
181
+ self,
182
+ viewer: Viewer,
183
+ orbit: Orbit,
184
+ celestial_body_name: str,
185
+ environment: Environment,
186
+ ):
187
+ viewer.add_celestial_body_direction(
188
+ profile_or_trajectory=orbit,
189
+ time_step=Duration.seconds(30.0),
190
+ celestial=environment.access_celestial_object_with_name(celestial_body_name),
191
+ )
192
+
193
+ rendered_html: str = viewer.render()
194
+
195
+ assert rendered_html.startswith('<meta charset="utf-8">')
196
+ assert "var widget = new Cesium.Viewer" in rendered_html
197
+ assert " widget.entities.add({position: widget" in rendered_html
198
+ assert (
199
+ f"widget.entities.add({{position: widget.{celestial_body_name.lower()}_direction_position"
200
+ in rendered_html
201
+ )
202
+ assert rendered_html.endswith("</script>")
203
+
204
+ def test_add_ground_tracks_success(
205
+ self,
206
+ viewer: Viewer,
207
+ orbit: Orbit,
208
+ ):
209
+ viewer.add_ground_tracks(profile_or_trajectory=orbit)
210
+
211
+ rendered_html: str = viewer.render()
212
+
213
+ assert rendered_html.startswith('<meta charset="utf-8">')
214
+ assert "var widget = new Cesium.Viewer" in rendered_html
215
+ assert "widget.entities.add({polyline:" in rendered_html
216
+ assert rendered_html.endswith("</script>")
217
+
175
218
  def test_add_target_success(
176
219
  self,
177
220
  viewer: Viewer,
@@ -260,3 +303,16 @@ class TestViewer:
260
303
  in rendered_html
261
304
  )
262
305
  assert rendered_html.endswith("</script>")
306
+
307
+
308
+ def test_compute_celestial_angular_diameter_from_states_success(
309
+ orbit: Orbit,
310
+ interval: Interval,
311
+ environment: Environment,
312
+ ) -> None:
313
+ assert _compute_celestial_angular_diameter_from_states(
314
+ celestial=environment.access_celestial_object_with_name("Sun"),
315
+ states=orbit.get_states_at(
316
+ interval.generate_grid(Duration.seconds(30.0)),
317
+ ),
318
+ ).mean() == pytest.approx(0.54, rel=1e-2)
@@ -16,6 +16,7 @@ except ImportError:
16
16
 
17
17
  from ostk.mathematics.geometry.d3.transformation.rotation import Quaternion
18
18
 
19
+ from ostk.physics.environment.object import Celestial
19
20
  from ostk.physics.unit import Length
20
21
  from ostk.physics.unit import Angle
21
22
  from ostk.physics.time import Instant, Interval, Duration
@@ -23,16 +24,20 @@ from ostk.physics.coordinate import Position
23
24
  from ostk.physics.coordinate import Frame
24
25
  from ostk.physics.coordinate.spherical import LLA
25
26
 
27
+ from ostk.astrodynamics import Trajectory
26
28
  from ostk.astrodynamics.flight import Profile
27
29
  from ostk.astrodynamics.trajectory import Orbit
28
30
  from ostk.astrodynamics.trajectory import State
29
31
 
30
32
  from .converters import coerce_to_datetime
31
33
  from .utilities import lla_from_position
34
+ from .utilities import lla_from_state
35
+ from .utilities import position_from_lla
32
36
 
33
37
  DEFAULT_SATELLITE_IMAGE: str = (
34
38
  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADJSURBVDhPnZHRDcMgEEMZjVEYpaNklIzSEfLfD4qNnXAJSFWfhO7w2Zc0Tf9QG2rXrEzSUeZLOGm47WoH95x3Hl3jEgilvDgsOQUTqsNl68ezEwn1vae6lceSEEYvvWNT/Rxc4CXQNGadho1NXoJ+9iaqc2xi2xbt23PJCDIB6TQjOC6Bho/sDy3fBQT8PrVhibU7yBFcEPaRxOoeTwbwByCOYf9VGp1BYI1BA+EeHhmfzKbBoJEQwn1yzUZtyspIQUha85MpkNIXB7GizqDEECsAAAAASUVORK5CYII="
35
39
  )
40
+ DEFAULT_STEP_DURATION: Duration = Duration.seconds(10.0)
36
41
 
37
42
 
38
43
  @dataclass
@@ -228,14 +233,7 @@ class Viewer:
228
233
  satellite = cesiumpy.Satellite(
229
234
  position=_generate_sampled_position_from_llas(instants, llas),
230
235
  orientation=_generate_sampled_orientation(states),
231
- availability=cesiumpy.TimeIntervalCollection(
232
- intervals=[
233
- cesiumpy.TimeInterval(
234
- start=coerce_to_datetime(self._interval.get_start()),
235
- stop=coerce_to_datetime(self._interval.get_end()),
236
- ),
237
- ],
238
- ),
236
+ availability=self._get_availability(),
239
237
  model=cesiumpy.IonResource(
240
238
  asset_id=cesium_asset_id or 0
241
239
  ), # TBM: Should be made more robust
@@ -267,6 +265,158 @@ class Viewer:
267
265
 
268
266
  return self
269
267
 
268
+ def add_celestial_body_direction(
269
+ self,
270
+ profile_or_trajectory: Profile | Trajectory,
271
+ celestial: Celestial,
272
+ time_step: Duration | None = None,
273
+ color: str | None = None,
274
+ ) -> Viewer:
275
+ """
276
+ Add the celestial direction to the viewer.
277
+
278
+ Args:
279
+ profile_or_trajectory (Profile | Trajectory): The profile or trajectory to be added.
280
+ celestial (Celestial, optional): The celestial body to be used.
281
+ time_step (Duration): The duration of each step in the grid.
282
+ Default to None. If None, the default step duration is used.
283
+ color (str, optional): The color of the celestial body direction.
284
+ Defaults to None. If None, the color depends on the celestial body (for the Earth, Sun and Moon).
285
+ Otherwise, use the default color (RED).
286
+
287
+ Returns:
288
+ Viewer: The Viewer.
289
+ """
290
+ time_step = time_step or DEFAULT_STEP_DURATION
291
+ alpha_color: float = 0.5
292
+ reference_frame: Frame = Frame.GCRF()
293
+ reference_vector: np.ndarray = np.array([0.0, 0.0, 1.0])
294
+ instants: list[Instant] = self._interval.generate_grid(time_step)
295
+ celestial_name: str = str(celestial.access_name())
296
+
297
+ if color is None:
298
+ if celestial_name == "Earth":
299
+ color = cesiumpy.color.BLUE
300
+ elif celestial_name == "Moon":
301
+ color = cesiumpy.color.GREY
302
+ elif celestial_name == "Sun":
303
+ color = cesiumpy.color.YELLOW
304
+ else:
305
+ color = cesiumpy.color.RED
306
+
307
+ # Apply an alpha to the color
308
+ color = color.with_alpha(alpha_color)
309
+
310
+ def _create_celestial_body_direction_state(
311
+ satellite_state: State,
312
+ reference_frame: Frame = reference_frame,
313
+ reference_vector: np.ndarray = reference_vector,
314
+ celestial: Celestial = celestial,
315
+ ) -> State:
316
+ state_in_reference_frame: State = satellite_state.in_frame(reference_frame)
317
+ return State(
318
+ instant=state_in_reference_frame.get_instant(),
319
+ position=state_in_reference_frame.get_position(),
320
+ velocity=state_in_reference_frame.get_velocity(),
321
+ attitude=Quaternion.shortest_rotation(
322
+ first_vector=_compute_celestial_direction_from_state(
323
+ state=satellite_state,
324
+ celestial=celestial,
325
+ frame=reference_frame,
326
+ ),
327
+ second_vector=reference_vector,
328
+ ),
329
+ angular_velocity=np.zeros(3),
330
+ attitude_frame=reference_frame,
331
+ )
332
+
333
+ celestial_direction_states: list[State] = list(
334
+ map(
335
+ _create_celestial_body_direction_state,
336
+ profile_or_trajectory.get_states_at(instants),
337
+ )
338
+ )
339
+
340
+ satellite = cesiumpy.Satellite(
341
+ position=_generate_sampled_position_from_llas(
342
+ instants=instants,
343
+ llas=_generate_llas(celestial_direction_states),
344
+ ),
345
+ orientation=_generate_sampled_orientation(celestial_direction_states),
346
+ availability=self._get_availability(),
347
+ )
348
+
349
+ _cesium_from_ostk_sensor(
350
+ ConicSensor(
351
+ name=celestial_name.lower() + "_direction",
352
+ direction=reference_vector,
353
+ # Compute the half angle from the celestial body diameter
354
+ half_angle=Angle.degrees(
355
+ _compute_celestial_angular_diameter_from_states(
356
+ celestial=celestial,
357
+ states=celestial_direction_states,
358
+ ).mean()
359
+ / 2.0
360
+ ),
361
+ length=Length.meters(2.0),
362
+ color=color,
363
+ )
364
+ ).render(
365
+ viewer=self._viewer,
366
+ satellite=satellite,
367
+ )
368
+
369
+ return self
370
+
371
+ def add_ground_tracks(
372
+ self,
373
+ profile_or_trajectory: Profile | Trajectory,
374
+ time_step: Duration | None = None,
375
+ show_current_position: bool = True,
376
+ ) -> Viewer:
377
+ """
378
+ Add ground tracks to the viewer.
379
+
380
+ Args:
381
+ profile_or_trajectory (Profile | Trajectory): The profile or trajectory to be added.
382
+ time_step (Duration, optional): The duration of each step in the grid.
383
+ Default to None. If None, the default step duration is used.
384
+ show_current_position (bool, optional): Whether to show the current position as a point. Defaults to True.
385
+
386
+ Returns:
387
+ Viewer: The Viewer.
388
+ """
389
+ time_step = time_step or DEFAULT_STEP_DURATION
390
+
391
+ instants: list[Instant] = self._interval.generate_grid(time_step)
392
+ llas: list[LLA] = []
393
+ ground_track_positions: list[Position] = []
394
+
395
+ for state in profile_or_trajectory.get_states_at(instants):
396
+ satellite_lla: LLA = lla_from_state(state)
397
+ lla: LLA = LLA(
398
+ latitude=satellite_lla.get_latitude(),
399
+ longitude=satellite_lla.get_longitude(),
400
+ altitude=Length.meters(0.0),
401
+ )
402
+ llas.append(lla)
403
+ ground_track_positions.append(position_from_lla(lla))
404
+
405
+ self.add_line(
406
+ positions=ground_track_positions,
407
+ size=1,
408
+ color=cesiumpy.color.GRAY,
409
+ )
410
+
411
+ if show_current_position:
412
+ self.add_moving_point(
413
+ instants=instants,
414
+ llas=llas,
415
+ color=cesiumpy.color.DARKORANGE,
416
+ )
417
+
418
+ return self
419
+
270
420
  def add_target(
271
421
  self,
272
422
  position: Position,
@@ -341,6 +491,40 @@ class Viewer:
341
491
 
342
492
  return self
343
493
 
494
+ def add_moving_point(
495
+ self,
496
+ instants: list[Instant],
497
+ llas: list[LLA],
498
+ color: str | None = None,
499
+ size: int | None = None,
500
+ ) -> Viewer:
501
+ """
502
+ Add a moving point to the Viewer.
503
+
504
+ Args:
505
+ instants (list[Instant]): The list of instants.
506
+ llas (list[LLA]): The list of Longitude, Latitude, Altitude (LLA) coordinates.
507
+ color (str, optional): The color of the point. Defaults to None. If None, the default color is used.
508
+ size (int, optional): The size of the point. Defaults to None. If None, the default size is used.
509
+
510
+ Returns:
511
+ Viewer: The Viewer.
512
+ """
513
+
514
+ self._viewer.entities.add(
515
+ cesiumpy.Point(
516
+ position=_generate_sampled_position_from_llas(
517
+ instants=instants,
518
+ llas=llas,
519
+ ),
520
+ availability=self._get_availability(),
521
+ color=color,
522
+ pixel_size=size,
523
+ )
524
+ )
525
+
526
+ return self
527
+
344
528
  def add_label(
345
529
  self,
346
530
  position: Position,
@@ -382,14 +566,21 @@ class Viewer:
382
566
 
383
567
  return self._viewer.to_html()
384
568
 
569
+ def _get_availability(self) -> cesiumpy.TimeIntervalCollection:
570
+ """
571
+ Get the availability of the viewer.
572
+
573
+ Returns:
574
+ cesiumpy.TimeIntervalCollection: The availability of the viewer.
575
+ """
576
+ return _cesium_from_ostk_intervals(intervals=[self._interval])
577
+
385
578
  def _repr_html_(self) -> str:
386
579
  return self.render()
387
580
 
388
581
 
389
582
  def _generate_llas(states: list[State]) -> list[LLA]:
390
- return [
391
- lla_from_position(state.get_position(), state.get_instant()) for state in states
392
- ]
583
+ return list(map(lla_from_state, states))
393
584
 
394
585
 
395
586
  def _generate_sampled_position_from_llas(
@@ -454,17 +645,38 @@ def _generate_sampled_position_from_states(
454
645
  Returns:
455
646
  cesiumpy.SampledPositionProperty: Sampled position property.
456
647
  """
648
+ return _generate_sampled_position_from_positions(
649
+ instants=[state.get_instant() for state in states],
650
+ positions=[state.get_position() for state in states],
651
+ )
652
+
653
+
654
+ def _generate_sampled_position_from_positions(
655
+ instants: list[Instant],
656
+ positions: list[Position],
657
+ ) -> cesiumpy.SampledPositionProperty:
658
+ """
659
+ Generate a sampled position property from a list of OSTk positions and instants.
660
+
661
+ Args:
662
+ instants (list[Instant]): A list of OSTk instants.
663
+ positions (list[Position]): A list of OSTk positions.
664
+
665
+ Returns:
666
+ cesiumpy.SampledPositionProperty: Sampled position property.
667
+ """
668
+ frame_itrf: Frame = Frame.ITRF()
457
669
 
458
670
  return cesiumpy.SampledPositionProperty(
459
671
  samples=[
460
672
  (
461
- coerce_to_datetime(state.get_instant()),
673
+ coerce_to_datetime(instant),
462
674
  _cesium_from_ostk_position(
463
- position=state.in_frame(Frame.ITRF()).get_position()
675
+ position.in_frame(instant=instant, frame=frame_itrf)
464
676
  ),
465
677
  None,
466
678
  )
467
- for state in states
679
+ for instant, position in zip(instants, positions)
468
680
  ],
469
681
  )
470
682
 
@@ -547,3 +759,93 @@ def _cesium_from_ostk_sensor(sensor: Sensor) -> cesiumpy.Sensor:
547
759
  )
548
760
 
549
761
  raise NotImplementedError("{sensor.__name__} is not supported yet.")
762
+
763
+
764
+ def _compute_celestial_direction_from_state(
765
+ state: State,
766
+ celestial: Celestial,
767
+ frame: Frame | None = None,
768
+ ) -> np.ndarray:
769
+ """
770
+ Compute the direction of a celestial body from a state.
771
+
772
+ Args:
773
+ state (State): The state of the observer.
774
+ celestial (Celestial): The celestial body.
775
+ frame (Frame): The frame in which the celestial body is expressed.
776
+ Defaults to None. If None, the GCRF frame is used.
777
+
778
+ Returns:
779
+ np.ndarray: The direction of the celestial body (in meters).
780
+ """
781
+ frame = frame or Frame.GCRF()
782
+ return (
783
+ celestial.get_position_in(
784
+ frame=frame,
785
+ instant=state.get_instant(),
786
+ )
787
+ .in_meters()
788
+ .get_coordinates()
789
+ - state.get_position()
790
+ .in_frame(
791
+ frame=frame,
792
+ instant=state.get_instant(),
793
+ )
794
+ .in_meters()
795
+ .get_coordinates()
796
+ )
797
+
798
+
799
+ def _compute_celestial_angular_diameter_from_states(
800
+ celestial: Celestial,
801
+ states: list[State],
802
+ ) -> np.ndarray:
803
+ """
804
+ Compute the angular diameter of a celestial body from the states of an observer.
805
+
806
+ Args:
807
+ celestial (Celestial): The celestial body.
808
+ states (list[State]): The states of the observer.
809
+
810
+ Returns:
811
+ np.ndarray: The angular diameter of the celestial body (in degrees).
812
+
813
+ Reference:
814
+ https://en.wikipedia.org/wiki/Angular_diameter
815
+ """
816
+ celestial_radius_meters: float = float(celestial.get_equatorial_radius().in_meters())
817
+ celestial_to_observer_meters: np.ndarray = np.zeros((3, len(states)))
818
+
819
+ for i, state in enumerate(states):
820
+ celestial_to_observer_meters[:, i] = (
821
+ state.in_frame(celestial.access_frame())
822
+ .get_position()
823
+ .in_meters()
824
+ .get_coordinates()
825
+ )
826
+ distances: np.ndarray = np.linalg.norm(celestial_to_observer_meters, axis=0)
827
+ return np.rad2deg(2 * np.arcsin(celestial_radius_meters / distances))
828
+
829
+
830
+ def _cesium_from_ostk_intervals(
831
+ intervals: list[Interval],
832
+ ) -> cesiumpy.TimeIntervalCollection:
833
+ """
834
+ Convert a list of OSTk intervals into Cesium TimeIntervalCollection.
835
+
836
+ Args:
837
+ intervals (list[Interval]): List of OSTk intervals.
838
+
839
+ Returns:
840
+ cesiumpy.TimeIntervalCollection: Converted intervals.
841
+ """
842
+
843
+ return cesiumpy.TimeIntervalCollection(
844
+ intervals=[
845
+ cesiumpy.TimeInterval(
846
+ start=coerce_to_datetime(interval.get_start()),
847
+ stop=coerce_to_datetime(interval.get_end()),
848
+ )
849
+ for interval in intervals
850
+ ],
851
+ )