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.
Files changed (135) hide show
  1. examples/example.py +119 -0
  2. sopp/__init__.py +0 -0
  3. sopp/builder/configuration_builder.py +119 -0
  4. sopp/config_file/__init__.py +0 -0
  5. sopp/config_file/config_file_factory.py +13 -0
  6. sopp/config_file/support/__init__.py +0 -0
  7. sopp/config_file/support/config_file_base.py +20 -0
  8. sopp/config_file/support/config_file_json.py +108 -0
  9. sopp/config_file/support/utilities.py +1 -0
  10. sopp/custom_dataclasses/__init__.py +0 -0
  11. sopp/custom_dataclasses/configuration.py +17 -0
  12. sopp/custom_dataclasses/coordinates.py +20 -0
  13. sopp/custom_dataclasses/facility.py +26 -0
  14. sopp/custom_dataclasses/frequency_range/__init__.py +0 -0
  15. sopp/custom_dataclasses/frequency_range/frequency_range.py +36 -0
  16. sopp/custom_dataclasses/frequency_range/support/__init__.py +0 -0
  17. sopp/custom_dataclasses/frequency_range/support/get_frequency_data_from_csv.py +75 -0
  18. sopp/custom_dataclasses/observation_target.py +7 -0
  19. sopp/custom_dataclasses/overhead_window.py +31 -0
  20. sopp/custom_dataclasses/position.py +12 -0
  21. sopp/custom_dataclasses/position_time.py +10 -0
  22. sopp/custom_dataclasses/reservation.py +19 -0
  23. sopp/custom_dataclasses/runtime_settings.py +14 -0
  24. sopp/custom_dataclasses/satellite/__init__.py +0 -0
  25. sopp/custom_dataclasses/satellite/international_designator.py +19 -0
  26. sopp/custom_dataclasses/satellite/mean_motion.py +8 -0
  27. sopp/custom_dataclasses/satellite/satellite.py +50 -0
  28. sopp/custom_dataclasses/satellite/tle_information.py +74 -0
  29. sopp/custom_dataclasses/time_window.py +19 -0
  30. sopp/event_finder/__init__.py +0 -0
  31. sopp/event_finder/event_finder.py +44 -0
  32. sopp/event_finder/event_finder_rhodesmill/__init__.py +0 -0
  33. sopp/event_finder/event_finder_rhodesmill/event_finder_rhodesmill.py +101 -0
  34. sopp/event_finder/event_finder_rhodesmill/support/evenly_spaced_time_intervals_calculator.py +15 -0
  35. sopp/event_finder/event_finder_rhodesmill/support/satellite_positions_with_respect_to_facility_retriever/__init__.py +0 -0
  36. sopp/event_finder/event_finder_rhodesmill/support/satellite_positions_with_respect_to_facility_retriever/satellite_positions_with_respect_to_facility_retriever.py +17 -0
  37. 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
  38. sopp/event_finder/event_finder_rhodesmill/support/satellites_within_main_beam_filter.py +81 -0
  39. sopp/event_finder/support/__init__.py +0 -0
  40. sopp/event_finder/support/overhead_window_from_events.py +56 -0
  41. sopp/frequency_filter/__init__.py +0 -0
  42. sopp/frequency_filter/frequency_filter.py +33 -0
  43. sopp/generate_tardys3.py +33 -0
  44. sopp/path_finder/__init__.py +0 -0
  45. sopp/path_finder/observation_path_finder.py +24 -0
  46. sopp/path_finder/observation_path_finder_astropy.py +47 -0
  47. sopp/path_finder/observation_path_finder_rhodesmill.py +70 -0
  48. sopp/retrievers/__init__.py +0 -0
  49. sopp/retrievers/retriever.py +7 -0
  50. sopp/retrievers/retriever_json_file.py +14 -0
  51. sopp/retrievers/satellite_retriever/__init__.py +0 -0
  52. sopp/retrievers/satellite_retriever/satellite_retriever.py +9 -0
  53. sopp/retrievers/satellite_retriever/satellite_retriever_json_file.py +12 -0
  54. sopp/retrievers/satellite_retriever/skyfield_satellite_retriever.py +14 -0
  55. sopp/satellites_loader/satellites_loader.py +10 -0
  56. sopp/satellites_loader/satellites_loader_from_files.py +48 -0
  57. sopp/tle_fetcher/__init__.py +0 -0
  58. sopp/tle_fetcher/tle_fetcher.py +37 -0
  59. sopp/utilities.py +66 -0
  60. sopp/variable_initializer/variable_initializer.py +22 -0
  61. sopp/variable_initializer/variable_initializer_from_config.py +29 -0
  62. sopp/window_finder.py +85 -0
  63. sopp-0.1.0.dist-info/METADATA +200 -0
  64. sopp-0.1.0.dist-info/RECORD +135 -0
  65. sopp-0.1.0.dist-info/WHEEL +4 -0
  66. sopp-0.1.0.dist-info/licenses/COPYING +661 -0
  67. tests/__init__.py +0 -0
  68. tests/config_file/__init__.py +0 -0
  69. tests/config_file/config_file_json/__init__.py +0 -0
  70. tests/config_file/config_file_json/arbitrary_config_file.json +25 -0
  71. tests/config_file/config_file_json/arbitrary_config_file_2.json +25 -0
  72. tests/config_file/config_file_json/arbitrary_config_file_no_observation_target.json +21 -0
  73. tests/config_file/config_file_json/arbitrary_config_file_no_static_antenna_position.json +21 -0
  74. tests/config_file/config_file_json/arbitrary_config_file_partial_observation_target.json +24 -0
  75. tests/config_file/config_file_json/arbitrary_config_file_partial_static_antenna_position.json +24 -0
  76. tests/config_file/config_file_json/arbitrary_config_file_runtime_settings.json +29 -0
  77. tests/config_file/config_file_json/arbitrary_config_file_with_antenna_position_times.json +37 -0
  78. tests/config_file/test_config_file_default_argument.py +71 -0
  79. tests/config_file/test_config_file_provided_argument.py +95 -0
  80. tests/dataclasses/__init__.py +0 -0
  81. tests/dataclasses/frequency_range/__init__.py +0 -0
  82. tests/dataclasses/frequency_range/arbitrary_frequency_file.csv +2 -0
  83. tests/dataclasses/frequency_range/arbitrary_frequency_file_junk.csv +5 -0
  84. tests/dataclasses/frequency_range/arbitrary_frequency_file_none.csv +5 -0
  85. tests/dataclasses/frequency_range/arbitrary_frequency_file_two_frequencies.csv +4 -0
  86. tests/dataclasses/frequency_range/arbitrary_frequency_file_with_bandwidth.csv +5 -0
  87. tests/dataclasses/frequency_range/test_from_csv.py +135 -0
  88. tests/dataclasses/satellite/__init__.py +0 -0
  89. tests/dataclasses/satellite/fake_ISS_frequency_file.csv +2 -0
  90. tests/dataclasses/satellite/fake_ISS_frequency_file_multiple.csv +3 -0
  91. tests/dataclasses/satellite/international_designator/__init__.py +0 -0
  92. tests/dataclasses/satellite/international_designator/test_international_designator_from_string.py +21 -0
  93. tests/dataclasses/satellite/international_designator/test_international_designator_to_string.py +24 -0
  94. tests/dataclasses/satellite/international_space_station_tle.tle +3 -0
  95. tests/dataclasses/satellite/international_space_station_tle_multiple.tle +6 -0
  96. tests/dataclasses/satellite/test_satellite_to_rhodesmill.py +52 -0
  97. tests/dataclasses/satellite/test_tle_to_cu_satellite.py +44 -0
  98. tests/dataclasses/satellite/utilities.py +38 -0
  99. tests/dataclasses/test_time_window_overlaps.py +47 -0
  100. tests/definitions.py +1 -0
  101. tests/event_finder/__init__.py +0 -0
  102. tests/event_finder/event_finder_rhodesmill/__init__.py +0 -0
  103. tests/event_finder/event_finder_rhodesmill/definitions.py +31 -0
  104. tests/event_finder/event_finder_rhodesmill/support/__init__.py +0 -0
  105. tests/event_finder/event_finder_rhodesmill/support/test_satellite_positions_with_respect_to_facility_retriever_rhodesmill.py +78 -0
  106. tests/event_finder/event_finder_rhodesmill/support/test_time_intervals_calculator.py +0 -0
  107. tests/event_finder/event_finder_rhodesmill/test_event_finder_reservation_start_time.py +70 -0
  108. tests/event_finder/event_finder_rhodesmill/test_event_finder_rhodesmill.py +123 -0
  109. tests/event_finder/event_finder_rhodesmill/test_rhodemill_integration.py +81 -0
  110. tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter.py +171 -0
  111. tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter_altitude.py +54 -0
  112. tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter_unsorted_antenna_positions.py +39 -0
  113. tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_filter_unsorted_satellite_positions.py +48 -0
  114. tests/event_finder/event_finder_rhodesmill/test_satellites_within_main_beam_modulo_360.py +39 -0
  115. tests/frequency_filter/__init__.py +0 -0
  116. tests/frequency_filter/test_frequency_filter.py +156 -0
  117. tests/path_finder/path_finder_base_test.py +40 -0
  118. tests/path_finder/test_path_finder_astropy.py +6 -0
  119. tests/path_finder/test_path_finder_astropy_rhodesmill_equivalency.py +44 -0
  120. tests/path_finder/test_path_finder_rhodesmill.py +22 -0
  121. tests/retrievers/__init__.py +0 -0
  122. tests/retrievers/facility_retriever/test_facility_retriever_json_file.py +0 -0
  123. tests/satellites_loader/satellite_frequencies.csv +6 -0
  124. tests/satellites_loader/satellites.tle +6 -0
  125. tests/satellites_loader/test_satellites_loader_from_files.py +109 -0
  126. tests/variable_initializer/test_variable_initializer_from_config.py +161 -0
  127. tests/window_finder/__init__.py +0 -0
  128. tests/window_finder/definitions.py +9 -0
  129. tests/window_finder/fake_ISS_frequency_file.csv +2 -0
  130. tests/window_finder/fake_ISS_frequency_file_multiple.csv +3 -0
  131. tests/window_finder/international_space_station_tle.tle +3 -0
  132. tests/window_finder/international_space_station_tle_multiple.tle +6 -0
  133. tests/window_finder/support/__init__.py +0 -0
  134. tests/window_finder/support/validator_satellites_are_overhead_at_specific_times.py +17 -0
  135. 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
@@ -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,7 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class ObservationTarget:
6
+ declination: str
7
+ right_ascension: str
@@ -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,10 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+
4
+ from sopp.custom_dataclasses.position import Position
5
+
6
+
7
+ @dataclass
8
+ class PositionTime:
9
+ position: Position
10
+ time: datetime
@@ -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)