satnogs-predict 0.2__tar.gz → 0.4__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.4}/PKG-INFO +4 -3
- {satnogs_predict-0.2 → satnogs_predict-0.4}/README.md +3 -2
- {satnogs_predict-0.2 → satnogs_predict-0.4}/docs/docs.md +144 -6
- satnogs_predict-0.4/src/satnogs_predict/__init__.py +24 -0
- satnogs_predict-0.4/src/satnogs_predict/constraints/__init__.py +15 -0
- satnogs_predict-0.4/src/satnogs_predict/constraints/constraints.py +433 -0
- satnogs_predict-0.4/src/satnogs_predict/core/__init__.py +3 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/core/engine.py +37 -9
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/__init__.py +4 -2
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/constraints.py +10 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/geometry.py +9 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/orbit.py +12 -6
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/time.py +16 -12
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/window.py +1 -1
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/planning/planner.py +2 -2
- satnogs_predict-0.4/src/satnogs_predict/propagation/__init__.py +19 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/propagation/propagator.py +77 -1
- satnogs_predict-0.4/src/satnogs_predict/tle.py +107 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict.egg-info/PKG-INFO +4 -3
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict.egg-info/SOURCES.txt +3 -0
- satnogs_predict-0.4/tests/constraints/test_constraints.py +745 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/core/test_engine.py +129 -6
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/domain/test_orbit.py +14 -3
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/domain/test_required_fields.py +10 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/domain/test_time.py +3 -4
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/helpers.py +22 -4
- satnogs_predict-0.4/tests/integration/test_fake_tle_integration.py +99 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/propagation/test_propagator.py +181 -17
- satnogs_predict-0.4/tests/test_tle.py +53 -0
- satnogs_predict-0.2/src/satnogs_predict/__init__.py +0 -9
- satnogs_predict-0.2/src/satnogs_predict/constraints/__init__.py +0 -3
- satnogs_predict-0.2/src/satnogs_predict/constraints/constraints.py +0 -111
- satnogs_predict-0.2/src/satnogs_predict/propagation/__init__.py +0 -0
- satnogs_predict-0.2/tests/__init__.py +0 -0
- satnogs_predict-0.2/tests/constraints/test_constraints.py +0 -102
- {satnogs_predict-0.2 → satnogs_predict-0.4}/.gitignore +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/.gitlab-ci.yml +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/.pre-commit-config.yaml +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/.python-version +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/CONTRIBUTING.md +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/LICENSE +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/pyproject.toml +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/setup.cfg +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/setup.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/observer.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/planner.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/domain/validation.py +0 -0
- {satnogs_predict-0.2/src/satnogs_predict/core → satnogs_predict-0.4/src/satnogs_predict/planning}/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict/py.typed +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict.egg-info/dependency_links.txt +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict.egg-info/requires.txt +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/src/satnogs_predict.egg-info/top_level.txt +0 -0
- {satnogs_predict-0.2/src/satnogs_predict/planning → satnogs_predict-0.4/tests}/__init__.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/conftest.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/domain/test_planner_domain.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/domain/test_window.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/tests/planning/test_planner.py +0 -0
- {satnogs_predict-0.2 → satnogs_predict-0.4}/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.4
|
|
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)
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
- [6.2 - Windows returned](#62---windows-returned)
|
|
22
22
|
- [6.3 - Implementation Details](#63---implementation-details)
|
|
23
23
|
- [6.4 - Full workflow for finding passes](#64---full-workflow-for-finding-passes)
|
|
24
|
-
- [6.5 -
|
|
24
|
+
- [6.5 - Sampling point-in-time geometry](#65---sampling-point-in-time-geometry)
|
|
25
|
+
- [6.6 - Full workflow for validating time ranges that belong to passes](#66---full-workflow-for-validating-time-ranges-that-belong-to-passes)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
# 1. Installation
|
|
@@ -41,10 +42,15 @@ level:
|
|
|
41
42
|
``` python
|
|
42
43
|
from satnogs_predict import (
|
|
43
44
|
find_observation_windows,
|
|
45
|
+
get_altaz,
|
|
44
46
|
get_and_validate_pass_from_range,
|
|
47
|
+
get_range,
|
|
45
48
|
check_observation_density_limit_respected,
|
|
46
49
|
has_range_list_overlap,
|
|
50
|
+
generate_fake_tle,
|
|
47
51
|
|
|
52
|
+
AngularSeparationConstraint,
|
|
53
|
+
AzimuthWindowConstraint,
|
|
48
54
|
MaxDurationConstraint,
|
|
49
55
|
MinCulminationConstraint,
|
|
50
56
|
MinDurationConstraint,
|
|
@@ -52,6 +58,7 @@ from satnogs_predict import (
|
|
|
52
58
|
ConstraintAppliedResult,
|
|
53
59
|
ConstraintCheckFailedError,
|
|
54
60
|
GeometrySample,
|
|
61
|
+
RangeSample,
|
|
55
62
|
Observer,
|
|
56
63
|
TLE,
|
|
57
64
|
OrbitRepresentation,
|
|
@@ -86,6 +93,22 @@ satellite = Satellite.from_tle(
|
|
|
86
93
|
)
|
|
87
94
|
```
|
|
88
95
|
|
|
96
|
+
The `identifier` is required. It is used both as part of the public model and as the key for the internal propagation cache.
|
|
97
|
+
|
|
98
|
+
For testing or development, you can also generate a synthetic TLE:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from datetime import datetime, timezone
|
|
102
|
+
|
|
103
|
+
fake_tle = generate_fake_tle(
|
|
104
|
+
latitude=37.983810,
|
|
105
|
+
longitude=23.727539,
|
|
106
|
+
date=datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
line0, line1, line2 = fake_tle.to_list()
|
|
110
|
+
```
|
|
111
|
+
|
|
89
112
|
|
|
90
113
|
## 3.2 - Observer
|
|
91
114
|
|
|
@@ -101,6 +124,8 @@ station = Observer(
|
|
|
101
124
|
)
|
|
102
125
|
```
|
|
103
126
|
|
|
127
|
+
The `identifier` is required here as well, and is also used by the internal propagation cache.
|
|
128
|
+
|
|
104
129
|
|
|
105
130
|
## 3.3 - Time Modeling
|
|
106
131
|
|
|
@@ -123,14 +148,47 @@ now_instant = Instant.from_datetime(now)
|
|
|
123
148
|
five_mins_dur = Duration.from_timedelta(delta)
|
|
124
149
|
five_mins_dur_2 = Duration.from_seconds(5 * 60)
|
|
125
150
|
|
|
151
|
+
later_instant = now_instant + five_mins_dur
|
|
152
|
+
earlier_instant = later_instant - five_mins_dur
|
|
153
|
+
|
|
126
154
|
time_range = TimeRange.from_datetimes(now, future)
|
|
155
|
+
range_duration = time_range.duration
|
|
156
|
+
shifted_range = time_range.shift(Duration.from_seconds(30))
|
|
157
|
+
padded_range = time_range.pad_duration(
|
|
158
|
+
left=Duration.from_seconds(10),
|
|
159
|
+
right=Duration.from_seconds(20),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
assert now_instant < later_instant
|
|
163
|
+
assert time_range.contains(now_instant)
|
|
164
|
+
assert time_range.intersects(shifted_range)
|
|
127
165
|
```
|
|
128
166
|
|
|
167
|
+
Supported operations include:
|
|
168
|
+
|
|
169
|
+
- `Instant.from_datetime(dt)` and `instant.to_datetime()`
|
|
170
|
+
- `Instant + Duration -> Instant`
|
|
171
|
+
- `Instant - Duration -> Instant`
|
|
172
|
+
- ordering/comparisons between `Instant` values
|
|
173
|
+
- `Duration.from_timedelta(td)`, `Duration.from_seconds(seconds)`, `duration.to_timedelta()`, `duration.to_seconds()`
|
|
174
|
+
- `Duration + Duration -> Duration`
|
|
175
|
+
- `TimeRange.from_datetimes(start, end)` creates a range from two timezone-aware datetimes
|
|
176
|
+
- `TimeRange.duration` returns the range length as a `Duration`
|
|
177
|
+
- `TimeRange.contains(instant)` checks whether an instant lies inside the range
|
|
178
|
+
- `TimeRange.intersects(other)` checks whether two ranges overlap
|
|
179
|
+
- `TimeRange.intersection(other)` returns the overlapping part of two ranges, or `None`
|
|
180
|
+
- `TimeRange.subtract(other)` removes the overlapping part and returns the remaining pieces
|
|
181
|
+
- `TimeRange.shift(duration)` moves the whole range by a duration
|
|
182
|
+
- `TimeRange.pad_secs(left, right)` extends the range by raw second values on each side
|
|
183
|
+
- `TimeRange.pad_duration(left, right)` extends the range by `Duration` values on each side
|
|
184
|
+
|
|
129
185
|
# 4. Observation Windows & samples
|
|
130
186
|
|
|
131
187
|
## 4.1 - Samples
|
|
132
188
|
|
|
133
|
-
A sample represents information of a satellite's orbit relative to the observer at a specific instant
|
|
189
|
+
A sample represents information of a satellite's orbit relative to the observer at a specific instant.
|
|
190
|
+
|
|
191
|
+
`GeometrySample` stores angular geometry:
|
|
134
192
|
|
|
135
193
|
```python
|
|
136
194
|
class GeometrySample:
|
|
@@ -142,6 +200,19 @@ class GeometrySample:
|
|
|
142
200
|
|
|
143
201
|
```
|
|
144
202
|
|
|
203
|
+
`RangeSample` stores distance information:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
class RangeSample:
|
|
207
|
+
"""Range and range velocity at an instant, relative to an observer."""
|
|
208
|
+
|
|
209
|
+
instant: Instant
|
|
210
|
+
range_m: float
|
|
211
|
+
range_rate: float
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`range_m` is the observer-to-satellite distance in meters, and `range_rate` is the radial velocity in meters per second.
|
|
215
|
+
|
|
145
216
|
## 4.2 - Observation Windows
|
|
146
217
|
|
|
147
218
|
An observation window represents a pass or a subset of a pass.
|
|
@@ -184,14 +255,48 @@ If due to the constraints, a window is rejected, the reason is set in `rejection
|
|
|
184
255
|
|
|
185
256
|
If the window has an overlap with the provided already scheduled ranges, `overlapped` will be set to `True` and the ratio will be the percentage (as a decimal) that is overlapped.
|
|
186
257
|
|
|
258
|
+
Note: if a later constraint modifies the window length, `overlap_ratio` is currently not recalculated. It remains the value computed earlier during planning.
|
|
259
|
+
|
|
187
260
|
## 4.3 - Constraints
|
|
188
261
|
|
|
189
262
|
When searching for observation windows (more on this below), certain constraints can be applied. Currently supported constraints are:
|
|
190
263
|
|
|
191
|
-
- `
|
|
264
|
+
- `AngularSeparationConstraint` Trims a window to stay within a target angular separation from a fixed pointing direction, with optional fallback behavior controlled by `min_duration`.
|
|
265
|
+
- `AzimuthWindowConstraint` Trims the start and end of a window so that both edge azimuths lie inside a clockwise azimuth interval.
|
|
266
|
+
- `MaxDurationConstraint` Rejects windows over a certain duration. If `trim=True`, it trims them symmetrically instead.
|
|
192
267
|
- `MinDurationConstraint` Rejects windows under a certain duration. Does not change the window.
|
|
193
268
|
- `MinCulminationConstraint` Rejects windows whose max altitude is under a certain threshold. Does not change the window.
|
|
194
269
|
|
|
270
|
+
For `AzimuthWindowConstraint`, the allowed region is defined by sweeping clockwise from `az_start` to `az_end`.
|
|
271
|
+
|
|
272
|
+
Examples:
|
|
273
|
+
|
|
274
|
+
- `AzimuthWindowConstraint(20.0, 100.0)` allows azimuths between `20°` and `100°`.
|
|
275
|
+
- `AzimuthWindowConstraint(350.0, 20.0)` wraps around north and allows azimuths between `350°..360°` and `0°..20°`.
|
|
276
|
+
|
|
277
|
+
If a window already satisfies the azimuth interval at both edges, it is left unchanged. Otherwise, the start is swept forward and the end is swept backward until both edge azimuths are inside the interval. If no valid sub-window remains, the window is rejected.
|
|
278
|
+
|
|
279
|
+
`AngularSeparationConstraint` defines a fixed antenna pointing direction with:
|
|
280
|
+
|
|
281
|
+
- `pointing_az_deg`
|
|
282
|
+
- `pointing_el_deg`
|
|
283
|
+
- `max_separation_deg`
|
|
284
|
+
|
|
285
|
+
The pass is sampled over time, and the angular separation between the satellite direction and that fixed pointing direction is checked at each sample.
|
|
286
|
+
|
|
287
|
+
If `min_duration` is `None`, the constraint trims the pass to the sampled interval that lies within `max_separation_deg`.
|
|
288
|
+
|
|
289
|
+
If `min_duration` is provided:
|
|
290
|
+
|
|
291
|
+
- if the within-separation interval is already at least `min_duration`, the pass is trimmed to that interval
|
|
292
|
+
- if trimming to the within-separation interval would make the pass shorter than `min_duration`, a fallback window of exactly `min_duration` is selected around the closest sampled approach, while staying inside the original pass
|
|
293
|
+
- if the original pass itself is shorter than `min_duration`, the constraint rejects it
|
|
294
|
+
|
|
295
|
+
`MaxDurationConstraint` supports two modes:
|
|
296
|
+
|
|
297
|
+
- by default, it rejects any window longer than `max_duration`
|
|
298
|
+
- if `trim=True`, it trims the window symmetrically around its center down to `max_duration`
|
|
299
|
+
|
|
195
300
|
Constraint behavior is:
|
|
196
301
|
|
|
197
302
|
- `constraint.apply(window) -> ObservationWindow`: annotates the window with `constraint_applied_result` / `rejection_reason`.
|
|
@@ -280,6 +385,8 @@ Internally, satellite & observer are converted to Skyfield's types, which is com
|
|
|
280
385
|
|
|
281
386
|
This way, repeated consecutive calls of `find_observation_windows` with the same satellite, or with the same `observer` reuse the objects under the hood.
|
|
282
387
|
|
|
388
|
+
If you need to clear these caches explicitly, the public helpers `clear_satellite_cache()` and `clear_observer_cache()` are available.
|
|
389
|
+
|
|
283
390
|
## 6.4 - Full workflow for finding passes
|
|
284
391
|
|
|
285
392
|
High-level process:
|
|
@@ -331,19 +438,50 @@ planning_config = PlanningConfig(
|
|
|
331
438
|
|
|
332
439
|
min_duration_constraint = MinDurationConstraint(Duration.from_seconds(5))
|
|
333
440
|
min_culmination_constraint = MinCulminationConstraint(55.0)
|
|
441
|
+
azimuth_window_constraint = AzimuthWindowConstraint(220.0, 330.0)
|
|
334
442
|
|
|
335
443
|
valid_windows, rejected_windows = find_observation_windows(
|
|
336
444
|
sat,
|
|
337
445
|
observer,
|
|
338
446
|
time_range,
|
|
339
447
|
planning_config,
|
|
340
|
-
pre_plan_constraints=[
|
|
341
|
-
|
|
448
|
+
pre_plan_constraints=[
|
|
449
|
+
min_duration_constraint,
|
|
450
|
+
min_culmination_constraint,
|
|
451
|
+
azimuth_window_constraint,
|
|
452
|
+
],
|
|
453
|
+
post_plan_constraints=[
|
|
454
|
+
min_duration_constraint,
|
|
455
|
+
min_culmination_constraint,
|
|
456
|
+
azimuth_window_constraint,
|
|
457
|
+
],
|
|
342
458
|
scheduled_intervals=scheduled_ranges,
|
|
343
459
|
)
|
|
344
460
|
```
|
|
345
461
|
|
|
346
|
-
## 6.5 -
|
|
462
|
+
## 6.5 - Sampling point-in-time geometry
|
|
463
|
+
|
|
464
|
+
For point-in-time queries, the public API also provides helpers for sampling a satellite relative to an observer at a single `Instant`.
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
altaz_sample = get_altaz(
|
|
468
|
+
satellite,
|
|
469
|
+
observer,
|
|
470
|
+
instant,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
range_sample = get_range(
|
|
474
|
+
satellite,
|
|
475
|
+
observer,
|
|
476
|
+
instant,
|
|
477
|
+
)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
- `get_altaz()` returns a `GeometrySample` with azimuth and altitude.
|
|
481
|
+
- `get_range()` returns a `RangeSample` with range in meters and range rate in meters per second.
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
## 6.6 - Full workflow for validating time ranges that belong to passes
|
|
347
485
|
|
|
348
486
|
- Given a time range, we can validate whether it belongs to a single pass and whether it satisfies constraints.
|
|
349
487
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from . import constraints, domain
|
|
2
|
+
from .constraints import * # noqa: F401,F403
|
|
3
|
+
from .core.engine import (
|
|
4
|
+
find_observation_windows,
|
|
5
|
+
get_altaz,
|
|
6
|
+
get_and_validate_pass_from_range,
|
|
7
|
+
get_range,
|
|
8
|
+
)
|
|
9
|
+
from .domain import * # noqa: F401,F403
|
|
10
|
+
from .planning.planner import check_observation_density_limit_respected, has_range_list_overlap
|
|
11
|
+
from .propagation import clear_observer_cache, clear_satellite_cache
|
|
12
|
+
from .tle import generate_fake_tle
|
|
13
|
+
|
|
14
|
+
__all__ = list(domain.__all__) + list(constraints.__all__)
|
|
15
|
+
__all__ += ["check_observation_density_limit_respected", "has_range_list_overlap"]
|
|
16
|
+
__all__ += [
|
|
17
|
+
"clear_observer_cache",
|
|
18
|
+
"clear_satellite_cache",
|
|
19
|
+
"find_observation_windows",
|
|
20
|
+
"get_altaz",
|
|
21
|
+
"get_and_validate_pass_from_range",
|
|
22
|
+
"get_range",
|
|
23
|
+
]
|
|
24
|
+
__all__ += ["generate_fake_tle"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .constraints import (
|
|
2
|
+
AngularSeparationConstraint,
|
|
3
|
+
AzimuthWindowConstraint,
|
|
4
|
+
MaxDurationConstraint,
|
|
5
|
+
MinCulminationConstraint,
|
|
6
|
+
MinDurationConstraint,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AngularSeparationConstraint",
|
|
11
|
+
"AzimuthWindowConstraint",
|
|
12
|
+
"MinCulminationConstraint",
|
|
13
|
+
"MinDurationConstraint",
|
|
14
|
+
"MaxDurationConstraint",
|
|
15
|
+
]
|