open-space-toolkit-astrodynamics 9.1.6__py39-none-manylinux2014_aarch64.whl → 13.0.2__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-9.1.6.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/METADATA +5 -5
- {open_space_toolkit_astrodynamics-9.1.6.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/RECORD +29 -27
- {open_space_toolkit_astrodynamics-9.1.6.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/WHEEL +1 -1
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-39-aarch64-linux-gnu.so +0 -0
- ostk/astrodynamics/converters.py +35 -90
- ostk/astrodynamics/dataframe.py +479 -0
- ostk/astrodynamics/display.py +2 -0
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.13 +0 -0
- ostk/astrodynamics/pytrajectory/pystate.py +216 -1
- ostk/astrodynamics/test/conftest.py +2 -2
- ostk/astrodynamics/test/flight/test_profile.py +155 -55
- ostk/astrodynamics/test/test_converters.py +43 -140
- ostk/astrodynamics/test/test_dataframe.py +875 -0
- ostk/astrodynamics/test/test_display.py +2 -3
- ostk/astrodynamics/test/test_event_condition.py +27 -7
- ostk/astrodynamics/test/test_trajectory.py +116 -38
- ostk/astrodynamics/test/test_utilities.py +31 -46
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +13 -0
- ostk/astrodynamics/test/trajectory/orbit/test_pass.py +9 -0
- ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +3 -0
- ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +2 -2
- ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +34 -2
- ostk/astrodynamics/test/trajectory/test_orbit.py +26 -2
- ostk/astrodynamics/test/trajectory/test_segment.py +99 -1
- ostk/astrodynamics/test/trajectory/test_sequence.py +53 -0
- ostk/astrodynamics/test/trajectory/test_state.py +306 -0
- ostk/astrodynamics/utilities.py +125 -36
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.9 +0 -0
- {open_space_toolkit_astrodynamics-9.1.6.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/top_level.txt +0 -0
- {open_space_toolkit_astrodynamics-9.1.6.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/zip-safe +0 -0
@@ -339,6 +339,59 @@ def instants(state: State) -> list[Instant]:
|
|
339
339
|
return [state.get_instant(), state.get_instant() + Duration.minutes(1.0)]
|
340
340
|
|
341
341
|
|
342
|
+
@pytest.fixture
|
343
|
+
def sequence_solution(
|
344
|
+
segment_solution: Segment.Solution,
|
345
|
+
):
|
346
|
+
return Sequence.Solution(
|
347
|
+
segment_solutions=[
|
348
|
+
segment_solution,
|
349
|
+
],
|
350
|
+
execution_is_complete=True,
|
351
|
+
)
|
352
|
+
|
353
|
+
|
354
|
+
class TestSequenceSolution:
|
355
|
+
def test_properties(
|
356
|
+
self,
|
357
|
+
sequence_solution: Sequence.Solution,
|
358
|
+
):
|
359
|
+
assert sequence_solution is not None
|
360
|
+
assert len(sequence_solution.segment_solutions) == 1
|
361
|
+
assert sequence_solution.execution_is_complete
|
362
|
+
|
363
|
+
def test_getters_and_accessors(
|
364
|
+
self,
|
365
|
+
sequence_solution: Sequence.Solution,
|
366
|
+
):
|
367
|
+
assert sequence_solution.access_start_instant() is not None
|
368
|
+
assert sequence_solution.access_end_instant() is not None
|
369
|
+
|
370
|
+
assert sequence_solution.get_states() is not None
|
371
|
+
assert sequence_solution.get_initial_mass() is not None
|
372
|
+
assert sequence_solution.get_final_mass() is not None
|
373
|
+
assert sequence_solution.get_propagation_duration() is not None
|
374
|
+
|
375
|
+
assert sequence_solution.compute_delta_mass() is not None
|
376
|
+
assert sequence_solution.compute_delta_v(1500.0) is not None
|
377
|
+
|
378
|
+
def test_calculate_states_at(
|
379
|
+
self,
|
380
|
+
sequence_solution: Sequence.Solution,
|
381
|
+
numerical_solver: NumericalSolver,
|
382
|
+
):
|
383
|
+
instants: list[Instant] = sequence_solution.get_interval().generate_grid(
|
384
|
+
Duration.seconds(10.0)
|
385
|
+
)
|
386
|
+
states: list[State] = sequence_solution.calculate_states_at(
|
387
|
+
instants,
|
388
|
+
numerical_solver,
|
389
|
+
)
|
390
|
+
|
391
|
+
assert states is not None
|
392
|
+
assert len(states) == len(instants)
|
393
|
+
|
394
|
+
|
342
395
|
class TestSequence:
|
343
396
|
def test_get_segments(
|
344
397
|
self,
|
@@ -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,310 @@ 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
|
+
assert state.get_size() == 6
|
199
|
+
|
200
|
+
def test_from_dict_with_ecef_coordinates(self):
|
201
|
+
data = {
|
202
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
203
|
+
"rx_ecef": 7000.0,
|
204
|
+
"ry_ecef": 0.0,
|
205
|
+
"rz_ecef": 0.0,
|
206
|
+
"vx_ecef": 0.0,
|
207
|
+
"vy_ecef": 7.5,
|
208
|
+
"vz_ecef": 0.0,
|
209
|
+
}
|
210
|
+
|
211
|
+
state: State = State.from_dict(data)
|
212
|
+
|
213
|
+
assert state is not None
|
214
|
+
assert isinstance(state, State)
|
215
|
+
assert state.get_frame() == Frame.ITRF()
|
216
|
+
assert state.get_size() == 6
|
217
|
+
|
218
|
+
def test_from_dict_with_generic_coordinates(self):
|
219
|
+
data = {
|
220
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
221
|
+
"rx": 7000.0,
|
222
|
+
"ry": 0.0,
|
223
|
+
"rz": 0.0,
|
224
|
+
"vx": 0.0,
|
225
|
+
"vy": 7.5,
|
226
|
+
"vz": 0.0,
|
227
|
+
"frame": "GCRF",
|
228
|
+
}
|
229
|
+
|
230
|
+
state: State = State.from_dict(data)
|
231
|
+
|
232
|
+
assert state is not None
|
233
|
+
assert isinstance(state, State)
|
234
|
+
assert state.get_frame() == Frame.GCRF()
|
235
|
+
assert state.get_size() == 6
|
236
|
+
|
237
|
+
with pytest.raises(
|
238
|
+
ValueError, match="Frame must be provided for generic columns."
|
239
|
+
):
|
240
|
+
data = {
|
241
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
242
|
+
"rx": 7000.0,
|
243
|
+
"ry": 0.0,
|
244
|
+
"rz": 0.0,
|
245
|
+
"vx": 0.0,
|
246
|
+
"vy": 7.5,
|
247
|
+
"vz": 0.0,
|
248
|
+
}
|
249
|
+
|
250
|
+
State.from_dict(data)
|
251
|
+
|
252
|
+
with pytest.raises(ValueError, match="No frame exists with name \\[RANDOM\\]."):
|
253
|
+
data = {
|
254
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
255
|
+
"rx": 7000.0,
|
256
|
+
"ry": 0.0,
|
257
|
+
"rz": 0.0,
|
258
|
+
"vx": 0.0,
|
259
|
+
"vy": 7.5,
|
260
|
+
"vz": 0.0,
|
261
|
+
"frame": "RANDOM",
|
262
|
+
}
|
263
|
+
|
264
|
+
State.from_dict(data)
|
265
|
+
|
266
|
+
with pytest.raises(ValueError, match="Invalid frame data \\[123\\]"):
|
267
|
+
data = {
|
268
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
269
|
+
"rx": 7000.0,
|
270
|
+
"ry": 0.0,
|
271
|
+
"rz": 0.0,
|
272
|
+
"vx": 0.0,
|
273
|
+
"vy": 7.5,
|
274
|
+
"vz": 0.0,
|
275
|
+
"frame": 123,
|
276
|
+
}
|
277
|
+
|
278
|
+
State.from_dict(data)
|
279
|
+
|
280
|
+
def test_from_dict_with_attitude_quaternion(self):
|
281
|
+
data = {
|
282
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
283
|
+
"rx_eci": 7000.0,
|
284
|
+
"ry_eci": 0.0,
|
285
|
+
"rz_eci": 0.0,
|
286
|
+
"vx_eci": 0.0,
|
287
|
+
"vy_eci": 7.5,
|
288
|
+
"vz_eci": 0.0,
|
289
|
+
"q_B_ECI_x": 0.0,
|
290
|
+
"q_B_ECI_y": 0.0,
|
291
|
+
"q_B_ECI_z": 0.0,
|
292
|
+
"q_B_ECI_s": 1.0,
|
293
|
+
}
|
294
|
+
|
295
|
+
state: State = State.from_dict(data)
|
296
|
+
|
297
|
+
assert state is not None
|
298
|
+
assert isinstance(state, State)
|
299
|
+
assert state.get_frame() == Frame.GCRF()
|
300
|
+
assert state.get_size() == 10
|
301
|
+
|
302
|
+
def test_from_dict_with_angular_velocity(self):
|
303
|
+
data = {
|
304
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
305
|
+
"rx_eci": 7000.0,
|
306
|
+
"ry_eci": 0.0,
|
307
|
+
"rz_eci": 0.0,
|
308
|
+
"vx_eci": 0.0,
|
309
|
+
"vy_eci": 7.5,
|
310
|
+
"vz_eci": 0.0,
|
311
|
+
"w_B_ECI_in_B_x": 0.1,
|
312
|
+
"w_B_ECI_in_B_y": 0.2,
|
313
|
+
"w_B_ECI_in_B_z": 0.3,
|
314
|
+
}
|
315
|
+
|
316
|
+
state: State = State.from_dict(data)
|
317
|
+
|
318
|
+
assert state is not None
|
319
|
+
assert isinstance(state, State)
|
320
|
+
assert state.get_size() == 9
|
321
|
+
|
322
|
+
def test_from_dict_with_drag_coefficient(self):
|
323
|
+
data = {
|
324
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
325
|
+
"rx_eci": 7000.0,
|
326
|
+
"ry_eci": 0.0,
|
327
|
+
"rz_eci": 0.0,
|
328
|
+
"vx_eci": 0.0,
|
329
|
+
"vy_eci": 7.5,
|
330
|
+
"vz_eci": 0.0,
|
331
|
+
"drag_coefficient": 2.2,
|
332
|
+
}
|
333
|
+
|
334
|
+
state: State = State.from_dict(data)
|
335
|
+
|
336
|
+
assert state is not None
|
337
|
+
assert isinstance(state, State)
|
338
|
+
assert state.get_size() == 7
|
339
|
+
|
340
|
+
data = {
|
341
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
342
|
+
"rx_eci": 7000.0,
|
343
|
+
"ry_eci": 0.0,
|
344
|
+
"rz_eci": 0.0,
|
345
|
+
"vx_eci": 0.0,
|
346
|
+
"vy_eci": 7.5,
|
347
|
+
"vz_eci": 0.0,
|
348
|
+
"cd": 2.2,
|
349
|
+
}
|
350
|
+
|
351
|
+
state: State = State.from_dict(data)
|
352
|
+
|
353
|
+
assert state is not None
|
354
|
+
assert isinstance(state, State)
|
355
|
+
assert state.get_size() == 7
|
356
|
+
|
357
|
+
def test_from_dict_with_surface_area(self):
|
358
|
+
data = {
|
359
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
360
|
+
"rx_eci": 7000.0,
|
361
|
+
"ry_eci": 0.0,
|
362
|
+
"rz_eci": 0.0,
|
363
|
+
"vx_eci": 0.0,
|
364
|
+
"vy_eci": 7.5,
|
365
|
+
"vz_eci": 0.0,
|
366
|
+
"surface_area": 2.2,
|
367
|
+
}
|
368
|
+
|
369
|
+
state: State = State.from_dict(data)
|
370
|
+
|
371
|
+
assert state is not None
|
372
|
+
assert isinstance(state, State)
|
373
|
+
assert state.get_size() == 7
|
374
|
+
|
375
|
+
data = {
|
376
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
377
|
+
"rx_eci": 7000.0,
|
378
|
+
"ry_eci": 0.0,
|
379
|
+
"rz_eci": 0.0,
|
380
|
+
"vx_eci": 0.0,
|
381
|
+
"vy_eci": 7.5,
|
382
|
+
"vz_eci": 0.0,
|
383
|
+
"cross_sectional_area": 2.2,
|
384
|
+
}
|
385
|
+
|
386
|
+
state: State = State.from_dict(data)
|
387
|
+
|
388
|
+
assert state is not None
|
389
|
+
assert isinstance(state, State)
|
390
|
+
assert state.get_size() == 7
|
391
|
+
|
392
|
+
def test_from_dict_with_mass(self):
|
393
|
+
data = {
|
394
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
395
|
+
"rx_eci": 7000.0,
|
396
|
+
"ry_eci": 0.0,
|
397
|
+
"rz_eci": 0.0,
|
398
|
+
"vx_eci": 0.0,
|
399
|
+
"vy_eci": 7.5,
|
400
|
+
"vz_eci": 0.0,
|
401
|
+
"mass": 2.2,
|
402
|
+
}
|
403
|
+
|
404
|
+
state: State = State.from_dict(data)
|
405
|
+
|
406
|
+
assert state is not None
|
407
|
+
assert isinstance(state, State)
|
408
|
+
assert state.get_size() == 7
|
409
|
+
|
410
|
+
@pytest.mark.parametrize(
|
411
|
+
("data", "expected_length", "expected_frame"),
|
412
|
+
[
|
413
|
+
(
|
414
|
+
{
|
415
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
416
|
+
"r_GCRF_x": 1.0,
|
417
|
+
"r_GCRF_y": 2.0,
|
418
|
+
"r_GCRF_z": 3.0,
|
419
|
+
"v_GCRF_x": 4.0,
|
420
|
+
"v_GCRF_y": 5.0,
|
421
|
+
"v_GCRF_z": 6.0,
|
422
|
+
},
|
423
|
+
6,
|
424
|
+
Frame.GCRF(),
|
425
|
+
),
|
426
|
+
(
|
427
|
+
{
|
428
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
429
|
+
"r_ITRF_x": 1.0,
|
430
|
+
"r_ITRF_y": 2.0,
|
431
|
+
"r_ITRF_z": 3.0,
|
432
|
+
"v_ITRF_x": 4.0,
|
433
|
+
"v_ITRF_y": 5.0,
|
434
|
+
"v_ITRF_z": 6.0,
|
435
|
+
},
|
436
|
+
6,
|
437
|
+
Frame.ITRF(),
|
438
|
+
),
|
439
|
+
],
|
440
|
+
)
|
441
|
+
def test_from_dict_cannonical_success(
|
442
|
+
self, data: dict, expected_length: int, expected_frame: Frame
|
443
|
+
):
|
444
|
+
state: State = State.from_dict(data)
|
445
|
+
|
446
|
+
assert state is not None
|
447
|
+
assert isinstance(state, State)
|
448
|
+
|
449
|
+
assert state.get_size() == expected_length
|
450
|
+
assert state.get_frame() == expected_frame
|
451
|
+
|
452
|
+
@pytest.mark.parametrize(
|
453
|
+
("data", "expected_failure_message"),
|
454
|
+
[
|
455
|
+
(
|
456
|
+
{
|
457
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
458
|
+
"r_GCRF_x": 1.0,
|
459
|
+
"r_GCRF_y": 2.0,
|
460
|
+
"r_GCRF_z": 3.0,
|
461
|
+
"v_GCRF_x": 4.0,
|
462
|
+
"v_GCRF_y": 5.0,
|
463
|
+
},
|
464
|
+
"Invalid state data.",
|
465
|
+
),
|
466
|
+
(
|
467
|
+
{
|
468
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
469
|
+
"r_TEST_x": 1.0,
|
470
|
+
"r_TEST_y": 2.0,
|
471
|
+
"r_TEST_z": 3.0,
|
472
|
+
"v_TEST_x": 4.0,
|
473
|
+
"v_TEST_y": 5.0,
|
474
|
+
"v_TEST_z": 6.0,
|
475
|
+
},
|
476
|
+
"No frame exists with name \\[TEST\\].",
|
477
|
+
),
|
478
|
+
],
|
479
|
+
)
|
480
|
+
def test_from_dict_cannonical_failure(
|
481
|
+
self, data: dict, expected_failure_message: str
|
482
|
+
):
|
483
|
+
with pytest.raises(ValueError, match=expected_failure_message):
|
484
|
+
State.from_dict(data)
|
485
|
+
|
180
486
|
def test_comparators(self, state: State):
|
181
487
|
assert (state == state) is True
|
182
488
|
assert (state != state) is False
|
ostk/astrodynamics/utilities.py
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Apache License 2.0
|
2
2
|
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from datetime import datetime
|
6
|
+
from datetime import timezone
|
7
|
+
|
3
8
|
from .OpenSpaceToolkitAstrodynamicsPy import *
|
4
9
|
|
5
10
|
from ostk.physics import Environment
|
@@ -7,6 +12,7 @@ from ostk.physics.time import Scale
|
|
7
12
|
from ostk.physics.time import Instant
|
8
13
|
from ostk.physics.time import Duration
|
9
14
|
from ostk.physics.time import Interval
|
15
|
+
from ostk.physics.unit import Length
|
10
16
|
from ostk.physics.coordinate.spherical import LLA
|
11
17
|
from ostk.physics.coordinate.spherical import AER
|
12
18
|
from ostk.physics.coordinate import Position
|
@@ -15,18 +21,18 @@ from ostk.physics.environment.object.celestial import Earth
|
|
15
21
|
from ostk.physics.environment.gravitational import Earth as EarthGravitationalModel
|
16
22
|
|
17
23
|
|
18
|
-
def lla_from_state(state: trajectory.State) ->
|
24
|
+
def lla_from_state(state: trajectory.State) -> LLA:
|
19
25
|
"""
|
20
26
|
Return latitude (degrees), longitude (degrees), altitude (meters) float list from a state.
|
21
|
-
"""
|
22
27
|
|
23
|
-
|
28
|
+
Args:
|
29
|
+
state (trajectory.State): A state.
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
)
|
31
|
+
Returns:
|
32
|
+
LLA: The LLA.
|
33
|
+
"""
|
34
|
+
|
35
|
+
return lla_from_position(state.get_position(), state.get_instant())
|
30
36
|
|
31
37
|
|
32
38
|
def lla_from_position(
|
@@ -35,6 +41,13 @@ def lla_from_position(
|
|
35
41
|
) -> LLA:
|
36
42
|
"""
|
37
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.
|
38
51
|
"""
|
39
52
|
|
40
53
|
if position.access_frame() != Frame.ITRF():
|
@@ -54,6 +67,12 @@ def lla_from_position(
|
|
54
67
|
def position_from_lla(lla: LLA) -> Position:
|
55
68
|
"""
|
56
69
|
Return position from lla.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
lla (LLA): A LLA.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Position: The position.
|
57
76
|
"""
|
58
77
|
|
59
78
|
return Position.meters(
|
@@ -70,9 +89,18 @@ def compute_aer(
|
|
70
89
|
from_position: Position,
|
71
90
|
to_position: Position,
|
72
91
|
environment: Environment,
|
73
|
-
) ->
|
92
|
+
) -> AER:
|
74
93
|
"""
|
75
|
-
Return
|
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.
|
76
104
|
"""
|
77
105
|
|
78
106
|
from_lla: LLA = lla_from_position(from_position, instant)
|
@@ -83,27 +111,30 @@ def compute_aer(
|
|
83
111
|
from_position_NED: Position = from_position.in_frame(ned_frame, instant)
|
84
112
|
to_position_NED: Position = to_position.in_frame(ned_frame, instant)
|
85
113
|
|
86
|
-
|
87
|
-
|
88
|
-
return (
|
89
|
-
float(aer.get_azimuth().in_degrees()),
|
90
|
-
float(aer.get_elevation().in_degrees()),
|
91
|
-
float(aer.get_range().in_meters()),
|
92
|
-
)
|
114
|
+
return AER.from_position_to_position(from_position_NED, to_position_NED, True)
|
93
115
|
|
94
116
|
|
95
|
-
def
|
117
|
+
def compute_time_lla_aer_coordinates(
|
96
118
|
state: trajectory.State,
|
97
119
|
from_position: Position,
|
98
120
|
environment: Environment,
|
99
|
-
) -> tuple[
|
121
|
+
) -> tuple[datetime, float, float, float, float, float, float]:
|
100
122
|
"""
|
101
|
-
Return [
|
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].
|
102
132
|
"""
|
103
133
|
|
104
134
|
instant: Instant = state.get_instant()
|
105
135
|
|
106
|
-
lla:
|
136
|
+
lla: LLA = lla_from_state(state)
|
137
|
+
|
107
138
|
aer: AER = compute_aer(
|
108
139
|
instant,
|
109
140
|
from_position,
|
@@ -111,44 +142,102 @@ def compute_time_lla_aer_state(
|
|
111
142
|
environment,
|
112
143
|
)
|
113
144
|
|
114
|
-
return (
|
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
|
+
)
|
115
154
|
|
116
155
|
|
117
156
|
def compute_trajectory_geometry(
|
118
157
|
trajectory: Trajectory,
|
119
158
|
interval: Interval,
|
120
|
-
|
159
|
+
step: Duration = Duration.minutes(1.0),
|
160
|
+
) -> list[LLA]:
|
121
161
|
"""
|
122
|
-
Return
|
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.
|
123
171
|
"""
|
124
172
|
|
125
173
|
return [
|
126
174
|
lla_from_state(state)
|
127
|
-
for state in trajectory.get_states_at(
|
128
|
-
interval.generate_grid(Duration.minutes(1.0))
|
129
|
-
)
|
175
|
+
for state in trajectory.get_states_at(interval.generate_grid(step))
|
130
176
|
]
|
131
177
|
|
132
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
|
+
|
133
211
|
def convert_state(
|
134
212
|
state: trajectory.State,
|
135
213
|
) -> tuple[str, float, float, float, float, float, float, float, float, float]:
|
136
214
|
"""
|
137
|
-
Convert
|
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.
|
138
233
|
"""
|
139
234
|
|
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
|
-
)
|
235
|
+
lla: LLA = lla_from_state(state)
|
147
236
|
|
148
237
|
instant: Instant = state.get_instant()
|
149
238
|
|
150
239
|
return (
|
151
|
-
|
240
|
+
str(instant.to_string()),
|
152
241
|
float(instant.get_modified_julian_date(Scale.UTC)),
|
153
242
|
*state.get_position().get_coordinates().transpose().tolist(),
|
154
243
|
*state.get_velocity().get_coordinates().transpose().tolist(),
|
Binary file
|
File without changes
|
File without changes
|