astrox-python 0.1.0__py3-none-any.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.
- astrox/__init__.py +49 -0
- astrox/_http.py +504 -0
- astrox/access.py +173 -0
- astrox/components/__init__.py +202 -0
- astrox/components/_axes.py +508 -0
- astrox/components/_common.py +141 -0
- astrox/components/_constraints.py +138 -0
- astrox/components/_objects.py +167 -0
- astrox/components/_positions.py +673 -0
- astrox/components/_rotations.py +115 -0
- astrox/components/_sensors.py +134 -0
- astrox/components/_vgt.py +337 -0
- astrox/coverage/__init__.py +41 -0
- astrox/coverage/_core.py +552 -0
- astrox/coverage/_fom.py +125 -0
- astrox/coverage/coverage_time.py +83 -0
- astrox/coverage/number_of_assets.py +160 -0
- astrox/coverage/response_time.py +160 -0
- astrox/coverage/revisit_time.py +160 -0
- astrox/coverage/simple_coverage.py +152 -0
- astrox/exceptions.py +92 -0
- astrox/lighting.py +121 -0
- astrox/orbits.py +499 -0
- astrox/propagator.py +1072 -0
- astrox/rocket.py +82 -0
- astrox_python-0.1.0.dist-info/METADATA +82 -0
- astrox_python-0.1.0.dist-info/RECORD +29 -0
- astrox_python-0.1.0.dist-info/WHEEL +4 -0
- astrox_python-0.1.0.dist-info/licenses/LICENSE +21 -0
astrox/orbits.py
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"""Public orbit value objects, constructors, and conversion helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from astrox._http import raw
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"CartesianState",
|
|
13
|
+
"KeplerianElements",
|
|
14
|
+
"MeanKeplerianElements",
|
|
15
|
+
"cartesian_state",
|
|
16
|
+
"cartesian_to_keplerian",
|
|
17
|
+
"convert_czml_position",
|
|
18
|
+
"earth_moon_libration",
|
|
19
|
+
"geo",
|
|
20
|
+
"geo_ym_lambert_delta_v",
|
|
21
|
+
"keplerian",
|
|
22
|
+
"keplerian_to_cartesian",
|
|
23
|
+
"kozai_izsak_mean_elements",
|
|
24
|
+
"lambert_delta_v",
|
|
25
|
+
"lla_at_ascending_node",
|
|
26
|
+
"molniya",
|
|
27
|
+
"sso",
|
|
28
|
+
"walker_custom",
|
|
29
|
+
"walker_delta",
|
|
30
|
+
"walker_star",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, kw_only=True)
|
|
35
|
+
class CartesianState:
|
|
36
|
+
"""Cartesian position and velocity state."""
|
|
37
|
+
|
|
38
|
+
x_m: float
|
|
39
|
+
y_m: float
|
|
40
|
+
z_m: float
|
|
41
|
+
vx_m_s: float
|
|
42
|
+
vy_m_s: float
|
|
43
|
+
vz_m_s: float
|
|
44
|
+
|
|
45
|
+
def to_wire(self) -> list[float]:
|
|
46
|
+
"""Lower to ASTROX Cartesian OrbitalElements order."""
|
|
47
|
+
return [
|
|
48
|
+
self.x_m,
|
|
49
|
+
self.y_m,
|
|
50
|
+
self.z_m,
|
|
51
|
+
self.vx_m_s,
|
|
52
|
+
self.vy_m_s,
|
|
53
|
+
self.vz_m_s,
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, kw_only=True)
|
|
58
|
+
class KeplerianElements:
|
|
59
|
+
"""Classical Keplerian orbital elements."""
|
|
60
|
+
|
|
61
|
+
semi_major_axis_m: float
|
|
62
|
+
eccentricity: float
|
|
63
|
+
inclination_deg: float
|
|
64
|
+
argument_of_periapsis_deg: float
|
|
65
|
+
raan_deg: float
|
|
66
|
+
true_anomaly_deg: float
|
|
67
|
+
|
|
68
|
+
def to_wire(self) -> list[float]:
|
|
69
|
+
"""Lower to ASTROX Classical OrbitalElements order."""
|
|
70
|
+
return [
|
|
71
|
+
self.semi_major_axis_m,
|
|
72
|
+
self.eccentricity,
|
|
73
|
+
self.inclination_deg,
|
|
74
|
+
self.argument_of_periapsis_deg,
|
|
75
|
+
self.raan_deg,
|
|
76
|
+
self.true_anomaly_deg,
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True, kw_only=True)
|
|
81
|
+
class MeanKeplerianElements:
|
|
82
|
+
"""Kozai-Izsak mean Keplerian elements returned by ASTROX."""
|
|
83
|
+
|
|
84
|
+
semi_major_axis_m: float
|
|
85
|
+
eccentricity: float
|
|
86
|
+
inclination_deg: float
|
|
87
|
+
argument_of_perigee_deg: float
|
|
88
|
+
raan_deg: float
|
|
89
|
+
mean_anomaly_deg: float
|
|
90
|
+
argument_of_latitude_deg: float
|
|
91
|
+
longitude_of_perigee_deg: float
|
|
92
|
+
mean_longitude_deg: float
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def keplerian(
|
|
96
|
+
*,
|
|
97
|
+
semi_major_axis_m: float,
|
|
98
|
+
eccentricity: float,
|
|
99
|
+
inclination_deg: float,
|
|
100
|
+
argument_of_periapsis_deg: float,
|
|
101
|
+
raan_deg: float,
|
|
102
|
+
true_anomaly_deg: float,
|
|
103
|
+
) -> KeplerianElements:
|
|
104
|
+
"""Create Classical Keplerian orbital elements."""
|
|
105
|
+
return KeplerianElements(
|
|
106
|
+
semi_major_axis_m=semi_major_axis_m,
|
|
107
|
+
eccentricity=eccentricity,
|
|
108
|
+
inclination_deg=inclination_deg,
|
|
109
|
+
argument_of_periapsis_deg=argument_of_periapsis_deg,
|
|
110
|
+
raan_deg=raan_deg,
|
|
111
|
+
true_anomaly_deg=true_anomaly_deg,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def cartesian_state(
|
|
116
|
+
*,
|
|
117
|
+
x_m: float,
|
|
118
|
+
y_m: float,
|
|
119
|
+
z_m: float,
|
|
120
|
+
vx_m_s: float,
|
|
121
|
+
vy_m_s: float,
|
|
122
|
+
vz_m_s: float,
|
|
123
|
+
) -> CartesianState:
|
|
124
|
+
"""Create Cartesian position and velocity state."""
|
|
125
|
+
return CartesianState(
|
|
126
|
+
x_m=x_m,
|
|
127
|
+
y_m=y_m,
|
|
128
|
+
z_m=z_m,
|
|
129
|
+
vx_m_s=vx_m_s,
|
|
130
|
+
vy_m_s=vy_m_s,
|
|
131
|
+
vz_m_s=vz_m_s,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _include_if_supplied(payload: dict[str, Any], wire_key: str, value: Any) -> None:
|
|
136
|
+
if value is not None:
|
|
137
|
+
payload[wire_key] = value
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _keplerian_to_wire_object(
|
|
141
|
+
orbit: KeplerianElements,
|
|
142
|
+
*,
|
|
143
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
144
|
+
) -> dict[str, Any]:
|
|
145
|
+
if not isinstance(orbit, KeplerianElements):
|
|
146
|
+
raise TypeError("orbit must be a KeplerianElements instance")
|
|
147
|
+
|
|
148
|
+
payload: dict[str, Any] = {
|
|
149
|
+
"SemimajorAxis": orbit.semi_major_axis_m,
|
|
150
|
+
"Eccentricity": orbit.eccentricity,
|
|
151
|
+
"Inclination": orbit.inclination_deg,
|
|
152
|
+
"ArgumentOfPeriapsis": orbit.argument_of_periapsis_deg,
|
|
153
|
+
"RightAscensionOfAscendingNode": orbit.raan_deg,
|
|
154
|
+
"TrueAnomaly": orbit.true_anomaly_deg,
|
|
155
|
+
}
|
|
156
|
+
_include_if_supplied(
|
|
157
|
+
payload,
|
|
158
|
+
"GravitationalParameter",
|
|
159
|
+
gravitational_parameter_m3_s2,
|
|
160
|
+
)
|
|
161
|
+
return payload
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _keplerian_from_wire_object(payload: dict[str, Any]) -> KeplerianElements:
|
|
165
|
+
return KeplerianElements(
|
|
166
|
+
semi_major_axis_m=payload["SemimajorAxis"],
|
|
167
|
+
eccentricity=payload["Eccentricity"],
|
|
168
|
+
inclination_deg=payload["Inclination"],
|
|
169
|
+
argument_of_periapsis_deg=payload["ArgumentOfPeriapsis"],
|
|
170
|
+
raan_deg=payload["RightAscensionOfAscendingNode"],
|
|
171
|
+
true_anomaly_deg=payload["TrueAnomaly"],
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _mean_keplerian_from_wire_object(payload: dict[str, Any]) -> MeanKeplerianElements:
|
|
176
|
+
return MeanKeplerianElements(
|
|
177
|
+
semi_major_axis_m=payload["SemimajorAxis"],
|
|
178
|
+
eccentricity=payload["Eccentricity"],
|
|
179
|
+
inclination_deg=payload["Inclination"],
|
|
180
|
+
argument_of_perigee_deg=payload["ArgOfPerigee"],
|
|
181
|
+
raan_deg=payload["RAAN"],
|
|
182
|
+
mean_anomaly_deg=payload["MeanAnomaly"],
|
|
183
|
+
argument_of_latitude_deg=payload["ArgOfLatitude"],
|
|
184
|
+
longitude_of_perigee_deg=payload["LongitudeOfPerigee"],
|
|
185
|
+
mean_longitude_deg=payload["MeanLongitude"],
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _cartesian_from_wire(values: Sequence[float]) -> CartesianState:
|
|
190
|
+
return CartesianState(
|
|
191
|
+
x_m=values[0],
|
|
192
|
+
y_m=values[1],
|
|
193
|
+
z_m=values[2],
|
|
194
|
+
vx_m_s=values[3],
|
|
195
|
+
vy_m_s=values[4],
|
|
196
|
+
vz_m_s=values[5],
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _wizard_pair_from_wire(
|
|
201
|
+
result: dict[str, Any],
|
|
202
|
+
) -> tuple[KeplerianElements, KeplerianElements]:
|
|
203
|
+
return (
|
|
204
|
+
_keplerian_from_wire_object(result["Elements_TOD"]),
|
|
205
|
+
_keplerian_from_wire_object(result["Elements_Inertial"]),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _walker_from_wire(
|
|
210
|
+
result: dict[str, Any],
|
|
211
|
+
) -> tuple[tuple[KeplerianElements, ...], ...]:
|
|
212
|
+
return tuple(
|
|
213
|
+
tuple(_keplerian_from_wire_object(satellite) for satellite in plane)
|
|
214
|
+
for plane in result["WalkerSatellites"]
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def keplerian_to_cartesian(
|
|
219
|
+
orbit: KeplerianElements,
|
|
220
|
+
*,
|
|
221
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
222
|
+
) -> CartesianState:
|
|
223
|
+
"""Convert Keplerian elements to Cartesian state in meters and meters per second."""
|
|
224
|
+
payload = _keplerian_to_wire_object(
|
|
225
|
+
orbit,
|
|
226
|
+
gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
result = raw.post("/OrbitConvert/Kepler2RV", json=payload)
|
|
230
|
+
return _cartesian_from_wire(result)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def cartesian_to_keplerian(state: CartesianState) -> KeplerianElements:
|
|
234
|
+
"""Convert Cartesian state to Keplerian elements using ASTROX's default Earth gravity parameter."""
|
|
235
|
+
if not isinstance(state, CartesianState):
|
|
236
|
+
raise TypeError("state must be a CartesianState instance")
|
|
237
|
+
|
|
238
|
+
result = raw.post("/OrbitConvert/RV2Kepler", json=state.to_wire())
|
|
239
|
+
return _keplerian_from_wire_object(result)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def lla_at_ascending_node(
|
|
243
|
+
orbit: KeplerianElements,
|
|
244
|
+
*,
|
|
245
|
+
orbit_epoch: str,
|
|
246
|
+
) -> tuple[float, float, float]:
|
|
247
|
+
"""Return ascending-node location as ``(longitude_deg, latitude_deg, height_m)``."""
|
|
248
|
+
payload = {
|
|
249
|
+
"OrbitEpoch": orbit_epoch,
|
|
250
|
+
**_keplerian_to_wire_object(orbit),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
result = raw.post("/OrbitConvert/Kepler2LLAAtAscendNode", json=payload)
|
|
254
|
+
return result[0], result[1], result[2]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def kozai_izsak_mean_elements(orbit: KeplerianElements) -> MeanKeplerianElements:
|
|
258
|
+
"""Convert osculating Keplerian elements to Kozai-Izsak mean elements."""
|
|
259
|
+
result = raw.post(
|
|
260
|
+
"/OrbitConvert/GetKozaiIzsakMeanElements",
|
|
261
|
+
json=_keplerian_to_wire_object(orbit),
|
|
262
|
+
)
|
|
263
|
+
return _mean_keplerian_from_wire_object(result)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def geo(
|
|
267
|
+
*,
|
|
268
|
+
orbit_epoch: str,
|
|
269
|
+
inclination_deg: float,
|
|
270
|
+
subsatellite_longitude_deg: float,
|
|
271
|
+
) -> tuple[KeplerianElements, KeplerianElements]:
|
|
272
|
+
"""Generate GEO elements as ``(elements_tod, elements_inertial)``."""
|
|
273
|
+
result = raw.post(
|
|
274
|
+
"/OrbitWizard/GEO",
|
|
275
|
+
json={
|
|
276
|
+
"OrbitEpoch": orbit_epoch,
|
|
277
|
+
"Inclination": inclination_deg,
|
|
278
|
+
"SubSatellitePoint": subsatellite_longitude_deg,
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
return _wizard_pair_from_wire(result)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def molniya(
|
|
285
|
+
*,
|
|
286
|
+
orbit_epoch: str,
|
|
287
|
+
perigee_altitude_km: float,
|
|
288
|
+
apogee_longitude_deg: float,
|
|
289
|
+
argument_of_periapsis_deg: float,
|
|
290
|
+
) -> tuple[KeplerianElements, KeplerianElements]:
|
|
291
|
+
"""Generate Molniya elements as ``(elements_tod, elements_inertial)``."""
|
|
292
|
+
result = raw.post(
|
|
293
|
+
"/OrbitWizard/Molniya",
|
|
294
|
+
json={
|
|
295
|
+
"OrbitEpoch": orbit_epoch,
|
|
296
|
+
"PerigeeAltitude": perigee_altitude_km,
|
|
297
|
+
"ApogeeLongitude": apogee_longitude_deg,
|
|
298
|
+
"ArgumentOfPeriapsis": argument_of_periapsis_deg,
|
|
299
|
+
},
|
|
300
|
+
)
|
|
301
|
+
return _wizard_pair_from_wire(result)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def sso(
|
|
305
|
+
*,
|
|
306
|
+
orbit_epoch: str,
|
|
307
|
+
altitude_km: float,
|
|
308
|
+
local_time_of_descending_node_hours: float,
|
|
309
|
+
) -> tuple[KeplerianElements, KeplerianElements]:
|
|
310
|
+
"""Generate SSO elements as ``(elements_tod, elements_inertial)``."""
|
|
311
|
+
result = raw.post(
|
|
312
|
+
"/OrbitWizard/SSO",
|
|
313
|
+
json={
|
|
314
|
+
"OrbitEpoch": orbit_epoch,
|
|
315
|
+
"Altitude": altitude_km,
|
|
316
|
+
"LocalTimeOfDescendingNode": local_time_of_descending_node_hours,
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
return _wizard_pair_from_wire(result)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _walker(
|
|
323
|
+
*,
|
|
324
|
+
seed_orbit: KeplerianElements,
|
|
325
|
+
walker_type: str,
|
|
326
|
+
num_planes: int,
|
|
327
|
+
num_sats_per_plane: int,
|
|
328
|
+
inter_plane_phase_increment: int | None = None,
|
|
329
|
+
inter_plane_true_anomaly_increment_deg: float | None = None,
|
|
330
|
+
raan_increment_deg: float | None = None,
|
|
331
|
+
) -> tuple[tuple[KeplerianElements, ...], ...]:
|
|
332
|
+
payload: dict[str, Any] = {
|
|
333
|
+
"SeedKepler": _keplerian_to_wire_object(seed_orbit),
|
|
334
|
+
"WalkerType": walker_type,
|
|
335
|
+
"NumPlanes": num_planes,
|
|
336
|
+
"NumSatsPerPlane": num_sats_per_plane,
|
|
337
|
+
}
|
|
338
|
+
_include_if_supplied(
|
|
339
|
+
payload,
|
|
340
|
+
"InterPlanePhaseIncrement",
|
|
341
|
+
inter_plane_phase_increment,
|
|
342
|
+
)
|
|
343
|
+
_include_if_supplied(
|
|
344
|
+
payload,
|
|
345
|
+
"InterPlaneTrueAnomalyIncrement",
|
|
346
|
+
inter_plane_true_anomaly_increment_deg,
|
|
347
|
+
)
|
|
348
|
+
_include_if_supplied(payload, "RAANIncrement", raan_increment_deg)
|
|
349
|
+
|
|
350
|
+
result = raw.post("/OrbitWizard/Walker", json=payload)
|
|
351
|
+
return _walker_from_wire(result)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def walker_delta(
|
|
355
|
+
*,
|
|
356
|
+
seed_orbit: KeplerianElements,
|
|
357
|
+
num_planes: int,
|
|
358
|
+
num_sats_per_plane: int,
|
|
359
|
+
inter_plane_phase_increment: int | None = None,
|
|
360
|
+
) -> tuple[tuple[KeplerianElements, ...], ...]:
|
|
361
|
+
"""Generate a Walker Delta constellation as nested ``(plane, satellite)`` tuples."""
|
|
362
|
+
return _walker(
|
|
363
|
+
seed_orbit=seed_orbit,
|
|
364
|
+
walker_type="Delta",
|
|
365
|
+
num_planes=num_planes,
|
|
366
|
+
num_sats_per_plane=num_sats_per_plane,
|
|
367
|
+
inter_plane_phase_increment=inter_plane_phase_increment,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def walker_star(
|
|
372
|
+
*,
|
|
373
|
+
seed_orbit: KeplerianElements,
|
|
374
|
+
num_planes: int,
|
|
375
|
+
num_sats_per_plane: int,
|
|
376
|
+
inter_plane_phase_increment: int | None = None,
|
|
377
|
+
) -> tuple[tuple[KeplerianElements, ...], ...]:
|
|
378
|
+
"""Generate a Walker Star constellation as nested ``(plane, satellite)`` tuples."""
|
|
379
|
+
return _walker(
|
|
380
|
+
seed_orbit=seed_orbit,
|
|
381
|
+
walker_type="Star",
|
|
382
|
+
num_planes=num_planes,
|
|
383
|
+
num_sats_per_plane=num_sats_per_plane,
|
|
384
|
+
inter_plane_phase_increment=inter_plane_phase_increment,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def walker_custom(
|
|
389
|
+
*,
|
|
390
|
+
seed_orbit: KeplerianElements,
|
|
391
|
+
num_planes: int,
|
|
392
|
+
num_sats_per_plane: int,
|
|
393
|
+
inter_plane_true_anomaly_increment_deg: float | None = None,
|
|
394
|
+
raan_increment_deg: float | None = None,
|
|
395
|
+
) -> tuple[tuple[KeplerianElements, ...], ...]:
|
|
396
|
+
"""Generate a custom Walker constellation as nested ``(plane, satellite)`` tuples."""
|
|
397
|
+
return _walker(
|
|
398
|
+
seed_orbit=seed_orbit,
|
|
399
|
+
walker_type="Custom",
|
|
400
|
+
num_planes=num_planes,
|
|
401
|
+
num_sats_per_plane=num_sats_per_plane,
|
|
402
|
+
inter_plane_true_anomaly_increment_deg=inter_plane_true_anomaly_increment_deg,
|
|
403
|
+
raan_increment_deg=raan_increment_deg,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def geo_ym_lambert_delta_v(
|
|
408
|
+
*,
|
|
409
|
+
platform_orbit: KeplerianElements,
|
|
410
|
+
target_orbit: KeplerianElements,
|
|
411
|
+
time_of_flight_s: float,
|
|
412
|
+
platform_gravitational_parameter_m3_s2: float | None = None,
|
|
413
|
+
) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
|
|
414
|
+
"""Return Lambert delta-v as ``(departure_delta_v_m_s, arrival_delta_v_m_s)``."""
|
|
415
|
+
payload = {
|
|
416
|
+
"keplerPt": _keplerian_to_wire_object(
|
|
417
|
+
platform_orbit,
|
|
418
|
+
gravitational_parameter_m3_s2=platform_gravitational_parameter_m3_s2,
|
|
419
|
+
),
|
|
420
|
+
"keplerMb": _keplerian_to_wire_object(target_orbit),
|
|
421
|
+
"tof": time_of_flight_s,
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
result = raw.post("/OrbitConvert/CalGEOYMLambertDv", json=payload)
|
|
425
|
+
return (
|
|
426
|
+
(result[0], result[1], result[2]),
|
|
427
|
+
(result[3], result[4], result[5]),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def lambert_delta_v(
|
|
432
|
+
*,
|
|
433
|
+
departure_state: CartesianState,
|
|
434
|
+
arrival_state: CartesianState,
|
|
435
|
+
time_of_flight_s: float,
|
|
436
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
437
|
+
) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
|
|
438
|
+
"""Return single-revolution Lambert delta-v between two Cartesian states."""
|
|
439
|
+
if not isinstance(departure_state, CartesianState):
|
|
440
|
+
raise TypeError("departure_state must be a CartesianState instance")
|
|
441
|
+
if not isinstance(arrival_state, CartesianState):
|
|
442
|
+
raise TypeError("arrival_state must be a CartesianState instance")
|
|
443
|
+
|
|
444
|
+
payload: dict[str, Any] = {
|
|
445
|
+
"RV1": departure_state.to_wire(),
|
|
446
|
+
"RV2": arrival_state.to_wire(),
|
|
447
|
+
"TOF": [time_of_flight_s],
|
|
448
|
+
}
|
|
449
|
+
_include_if_supplied(payload, "Gm", gravitational_parameter_m3_s2)
|
|
450
|
+
|
|
451
|
+
result = raw.post("/orbit/lambert", json=payload)
|
|
452
|
+
return (
|
|
453
|
+
(result["DV1"][0], result["DV1"][1], result["DV1"][2]),
|
|
454
|
+
(result["DV2"][0], result["DV2"][1], result["DV2"][2]),
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def convert_czml_position(
|
|
459
|
+
position: components.CzmlPosition,
|
|
460
|
+
*,
|
|
461
|
+
to_central_body: str,
|
|
462
|
+
target_reference_frame: str,
|
|
463
|
+
) -> tuple[float, components.CzmlPosition]:
|
|
464
|
+
"""Transform a sampled CZML position to another central-body frame.
|
|
465
|
+
|
|
466
|
+
Returns ``(period_s, transformed_position)``.
|
|
467
|
+
"""
|
|
468
|
+
if not isinstance(position, components.CzmlPosition):
|
|
469
|
+
raise TypeError("position must be a CzmlPosition instance")
|
|
470
|
+
|
|
471
|
+
result = raw.post(
|
|
472
|
+
"/OrbitSystem/ConvertCzmlPosition",
|
|
473
|
+
json={
|
|
474
|
+
"Position": position.to_czml_wire(),
|
|
475
|
+
"TargetCbName": to_central_body,
|
|
476
|
+
"TargetFrame": target_reference_frame,
|
|
477
|
+
},
|
|
478
|
+
)
|
|
479
|
+
return result["Period"], components.CzmlPosition.from_czml_wire(result["Position"])
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def earth_moon_libration(
|
|
483
|
+
position: components.CzmlPosition,
|
|
484
|
+
) -> components.CzmlPositionSTM:
|
|
485
|
+
"""Transform a sampled CZML position to the Earth-Moon libration frame.
|
|
486
|
+
|
|
487
|
+
Wires to ``/OrbitSystem/EarthMoonLibration2``.
|
|
488
|
+
"""
|
|
489
|
+
if not isinstance(position, components.CzmlPosition):
|
|
490
|
+
raise TypeError("position must be a CzmlPosition instance")
|
|
491
|
+
|
|
492
|
+
result = raw.post(
|
|
493
|
+
"/OrbitSystem/EarthMoonLibration2",
|
|
494
|
+
json=position.to_czml_wire(),
|
|
495
|
+
)
|
|
496
|
+
return components.CzmlPositionSTM.from_czml_wire(result["position"])
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
import astrox.components as components # noqa: E402
|