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,152 @@
|
|
|
1
|
+
"""Simple-coverage FOM routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from astrox import components
|
|
9
|
+
|
|
10
|
+
from ._core import CoverageGrid
|
|
11
|
+
from ._fom import post_coverage_input, post_time_value_by_grid_point_input
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"by_grid_point",
|
|
15
|
+
"by_grid_point_at_time",
|
|
16
|
+
"grid_stats",
|
|
17
|
+
"grid_stats_over_time",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def by_grid_point(
|
|
22
|
+
*,
|
|
23
|
+
start: str,
|
|
24
|
+
stop: str,
|
|
25
|
+
grid: CoverageGrid,
|
|
26
|
+
assets: Sequence[components.Entity],
|
|
27
|
+
minimum_assets: int | None = None,
|
|
28
|
+
exactly_assets: int | None = None,
|
|
29
|
+
grid_point_sensor: components.EntitySensor | None = None,
|
|
30
|
+
grid_point_constraints: Sequence[components.Constraint] | None = None,
|
|
31
|
+
include_asset_access_results: bool | None = None,
|
|
32
|
+
include_coverage_points: bool | None = None,
|
|
33
|
+
step_s: float | None = None,
|
|
34
|
+
description: str | None = None,
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
"""Compute simple-coverage FOM values for each grid point."""
|
|
37
|
+
return post_coverage_input(
|
|
38
|
+
"/Coverage/FOM/ValueByGridPoint/SimpleCoverage",
|
|
39
|
+
start=start,
|
|
40
|
+
stop=stop,
|
|
41
|
+
grid=grid,
|
|
42
|
+
assets=assets,
|
|
43
|
+
minimum_assets=minimum_assets,
|
|
44
|
+
exactly_assets=exactly_assets,
|
|
45
|
+
grid_point_sensor=grid_point_sensor,
|
|
46
|
+
grid_point_constraints=grid_point_constraints,
|
|
47
|
+
include_asset_access_results=include_asset_access_results,
|
|
48
|
+
include_coverage_points=include_coverage_points,
|
|
49
|
+
step_s=step_s,
|
|
50
|
+
description=description,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def by_grid_point_at_time(
|
|
55
|
+
*,
|
|
56
|
+
time: str,
|
|
57
|
+
start: str,
|
|
58
|
+
stop: str,
|
|
59
|
+
grid: CoverageGrid,
|
|
60
|
+
assets: Sequence[components.Entity],
|
|
61
|
+
minimum_assets: int | None = None,
|
|
62
|
+
exactly_assets: int | None = None,
|
|
63
|
+
grid_point_sensor: components.EntitySensor | None = None,
|
|
64
|
+
grid_point_constraints: Sequence[components.Constraint] | None = None,
|
|
65
|
+
include_asset_access_results: bool | None = None,
|
|
66
|
+
include_coverage_points: bool | None = None,
|
|
67
|
+
step_s: float | None = None,
|
|
68
|
+
description: str | None = None,
|
|
69
|
+
) -> dict[str, Any]:
|
|
70
|
+
"""Compute simple-coverage FOM values for each grid point at one time."""
|
|
71
|
+
return post_time_value_by_grid_point_input(
|
|
72
|
+
"/Coverage/FOM/ValueByGridPointAtTime/SimpleCoverage",
|
|
73
|
+
time=time,
|
|
74
|
+
start=start,
|
|
75
|
+
stop=stop,
|
|
76
|
+
grid=grid,
|
|
77
|
+
assets=assets,
|
|
78
|
+
minimum_assets=minimum_assets,
|
|
79
|
+
exactly_assets=exactly_assets,
|
|
80
|
+
grid_point_sensor=grid_point_sensor,
|
|
81
|
+
grid_point_constraints=grid_point_constraints,
|
|
82
|
+
include_asset_access_results=include_asset_access_results,
|
|
83
|
+
include_coverage_points=include_coverage_points,
|
|
84
|
+
step_s=step_s,
|
|
85
|
+
description=description,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def grid_stats(
|
|
90
|
+
*,
|
|
91
|
+
start: str,
|
|
92
|
+
stop: str,
|
|
93
|
+
grid: CoverageGrid,
|
|
94
|
+
assets: Sequence[components.Entity],
|
|
95
|
+
minimum_assets: int | None = None,
|
|
96
|
+
exactly_assets: int | None = None,
|
|
97
|
+
grid_point_sensor: components.EntitySensor | None = None,
|
|
98
|
+
grid_point_constraints: Sequence[components.Constraint] | None = None,
|
|
99
|
+
include_asset_access_results: bool | None = None,
|
|
100
|
+
include_coverage_points: bool | None = None,
|
|
101
|
+
step_s: float | None = None,
|
|
102
|
+
description: str | None = None,
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
"""Compute minimum, maximum, and average simple-coverage FOM values."""
|
|
105
|
+
return post_coverage_input(
|
|
106
|
+
"/Coverage/FOM/GridStats/SimpleCoverage",
|
|
107
|
+
start=start,
|
|
108
|
+
stop=stop,
|
|
109
|
+
grid=grid,
|
|
110
|
+
assets=assets,
|
|
111
|
+
minimum_assets=minimum_assets,
|
|
112
|
+
exactly_assets=exactly_assets,
|
|
113
|
+
grid_point_sensor=grid_point_sensor,
|
|
114
|
+
grid_point_constraints=grid_point_constraints,
|
|
115
|
+
include_asset_access_results=include_asset_access_results,
|
|
116
|
+
include_coverage_points=include_coverage_points,
|
|
117
|
+
step_s=step_s,
|
|
118
|
+
description=description,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def grid_stats_over_time(
|
|
123
|
+
*,
|
|
124
|
+
start: str,
|
|
125
|
+
stop: str,
|
|
126
|
+
grid: CoverageGrid,
|
|
127
|
+
assets: Sequence[components.Entity],
|
|
128
|
+
minimum_assets: int | None = None,
|
|
129
|
+
exactly_assets: int | None = None,
|
|
130
|
+
grid_point_sensor: components.EntitySensor | None = None,
|
|
131
|
+
grid_point_constraints: Sequence[components.Constraint] | None = None,
|
|
132
|
+
include_asset_access_results: bool | None = None,
|
|
133
|
+
include_coverage_points: bool | None = None,
|
|
134
|
+
step_s: float | None = None,
|
|
135
|
+
description: str | None = None,
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""Compute simple-coverage grid statistics over time."""
|
|
138
|
+
return post_coverage_input(
|
|
139
|
+
"/Coverage/FOM/GridStatsOverTime/SimpleCoverage",
|
|
140
|
+
start=start,
|
|
141
|
+
stop=stop,
|
|
142
|
+
grid=grid,
|
|
143
|
+
assets=assets,
|
|
144
|
+
minimum_assets=minimum_assets,
|
|
145
|
+
exactly_assets=exactly_assets,
|
|
146
|
+
grid_point_sensor=grid_point_sensor,
|
|
147
|
+
grid_point_constraints=grid_point_constraints,
|
|
148
|
+
include_asset_access_results=include_asset_access_results,
|
|
149
|
+
include_coverage_points=include_coverage_points,
|
|
150
|
+
step_s=step_s,
|
|
151
|
+
description=description,
|
|
152
|
+
)
|
astrox/exceptions.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Custom exceptions for the astrox package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AstroxError(Exception):
|
|
9
|
+
"""Base exception for all astrox errors."""
|
|
10
|
+
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AstroxAPIError(AstroxError):
|
|
15
|
+
"""API returned IsSuccess=false or other API-level error."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, endpoint: str, response: Any):
|
|
18
|
+
"""Initialize API error.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
message: Error message from API
|
|
22
|
+
endpoint: API endpoint that was called
|
|
23
|
+
response: Response object from requests
|
|
24
|
+
"""
|
|
25
|
+
self.message = message
|
|
26
|
+
self.endpoint = endpoint
|
|
27
|
+
self.response = response
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AstroxHTTPError(AstroxError):
|
|
32
|
+
"""HTTP status code indicates error."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, status_code: int, message: str, endpoint: str, response: Any):
|
|
35
|
+
"""Initialize HTTP error.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
status_code: HTTP status code
|
|
39
|
+
message: Error message
|
|
40
|
+
endpoint: API endpoint that was called
|
|
41
|
+
response: Response object from requests
|
|
42
|
+
"""
|
|
43
|
+
self.status_code = status_code
|
|
44
|
+
self.message = message
|
|
45
|
+
self.endpoint = endpoint
|
|
46
|
+
self.response = response
|
|
47
|
+
super().__init__(f"HTTP {status_code}: {message}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AstroxTimeoutError(AstroxError):
|
|
51
|
+
"""Request timed out."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, endpoint: str, timeout: float):
|
|
54
|
+
"""Initialize timeout error.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
endpoint: API endpoint that was called
|
|
58
|
+
timeout: Timeout value in seconds
|
|
59
|
+
"""
|
|
60
|
+
self.endpoint = endpoint
|
|
61
|
+
self.timeout = timeout
|
|
62
|
+
super().__init__(f"Request to {endpoint} timed out after {timeout}s")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AstroxConnectionError(AstroxError):
|
|
66
|
+
"""Failed to connect to API."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, message: str, original_error: Exception | None):
|
|
69
|
+
"""Initialize connection error.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
message: Error message
|
|
73
|
+
original_error: Original exception that caused the error
|
|
74
|
+
"""
|
|
75
|
+
self.message = message
|
|
76
|
+
self.original_error = original_error
|
|
77
|
+
super().__init__(message)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AstroxValidationError(AstroxError):
|
|
81
|
+
"""Response validation failed."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, message: str, errors: list[dict[str, Any]]):
|
|
84
|
+
"""Initialize validation error.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
message: Error message
|
|
88
|
+
errors: List of validation errors from the response model
|
|
89
|
+
"""
|
|
90
|
+
self.message = message
|
|
91
|
+
self.errors = errors
|
|
92
|
+
super().__init__(message)
|
astrox/lighting.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Lighting analysis functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from numbers import Real
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from astrox import components
|
|
10
|
+
from astrox._http import raw
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"lighting_times",
|
|
14
|
+
"solar_aer",
|
|
15
|
+
"solar_intensity",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _include_if_supplied(payload: dict[str, Any], wire_key: str, value: Any) -> None:
|
|
20
|
+
if value is not None:
|
|
21
|
+
payload[wire_key] = value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _number_sequence_to_list(value: Sequence[float], *, parameter: str) -> list[float]:
|
|
25
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
26
|
+
raise TypeError(f"{parameter} must be a sequence of numbers")
|
|
27
|
+
items = list(value)
|
|
28
|
+
if not all(isinstance(item, Real) and not isinstance(item, bool) for item in items):
|
|
29
|
+
raise TypeError(f"{parameter} must be a sequence of numbers")
|
|
30
|
+
return items
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _string_sequence_to_list(value: Sequence[str], *, parameter: str) -> list[str]:
|
|
34
|
+
if isinstance(value, (str, bytes)) or not isinstance(value, Sequence):
|
|
35
|
+
raise TypeError(f"{parameter} must be a sequence of strings")
|
|
36
|
+
items = list(value)
|
|
37
|
+
if not all(isinstance(item, str) for item in items):
|
|
38
|
+
raise TypeError(f"{parameter} must be a sequence of strings")
|
|
39
|
+
return items
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def lighting_times(
|
|
43
|
+
*,
|
|
44
|
+
start: str,
|
|
45
|
+
stop: str,
|
|
46
|
+
position: components.EntityPosition,
|
|
47
|
+
description: str | None = None,
|
|
48
|
+
az_el_mask_data: Sequence[float] | None = None,
|
|
49
|
+
occultation_bodies: Sequence[str] | None = None,
|
|
50
|
+
) -> dict[str, Any]:
|
|
51
|
+
"""Compute sunlight, penumbra, and umbra intervals for a position source."""
|
|
52
|
+
payload: dict[str, Any] = {
|
|
53
|
+
"Start": start,
|
|
54
|
+
"Stop": stop,
|
|
55
|
+
"Position": components._position_to_wire(position),
|
|
56
|
+
}
|
|
57
|
+
_include_if_supplied(payload, "Description", description)
|
|
58
|
+
if az_el_mask_data is not None:
|
|
59
|
+
payload["AzElMaskData"] = _number_sequence_to_list(
|
|
60
|
+
az_el_mask_data,
|
|
61
|
+
parameter="az_el_mask_data",
|
|
62
|
+
)
|
|
63
|
+
if occultation_bodies is not None:
|
|
64
|
+
payload["OccultationBodies"] = _string_sequence_to_list(
|
|
65
|
+
occultation_bodies,
|
|
66
|
+
parameter="occultation_bodies",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return raw.post("/Lighting/LightingTimes", json=payload)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def solar_intensity(
|
|
73
|
+
*,
|
|
74
|
+
start: str,
|
|
75
|
+
stop: str,
|
|
76
|
+
position: components.EntityPosition,
|
|
77
|
+
description: str | None = None,
|
|
78
|
+
az_el_mask_data: Sequence[float] | None = None,
|
|
79
|
+
step_s: float | None = None,
|
|
80
|
+
occultation_bodies: Sequence[str] | None = None,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""Compute solar intensity samples for a position source."""
|
|
83
|
+
payload: dict[str, Any] = {
|
|
84
|
+
"Start": start,
|
|
85
|
+
"Stop": stop,
|
|
86
|
+
"Position": components._position_to_wire(position),
|
|
87
|
+
}
|
|
88
|
+
_include_if_supplied(payload, "Description", description)
|
|
89
|
+
_include_if_supplied(payload, "TimeStepSec", step_s)
|
|
90
|
+
if az_el_mask_data is not None:
|
|
91
|
+
payload["AzElMaskData"] = _number_sequence_to_list(
|
|
92
|
+
az_el_mask_data,
|
|
93
|
+
parameter="az_el_mask_data",
|
|
94
|
+
)
|
|
95
|
+
if occultation_bodies is not None:
|
|
96
|
+
payload["OccultationBodies"] = _string_sequence_to_list(
|
|
97
|
+
occultation_bodies,
|
|
98
|
+
parameter="occultation_bodies",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return raw.post("/Lighting/SolarIntensity", json=payload)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def solar_aer(
|
|
105
|
+
*,
|
|
106
|
+
start: str,
|
|
107
|
+
stop: str,
|
|
108
|
+
position: components.EntityPosition,
|
|
109
|
+
text: str | None = None,
|
|
110
|
+
step_s: int | None = None,
|
|
111
|
+
) -> dict[str, Any]:
|
|
112
|
+
"""Compute solar azimuth, elevation, and range samples for a position source."""
|
|
113
|
+
payload: dict[str, Any] = {
|
|
114
|
+
"Start": start,
|
|
115
|
+
"Stop": stop,
|
|
116
|
+
"Position": components._position_to_wire(position),
|
|
117
|
+
}
|
|
118
|
+
_include_if_supplied(payload, "Text", text)
|
|
119
|
+
_include_if_supplied(payload, "TimeStepSec", step_s)
|
|
120
|
+
|
|
121
|
+
return raw.post("/Lighting/SolarAER", json=payload)
|