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.
@@ -0,0 +1,673 @@
1
+ """Position-source component value objects."""
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.orbits import CartesianState, KeplerianElements
10
+ from astrox.propagator import HpopConfig
11
+
12
+ from ._common import (
13
+ _cartesian_state_to_wire,
14
+ _hpop_config_to_wire,
15
+ _include_if_supplied,
16
+ _number_sequence_to_list,
17
+ _orbit_elements_to_wire,
18
+ _tle_lines_to_list,
19
+ )
20
+
21
+ @dataclass(frozen=True, kw_only=True)
22
+ class SitePosition:
23
+ """Fixed geodetic site position."""
24
+
25
+ longitude_deg: float
26
+ latitude_deg: float
27
+ height_m: float
28
+ central_body: str | None = None
29
+ clamp_to_ground: bool | None = None
30
+ height_above_ground_m: float | None = None
31
+
32
+ def to_wire(self) -> dict[str, Any]:
33
+ """Lower to a typed ASTROX generic entity-position fragment."""
34
+ return {"$type": "SitePosition", **self.to_site_wire()}
35
+
36
+ def to_site_wire(self) -> dict[str, Any]:
37
+ """Lower to ASTROX site-only position shape."""
38
+ payload: dict[str, Any] = {
39
+ "cartographicDegrees": [
40
+ self.longitude_deg,
41
+ self.latitude_deg,
42
+ self.height_m,
43
+ ],
44
+ }
45
+ _include_if_supplied(payload, "CentralBody", self.central_body)
46
+ _include_if_supplied(payload, "clampToGround", self.clamp_to_ground)
47
+ _include_if_supplied(payload, "HeightAboveGround", self.height_above_ground_m)
48
+ return payload
49
+
50
+
51
+ @dataclass(frozen=True, kw_only=True)
52
+ class CzmlPosition:
53
+ """CZML-like sampled position source."""
54
+
55
+ epoch: str
56
+ central_body: str | None = None
57
+ interpolation_algorithm: str | None = None
58
+ interpolation_degree: int | None = None
59
+ reference_frame: str | None = None
60
+ interval: str | None = None
61
+ cartesian: tuple[float, ...] | None = None
62
+ cartesian_velocity: tuple[float, ...] | None = None
63
+
64
+ def to_wire(self) -> dict[str, Any]:
65
+ """Lower to a typed ASTROX generic entity-position fragment."""
66
+ return {"$type": "CzmlPosition", **self.to_czml_wire()}
67
+
68
+ def to_czml_wire(self) -> dict[str, Any]:
69
+ """Lower to an ASTROX CZML position-data fragment."""
70
+ payload: dict[str, Any] = {"epoch": self.epoch}
71
+ _include_if_supplied(payload, "CentralBody", self.central_body)
72
+ _include_if_supplied(
73
+ payload,
74
+ "interpolationAlgorithm",
75
+ self.interpolation_algorithm,
76
+ )
77
+ _include_if_supplied(payload, "interpolationDegree", self.interpolation_degree)
78
+ _include_if_supplied(payload, "referenceFrame", self.reference_frame)
79
+ _include_if_supplied(payload, "interval", self.interval)
80
+ if self.cartesian is not None:
81
+ payload["cartesian"] = list(self.cartesian)
82
+ if self.cartesian_velocity is not None:
83
+ payload["cartesianVelocity"] = list(self.cartesian_velocity)
84
+ return payload
85
+
86
+ @classmethod
87
+ def from_czml_wire(cls, payload: dict[str, Any]) -> CzmlPosition:
88
+ """Build from an ASTROX CZML position-data payload."""
89
+ cartesian = payload.get("cartesian")
90
+ cartesian_velocity = payload.get("cartesianVelocity")
91
+ return cls(
92
+ epoch=payload["epoch"],
93
+ central_body=payload.get("CentralBody"),
94
+ interpolation_algorithm=payload.get("interpolationAlgorithm"),
95
+ interpolation_degree=payload.get("interpolationDegree"),
96
+ reference_frame=payload.get("referenceFrame"),
97
+ interval=payload.get("interval"),
98
+ cartesian=None if cartesian is None else tuple(cartesian),
99
+ cartesian_velocity=None
100
+ if cartesian_velocity is None
101
+ else tuple(cartesian_velocity),
102
+ )
103
+
104
+
105
+ @dataclass(frozen=True, kw_only=True)
106
+ class CzmlPositionSTM(CzmlPosition):
107
+ """CZML position sample augmented with STM-like orientation and translation."""
108
+
109
+ unit_quaternion: tuple[float, ...]
110
+ cartesian_translation: tuple[float, ...] | None = None
111
+
112
+ @classmethod
113
+ def from_czml_wire(cls, payload: dict[str, Any]) -> CzmlPositionSTM:
114
+ """Build from the ASTROX CzmlPositionSTM payload."""
115
+ cartesian_velocity = payload.get("cartesianVelocity")
116
+ cartesian_translation = payload.get("cartesianTranslation")
117
+ return cls(
118
+ epoch=payload["epoch"],
119
+ central_body=payload.get("CentralBody"),
120
+ interpolation_algorithm=payload.get("interpolationAlgorithm"),
121
+ interpolation_degree=payload.get("interpolationDegree"),
122
+ reference_frame=payload["referenceFrame"],
123
+ interval=payload.get("interval"),
124
+ cartesian=tuple(payload["cartesian"]),
125
+ cartesian_velocity=None
126
+ if cartesian_velocity is None
127
+ else tuple(cartesian_velocity),
128
+ unit_quaternion=tuple(payload["unitQuaternion"]),
129
+ cartesian_translation=None
130
+ if cartesian_translation is None
131
+ else tuple(cartesian_translation),
132
+ )
133
+
134
+
135
+ @dataclass(frozen=True, kw_only=True)
136
+ class CzmlPositions:
137
+ """Composite CZML-like sampled position source."""
138
+
139
+ positions: tuple[CzmlPosition, ...]
140
+ central_body: str | None = None
141
+
142
+ def to_wire(self) -> dict[str, Any]:
143
+ """Lower to a typed ASTROX generic entity-position fragment."""
144
+ payload: dict[str, Any] = {
145
+ "$type": "CzmlPositions",
146
+ "CzmlPositions": [position.to_czml_wire() for position in self.positions],
147
+ }
148
+ _include_if_supplied(payload, "CentralBody", self.central_body)
149
+ return payload
150
+
151
+
152
+ @dataclass(frozen=True, kw_only=True)
153
+ class CentralBodyPosition:
154
+ """Central-body position source."""
155
+
156
+ name: str
157
+
158
+ def to_wire(self) -> dict[str, Any]:
159
+ """Lower to a typed ASTROX generic entity-position fragment."""
160
+ return {"$type": "CentralBody", "Name": self.name}
161
+
162
+
163
+ @dataclass(frozen=True, kw_only=True)
164
+ class J2Position:
165
+ """J2-propagated Keplerian position source."""
166
+
167
+ orbit_epoch: str
168
+ orbit: KeplerianElements
169
+ start: str | None = None
170
+ stop: str | None = None
171
+ step_s: float | None = None
172
+ central_body: str | None = None
173
+ gravitational_parameter_m3_s2: float | None = None
174
+ coord_system: str | None = None
175
+ j2_normalized_value: float | None = None
176
+ ref_distance_m: float | None = None
177
+
178
+ def to_wire(self) -> dict[str, Any]:
179
+ """Lower to a typed ASTROX generic entity-position fragment."""
180
+ payload: dict[str, Any] = {
181
+ "$type": "J2",
182
+ "OrbitEpoch": self.orbit_epoch,
183
+ "CoordType": "Classical",
184
+ "OrbitalElements": _orbit_elements_to_wire(self.orbit, parameter="orbit"),
185
+ }
186
+ _include_if_supplied(payload, "Start", self.start)
187
+ _include_if_supplied(payload, "Stop", self.stop)
188
+ _include_if_supplied(payload, "Step", self.step_s)
189
+ _include_if_supplied(payload, "CentralBody", self.central_body)
190
+ _include_if_supplied(
191
+ payload,
192
+ "GravitationalParameter",
193
+ self.gravitational_parameter_m3_s2,
194
+ )
195
+ _include_if_supplied(payload, "CoordSystem", self.coord_system)
196
+ _include_if_supplied(payload, "J2NormalizedValue", self.j2_normalized_value)
197
+ _include_if_supplied(payload, "RefDistance", self.ref_distance_m)
198
+ return payload
199
+
200
+
201
+ @dataclass(frozen=True, kw_only=True)
202
+ class TwoBodyPosition:
203
+ """Two-body propagated Keplerian position source."""
204
+
205
+ orbit_epoch: str
206
+ orbit: KeplerianElements
207
+ start: str | None = None
208
+ stop: str | None = None
209
+ step_s: float | None = None
210
+ central_body: str | None = None
211
+ gravitational_parameter_m3_s2: float | None = None
212
+ coord_system: str | None = None
213
+
214
+ def to_wire(self) -> dict[str, Any]:
215
+ """Lower to a typed ASTROX generic entity-position fragment."""
216
+ payload: dict[str, Any] = {
217
+ "$type": "TwoBody",
218
+ "OrbitEpoch": self.orbit_epoch,
219
+ "CoordType": "Classical",
220
+ "OrbitalElements": _orbit_elements_to_wire(self.orbit, parameter="orbit"),
221
+ }
222
+ _include_if_supplied(payload, "Start", self.start)
223
+ _include_if_supplied(payload, "Stop", self.stop)
224
+ _include_if_supplied(payload, "Step", self.step_s)
225
+ _include_if_supplied(payload, "CentralBody", self.central_body)
226
+ _include_if_supplied(
227
+ payload,
228
+ "GravitationalParameter",
229
+ self.gravitational_parameter_m3_s2,
230
+ )
231
+ _include_if_supplied(payload, "CoordSystem", self.coord_system)
232
+ return payload
233
+
234
+
235
+ @dataclass(frozen=True, kw_only=True)
236
+ class Sgp4Position:
237
+ """SGP4 TLE position source."""
238
+
239
+ tle_lines: tuple[str, str]
240
+ start: str | None = None
241
+ stop: str | None = None
242
+ step_s: float | None = None
243
+ satellite_number: str | None = None
244
+
245
+ def to_wire(self) -> dict[str, Any]:
246
+ """Lower to a typed ASTROX generic entity-position fragment."""
247
+ payload: dict[str, Any] = {
248
+ "$type": "SGP4",
249
+ "TLEs": list(self.tle_lines),
250
+ }
251
+ _include_if_supplied(payload, "Start", self.start)
252
+ _include_if_supplied(payload, "Stop", self.stop)
253
+ _include_if_supplied(payload, "Step", self.step_s)
254
+ _include_if_supplied(payload, "SatelliteNumber", self.satellite_number)
255
+ return payload
256
+
257
+
258
+ @dataclass(frozen=True, kw_only=True)
259
+ class HpopPosition:
260
+ """HPOP-propagated position source."""
261
+
262
+ start: str
263
+ stop: str
264
+ orbit_epoch: str
265
+ orbital_elements: tuple[float, ...]
266
+ coord_type: str
267
+ config: HpopConfig | Mapping[str, Any] | None = None
268
+ coord_epoch: str | None = None
269
+ coord_system: str | None = None
270
+ gravitational_parameter_m3_s2: float | None = None
271
+ coefficient_of_drag: float | None = None
272
+ area_mass_ratio_drag_m2_kg: float | None = None
273
+ coefficient_of_srp: float | None = None
274
+ area_mass_ratio_srp_m2_kg: float | None = None
275
+
276
+ def to_wire(self) -> dict[str, Any]:
277
+ """Lower to a typed ASTROX generic entity-position fragment."""
278
+ payload: dict[str, Any] = {
279
+ "$type": "HPOP",
280
+ "Start": self.start,
281
+ "Stop": self.stop,
282
+ "OrbitEpoch": self.orbit_epoch,
283
+ "CoordType": self.coord_type,
284
+ "OrbitalElements": list(self.orbital_elements),
285
+ }
286
+ _include_if_supplied(payload, "CoordEpoch", self.coord_epoch)
287
+ _include_if_supplied(payload, "CoordSystem", self.coord_system)
288
+ _include_if_supplied(
289
+ payload,
290
+ "GravitationalParameter",
291
+ self.gravitational_parameter_m3_s2,
292
+ )
293
+ _include_if_supplied(payload, "CoefficientOfDrag", self.coefficient_of_drag)
294
+ _include_if_supplied(
295
+ payload,
296
+ "AreaMassRatioDrag",
297
+ self.area_mass_ratio_drag_m2_kg,
298
+ )
299
+ _include_if_supplied(payload, "CoefficientOfSRP", self.coefficient_of_srp)
300
+ _include_if_supplied(
301
+ payload,
302
+ "AreaMassRatioSRP",
303
+ self.area_mass_ratio_srp_m2_kg,
304
+ )
305
+ if self.config is not None:
306
+ payload["HpopPropagator"] = _hpop_config_to_wire(
307
+ self.config,
308
+ parameter="config",
309
+ )
310
+ return payload
311
+
312
+
313
+ @dataclass(frozen=True, kw_only=True)
314
+ class SimpleAscentPosition:
315
+ """Simple ascent position source."""
316
+
317
+ start: str
318
+ stop: str
319
+ launch_latitude_deg: float
320
+ launch_longitude_deg: float
321
+ launch_altitude_m: float
322
+ burnout_velocity_m_s: float
323
+ burnout_latitude_deg: float
324
+ burnout_longitude_deg: float
325
+ burnout_altitude_m: float
326
+ step_s: float | None = None
327
+ central_body: str | None = None
328
+
329
+ def to_wire(self) -> dict[str, Any]:
330
+ """Lower to a typed ASTROX generic entity-position fragment."""
331
+ payload: dict[str, Any] = {
332
+ "$type": "SimpleAscent",
333
+ "Start": self.start,
334
+ "Stop": self.stop,
335
+ "LaunchLatitude": self.launch_latitude_deg,
336
+ "LaunchLongitude": self.launch_longitude_deg,
337
+ "LaunchAltitude": self.launch_altitude_m,
338
+ "BurnoutVelocity": self.burnout_velocity_m_s,
339
+ "BurnoutLatitude": self.burnout_latitude_deg,
340
+ "BurnoutLongitude": self.burnout_longitude_deg,
341
+ "BurnoutAltitude": self.burnout_altitude_m,
342
+ }
343
+ _include_if_supplied(payload, "Step", self.step_s)
344
+ _include_if_supplied(payload, "CentralBody", self.central_body)
345
+ return payload
346
+
347
+
348
+ @dataclass(frozen=True, kw_only=True)
349
+ class BallisticPosition:
350
+ """Ballistic flight position source."""
351
+
352
+ start: str
353
+ ballistic_type: str
354
+ ballistic_type_value: float
355
+ step_s: float | None = None
356
+ central_body: str | None = None
357
+ gravitational_parameter_m3_s2: float | None = None
358
+ launch_latitude_deg: float | None = None
359
+ launch_longitude_deg: float | None = None
360
+ launch_altitude_m: float | None = None
361
+ impact_latitude_deg: float | None = None
362
+ impact_longitude_deg: float | None = None
363
+ impact_altitude_m: float | None = None
364
+
365
+ def to_wire(self) -> dict[str, Any]:
366
+ """Lower to a typed ASTROX generic entity-position fragment."""
367
+ payload: dict[str, Any] = {
368
+ "$type": "Ballistic",
369
+ "Start": self.start,
370
+ "BallisticType": self.ballistic_type,
371
+ "BallisticTypeValue": self.ballistic_type_value,
372
+ }
373
+ _include_if_supplied(payload, "Step", self.step_s)
374
+ _include_if_supplied(payload, "CentralBody", self.central_body)
375
+ _include_if_supplied(
376
+ payload,
377
+ "GravitationalParameter",
378
+ self.gravitational_parameter_m3_s2,
379
+ )
380
+ _include_if_supplied(payload, "LaunchLatitude", self.launch_latitude_deg)
381
+ _include_if_supplied(payload, "LaunchLongitude", self.launch_longitude_deg)
382
+ _include_if_supplied(payload, "LaunchAltitude", self.launch_altitude_m)
383
+ _include_if_supplied(payload, "ImpactLatitude", self.impact_latitude_deg)
384
+ _include_if_supplied(payload, "ImpactLongitude", self.impact_longitude_deg)
385
+ _include_if_supplied(payload, "ImpactAltitude", self.impact_altitude_m)
386
+ return payload
387
+
388
+
389
+ EntityPosition: TypeAlias = (
390
+ SitePosition
391
+ | CzmlPosition
392
+ | CzmlPositions
393
+ | CentralBodyPosition
394
+ | J2Position
395
+ | TwoBodyPosition
396
+ | Sgp4Position
397
+ | HpopPosition
398
+ | SimpleAscentPosition
399
+ | BallisticPosition
400
+ )
401
+
402
+
403
+ _POSITION_TYPES = (
404
+ SitePosition,
405
+ CzmlPosition,
406
+ CzmlPositions,
407
+ CentralBodyPosition,
408
+ J2Position,
409
+ TwoBodyPosition,
410
+ Sgp4Position,
411
+ HpopPosition,
412
+ SimpleAscentPosition,
413
+ BallisticPosition,
414
+ )
415
+
416
+
417
+ def site_position(
418
+ *,
419
+ longitude_deg: float,
420
+ latitude_deg: float,
421
+ height_m: float,
422
+ central_body: str | None = None,
423
+ clamp_to_ground: bool | None = None,
424
+ height_above_ground_m: float | None = None,
425
+ ) -> SitePosition:
426
+ """Create a fixed geodetic site position."""
427
+ return SitePosition(
428
+ longitude_deg=longitude_deg,
429
+ latitude_deg=latitude_deg,
430
+ height_m=height_m,
431
+ central_body=central_body,
432
+ clamp_to_ground=clamp_to_ground,
433
+ height_above_ground_m=height_above_ground_m,
434
+ )
435
+
436
+
437
+ def czml_position(
438
+ *,
439
+ epoch: str,
440
+ central_body: str | None = None,
441
+ interpolation_algorithm: str | None = None,
442
+ interpolation_degree: int | None = None,
443
+ reference_frame: str | None = None,
444
+ interval: str | None = None,
445
+ cartesian: Sequence[float] | None = None,
446
+ cartesian_velocity: Sequence[float] | None = None,
447
+ ) -> CzmlPosition:
448
+ """Create a CZML-like sampled position source."""
449
+ return CzmlPosition(
450
+ epoch=epoch,
451
+ central_body=central_body,
452
+ interpolation_algorithm=interpolation_algorithm,
453
+ interpolation_degree=interpolation_degree,
454
+ reference_frame=reference_frame,
455
+ interval=interval,
456
+ cartesian=tuple(_number_sequence_to_list(cartesian, parameter="cartesian"))
457
+ if cartesian is not None
458
+ else None,
459
+ cartesian_velocity=tuple(
460
+ _number_sequence_to_list(cartesian_velocity, parameter="cartesian_velocity")
461
+ )
462
+ if cartesian_velocity is not None
463
+ else None,
464
+ )
465
+
466
+
467
+ def czml_positions(
468
+ positions: Sequence[CzmlPosition],
469
+ *,
470
+ central_body: str | None = None,
471
+ ) -> CzmlPositions:
472
+ """Create a composite CZML-like sampled position source."""
473
+ if isinstance(positions, (str, bytes)) or not isinstance(positions, Sequence):
474
+ raise TypeError("positions must be a sequence of CzmlPosition values")
475
+ items = tuple(positions)
476
+ if not all(isinstance(position, CzmlPosition) for position in items):
477
+ raise TypeError("positions must be a sequence of CzmlPosition values")
478
+ return CzmlPositions(positions=items, central_body=central_body)
479
+
480
+
481
+ def central_body_position(name: str) -> CentralBodyPosition:
482
+ """Create a central-body position source."""
483
+ return CentralBodyPosition(name=name)
484
+
485
+
486
+ def j2_position(
487
+ *,
488
+ orbit_epoch: str,
489
+ orbit: KeplerianElements,
490
+ start: str | None = None,
491
+ stop: str | None = None,
492
+ step_s: float | None = None,
493
+ central_body: str | None = None,
494
+ gravitational_parameter_m3_s2: float | None = None,
495
+ coord_system: str | None = None,
496
+ j2_normalized_value: float | None = None,
497
+ ref_distance_m: float | None = None,
498
+ ) -> J2Position:
499
+ """Create a J2-propagated Keplerian position source."""
500
+ _orbit_elements_to_wire(orbit, parameter="orbit")
501
+ return J2Position(
502
+ orbit_epoch=orbit_epoch,
503
+ orbit=orbit,
504
+ start=start,
505
+ stop=stop,
506
+ step_s=step_s,
507
+ central_body=central_body,
508
+ gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
509
+ coord_system=coord_system,
510
+ j2_normalized_value=j2_normalized_value,
511
+ ref_distance_m=ref_distance_m,
512
+ )
513
+
514
+
515
+ def two_body_position(
516
+ *,
517
+ orbit_epoch: str,
518
+ orbit: KeplerianElements,
519
+ start: str | None = None,
520
+ stop: str | None = None,
521
+ step_s: float | None = None,
522
+ central_body: str | None = None,
523
+ gravitational_parameter_m3_s2: float | None = None,
524
+ coord_system: str | None = None,
525
+ ) -> TwoBodyPosition:
526
+ """Create a two-body propagated Keplerian position source."""
527
+ _orbit_elements_to_wire(orbit, parameter="orbit")
528
+ return TwoBodyPosition(
529
+ orbit_epoch=orbit_epoch,
530
+ orbit=orbit,
531
+ start=start,
532
+ stop=stop,
533
+ step_s=step_s,
534
+ central_body=central_body,
535
+ gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
536
+ coord_system=coord_system,
537
+ )
538
+
539
+
540
+ def sgp4_position(
541
+ *,
542
+ tle_lines: tuple[str, str] | list[str],
543
+ start: str | None = None,
544
+ stop: str | None = None,
545
+ step_s: float | None = None,
546
+ satellite_number: str | None = None,
547
+ ) -> Sgp4Position:
548
+ """Create an SGP4 TLE position source."""
549
+ return Sgp4Position(
550
+ tle_lines=tuple(_tle_lines_to_list(tle_lines)),
551
+ start=start,
552
+ stop=stop,
553
+ step_s=step_s,
554
+ satellite_number=satellite_number,
555
+ )
556
+
557
+
558
+ def hpop_position(
559
+ *,
560
+ start: str,
561
+ stop: str,
562
+ orbit_epoch: str,
563
+ orbit: KeplerianElements | None = None,
564
+ state: CartesianState | None = None,
565
+ config: HpopConfig | Mapping[str, Any] | None = None,
566
+ coord_epoch: str | None = None,
567
+ coord_system: str | None = None,
568
+ gravitational_parameter_m3_s2: float | None = None,
569
+ coefficient_of_drag: float | None = None,
570
+ area_mass_ratio_drag_m2_kg: float | None = None,
571
+ coefficient_of_srp: float | None = None,
572
+ area_mass_ratio_srp_m2_kg: float | None = None,
573
+ ) -> HpopPosition:
574
+ """Create an HPOP-propagated position source."""
575
+ if (orbit is None) == (state is None):
576
+ raise ValueError("exactly one of orbit or state must be provided")
577
+ if orbit is not None:
578
+ coord_type = "Classical"
579
+ orbital_elements = tuple(_orbit_elements_to_wire(orbit, parameter="orbit"))
580
+ else:
581
+ coord_type = "Cartesian"
582
+ orbital_elements = tuple(_cartesian_state_to_wire(state, parameter="state"))
583
+ if config is not None:
584
+ _hpop_config_to_wire(config, parameter="config")
585
+ return HpopPosition(
586
+ start=start,
587
+ stop=stop,
588
+ orbit_epoch=orbit_epoch,
589
+ orbital_elements=orbital_elements,
590
+ coord_type=coord_type,
591
+ config=config,
592
+ coord_epoch=coord_epoch,
593
+ coord_system=coord_system,
594
+ gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
595
+ coefficient_of_drag=coefficient_of_drag,
596
+ area_mass_ratio_drag_m2_kg=area_mass_ratio_drag_m2_kg,
597
+ coefficient_of_srp=coefficient_of_srp,
598
+ area_mass_ratio_srp_m2_kg=area_mass_ratio_srp_m2_kg,
599
+ )
600
+
601
+
602
+ def simple_ascent_position(
603
+ *,
604
+ start: str,
605
+ stop: str,
606
+ launch_latitude_deg: float,
607
+ launch_longitude_deg: float,
608
+ launch_altitude_m: float,
609
+ burnout_velocity_m_s: float,
610
+ burnout_latitude_deg: float,
611
+ burnout_longitude_deg: float,
612
+ burnout_altitude_m: float,
613
+ step_s: float | None = None,
614
+ central_body: str | None = None,
615
+ ) -> SimpleAscentPosition:
616
+ """Create a simple ascent position source."""
617
+ return SimpleAscentPosition(
618
+ start=start,
619
+ stop=stop,
620
+ launch_latitude_deg=launch_latitude_deg,
621
+ launch_longitude_deg=launch_longitude_deg,
622
+ launch_altitude_m=launch_altitude_m,
623
+ burnout_velocity_m_s=burnout_velocity_m_s,
624
+ burnout_latitude_deg=burnout_latitude_deg,
625
+ burnout_longitude_deg=burnout_longitude_deg,
626
+ burnout_altitude_m=burnout_altitude_m,
627
+ step_s=step_s,
628
+ central_body=central_body,
629
+ )
630
+
631
+
632
+ def ballistic_position(
633
+ *,
634
+ start: str,
635
+ ballistic_type: str,
636
+ ballistic_type_value: float,
637
+ step_s: float | None = None,
638
+ central_body: str | None = None,
639
+ gravitational_parameter_m3_s2: float | None = None,
640
+ launch_latitude_deg: float | None = None,
641
+ launch_longitude_deg: float | None = None,
642
+ launch_altitude_m: float | None = None,
643
+ impact_latitude_deg: float | None = None,
644
+ impact_longitude_deg: float | None = None,
645
+ impact_altitude_m: float | None = None,
646
+ ) -> BallisticPosition:
647
+ """Create a ballistic flight position source."""
648
+ return BallisticPosition(
649
+ start=start,
650
+ ballistic_type=ballistic_type,
651
+ ballistic_type_value=ballistic_type_value,
652
+ step_s=step_s,
653
+ central_body=central_body,
654
+ gravitational_parameter_m3_s2=gravitational_parameter_m3_s2,
655
+ launch_latitude_deg=launch_latitude_deg,
656
+ launch_longitude_deg=launch_longitude_deg,
657
+ launch_altitude_m=launch_altitude_m,
658
+ impact_latitude_deg=impact_latitude_deg,
659
+ impact_longitude_deg=impact_longitude_deg,
660
+ impact_altitude_m=impact_altitude_m,
661
+ )
662
+
663
+
664
+ def _position_to_wire(position: EntityPosition) -> dict[str, Any]:
665
+ if not isinstance(position, _POSITION_TYPES):
666
+ raise TypeError("position must be an astrox.components position value")
667
+ return position.to_wire()
668
+
669
+
670
+ def _site_position_to_wire(position: SitePosition) -> dict[str, Any]:
671
+ if not isinstance(position, SitePosition):
672
+ raise TypeError("site_position must be an astrox.components.SitePosition value")
673
+ return position.to_site_wire()