astroz 0.4.1__cp312-cp312-manylinux_2_38_x86_64.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.
- astroz/__init__.py +22 -0
- astroz/__version__.py +1 -0
- astroz/_lib.py +111 -0
- astroz/coords.py +30 -0
- astroz/exceptions.py +40 -0
- astroz/lib/libastroz_c.so +0 -0
- astroz/orbital.py +38 -0
- astroz/sgp4.py +86 -0
- astroz/tle.py +43 -0
- astroz-0.4.1.dist-info/METADATA +94 -0
- astroz-0.4.1.dist-info/RECORD +13 -0
- astroz-0.4.1.dist-info/WHEEL +5 -0
- astroz-0.4.1.dist-info/top_level.txt +2 -0
astroz/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""astroz - High-performance astrodynamics library."""
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
from .__version__ import __version__
|
|
5
|
+
from ._lib import _lib
|
|
6
|
+
from .tle import Tle
|
|
7
|
+
from .sgp4 import Sgp4
|
|
8
|
+
from . import orbital
|
|
9
|
+
from . import coords
|
|
10
|
+
|
|
11
|
+
# initialize on import
|
|
12
|
+
_lib.astroz_init()
|
|
13
|
+
atexit.register(_lib.astroz_deinit)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def version() -> str:
|
|
17
|
+
"""Return astroz version string."""
|
|
18
|
+
v = _lib.astroz_version()
|
|
19
|
+
return f"{(v >> 16) & 0xFF}.{(v >> 8) & 0xFF}.{v & 0xFF}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = ["__version__", "version", "Tle", "Sgp4", "orbital", "coords"]
|
astroz/__version__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.1"
|
astroz/_lib.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Load the astroz shared library."""
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_LIB_NAMES = {
|
|
9
|
+
"Linux": "libastroz_c.so",
|
|
10
|
+
"Darwin": "libastroz_c.dylib",
|
|
11
|
+
"Windows": "astroz_c.dll",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _find_library():
|
|
16
|
+
"""Find libastroz_c shared library.
|
|
17
|
+
|
|
18
|
+
Search order:
|
|
19
|
+
1. ASTROZ_LIB env var (explicit override)
|
|
20
|
+
2. Next to this package (installed via pip)
|
|
21
|
+
3. zig-out/lib/ (development)
|
|
22
|
+
"""
|
|
23
|
+
system = platform.system()
|
|
24
|
+
name = _LIB_NAMES.get(system)
|
|
25
|
+
if name is None:
|
|
26
|
+
raise OSError(f"Unsupported platform: {system}")
|
|
27
|
+
|
|
28
|
+
# Explicit override
|
|
29
|
+
env_path = os.environ.get("ASTROZ_LIB")
|
|
30
|
+
if env_path:
|
|
31
|
+
if os.path.exists(env_path):
|
|
32
|
+
return env_path
|
|
33
|
+
raise OSError(f"ASTROZ_LIB set but file not found: {env_path}")
|
|
34
|
+
|
|
35
|
+
# Standard search paths
|
|
36
|
+
pkg_dir = os.path.dirname(__file__)
|
|
37
|
+
search_paths = [
|
|
38
|
+
os.path.join(pkg_dir, "lib", name), # installed package (wheel)
|
|
39
|
+
os.path.join(pkg_dir, name), # legacy location
|
|
40
|
+
os.path.join(pkg_dir, "..", "..", "zig-out", "lib", name), # dev
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
for path in search_paths:
|
|
44
|
+
if os.path.exists(path):
|
|
45
|
+
return path
|
|
46
|
+
|
|
47
|
+
raise OSError(
|
|
48
|
+
f"Cannot find {name}. Either:\n"
|
|
49
|
+
f" - Run 'zig build c-api' for development\n"
|
|
50
|
+
f" - Set ASTROZ_LIB=/path/to/{name}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_lib = ctypes.CDLL(_find_library())
|
|
55
|
+
|
|
56
|
+
# aliases
|
|
57
|
+
_ptr = ctypes.c_void_p
|
|
58
|
+
_i32 = ctypes.c_int32
|
|
59
|
+
_u32 = ctypes.c_uint32
|
|
60
|
+
_f32 = ctypes.c_float
|
|
61
|
+
_f64 = ctypes.c_double
|
|
62
|
+
_str = ctypes.c_char_p
|
|
63
|
+
_out_ptr = ctypes.POINTER(ctypes.c_void_p)
|
|
64
|
+
_out_vec3 = ctypes.POINTER(ctypes.c_double * 3)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _sig(func, args, ret=_i32):
|
|
68
|
+
"""Set function signature: argtypes and restype."""
|
|
69
|
+
func.argtypes = args
|
|
70
|
+
func.restype = ret
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_sig(_lib.astroz_init, [], None)
|
|
74
|
+
_sig(_lib.astroz_deinit, [], None)
|
|
75
|
+
_sig(_lib.astroz_version, [], _u32)
|
|
76
|
+
|
|
77
|
+
_sig(_lib.tle_parse, [_str, _out_ptr])
|
|
78
|
+
_sig(_lib.tle_free, [_ptr], None)
|
|
79
|
+
_sig(_lib.tle_get_satellite_number, [_ptr], _u32)
|
|
80
|
+
_sig(_lib.tle_get_epoch, [_ptr], _f64)
|
|
81
|
+
_sig(_lib.tle_get_inclination, [_ptr], _f64)
|
|
82
|
+
_sig(_lib.tle_get_eccentricity, [_ptr], _f64)
|
|
83
|
+
_sig(_lib.tle_get_mean_motion, [_ptr], _f64)
|
|
84
|
+
|
|
85
|
+
_sig(_lib.sgp4_init, [_ptr, _i32, _out_ptr])
|
|
86
|
+
_sig(_lib.sgp4_free, [_ptr], None)
|
|
87
|
+
_sig(_lib.sgp4_propagate, [_ptr, _f64, _out_vec3, _out_vec3])
|
|
88
|
+
_sig(
|
|
89
|
+
_lib.sgp4_propagate_batch, [_ptr, ctypes.POINTER(_f64), ctypes.POINTER(_f64), _u32]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class HohmannResult(ctypes.Structure):
|
|
94
|
+
_fields_ = [
|
|
95
|
+
("semi_major_axis", _f64),
|
|
96
|
+
("delta_v1", _f64),
|
|
97
|
+
("delta_v2", _f64),
|
|
98
|
+
("total_delta_v", _f64),
|
|
99
|
+
("transfer_time", _f64),
|
|
100
|
+
("transfer_time_days", _f64),
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_sig(_lib.orbital_hohmann, [_f64, _f64, _f64, ctypes.POINTER(HohmannResult)])
|
|
105
|
+
_sig(_lib.orbital_velocity, [_f64, _f64, _f64], _f64)
|
|
106
|
+
_sig(_lib.orbital_period, [_f64, _f64], _f64)
|
|
107
|
+
_sig(_lib.orbital_escape_velocity, [_f64, _f64], _f64)
|
|
108
|
+
|
|
109
|
+
_sig(_lib.coords_eci_to_ecef, [_out_vec3, _f64, _out_vec3], None)
|
|
110
|
+
_sig(_lib.coords_ecef_to_geodetic, [_out_vec3, _out_vec3], None)
|
|
111
|
+
_sig(_lib.coords_julian_to_gmst, [_f64], _f64)
|
astroz/coords.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Coordinate transformations."""
|
|
2
|
+
|
|
3
|
+
from ctypes import byref, c_double
|
|
4
|
+
from ._lib import _lib
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def eci_to_ecef(eci: tuple, gmst: float) -> tuple:
|
|
8
|
+
"""ECI to ECEF. Returns (x, y, z) in km."""
|
|
9
|
+
eci_arr = (c_double * 3)(*eci)
|
|
10
|
+
ecef_arr = (c_double * 3)()
|
|
11
|
+
_lib.coords_eci_to_ecef(byref(eci_arr), gmst, byref(ecef_arr))
|
|
12
|
+
return (ecef_arr[0], ecef_arr[1], ecef_arr[2])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def ecef_to_geodetic(ecef: tuple) -> tuple:
|
|
16
|
+
"""ECEF to geodetic (WGS84). Returns (lat_deg, lon_deg, alt_km)."""
|
|
17
|
+
ecef_arr = (c_double * 3)(*ecef)
|
|
18
|
+
lla_arr = (c_double * 3)()
|
|
19
|
+
_lib.coords_ecef_to_geodetic(byref(ecef_arr), byref(lla_arr))
|
|
20
|
+
return (lla_arr[0], lla_arr[1], lla_arr[2])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def eci_to_geodetic(eci: tuple, gmst: float) -> tuple:
|
|
24
|
+
"""ECI to geodetic (WGS84). Returns (lat_deg, lon_deg, alt_km)."""
|
|
25
|
+
return ecef_to_geodetic(eci_to_ecef(eci, gmst))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def julian_to_gmst(jd: float) -> float:
|
|
29
|
+
"""Julian date to GMST (radians)."""
|
|
30
|
+
return _lib.coords_julian_to_gmst(jd)
|
astroz/exceptions.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Exception handling for astroz errors."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AstrozError(Exception):
|
|
5
|
+
"""Base exception for astroz errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TleParseError(AstrozError):
|
|
9
|
+
"""TLE parsing failed."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Sgp4Error(AstrozError):
|
|
13
|
+
"""SGP4 propagation error."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OrbitalError(AstrozError):
|
|
17
|
+
"""Orbital mechanics calculation error."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_ERROR_MAP = {
|
|
21
|
+
-1: (TleParseError, "Bad TLE length"),
|
|
22
|
+
-2: (TleParseError, "Bad checksum"),
|
|
23
|
+
-10: (Sgp4Error, "Deep space not supported"),
|
|
24
|
+
-11: (Sgp4Error, "Invalid eccentricity"),
|
|
25
|
+
-12: (Sgp4Error, "Satellite decayed"),
|
|
26
|
+
-20: (OrbitalError, "Invalid orbital parameters"),
|
|
27
|
+
-100: (AstrozError, "Allocation failed"),
|
|
28
|
+
-101: (AstrozError, "Null pointer"),
|
|
29
|
+
-102: (AstrozError, "Not initialized"),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def check(code: int) -> None:
|
|
34
|
+
"""Raise exception if error code is non-zero."""
|
|
35
|
+
if code == 0:
|
|
36
|
+
return
|
|
37
|
+
err = _ERROR_MAP.get(code)
|
|
38
|
+
if err:
|
|
39
|
+
raise err[0](err[1])
|
|
40
|
+
raise AstrozError(f"Unknown error: {code}")
|
|
Binary file
|
astroz/orbital.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Orbital mechanics functions."""
|
|
2
|
+
|
|
3
|
+
from ctypes import byref
|
|
4
|
+
from ._lib import _lib, HohmannResult
|
|
5
|
+
from .exceptions import check
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def hohmann(mu: float, r1: float, r2: float) -> dict:
|
|
9
|
+
"""Hohmann transfer between two circular orbits.
|
|
10
|
+
|
|
11
|
+
Returns dict with: semi_major_axis, delta_v1, delta_v2,
|
|
12
|
+
total_delta_v, transfer_time, transfer_time_days
|
|
13
|
+
"""
|
|
14
|
+
result = HohmannResult()
|
|
15
|
+
check(_lib.orbital_hohmann(mu, r1, r2, byref(result)))
|
|
16
|
+
return {
|
|
17
|
+
"semi_major_axis": result.semi_major_axis,
|
|
18
|
+
"delta_v1": result.delta_v1,
|
|
19
|
+
"delta_v2": result.delta_v2,
|
|
20
|
+
"total_delta_v": result.total_delta_v,
|
|
21
|
+
"transfer_time": result.transfer_time,
|
|
22
|
+
"transfer_time_days": result.transfer_time_days,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def velocity(mu: float, radius: float, sma: float = 0) -> float:
|
|
27
|
+
"""Orbital velocity (km/s). Use sma=0 for circular orbit."""
|
|
28
|
+
return _lib.orbital_velocity(mu, radius, sma)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def period(mu: float, sma: float) -> float:
|
|
32
|
+
"""Orbital period (seconds)."""
|
|
33
|
+
return _lib.orbital_period(mu, sma)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def escape_velocity(mu: float, radius: float) -> float:
|
|
37
|
+
"""Escape velocity (km/s)."""
|
|
38
|
+
return _lib.orbital_escape_velocity(mu, radius)
|
astroz/sgp4.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""SGP4 orbit propagator wrapper."""
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
from ._lib import _lib
|
|
5
|
+
from .exceptions import check
|
|
6
|
+
from .tle import Tle
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Sgp4:
|
|
10
|
+
"""SGP4 orbit propagator."""
|
|
11
|
+
|
|
12
|
+
WGS84 = 0
|
|
13
|
+
WGS72 = 1
|
|
14
|
+
|
|
15
|
+
def __init__(self, tle: Tle, gravity_model: int = WGS84):
|
|
16
|
+
"""Initialize SGP4 propagator from TLE."""
|
|
17
|
+
self._handle = ctypes.c_void_p()
|
|
18
|
+
check(_lib.sgp4_init(tle._handle, gravity_model, ctypes.byref(self._handle)))
|
|
19
|
+
|
|
20
|
+
def __del__(self):
|
|
21
|
+
if hasattr(self, "_handle") and self._handle:
|
|
22
|
+
_lib.sgp4_free(self._handle)
|
|
23
|
+
|
|
24
|
+
def propagate(self, tsince: float) -> tuple:
|
|
25
|
+
"""Propagate to time since TLE epoch.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
tsince: Minutes since TLE epoch
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
(position, velocity) tuples in km and km/s (TEME frame)
|
|
32
|
+
"""
|
|
33
|
+
pos = (ctypes.c_double * 3)()
|
|
34
|
+
vel = (ctypes.c_double * 3)()
|
|
35
|
+
check(
|
|
36
|
+
_lib.sgp4_propagate(
|
|
37
|
+
self._handle, tsince, ctypes.byref(pos), ctypes.byref(vel)
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
return (pos[0], pos[1], pos[2]), (vel[0], vel[1], vel[2])
|
|
41
|
+
|
|
42
|
+
def propagate_batch(self, times: list) -> list:
|
|
43
|
+
"""Batch propagation - much faster for multiple time steps.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
times: List of minutes since TLE epoch
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
List of (position, velocity) tuples
|
|
50
|
+
"""
|
|
51
|
+
n = len(times)
|
|
52
|
+
times_arr = (ctypes.c_double * n)(*times)
|
|
53
|
+
results_arr = (ctypes.c_double * (n * 6))()
|
|
54
|
+
check(_lib.sgp4_propagate_batch(self._handle, times_arr, results_arr, n))
|
|
55
|
+
out = []
|
|
56
|
+
for i in range(n):
|
|
57
|
+
base = i * 6
|
|
58
|
+
pos = (results_arr[base], results_arr[base + 1], results_arr[base + 2])
|
|
59
|
+
vel = (results_arr[base + 3], results_arr[base + 4], results_arr[base + 5])
|
|
60
|
+
out.append((pos, vel))
|
|
61
|
+
return out
|
|
62
|
+
|
|
63
|
+
def propagate_batch_np(self, times):
|
|
64
|
+
"""Batch propagation returning numpy arrays (fastest).
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
times: Array-like of minutes since TLE epoch
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
(positions, velocities) as numpy arrays of shape (n, 3)
|
|
71
|
+
"""
|
|
72
|
+
import numpy as np
|
|
73
|
+
|
|
74
|
+
times = np.asarray(times, dtype=np.float64)
|
|
75
|
+
n = len(times)
|
|
76
|
+
results = np.empty(n * 6, dtype=np.float64)
|
|
77
|
+
check(
|
|
78
|
+
_lib.sgp4_propagate_batch(
|
|
79
|
+
self._handle,
|
|
80
|
+
times.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
81
|
+
results.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
82
|
+
n,
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
results = results.reshape(n, 6)
|
|
86
|
+
return results[:, :3], results[:, 3:]
|
astroz/tle.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""TLE (Two-Line Element) wrapper."""
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
from ._lib import _lib
|
|
5
|
+
from .exceptions import check
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Tle:
|
|
9
|
+
"""Two-Line Element set for satellite orbit data."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, tle_string: str):
|
|
12
|
+
"""Parse a TLE string (both lines)."""
|
|
13
|
+
self._handle = ctypes.c_void_p()
|
|
14
|
+
check(_lib.tle_parse(tle_string.encode(), ctypes.byref(self._handle)))
|
|
15
|
+
|
|
16
|
+
def __del__(self):
|
|
17
|
+
if hasattr(self, "_handle") and self._handle:
|
|
18
|
+
_lib.tle_free(self._handle)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def satellite_number(self) -> int:
|
|
22
|
+
"""NORAD catalog number."""
|
|
23
|
+
return _lib.tle_get_satellite_number(self._handle)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def epoch(self) -> float:
|
|
27
|
+
"""Epoch in J2000 seconds."""
|
|
28
|
+
return _lib.tle_get_epoch(self._handle)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def inclination(self) -> float:
|
|
32
|
+
"""Orbital inclination in degrees."""
|
|
33
|
+
return _lib.tle_get_inclination(self._handle)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def eccentricity(self) -> float:
|
|
37
|
+
"""Orbital eccentricity."""
|
|
38
|
+
return _lib.tle_get_eccentricity(self._handle)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def mean_motion(self) -> float:
|
|
42
|
+
"""Mean motion in revolutions per day."""
|
|
43
|
+
return _lib.tle_get_mean_motion(self._handle)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: astroz
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Python bindings for astroz - high-performance astrodynamics library
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/ATTron/astroz
|
|
7
|
+
Keywords: astrodynamics,orbital-mechanics,sgp4,tle,satellite
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# astroz Python bindings
|
|
17
|
+
|
|
18
|
+
Python bindings for [astroz](https://github.com/ATTron/astroz), a high-performance astrodynamics library written in Zig.
|
|
19
|
+
|
|
20
|
+
## Performance
|
|
21
|
+
|
|
22
|
+
Using the batch numpy API, astroz significantly outperforms other Python SGP4 libraries:
|
|
23
|
+
|
|
24
|
+
| Scenario | astroz | python-sgp4 | skyfield | vs sgp4 |
|
|
25
|
+
|----------|--------|-------------|----------|---------|
|
|
26
|
+
| 1 day (minute res) | 0.37 ms | 0.54 ms | 8 ms | **1.5x faster** |
|
|
27
|
+
| 1 week (minute res) | 2.6 ms | 3.7 ms | 50 ms | **1.4x faster** |
|
|
28
|
+
| 2 weeks (minute res) | 4.8 ms | 7.4 ms | 92 ms | **1.5x faster** |
|
|
29
|
+
| 2 weeks (second res) | 280 ms | 460 ms | - | **1.6x faster** |
|
|
30
|
+
| 1 month (minute res) | 9.8 ms | 17 ms | 187 ms | **1.7x faster** |
|
|
31
|
+
|
|
32
|
+
The raw Zig implementation achieves ~5 million propagations/second, outperforming
|
|
33
|
+
the Rust [sgp4 crate](https://github.com/neuromorphicsystems/sgp4) by 1.3-2.2x.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Build the shared library first
|
|
39
|
+
cd /path/to/astroz
|
|
40
|
+
zig build c-api
|
|
41
|
+
|
|
42
|
+
# Install Python package
|
|
43
|
+
cd python
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import astroz
|
|
51
|
+
from astroz import Tle, Sgp4, orbital, coords
|
|
52
|
+
|
|
53
|
+
# Parse TLE and propagate with SGP4
|
|
54
|
+
tle = Tle("""1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025
|
|
55
|
+
2 25544 51.6400 208.9163 0006703 40.6542 114.5435 15.49910020999999""")
|
|
56
|
+
|
|
57
|
+
sgp4 = Sgp4(tle)
|
|
58
|
+
pos, vel = sgp4.propagate(30.0) # 30 minutes after epoch
|
|
59
|
+
print(f"Position: {pos} km")
|
|
60
|
+
print(f"Velocity: {vel} km/s")
|
|
61
|
+
|
|
62
|
+
# Convert to ground coordinates
|
|
63
|
+
gmst = coords.julian_to_gmst(2460310.5)
|
|
64
|
+
lat, lon, alt = coords.eci_to_geodetic(pos, gmst)
|
|
65
|
+
print(f"Lat: {lat:.2f}, Lon: {lon:.2f}, Alt: {alt:.1f} km")
|
|
66
|
+
|
|
67
|
+
# Orbital mechanics
|
|
68
|
+
transfer = orbital.hohmann(398600.5, 6778, 42164) # LEO to GEO
|
|
69
|
+
print(f"Delta-V: {transfer['total_delta_v']:.2f} km/s")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## API
|
|
73
|
+
|
|
74
|
+
### TLE Parsing
|
|
75
|
+
- `Tle(tle_string)` - Parse two-line element set
|
|
76
|
+
- Properties: `satellite_number`, `epoch`, `inclination`, `eccentricity`, `mean_motion`
|
|
77
|
+
|
|
78
|
+
### SGP4 Propagation
|
|
79
|
+
- `Sgp4(tle, gravity_model=WGS84)` - Initialize propagator
|
|
80
|
+
- `propagate(tsince)` - Single propagation (returns tuple)
|
|
81
|
+
- `propagate_batch(times)` - Batch propagation (returns list)
|
|
82
|
+
- `propagate_batch_np(times)` - Batch with numpy (fastest, returns arrays)
|
|
83
|
+
|
|
84
|
+
### Coordinates
|
|
85
|
+
- `coords.eci_to_ecef(eci, gmst)` - ECI to ECEF
|
|
86
|
+
- `coords.ecef_to_geodetic(ecef)` - ECEF to lat/lon/alt
|
|
87
|
+
- `coords.eci_to_geodetic(eci, gmst)` - ECI to lat/lon/alt
|
|
88
|
+
- `coords.julian_to_gmst(jd)` - Julian date to GMST
|
|
89
|
+
|
|
90
|
+
### Orbital Mechanics
|
|
91
|
+
- `orbital.hohmann(mu, r1, r2)` - Hohmann transfer calculation
|
|
92
|
+
- `orbital.velocity(mu, radius, sma=0)` - Orbital velocity
|
|
93
|
+
- `orbital.period(mu, sma)` - Orbital period
|
|
94
|
+
- `orbital.escape_velocity(mu, radius)` - Escape velocity
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
astroz/__init__.py,sha256=bVQmXRjQQDIjBs8wW8iGwT9LTy7LPNUB8qNhfPnbY_I,527
|
|
2
|
+
astroz/__version__.py,sha256=pMtTmSUht-XtbR_7Doz6bsQqopJJd8rZ8I8zy2HwwoA,22
|
|
3
|
+
astroz/_lib.py,sha256=FGADZSXmW5fRIr53H3RDzSEz-rK5lR1j2BTAwALv5Rg,3065
|
|
4
|
+
astroz/coords.py,sha256=fYODTZ0ZqZ9tf8NjRo-2Dr7H2FOol0frMD0t45DgIlc,984
|
|
5
|
+
astroz/exceptions.py,sha256=7FdJk0FENI3DZ0UJxBeouwmQAWExcWHHPkZsncq05ts,1000
|
|
6
|
+
astroz/orbital.py,sha256=xSfXJPc_vwhkSKCk4GOQ-0R5yDKFDbwNNiR6-Cw_wFg,1191
|
|
7
|
+
astroz/sgp4.py,sha256=efsOBmtYLLS8Z1MdcjyXiMKcV5cwDC9tkQqIw-UYAfs,2658
|
|
8
|
+
astroz/tle.py,sha256=6OLdcLG_wSNJ3u8SL9JSoEvRxOBizKqwZ3MAV-s2b-I,1232
|
|
9
|
+
astroz/lib/libastroz_c.so,sha256=xV8K7x_Jt2l_0r-ejVGuZMtmEaURszrFwZ4Hr15oXU0,276368
|
|
10
|
+
astroz-0.4.1.dist-info/METADATA,sha256=XLsAGlFkB42qIs0F3rVL8oD0WEVp2ODBOMxVC7fQ328,3252
|
|
11
|
+
astroz-0.4.1.dist-info/WHEEL,sha256=w_6alU7T_sBwGm5Vyu6GD3AOYNoPHJEZwBbFBO_bp5Q,113
|
|
12
|
+
astroz-0.4.1.dist-info/top_level.txt,sha256=X8LWxpbPipvBmFB0vnafmfaz1WmkzU2MOyoeJX30Yig,12
|
|
13
|
+
astroz-0.4.1.dist-info/RECORD,,
|