satnogs-predict 0.2__tar.gz → 0.3__tar.gz
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.
- {satnogs_predict-0.2 → satnogs_predict-0.3}/PKG-INFO +4 -3
- {satnogs_predict-0.2 → satnogs_predict-0.3}/README.md +3 -2
- {satnogs_predict-0.2 → satnogs_predict-0.3}/docs/docs.md +15 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/__init__.py +2 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/orbit.py +3 -0
- satnogs_predict-0.3/src/satnogs_predict/tle.py +107 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict.egg-info/PKG-INFO +4 -3
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict.egg-info/SOURCES.txt +2 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/domain/test_orbit.py +7 -0
- satnogs_predict-0.3/tests/test_tle.py +53 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/.gitignore +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/.gitlab-ci.yml +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/.pre-commit-config.yaml +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/.python-version +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/CONTRIBUTING.md +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/LICENSE +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/pyproject.toml +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/setup.cfg +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/setup.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/constraints/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/constraints/constraints.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/core/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/core/engine.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/constraints.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/geometry.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/observer.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/planner.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/time.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/validation.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/domain/window.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/planning/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/planning/planner.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/propagation/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/propagation/propagator.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict/py.typed +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict.egg-info/dependency_links.txt +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict.egg-info/requires.txt +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict.egg-info/top_level.txt +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/conftest.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/constraints/test_constraints.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/core/test_engine.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/domain/test_planner_domain.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/domain/test_required_fields.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/domain/test_time.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/domain/test_window.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/helpers.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/planning/test_planner.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/tests/propagation/test_propagator.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: satnogs-predict
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3
|
|
4
4
|
Summary: A package for calculating passes and observation windows for satellites.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,8 +13,7 @@ Dynamic: license-file
|
|
|
13
13
|
[](https://gitlab.com/librespacefoundation/satnogs/satnogs-predict/-/pipelines)
|
|
14
14
|
[](https://gitlab.com/librespacefoundation/satnogs/satnogs-predict/-/pipelines)
|
|
15
15
|
[](https://pypi.org/project/satnogs-predict/)
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+

|
|
18
17
|
---
|
|
19
18
|
|
|
20
19
|
## 🚀 Overview
|
|
@@ -45,6 +44,8 @@ pip install satnogs-predict
|
|
|
45
44
|
from satnogs_predict import find_observation_windows
|
|
46
45
|
```
|
|
47
46
|
|
|
47
|
+
For more complete examples and usage patterns, see [docs/docs.md](docs/docs.md).
|
|
48
|
+
|
|
48
49
|
## License
|
|
49
50
|
|
|
50
51
|
[](LICENSE)
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
[](https://gitlab.com/librespacefoundation/satnogs/satnogs-predict/-/pipelines)
|
|
4
4
|
[](https://gitlab.com/librespacefoundation/satnogs/satnogs-predict/-/pipelines)
|
|
5
5
|
[](https://pypi.org/project/satnogs-predict/)
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+

|
|
8
7
|
---
|
|
9
8
|
|
|
10
9
|
## 🚀 Overview
|
|
@@ -35,6 +34,8 @@ pip install satnogs-predict
|
|
|
35
34
|
from satnogs_predict import find_observation_windows
|
|
36
35
|
```
|
|
37
36
|
|
|
37
|
+
For more complete examples and usage patterns, see [docs/docs.md](docs/docs.md).
|
|
38
|
+
|
|
38
39
|
## License
|
|
39
40
|
|
|
40
41
|
[](LICENSE)
|
|
@@ -44,6 +44,7 @@ from satnogs_predict import (
|
|
|
44
44
|
get_and_validate_pass_from_range,
|
|
45
45
|
check_observation_density_limit_respected,
|
|
46
46
|
has_range_list_overlap,
|
|
47
|
+
generate_fake_tle,
|
|
47
48
|
|
|
48
49
|
MaxDurationConstraint,
|
|
49
50
|
MinCulminationConstraint,
|
|
@@ -86,6 +87,20 @@ satellite = Satellite.from_tle(
|
|
|
86
87
|
)
|
|
87
88
|
```
|
|
88
89
|
|
|
90
|
+
For testing or development, you can also generate a synthetic TLE:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from datetime import datetime, timezone
|
|
94
|
+
|
|
95
|
+
fake_tle = generate_fake_tle(
|
|
96
|
+
latitude=37.983810,
|
|
97
|
+
longitude=23.727539,
|
|
98
|
+
date=datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
line0, line1, line2 = fake_tle.to_list()
|
|
102
|
+
```
|
|
103
|
+
|
|
89
104
|
|
|
90
105
|
## 3.2 - Observer
|
|
91
106
|
|
|
@@ -3,7 +3,9 @@ from .constraints import * # noqa: F401,F403
|
|
|
3
3
|
from .core.engine import find_observation_windows, get_and_validate_pass_from_range
|
|
4
4
|
from .domain import * # noqa: F401,F403
|
|
5
5
|
from .planning.planner import check_observation_density_limit_respected, has_range_list_overlap
|
|
6
|
+
from .tle import generate_fake_tle
|
|
6
7
|
|
|
7
8
|
__all__ = list(domain.__all__) + list(constraints.__all__)
|
|
8
9
|
__all__ += ["check_observation_density_limit_respected", "has_range_list_overlap"]
|
|
9
10
|
__all__ += ["find_observation_windows", "get_and_validate_pass_from_range"]
|
|
11
|
+
__all__ += ["generate_fake_tle"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
|
|
6
|
+
from skyfield.api import load # type: ignore[import-untyped]
|
|
7
|
+
|
|
8
|
+
from satnogs_predict.domain.orbit import TLE
|
|
9
|
+
|
|
10
|
+
_ts = load.timescale()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class FakeTLEParameters:
|
|
15
|
+
name: str = "FAKESAT-1"
|
|
16
|
+
catalog_num: int = 0
|
|
17
|
+
classifier: str = "U"
|
|
18
|
+
cospar_id: str = "00000A"
|
|
19
|
+
mean_motion_first: str = ".00000000"
|
|
20
|
+
mean_motion_second: str = "00000-0"
|
|
21
|
+
drag_term: str = "50000-4"
|
|
22
|
+
set_number: str = "0"
|
|
23
|
+
inclination: str = "90.0000"
|
|
24
|
+
eccentricity: str = "0001000"
|
|
25
|
+
argument_perigee: str = "000.0000"
|
|
26
|
+
mean_motion: str = "15.50000000"
|
|
27
|
+
revolutions: str = "0"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
DEFAULT_FAKE_TLE_PARAMETERS = FakeTLEParameters()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _tle_checksum(line_without_checksum: str) -> int:
|
|
34
|
+
total = 0
|
|
35
|
+
for char in line_without_checksum:
|
|
36
|
+
if char.isdigit():
|
|
37
|
+
total += int(char)
|
|
38
|
+
elif char == "-":
|
|
39
|
+
total += 1
|
|
40
|
+
return total % 10
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _epoch_day_of_year_fraction(epoch: datetime) -> float:
|
|
44
|
+
year_start = datetime(epoch.year, 1, 1, tzinfo=timezone.utc)
|
|
45
|
+
delta = epoch - year_start
|
|
46
|
+
day = delta.days + 1
|
|
47
|
+
day_fraction = (delta.seconds + delta.microseconds / 1_000_000) / 86_400
|
|
48
|
+
return day + day_fraction
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def generate_fake_tle(
|
|
52
|
+
latitude: float,
|
|
53
|
+
longitude: float,
|
|
54
|
+
date: datetime,
|
|
55
|
+
params: FakeTLEParameters = DEFAULT_FAKE_TLE_PARAMETERS,
|
|
56
|
+
) -> TLE:
|
|
57
|
+
"""
|
|
58
|
+
Generate a deterministic, synthetic TLE from observer coordinates and an epoch.
|
|
59
|
+
|
|
60
|
+
The result is intended for testing and development scenarios where a plausible
|
|
61
|
+
but not physically accurate TLE is sufficient.
|
|
62
|
+
"""
|
|
63
|
+
if date.tzinfo is None:
|
|
64
|
+
raise ValueError("date must be timezone-aware")
|
|
65
|
+
|
|
66
|
+
epoch = date.astimezone(timezone.utc)
|
|
67
|
+
skyfield_time = _ts.from_datetime(epoch)
|
|
68
|
+
|
|
69
|
+
# Derived values
|
|
70
|
+
right_ascension = (skyfield_time.gmst * 15.0 + longitude) % 360.0
|
|
71
|
+
mean_anomaly = latitude % 360.0
|
|
72
|
+
|
|
73
|
+
epoch_year = epoch.year
|
|
74
|
+
epoch_day = _epoch_day_of_year_fraction(epoch)
|
|
75
|
+
|
|
76
|
+
line0 = params.name.ljust(24)
|
|
77
|
+
|
|
78
|
+
line1_no_checksum = (
|
|
79
|
+
"1 {:5.5}{:.1} {:8.8} {:.2}{:012.8f} {:>10.10} {:>8.8} {:>8.8} 0 {:>4.4}".format(
|
|
80
|
+
f"{params.catalog_num:05d}",
|
|
81
|
+
params.classifier,
|
|
82
|
+
params.cospar_id,
|
|
83
|
+
str(epoch_year),
|
|
84
|
+
epoch_day,
|
|
85
|
+
params.mean_motion_first,
|
|
86
|
+
params.mean_motion_second,
|
|
87
|
+
params.drag_term,
|
|
88
|
+
params.set_number,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
line2_no_checksum = (
|
|
93
|
+
"2 {:>5.5} {:>8.8} {:>8.4f} {:>7.7} {:>8.8} {:>8.4f} {:>11.11}{:>5.5}".format(
|
|
94
|
+
f"{params.catalog_num:05d}",
|
|
95
|
+
params.inclination,
|
|
96
|
+
right_ascension,
|
|
97
|
+
params.eccentricity,
|
|
98
|
+
params.argument_perigee,
|
|
99
|
+
mean_anomaly,
|
|
100
|
+
params.mean_motion,
|
|
101
|
+
params.revolutions,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
line1 = f"{line1_no_checksum}{_tle_checksum(line1_no_checksum)}"
|
|
106
|
+
line2 = f"{line2_no_checksum}{_tle_checksum(line2_no_checksum)}"
|
|
107
|
+
return TLE(line0=line0, line1=line1, line2=line2)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: satnogs-predict
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3
|
|
4
4
|
Summary: A package for calculating passes and observation windows for satellites.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,8 +13,7 @@ Dynamic: license-file
|
|
|
13
13
|
[](https://gitlab.com/librespacefoundation/satnogs/satnogs-predict/-/pipelines)
|
|
14
14
|
[](https://gitlab.com/librespacefoundation/satnogs/satnogs-predict/-/pipelines)
|
|
15
15
|
[](https://pypi.org/project/satnogs-predict/)
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+

|
|
18
17
|
---
|
|
19
18
|
|
|
20
19
|
## 🚀 Overview
|
|
@@ -45,6 +44,8 @@ pip install satnogs-predict
|
|
|
45
44
|
from satnogs_predict import find_observation_windows
|
|
46
45
|
```
|
|
47
46
|
|
|
47
|
+
For more complete examples and usage patterns, see [docs/docs.md](docs/docs.md).
|
|
48
|
+
|
|
48
49
|
## License
|
|
49
50
|
|
|
50
51
|
[](LICENSE)
|
|
@@ -11,6 +11,7 @@ uv.lock
|
|
|
11
11
|
docs/docs.md
|
|
12
12
|
src/satnogs_predict/__init__.py
|
|
13
13
|
src/satnogs_predict/py.typed
|
|
14
|
+
src/satnogs_predict/tle.py
|
|
14
15
|
src/satnogs_predict.egg-info/PKG-INFO
|
|
15
16
|
src/satnogs_predict.egg-info/SOURCES.txt
|
|
16
17
|
src/satnogs_predict.egg-info/dependency_links.txt
|
|
@@ -36,6 +37,7 @@ src/satnogs_predict/propagation/propagator.py
|
|
|
36
37
|
tests/__init__.py
|
|
37
38
|
tests/conftest.py
|
|
38
39
|
tests/helpers.py
|
|
40
|
+
tests/test_tle.py
|
|
39
41
|
tests/constraints/test_constraints.py
|
|
40
42
|
tests/core/test_engine.py
|
|
41
43
|
tests/domain/test_orbit.py
|
|
@@ -3,6 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
from satnogs_predict.domain.orbit import TLE, OrbitRepresentation, Satellite
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
class TestTle:
|
|
7
|
+
def test_given_tle_when_to_list_then_returns_three_lines_in_order(self) -> None:
|
|
8
|
+
tle = TLE(line0="LINE0", line1="LINE1", line2="LINE2")
|
|
9
|
+
|
|
10
|
+
assert tle.to_list() == ["LINE0", "LINE1", "LINE2"]
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
class TestSatelliteFromTle:
|
|
7
14
|
def test_given_no_identifier_when_created_then_uses_line0(self) -> None:
|
|
8
15
|
line0 = "ISS (ZARYA)"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from skyfield.api import EarthSatellite, load # type: ignore[import-untyped]
|
|
7
|
+
|
|
8
|
+
from satnogs_predict import generate_fake_tle
|
|
9
|
+
|
|
10
|
+
ts = load.timescale()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _checksum(line_without_checksum: str) -> int:
|
|
14
|
+
total = 0
|
|
15
|
+
for char in line_without_checksum:
|
|
16
|
+
if char.isdigit():
|
|
17
|
+
total += int(char)
|
|
18
|
+
elif char == "-":
|
|
19
|
+
total += 1
|
|
20
|
+
return total % 10
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestGenerateFakeTle:
|
|
24
|
+
def test_given_valid_input_when_generated_then_returns_parseable_tle(self) -> None:
|
|
25
|
+
tle = generate_fake_tle(
|
|
26
|
+
latitude=37.9838,
|
|
27
|
+
longitude=23.7275,
|
|
28
|
+
date=datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
assert len(tle.line1) == 69
|
|
32
|
+
assert len(tle.line2) == 69
|
|
33
|
+
assert tle.line1[-1] == str(_checksum(tle.line1[:-1]))
|
|
34
|
+
assert tle.line2[-1] == str(_checksum(tle.line2[:-1]))
|
|
35
|
+
|
|
36
|
+
sat = EarthSatellite(tle.line1, tle.line2, tle.line0, ts)
|
|
37
|
+
assert sat.name == tle.line0.strip()
|
|
38
|
+
|
|
39
|
+
def test_given_same_input_when_generated_twice_then_result_is_deterministic(self) -> None:
|
|
40
|
+
date = datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc)
|
|
41
|
+
|
|
42
|
+
tle1 = generate_fake_tle(latitude=10.0, longitude=20.0, date=date)
|
|
43
|
+
tle2 = generate_fake_tle(latitude=10.0, longitude=20.0, date=date)
|
|
44
|
+
|
|
45
|
+
assert tle1 == tle2
|
|
46
|
+
|
|
47
|
+
def test_given_naive_date_when_generated_then_raises(self) -> None:
|
|
48
|
+
with pytest.raises(ValueError, match="timezone-aware"):
|
|
49
|
+
generate_fake_tle(
|
|
50
|
+
latitude=0.0,
|
|
51
|
+
longitude=0.0,
|
|
52
|
+
date=datetime(2026, 3, 6, 12, 0, 0),
|
|
53
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{satnogs_predict-0.2 → satnogs_predict-0.3}/src/satnogs_predict.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|