sopp 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.
- examples/example.py +119 -0
- sopp/__init__.py +0 -0
- sopp/builder/configuration_builder.py +119 -0
- sopp/config_file/__init__.py +0 -0
- sopp/config_file/config_file_factory.py +13 -0
- sopp/config_file/support/__init__.py +0 -0
- sopp/config_file/support/config_file_base.py +20 -0
- sopp/config_file/support/config_file_json.py +108 -0
- sopp/config_file/support/utilities.py +1 -0
- sopp/custom_dataclasses/__init__.py +0 -0
- sopp/custom_dataclasses/configuration.py +17 -0
- sopp/custom_dataclasses/coordinates.py +20 -0
- sopp/custom_dataclasses/facility.py +26 -0
- sopp/custom_dataclasses/frequency_range/__init__.py +0 -0
- sopp/custom_dataclasses/frequency_range/frequency_range.py +36 -0
- sopp/custom_dataclasses/frequency_range/support/__init__.py +0 -0
- sopp/custom_dataclasses/frequency_range/support/get_frequency_data_from_csv.py +75 -0
- sopp/custom_dataclasses/observation_target.py +7 -0
- sopp/custom_dataclasses/overhead_window.py +31 -0
- sopp/custom_dataclasses/position.py +12 -0
- sopp/custom_dataclasses/position_time.py +10 -0
- sopp/custom_dataclasses/reservation.py +19 -0
- sopp/custom_dataclasses/runtime_settings.py +14 -0
- sopp/custom_dataclasses/satellite/__init__.py +0 -0
- sopp/custom_dataclasses/satellite/international_designator.py +19 -0
- sopp/custom_dataclasses/satellite/mean_motion.py +8 -0
- sopp/custom_dataclasses/satellite/satellite.py +50 -0
- sopp/custom_dataclasses/satellite/tle_information.py +74 -0
- sopp/custom_dataclasses/time_window.py +19 -0
- sopp/event_finder/__init__.py +0 -0
- sopp/event_finder/event_finder.py +44 -0
- sopp/event_finder/event_finder_rhodesmill/__init__.py +0 -0
- sopp/event_finder/event_finder_rhodesmill/event_finder_rhodesmill.py +101 -0
- sopp/event_finder/event_finder_rhodesmill/support/evenly_spaced_time_intervals_calculator.py +15 -0
- sopp/event_finder/event_finder_rhodesmill/support/satellite_positions_with_respect_to_facility_retriever/__init__.py +0 -0
- sopp/event_finder/event_finder_rhodesmill/support/satellite_positions_with_respect_to_facility_retriever/satellite_positions_with_respect_to_facility_retriever.py +17 -0
- sopp/event_finder/event_finder_rhodesmill/support/satellite_positions_with_respect_to_facility_retriever/satellite_positions_with_respect_to_facility_retriever_rhodesmill.py +43 -0
- sopp/event_finder/event_finder_rhodesmill/support/satellites_within_main_beam_filter.py +81 -0
- sopp/event_finder/support/__init__.py +0 -0
- sopp/event_finder/support/overhead_window_from_events.py +56 -0
- sopp/frequency_filter/__init__.py +0 -0
- sopp/frequency_filter/frequency_filter.py +33 -0
- sopp/generate_tardys3.py +33 -0
- sopp/path_finder/__init__.py +0 -0
- sopp/path_finder/observation_path_finder.py +24 -0
- sopp/path_finder/observation_path_finder_astropy.py +47 -0
- sopp/path_finder/observation_path_finder_rhodesmill.py +70 -0
- sopp/retrievers/__init__.py +0 -0
- sopp/retrievers/retriever.py +7 -0
- sopp/retrievers/retriever_json_file.py +14 -0
- sopp/retrievers/satellite_retriever/__init__.py +0 -0
- sopp/retrievers/satellite_retriever/satellite_retriever.py +9 -0
- sopp/retrievers/satellite_retriever/satellite_retriever_json_file.py +12 -0
- sopp/retrievers/satellite_retriever/skyfield_satellite_retriever.py +14 -0
- sopp/satellites_loader/satellites_loader.py +10 -0
- sopp/satellites_loader/satellites_loader_from_files.py +48 -0
- sopp/tle_fetcher/__init__.py +0 -0
- sopp/tle_fetcher/tle_fetcher.py +37 -0
- sopp/utilities.py +66 -0
- sopp/variable_initializer/variable_initializer.py +22 -0
- sopp/variable_initializer/variable_initializer_from_config.py +29 -0
- sopp/window_finder.py +85 -0
- sopp-0.1.0.dist-info/METADATA +200 -0
- sopp-0.1.0.dist-info/RECORD +135 -0
- sopp-0.1.0.dist-info/WHEEL +4 -0
- sopp-0.1.0.dist-info/licenses/COPYING +661 -0
- tests/__init__.py +0 -0
- tests/config_file/__init__.py +0 -0
- tests/config_file/config_file_json/__init__.py +0 -0
- tests/config_file/config_file_json/arbitrary_config_file.json +25 -0
- tests/config_file/config_file_json/arbitrary_config_file_2.json +25 -0
- tests/config_file/config_file_json/arbitrary_config_file_no_observation_target.json +21 -0
- tests/config_file/config_file_json/arbitrary_config_file_no_static_antenna_position.json +21 -0
- tests/config_file/config_file_json/arbitrary_config_file_partial_observation_target.json +24 -0
- tests/config_file/config_file_json/arbitrary_config_file_partial_static_antenna_position.json +24 -0
- tests/config_file/config_file_json/arbitrary_config_file_runtime_settings.json +29 -0
- tests/config_file/config_file_json/arbitrary_config_file_with_antenna_position_times.json +37 -0
- tests/config_file/test_config_file_default_argument.py +71 -0
- tests/config_file/test_config_file_provided_argument.py +95 -0
- tests/dataclasses/__init__.py +0 -0
- tests/dataclasses/frequency_range/__init__.py +0 -0
- tests/dataclasses/frequency_range/arbitrary_frequency_file.csv +2 -0
- tests/dataclasses/frequency_range/arbitrary_frequency_file_junk.csv +5 -0
- tests/dataclasses/frequency_range/arbitrary_frequency_file_none.csv +5 -0
- tests/dataclasses/frequency_range/arbitrary_frequency_file_two_frequencies.csv +4 -0
- tests/dataclasses/frequency_range/arbitrary_frequency_file_with_bandwidth.csv +5 -0
- tests/dataclasses/frequency_range/test_from_csv.py +135 -0
- tests/dataclasses/satellite/__init__.py +0 -0
- tests/dataclasses/satellite/fake_ISS_frequency_file.csv +2 -0
- tests/dataclasses/satellite/fake_ISS_frequency_file_multiple.csv +3 -0
- tests/dataclasses/satellite/international_designator/__init__.py +0 -0
- tests/dataclasses/satellite/international_designator/test_international_designator_from_string.py +21 -0
- tests/dataclasses/satellite/international_designator/test_international_designator_to_string.py +24 -0
- tests/dataclasses/satellite/international_space_station_tle.tle +3 -0
- tests/dataclasses/satellite/international_space_station_tle_multiple.tle +6 -0
- tests/dataclasses/satellite/test_satellite_to_rhodesmill.py +52 -0
- tests/dataclasses/satellite/test_tle_to_cu_satellite.py +44 -0
- tests/dataclasses/satellite/utilities.py +38 -0
- tests/dataclasses/test_time_window_overlaps.py +47 -0
- tests/definitions.py +1 -0
- tests/event_finder/__init__.py +0 -0
- tests/event_finder/event_finder_rhodesmill/__init__.py +0 -0
- tests/event_finder/event_finder_rhodesmill/definitions.py +31 -0
- tests/event_finder/event_finder_rhodesmill/support/__init__.py +0 -0
- tests/event_finder/event_finder_rhodesmill/support/test_satellite_positions_with_respect_to_facility_retriever_rhodesmill.py +78 -0
- tests/event_finder/event_finder_rhodesmill/support/test_time_intervals_calculator.py +0 -0
- tests/event_finder/event_finder_rhodesmill/test_event_finder_reservation_start_time.py +70 -0
- tests/event_finder/event_finder_rhodesmill/test_event_finder_rhodesmill.py +123 -0
- tests/event_finder/event_finder_rhodesmill/test_rhodemill_integration.py +81 -0
- tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter.py +171 -0
- tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter_altitude.py +54 -0
- tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter_unsorted_antenna_positions.py +39 -0
- tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter_unsorted_satellite_positions.py +48 -0
- tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_modulo_360.py +39 -0
- tests/frequency_filter/__init__.py +0 -0
- tests/frequency_filter/test_frequency_filter.py +156 -0
- tests/path_finder/path_finder_base_test.py +40 -0
- tests/path_finder/test_path_finder_astropy.py +6 -0
- tests/path_finder/test_path_finder_astropy_rhodesmill_equivalency.py +44 -0
- tests/path_finder/test_path_finder_rhodesmill.py +22 -0
- tests/retrievers/__init__.py +0 -0
- tests/retrievers/facility_retriever/test_facility_retriever_json_file.py +0 -0
- tests/satellites_loader/satellite_frequencies.csv +6 -0
- tests/satellites_loader/satellites.tle +6 -0
- tests/satellites_loader/test_satellites_loader_from_files.py +109 -0
- tests/variable_initializer/test_variable_initializer_from_config.py +161 -0
- tests/window_finder/__init__.py +0 -0
- tests/window_finder/definitions.py +9 -0
- tests/window_finder/fake_ISS_frequency_file.csv +2 -0
- tests/window_finder/fake_ISS_frequency_file_multiple.csv +3 -0
- tests/window_finder/international_space_station_tle.tle +3 -0
- tests/window_finder/international_space_station_tle_multiple.tle +6 -0
- tests/window_finder/support/__init__.py +0 -0
- tests/window_finder/support/validator_satellites_are_overhead_at_specific_times.py +17 -0
- tests/window_finder/test_sorted_by_least_number_of_satellites.py +223 -0
examples/example.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from sopp.utilities import read_datetime_string_as_utc
|
|
2
|
+
from sopp.satellites_loader.satellites_loader_from_files import \
|
|
3
|
+
SatellitesLoaderFromFiles
|
|
4
|
+
from sopp.event_finder.event_finder_rhodesmill.event_finder_rhodesmill import \
|
|
5
|
+
EventFinderRhodesmill
|
|
6
|
+
from sopp.path_finder.observation_path_finder_rhodesmill import \
|
|
7
|
+
ObservationPathFinderRhodesmill
|
|
8
|
+
from sopp.custom_dataclasses.observation_target import ObservationTarget
|
|
9
|
+
from sopp.custom_dataclasses.facility import Facility
|
|
10
|
+
from sopp.custom_dataclasses.coordinates import Coordinates
|
|
11
|
+
from sopp.custom_dataclasses.time_window import TimeWindow
|
|
12
|
+
from sopp.custom_dataclasses.reservation import Reservation
|
|
13
|
+
from sopp.custom_dataclasses.runtime_settings import RuntimeSettings
|
|
14
|
+
from sopp.custom_dataclasses.frequency_range.frequency_range import \
|
|
15
|
+
FrequencyRange
|
|
16
|
+
from sopp.frequency_filter.frequency_filter import FrequencyFilter
|
|
17
|
+
from datetime import timedelta
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
# Facility
|
|
22
|
+
facility = Facility(
|
|
23
|
+
Coordinates(
|
|
24
|
+
latitude=40.8178049,
|
|
25
|
+
longitude=-121.4695413,
|
|
26
|
+
),
|
|
27
|
+
elevation=986, # meters
|
|
28
|
+
beamwidth=3, # degrees
|
|
29
|
+
name='HCRO',
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Observation Window
|
|
33
|
+
time_window = TimeWindow(
|
|
34
|
+
begin=read_datetime_string_as_utc('2023-11-15T08:00:00.000000'),
|
|
35
|
+
end=read_datetime_string_as_utc('2023-11-15T08:30:00.000000'),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Frequency Range
|
|
39
|
+
frequency_range = FrequencyRange(bandwidth=10, frequency=135)
|
|
40
|
+
|
|
41
|
+
# Reservation
|
|
42
|
+
reservation = Reservation(
|
|
43
|
+
facility=facility,
|
|
44
|
+
time=time_window,
|
|
45
|
+
frequency=frequency_range
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Specify Observation Target
|
|
49
|
+
observation_target = ObservationTarget(
|
|
50
|
+
declination='7d24m25.426s',
|
|
51
|
+
right_ascension='5h55m10.3s'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Antenna Direction Path (going to do automatically)
|
|
55
|
+
antenna_direction_path = ObservationPathFinderRhodesmill(
|
|
56
|
+
facility,
|
|
57
|
+
observation_target,
|
|
58
|
+
time_window
|
|
59
|
+
).calculate_path()
|
|
60
|
+
|
|
61
|
+
# Load Satellites
|
|
62
|
+
all_satellites = SatellitesLoaderFromFiles(
|
|
63
|
+
tle_file='./satellites.tle',
|
|
64
|
+
).load_satellites()
|
|
65
|
+
|
|
66
|
+
# Filter satellites on frequency (optional, going to do automatically)
|
|
67
|
+
filtered_satellites = FrequencyFilter(
|
|
68
|
+
satellites=all_satellites,
|
|
69
|
+
observation_frequency=frequency_range
|
|
70
|
+
).filter_frequencies()
|
|
71
|
+
|
|
72
|
+
# Runtime Settings
|
|
73
|
+
runtime_settings = RuntimeSettings(
|
|
74
|
+
concurrency_level=8,
|
|
75
|
+
time_continuity_resolution=timedelta(seconds=1)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Display configuration
|
|
79
|
+
print('\nFinding satellite interference events for:\n')
|
|
80
|
+
print(f'Facility: {reservation.facility.name}')
|
|
81
|
+
print(f'Location: {reservation.facility.coordinates} at elevation '
|
|
82
|
+
f'{reservation.facility.elevation}')
|
|
83
|
+
print(f'Reservation start time: {reservation.time.begin}')
|
|
84
|
+
print(f'Reservation end time: {reservation.time.end}')
|
|
85
|
+
print(f'Observation frequency: {reservation.frequency.frequency} MHz')
|
|
86
|
+
print(f'Observing celestial object at: '
|
|
87
|
+
f'Declination: {observation_target.declination} '
|
|
88
|
+
f'Right Ascension:{observation_target.right_ascension}')
|
|
89
|
+
|
|
90
|
+
# Determine Satellite Interference
|
|
91
|
+
interference_events = EventFinderRhodesmill(
|
|
92
|
+
list_of_satellites=filtered_satellites,
|
|
93
|
+
reservation=reservation,
|
|
94
|
+
antenna_direction_path=antenna_direction_path,
|
|
95
|
+
runtime_settings=runtime_settings,
|
|
96
|
+
).get_satellites_crossing_main_beam()
|
|
97
|
+
|
|
98
|
+
########################################################################
|
|
99
|
+
|
|
100
|
+
print('\n==============================================================\n')
|
|
101
|
+
print(f'There are {len(interference_events)} satellite interference\n'
|
|
102
|
+
f'events during the reservation\n')
|
|
103
|
+
print('==============================================================\n')
|
|
104
|
+
|
|
105
|
+
for i, window in enumerate(interference_events, start=1):
|
|
106
|
+
max_alt = max(window.positions, key=lambda pt: pt.position.altitude)
|
|
107
|
+
|
|
108
|
+
print(f'Satellite interference event #{i}:')
|
|
109
|
+
print(f'Satellite: {window.satellite.name}')
|
|
110
|
+
print(f'Satellite enters view: {window.overhead_time.begin} at '
|
|
111
|
+
f'{window.positions[0].position.azimuth:.2f}')
|
|
112
|
+
print(f'Satellite leaves view: {window.overhead_time.end} at '
|
|
113
|
+
f'{window.positions[-1].position.azimuth:.2f}')
|
|
114
|
+
print(f'Satellite maximum altitude: {max_alt.position.altitude:.2f}')
|
|
115
|
+
print('__________________________________________________\n')
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == '__main__':
|
|
119
|
+
main()
|
sopp/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from sopp.custom_dataclasses.observation_target import ObservationTarget
|
|
2
|
+
from sopp.custom_dataclasses.facility import Facility
|
|
3
|
+
from sopp.custom_dataclasses.coordinates import Coordinates
|
|
4
|
+
from sopp.custom_dataclasses.time_window import TimeWindow
|
|
5
|
+
from sopp.custom_dataclasses.reservation import Reservation
|
|
6
|
+
from sopp.custom_dataclasses.runtime_settings import RuntimeSettings
|
|
7
|
+
from sopp.custom_dataclasses.frequency_range.frequency_range import FrequencyRange
|
|
8
|
+
from sopp.path_finder.observation_path_finder_rhodesmill import ObservationPathFinderRhodesmill
|
|
9
|
+
from sopp.frequency_filter.frequency_filter import FrequencyFilter
|
|
10
|
+
from sopp.satellites_loader.satellites_loader_from_files import SatellitesLoaderFromFiles
|
|
11
|
+
from sopp.config_file.config_file_factory import get_config_file_object
|
|
12
|
+
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConfigurationBuilder:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._facility = None
|
|
20
|
+
self._time_window = None
|
|
21
|
+
self._frequency_range = None
|
|
22
|
+
self._observation_target = None
|
|
23
|
+
|
|
24
|
+
self.antenna_direction_path = None
|
|
25
|
+
self.satellites = None
|
|
26
|
+
self.reservation = None
|
|
27
|
+
self.runtime_settings = RuntimeSettings()
|
|
28
|
+
|
|
29
|
+
def set_facility(self,
|
|
30
|
+
latitude: float,
|
|
31
|
+
longitude: float,
|
|
32
|
+
elevation: float,
|
|
33
|
+
name: str,
|
|
34
|
+
beamwidth: float,
|
|
35
|
+
bandwidth: float,
|
|
36
|
+
frequency: float,
|
|
37
|
+
):
|
|
38
|
+
self._facility = Facility(
|
|
39
|
+
Coordinates(latitude=latitude, longitude=longitude),
|
|
40
|
+
elevation=elevation,
|
|
41
|
+
beamwidth=beamwidth,
|
|
42
|
+
name=name,
|
|
43
|
+
)
|
|
44
|
+
self._frequency_range = FrequencyRange(
|
|
45
|
+
bandwidth=bandwidth,
|
|
46
|
+
frequency=frequency,
|
|
47
|
+
)
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def set_time_window(self, begin: datetime, end: datetime):
|
|
51
|
+
self._time_window = TimeWindow(
|
|
52
|
+
begin=begin,
|
|
53
|
+
end=end,
|
|
54
|
+
)
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def set_observation_target(self, declination: str, right_ascension: str):
|
|
58
|
+
self._observation_target = ObservationTarget(
|
|
59
|
+
declination=declination,
|
|
60
|
+
right_ascension=right_ascension,
|
|
61
|
+
)
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
def set_satellites(self, tle_file: str, frequency_file: Optional[str] = None):
|
|
65
|
+
self.satellites = SatellitesLoaderFromFiles(
|
|
66
|
+
tle_file=tle_file,
|
|
67
|
+
frequency_file=frequency_file,
|
|
68
|
+
).load_satellites()
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def set_runtime_settings(self, concurrency_level: int, time_continuity_resolution: int):
|
|
72
|
+
self.runtime_settings = RuntimeSettings(
|
|
73
|
+
concurrency_level=concurrency_level,
|
|
74
|
+
time_continuity_resolution=time_continuity_resolution,
|
|
75
|
+
)
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
def set_config_file(self, config_file: str):
|
|
79
|
+
config = get_config_file_object(config_filepath=config_file).configuration
|
|
80
|
+
self._frequency_range = config.reservation.frequency
|
|
81
|
+
self._facility = config.reservation.facility
|
|
82
|
+
self._time_window = config.reservation.time
|
|
83
|
+
self._observation_target = config.observation_target
|
|
84
|
+
self.runtime_settings = config.runtime_settings
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def _frequency_filter_satellites(self):
|
|
88
|
+
self.satellites = FrequencyFilter(
|
|
89
|
+
satellites=self.satellites,
|
|
90
|
+
observation_frequency=self._frequency_range
|
|
91
|
+
).filter_frequencies()
|
|
92
|
+
|
|
93
|
+
def _build_reservation(self):
|
|
94
|
+
self.reservation = Reservation(
|
|
95
|
+
facility=self._facility,
|
|
96
|
+
time=self._time_window,
|
|
97
|
+
frequency=self._frequency_range
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _build_antenna_direction_path(self):
|
|
101
|
+
if self._observation_target is not None:
|
|
102
|
+
self.antenna_direction_path = ObservationPathFinderRhodesmill(
|
|
103
|
+
self._facility,
|
|
104
|
+
self._observation_target,
|
|
105
|
+
self._time_window
|
|
106
|
+
).calculate_path()
|
|
107
|
+
|
|
108
|
+
def build(self):
|
|
109
|
+
if not all(
|
|
110
|
+
[self._facility, self._time_window, self._frequency_range,
|
|
111
|
+
self._observation_target, self.satellites]
|
|
112
|
+
):
|
|
113
|
+
raise ValueError("Incomplete configuration. Please set all required parameters.")
|
|
114
|
+
|
|
115
|
+
self._frequency_filter_satellites()
|
|
116
|
+
self._build_antenna_direction_path()
|
|
117
|
+
self._build_reservation()
|
|
118
|
+
|
|
119
|
+
return self
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from sopp.config_file.support.config_file_base import ConfigFileBase
|
|
5
|
+
from sopp.config_file.support.config_file_json import ConfigFileJson
|
|
6
|
+
from sopp.utilities import get_default_config_file_filepath
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_config_file_object(config_filepath: Optional[Path] = None) -> ConfigFileBase:
|
|
10
|
+
config_filepath = config_filepath or get_default_config_file_filepath()
|
|
11
|
+
for config_class in [ConfigFileJson]:
|
|
12
|
+
if config_class.filename_extension() in str(config_filepath):
|
|
13
|
+
return config_class(filepath=config_filepath)
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from sopp.custom_dataclasses.configuration import Configuration
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConfigFileBase(ABC):
|
|
9
|
+
def __init__(self, filepath: Path):
|
|
10
|
+
self._filepath = filepath
|
|
11
|
+
|
|
12
|
+
@cached_property
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def configuration(self) -> Configuration:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def filename_extension(cls) -> str:
|
|
20
|
+
pass
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from sopp.config_file.support.config_file_base import ConfigFileBase
|
|
6
|
+
from sopp.custom_dataclasses.configuration import Configuration
|
|
7
|
+
from sopp.custom_dataclasses.coordinates import Coordinates
|
|
8
|
+
from sopp.custom_dataclasses.facility import Facility
|
|
9
|
+
from sopp.custom_dataclasses.frequency_range.frequency_range import FrequencyRange
|
|
10
|
+
from sopp.custom_dataclasses.observation_target import ObservationTarget
|
|
11
|
+
from sopp.custom_dataclasses.position import Position
|
|
12
|
+
from sopp.custom_dataclasses.position_time import PositionTime
|
|
13
|
+
from sopp.custom_dataclasses.reservation import Reservation
|
|
14
|
+
from sopp.custom_dataclasses.time_window import TimeWindow
|
|
15
|
+
from sopp.custom_dataclasses.runtime_settings import RuntimeSettings
|
|
16
|
+
from sopp.utilities import read_datetime_string_as_utc
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConfigFileJson(ConfigFileBase):
|
|
20
|
+
@cached_property
|
|
21
|
+
def configuration(self) -> Configuration:
|
|
22
|
+
return Configuration(
|
|
23
|
+
reservation=self._reservation,
|
|
24
|
+
runtime_settings=self._runtime_settings,
|
|
25
|
+
antenna_position_times=self._antenna_position_times,
|
|
26
|
+
observation_target=self._observation_target,
|
|
27
|
+
static_antenna_position=self._static_antenna_position
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@cached_property
|
|
31
|
+
def _reservation(self) -> Reservation:
|
|
32
|
+
return Reservation(
|
|
33
|
+
facility=self._facility,
|
|
34
|
+
time=self._reservation_window,
|
|
35
|
+
frequency=self._frequency_range
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@cached_property
|
|
39
|
+
def _facility(self) -> Facility:
|
|
40
|
+
configuration = self._config_object.get('facility')
|
|
41
|
+
|
|
42
|
+
return Facility(
|
|
43
|
+
coordinates=Coordinates(
|
|
44
|
+
latitude=configuration['latitude'],
|
|
45
|
+
longitude=configuration['longitude']
|
|
46
|
+
),
|
|
47
|
+
name=configuration['name'],
|
|
48
|
+
elevation=configuration['elevation'],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@cached_property
|
|
52
|
+
def _reservation_window(self) -> TimeWindow:
|
|
53
|
+
configuration = self._config_object.get('reservationWindow')
|
|
54
|
+
start_datetime = read_datetime_string_as_utc(configuration['startTimeUtc'])
|
|
55
|
+
end_datetime_str = read_datetime_string_as_utc(configuration['endTimeUtc'])
|
|
56
|
+
|
|
57
|
+
return TimeWindow(begin=start_datetime, end=end_datetime_str)
|
|
58
|
+
|
|
59
|
+
@cached_property
|
|
60
|
+
def _frequency_range(self) -> FrequencyRange:
|
|
61
|
+
configuration = self._config_object.get('frequencyRange')
|
|
62
|
+
|
|
63
|
+
return FrequencyRange(
|
|
64
|
+
frequency=configuration['frequency'],
|
|
65
|
+
bandwidth=configuration['bandwidth']
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@cached_property
|
|
69
|
+
def _runtime_settings(self) -> RuntimeSettings:
|
|
70
|
+
configuration = self._config_object.get('runtimeSettings')
|
|
71
|
+
|
|
72
|
+
return RuntimeSettings(
|
|
73
|
+
time_continuity_resolution=configuration['time_continuity_resolution'],
|
|
74
|
+
concurrency_level=configuration['concurrency_level']
|
|
75
|
+
) if configuration else RuntimeSettings()
|
|
76
|
+
|
|
77
|
+
@cached_property
|
|
78
|
+
def _antenna_position_times(self) -> List[PositionTime]:
|
|
79
|
+
configuration = self._config_object.get('antennaPositionTimes')
|
|
80
|
+
return configuration and [PositionTime(position=Position(altitude=position_time['altitude'],
|
|
81
|
+
azimuth=position_time['azimuth']),
|
|
82
|
+
time=read_datetime_string_as_utc(string_value=position_time['time']))
|
|
83
|
+
for position_time in configuration]
|
|
84
|
+
|
|
85
|
+
@cached_property
|
|
86
|
+
def _observation_target(self) -> ObservationTarget:
|
|
87
|
+
configuration = self._config_object.get('observationTarget')
|
|
88
|
+
return configuration and ObservationTarget(
|
|
89
|
+
declination=configuration['declination'],
|
|
90
|
+
right_ascension=configuration['rightAscension']
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@cached_property
|
|
94
|
+
def _static_antenna_position(self) -> Position:
|
|
95
|
+
configuration = self._config_object.get('staticAntennaPosition')
|
|
96
|
+
return configuration and Position(
|
|
97
|
+
altitude=configuration['altitude'],
|
|
98
|
+
azimuth=configuration['azimuth']
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@cached_property
|
|
102
|
+
def _config_object(self) -> dict:
|
|
103
|
+
with open(self._filepath, 'r') as f:
|
|
104
|
+
return json.load(f)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def filename_extension(cls) -> str:
|
|
108
|
+
return '.json'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from sopp.custom_dataclasses.observation_target import ObservationTarget
|
|
5
|
+
from sopp.custom_dataclasses.position import Position
|
|
6
|
+
from sopp.custom_dataclasses.position_time import PositionTime
|
|
7
|
+
from sopp.custom_dataclasses.reservation import Reservation
|
|
8
|
+
from sopp.custom_dataclasses.runtime_settings import RuntimeSettings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Configuration:
|
|
13
|
+
reservation: Reservation
|
|
14
|
+
runtime_settings: Optional[RuntimeSettings] = RuntimeSettings()
|
|
15
|
+
antenna_position_times: Optional[List[PositionTime]] = None
|
|
16
|
+
observation_target: Optional[ObservationTarget] = None
|
|
17
|
+
static_antenna_position: Optional[Position] = None
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CoordinatesJsonKey(Enum):
|
|
6
|
+
latitude = 'latitude'
|
|
7
|
+
longitude = 'longitude'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Coordinates:
|
|
12
|
+
latitude: float
|
|
13
|
+
longitude: float
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_json(cls, info: dict) -> 'Coordinates':
|
|
17
|
+
return cls(
|
|
18
|
+
latitude=info[CoordinatesJsonKey.latitude.value],
|
|
19
|
+
longitude=info[CoordinatesJsonKey.longitude.value]
|
|
20
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from sopp.custom_dataclasses.coordinates import Coordinates
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from sopp.custom_dataclasses.position_time import PositionTime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Facility:
|
|
10
|
+
'''
|
|
11
|
+
The Facility data class contains the observation parameters of the facility and the object it is tracking, including coordinates
|
|
12
|
+
of the RA telescope and its beamwidth, as well as the right ascension and declination values for its observation target:
|
|
13
|
+
|
|
14
|
+
-coordinates: location of RA facility. Coordinates.
|
|
15
|
+
-beamwidth: beamwidth of the telescope. float. Defaults to 3
|
|
16
|
+
-elevation: ground elevation of the telescope in meters. float. Defaults to 0
|
|
17
|
+
-name: name of the facility. String. Defaults to 'Unnamed Facility'
|
|
18
|
+
'''
|
|
19
|
+
coordinates: Coordinates
|
|
20
|
+
beamwidth: float = 3
|
|
21
|
+
elevation: float = 0
|
|
22
|
+
name: Optional[str] = 'Unnamed Facility'
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def half_beamwidth(self) -> float:
|
|
26
|
+
return self.beamwidth / 2
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
'''
|
|
5
|
+
The FrequencyRange class is used for storing frequency ranges of both the RA telescopes observation and each satellite's downlink transmission
|
|
6
|
+
frequency information. The frequency parameter represents the center frequency of the observation or downlink. The status parameter is only relevant
|
|
7
|
+
to satellites and is used to store information from the satellite frequency database on whether an antenna is operational 'active' or not 'inactive'
|
|
8
|
+
|
|
9
|
+
The overlaps function determines if two FrequencyRanges overlap with each other and is used to determine if any of the satellite downlink frequencies
|
|
10
|
+
overlap with the observation frequency. Satellite frequency data is read from a csv file (as of May 15, 2023) using the GetFrequencyDataFromCsv class
|
|
11
|
+
under the support folder.f
|
|
12
|
+
|
|
13
|
+
'''
|
|
14
|
+
|
|
15
|
+
DEFAULT_BANDWIDTH = 10
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class FrequencyRange:
|
|
19
|
+
frequency: Optional[float] = None
|
|
20
|
+
bandwidth: Optional[float] = None
|
|
21
|
+
status: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
def overlaps(self, satellite_frequency: 'FrequencyRange'):
|
|
24
|
+
half_bandwidth_res = self.bandwidth/2
|
|
25
|
+
if satellite_frequency.bandwidth is None:
|
|
26
|
+
low_in_mghz_sat = satellite_frequency.frequency - (DEFAULT_BANDWIDTH/2)
|
|
27
|
+
high_in_mghz_sat = satellite_frequency.frequency + (DEFAULT_BANDWIDTH/2)
|
|
28
|
+
low_in_mghz_res = self.frequency - half_bandwidth_res
|
|
29
|
+
high_in_mghz_res = self.frequency + half_bandwidth_res
|
|
30
|
+
else:
|
|
31
|
+
half_bandwidth_sat = satellite_frequency.bandwidth / 2
|
|
32
|
+
low_in_mghz_res = self.frequency - half_bandwidth_res
|
|
33
|
+
high_in_mghz_res = self.frequency + half_bandwidth_res
|
|
34
|
+
low_in_mghz_sat = satellite_frequency.frequency - half_bandwidth_sat
|
|
35
|
+
high_in_mghz_sat = satellite_frequency.frequency + half_bandwidth_sat
|
|
36
|
+
return low_in_mghz_sat < high_in_mghz_res and high_in_mghz_sat > low_in_mghz_res
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
from sopp.custom_dataclasses.frequency_range.frequency_range import FrequencyRange
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FrequencyCsvKeys(Enum):
|
|
11
|
+
LINENO = ''
|
|
12
|
+
ID = 'ID'
|
|
13
|
+
NAME = 'Name'
|
|
14
|
+
FREQUENCY = 'Frequency [MHz]'
|
|
15
|
+
BANDWIDTH = 'Bandwidth [kHz]/Baud'
|
|
16
|
+
STATUS = 'Status'
|
|
17
|
+
DESCRIPTION = 'Description'
|
|
18
|
+
SOURCE = 'Source'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GetFrequencyDataFromCsv:
|
|
22
|
+
'''
|
|
23
|
+
Reads frequency data from a supplied CSV. The CSV should be placed in the `supplements` folder under the name `satellite_frequencies.csv` and should be
|
|
24
|
+
formatted with the following columns:
|
|
25
|
+
________________________________________________________________________________________________________
|
|
26
|
+
| LineNo | ID | Name | Frequency | Bandwidth | Status | Description | Source |
|
|
27
|
+
|
|
28
|
+
With all values in the frequency column of the same order of magnitude (typically MHz). The same goes for bandwidth. These columns should have the
|
|
29
|
+
integer value alone.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
'''
|
|
33
|
+
def __init__(self, filepath: Path):
|
|
34
|
+
self._filepath = filepath
|
|
35
|
+
|
|
36
|
+
def get(self) -> Dict[int, List['FrequencyRange']]:
|
|
37
|
+
frequencies = defaultdict(list)
|
|
38
|
+
for line in self._data[1:]:
|
|
39
|
+
id_string = line[FrequencyCsvKeys.ID.value]
|
|
40
|
+
if not id_string or id_string == 'None' or id_string == "nan":
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
frequency_range = FrequencyRange(frequency=self._get_frequency(line),
|
|
44
|
+
bandwidth=self._get_bandwidth(line),
|
|
45
|
+
status=self._get_status(line))
|
|
46
|
+
id_int = int(id_string)
|
|
47
|
+
frequencies[id_int].append(frequency_range)
|
|
48
|
+
|
|
49
|
+
return frequencies
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _get_frequency(line: Dict[str, str]):
|
|
53
|
+
frequency = line[FrequencyCsvKeys.FREQUENCY.value]
|
|
54
|
+
try:
|
|
55
|
+
return float(frequency)
|
|
56
|
+
except (TypeError, ValueError):
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _get_bandwidth(line: Dict[str, str]):
|
|
61
|
+
bandwidth = line[FrequencyCsvKeys.BANDWIDTH.value]
|
|
62
|
+
try:
|
|
63
|
+
return float(bandwidth.split()[0])
|
|
64
|
+
except (TypeError, ValueError, IndexError):
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _get_status(line: Dict[str, str]):
|
|
69
|
+
status = line[FrequencyCsvKeys.STATUS.value].lower()
|
|
70
|
+
return status
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def _data(self) -> List[Dict[str, str]]:
|
|
74
|
+
with open(self._filepath, 'r') as file:
|
|
75
|
+
return list(csv.DictReader(file, fieldnames=[e.value for e in FrequencyCsvKeys]))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from operator import attrgetter
|
|
3
|
+
from typing import List
|
|
4
|
+
from sopp.custom_dataclasses.satellite.satellite import Satellite
|
|
5
|
+
from sopp.custom_dataclasses.time_window import TimeWindow
|
|
6
|
+
from sopp.custom_dataclasses.position_time import PositionTime
|
|
7
|
+
|
|
8
|
+
'''
|
|
9
|
+
OverheadWindow class is designed to store the time windows that a given satellite is overhead and includes the Satellite object,
|
|
10
|
+
as well as a TimeWindow object that contains the interference start and end times.
|
|
11
|
+
|
|
12
|
+
+ satellite: the Satellite that is overhead during the time window.
|
|
13
|
+
+ positions: a list of PositionTimes of the satellite while within the main beam
|
|
14
|
+
+ overhead_time: a property TimeWindow representing the time the satellite enters and exits view.
|
|
15
|
+
'''
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class OverheadWindow:
|
|
19
|
+
satellite: Satellite
|
|
20
|
+
positions: List[PositionTime] = field(default_factory=list)
|
|
21
|
+
|
|
22
|
+
def __post_init__(self):
|
|
23
|
+
self.positions.sort(key=attrgetter('time'))
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def overhead_time(self):
|
|
27
|
+
if not self.positions:
|
|
28
|
+
return None
|
|
29
|
+
begin = self.positions[0].time
|
|
30
|
+
end = self.positions[-1].time
|
|
31
|
+
return TimeWindow(begin=begin, end=end)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Position:
|
|
6
|
+
"""
|
|
7
|
+
+ altitude: degrees above the horizon
|
|
8
|
+
+ azimuth: degrees east along the horizon from geographic north (so 0 degrees means north, 90 is east, 180
|
|
9
|
+
is south, and 270 is west).
|
|
10
|
+
"""
|
|
11
|
+
altitude: float
|
|
12
|
+
azimuth: float
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from sopp.custom_dataclasses.facility import Facility
|
|
4
|
+
from sopp.custom_dataclasses.frequency_range.frequency_range import FrequencyRange
|
|
5
|
+
from sopp.custom_dataclasses.time_window import TimeWindow
|
|
6
|
+
|
|
7
|
+
'''
|
|
8
|
+
The Reservation class stores the Facility, as well as some additional reservation-specific information, such as reservation start and end times.
|
|
9
|
+
+ facility: Facility object with RA facility and observation parameters
|
|
10
|
+
+ time: TimeWindow that represents the start and end time of the ideal reservation.
|
|
11
|
+
+ frequency: FrequencyRange of the requested observation. This is the frequency that the RA telescope wants to observe at.
|
|
12
|
+
'''
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Reservation:
|
|
17
|
+
facility: Facility
|
|
18
|
+
time: TimeWindow
|
|
19
|
+
frequency: FrequencyRange = field(default_factory=FrequencyRange)
|