satnogs-predict 0.6__tar.gz → 1.1__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-1.1/CHANGELOG.md +19 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/CONTRIBUTING.md +88 -10
- {satnogs_predict-0.6 → satnogs_predict-1.1}/PKG-INFO +3 -1
- {satnogs_predict-0.6 → satnogs_predict-1.1}/README.md +2 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/docs/docs.md +21 -22
- satnogs_predict-1.1/src/satnogs_predict/domain/observer.py +35 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/orbit.py +37 -12
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/window.py +3 -3
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/propagation/propagator.py +13 -14
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/tle.py +3 -3
- satnogs_predict-1.1/src/satnogs_predict/utils.py +7 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict.egg-info/PKG-INFO +3 -1
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict.egg-info/SOURCES.txt +4 -0
- satnogs_predict-1.1/src/satnogs_predict.egg-info/scm_file_list.json +52 -0
- satnogs_predict-1.1/src/satnogs_predict.egg-info/scm_version.json +8 -0
- satnogs_predict-1.1/tests/domain/test_observer.py +59 -0
- satnogs_predict-1.1/tests/domain/test_orbit.py +131 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/domain/test_required_fields.py +0 -11
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/helpers.py +24 -11
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/integration/test_fake_tle_integration.py +7 -4
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/propagation/test_propagator.py +6 -10
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/test_tle.py +20 -0
- satnogs_predict-0.6/src/satnogs_predict/domain/observer.py +0 -22
- satnogs_predict-0.6/tests/domain/test_observer.py +0 -44
- satnogs_predict-0.6/tests/domain/test_orbit.py +0 -146
- {satnogs_predict-0.6 → satnogs_predict-1.1}/.gitignore +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/.gitlab-ci.yml +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/.pre-commit-config.yaml +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/.python-version +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/LICENSE +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/pyproject.toml +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/setup.cfg +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/setup.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/__init__.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/constraints/__init__.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/constraints/constraints.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/core/__init__.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/core/engine.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/__init__.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/constraints.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/geometry.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/planner.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/time.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/domain/validation.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/planning/__init__.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/planning/planner.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/propagation/__init__.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict/py.typed +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict.egg-info/dependency_links.txt +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict.egg-info/requires.txt +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/src/satnogs_predict.egg-info/top_level.txt +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/__init__.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/conftest.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/constraints/test_constraints.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/core/test_engine.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/domain/test_planner_domain.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/domain/test_time.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/domain/test_window.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/tests/planning/test_planner.py +0 -0
- {satnogs_predict-0.6 → satnogs_predict-1.1}/uv.lock +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- `Satellite.identifier` no longer defaults to a generated UUID. It now defaults to an empty string and is treated as caller metadata.
|
|
8
|
+
- `Observer.identifier` no longer defaults to a generated UUID. It now defaults to an empty string and is treated as caller metadata.
|
|
9
|
+
- Skyfield satellite and observer caches **no longer use identifiers as cache keys**. Satellite cache identity is based on orbital data, and observer cache identity is based on latitude, longitude, and elevation.
|
|
10
|
+
- `OMM.fields` is now stored as a read-only mapping. Use `OMM.to_dict()` when a mutable dictionary is needed.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added `cache_key` properties for `TLE`, `OMM`, `Orbit`, `Satellite`, and `Observer`.
|
|
15
|
+
- Added a shared internal `cache_digest()` helper.
|
|
16
|
+
|
|
17
|
+
### Improved
|
|
18
|
+
|
|
19
|
+
- It is no longer the caller's responsibility to manage cache by using identifiers.
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* [Respond to review comments](#respond-to-review-comments)
|
|
12
12
|
* [Don’t get discouraged - or impatient](#dont-get-discouraged-or-impatient)
|
|
13
13
|
* [Sign your work - the Developer’s Certificate of Origin](#sign-your-work-the-developers-certificate-of-origin)
|
|
14
|
+
* [Generative AI / LLM Policy](#generative-ai--llm-policy)
|
|
14
15
|
* [References](#references)
|
|
15
16
|
|
|
16
17
|
## Introduction ##
|
|
@@ -22,7 +23,7 @@ It is based on Linux kernel patch submission guidelines.
|
|
|
22
23
|
|
|
23
24
|
All Libre Space Foundation projects code is maintained under Git.
|
|
24
25
|
Obtain a copy of the source code using `git clone`.
|
|
25
|
-
Development usually takes place under `master` or `dev` branch.
|
|
26
|
+
Development usually takes place under `main`, `master` or `dev` branch.
|
|
26
27
|
|
|
27
28
|
## Separate the changes ##
|
|
28
29
|
|
|
@@ -39,16 +40,16 @@ The point to remember is that each commit should make an easily understood chang
|
|
|
39
40
|
Each commit should be justifiable on its own merits.
|
|
40
41
|
|
|
41
42
|
When creating a merge request from a series of commits, take special care to ensure that the software builds and runs properly after each commit in the series.
|
|
42
|
-
Developers using `git bisect` to track down a problem can end up splitting your merge request at any point;
|
|
43
|
+
Developers using `git bisect` to track down a problem can end up splitting your merge request at any point; not thanking you if you introduce bugs in the middle.
|
|
43
44
|
|
|
44
45
|
If you cannot condense your merge request into a smaller set of commits, then only submit say 15 or so at a time and wait for review and integration.
|
|
45
46
|
|
|
46
|
-
Never commit binary files.
|
|
47
|
+
Never commit binary files or statically generated code.
|
|
47
48
|
Once they are part of Git repository history they cannot be removed.
|
|
48
49
|
|
|
49
50
|
## Quality check of the changes ##
|
|
50
51
|
|
|
51
|
-
Continuous Integration
|
|
52
|
+
Continuous Integration checks your changes automatically once you create a merge request.
|
|
52
53
|
Nevertheless, local quality check should also be done before submitting a merge request.
|
|
53
54
|
The checks can include:
|
|
54
55
|
* Static code analysis and coverage
|
|
@@ -78,10 +79,10 @@ See [Separate the changes](#separate-the-changes).
|
|
|
78
79
|
|
|
79
80
|
Create commit or merge requests descriptions that are self-contained.
|
|
80
81
|
This benefits both the maintainers and reviewers.
|
|
81
|
-
Always use
|
|
82
|
+
Always use a known identity and email address for committing changes.
|
|
82
83
|
|
|
83
84
|
If the commit fixes a reported issue, refer to that bug entry by issue number.
|
|
84
|
-
The issue
|
|
85
|
+
The issue closes automatically after merge, if it matches [this regular expression](https://docs.gitlab.com/ce/user/project/issues/automatic_issue_closing.html).
|
|
85
86
|
|
|
86
87
|
## Create a merge request ##
|
|
87
88
|
|
|
@@ -91,7 +92,7 @@ Merge requests must not contain other merge requests; re-base your commits if ne
|
|
|
91
92
|
|
|
92
93
|
## Respond to review comments ##
|
|
93
94
|
|
|
94
|
-
Your merge request
|
|
95
|
+
Your merge request almost certainly gets comments from reviewers on ways in which it can be improved.
|
|
95
96
|
You must respond to those comments; ignoring reviewers is a good way to get ignored in return.
|
|
96
97
|
Be sure to tell the reviewers what changes you are making and to thank them for their time.
|
|
97
98
|
Code review is a tiring and time-consuming process, and reviewers sometimes get grumpy.
|
|
@@ -106,9 +107,9 @@ Wait for a minimum of one week before pinging reviewers - possibly longer during
|
|
|
106
107
|
|
|
107
108
|
## Sign your work - the Developer’s Certificate of Origin ##
|
|
108
109
|
|
|
109
|
-
To improve tracking of who did what, especially with commits that can percolate to their final resting place in Libre Space Foundation projects code-base through several layers of maintainers,
|
|
110
|
+
To improve tracking of who did what, especially with commits that can percolate to their final resting place in Libre Space Foundation projects code-base through several layers of maintainers, a “sign-off” procedure on commits is enforced.
|
|
110
111
|
|
|
111
|
-
The sign-off is a simple line at the end of the explanation for the commit, which certifies that you wrote it or otherwise have the right to pass it on as an open
|
|
112
|
+
The sign-off is a simple line at the end of the explanation for the commit, which certifies that you wrote it or otherwise have the right to pass it on as an open source patch.
|
|
112
113
|
The rules are pretty simple:
|
|
113
114
|
|
|
114
115
|
if you can certify the below:
|
|
@@ -138,7 +139,7 @@ then you just add a line saying:
|
|
|
138
139
|
|
|
139
140
|
`Signed-off-by: Random J Developer <random@developer.example.org>`
|
|
140
141
|
|
|
141
|
-
using
|
|
142
|
+
using a known identity (sorry, no anonymous contributions.)
|
|
142
143
|
|
|
143
144
|
If you are a maintainer, sometimes you need to slightly modify commits order to merge them.
|
|
144
145
|
If you stick strictly to rule (c), you should ask the submitter to rediff, but this is a totally counter-productive waste of time and energy.
|
|
@@ -152,6 +153,83 @@ Signed-off-by: Random J Developer <random@developer.example.org>
|
|
|
152
153
|
Signed-off-by: Lucky K Maintainer <lucky@maintainer.example.org>
|
|
153
154
|
```
|
|
154
155
|
|
|
156
|
+
## Generative AI / LLM Policy ##
|
|
157
|
+
|
|
158
|
+
We appreciate that we can't realistically police how you author your merge requests, which includes whether you employ large-language model (LLM)-based development tools.
|
|
159
|
+
So, we don't.
|
|
160
|
+
|
|
161
|
+
However, due to both legal and human reasons, we have to establish boundaries.
|
|
162
|
+
|
|
163
|
+
### Overview ###
|
|
164
|
+
|
|
165
|
+
- We take the responsibility for this project very seriously and we expect you to take your responsibility for your contributions seriously, too.
|
|
166
|
+
This used to be a given, but it changed now that a merge request is just one prompt away.
|
|
167
|
+
- Every contribution has to be backed by a human who unequivocally owns the copyright for all changes.
|
|
168
|
+
No LLM bots in `Co-authored-by:`s.
|
|
169
|
+
- Repeated slop contributions are unacceptable, and will be closed without review.
|
|
170
|
+
We reserve the right to exclude contributors who continue to submit such material.
|
|
171
|
+
- Absolutely no unsupervised agentic tools like OpenClaw.
|
|
172
|
+
|
|
173
|
+
By submitting a merge request, you certify that:
|
|
174
|
+
|
|
175
|
+
- You are the author of the contribution or have the legal right to submit it.
|
|
176
|
+
- You either hold the copyright to the changes or have explicit legal authorization to contribute them under this project's license.
|
|
177
|
+
- You understand the code.
|
|
178
|
+
- You accept full responsibility for it.
|
|
179
|
+
|
|
180
|
+
### Legal ###
|
|
181
|
+
|
|
182
|
+
There is ongoing legal uncertainty regarding the copyright status of LLM-generated works and their provenance.
|
|
183
|
+
Because of this, allowing contributions by LLMs has unpredictable consequences for the copyright status of this project - even when leaving aside possible copyright violations due to plagiarism.
|
|
184
|
+
|
|
185
|
+
### Human ###
|
|
186
|
+
|
|
187
|
+
As the makers of software that is used by many people worldwide and with a reputation for high-quality maintenance, we take our responsibility to our users very seriously.
|
|
188
|
+
No matter what LLM vendors or boosters on LinkedIn tell you, we have to manually review every change before merging, because it's our responsibility to keep the project stable.
|
|
189
|
+
|
|
190
|
+
Please understand that by opening low-quality merge requests you're not helping anyone.
|
|
191
|
+
Worse, you're [poisoning the open source ecosystem](https://lwn.net/Articles/1058266/) that was precarious even before the arrival of LLM tools.
|
|
192
|
+
Having to wade through plausible-looking-but-low-quality merge requests and trying to determine which ones are legit is extremely demoralizing and has already burned out many good maintainers.
|
|
193
|
+
|
|
194
|
+
In particular, note that [code reviews are a social process](https://blog.glyph.im/2026/03/what-is-code-review-for.html) and are *not* intended to catch bugs in PRs created by an LLM.
|
|
195
|
+
If you submit a merge request, we expect you to engage with reviewers and learn from the review, not
|
|
196
|
+
simply update the prompt that generated the PR and regenerate the code.
|
|
197
|
+
|
|
198
|
+
Put bluntly, we have no time or interest to become part of your vibe coding loop where you drop LLM slop at our door, we spend time and energy to review it, and you just feed it back into the LLM for another iteration.
|
|
199
|
+
|
|
200
|
+
This dynamic is especially pernicious because it poisons the well for mentoring new contributors which we are committed to.
|
|
201
|
+
|
|
202
|
+
### Summary ###
|
|
203
|
+
|
|
204
|
+
In practice, this means:
|
|
205
|
+
|
|
206
|
+
- Merge requests that have an LLM product listed as co-author can't be merged and will be closed without further discussion.
|
|
207
|
+
We cannot risk the copyright status of this project.
|
|
208
|
+
|
|
209
|
+
If you used LLM tools during development, you may still submit - but you must remove any LLM co-author tags and take full ownership of every line.
|
|
210
|
+
|
|
211
|
+
- By submitting a merge request, you take full technical and legal responsibility for the contents of the merge request and promise that you hold the copyright for the changes submitted.
|
|
212
|
+
|
|
213
|
+
"An LLM wrote it" is *not* an acceptable response to questions or critique.
|
|
214
|
+
If you cannot explain and defend the changes you submit, do not submit them and open a high-quality bug report or feature request instead.
|
|
215
|
+
|
|
216
|
+
- Accounts that exercise bot-like behavior, like automated mass merge requests, will be permanently banned, whether they belong to a human or not.
|
|
217
|
+
|
|
218
|
+
- LLM-generated comments must be concise and accurate, and you must be prepared to stand by them.
|
|
219
|
+
Do not post summaries unless you are certain that they add value to the discussion.
|
|
220
|
+
Remember that all LLM output *looks* plausible.
|
|
221
|
+
When using these tools, it's your responsibility to ensure that the output is correct, and useful.
|
|
222
|
+
|
|
223
|
+
- Remember that LLM generated content is generally easier for you to produce, but *harder* for others to read, review or interpret.
|
|
224
|
+
Prioritising your time over that of others is contrary to the project's code of conduct.
|
|
225
|
+
Verbose, repetitive, or off topic comments may be marked as spam.
|
|
226
|
+
|
|
227
|
+
### Thanks ###
|
|
228
|
+
|
|
229
|
+
This LLM policy was based on the policy of the `pip` and `attrs` project.
|
|
230
|
+
Many thanks to them for developing it.
|
|
231
|
+
|
|
155
232
|
## References ##
|
|
156
233
|
|
|
157
234
|
* Linux kernel documentation - [Submitting patches: the essential guide to getting your code into the kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html)
|
|
235
|
+
* pip AI policy - [Generative AI / LLM Policy](https://github.com/pypa/pip/blob/main/AI_POLICY.md)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: satnogs-predict
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1
|
|
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
|
|
@@ -46,6 +46,8 @@ from satnogs_predict import find_observation_windows
|
|
|
46
46
|
|
|
47
47
|
For more complete examples and usage patterns, see [docs/docs.md](docs/docs.md).
|
|
48
48
|
|
|
49
|
+
For release notes and breaking changes, see [CHANGELOG.md](CHANGELOG.md).
|
|
50
|
+
|
|
49
51
|
## License
|
|
50
52
|
|
|
51
53
|
[](LICENSE)
|
|
@@ -36,6 +36,8 @@ from satnogs_predict import find_observation_windows
|
|
|
36
36
|
|
|
37
37
|
For more complete examples and usage patterns, see [docs/docs.md](docs/docs.md).
|
|
38
38
|
|
|
39
|
+
For release notes and breaking changes, see [CHANGELOG.md](CHANGELOG.md).
|
|
40
|
+
|
|
39
41
|
## License
|
|
40
42
|
|
|
41
43
|
[](LICENSE)
|
|
@@ -20,10 +20,9 @@
|
|
|
20
20
|
- [6 - Finding passes](#6---finding-passes)
|
|
21
21
|
- [6.1 - Constraints](#61---constraints)
|
|
22
22
|
- [6.2 - Windows returned](#62---windows-returned)
|
|
23
|
-
- [6.3 -
|
|
24
|
-
- [6.4 -
|
|
25
|
-
- [6.5 -
|
|
26
|
-
- [6.6 - Full workflow for validating time ranges that belong to passes](#66---full-workflow-for-validating-time-ranges-that-belong-to-passes)
|
|
23
|
+
- [6.3 - Full workflow for finding passes](#63---full-workflow-for-finding-passes)
|
|
24
|
+
- [6.4 - Sampling point-in-time geometry](#64---sampling-point-in-time-geometry)
|
|
25
|
+
- [6.5 - Full workflow for validating time ranges that belong to passes](#65---full-workflow-for-validating-time-ranges-that-belong-to-passes)
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
# 1. Installation
|
|
@@ -102,8 +101,12 @@ satellite = Satellite.from_omm(
|
|
|
102
101
|
)
|
|
103
102
|
```
|
|
104
103
|
|
|
105
|
-
The `identifier` is optional and defaults to
|
|
106
|
-
|
|
104
|
+
The `identifier` is optional metadata and defaults to an empty string. It is copied
|
|
105
|
+
to produced `ObservationWindow.sat_identifier` values so callers can conveniently connect windows back to their own satellite records.
|
|
106
|
+
|
|
107
|
+
Because the underlying skyfield object EarthSatellite is computationally expensive to create, the EarthSatellite of the last-used satellite is cached. Cache identity is based on orbital state: TLE `line1`/`line2`, or normalized OMM fields.
|
|
108
|
+
|
|
109
|
+
OMM field data are copied when the `OMM` object is created. The stored mapping is read-only, and `to_dict()` returns a mutable copy.
|
|
107
110
|
|
|
108
111
|
For testing or development, you can also generate a synthetic TLE:
|
|
109
112
|
|
|
@@ -133,8 +136,11 @@ station = Observer(
|
|
|
133
136
|
)
|
|
134
137
|
```
|
|
135
138
|
|
|
136
|
-
The `identifier` is optional
|
|
137
|
-
|
|
139
|
+
The `identifier` is optional metadata and defaults to an empty string. It is copied
|
|
140
|
+
to produced `ObservationWindow.observer_identifier` for convenience.
|
|
141
|
+
|
|
142
|
+
Because the underlying skyfield object Topos that represents an observer is computationally expensive, the Topos of the last-used observer is cached.
|
|
143
|
+
Observer cache identity depends on `lat_deg`, `lon_deg` and `elevation_meters`. The `horizon_deg` value is not part of the Topos object so it doesn't affect caching.
|
|
138
144
|
|
|
139
145
|
|
|
140
146
|
## 3.3 - Time Modeling
|
|
@@ -248,13 +254,14 @@ class ObservationWindow:
|
|
|
248
254
|
A schedulable observation window.
|
|
249
255
|
"""
|
|
250
256
|
|
|
251
|
-
sat_identifier: str
|
|
252
|
-
observer_identifier: str
|
|
253
|
-
|
|
254
257
|
start_sample: GeometrySample
|
|
255
258
|
end_sample: GeometrySample
|
|
256
259
|
max_altitude_sample: GeometrySample
|
|
257
260
|
|
|
261
|
+
# Caller metadata copied from Satellite.identifier and Observer.identifier.
|
|
262
|
+
sat_identifier: str = ""
|
|
263
|
+
observer_identifier: str = ""
|
|
264
|
+
|
|
258
265
|
# The altitude has multiple peaks during the window (None means unknown)
|
|
259
266
|
has_multiple_peaks: bool | None = None
|
|
260
267
|
|
|
@@ -404,15 +411,7 @@ For the helper `get_and_validate_pass_from_range`, constraints are validated via
|
|
|
404
411
|
|
|
405
412
|
The function returns a tuple of observation window lists. The first element of the tuple is the list of valid windows, and the second is the list of windows that were rejected due to constraints.
|
|
406
413
|
|
|
407
|
-
## 6.3 -
|
|
408
|
-
|
|
409
|
-
Internally, satellite & observer are converted to Skyfield's types, which is computationally expensive. For this reason, the last satellite and the last observer used is cached, differentiated by the `identifier` field.
|
|
410
|
-
|
|
411
|
-
This way, repeated consecutive calls of `find_observation_windows` with the same satellite, or with the same `observer` reuse the objects under the hood.
|
|
412
|
-
|
|
413
|
-
If you need to clear these caches explicitly, the public helpers `clear_satellite_cache()` and `clear_observer_cache()` are available.
|
|
414
|
-
|
|
415
|
-
## 6.4 - Full workflow for finding passes
|
|
414
|
+
## 6.3 - Full workflow for finding passes
|
|
416
415
|
|
|
417
416
|
High-level process:
|
|
418
417
|
|
|
@@ -484,7 +483,7 @@ valid_windows, rejected_windows = find_observation_windows(
|
|
|
484
483
|
)
|
|
485
484
|
```
|
|
486
485
|
|
|
487
|
-
## 6.
|
|
486
|
+
## 6.4 - Sampling point-in-time geometry
|
|
488
487
|
|
|
489
488
|
For point-in-time queries, the public API also provides helpers for sampling a satellite relative to an observer at a single `Instant`.
|
|
490
489
|
|
|
@@ -506,7 +505,7 @@ range_sample = get_range(
|
|
|
506
505
|
- `get_range()` returns a `RangeSample` with range in meters and range rate in meters per second.
|
|
507
506
|
|
|
508
507
|
|
|
509
|
-
## 6.
|
|
508
|
+
## 6.5 - Full workflow for validating time ranges that belong to passes
|
|
510
509
|
|
|
511
510
|
- Given a time range, we can validate whether it belongs to a single pass and whether it satisfies constraints.
|
|
512
511
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from satnogs_predict.utils import cache_digest
|
|
6
|
+
|
|
7
|
+
from .validation import ensure_required_fields_not_none
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class Observer:
|
|
12
|
+
"""Observer to calculate passes over e.g. a ground station"""
|
|
13
|
+
|
|
14
|
+
lat_deg: float
|
|
15
|
+
lon_deg: float
|
|
16
|
+
elevation_meters: int
|
|
17
|
+
horizon_deg: float
|
|
18
|
+
identifier: str = ""
|
|
19
|
+
_cache_key: str = field(init=False, repr=False, compare=False, default="")
|
|
20
|
+
|
|
21
|
+
def __post_init__(self) -> None:
|
|
22
|
+
ensure_required_fields_not_none(self)
|
|
23
|
+
object.__setattr__(
|
|
24
|
+
self,
|
|
25
|
+
"_cache_key",
|
|
26
|
+
cache_digest(
|
|
27
|
+
str(self.lat_deg),
|
|
28
|
+
str(self.lon_deg),
|
|
29
|
+
str(self.elevation_meters),
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def cache_key(self) -> str:
|
|
35
|
+
return self._cache_key
|
|
@@ -3,7 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from enum import IntEnum
|
|
6
|
-
from
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
|
|
8
|
+
from satnogs_predict.utils import cache_digest
|
|
7
9
|
|
|
8
10
|
from .validation import ensure_required_fields_not_none
|
|
9
11
|
|
|
@@ -13,24 +15,41 @@ class TLE:
|
|
|
13
15
|
line0: str
|
|
14
16
|
line1: str
|
|
15
17
|
line2: str
|
|
18
|
+
_cache_key: str = field(init=False, repr=False, compare=False, default="")
|
|
16
19
|
|
|
17
20
|
def __post_init__(self) -> None:
|
|
18
21
|
ensure_required_fields_not_none(self)
|
|
22
|
+
object.__setattr__(self, "_cache_key", cache_digest(self.line1, self.line2))
|
|
19
23
|
|
|
20
24
|
def to_list(self) -> list[str]:
|
|
21
25
|
return [self.line0, self.line1, self.line2]
|
|
22
26
|
|
|
27
|
+
@property
|
|
28
|
+
def cache_key(self) -> str:
|
|
29
|
+
return self._cache_key
|
|
30
|
+
|
|
23
31
|
|
|
24
32
|
@dataclass(frozen=True, slots=True)
|
|
25
33
|
class OMM:
|
|
26
|
-
fields:
|
|
34
|
+
fields: Mapping[str, str]
|
|
35
|
+
_cache_key: str = field(init=False, repr=False, compare=False, default="")
|
|
27
36
|
|
|
28
37
|
def __post_init__(self) -> None:
|
|
29
38
|
ensure_required_fields_not_none(self)
|
|
39
|
+
fields = MappingProxyType(dict(self.fields))
|
|
40
|
+
object.__setattr__(self, "fields", fields)
|
|
41
|
+
parts: list[str] = []
|
|
42
|
+
for key, value in sorted(fields.items()):
|
|
43
|
+
parts.extend((key, value))
|
|
44
|
+
object.__setattr__(self, "_cache_key", cache_digest(*parts))
|
|
30
45
|
|
|
31
46
|
def to_dict(self) -> dict[str, str]:
|
|
32
47
|
return dict(self.fields)
|
|
33
48
|
|
|
49
|
+
@property
|
|
50
|
+
def cache_key(self) -> str:
|
|
51
|
+
return self._cache_key
|
|
52
|
+
|
|
34
53
|
|
|
35
54
|
class OrbitRepresentation(IntEnum):
|
|
36
55
|
TLE = 0
|
|
@@ -41,27 +60,37 @@ class OrbitRepresentation(IntEnum):
|
|
|
41
60
|
class Orbit:
|
|
42
61
|
representation: OrbitRepresentation
|
|
43
62
|
data: TLE | OMM
|
|
63
|
+
_cache_key: str = field(init=False, repr=False, compare=False, default="")
|
|
44
64
|
|
|
45
65
|
def __post_init__(self) -> None:
|
|
46
66
|
ensure_required_fields_not_none(self)
|
|
67
|
+
object.__setattr__(self, "_cache_key", self.data.cache_key)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def cache_key(self) -> str:
|
|
71
|
+
return self._cache_key
|
|
47
72
|
|
|
48
73
|
@classmethod
|
|
49
74
|
def from_omm(
|
|
50
75
|
cls,
|
|
51
76
|
fields: Mapping[str, str],
|
|
52
77
|
):
|
|
53
|
-
return cls(representation=OrbitRepresentation.OMM, data=OMM(fields=
|
|
78
|
+
return cls(representation=OrbitRepresentation.OMM, data=OMM(fields=fields))
|
|
54
79
|
|
|
55
80
|
|
|
56
81
|
@dataclass(frozen=True, slots=True)
|
|
57
82
|
class Satellite:
|
|
58
83
|
orbit: Orbit
|
|
59
|
-
identifier: str =
|
|
84
|
+
identifier: str = ""
|
|
85
|
+
_cache_key: str = field(init=False, repr=False, compare=False, default="")
|
|
60
86
|
|
|
61
87
|
def __post_init__(self) -> None:
|
|
62
|
-
if self.identifier is None:
|
|
63
|
-
object.__setattr__(self, "identifier", str(uuid4()))
|
|
64
88
|
ensure_required_fields_not_none(self)
|
|
89
|
+
object.__setattr__(self, "_cache_key", self.orbit.cache_key)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def cache_key(self) -> str:
|
|
93
|
+
return self._cache_key
|
|
65
94
|
|
|
66
95
|
@classmethod
|
|
67
96
|
def from_tle(
|
|
@@ -69,21 +98,17 @@ class Satellite:
|
|
|
69
98
|
line0: str,
|
|
70
99
|
line1: str,
|
|
71
100
|
line2: str,
|
|
72
|
-
identifier: str
|
|
101
|
+
identifier: str = "",
|
|
73
102
|
):
|
|
74
103
|
tle = TLE(line0=line0, line1=line1, line2=line2)
|
|
75
104
|
orbit = Orbit(representation=OrbitRepresentation.TLE, data=tle)
|
|
76
|
-
if identifier is None:
|
|
77
|
-
return cls(orbit=orbit)
|
|
78
105
|
return cls(orbit=orbit, identifier=identifier)
|
|
79
106
|
|
|
80
107
|
@classmethod
|
|
81
108
|
def from_omm(
|
|
82
109
|
cls,
|
|
83
110
|
fields: Mapping[str, str],
|
|
84
|
-
identifier: str
|
|
111
|
+
identifier: str = "",
|
|
85
112
|
):
|
|
86
113
|
orbit = Orbit.from_omm(fields)
|
|
87
|
-
if identifier is None:
|
|
88
|
-
return cls(orbit=orbit)
|
|
89
114
|
return cls(orbit=orbit, identifier=identifier)
|
|
@@ -19,13 +19,13 @@ class ObservationWindow:
|
|
|
19
19
|
A schedulable observation window.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
sat_identifier: str
|
|
23
|
-
observer_identifier: str
|
|
24
|
-
|
|
25
22
|
start_sample: GeometrySample
|
|
26
23
|
end_sample: GeometrySample
|
|
27
24
|
max_altitude_sample: GeometrySample
|
|
28
25
|
|
|
26
|
+
sat_identifier: str = ""
|
|
27
|
+
observer_identifier: str = ""
|
|
28
|
+
|
|
29
29
|
# The altitude does has multiple peaks during the window (None means unknown)
|
|
30
30
|
has_multiple_peaks: bool | None = None
|
|
31
31
|
|
|
@@ -65,20 +65,20 @@ def _instant_from_skyfield_time(t: Time):
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
_last_sat: EarthSatellite | None = None
|
|
68
|
-
|
|
68
|
+
_last_sat_cache_key: str | None = None
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def clear_satellite_cache() -> None:
|
|
72
|
-
global _last_sat,
|
|
72
|
+
global _last_sat, _last_sat_cache_key
|
|
73
73
|
|
|
74
74
|
_last_sat = None
|
|
75
|
-
|
|
75
|
+
_last_sat_cache_key = None
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def _skyfield_sat_from_satellite(satellite: Satellite) -> EarthSatellite:
|
|
79
|
-
global _last_sat,
|
|
79
|
+
global _last_sat, _last_sat_cache_key
|
|
80
80
|
|
|
81
|
-
if
|
|
81
|
+
if _last_sat_cache_key == satellite.cache_key and _last_sat is not None:
|
|
82
82
|
return _last_sat
|
|
83
83
|
|
|
84
84
|
match satellite.orbit.representation:
|
|
@@ -87,8 +87,7 @@ def _skyfield_sat_from_satellite(satellite: Satellite) -> EarthSatellite:
|
|
|
87
87
|
skyfield_sat = EarthSatellite(
|
|
88
88
|
tle.line1,
|
|
89
89
|
tle.line2,
|
|
90
|
-
|
|
91
|
-
ts,
|
|
90
|
+
ts=ts,
|
|
92
91
|
)
|
|
93
92
|
case OrbitRepresentation.OMM:
|
|
94
93
|
omm = cast(OMM, satellite.orbit.data)
|
|
@@ -96,26 +95,26 @@ def _skyfield_sat_from_satellite(satellite: Satellite) -> EarthSatellite:
|
|
|
96
95
|
case _:
|
|
97
96
|
raise ValueError("Unknown orbit representation")
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
_last_sat_cache_key = satellite.cache_key
|
|
100
99
|
_last_sat = skyfield_sat
|
|
101
100
|
return skyfield_sat
|
|
102
101
|
|
|
103
102
|
|
|
104
103
|
_last_topos: Topos | None = None
|
|
105
|
-
|
|
104
|
+
_last_observer_cache_key: str | None = None
|
|
106
105
|
|
|
107
106
|
|
|
108
107
|
def clear_observer_cache() -> None:
|
|
109
|
-
global _last_topos,
|
|
108
|
+
global _last_topos, _last_observer_cache_key
|
|
110
109
|
|
|
111
110
|
_last_topos = None
|
|
112
|
-
|
|
111
|
+
_last_observer_cache_key = None
|
|
113
112
|
|
|
114
113
|
|
|
115
114
|
def _skyfield_topos_from_observer(observer: Observer) -> Topos:
|
|
116
|
-
global _last_topos,
|
|
115
|
+
global _last_topos, _last_observer_cache_key
|
|
117
116
|
|
|
118
|
-
if
|
|
117
|
+
if _last_observer_cache_key == observer.cache_key and _last_topos is not None:
|
|
119
118
|
return _last_topos
|
|
120
119
|
|
|
121
120
|
_last_topos = Topos(
|
|
@@ -123,7 +122,7 @@ def _skyfield_topos_from_observer(observer: Observer) -> Topos:
|
|
|
123
122
|
longitude_degrees=observer.lon_deg,
|
|
124
123
|
elevation_m=observer.elevation_meters,
|
|
125
124
|
)
|
|
126
|
-
|
|
125
|
+
_last_observer_cache_key = observer.cache_key
|
|
127
126
|
return _last_topos
|
|
128
127
|
|
|
129
128
|
|
|
@@ -71,17 +71,17 @@ def generate_fake_tle(
|
|
|
71
71
|
right_ascension = (observer_location.lst_hours_at(skyfield_time) * 15.0) % 360.0
|
|
72
72
|
mean_anomaly = latitude % 360.0
|
|
73
73
|
|
|
74
|
-
epoch_year = epoch.year
|
|
74
|
+
epoch_year = epoch.year % 100
|
|
75
75
|
epoch_day = _epoch_day_of_year_fraction(epoch)
|
|
76
76
|
|
|
77
77
|
line0 = params.name.ljust(24)
|
|
78
78
|
|
|
79
79
|
line1_no_checksum = (
|
|
80
|
-
"1 {:5.5}{:.1} {:8.8} {
|
|
80
|
+
"1 {:5.5}{:.1} {:8.8} {:02d}{:012.8f} {:>10.10} {:>8.8} {:>8.8} 0 {:>4.4}".format(
|
|
81
81
|
f"{params.catalog_num:05d}",
|
|
82
82
|
params.classifier,
|
|
83
83
|
params.cospar_id,
|
|
84
|
-
|
|
84
|
+
epoch_year,
|
|
85
85
|
epoch_day,
|
|
86
86
|
params.mean_motion_first,
|
|
87
87
|
params.mean_motion_second,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: satnogs-predict
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1
|
|
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
|
|
@@ -46,6 +46,8 @@ from satnogs_predict import find_observation_windows
|
|
|
46
46
|
|
|
47
47
|
For more complete examples and usage patterns, see [docs/docs.md](docs/docs.md).
|
|
48
48
|
|
|
49
|
+
For release notes and breaking changes, see [CHANGELOG.md](CHANGELOG.md).
|
|
50
|
+
|
|
49
51
|
## License
|
|
50
52
|
|
|
51
53
|
[](LICENSE)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
.gitlab-ci.yml
|
|
3
3
|
.pre-commit-config.yaml
|
|
4
4
|
.python-version
|
|
5
|
+
CHANGELOG.md
|
|
5
6
|
CONTRIBUTING.md
|
|
6
7
|
LICENSE
|
|
7
8
|
README.md
|
|
@@ -12,10 +13,13 @@ docs/docs.md
|
|
|
12
13
|
src/satnogs_predict/__init__.py
|
|
13
14
|
src/satnogs_predict/py.typed
|
|
14
15
|
src/satnogs_predict/tle.py
|
|
16
|
+
src/satnogs_predict/utils.py
|
|
15
17
|
src/satnogs_predict.egg-info/PKG-INFO
|
|
16
18
|
src/satnogs_predict.egg-info/SOURCES.txt
|
|
17
19
|
src/satnogs_predict.egg-info/dependency_links.txt
|
|
18
20
|
src/satnogs_predict.egg-info/requires.txt
|
|
21
|
+
src/satnogs_predict.egg-info/scm_file_list.json
|
|
22
|
+
src/satnogs_predict.egg-info/scm_version.json
|
|
19
23
|
src/satnogs_predict.egg-info/top_level.txt
|
|
20
24
|
src/satnogs_predict/constraints/__init__.py
|
|
21
25
|
src/satnogs_predict/constraints/constraints.py
|