astrox-python 0.2.0__py3-none-any.whl → 1.0.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 +3 -1
- astrox/catalog.py +22 -3
- astrox/celestial.py +208 -4
- astrox/libration.py +448 -0
- astrox/terrain.py +14 -1
- {astrox_python-0.2.0.dist-info → astrox_python-1.0.0.dist-info}/METADATA +3 -3
- {astrox_python-0.2.0.dist-info → astrox_python-1.0.0.dist-info}/RECORD +9 -8
- {astrox_python-0.2.0.dist-info → astrox_python-1.0.0.dist-info}/WHEEL +1 -1
- {astrox_python-0.2.0.dist-info → astrox_python-1.0.0.dist-info}/licenses/LICENSE +0 -0
astrox/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ Functions are organized by domain modules and can use package-level
|
|
|
4
4
|
configuration without requiring explicit client management:
|
|
5
5
|
|
|
6
6
|
import astrox
|
|
7
|
-
from astrox import access, celestial, catalog, components, coverage, lighting, orbits, propagator, terrain
|
|
7
|
+
from astrox import access, celestial, catalog, components, coverage, libration, lighting, orbits, propagator, terrain
|
|
8
8
|
|
|
9
9
|
astrox.configure(base_url="http://custom:8765", timeout=120)
|
|
10
10
|
orbit = orbits.keplerian(...)
|
|
@@ -35,6 +35,7 @@ from astrox import (
|
|
|
35
35
|
conjunction,
|
|
36
36
|
components,
|
|
37
37
|
coverage,
|
|
38
|
+
libration,
|
|
38
39
|
lighting,
|
|
39
40
|
orbits,
|
|
40
41
|
propagator,
|
|
@@ -60,6 +61,7 @@ __all__ = [
|
|
|
60
61
|
"conjunction",
|
|
61
62
|
"coverage",
|
|
62
63
|
"components",
|
|
64
|
+
"libration",
|
|
63
65
|
"lighting",
|
|
64
66
|
"orbits",
|
|
65
67
|
"propagator",
|
astrox/catalog.py
CHANGED
|
@@ -43,6 +43,16 @@ def _optional_boolean_query(value: bool | None, *, parameter: str) -> str | None
|
|
|
43
43
|
return "true" if value else "false"
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def _without_status_fields(value: Any, *, endpoint: str) -> dict[str, Any]:
|
|
47
|
+
if not isinstance(value, dict):
|
|
48
|
+
raise TypeError(f"{endpoint} response must be an object")
|
|
49
|
+
return {
|
|
50
|
+
key: item
|
|
51
|
+
for key, item in value.items()
|
|
52
|
+
if key not in {"IsSuccess", "Message"}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
46
56
|
def query_cities(
|
|
47
57
|
*,
|
|
48
58
|
city_name: str | None = None,
|
|
@@ -72,7 +82,10 @@ def query_cities(
|
|
|
72
82
|
"typeOfCity",
|
|
73
83
|
_optional_string(city_type, parameter="city_type"),
|
|
74
84
|
)
|
|
75
|
-
return
|
|
85
|
+
return _without_status_fields(
|
|
86
|
+
raw.get("/city", params=params),
|
|
87
|
+
endpoint="/city",
|
|
88
|
+
)
|
|
76
89
|
|
|
77
90
|
|
|
78
91
|
def query_facilities(
|
|
@@ -92,7 +105,10 @@ def query_facilities(
|
|
|
92
105
|
"networkName",
|
|
93
106
|
_optional_string(network_name, parameter="network_name"),
|
|
94
107
|
)
|
|
95
|
-
return
|
|
108
|
+
return _without_status_fields(
|
|
109
|
+
raw.get("/facility", params=params),
|
|
110
|
+
endpoint="/facility",
|
|
111
|
+
)
|
|
96
112
|
|
|
97
113
|
|
|
98
114
|
def query_satellites(
|
|
@@ -172,4 +188,7 @@ def query_satellites(
|
|
|
172
188
|
parameter="maximum_inclination_deg",
|
|
173
189
|
),
|
|
174
190
|
)
|
|
175
|
-
return
|
|
191
|
+
return _without_status_fields(
|
|
192
|
+
raw.get("/ssc", params=params),
|
|
193
|
+
endpoint="/ssc",
|
|
194
|
+
)
|
astrox/celestial.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Celestial read-like and computational endpoint functions."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from numbers import Real
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
9
|
from astrox._http import raw
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
12
|
+
"MpcOrbitalElements",
|
|
11
13
|
"cb_axes_rotation",
|
|
12
14
|
"ephemeris",
|
|
15
|
+
"lambert_transfer_window",
|
|
13
16
|
"mpc_ephemeris",
|
|
17
|
+
"mpc_orbital_elements",
|
|
14
18
|
]
|
|
15
19
|
|
|
16
20
|
|
|
@@ -47,6 +51,197 @@ def _optional_integer(value: int | None, *, parameter: str) -> int | None:
|
|
|
47
51
|
return value
|
|
48
52
|
|
|
49
53
|
|
|
54
|
+
def _without_status_fields(value: Any, *, endpoint: str) -> dict[str, Any]:
|
|
55
|
+
if not isinstance(value, dict):
|
|
56
|
+
raise TypeError(f"{endpoint} response must be an object")
|
|
57
|
+
return {
|
|
58
|
+
key: item
|
|
59
|
+
for key, item in value.items()
|
|
60
|
+
if key not in {"IsSuccess", "Message"}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True, kw_only=True)
|
|
65
|
+
class MpcOrbitalElements:
|
|
66
|
+
"""MPC heliocentric orbital elements used by celestial transfer routes."""
|
|
67
|
+
|
|
68
|
+
epoch_mjd_tdt: float | None = None
|
|
69
|
+
periapsis_time_mjd_tdt: float | None = None
|
|
70
|
+
periapsis_distance_au: float | None = None
|
|
71
|
+
semi_major_axis_au: float | None = None
|
|
72
|
+
eccentricity: float | None = None
|
|
73
|
+
inclination_deg: float | None = None
|
|
74
|
+
raan_deg: float | None = None
|
|
75
|
+
argument_of_periapsis_deg: float | None = None
|
|
76
|
+
mean_anomaly_deg: float | None = None
|
|
77
|
+
|
|
78
|
+
def __post_init__(self) -> None:
|
|
79
|
+
for field_name in (
|
|
80
|
+
"epoch_mjd_tdt",
|
|
81
|
+
"periapsis_time_mjd_tdt",
|
|
82
|
+
"periapsis_distance_au",
|
|
83
|
+
"semi_major_axis_au",
|
|
84
|
+
"eccentricity",
|
|
85
|
+
"inclination_deg",
|
|
86
|
+
"raan_deg",
|
|
87
|
+
"argument_of_periapsis_deg",
|
|
88
|
+
"mean_anomaly_deg",
|
|
89
|
+
):
|
|
90
|
+
object.__setattr__(
|
|
91
|
+
self,
|
|
92
|
+
field_name,
|
|
93
|
+
_optional_number(
|
|
94
|
+
getattr(self, field_name),
|
|
95
|
+
parameter=field_name,
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def to_wire(self) -> dict[str, float]:
|
|
100
|
+
payload: dict[str, float] = {}
|
|
101
|
+
_include_if_supplied(payload, "EpochMjdTdt", self.epoch_mjd_tdt)
|
|
102
|
+
_include_if_supplied(payload, "PeriTimeMjdTdt", self.periapsis_time_mjd_tdt)
|
|
103
|
+
_include_if_supplied(payload, "Q", self.periapsis_distance_au)
|
|
104
|
+
_include_if_supplied(payload, "SemimajorAxis", self.semi_major_axis_au)
|
|
105
|
+
_include_if_supplied(payload, "Eccentricity", self.eccentricity)
|
|
106
|
+
_include_if_supplied(payload, "Inclination", self.inclination_deg)
|
|
107
|
+
_include_if_supplied(payload, "Raan", self.raan_deg)
|
|
108
|
+
_include_if_supplied(
|
|
109
|
+
payload,
|
|
110
|
+
"ArgOfPeriapsis",
|
|
111
|
+
self.argument_of_periapsis_deg,
|
|
112
|
+
)
|
|
113
|
+
_include_if_supplied(payload, "MeanAnomaly", self.mean_anomaly_deg)
|
|
114
|
+
return payload
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def mpc_orbital_elements(
|
|
118
|
+
*,
|
|
119
|
+
epoch_mjd_tdt: float | None = None,
|
|
120
|
+
periapsis_time_mjd_tdt: float | None = None,
|
|
121
|
+
periapsis_distance_au: float | None = None,
|
|
122
|
+
semi_major_axis_au: float | None = None,
|
|
123
|
+
eccentricity: float | None = None,
|
|
124
|
+
inclination_deg: float | None = None,
|
|
125
|
+
raan_deg: float | None = None,
|
|
126
|
+
argument_of_periapsis_deg: float | None = None,
|
|
127
|
+
mean_anomaly_deg: float | None = None,
|
|
128
|
+
) -> MpcOrbitalElements:
|
|
129
|
+
"""Build an MPC orbital-element fragment without adding physics policy."""
|
|
130
|
+
return MpcOrbitalElements(
|
|
131
|
+
epoch_mjd_tdt=epoch_mjd_tdt,
|
|
132
|
+
periapsis_time_mjd_tdt=periapsis_time_mjd_tdt,
|
|
133
|
+
periapsis_distance_au=periapsis_distance_au,
|
|
134
|
+
semi_major_axis_au=semi_major_axis_au,
|
|
135
|
+
eccentricity=eccentricity,
|
|
136
|
+
inclination_deg=inclination_deg,
|
|
137
|
+
raan_deg=raan_deg,
|
|
138
|
+
argument_of_periapsis_deg=argument_of_periapsis_deg,
|
|
139
|
+
mean_anomaly_deg=mean_anomaly_deg,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _interval(start: str, stop: str, *, parameter: str) -> str:
|
|
144
|
+
return "/".join(
|
|
145
|
+
(
|
|
146
|
+
_string(start, parameter=f"{parameter}_start"),
|
|
147
|
+
_string(stop, parameter=f"{parameter}_stop"),
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _elements_to_wire(
|
|
153
|
+
value: MpcOrbitalElements | None,
|
|
154
|
+
*,
|
|
155
|
+
parameter: str,
|
|
156
|
+
) -> dict[str, float] | None:
|
|
157
|
+
if value is None:
|
|
158
|
+
return None
|
|
159
|
+
if not isinstance(value, MpcOrbitalElements):
|
|
160
|
+
raise TypeError(f"{parameter} must be an MpcOrbitalElements value")
|
|
161
|
+
return value.to_wire()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def lambert_transfer_window(
|
|
165
|
+
*,
|
|
166
|
+
departure_body: str,
|
|
167
|
+
arrival_body: str,
|
|
168
|
+
departure_start: str,
|
|
169
|
+
departure_stop: str,
|
|
170
|
+
arrival_start: str,
|
|
171
|
+
arrival_stop: str,
|
|
172
|
+
sun_frame: str | None = None,
|
|
173
|
+
min_time_of_flight_days: int | None = None,
|
|
174
|
+
departure_step_days: float | None = None,
|
|
175
|
+
arrival_step_days: float | None = None,
|
|
176
|
+
departure_elements: MpcOrbitalElements | None = None,
|
|
177
|
+
arrival_elements: MpcOrbitalElements | None = None,
|
|
178
|
+
) -> dict[str, Any]:
|
|
179
|
+
"""Return a Lambert transfer-window scan from ASTROX.
|
|
180
|
+
|
|
181
|
+
The returned dictionary preserves every server field except the transport
|
|
182
|
+
success envelope fields ``IsSuccess`` and ``Message``. The transfer route
|
|
183
|
+
itself returns one result for each sampled departure/arrival time pair that
|
|
184
|
+
it evaluates; it is not the single-case ``orbits.lambert_delta_v`` route.
|
|
185
|
+
"""
|
|
186
|
+
payload: dict[str, Any] = {
|
|
187
|
+
"DepartureCbName": _string(departure_body, parameter="departure_body"),
|
|
188
|
+
"ArrivalCbName": _string(arrival_body, parameter="arrival_body"),
|
|
189
|
+
"DepartureInterval": _interval(
|
|
190
|
+
departure_start,
|
|
191
|
+
departure_stop,
|
|
192
|
+
parameter="departure_interval",
|
|
193
|
+
),
|
|
194
|
+
"ArrivalInterval": _interval(
|
|
195
|
+
arrival_start,
|
|
196
|
+
arrival_stop,
|
|
197
|
+
parameter="arrival_interval",
|
|
198
|
+
),
|
|
199
|
+
}
|
|
200
|
+
_include_if_supplied(
|
|
201
|
+
payload,
|
|
202
|
+
"SunFrameName",
|
|
203
|
+
_optional_string(sun_frame, parameter="sun_frame"),
|
|
204
|
+
)
|
|
205
|
+
_include_if_supplied(
|
|
206
|
+
payload,
|
|
207
|
+
"MinTofDays",
|
|
208
|
+
_optional_integer(
|
|
209
|
+
min_time_of_flight_days,
|
|
210
|
+
parameter="min_time_of_flight_days",
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
_include_if_supplied(
|
|
214
|
+
payload,
|
|
215
|
+
"DepartureStepDay",
|
|
216
|
+
_optional_number(
|
|
217
|
+
departure_step_days,
|
|
218
|
+
parameter="departure_step_days",
|
|
219
|
+
),
|
|
220
|
+
)
|
|
221
|
+
_include_if_supplied(
|
|
222
|
+
payload,
|
|
223
|
+
"ArrivalStepDay",
|
|
224
|
+
_optional_number(
|
|
225
|
+
arrival_step_days,
|
|
226
|
+
parameter="arrival_step_days",
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
_include_if_supplied(
|
|
230
|
+
payload,
|
|
231
|
+
"DepartureElements",
|
|
232
|
+
_elements_to_wire(departure_elements, parameter="departure_elements"),
|
|
233
|
+
)
|
|
234
|
+
_include_if_supplied(
|
|
235
|
+
payload,
|
|
236
|
+
"ArrivalElements",
|
|
237
|
+
_elements_to_wire(arrival_elements, parameter="arrival_elements"),
|
|
238
|
+
)
|
|
239
|
+
return _without_status_fields(
|
|
240
|
+
raw.post("/celestial/transfer", json=payload),
|
|
241
|
+
endpoint="/celestial/transfer",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
50
245
|
def ephemeris(
|
|
51
246
|
*,
|
|
52
247
|
target_name: str,
|
|
@@ -85,7 +280,10 @@ def ephemeris(
|
|
|
85
280
|
"Step",
|
|
86
281
|
_optional_number(step_s, parameter="step_s"),
|
|
87
282
|
)
|
|
88
|
-
return
|
|
283
|
+
return _without_status_fields(
|
|
284
|
+
raw.post("/celestial/ephemeris", json=payload),
|
|
285
|
+
endpoint="/celestial/ephemeris",
|
|
286
|
+
)
|
|
89
287
|
|
|
90
288
|
|
|
91
289
|
def cb_axes_rotation(
|
|
@@ -121,7 +319,10 @@ def cb_axes_rotation(
|
|
|
121
319
|
"Order",
|
|
122
320
|
_optional_integer(order, parameter="order"),
|
|
123
321
|
)
|
|
124
|
-
return
|
|
322
|
+
return _without_status_fields(
|
|
323
|
+
raw.post("/celestial/CbAxesRotation", json=payload),
|
|
324
|
+
endpoint="/celestial/CbAxesRotation",
|
|
325
|
+
)
|
|
125
326
|
|
|
126
327
|
|
|
127
328
|
def mpc_ephemeris(
|
|
@@ -150,4 +351,7 @@ def mpc_ephemeris(
|
|
|
150
351
|
"Stop",
|
|
151
352
|
_optional_string(stop, parameter="stop"),
|
|
152
353
|
)
|
|
153
|
-
return
|
|
354
|
+
return _without_status_fields(
|
|
355
|
+
raw.post("/celestial/mpc", json=payload),
|
|
356
|
+
endpoint="/celestial/mpc",
|
|
357
|
+
)
|
astrox/libration.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""CRTBP and Earth-Moon libration-dynamics functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from numbers import Real
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from astrox._http import raw
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CrtbpSample",
|
|
15
|
+
"CrtbpState",
|
|
16
|
+
"CrtbpTrajectory",
|
|
17
|
+
"LibrationPoint",
|
|
18
|
+
"LibrationPoints",
|
|
19
|
+
"LibrationUnitSystem",
|
|
20
|
+
"PeriodicOrbit",
|
|
21
|
+
"correct_periodic_orbit_fixed_x",
|
|
22
|
+
"crtbp_state",
|
|
23
|
+
"crtbp_trajectory",
|
|
24
|
+
"earth_moon_dro",
|
|
25
|
+
"earth_moon_l1_halo",
|
|
26
|
+
"earth_moon_l2_halo",
|
|
27
|
+
"positions",
|
|
28
|
+
"units",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _number(value: Any, *, field: str) -> float:
|
|
33
|
+
if isinstance(value, bool) or not isinstance(value, Real):
|
|
34
|
+
raise TypeError(f"{field} must be a number")
|
|
35
|
+
result = float(value)
|
|
36
|
+
if not math.isfinite(result):
|
|
37
|
+
raise ValueError(f"{field} must be finite")
|
|
38
|
+
return result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _optional_number(value: float | None, *, parameter: str) -> float | None:
|
|
42
|
+
if value is None:
|
|
43
|
+
return None
|
|
44
|
+
return _number(value, field=parameter)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _optional_boolean(value: bool | None, *, parameter: str) -> bool | None:
|
|
48
|
+
if value is None:
|
|
49
|
+
return None
|
|
50
|
+
if not isinstance(value, bool):
|
|
51
|
+
raise TypeError(f"{parameter} must be a boolean")
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _mapping(value: Any, *, field: str) -> Mapping[str, Any]:
|
|
56
|
+
if not isinstance(value, Mapping):
|
|
57
|
+
raise TypeError(f"{field} must be an object")
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _sequence(value: Any, *, field: str) -> Sequence[Any]:
|
|
62
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
63
|
+
raise TypeError(f"{field} must be an array")
|
|
64
|
+
return value
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _boolean(value: Any, *, field: str) -> bool:
|
|
68
|
+
if not isinstance(value, bool):
|
|
69
|
+
raise TypeError(f"{field} must be a boolean")
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _include_if_supplied(payload: dict[str, Any], wire_key: str, value: Any) -> None:
|
|
74
|
+
if value is not None:
|
|
75
|
+
payload[wire_key] = value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _boolean_query(value: bool | None, *, parameter: str) -> str | None:
|
|
79
|
+
parsed = _optional_boolean(value, parameter=parameter)
|
|
80
|
+
if parsed is None:
|
|
81
|
+
return None
|
|
82
|
+
return "true" if parsed else "false"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True, kw_only=True)
|
|
86
|
+
class LibrationPoint:
|
|
87
|
+
"""One nondimensional CRTBP equilibrium-point coordinate."""
|
|
88
|
+
|
|
89
|
+
x: float
|
|
90
|
+
y: float
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True, kw_only=True)
|
|
94
|
+
class LibrationPoints:
|
|
95
|
+
"""The five CRTBP equilibrium points and collinear-point distances."""
|
|
96
|
+
|
|
97
|
+
l1: LibrationPoint
|
|
98
|
+
l2: LibrationPoint
|
|
99
|
+
l3: LibrationPoint
|
|
100
|
+
l4: LibrationPoint
|
|
101
|
+
l5: LibrationPoint
|
|
102
|
+
l1_distance_to_secondary: float
|
|
103
|
+
l2_distance_to_secondary: float
|
|
104
|
+
l3_distance_to_primary: float
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True, kw_only=True)
|
|
108
|
+
class LibrationUnitSystem:
|
|
109
|
+
"""Dimensional scales for a nondimensional CRTBP system."""
|
|
110
|
+
|
|
111
|
+
primary_gravitational_parameter_m3_s2: float
|
|
112
|
+
secondary_gravitational_parameter_m3_s2: float
|
|
113
|
+
mass_ratio: float
|
|
114
|
+
length_unit_m: float
|
|
115
|
+
time_unit_s: float
|
|
116
|
+
velocity_unit_m_s: float
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True, kw_only=True)
|
|
120
|
+
class CrtbpState:
|
|
121
|
+
"""Nondimensional CRTBP position and rotating-frame velocity."""
|
|
122
|
+
|
|
123
|
+
x: float
|
|
124
|
+
y: float
|
|
125
|
+
z: float
|
|
126
|
+
vx: float
|
|
127
|
+
vy: float
|
|
128
|
+
vz: float
|
|
129
|
+
|
|
130
|
+
def __post_init__(self) -> None:
|
|
131
|
+
for field_name in ("x", "y", "z", "vx", "vy", "vz"):
|
|
132
|
+
object.__setattr__(
|
|
133
|
+
self,
|
|
134
|
+
field_name,
|
|
135
|
+
_number(getattr(self, field_name), field=field_name),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def to_wire(self) -> list[float]:
|
|
139
|
+
"""Lower to ASTROX ``[x, y, z, vx, vy, vz]`` order."""
|
|
140
|
+
return [self.x, self.y, self.z, self.vx, self.vy, self.vz]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(frozen=True, kw_only=True)
|
|
144
|
+
class CrtbpSample:
|
|
145
|
+
"""One nondimensional CRTBP state at a nondimensional time."""
|
|
146
|
+
|
|
147
|
+
time: float
|
|
148
|
+
state: CrtbpState
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(frozen=True, kw_only=True)
|
|
152
|
+
class CrtbpTrajectory:
|
|
153
|
+
"""A sampled nondimensional CRTBP trajectory."""
|
|
154
|
+
|
|
155
|
+
mass_ratio: float
|
|
156
|
+
is_barycentric: bool
|
|
157
|
+
samples: tuple[CrtbpSample, ...]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True, kw_only=True)
|
|
161
|
+
class PeriodicOrbit:
|
|
162
|
+
"""A corrected periodic CRTBP orbit and one sampled period."""
|
|
163
|
+
|
|
164
|
+
is_barycentric: bool
|
|
165
|
+
period: float
|
|
166
|
+
initial_state: CrtbpState
|
|
167
|
+
corrected_state: CrtbpState
|
|
168
|
+
samples: tuple[CrtbpSample, ...]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def crtbp_state(
|
|
172
|
+
*,
|
|
173
|
+
x: float,
|
|
174
|
+
y: float,
|
|
175
|
+
z: float,
|
|
176
|
+
vx: float,
|
|
177
|
+
vy: float,
|
|
178
|
+
vz: float,
|
|
179
|
+
) -> CrtbpState:
|
|
180
|
+
"""Create a nondimensional rotating-frame CRTBP state."""
|
|
181
|
+
return CrtbpState(x=x, y=y, z=z, vx=vx, vy=vy, vz=vz)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _state_to_wire(value: CrtbpState, *, parameter: str) -> list[float]:
|
|
185
|
+
if not isinstance(value, CrtbpState):
|
|
186
|
+
raise TypeError(f"{parameter} must be a CrtbpState value")
|
|
187
|
+
return value.to_wire()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _state_from_wire(value: Any, *, field: str) -> CrtbpState:
|
|
191
|
+
values = _sequence(value, field=field)
|
|
192
|
+
if len(values) != 6:
|
|
193
|
+
raise TypeError(f"{field} must contain six numbers")
|
|
194
|
+
return CrtbpState(
|
|
195
|
+
x=_number(values[0], field=f"{field}[0]"),
|
|
196
|
+
y=_number(values[1], field=f"{field}[1]"),
|
|
197
|
+
z=_number(values[2], field=f"{field}[2]"),
|
|
198
|
+
vx=_number(values[3], field=f"{field}[3]"),
|
|
199
|
+
vy=_number(values[4], field=f"{field}[4]"),
|
|
200
|
+
vz=_number(values[5], field=f"{field}[5]"),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _points_from_wire(value: Any) -> LibrationPoints:
|
|
205
|
+
values = _sequence(value, field="positions response")
|
|
206
|
+
if len(values) != 10:
|
|
207
|
+
raise TypeError("positions response must contain ten numbers")
|
|
208
|
+
numbers = tuple(
|
|
209
|
+
_number(item, field=f"positions response[{index}]")
|
|
210
|
+
for index, item in enumerate(values)
|
|
211
|
+
)
|
|
212
|
+
return LibrationPoints(
|
|
213
|
+
l1=LibrationPoint(x=numbers[3], y=0.0),
|
|
214
|
+
l2=LibrationPoint(x=numbers[4], y=0.0),
|
|
215
|
+
l3=LibrationPoint(x=numbers[5], y=0.0),
|
|
216
|
+
l4=LibrationPoint(x=numbers[6], y=numbers[8]),
|
|
217
|
+
l5=LibrationPoint(x=numbers[7], y=numbers[9]),
|
|
218
|
+
l1_distance_to_secondary=numbers[0],
|
|
219
|
+
l2_distance_to_secondary=numbers[1],
|
|
220
|
+
l3_distance_to_primary=numbers[2],
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _units_from_wire(value: Any) -> LibrationUnitSystem:
|
|
225
|
+
payload = _mapping(value, field="unit response")
|
|
226
|
+
return LibrationUnitSystem(
|
|
227
|
+
primary_gravitational_parameter_m3_s2=_number(
|
|
228
|
+
payload["GravitationalParameter1"],
|
|
229
|
+
field="GravitationalParameter1",
|
|
230
|
+
),
|
|
231
|
+
secondary_gravitational_parameter_m3_s2=_number(
|
|
232
|
+
payload["GravitationalParameter2"],
|
|
233
|
+
field="GravitationalParameter2",
|
|
234
|
+
),
|
|
235
|
+
mass_ratio=_number(payload["U"], field="U"),
|
|
236
|
+
length_unit_m=_number(payload["UnitL"], field="UnitL"),
|
|
237
|
+
time_unit_s=_number(payload["UnitT"], field="UnitT"),
|
|
238
|
+
velocity_unit_m_s=_number(payload["UnitV"], field="UnitV"),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _trajectory_from_wire(value: Any) -> CrtbpTrajectory:
|
|
243
|
+
payload = _mapping(value, field="crtbp trajectory response")
|
|
244
|
+
positions = _sequence(payload["Positions"], field="Positions")
|
|
245
|
+
if not positions or len(positions) % 7 != 0:
|
|
246
|
+
raise TypeError("Positions must contain one or more seven-number samples")
|
|
247
|
+
samples = tuple(
|
|
248
|
+
CrtbpSample(
|
|
249
|
+
time=_number(positions[index], field=f"Positions[{index}]"),
|
|
250
|
+
state=_state_from_wire(
|
|
251
|
+
positions[index + 1 : index + 7],
|
|
252
|
+
field=f"Positions[{index + 1}:{index + 7}]",
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
for index in range(0, len(positions), 7)
|
|
256
|
+
)
|
|
257
|
+
return CrtbpTrajectory(
|
|
258
|
+
mass_ratio=_number(payload["U"], field="U"),
|
|
259
|
+
is_barycentric=_boolean(payload["IsBarycentric"], field="IsBarycentric"),
|
|
260
|
+
samples=samples,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _periodic_orbit_from_wire(value: Any) -> PeriodicOrbit:
|
|
265
|
+
payload = _mapping(value, field="periodic orbit response")
|
|
266
|
+
times = _sequence(payload["ListT"], field="ListT")
|
|
267
|
+
states = _sequence(payload["ListX"], field="ListX")
|
|
268
|
+
if not times or len(times) != len(states):
|
|
269
|
+
raise TypeError("ListT and ListX must be non-empty arrays of equal length")
|
|
270
|
+
samples = tuple(
|
|
271
|
+
CrtbpSample(
|
|
272
|
+
time=_number(time, field=f"ListT[{index}]"),
|
|
273
|
+
state=_state_from_wire(state, field=f"ListX[{index}]"),
|
|
274
|
+
)
|
|
275
|
+
for index, (time, state) in enumerate(zip(times, states, strict=True))
|
|
276
|
+
)
|
|
277
|
+
return PeriodicOrbit(
|
|
278
|
+
is_barycentric=_boolean(payload["IsBarycentric"], field="IsBarycentric"),
|
|
279
|
+
period=_number(payload["Period"], field="Period"),
|
|
280
|
+
initial_state=_state_from_wire(payload["InitialX0"], field="InitialX0"),
|
|
281
|
+
corrected_state=_state_from_wire(payload["X0"], field="X0"),
|
|
282
|
+
samples=samples,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def positions(*, mass_ratio: float) -> LibrationPoints:
|
|
287
|
+
"""Return the five barycentric CRTBP equilibrium points."""
|
|
288
|
+
return _points_from_wire(
|
|
289
|
+
raw.get(
|
|
290
|
+
"/libration/positions",
|
|
291
|
+
params={"u": _number(mass_ratio, field="mass_ratio")},
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def units(
|
|
297
|
+
*,
|
|
298
|
+
primary_gravitational_parameter_m3_s2: float | None = None,
|
|
299
|
+
secondary_gravitational_parameter_m3_s2: float | None = None,
|
|
300
|
+
mean_separation_m: float | None = None,
|
|
301
|
+
) -> LibrationUnitSystem:
|
|
302
|
+
"""Return the dimensional scales for a CRTBP primary-secondary system."""
|
|
303
|
+
params: dict[str, Any] = {}
|
|
304
|
+
_include_if_supplied(
|
|
305
|
+
params,
|
|
306
|
+
"gm1",
|
|
307
|
+
_optional_number(
|
|
308
|
+
primary_gravitational_parameter_m3_s2,
|
|
309
|
+
parameter="primary_gravitational_parameter_m3_s2",
|
|
310
|
+
),
|
|
311
|
+
)
|
|
312
|
+
_include_if_supplied(
|
|
313
|
+
params,
|
|
314
|
+
"gm2",
|
|
315
|
+
_optional_number(
|
|
316
|
+
secondary_gravitational_parameter_m3_s2,
|
|
317
|
+
parameter="secondary_gravitational_parameter_m3_s2",
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
_include_if_supplied(
|
|
321
|
+
params,
|
|
322
|
+
"meanRange",
|
|
323
|
+
_optional_number(mean_separation_m, parameter="mean_separation_m"),
|
|
324
|
+
)
|
|
325
|
+
return _units_from_wire(raw.get("/libration/unit", params=params))
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def crtbp_trajectory(
|
|
329
|
+
*,
|
|
330
|
+
initial_state: CrtbpState,
|
|
331
|
+
mass_ratio: float,
|
|
332
|
+
start_time: float | None = None,
|
|
333
|
+
end_time: float | None = None,
|
|
334
|
+
barycentric: bool | None = None,
|
|
335
|
+
output_step: float | None = None,
|
|
336
|
+
) -> CrtbpTrajectory:
|
|
337
|
+
"""Integrate a nondimensional CRTBP state in the rotating frame."""
|
|
338
|
+
payload: dict[str, Any] = {
|
|
339
|
+
"RV0": _state_to_wire(initial_state, parameter="initial_state"),
|
|
340
|
+
"U": _number(mass_ratio, field="mass_ratio"),
|
|
341
|
+
}
|
|
342
|
+
_include_if_supplied(
|
|
343
|
+
payload,
|
|
344
|
+
"T0",
|
|
345
|
+
_optional_number(start_time, parameter="start_time"),
|
|
346
|
+
)
|
|
347
|
+
_include_if_supplied(
|
|
348
|
+
payload,
|
|
349
|
+
"TEnd",
|
|
350
|
+
_optional_number(end_time, parameter="end_time"),
|
|
351
|
+
)
|
|
352
|
+
_include_if_supplied(
|
|
353
|
+
payload,
|
|
354
|
+
"IsBarycentric",
|
|
355
|
+
_optional_boolean(barycentric, parameter="barycentric"),
|
|
356
|
+
)
|
|
357
|
+
_include_if_supplied(
|
|
358
|
+
payload,
|
|
359
|
+
"OutStep",
|
|
360
|
+
_optional_number(output_step, parameter="output_step"),
|
|
361
|
+
)
|
|
362
|
+
return _trajectory_from_wire(
|
|
363
|
+
raw.post("/libration/crtbp-trajectory", json=payload)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def earth_moon_l1_halo(
|
|
368
|
+
*,
|
|
369
|
+
z_amplitude: float | None = None,
|
|
370
|
+
southern: bool | None = None,
|
|
371
|
+
) -> PeriodicOrbit:
|
|
372
|
+
"""Return a corrected Earth-Moon L1 Halo orbit."""
|
|
373
|
+
params: dict[str, Any] = {}
|
|
374
|
+
_include_if_supplied(
|
|
375
|
+
params,
|
|
376
|
+
"az",
|
|
377
|
+
_optional_number(z_amplitude, parameter="z_amplitude"),
|
|
378
|
+
)
|
|
379
|
+
_include_if_supplied(
|
|
380
|
+
params,
|
|
381
|
+
"isSouth",
|
|
382
|
+
_boolean_query(southern, parameter="southern"),
|
|
383
|
+
)
|
|
384
|
+
return _periodic_orbit_from_wire(
|
|
385
|
+
raw.get("/libration/em-l1-halo", params=params)
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def earth_moon_l2_halo(
|
|
390
|
+
*,
|
|
391
|
+
x_amplitude: float | None = None,
|
|
392
|
+
southern: bool | None = None,
|
|
393
|
+
) -> PeriodicOrbit:
|
|
394
|
+
"""Return a corrected Earth-Moon L2 Halo orbit."""
|
|
395
|
+
params: dict[str, Any] = {}
|
|
396
|
+
_include_if_supplied(
|
|
397
|
+
params,
|
|
398
|
+
"ax",
|
|
399
|
+
_optional_number(x_amplitude, parameter="x_amplitude"),
|
|
400
|
+
)
|
|
401
|
+
_include_if_supplied(
|
|
402
|
+
params,
|
|
403
|
+
"isSouth",
|
|
404
|
+
_boolean_query(southern, parameter="southern"),
|
|
405
|
+
)
|
|
406
|
+
return _periodic_orbit_from_wire(
|
|
407
|
+
raw.get("/libration/em-l2-halo", params=params)
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def earth_moon_dro(*, x_amplitude: float | None = None) -> PeriodicOrbit:
|
|
412
|
+
"""Return a corrected planar Earth-Moon distant retrograde orbit."""
|
|
413
|
+
params: dict[str, Any] = {}
|
|
414
|
+
_include_if_supplied(
|
|
415
|
+
params,
|
|
416
|
+
"ax",
|
|
417
|
+
_optional_number(x_amplitude, parameter="x_amplitude"),
|
|
418
|
+
)
|
|
419
|
+
return _periodic_orbit_from_wire(raw.get("/libration/em-dro", params=params))
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def correct_periodic_orbit_fixed_x(
|
|
423
|
+
*,
|
|
424
|
+
initial_state: CrtbpState,
|
|
425
|
+
period_guess: float,
|
|
426
|
+
mass_ratio: float,
|
|
427
|
+
barycentric: bool | None = None,
|
|
428
|
+
output_step: float | None = None,
|
|
429
|
+
) -> PeriodicOrbit:
|
|
430
|
+
"""Correct an XZ-plane-crossing seed while holding its x coordinate fixed."""
|
|
431
|
+
payload: dict[str, Any] = {
|
|
432
|
+
"RV0": _state_to_wire(initial_state, parameter="initial_state"),
|
|
433
|
+
"TEnd": _number(period_guess, field="period_guess"),
|
|
434
|
+
"U": _number(mass_ratio, field="mass_ratio"),
|
|
435
|
+
}
|
|
436
|
+
_include_if_supplied(
|
|
437
|
+
payload,
|
|
438
|
+
"IsBarycentric",
|
|
439
|
+
_optional_boolean(barycentric, parameter="barycentric"),
|
|
440
|
+
)
|
|
441
|
+
_include_if_supplied(
|
|
442
|
+
payload,
|
|
443
|
+
"OutStep",
|
|
444
|
+
_optional_number(output_step, parameter="output_step"),
|
|
445
|
+
)
|
|
446
|
+
return _periodic_orbit_from_wire(
|
|
447
|
+
raw.post("/libration/crtbp-period-orbit-fixed-x", json=payload)
|
|
448
|
+
)
|
astrox/terrain.py
CHANGED
|
@@ -45,6 +45,16 @@ def _optional_integer(value: int | None, *, parameter: str) -> int | None:
|
|
|
45
45
|
return value
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def _without_status_fields(value: Any, *, endpoint: str) -> dict[str, Any]:
|
|
49
|
+
if not isinstance(value, dict):
|
|
50
|
+
raise TypeError(f"{endpoint} response must be an object")
|
|
51
|
+
return {
|
|
52
|
+
key: item
|
|
53
|
+
for key, item in value.items()
|
|
54
|
+
if key not in {"IsSuccess", "Message"}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
48
58
|
def _site_position_to_wire(
|
|
49
59
|
value: components.SitePosition,
|
|
50
60
|
*,
|
|
@@ -164,7 +174,10 @@ def _mask_request(
|
|
|
164
174
|
"TerrainMaskPara",
|
|
165
175
|
_terrain_mask_config_to_wire(config, parameter="config"),
|
|
166
176
|
)
|
|
167
|
-
return
|
|
177
|
+
return _without_status_fields(
|
|
178
|
+
raw.post(endpoint, json=payload),
|
|
179
|
+
endpoint=endpoint,
|
|
180
|
+
)
|
|
168
181
|
|
|
169
182
|
|
|
170
183
|
def azimuth_elevation_mask(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: astrox-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Python SDK for the ASTROX Web API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Mtrya/astrox-python
|
|
6
6
|
Project-URL: Repository, https://github.com/Mtrya/astrox-python.git
|
|
@@ -10,7 +10,7 @@ Author-email: Mtrya <erchanmion20@outlook.com>
|
|
|
10
10
|
License-Expression: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Keywords: astrodynamics,astrox,orbit,propagation,sdk,space
|
|
13
|
-
Classifier: Development Status ::
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Intended Audience :: Science/Research
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
astrox/__init__.py,sha256=
|
|
1
|
+
astrox/__init__.py,sha256=2OlHH2Bx83mXoazHMd6hOD3laKNqrYQrgK-JNmRvtAM,1566
|
|
2
2
|
astrox/_http.py,sha256=ZZDkJ6qPG4VbMDRZO7xonIib88aYS6PPTVRxRYwXU3A,15606
|
|
3
3
|
astrox/access.py,sha256=pjyCher_yQNmkoojf2sWpG1HRDnLLK9gI90jM1iEoTo,5817
|
|
4
4
|
astrox/cat.py,sha256=doZqy6lGDtgrVe9QlU57l1lccmzirc14RDSR41tyGUU,15057
|
|
5
|
-
astrox/catalog.py,sha256=
|
|
6
|
-
astrox/celestial.py,sha256=
|
|
5
|
+
astrox/catalog.py,sha256=WDb3adiDO41YY6IdivjN3ri1vIQMHi6DPI3dBa8W1Gc,5255
|
|
6
|
+
astrox/celestial.py,sha256=Mh8VChM-YWGAM4Jn2fs09SxfOuV4kR56j36LmKv6MP0,10840
|
|
7
7
|
astrox/conjunction.py,sha256=sXhTNAlMai27ejuJekksaAw-850nHPimo21YdFpuzQo,9108
|
|
8
8
|
astrox/exceptions.py,sha256=apClRT1r-N3XY1ibXxkW3oxpsJgiHos4tC0cMJ8eBRk,2592
|
|
9
|
+
astrox/libration.py,sha256=YBgcZi2pTAjL5VUlE8y6jLhVbYnwRxv-PIUxJQmFBSg,13454
|
|
9
10
|
astrox/lighting.py,sha256=aWMty64rU3wv1H1Q5q-yXG5mINbIY31omyd-pjdNaVs,3876
|
|
10
11
|
astrox/orbits.py,sha256=JiSkCyWqYmeV-yX5WBL9554_JNhec1Li8EdmkZmzXQc,17823
|
|
11
12
|
astrox/propagator.py,sha256=kiW4kFSBy2_quZPZ2VqP49vfSYDKkJ1dna5rM5bo-9M,37334
|
|
12
13
|
astrox/rocket.py,sha256=tb6wXc4bZ0EMAd9bVzva_7k3I9S2lsYA7Q2RD6byqfk,2755
|
|
13
|
-
astrox/terrain.py,sha256=
|
|
14
|
+
astrox/terrain.py,sha256=2v_Ca5OnXjmMtIKNFQJ_Kbdsh42ZuIc4ZDe-0aWnpdg,6146
|
|
14
15
|
astrox/astrogator/__init__.py,sha256=rgIHos9rHjQ3i-qkpLeLRwZL6zsVthc2bOVbAdwHWHU,267
|
|
15
16
|
astrox/astrogator/_api.py,sha256=ovfBHzstXmhLj1QKIFFSpraEQb5ZRlMlAwaUCXvIiqA,1882
|
|
16
17
|
astrox/astrogator/_models.py,sha256=jIFts5jH6RjAb6NneU4WLqpbwQU2IWGX_1h3OMs8Tww,50620
|
|
@@ -32,7 +33,7 @@ astrox/coverage/number_of_assets.py,sha256=Bf1OQRu1r9lunyARyQoV4x7KetkgY_7OMNcEc
|
|
|
32
33
|
astrox/coverage/response_time.py,sha256=yLnv8eEDL6CS0PJHC1JIKBCoOachOwo3m8dNoHH3HfQ,5164
|
|
33
34
|
astrox/coverage/revisit_time.py,sha256=R5vF2egixCF9elvsGYXzpgWbD5lfcQW8uZqi1ifIDX8,5155
|
|
34
35
|
astrox/coverage/simple_coverage.py,sha256=ZPj3HrP3M59rG47pz4eHt1rq-vTlk-oZ7c2ZBmFsz1U,4992
|
|
35
|
-
astrox_python-0.
|
|
36
|
-
astrox_python-0.
|
|
37
|
-
astrox_python-0.
|
|
38
|
-
astrox_python-0.
|
|
36
|
+
astrox_python-1.0.0.dist-info/METADATA,sha256=zlaPEYsPsqHp6bnpp655SzjcopUFyKAAK-sC1-Z7r80,2520
|
|
37
|
+
astrox_python-1.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
38
|
+
astrox_python-1.0.0.dist-info/licenses/LICENSE,sha256=Fbt55lCQnv_n5yGhbG_VDlowjaV_FPHBQWsJIyuBpDk,1062
|
|
39
|
+
astrox_python-1.0.0.dist-info/RECORD,,
|
|
File without changes
|