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/propagator.py
ADDED
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
"""Orbit propagation functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, TypeAlias
|
|
8
|
+
|
|
9
|
+
from astrox._http import raw
|
|
10
|
+
from astrox.orbits import CartesianState, KeplerianElements
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"HpopAtmosphere",
|
|
14
|
+
"HpopConfig",
|
|
15
|
+
"HpopGravity",
|
|
16
|
+
"HpopIntegrator",
|
|
17
|
+
"HpopJacchiaRoberts",
|
|
18
|
+
"HpopGravityField",
|
|
19
|
+
"HpopRkf78",
|
|
20
|
+
"HpopSrp",
|
|
21
|
+
"HpopSrpSpherical",
|
|
22
|
+
"HpopThirdBody",
|
|
23
|
+
"HpopTwoBodyGravity",
|
|
24
|
+
"PropagatorPosition",
|
|
25
|
+
"ballistic",
|
|
26
|
+
"ballistic_apogee_altitude",
|
|
27
|
+
"ballistic_delta_v",
|
|
28
|
+
"ballistic_delta_v_min_ecc",
|
|
29
|
+
"ballistic_time_of_flight",
|
|
30
|
+
"hpop",
|
|
31
|
+
"hpop_config",
|
|
32
|
+
"hpop_gravity_field",
|
|
33
|
+
"hpop_jacchia_roberts",
|
|
34
|
+
"hpop_rkf78",
|
|
35
|
+
"hpop_srp_spherical",
|
|
36
|
+
"hpop_third_body",
|
|
37
|
+
"hpop_two_body_gravity",
|
|
38
|
+
"j2",
|
|
39
|
+
"multi_j2",
|
|
40
|
+
"multi_sgp4",
|
|
41
|
+
"multi_two_body",
|
|
42
|
+
"sgp4",
|
|
43
|
+
"simple_ascent",
|
|
44
|
+
"two_body",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True, kw_only=True)
|
|
49
|
+
class PropagatorPosition:
|
|
50
|
+
"""Nested propagated position output."""
|
|
51
|
+
|
|
52
|
+
central_body: str
|
|
53
|
+
epoch: str
|
|
54
|
+
reference_frame: str
|
|
55
|
+
interpolation_algorithm: str
|
|
56
|
+
interpolation_degree: int
|
|
57
|
+
cartesian_velocity: tuple[float, ...]
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_wire(cls, position_payload: dict[str, Any]) -> PropagatorPosition:
|
|
61
|
+
"""Build from the ASTROX nested Position payload."""
|
|
62
|
+
return cls(
|
|
63
|
+
central_body=position_payload["CentralBody"],
|
|
64
|
+
epoch=position_payload["epoch"],
|
|
65
|
+
reference_frame=position_payload["referenceFrame"],
|
|
66
|
+
interpolation_algorithm=position_payload["interpolationAlgorithm"],
|
|
67
|
+
interpolation_degree=position_payload["interpolationDegree"],
|
|
68
|
+
cartesian_velocity=tuple(position_payload["cartesianVelocity"]),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _success_path(result: dict[str, Any]) -> tuple[float, PropagatorPosition]:
|
|
73
|
+
return result["Period"], PropagatorPosition.from_wire(result["Position"])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _include_if_supplied(payload: dict[str, Any], wire_key: str, value: Any) -> None:
|
|
77
|
+
if value is not None:
|
|
78
|
+
payload[wire_key] = value
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _sequence_to_list(value: Sequence[str], *, parameter: str) -> list[str]:
|
|
82
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
83
|
+
raise TypeError(f"{parameter} must be a sequence of strings")
|
|
84
|
+
items = list(value)
|
|
85
|
+
if not all(isinstance(item, str) for item in items):
|
|
86
|
+
raise TypeError(f"{parameter} must be a sequence of strings")
|
|
87
|
+
return items
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _hpop_value_to_wire(
|
|
91
|
+
value: Any,
|
|
92
|
+
*,
|
|
93
|
+
expected_type: type | tuple[type, ...],
|
|
94
|
+
parameter: str,
|
|
95
|
+
) -> dict[str, Any]:
|
|
96
|
+
if not isinstance(value, expected_type):
|
|
97
|
+
raise TypeError(f"{parameter} must be an HPOP config value")
|
|
98
|
+
return value.to_wire()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True, kw_only=True)
|
|
102
|
+
class HpopRkf78:
|
|
103
|
+
"""HPOP RKF7(8) integrator configuration."""
|
|
104
|
+
|
|
105
|
+
name: str | None = None
|
|
106
|
+
description: str | None = None
|
|
107
|
+
user_comment: str | None = None
|
|
108
|
+
use_fixed_step: bool | None = None
|
|
109
|
+
initial_step_s: float | None = None
|
|
110
|
+
max_step_s: float | None = None
|
|
111
|
+
min_step_s: float | None = None
|
|
112
|
+
max_abs_error: float | None = None
|
|
113
|
+
max_rel_error: float | None = None
|
|
114
|
+
max_iterations: int | None = None
|
|
115
|
+
|
|
116
|
+
def to_wire(self) -> dict[str, Any]:
|
|
117
|
+
"""Lower to the ASTROX RKF7(8) integrator fragment."""
|
|
118
|
+
payload: dict[str, Any] = {"$type": "RKF7th8th"}
|
|
119
|
+
_include_if_supplied(payload, "Name", self.name)
|
|
120
|
+
_include_if_supplied(payload, "Description", self.description)
|
|
121
|
+
_include_if_supplied(payload, "UserComment", self.user_comment)
|
|
122
|
+
_include_if_supplied(payload, "UseFixedStep", self.use_fixed_step)
|
|
123
|
+
_include_if_supplied(payload, "InitialStep", self.initial_step_s)
|
|
124
|
+
_include_if_supplied(payload, "MaxStep", self.max_step_s)
|
|
125
|
+
_include_if_supplied(payload, "MinStep", self.min_step_s)
|
|
126
|
+
_include_if_supplied(payload, "MaxAbsErr", self.max_abs_error)
|
|
127
|
+
_include_if_supplied(payload, "MaxRelErr", self.max_rel_error)
|
|
128
|
+
_include_if_supplied(payload, "MaxIterations", self.max_iterations)
|
|
129
|
+
return payload
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True, kw_only=True)
|
|
133
|
+
class HpopTwoBodyGravity:
|
|
134
|
+
"""HPOP two-body gravity configuration."""
|
|
135
|
+
|
|
136
|
+
name: str | None = None
|
|
137
|
+
description: str | None = None
|
|
138
|
+
user_comment: str | None = None
|
|
139
|
+
|
|
140
|
+
def to_wire(self) -> dict[str, Any]:
|
|
141
|
+
"""Lower to the ASTROX two-body gravity fragment."""
|
|
142
|
+
payload: dict[str, Any] = {"$type": "TwoBody"}
|
|
143
|
+
_include_if_supplied(payload, "Name", self.name)
|
|
144
|
+
_include_if_supplied(payload, "Description", self.description)
|
|
145
|
+
_include_if_supplied(payload, "UserComment", self.user_comment)
|
|
146
|
+
return payload
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True, kw_only=True)
|
|
150
|
+
class HpopGravityField:
|
|
151
|
+
"""HPOP gravity-field configuration."""
|
|
152
|
+
|
|
153
|
+
gravity_file_name: str
|
|
154
|
+
degree: int
|
|
155
|
+
order: int
|
|
156
|
+
name: str | None = None
|
|
157
|
+
description: str | None = None
|
|
158
|
+
user_comment: str | None = None
|
|
159
|
+
use_secular_variations: bool | None = None
|
|
160
|
+
solid_tide_type: str | None = None
|
|
161
|
+
eop_file_path: str | None = None
|
|
162
|
+
|
|
163
|
+
def to_wire(self) -> dict[str, Any]:
|
|
164
|
+
"""Lower to the ASTROX gravity-field fragment."""
|
|
165
|
+
payload: dict[str, Any] = {
|
|
166
|
+
"$type": "GravityField",
|
|
167
|
+
"GravityFileName": self.gravity_file_name,
|
|
168
|
+
"Degree": self.degree,
|
|
169
|
+
"Order": self.order,
|
|
170
|
+
}
|
|
171
|
+
_include_if_supplied(payload, "Name", self.name)
|
|
172
|
+
_include_if_supplied(payload, "Description", self.description)
|
|
173
|
+
_include_if_supplied(payload, "UserComment", self.user_comment)
|
|
174
|
+
_include_if_supplied(payload, "UseSecularVariations", self.use_secular_variations)
|
|
175
|
+
_include_if_supplied(payload, "SolidTideType", self.solid_tide_type)
|
|
176
|
+
_include_if_supplied(payload, "EOPfilePath", self.eop_file_path)
|
|
177
|
+
return payload
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True, kw_only=True)
|
|
181
|
+
class HpopJacchiaRoberts:
|
|
182
|
+
"""HPOP Jacchia-Roberts atmosphere configuration."""
|
|
183
|
+
|
|
184
|
+
name: str | None = None
|
|
185
|
+
description: str | None = None
|
|
186
|
+
user_comment: str | None = None
|
|
187
|
+
drag_model_type: str | None = None
|
|
188
|
+
atmos_data_source: str | None = None
|
|
189
|
+
f10p7: float | None = None
|
|
190
|
+
f10p7_avg: float | None = None
|
|
191
|
+
kp: float | None = None
|
|
192
|
+
|
|
193
|
+
def to_wire(self) -> dict[str, Any]:
|
|
194
|
+
"""Lower to the ASTROX Jacchia-Roberts atmosphere fragment."""
|
|
195
|
+
payload: dict[str, Any] = {"$type": "JacchiaRoberts"}
|
|
196
|
+
_include_if_supplied(payload, "Name", self.name)
|
|
197
|
+
_include_if_supplied(payload, "Description", self.description)
|
|
198
|
+
_include_if_supplied(payload, "UserComment", self.user_comment)
|
|
199
|
+
_include_if_supplied(payload, "DragModelType", self.drag_model_type)
|
|
200
|
+
_include_if_supplied(payload, "AtmosDataSource", self.atmos_data_source)
|
|
201
|
+
_include_if_supplied(payload, "F10p7", self.f10p7)
|
|
202
|
+
_include_if_supplied(payload, "F10p7Avg", self.f10p7_avg)
|
|
203
|
+
_include_if_supplied(payload, "Kp", self.kp)
|
|
204
|
+
return payload
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass(frozen=True, kw_only=True)
|
|
208
|
+
class HpopSrpSpherical:
|
|
209
|
+
"""HPOP spherical solar-radiation-pressure configuration."""
|
|
210
|
+
|
|
211
|
+
name: str | None = None
|
|
212
|
+
description: str | None = None
|
|
213
|
+
user_comment: str | None = None
|
|
214
|
+
shadow_model: str | None = None
|
|
215
|
+
sun_position: str | None = None
|
|
216
|
+
eclipsing_bodies: tuple[str, ...] | None = None
|
|
217
|
+
|
|
218
|
+
def to_wire(self) -> dict[str, Any]:
|
|
219
|
+
"""Lower to the ASTROX spherical SRP fragment."""
|
|
220
|
+
payload: dict[str, Any] = {"$type": "SRPSpherical"}
|
|
221
|
+
_include_if_supplied(payload, "Name", self.name)
|
|
222
|
+
_include_if_supplied(payload, "Description", self.description)
|
|
223
|
+
_include_if_supplied(payload, "UserComment", self.user_comment)
|
|
224
|
+
_include_if_supplied(payload, "ShadowModel", self.shadow_model)
|
|
225
|
+
_include_if_supplied(payload, "SunPosition", self.sun_position)
|
|
226
|
+
if self.eclipsing_bodies is not None:
|
|
227
|
+
payload["EclipsingBodies"] = list(self.eclipsing_bodies)
|
|
228
|
+
return payload
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass(frozen=True, kw_only=True)
|
|
232
|
+
class HpopThirdBody:
|
|
233
|
+
"""HPOP third-body force configuration."""
|
|
234
|
+
|
|
235
|
+
third_body_name: str
|
|
236
|
+
name: str | None = None
|
|
237
|
+
description: str | None = None
|
|
238
|
+
user_comment: str | None = None
|
|
239
|
+
mode_type: str | None = None
|
|
240
|
+
ephem_source: str | None = None
|
|
241
|
+
grav_source: str | None = None
|
|
242
|
+
mu_m3_s2: float | None = None
|
|
243
|
+
|
|
244
|
+
def to_wire(self) -> dict[str, Any]:
|
|
245
|
+
"""Lower to the ASTROX third-body force fragment."""
|
|
246
|
+
payload: dict[str, Any] = {"ThirdBodyName": self.third_body_name}
|
|
247
|
+
_include_if_supplied(payload, "Name", self.name)
|
|
248
|
+
_include_if_supplied(payload, "Description", self.description)
|
|
249
|
+
_include_if_supplied(payload, "UserComment", self.user_comment)
|
|
250
|
+
_include_if_supplied(payload, "ModeType", self.mode_type)
|
|
251
|
+
_include_if_supplied(payload, "EphemSource", self.ephem_source)
|
|
252
|
+
_include_if_supplied(payload, "GravSource", self.grav_source)
|
|
253
|
+
_include_if_supplied(payload, "Mu", self.mu_m3_s2)
|
|
254
|
+
return payload
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
HpopIntegrator: TypeAlias = HpopRkf78
|
|
258
|
+
HpopGravity: TypeAlias = HpopTwoBodyGravity | HpopGravityField
|
|
259
|
+
HpopAtmosphere: TypeAlias = HpopJacchiaRoberts
|
|
260
|
+
HpopSrp: TypeAlias = HpopSrpSpherical
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass(frozen=True, kw_only=True)
|
|
264
|
+
class HpopConfig:
|
|
265
|
+
"""HPOP propagator configuration."""
|
|
266
|
+
|
|
267
|
+
name: str | None = None
|
|
268
|
+
description: str | None = None
|
|
269
|
+
user_comment: str | None = None
|
|
270
|
+
central_body: str | None = None
|
|
271
|
+
integrator: HpopIntegrator | None = None
|
|
272
|
+
gravity: HpopGravity | None = None
|
|
273
|
+
atmosphere: HpopAtmosphere | None = None
|
|
274
|
+
srp: HpopSrp | None = None
|
|
275
|
+
third_bodies: tuple[HpopThirdBody, ...] | None = None
|
|
276
|
+
|
|
277
|
+
def to_wire(self) -> dict[str, Any]:
|
|
278
|
+
"""Lower to the ASTROX HPOP propagator fragment."""
|
|
279
|
+
payload: dict[str, Any] = {}
|
|
280
|
+
_include_if_supplied(payload, "Name", self.name)
|
|
281
|
+
_include_if_supplied(payload, "Description", self.description)
|
|
282
|
+
_include_if_supplied(payload, "UserComment", self.user_comment)
|
|
283
|
+
_include_if_supplied(payload, "CentralBodyName", self.central_body)
|
|
284
|
+
if self.integrator is not None:
|
|
285
|
+
payload["NumericalIntegrator"] = self.integrator.to_wire()
|
|
286
|
+
if self.gravity is not None:
|
|
287
|
+
payload["GravityModel"] = self.gravity.to_wire()
|
|
288
|
+
if self.atmosphere is not None:
|
|
289
|
+
payload["AtmosphericModel"] = self.atmosphere.to_wire()
|
|
290
|
+
if self.srp is not None:
|
|
291
|
+
payload["SRPModel"] = self.srp.to_wire()
|
|
292
|
+
if self.third_bodies is not None:
|
|
293
|
+
payload["ThirdBodyForce"] = [
|
|
294
|
+
third_body.to_wire()
|
|
295
|
+
for third_body in self.third_bodies
|
|
296
|
+
]
|
|
297
|
+
return payload
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _keplerian_from_elements_object(payload: dict[str, Any]) -> KeplerianElements:
|
|
301
|
+
return KeplerianElements(
|
|
302
|
+
semi_major_axis_m=payload["SemimajorAxis"],
|
|
303
|
+
eccentricity=payload["Eccentricity"],
|
|
304
|
+
inclination_deg=payload["Inclination"],
|
|
305
|
+
argument_of_periapsis_deg=payload["ArgumentOfPeriapsis"],
|
|
306
|
+
raan_deg=payload["RightAscensionOfAscendingNode"],
|
|
307
|
+
true_anomaly_deg=payload["TrueAnomaly"],
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _batch_success_path(result: dict[str, Any]) -> tuple[KeplerianElements, ...]:
|
|
312
|
+
return tuple(
|
|
313
|
+
_keplerian_from_elements_object(item)
|
|
314
|
+
for item in result["AllElementsAtEpoch"]
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _state_item_to_wire(
|
|
319
|
+
item: Sequence[object],
|
|
320
|
+
*,
|
|
321
|
+
gravitational_parameter_m3_s2: float | None,
|
|
322
|
+
) -> dict[str, Any]:
|
|
323
|
+
if not isinstance(item, (list, tuple)) or len(item) != 2:
|
|
324
|
+
raise TypeError("states items must be two-item sequences of orbit epoch and KeplerianElements")
|
|
325
|
+
orbit_epoch, orbit = item
|
|
326
|
+
if not isinstance(orbit_epoch, str):
|
|
327
|
+
raise TypeError("states item orbit epoch must be a string")
|
|
328
|
+
if not isinstance(orbit, KeplerianElements):
|
|
329
|
+
raise TypeError("states item orbit must be a KeplerianElements instance")
|
|
330
|
+
|
|
331
|
+
payload: dict[str, Any] = {
|
|
332
|
+
"OrbitEpoch": orbit_epoch,
|
|
333
|
+
"SemimajorAxis": orbit.semi_major_axis_m,
|
|
334
|
+
"Eccentricity": orbit.eccentricity,
|
|
335
|
+
"Inclination": orbit.inclination_deg,
|
|
336
|
+
"ArgumentOfPeriapsis": orbit.argument_of_periapsis_deg,
|
|
337
|
+
"RightAscensionOfAscendingNode": orbit.raan_deg,
|
|
338
|
+
"TrueAnomaly": orbit.true_anomaly_deg,
|
|
339
|
+
}
|
|
340
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
341
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
342
|
+
return payload
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _states_to_wire(
|
|
346
|
+
states: Sequence[Sequence[object]],
|
|
347
|
+
*,
|
|
348
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
349
|
+
) -> list[dict[str, Any]]:
|
|
350
|
+
if not isinstance(states, Sequence) or isinstance(states, (str, bytes)):
|
|
351
|
+
raise TypeError("states must be a sequence of two-item state sequences")
|
|
352
|
+
return [
|
|
353
|
+
_state_item_to_wire(
|
|
354
|
+
item,
|
|
355
|
+
gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
|
|
356
|
+
)
|
|
357
|
+
for item in states
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _tle_set_to_wire(item: Sequence[object]) -> str:
|
|
362
|
+
if not isinstance(item, (list, tuple)) or len(item) != 2:
|
|
363
|
+
raise TypeError("tle_sets items must be two-item sequences of TLE strings")
|
|
364
|
+
line1, line2 = item
|
|
365
|
+
if not isinstance(line1, str) or not isinstance(line2, str):
|
|
366
|
+
raise TypeError("tle_sets items must contain TLE strings")
|
|
367
|
+
return f"{line1}\n{line2}"
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _tle_sets_to_wire(tle_sets: Sequence[Sequence[object]]) -> list[str]:
|
|
371
|
+
if not isinstance(tle_sets, Sequence) or isinstance(tle_sets, (str, bytes)):
|
|
372
|
+
raise TypeError("tle_sets must be a sequence of two-item TLE string sequences")
|
|
373
|
+
return [_tle_set_to_wire(item) for item in tle_sets]
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def hpop_rkf78(
|
|
377
|
+
*,
|
|
378
|
+
name: str | None = None,
|
|
379
|
+
description: str | None = None,
|
|
380
|
+
user_comment: str | None = None,
|
|
381
|
+
use_fixed_step: bool | None = None,
|
|
382
|
+
initial_step_s: float | None = None,
|
|
383
|
+
max_step_s: float | None = None,
|
|
384
|
+
min_step_s: float | None = None,
|
|
385
|
+
max_abs_error: float | None = None,
|
|
386
|
+
max_rel_error: float | None = None,
|
|
387
|
+
max_iterations: int | None = None,
|
|
388
|
+
) -> HpopIntegrator:
|
|
389
|
+
"""Create an HPOP RKF7(8) integrator fragment."""
|
|
390
|
+
return HpopRkf78(
|
|
391
|
+
name=name,
|
|
392
|
+
description=description,
|
|
393
|
+
user_comment=user_comment,
|
|
394
|
+
use_fixed_step=use_fixed_step,
|
|
395
|
+
initial_step_s=initial_step_s,
|
|
396
|
+
max_step_s=max_step_s,
|
|
397
|
+
min_step_s=min_step_s,
|
|
398
|
+
max_abs_error=max_abs_error,
|
|
399
|
+
max_rel_error=max_rel_error,
|
|
400
|
+
max_iterations=max_iterations,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def hpop_two_body_gravity(
|
|
405
|
+
*,
|
|
406
|
+
name: str | None = None,
|
|
407
|
+
description: str | None = None,
|
|
408
|
+
user_comment: str | None = None,
|
|
409
|
+
) -> HpopGravity:
|
|
410
|
+
"""Create an HPOP two-body gravity fragment.
|
|
411
|
+
|
|
412
|
+
ASTROX owns the two-body gravity constants for this branch; use
|
|
413
|
+
``hpop(...)`` top-level scalar arguments for spacecraft and central-body
|
|
414
|
+
propagation knobs exposed by the curated SDK.
|
|
415
|
+
"""
|
|
416
|
+
return HpopTwoBodyGravity(
|
|
417
|
+
name=name,
|
|
418
|
+
description=description,
|
|
419
|
+
user_comment=user_comment,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def hpop_gravity_field(
|
|
424
|
+
*,
|
|
425
|
+
gravity_file_name: str,
|
|
426
|
+
degree: int,
|
|
427
|
+
order: int,
|
|
428
|
+
name: str | None = None,
|
|
429
|
+
description: str | None = None,
|
|
430
|
+
user_comment: str | None = None,
|
|
431
|
+
use_secular_variations: bool | None = None,
|
|
432
|
+
solid_tide_type: str | None = None,
|
|
433
|
+
eop_file_path: str | None = None,
|
|
434
|
+
) -> HpopGravity:
|
|
435
|
+
"""Create an HPOP gravity-field fragment."""
|
|
436
|
+
return HpopGravityField(
|
|
437
|
+
gravity_file_name=gravity_file_name,
|
|
438
|
+
degree=degree,
|
|
439
|
+
order=order,
|
|
440
|
+
name=name,
|
|
441
|
+
description=description,
|
|
442
|
+
user_comment=user_comment,
|
|
443
|
+
use_secular_variations=use_secular_variations,
|
|
444
|
+
solid_tide_type=solid_tide_type,
|
|
445
|
+
eop_file_path=eop_file_path,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def hpop_jacchia_roberts(
|
|
450
|
+
*,
|
|
451
|
+
name: str | None = None,
|
|
452
|
+
description: str | None = None,
|
|
453
|
+
user_comment: str | None = None,
|
|
454
|
+
drag_model_type: str | None = None,
|
|
455
|
+
atmos_data_source: str | None = None,
|
|
456
|
+
f10p7: float | None = None,
|
|
457
|
+
f10p7_avg: float | None = None,
|
|
458
|
+
kp: float | None = None,
|
|
459
|
+
) -> HpopAtmosphere:
|
|
460
|
+
"""Create an HPOP Jacchia-Roberts atmosphere fragment."""
|
|
461
|
+
return HpopJacchiaRoberts(
|
|
462
|
+
name=name,
|
|
463
|
+
description=description,
|
|
464
|
+
user_comment=user_comment,
|
|
465
|
+
drag_model_type=drag_model_type,
|
|
466
|
+
atmos_data_source=atmos_data_source,
|
|
467
|
+
f10p7=f10p7,
|
|
468
|
+
f10p7_avg=f10p7_avg,
|
|
469
|
+
kp=kp,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def hpop_srp_spherical(
|
|
474
|
+
*,
|
|
475
|
+
name: str | None = None,
|
|
476
|
+
description: str | None = None,
|
|
477
|
+
user_comment: str | None = None,
|
|
478
|
+
shadow_model: str | None = None,
|
|
479
|
+
sun_position: str | None = None,
|
|
480
|
+
eclipsing_bodies: Sequence[str] | None = None,
|
|
481
|
+
) -> HpopSrp:
|
|
482
|
+
"""Create an HPOP spherical solar-radiation-pressure fragment."""
|
|
483
|
+
bodies = None
|
|
484
|
+
if eclipsing_bodies is not None:
|
|
485
|
+
bodies = tuple(
|
|
486
|
+
_sequence_to_list(
|
|
487
|
+
eclipsing_bodies,
|
|
488
|
+
parameter="eclipsing_bodies",
|
|
489
|
+
)
|
|
490
|
+
)
|
|
491
|
+
return HpopSrpSpherical(
|
|
492
|
+
name=name,
|
|
493
|
+
description=description,
|
|
494
|
+
user_comment=user_comment,
|
|
495
|
+
shadow_model=shadow_model,
|
|
496
|
+
sun_position=sun_position,
|
|
497
|
+
eclipsing_bodies=bodies,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def hpop_third_body(
|
|
502
|
+
third_body_name: str,
|
|
503
|
+
*,
|
|
504
|
+
name: str | None = None,
|
|
505
|
+
description: str | None = None,
|
|
506
|
+
user_comment: str | None = None,
|
|
507
|
+
mode_type: str | None = None,
|
|
508
|
+
ephem_source: str | None = None,
|
|
509
|
+
grav_source: str | None = None,
|
|
510
|
+
mu_m3_s2: float | None = None,
|
|
511
|
+
) -> HpopThirdBody:
|
|
512
|
+
"""Create an HPOP third-body force fragment."""
|
|
513
|
+
return HpopThirdBody(
|
|
514
|
+
third_body_name=third_body_name,
|
|
515
|
+
name=name,
|
|
516
|
+
description=description,
|
|
517
|
+
user_comment=user_comment,
|
|
518
|
+
mode_type=mode_type,
|
|
519
|
+
ephem_source=ephem_source,
|
|
520
|
+
grav_source=grav_source,
|
|
521
|
+
mu_m3_s2=mu_m3_s2,
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def hpop_config(
|
|
526
|
+
*,
|
|
527
|
+
name: str | None = None,
|
|
528
|
+
description: str | None = None,
|
|
529
|
+
user_comment: str | None = None,
|
|
530
|
+
central_body: str | None = None,
|
|
531
|
+
integrator: HpopIntegrator | None = None,
|
|
532
|
+
gravity: HpopGravity | None = None,
|
|
533
|
+
atmosphere: HpopAtmosphere | None = None,
|
|
534
|
+
srp: HpopSrp | None = None,
|
|
535
|
+
third_bodies: Sequence[HpopThirdBody] | None = None,
|
|
536
|
+
) -> HpopConfig:
|
|
537
|
+
"""Create an HPOP propagator configuration fragment."""
|
|
538
|
+
if integrator is not None:
|
|
539
|
+
_hpop_value_to_wire(
|
|
540
|
+
integrator,
|
|
541
|
+
expected_type=HpopRkf78,
|
|
542
|
+
parameter="integrator",
|
|
543
|
+
)
|
|
544
|
+
if gravity is not None:
|
|
545
|
+
_hpop_value_to_wire(
|
|
546
|
+
gravity,
|
|
547
|
+
expected_type=(HpopTwoBodyGravity, HpopGravityField),
|
|
548
|
+
parameter="gravity",
|
|
549
|
+
)
|
|
550
|
+
if atmosphere is not None:
|
|
551
|
+
_hpop_value_to_wire(
|
|
552
|
+
atmosphere,
|
|
553
|
+
expected_type=HpopJacchiaRoberts,
|
|
554
|
+
parameter="atmosphere",
|
|
555
|
+
)
|
|
556
|
+
if srp is not None:
|
|
557
|
+
_hpop_value_to_wire(
|
|
558
|
+
srp,
|
|
559
|
+
expected_type=HpopSrpSpherical,
|
|
560
|
+
parameter="srp",
|
|
561
|
+
)
|
|
562
|
+
body_values = None
|
|
563
|
+
if third_bodies is not None:
|
|
564
|
+
if isinstance(third_bodies, (str, bytes)) or not isinstance(
|
|
565
|
+
third_bodies,
|
|
566
|
+
Sequence,
|
|
567
|
+
):
|
|
568
|
+
raise TypeError("third_bodies must be a sequence of HPOP config values")
|
|
569
|
+
body_values = tuple(third_bodies)
|
|
570
|
+
if not all(isinstance(body, HpopThirdBody) for body in body_values):
|
|
571
|
+
raise TypeError("third_bodies must be a sequence of HPOP config values")
|
|
572
|
+
return HpopConfig(
|
|
573
|
+
name=name,
|
|
574
|
+
description=description,
|
|
575
|
+
user_comment=user_comment,
|
|
576
|
+
central_body=central_body,
|
|
577
|
+
integrator=integrator,
|
|
578
|
+
gravity=gravity,
|
|
579
|
+
atmosphere=atmosphere,
|
|
580
|
+
srp=srp,
|
|
581
|
+
third_bodies=body_values,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def hpop(
|
|
586
|
+
*,
|
|
587
|
+
start: str,
|
|
588
|
+
stop: str,
|
|
589
|
+
orbit_epoch: str,
|
|
590
|
+
orbit: KeplerianElements | None = None,
|
|
591
|
+
state: CartesianState | None = None,
|
|
592
|
+
config: HpopConfig | Mapping[str, Any] | None = None,
|
|
593
|
+
coord_system: str | None = None,
|
|
594
|
+
coord_epoch: str | None = None,
|
|
595
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
596
|
+
coefficient_of_drag: float | None = None,
|
|
597
|
+
area_mass_ratio_drag_m2_kg: float | None = None,
|
|
598
|
+
coefficient_of_srp: float | None = None,
|
|
599
|
+
area_mass_ratio_srp_m2_kg: float | None = None,
|
|
600
|
+
) -> tuple[float, PropagatorPosition]:
|
|
601
|
+
"""Propagate Classical or Cartesian state with ASTROX HPOP."""
|
|
602
|
+
if (orbit is None) == (state is None):
|
|
603
|
+
raise ValueError("exactly one of orbit or state must be provided")
|
|
604
|
+
if orbit is not None:
|
|
605
|
+
if not isinstance(orbit, KeplerianElements):
|
|
606
|
+
raise TypeError("orbit must be a KeplerianElements instance")
|
|
607
|
+
coord_type = "Classical"
|
|
608
|
+
orbital_elements = orbit.to_wire()
|
|
609
|
+
else:
|
|
610
|
+
if not isinstance(state, CartesianState):
|
|
611
|
+
raise TypeError("state must be a CartesianState instance")
|
|
612
|
+
coord_type = "Cartesian"
|
|
613
|
+
orbital_elements = state.to_wire()
|
|
614
|
+
|
|
615
|
+
payload: dict[str, Any] = {
|
|
616
|
+
"Start": start,
|
|
617
|
+
"Stop": stop,
|
|
618
|
+
"OrbitEpoch": orbit_epoch,
|
|
619
|
+
"CoordType": coord_type,
|
|
620
|
+
"OrbitalElements": orbital_elements,
|
|
621
|
+
}
|
|
622
|
+
_include_if_supplied(payload, "CoordSystem", coord_system)
|
|
623
|
+
_include_if_supplied(payload, "CoordEpoch", coord_epoch)
|
|
624
|
+
_include_if_supplied(payload, "GravitationalParameter", gravitational_parameter_m3_s2)
|
|
625
|
+
_include_if_supplied(payload, "CoefficientOfDrag", coefficient_of_drag)
|
|
626
|
+
_include_if_supplied(payload, "AreaMassRatioDrag", area_mass_ratio_drag_m2_kg)
|
|
627
|
+
_include_if_supplied(payload, "CoefficientOfSRP", coefficient_of_srp)
|
|
628
|
+
_include_if_supplied(payload, "AreaMassRatioSRP", area_mass_ratio_srp_m2_kg)
|
|
629
|
+
if config is not None:
|
|
630
|
+
if isinstance(config, HpopConfig):
|
|
631
|
+
payload["HpopPropagator"] = config.to_wire()
|
|
632
|
+
elif isinstance(config, Mapping):
|
|
633
|
+
payload["HpopPropagator"] = dict(config)
|
|
634
|
+
else:
|
|
635
|
+
raise TypeError("config must be an HpopConfig value or mapping fragment")
|
|
636
|
+
|
|
637
|
+
result = raw.post("/Propagator/HPOP", json=payload)
|
|
638
|
+
return _success_path(result)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def two_body(
|
|
642
|
+
*,
|
|
643
|
+
start: str,
|
|
644
|
+
stop: str,
|
|
645
|
+
orbit_epoch: str,
|
|
646
|
+
orbit: KeplerianElements,
|
|
647
|
+
step_s: float | None = None,
|
|
648
|
+
central_body: str | None = None,
|
|
649
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
650
|
+
coord_system: str | None = None,
|
|
651
|
+
) -> tuple[float, PropagatorPosition]:
|
|
652
|
+
"""Propagate Classical Keplerian elements using two-body dynamics."""
|
|
653
|
+
if not isinstance(orbit, KeplerianElements):
|
|
654
|
+
raise TypeError("orbit must be a KeplerianElements instance")
|
|
655
|
+
|
|
656
|
+
payload: dict[str, Any] = {
|
|
657
|
+
"Start": start,
|
|
658
|
+
"Stop": stop,
|
|
659
|
+
"OrbitEpoch": orbit_epoch,
|
|
660
|
+
"CoordType": "Classical",
|
|
661
|
+
"OrbitalElements": orbit.to_wire(),
|
|
662
|
+
}
|
|
663
|
+
if step_s is not None:
|
|
664
|
+
payload["Step"] = step_s
|
|
665
|
+
if central_body is not None:
|
|
666
|
+
payload["CentralBody"] = central_body
|
|
667
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
668
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
669
|
+
if coord_system is not None:
|
|
670
|
+
payload["CoordSystem"] = coord_system
|
|
671
|
+
|
|
672
|
+
result = raw.post("/Propagator/TwoBody", json=payload)
|
|
673
|
+
return _success_path(result)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def j2(
|
|
677
|
+
*,
|
|
678
|
+
start: str,
|
|
679
|
+
stop: str,
|
|
680
|
+
orbit_epoch: str,
|
|
681
|
+
orbit: KeplerianElements,
|
|
682
|
+
step_s: float | None = None,
|
|
683
|
+
central_body: str | None = None,
|
|
684
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
685
|
+
coord_system: str | None = None,
|
|
686
|
+
j2_normalized_value: float | None = None,
|
|
687
|
+
ref_distance_m: float | None = None,
|
|
688
|
+
) -> tuple[float, PropagatorPosition]:
|
|
689
|
+
"""Propagate Classical Keplerian elements using the J2 model."""
|
|
690
|
+
if not isinstance(orbit, KeplerianElements):
|
|
691
|
+
raise TypeError("orbit must be a KeplerianElements instance")
|
|
692
|
+
|
|
693
|
+
payload: dict[str, Any] = {
|
|
694
|
+
"Start": start,
|
|
695
|
+
"Stop": stop,
|
|
696
|
+
"OrbitEpoch": orbit_epoch,
|
|
697
|
+
"CoordType": "Classical",
|
|
698
|
+
"OrbitalElements": orbit.to_wire(),
|
|
699
|
+
}
|
|
700
|
+
if step_s is not None:
|
|
701
|
+
payload["Step"] = step_s
|
|
702
|
+
if central_body is not None:
|
|
703
|
+
payload["CentralBody"] = central_body
|
|
704
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
705
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
706
|
+
if coord_system is not None:
|
|
707
|
+
payload["CoordSystem"] = coord_system
|
|
708
|
+
if j2_normalized_value is not None:
|
|
709
|
+
payload["J2NormalizedValue"] = j2_normalized_value
|
|
710
|
+
if ref_distance_m is not None:
|
|
711
|
+
payload["RefDistance"] = ref_distance_m
|
|
712
|
+
|
|
713
|
+
result = raw.post("/Propagator/J2", json=payload)
|
|
714
|
+
return _success_path(result)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def multi_two_body(
|
|
718
|
+
*,
|
|
719
|
+
epoch: str,
|
|
720
|
+
states: Sequence[tuple[str, KeplerianElements]],
|
|
721
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
722
|
+
) -> tuple[KeplerianElements, ...]:
|
|
723
|
+
"""Propagate multiple Classical states to one target epoch using two-body dynamics.
|
|
724
|
+
|
|
725
|
+
ASTROX raw batch responses include ``GravitationalParameter`` on each returned
|
|
726
|
+
element. The curated return intentionally omits it because live behavior shows
|
|
727
|
+
that field is not a reliable echo of the propagation parameter used for the
|
|
728
|
+
result; use ``astrox.raw`` for the full raw envelope.
|
|
729
|
+
"""
|
|
730
|
+
payload = {
|
|
731
|
+
"Epoch": epoch,
|
|
732
|
+
"AllSateElements": _states_to_wire(
|
|
733
|
+
states,
|
|
734
|
+
gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
|
|
735
|
+
),
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
result = raw.post("/Propagator/MultiTwoBody", json=payload)
|
|
739
|
+
return _batch_success_path(result)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def multi_j2(
|
|
743
|
+
*,
|
|
744
|
+
epoch: str,
|
|
745
|
+
states: Sequence[tuple[str, KeplerianElements]],
|
|
746
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
747
|
+
) -> tuple[KeplerianElements, ...]:
|
|
748
|
+
"""Propagate multiple Classical states to one target epoch using ASTROX J2.
|
|
749
|
+
|
|
750
|
+
The batch ASTROX route owns its J2 constants; the curated SDK does not expose
|
|
751
|
+
J2 constants for this function because live behavior does not show those
|
|
752
|
+
inputs affecting the endpoint. ASTROX raw batch responses include
|
|
753
|
+
``GravitationalParameter`` on each returned element. The curated return
|
|
754
|
+
intentionally omits it because live behavior shows that field is not a
|
|
755
|
+
reliable echo of the propagation parameter used for the result; use
|
|
756
|
+
``astrox.raw`` for the full raw envelope.
|
|
757
|
+
"""
|
|
758
|
+
payload = {
|
|
759
|
+
"Epoch": epoch,
|
|
760
|
+
"AllSateElements": _states_to_wire(
|
|
761
|
+
states,
|
|
762
|
+
gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
|
|
763
|
+
),
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
result = raw.post("/Propagator/MultiJ2", json=payload)
|
|
767
|
+
return _batch_success_path(result)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def multi_sgp4(
|
|
771
|
+
*,
|
|
772
|
+
epoch: str,
|
|
773
|
+
tle_sets: Sequence[tuple[str, str]],
|
|
774
|
+
) -> tuple[KeplerianElements, ...]:
|
|
775
|
+
"""Propagate multiple TLEs to one target epoch using SGP4.
|
|
776
|
+
|
|
777
|
+
Each public ``tle_sets`` item is a two-line TLE sequence. The SDK lowers it
|
|
778
|
+
to the ASTROX batch route's newline-joined string format. ASTROX raw batch
|
|
779
|
+
responses include ``GravitationalParameter`` on each returned element. The
|
|
780
|
+
curated return intentionally omits it because live behavior shows that field
|
|
781
|
+
is not a reliable echo of the propagation parameter used for the result; use
|
|
782
|
+
``astrox.raw`` for the full raw envelope.
|
|
783
|
+
"""
|
|
784
|
+
payload = {
|
|
785
|
+
"Epoch": epoch,
|
|
786
|
+
"TLEs": _tle_sets_to_wire(tle_sets),
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
result = raw.post("/Propagator/MultiSgp4", json=payload)
|
|
790
|
+
return _batch_success_path(result)
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def sgp4(
|
|
794
|
+
*,
|
|
795
|
+
start: str,
|
|
796
|
+
stop: str,
|
|
797
|
+
tle_lines: tuple[str, str] | list[str],
|
|
798
|
+
step_s: float | None = None,
|
|
799
|
+
satellite_number: str | None = None,
|
|
800
|
+
) -> tuple[float, PropagatorPosition]:
|
|
801
|
+
"""Propagate a satellite from two-line element data using SGP4."""
|
|
802
|
+
if (
|
|
803
|
+
not isinstance(tle_lines, (list, tuple))
|
|
804
|
+
or len(tle_lines) != 2
|
|
805
|
+
or not all(isinstance(line, str) for line in tle_lines)
|
|
806
|
+
):
|
|
807
|
+
raise TypeError("tle_lines must be a two-item sequence of TLE strings")
|
|
808
|
+
|
|
809
|
+
payload: dict[str, Any] = {
|
|
810
|
+
"Start": start,
|
|
811
|
+
"Stop": stop,
|
|
812
|
+
"TLEs": list(tle_lines),
|
|
813
|
+
}
|
|
814
|
+
if step_s is not None:
|
|
815
|
+
payload["Step"] = step_s
|
|
816
|
+
if satellite_number is not None:
|
|
817
|
+
payload["SatelliteNumber"] = satellite_number
|
|
818
|
+
|
|
819
|
+
result = raw.post("/Propagator/sgp4", json=payload)
|
|
820
|
+
return _success_path(result)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def simple_ascent(
|
|
824
|
+
*,
|
|
825
|
+
start: str,
|
|
826
|
+
stop: str,
|
|
827
|
+
launch_latitude_deg: float,
|
|
828
|
+
launch_longitude_deg: float,
|
|
829
|
+
launch_altitude_m: float,
|
|
830
|
+
burnout_velocity_m_s: float,
|
|
831
|
+
burnout_latitude_deg: float,
|
|
832
|
+
burnout_longitude_deg: float,
|
|
833
|
+
burnout_altitude_m: float,
|
|
834
|
+
step_s: float | None = None,
|
|
835
|
+
central_body: str | None = None,
|
|
836
|
+
) -> tuple[float, PropagatorPosition]:
|
|
837
|
+
"""Propagate a simple ascent from launch point to burnout point."""
|
|
838
|
+
payload: dict[str, Any] = {
|
|
839
|
+
"Start": start,
|
|
840
|
+
"Stop": stop,
|
|
841
|
+
"LaunchLatitude": launch_latitude_deg,
|
|
842
|
+
"LaunchLongitude": launch_longitude_deg,
|
|
843
|
+
"LaunchAltitude": launch_altitude_m,
|
|
844
|
+
"BurnoutVelocity": burnout_velocity_m_s,
|
|
845
|
+
"BurnoutLatitude": burnout_latitude_deg,
|
|
846
|
+
"BurnoutLongitude": burnout_longitude_deg,
|
|
847
|
+
"BurnoutAltitude": burnout_altitude_m,
|
|
848
|
+
}
|
|
849
|
+
if step_s is not None:
|
|
850
|
+
payload["Step"] = step_s
|
|
851
|
+
if central_body is not None:
|
|
852
|
+
payload["CentralBody"] = central_body
|
|
853
|
+
|
|
854
|
+
result = raw.post("/Propagator/SimpleAscent", json=payload)
|
|
855
|
+
return _success_path(result)
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def ballistic(
|
|
859
|
+
*,
|
|
860
|
+
start: str,
|
|
861
|
+
impact_latitude_deg: float,
|
|
862
|
+
impact_longitude_deg: float,
|
|
863
|
+
stop: str | None = None,
|
|
864
|
+
step_s: float | None = None,
|
|
865
|
+
central_body: str | None = None,
|
|
866
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
867
|
+
launch_latitude_deg: float | None = None,
|
|
868
|
+
launch_longitude_deg: float | None = None,
|
|
869
|
+
launch_altitude_m: float | None = None,
|
|
870
|
+
impact_altitude_m: float | None = None,
|
|
871
|
+
) -> tuple[float, PropagatorPosition]:
|
|
872
|
+
"""Propagate the verified nominal ballistic trajectory shape."""
|
|
873
|
+
payload: dict[str, Any] = {
|
|
874
|
+
"Start": start,
|
|
875
|
+
"ImpactLatitude": impact_latitude_deg,
|
|
876
|
+
"ImpactLongitude": impact_longitude_deg,
|
|
877
|
+
}
|
|
878
|
+
if step_s is not None:
|
|
879
|
+
payload["Step"] = step_s
|
|
880
|
+
if central_body is not None:
|
|
881
|
+
payload["CentralBody"] = central_body
|
|
882
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
883
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
884
|
+
if launch_latitude_deg is not None:
|
|
885
|
+
payload["LaunchLatitude"] = launch_latitude_deg
|
|
886
|
+
if launch_longitude_deg is not None:
|
|
887
|
+
payload["LaunchLongitude"] = launch_longitude_deg
|
|
888
|
+
if launch_altitude_m is not None:
|
|
889
|
+
payload["LaunchAltitude"] = launch_altitude_m
|
|
890
|
+
if impact_altitude_m is not None:
|
|
891
|
+
payload["ImpactAltitude"] = impact_altitude_m
|
|
892
|
+
if stop is not None:
|
|
893
|
+
payload["Stop"] = stop
|
|
894
|
+
|
|
895
|
+
result = raw.post("/Propagator/Ballistic", json=payload)
|
|
896
|
+
return _success_path(result)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def ballistic_delta_v(
|
|
900
|
+
*,
|
|
901
|
+
start: str,
|
|
902
|
+
impact_latitude_deg: float,
|
|
903
|
+
impact_longitude_deg: float,
|
|
904
|
+
delta_v_m_s: float,
|
|
905
|
+
stop: str | None = None,
|
|
906
|
+
step_s: float | None = None,
|
|
907
|
+
central_body: str | None = None,
|
|
908
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
909
|
+
launch_latitude_deg: float | None = None,
|
|
910
|
+
launch_longitude_deg: float | None = None,
|
|
911
|
+
launch_altitude_m: float | None = None,
|
|
912
|
+
impact_altitude_m: float | None = None,
|
|
913
|
+
) -> tuple[float, PropagatorPosition]:
|
|
914
|
+
"""Propagate a ballistic trajectory using the DeltaV branch."""
|
|
915
|
+
payload: dict[str, Any] = {
|
|
916
|
+
"Start": start,
|
|
917
|
+
"ImpactLatitude": impact_latitude_deg,
|
|
918
|
+
"ImpactLongitude": impact_longitude_deg,
|
|
919
|
+
"BallisticType": "DeltaV",
|
|
920
|
+
"BallisticTypeValue": delta_v_m_s,
|
|
921
|
+
}
|
|
922
|
+
if step_s is not None:
|
|
923
|
+
payload["Step"] = step_s
|
|
924
|
+
if central_body is not None:
|
|
925
|
+
payload["CentralBody"] = central_body
|
|
926
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
927
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
928
|
+
if launch_latitude_deg is not None:
|
|
929
|
+
payload["LaunchLatitude"] = launch_latitude_deg
|
|
930
|
+
if launch_longitude_deg is not None:
|
|
931
|
+
payload["LaunchLongitude"] = launch_longitude_deg
|
|
932
|
+
if launch_altitude_m is not None:
|
|
933
|
+
payload["LaunchAltitude"] = launch_altitude_m
|
|
934
|
+
if impact_altitude_m is not None:
|
|
935
|
+
payload["ImpactAltitude"] = impact_altitude_m
|
|
936
|
+
if stop is not None:
|
|
937
|
+
payload["Stop"] = stop
|
|
938
|
+
|
|
939
|
+
result = raw.post("/Propagator/Ballistic", json=payload)
|
|
940
|
+
return _success_path(result)
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
def ballistic_delta_v_min_ecc(
|
|
944
|
+
*,
|
|
945
|
+
start: str,
|
|
946
|
+
impact_latitude_deg: float,
|
|
947
|
+
impact_longitude_deg: float,
|
|
948
|
+
delta_v_m_s: float,
|
|
949
|
+
stop: str | None = None,
|
|
950
|
+
step_s: float | None = None,
|
|
951
|
+
central_body: str | None = None,
|
|
952
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
953
|
+
launch_latitude_deg: float | None = None,
|
|
954
|
+
launch_longitude_deg: float | None = None,
|
|
955
|
+
launch_altitude_m: float | None = None,
|
|
956
|
+
impact_altitude_m: float | None = None,
|
|
957
|
+
) -> tuple[float, PropagatorPosition]:
|
|
958
|
+
"""Propagate a ballistic trajectory using the DeltaV_MinEcc branch."""
|
|
959
|
+
payload: dict[str, Any] = {
|
|
960
|
+
"Start": start,
|
|
961
|
+
"ImpactLatitude": impact_latitude_deg,
|
|
962
|
+
"ImpactLongitude": impact_longitude_deg,
|
|
963
|
+
"BallisticType": "DeltaV_MinEcc",
|
|
964
|
+
"BallisticTypeValue": delta_v_m_s,
|
|
965
|
+
}
|
|
966
|
+
if step_s is not None:
|
|
967
|
+
payload["Step"] = step_s
|
|
968
|
+
if central_body is not None:
|
|
969
|
+
payload["CentralBody"] = central_body
|
|
970
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
971
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
972
|
+
if launch_latitude_deg is not None:
|
|
973
|
+
payload["LaunchLatitude"] = launch_latitude_deg
|
|
974
|
+
if launch_longitude_deg is not None:
|
|
975
|
+
payload["LaunchLongitude"] = launch_longitude_deg
|
|
976
|
+
if launch_altitude_m is not None:
|
|
977
|
+
payload["LaunchAltitude"] = launch_altitude_m
|
|
978
|
+
if impact_altitude_m is not None:
|
|
979
|
+
payload["ImpactAltitude"] = impact_altitude_m
|
|
980
|
+
if stop is not None:
|
|
981
|
+
payload["Stop"] = stop
|
|
982
|
+
|
|
983
|
+
result = raw.post("/Propagator/Ballistic", json=payload)
|
|
984
|
+
return _success_path(result)
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def ballistic_apogee_altitude(
|
|
988
|
+
*,
|
|
989
|
+
start: str,
|
|
990
|
+
impact_latitude_deg: float,
|
|
991
|
+
impact_longitude_deg: float,
|
|
992
|
+
apogee_altitude_m: float,
|
|
993
|
+
stop: str | None = None,
|
|
994
|
+
step_s: float | None = None,
|
|
995
|
+
central_body: str | None = None,
|
|
996
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
997
|
+
launch_latitude_deg: float | None = None,
|
|
998
|
+
launch_longitude_deg: float | None = None,
|
|
999
|
+
launch_altitude_m: float | None = None,
|
|
1000
|
+
impact_altitude_m: float | None = None,
|
|
1001
|
+
) -> tuple[float, PropagatorPosition]:
|
|
1002
|
+
"""Propagate a ballistic trajectory using the ApogeeAlt branch."""
|
|
1003
|
+
payload: dict[str, Any] = {
|
|
1004
|
+
"Start": start,
|
|
1005
|
+
"ImpactLatitude": impact_latitude_deg,
|
|
1006
|
+
"ImpactLongitude": impact_longitude_deg,
|
|
1007
|
+
"BallisticType": "ApogeeAlt",
|
|
1008
|
+
"BallisticTypeValue": apogee_altitude_m,
|
|
1009
|
+
}
|
|
1010
|
+
if step_s is not None:
|
|
1011
|
+
payload["Step"] = step_s
|
|
1012
|
+
if central_body is not None:
|
|
1013
|
+
payload["CentralBody"] = central_body
|
|
1014
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
1015
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
1016
|
+
if launch_latitude_deg is not None:
|
|
1017
|
+
payload["LaunchLatitude"] = launch_latitude_deg
|
|
1018
|
+
if launch_longitude_deg is not None:
|
|
1019
|
+
payload["LaunchLongitude"] = launch_longitude_deg
|
|
1020
|
+
if launch_altitude_m is not None:
|
|
1021
|
+
payload["LaunchAltitude"] = launch_altitude_m
|
|
1022
|
+
if impact_altitude_m is not None:
|
|
1023
|
+
payload["ImpactAltitude"] = impact_altitude_m
|
|
1024
|
+
if stop is not None:
|
|
1025
|
+
payload["Stop"] = stop
|
|
1026
|
+
|
|
1027
|
+
result = raw.post("/Propagator/Ballistic", json=payload)
|
|
1028
|
+
return _success_path(result)
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def ballistic_time_of_flight(
|
|
1032
|
+
*,
|
|
1033
|
+
start: str,
|
|
1034
|
+
impact_latitude_deg: float,
|
|
1035
|
+
impact_longitude_deg: float,
|
|
1036
|
+
time_of_flight_s: float,
|
|
1037
|
+
stop: str | None = None,
|
|
1038
|
+
step_s: float | None = None,
|
|
1039
|
+
central_body: str | None = None,
|
|
1040
|
+
gravitational_parameter_m3_s2: float | None = None,
|
|
1041
|
+
launch_latitude_deg: float | None = None,
|
|
1042
|
+
launch_longitude_deg: float | None = None,
|
|
1043
|
+
launch_altitude_m: float | None = None,
|
|
1044
|
+
impact_altitude_m: float | None = None,
|
|
1045
|
+
) -> tuple[float, PropagatorPosition]:
|
|
1046
|
+
"""Propagate a ballistic trajectory using the TimeOfFlight branch."""
|
|
1047
|
+
payload: dict[str, Any] = {
|
|
1048
|
+
"Start": start,
|
|
1049
|
+
"ImpactLatitude": impact_latitude_deg,
|
|
1050
|
+
"ImpactLongitude": impact_longitude_deg,
|
|
1051
|
+
"BallisticType": "TimeOfFlight",
|
|
1052
|
+
"BallisticTypeValue": time_of_flight_s,
|
|
1053
|
+
}
|
|
1054
|
+
if step_s is not None:
|
|
1055
|
+
payload["Step"] = step_s
|
|
1056
|
+
if central_body is not None:
|
|
1057
|
+
payload["CentralBody"] = central_body
|
|
1058
|
+
if gravitational_parameter_m3_s2 is not None:
|
|
1059
|
+
payload["GravitationalParameter"] = gravitational_parameter_m3_s2
|
|
1060
|
+
if launch_latitude_deg is not None:
|
|
1061
|
+
payload["LaunchLatitude"] = launch_latitude_deg
|
|
1062
|
+
if launch_longitude_deg is not None:
|
|
1063
|
+
payload["LaunchLongitude"] = launch_longitude_deg
|
|
1064
|
+
if launch_altitude_m is not None:
|
|
1065
|
+
payload["LaunchAltitude"] = launch_altitude_m
|
|
1066
|
+
if impact_altitude_m is not None:
|
|
1067
|
+
payload["ImpactAltitude"] = impact_altitude_m
|
|
1068
|
+
if stop is not None:
|
|
1069
|
+
payload["Stop"] = stop
|
|
1070
|
+
|
|
1071
|
+
result = raw.post("/Propagator/Ballistic", json=payload)
|
|
1072
|
+
return _success_path(result)
|