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
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Axes component value objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, TYPE_CHECKING, TypeAlias
|
|
8
|
+
|
|
9
|
+
from ._common import (
|
|
10
|
+
_axes_type,
|
|
11
|
+
_include_axes_metadata,
|
|
12
|
+
_include_if_supplied,
|
|
13
|
+
_number_sequence_to_list,
|
|
14
|
+
_optional_string,
|
|
15
|
+
_string,
|
|
16
|
+
_typed_tuple,
|
|
17
|
+
_validate_axis_direction,
|
|
18
|
+
_validate_relative_to,
|
|
19
|
+
)
|
|
20
|
+
from ._rotations import Rotation, _rotation_to_wire
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from ._vgt import VgtVector
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _vector_reference_name(value: VgtVector | str, *, parameter: str) -> str:
|
|
27
|
+
from ._vgt import _VGT_VECTOR_TYPES
|
|
28
|
+
|
|
29
|
+
if isinstance(value, str):
|
|
30
|
+
return value
|
|
31
|
+
if isinstance(value, _VGT_VECTOR_TYPES):
|
|
32
|
+
if value.name is None:
|
|
33
|
+
raise TypeError(
|
|
34
|
+
f"{parameter} object must have a name before it can be referenced"
|
|
35
|
+
)
|
|
36
|
+
return value.name
|
|
37
|
+
raise TypeError(
|
|
38
|
+
f"{parameter} must be an astrox.components VGT vector value or string name"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, kw_only=True)
|
|
42
|
+
class VvlhAxes:
|
|
43
|
+
"""Entity attitude axes using ASTROX VVLH variants."""
|
|
44
|
+
|
|
45
|
+
relative_to: str | None = None
|
|
46
|
+
name: str | None = None
|
|
47
|
+
description: str | None = None
|
|
48
|
+
start: str | None = None
|
|
49
|
+
stop: str | None = None
|
|
50
|
+
|
|
51
|
+
def to_wire(self) -> dict[str, Any]:
|
|
52
|
+
"""Lower to an ASTROX VVLH CrdnAxes fragment."""
|
|
53
|
+
payload: dict[str, Any] = {"$type": _axes_type("VVLH", self.relative_to)}
|
|
54
|
+
_include_axes_metadata(
|
|
55
|
+
payload,
|
|
56
|
+
name=self.name,
|
|
57
|
+
description=self.description,
|
|
58
|
+
start=self.start,
|
|
59
|
+
stop=self.stop,
|
|
60
|
+
)
|
|
61
|
+
return payload
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True, kw_only=True)
|
|
65
|
+
class LvlhAxes:
|
|
66
|
+
"""Entity attitude axes using ASTROX LVLH variants."""
|
|
67
|
+
|
|
68
|
+
relative_to: str | None = None
|
|
69
|
+
name: str | None = None
|
|
70
|
+
description: str | None = None
|
|
71
|
+
start: str | None = None
|
|
72
|
+
stop: str | None = None
|
|
73
|
+
|
|
74
|
+
def to_wire(self) -> dict[str, Any]:
|
|
75
|
+
"""Lower to an ASTROX LVLH CrdnAxes fragment."""
|
|
76
|
+
payload: dict[str, Any] = {"$type": _axes_type("LVLH", self.relative_to)}
|
|
77
|
+
_include_axes_metadata(
|
|
78
|
+
payload,
|
|
79
|
+
name=self.name,
|
|
80
|
+
description=self.description,
|
|
81
|
+
start=self.start,
|
|
82
|
+
stop=self.stop,
|
|
83
|
+
)
|
|
84
|
+
return payload
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True, kw_only=True)
|
|
88
|
+
class VncAxes:
|
|
89
|
+
"""Entity attitude axes using ASTROX VNC variants."""
|
|
90
|
+
|
|
91
|
+
relative_to: str | None = None
|
|
92
|
+
name: str | None = None
|
|
93
|
+
description: str | None = None
|
|
94
|
+
start: str | None = None
|
|
95
|
+
stop: str | None = None
|
|
96
|
+
|
|
97
|
+
def to_wire(self) -> dict[str, Any]:
|
|
98
|
+
"""Lower to an ASTROX VNC CrdnAxes fragment."""
|
|
99
|
+
payload: dict[str, Any] = {"$type": _axes_type("VNC", self.relative_to)}
|
|
100
|
+
_include_axes_metadata(
|
|
101
|
+
payload,
|
|
102
|
+
name=self.name,
|
|
103
|
+
description=self.description,
|
|
104
|
+
start=self.start,
|
|
105
|
+
stop=self.stop,
|
|
106
|
+
)
|
|
107
|
+
return payload
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True, kw_only=True)
|
|
111
|
+
class FixedAxes:
|
|
112
|
+
"""Entity attitude axes fixed relative to named reference axes."""
|
|
113
|
+
|
|
114
|
+
reference_axes: EntityAxes | str
|
|
115
|
+
rotation: Rotation
|
|
116
|
+
name: str | None = None
|
|
117
|
+
description: str | None = None
|
|
118
|
+
start: str | None = None
|
|
119
|
+
stop: str | None = None
|
|
120
|
+
|
|
121
|
+
def to_wire(self) -> dict[str, Any]:
|
|
122
|
+
"""Lower to an ASTROX Fixed CrdnAxes fragment."""
|
|
123
|
+
payload: dict[str, Any] = {
|
|
124
|
+
"$type": "Fixed",
|
|
125
|
+
"FixedOrientation": _rotation_to_wire(self.rotation),
|
|
126
|
+
"ReferenceAxesName": _axes_reference_name(
|
|
127
|
+
self.reference_axes,
|
|
128
|
+
parameter="reference_axes",
|
|
129
|
+
),
|
|
130
|
+
}
|
|
131
|
+
_include_axes_metadata(
|
|
132
|
+
payload,
|
|
133
|
+
name=self.name,
|
|
134
|
+
description=self.description,
|
|
135
|
+
start=self.start,
|
|
136
|
+
stop=self.stop,
|
|
137
|
+
)
|
|
138
|
+
return payload
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True, kw_only=True)
|
|
142
|
+
class FixedAtEpochAxes:
|
|
143
|
+
"""Entity attitude axes frozen between source and reference axes at an epoch."""
|
|
144
|
+
|
|
145
|
+
source_axes: EntityAxes | str
|
|
146
|
+
reference_axes: EntityAxes | str
|
|
147
|
+
epoch: str
|
|
148
|
+
name: str | None = None
|
|
149
|
+
description: str | None = None
|
|
150
|
+
start: str | None = None
|
|
151
|
+
stop: str | None = None
|
|
152
|
+
|
|
153
|
+
def to_wire(self) -> dict[str, Any]:
|
|
154
|
+
"""Lower to an ASTROX FixedAtEpoch CrdnAxes fragment."""
|
|
155
|
+
payload: dict[str, Any] = {
|
|
156
|
+
"$type": "FixedAtEpoch",
|
|
157
|
+
"SourceAxesName": _axes_reference_name(
|
|
158
|
+
self.source_axes,
|
|
159
|
+
parameter="source_axes",
|
|
160
|
+
),
|
|
161
|
+
"ReferenceAxesName": _axes_reference_name(
|
|
162
|
+
self.reference_axes,
|
|
163
|
+
parameter="reference_axes",
|
|
164
|
+
),
|
|
165
|
+
"Epoch": self.epoch,
|
|
166
|
+
}
|
|
167
|
+
_include_axes_metadata(
|
|
168
|
+
payload,
|
|
169
|
+
name=self.name,
|
|
170
|
+
description=self.description,
|
|
171
|
+
start=self.start,
|
|
172
|
+
stop=self.stop,
|
|
173
|
+
)
|
|
174
|
+
return payload
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass(frozen=True, kw_only=True)
|
|
178
|
+
class AlignedAndConstrainedAxes:
|
|
179
|
+
"""Entity attitude axes aligned and constrained by named vectors."""
|
|
180
|
+
|
|
181
|
+
principal: VgtVector | str
|
|
182
|
+
principal_axis: str
|
|
183
|
+
reference: VgtVector | str
|
|
184
|
+
reference_axis: str
|
|
185
|
+
name: str | None = None
|
|
186
|
+
description: str | None = None
|
|
187
|
+
start: str | None = None
|
|
188
|
+
stop: str | None = None
|
|
189
|
+
|
|
190
|
+
def to_wire(self) -> dict[str, Any]:
|
|
191
|
+
"""Lower to an ASTROX AlignedAndConstrained CrdnAxes fragment."""
|
|
192
|
+
payload: dict[str, Any] = {
|
|
193
|
+
"$type": "AlignedAndConstrained",
|
|
194
|
+
"Principal": _vector_reference_name(
|
|
195
|
+
self.principal,
|
|
196
|
+
parameter="principal",
|
|
197
|
+
),
|
|
198
|
+
"PrincipalAxis": self.principal_axis,
|
|
199
|
+
"Reference": _vector_reference_name(
|
|
200
|
+
self.reference,
|
|
201
|
+
parameter="reference",
|
|
202
|
+
),
|
|
203
|
+
"ReferenceAxis": self.reference_axis,
|
|
204
|
+
}
|
|
205
|
+
_include_axes_metadata(
|
|
206
|
+
payload,
|
|
207
|
+
name=self.name,
|
|
208
|
+
description=self.description,
|
|
209
|
+
start=self.start,
|
|
210
|
+
stop=self.stop,
|
|
211
|
+
)
|
|
212
|
+
return payload
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass(frozen=True, kw_only=True)
|
|
216
|
+
class CompositeAxes:
|
|
217
|
+
"""Entity attitude axes made from multiple axes intervals."""
|
|
218
|
+
|
|
219
|
+
intervals: tuple[EntityAxes, ...]
|
|
220
|
+
name: str | None = None
|
|
221
|
+
description: str | None = None
|
|
222
|
+
start: str | None = None
|
|
223
|
+
stop: str | None = None
|
|
224
|
+
|
|
225
|
+
def to_wire(self) -> dict[str, Any]:
|
|
226
|
+
"""Lower to an ASTROX Composite CrdnAxes fragment."""
|
|
227
|
+
payload: dict[str, Any] = {
|
|
228
|
+
"$type": "Composite",
|
|
229
|
+
"Intervals": [interval.to_wire() for interval in self.intervals],
|
|
230
|
+
}
|
|
231
|
+
_include_axes_metadata(
|
|
232
|
+
payload,
|
|
233
|
+
name=self.name,
|
|
234
|
+
description=self.description,
|
|
235
|
+
start=self.start,
|
|
236
|
+
stop=self.stop,
|
|
237
|
+
)
|
|
238
|
+
return payload
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dataclass(frozen=True, kw_only=True)
|
|
242
|
+
class CzmlAxes:
|
|
243
|
+
"""Entity attitude axes from CZML unit-quaternion samples."""
|
|
244
|
+
|
|
245
|
+
epoch: str
|
|
246
|
+
unit_quaternion_xyzw: tuple[float, ...]
|
|
247
|
+
central_body: str | None = None
|
|
248
|
+
interpolation_algorithm: str | None = None
|
|
249
|
+
interpolation_degree: int | None = None
|
|
250
|
+
name: str | None = None
|
|
251
|
+
description: str | None = None
|
|
252
|
+
start: str | None = None
|
|
253
|
+
stop: str | None = None
|
|
254
|
+
|
|
255
|
+
def to_wire(self) -> dict[str, Any]:
|
|
256
|
+
"""Lower to an ASTROX CzmlOrientation CrdnAxes fragment."""
|
|
257
|
+
payload: dict[str, Any] = {
|
|
258
|
+
"$type": "CzmlOrientation",
|
|
259
|
+
"epoch": self.epoch,
|
|
260
|
+
"unitQuaternion": list(self.unit_quaternion_xyzw),
|
|
261
|
+
}
|
|
262
|
+
_include_if_supplied(payload, "CentralBody", self.central_body)
|
|
263
|
+
_include_if_supplied(
|
|
264
|
+
payload,
|
|
265
|
+
"interpolationAlgorithm",
|
|
266
|
+
self.interpolation_algorithm,
|
|
267
|
+
)
|
|
268
|
+
_include_if_supplied(payload, "interpolationDegree", self.interpolation_degree)
|
|
269
|
+
_include_axes_metadata(
|
|
270
|
+
payload,
|
|
271
|
+
name=self.name,
|
|
272
|
+
description=self.description,
|
|
273
|
+
start=self.start,
|
|
274
|
+
stop=self.stop,
|
|
275
|
+
)
|
|
276
|
+
return payload
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
EntityAxes: TypeAlias = (
|
|
280
|
+
VvlhAxes
|
|
281
|
+
| LvlhAxes
|
|
282
|
+
| VncAxes
|
|
283
|
+
| FixedAxes
|
|
284
|
+
| FixedAtEpochAxes
|
|
285
|
+
| AlignedAndConstrainedAxes
|
|
286
|
+
| CompositeAxes
|
|
287
|
+
| CzmlAxes
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
_AXES_TYPES = (
|
|
292
|
+
VvlhAxes,
|
|
293
|
+
LvlhAxes,
|
|
294
|
+
VncAxes,
|
|
295
|
+
FixedAxes,
|
|
296
|
+
FixedAtEpochAxes,
|
|
297
|
+
AlignedAndConstrainedAxes,
|
|
298
|
+
CompositeAxes,
|
|
299
|
+
CzmlAxes,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def vvlh_axes(
|
|
304
|
+
*,
|
|
305
|
+
relative_to: str | None = None,
|
|
306
|
+
name: str | None = None,
|
|
307
|
+
description: str | None = None,
|
|
308
|
+
start: str | None = None,
|
|
309
|
+
stop: str | None = None,
|
|
310
|
+
) -> VvlhAxes:
|
|
311
|
+
"""Create VVLH entity attitude axes."""
|
|
312
|
+
return VvlhAxes(
|
|
313
|
+
relative_to=_validate_relative_to(relative_to, parameter="relative_to"),
|
|
314
|
+
name=_optional_string(name, parameter="name"),
|
|
315
|
+
description=_optional_string(description, parameter="description"),
|
|
316
|
+
start=_optional_string(start, parameter="start"),
|
|
317
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def lvlh_axes(
|
|
322
|
+
*,
|
|
323
|
+
relative_to: str | None = None,
|
|
324
|
+
name: str | None = None,
|
|
325
|
+
description: str | None = None,
|
|
326
|
+
start: str | None = None,
|
|
327
|
+
stop: str | None = None,
|
|
328
|
+
) -> LvlhAxes:
|
|
329
|
+
"""Create LVLH entity attitude axes."""
|
|
330
|
+
return LvlhAxes(
|
|
331
|
+
relative_to=_validate_relative_to(relative_to, parameter="relative_to"),
|
|
332
|
+
name=_optional_string(name, parameter="name"),
|
|
333
|
+
description=_optional_string(description, parameter="description"),
|
|
334
|
+
start=_optional_string(start, parameter="start"),
|
|
335
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def vnc_axes(
|
|
340
|
+
*,
|
|
341
|
+
relative_to: str | None = None,
|
|
342
|
+
name: str | None = None,
|
|
343
|
+
description: str | None = None,
|
|
344
|
+
start: str | None = None,
|
|
345
|
+
stop: str | None = None,
|
|
346
|
+
) -> VncAxes:
|
|
347
|
+
"""Create VNC entity attitude axes."""
|
|
348
|
+
return VncAxes(
|
|
349
|
+
relative_to=_validate_relative_to(relative_to, parameter="relative_to"),
|
|
350
|
+
name=_optional_string(name, parameter="name"),
|
|
351
|
+
description=_optional_string(description, parameter="description"),
|
|
352
|
+
start=_optional_string(start, parameter="start"),
|
|
353
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def fixed_axes(
|
|
358
|
+
*,
|
|
359
|
+
reference_axes: EntityAxes | str,
|
|
360
|
+
rotation: Rotation,
|
|
361
|
+
name: str | None = None,
|
|
362
|
+
description: str | None = None,
|
|
363
|
+
start: str | None = None,
|
|
364
|
+
stop: str | None = None,
|
|
365
|
+
) -> FixedAxes:
|
|
366
|
+
"""Create entity attitude axes fixed relative to named reference axes."""
|
|
367
|
+
_axes_reference_name(reference_axes, parameter="reference_axes")
|
|
368
|
+
_rotation_to_wire(rotation)
|
|
369
|
+
return FixedAxes(
|
|
370
|
+
reference_axes=reference_axes,
|
|
371
|
+
rotation=rotation,
|
|
372
|
+
name=_optional_string(name, parameter="name"),
|
|
373
|
+
description=_optional_string(description, parameter="description"),
|
|
374
|
+
start=_optional_string(start, parameter="start"),
|
|
375
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def fixed_at_epoch_axes(
|
|
380
|
+
*,
|
|
381
|
+
source_axes: EntityAxes | str,
|
|
382
|
+
reference_axes: EntityAxes | str,
|
|
383
|
+
epoch: str,
|
|
384
|
+
name: str | None = None,
|
|
385
|
+
description: str | None = None,
|
|
386
|
+
start: str | None = None,
|
|
387
|
+
stop: str | None = None,
|
|
388
|
+
) -> FixedAtEpochAxes:
|
|
389
|
+
"""Create entity attitude axes fixed at an epoch."""
|
|
390
|
+
_axes_reference_name(source_axes, parameter="source_axes")
|
|
391
|
+
_axes_reference_name(reference_axes, parameter="reference_axes")
|
|
392
|
+
return FixedAtEpochAxes(
|
|
393
|
+
source_axes=source_axes,
|
|
394
|
+
reference_axes=reference_axes,
|
|
395
|
+
epoch=_string(epoch, parameter="epoch"),
|
|
396
|
+
name=_optional_string(name, parameter="name"),
|
|
397
|
+
description=_optional_string(description, parameter="description"),
|
|
398
|
+
start=_optional_string(start, parameter="start"),
|
|
399
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def aligned_and_constrained_axes(
|
|
404
|
+
*,
|
|
405
|
+
principal: VgtVector | str,
|
|
406
|
+
principal_axis: str,
|
|
407
|
+
reference: VgtVector | str,
|
|
408
|
+
reference_axis: str,
|
|
409
|
+
name: str | None = None,
|
|
410
|
+
description: str | None = None,
|
|
411
|
+
start: str | None = None,
|
|
412
|
+
stop: str | None = None,
|
|
413
|
+
) -> AlignedAndConstrainedAxes:
|
|
414
|
+
"""Create entity attitude axes aligned and constrained by named vectors."""
|
|
415
|
+
_vector_reference_name(principal, parameter="principal")
|
|
416
|
+
_vector_reference_name(reference, parameter="reference")
|
|
417
|
+
return AlignedAndConstrainedAxes(
|
|
418
|
+
principal=principal,
|
|
419
|
+
principal_axis=_validate_axis_direction(
|
|
420
|
+
principal_axis,
|
|
421
|
+
parameter="principal_axis",
|
|
422
|
+
),
|
|
423
|
+
reference=reference,
|
|
424
|
+
reference_axis=_validate_axis_direction(
|
|
425
|
+
reference_axis,
|
|
426
|
+
parameter="reference_axis",
|
|
427
|
+
),
|
|
428
|
+
name=_optional_string(name, parameter="name"),
|
|
429
|
+
description=_optional_string(description, parameter="description"),
|
|
430
|
+
start=_optional_string(start, parameter="start"),
|
|
431
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def composite_axes(
|
|
436
|
+
*,
|
|
437
|
+
intervals: Sequence[EntityAxes],
|
|
438
|
+
name: str | None = None,
|
|
439
|
+
description: str | None = None,
|
|
440
|
+
start: str | None = None,
|
|
441
|
+
stop: str | None = None,
|
|
442
|
+
) -> CompositeAxes:
|
|
443
|
+
"""Create composite entity attitude axes."""
|
|
444
|
+
return CompositeAxes(
|
|
445
|
+
intervals=_axes_tuple(intervals, parameter="intervals"),
|
|
446
|
+
name=_optional_string(name, parameter="name"),
|
|
447
|
+
description=_optional_string(description, parameter="description"),
|
|
448
|
+
start=_optional_string(start, parameter="start"),
|
|
449
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def czml_axes(
|
|
454
|
+
*,
|
|
455
|
+
epoch: str,
|
|
456
|
+
unit_quaternion_xyzw: Sequence[float],
|
|
457
|
+
central_body: str | None = None,
|
|
458
|
+
interpolation_algorithm: str | None = None,
|
|
459
|
+
interpolation_degree: int | None = None,
|
|
460
|
+
name: str | None = None,
|
|
461
|
+
description: str | None = None,
|
|
462
|
+
start: str | None = None,
|
|
463
|
+
stop: str | None = None,
|
|
464
|
+
) -> CzmlAxes:
|
|
465
|
+
"""Create CZML-sampled entity attitude axes."""
|
|
466
|
+
return CzmlAxes(
|
|
467
|
+
epoch=_string(epoch, parameter="epoch"),
|
|
468
|
+
unit_quaternion_xyzw=tuple(
|
|
469
|
+
_number_sequence_to_list(
|
|
470
|
+
unit_quaternion_xyzw,
|
|
471
|
+
parameter="unit_quaternion_xyzw",
|
|
472
|
+
)
|
|
473
|
+
),
|
|
474
|
+
central_body=_optional_string(central_body, parameter="central_body"),
|
|
475
|
+
interpolation_algorithm=_optional_string(
|
|
476
|
+
interpolation_algorithm,
|
|
477
|
+
parameter="interpolation_algorithm",
|
|
478
|
+
),
|
|
479
|
+
interpolation_degree=interpolation_degree,
|
|
480
|
+
name=_optional_string(name, parameter="name"),
|
|
481
|
+
description=_optional_string(description, parameter="description"),
|
|
482
|
+
start=_optional_string(start, parameter="start"),
|
|
483
|
+
stop=_optional_string(stop, parameter="stop"),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _axes_to_wire(axes: EntityAxes) -> dict[str, Any]:
|
|
488
|
+
if not isinstance(axes, _AXES_TYPES):
|
|
489
|
+
raise TypeError("axes must be an astrox.components axes value")
|
|
490
|
+
return axes.to_wire()
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _axes_reference_name(value: EntityAxes | str, *, parameter: str) -> str:
|
|
494
|
+
if isinstance(value, str):
|
|
495
|
+
return value
|
|
496
|
+
if isinstance(value, _AXES_TYPES):
|
|
497
|
+
if value.name is None:
|
|
498
|
+
raise TypeError(
|
|
499
|
+
f"{parameter} object must have a name before it can be referenced"
|
|
500
|
+
)
|
|
501
|
+
return value.name
|
|
502
|
+
raise TypeError(f"{parameter} must be an astrox.components axes value or string name")
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _axes_tuple(
|
|
506
|
+
values: Sequence[EntityAxes], *, parameter: str
|
|
507
|
+
) -> tuple[EntityAxes, ...]:
|
|
508
|
+
return _typed_tuple(values, _AXES_TYPES, parameter=parameter)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Shared helpers for ASTROX reusable component value objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from numbers import Real
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from astrox.orbits import CartesianState, KeplerianElements
|
|
10
|
+
from astrox.propagator import HpopConfig
|
|
11
|
+
|
|
12
|
+
_GROUP_RESTRICTIONS = {"AnyOf", "AtLeastN"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_RELATIVE_TO_VALUES = {"Earth", "Moon", "Mars", "Sun", "CBF"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_AXIS_DIRECTIONS = {"+X", "-X", "+Y", "-Y", "+Z", "-Z"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _include_if_supplied(payload: dict[str, Any], wire_key: str, value: Any) -> None:
|
|
22
|
+
if value is not None:
|
|
23
|
+
payload[wire_key] = value
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _real_number(value: float, *, parameter: str) -> float:
|
|
27
|
+
if not isinstance(value, Real) or isinstance(value, bool):
|
|
28
|
+
raise TypeError(f"{parameter} must be a number")
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _string(value: str, *, parameter: str) -> str:
|
|
33
|
+
if not isinstance(value, str):
|
|
34
|
+
raise TypeError(f"{parameter} must be a string")
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _optional_string(value: str | None, *, parameter: str) -> str | None:
|
|
39
|
+
if value is None:
|
|
40
|
+
return None
|
|
41
|
+
return _string(value, parameter=parameter)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _number_sequence_to_list(value: Sequence[float], *, parameter: str) -> list[float]:
|
|
45
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
46
|
+
raise TypeError(f"{parameter} must be a sequence of numbers")
|
|
47
|
+
items = list(value)
|
|
48
|
+
if not all(isinstance(item, Real) and not isinstance(item, bool) for item in items):
|
|
49
|
+
raise TypeError(f"{parameter} must be a sequence of numbers")
|
|
50
|
+
return items
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _tle_lines_to_list(value: tuple[str, str] | list[str]) -> list[str]:
|
|
54
|
+
if (
|
|
55
|
+
not isinstance(value, (list, tuple))
|
|
56
|
+
or len(value) != 2
|
|
57
|
+
or not all(isinstance(line, str) for line in value)
|
|
58
|
+
):
|
|
59
|
+
raise TypeError("tle_lines must be a two-item sequence of TLE strings")
|
|
60
|
+
return list(value)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _orbit_elements_to_wire(orbit: KeplerianElements, *, parameter: str) -> list[float]:
|
|
64
|
+
if not isinstance(orbit, KeplerianElements):
|
|
65
|
+
raise TypeError(f"{parameter} must be a KeplerianElements instance")
|
|
66
|
+
return orbit.to_wire()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _cartesian_state_to_wire(state: CartesianState, *, parameter: str) -> list[float]:
|
|
70
|
+
if not isinstance(state, CartesianState):
|
|
71
|
+
raise TypeError(f"{parameter} must be a CartesianState instance")
|
|
72
|
+
return state.to_wire()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _hpop_config_to_wire(
|
|
76
|
+
config: HpopConfig | Mapping[str, Any],
|
|
77
|
+
*,
|
|
78
|
+
parameter: str,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
if isinstance(config, HpopConfig):
|
|
81
|
+
return config.to_wire()
|
|
82
|
+
if isinstance(config, Mapping):
|
|
83
|
+
return dict(config)
|
|
84
|
+
raise TypeError(f"{parameter} must be an HpopConfig value or mapping fragment")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _validate_group_restriction(value: str | None, *, parameter: str) -> str | None:
|
|
88
|
+
if value is None:
|
|
89
|
+
return None
|
|
90
|
+
if value not in _GROUP_RESTRICTIONS:
|
|
91
|
+
accepted = ", ".join(sorted(_GROUP_RESTRICTIONS))
|
|
92
|
+
raise ValueError(f"{parameter} must be one of: {accepted}")
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _validate_relative_to(value: str | None, *, parameter: str) -> str | None:
|
|
97
|
+
if value is None:
|
|
98
|
+
return None
|
|
99
|
+
if value not in _RELATIVE_TO_VALUES:
|
|
100
|
+
accepted = ", ".join(sorted(_RELATIVE_TO_VALUES))
|
|
101
|
+
raise ValueError(f"{parameter} must be one of: {accepted}")
|
|
102
|
+
return value
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _validate_axis_direction(value: str, *, parameter: str) -> str:
|
|
106
|
+
if value not in _AXIS_DIRECTIONS:
|
|
107
|
+
accepted = ", ".join(sorted(_AXIS_DIRECTIONS))
|
|
108
|
+
raise ValueError(f"{parameter} must be one of: {accepted}")
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _axes_type(family: str, relative_to: str | None) -> str:
|
|
113
|
+
return family if relative_to is None else f"{family}({relative_to})"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _include_axes_metadata(
|
|
117
|
+
payload: dict[str, Any],
|
|
118
|
+
*,
|
|
119
|
+
name: str | None,
|
|
120
|
+
description: str | None,
|
|
121
|
+
start: str | None,
|
|
122
|
+
stop: str | None,
|
|
123
|
+
) -> None:
|
|
124
|
+
_include_if_supplied(payload, "Name", name)
|
|
125
|
+
_include_if_supplied(payload, "Description", description)
|
|
126
|
+
_include_if_supplied(payload, "Start", start)
|
|
127
|
+
_include_if_supplied(payload, "Stop", stop)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _typed_tuple(
|
|
131
|
+
values: Sequence[Any],
|
|
132
|
+
accepted_types: tuple[type[Any], ...],
|
|
133
|
+
*,
|
|
134
|
+
parameter: str,
|
|
135
|
+
) -> tuple[Any, ...]:
|
|
136
|
+
if isinstance(values, (str, bytes)) or not isinstance(values, Sequence):
|
|
137
|
+
raise TypeError(f"{parameter} must be a sequence")
|
|
138
|
+
items = tuple(values)
|
|
139
|
+
if not all(isinstance(item, accepted_types) for item in items):
|
|
140
|
+
raise TypeError(f"{parameter} contains unsupported item values")
|
|
141
|
+
return items
|