SOPP is an open-source tool for calculating satellite interference to radio astronomy observations.
Project description
S.O.P.P. - Satellite Orbit Prediction Processor
Quick Start Guide
Welcome to S.O.P.P., an open-source tool for calculating satellite interference in radio astronomy observations.
Introduction
The SOPP package assists astronomers in optimizing observation scheduling to mitigate radio interference from satellite sources. This is achieved by computing the positions of satellites relative to the observation facility and determining which of these satellites cause interference with the main beam during the observation.
The primary functionality offered by the package is accessed through the Sopp
class. This class implements two methods:
get_satellites_crossing_main_beam
get_satellites_above_horizon
High-Level Overview
-
Define Observation Characteristics:
- Provide the necessary observation characteristics to the
ConfigurationBuilder
class:- Facility Location
- Observation Time Window
- Antenna Frequency and Bandwidth
- Antenna observation path
- Satellite TLE data
- Satellite Filtering (optional)
- Runtime settings
- Provide the necessary observation characteristics to the
-
Determine Satellite Interference:
- Create an instance of Sopp using the
Configuration
built by theConfigurationBuilder
class. - Utilize the methods of the Sopp class to obtain position data of interfering satellites:
get_satellites_crossing_main_beam
: Returns satellites that cross the main beam during observation.get_satellites_above_horizon
: Returns all satellites above the horizon during the observation.
- Create an instance of Sopp using the
Define Observation Characteristics
ConfigurationBuilder
The observation characteristics must be provided to the ConfigurationBuilder
class. The configuration builder will then construct a fully prepared Configuration
object used to determine satellite interference.
An example using ConfigurationBuilder
:
configuration = (
ConfigurationBuilder()
.set_facility(
latitude=40.8178049,
longitude=-121.4695413,
elevation=986,
name='HCRO',
beamwidth=3,
bandwidth=10,
frequency=135
)
.set_time_window(
begin='2023-11-15T08:00:00.0',
end='2023-11-15T08:30:00.0'
)
.set_observation_target(
declination='7d24m25.426s',
right_ascension='5h55m10.3s'
)
.set_runtime_settings(
concurrency_level=8,
time_continuity_resolution=1
)
# Alternatively set all of the above settings from a config file
#.set_from_config_file(config_file='./supplements/config.json')
.set_satellites(tle_file='./supplements/satellites.tle')
.build()
)
The ConfigurationBuilder
must call the following methods with the necessary arguments:
set_facility()
set_time_window()
set_observation_target()
set_satellites()
set_runtime_settings()
set_facility()
The set_facility()
method specifies the latitude, longitude, elevation (in meters), name, bandwidth, beamwidth and frequency:
configuration.set_facility(
latitude=40.8178049,
longitude=-121.4695413,
elevation=986,
name='HCRO',
beamwidth=3,
bandwidth=10,
frequency=135
)
set_time_window()
The set_time_window()
method defines the observation time window in UTC, specifying when the observation will take place. The date format follows the ISO 8601 datetime format: Y-m-dTH:M:S.f
. The provided datetime string can include microseconds or not. It additionally accepts a time zone, for example, 2023-11-15T08:00:00-7:00
. All times are converted to UTC.
configuration.set_time_window(
begin='2023-11-15T08:00:00.0',
end='2023-11-15T08:30:00.0'
)
set_observation_target()
The set_observation_target()
method defines the target for observation. It has three options:
- Specify a target by providing its declination and right ascension:
configuration.set_observation_target(
declination='7d24m25.426s',
right_ascension='5h55m10.3s'
)
- Specify a static location to observe by providing an azimuth and altitude:
configuration.set_observation_target(
azimuth=24.2,
right_ascension=78.1
)
- Provide a custom antenna direction path (how to construct a custom path is explained later):
configuration.set_observation_target(
custom_path=custom_path
)
set_satellites()
The set_satellites()
method takes the file path of the satellite TLE data and an optional frequency file. The frequency data can be utilized to filter out satellites whose downlink frequency does not overlap with the observation frequency.
configuration.set_satellites(
tle_file='path/to/satellites.tle',
frequency_file='/path/to/frequency.csv'
)
set_satellites_filter()
There is optionally the ability to filter the satellites list. To do this call the set_satellites_filter()
method with a Filterer
object. The Filterer
object is explained in more detail in the 'Filtering Satellites' section.
configuration.set_satellites_filter(
filterer=filterer
)
set_runtime_settings()
The set_runtime_settings()
method specifies the time resolution for calculating satellite positions in seconds via the time_continuity_resolution
parameter. Additionally, the concurrency_level
parameter determines the number of parallel jobs during satellite position calculation, optimizing runtime speeds. This value should be not exceed the number of cores on the machine.
configuration.set_runtime_settings(
concurrency_level=8,
time_continuity_resolution=1
)
build()
Finally, once all the required methods have been called use the build()
method to obtain the Configuration
object:
configuration = configuration.build()
set_from_config_file()
The set_from_config_file()
method can be used to provide all of the observation characteristics via a JSON configuration file instead of being set programatically.
configuration = (
ConfigurationBuilder()
.set_from_config_file(config_file='./supplements/config.json')
.set_satellites(tle_file='./supplements/satellites.tle')
.build()
)
The JSON config file follows the following format:
{
"facility": {
"beamwidth": 3,
"elevation": 986,
"latitude": 40.8178049,
"longitude": -121.4695413,
"name": "HCRO"
},
"frequencyRange": {
"bandwidth": 10,
"frequency": 135
},
"observationTarget": {
"declination": "-38d6m50.8s",
"rightAscension": "4h42m"
},
"reservationWindow": {
"startTimeUtc": "2023-09-27T12:00:00.000000",
"endTimeUtc": "2023-09-27T13:00:00.000000"
},
"runtimeSettings": {
"concurrency_level": 4,
"time_continuity_resolution": 1
}
}
Determine Satellite Interference
Sopp
The Sopp
class utilizes the previously created configuration object to identify satellite interference. It is initialized with the Configuration
obtained from ConfigurationBuilder
.
sopp = Sopp(configuration=configuration)
Finally, obtain the position data of interfering satellites, run either:
get_satellites_crossing_main_beam
: Returns satellites that cross the main beam during observation.get_satellites_above_horizon
: Returns all satellites that are above the horizon during the observation.
interference_events = sopp.get_satellites_crossing_main_beam()
The data is returned as a list of OverheadWindow
, which is defined as:
class OverheadWindow:
satellite: Satellite
positions: List[PositionTime]
The Satellite
class, containins details about the satellite and a list of PositionTime objects. The PositionTime
dataclass specifies the satellite's position in altitude and azimuth at a discrete point in time. All times are in UTC.
Filtering Satellites
The list of satellites can be filtered by using a Filterer
object, adding filters to it and then passing the Filterer
object to a ConfigurationBuilder
. The user can define any filtering logic wanted, however a few built in filters are provided. If the filtering condition evaluates to True
the Satellite will be included in the final list.
The provided filters accessible from sopp.satellites_filter.filters
include:
filter_frequency
:
returns True
if a satellite's downlink transmission frequency
overlaps with the desired observation frequency. If there is no information
on the satellite frequency, it will return True to err on the side of caution
for potential interference. Accepts a FrequencyRange
object.
filter_name_contains
:
returns True
if a given substring is present in the name of a Satellite.
filter_name_does_not_contain
:
returns True
if a given substring is not present in the name of a Satellite.
filter_name_is
:
returns True
if a given substring matches exactly the name of a Satellite.
filter_is_leo
:
returns Low Earth Orbit (LEO) satellites based on their orbital period. The filter checks if the satellite's orbits per day is >= 5.0
filter_is_meo
:
returns Medium Earth Orbit (MEO) satellites based on their orbital period. The filter checks if the satellite's orbits per day is >= 1.5 and < 5.0
filter_is_geo
:
returns Geostationary Orbit (GEO) satellites based on their orbital period.
The filter checks if the satellite's orbits per day is >= 0.85 and < 1.5
For example, to find all Satellites that are not Starlink, but are in LEO and that have overlapping downlink transmission frequency:
from sopp.satellites_filter.filterer import Filterer
filterer = (
Filterer()
.add_filter(filter_name_does_not_contain('STARLINK'))
.add_filter(filter_is_leo())
.add_filter(filter_frequency(FrequencyRange(135.5, 10)))
)
User defined filters can be defined as well. The add_filter
method takes a lambda. For example, if the user would prefer to define LEO satellites differently than the provided filtering function:
filterer = (
Filterer()
.add_filter(lambda satellite: satellite.orbital_period <= 100.0)
)
Using TleFetcher to Obtain TLE File
A TleFetcher class exists that will automatically fetch the latest TLEs from Celestrak or SpaceTrack:
from sopp.tle_fetcher.tle_fetcher_celestrak import TleFetcherCelestrak
fetcher = TleFetcherCelestrak(tle_file_path='path/to/save/satellites.tle')
fetcher.fetch_tles()
SpaceTrack is called identically, however you must set the environment variable IDENTITY
with your username and PASSWORD
with your password:
from sopp.tle_fetcher.tle_fetcher_spacetrack import TleFetcherSpacetrack
fetcher = TleFetcherSpacetrack(tle_file_path='path/to/save/satellites.tle')
fetcher.fetch_tles()
Providing Custom Path for Observation
Instead of specifying an observation target or static observation with altitude and azimuth a custom path can be provided as a list of PositionTime
objects.
custom_path = [
PositionTime(
position=Position(altitude=.0, azimuth=.1),
time=datetime(year=2023, month=3, day=30, hour=10, minute=1, tzinfo=pytz.UTC)
),
PositionTime(
position=Position(altitude=.1, azimuth=.2),
time=datetime(year=2023, month=3, day=30, hour=10, minute=2, tzinfo=pytz.UTC)
),
PositionTime(
position=Position(altitude=.2, azimuth=.2),
time=datetime(year=2023, month=3, day=30, hour=10, minute=3, tzinfo=pytz.UTC)
),
]
Example Code
from sopp.sopp import Sopp
from sopp.builder.configuration_builder import ConfigurationBuilder
from sopp.satellites_filter.filterer import Filterer
from sopp.satellites_filter.filters import (
filter_name_does_not_contain,
filter_is_leo,
)
def main():
filterer = (
Filterer()
.add_filter(filter_name_does_not_contain('STARLINK'))
.add_filter(filter_is_leo())
)
configuration = (
ConfigurationBuilder()
.set_facility(
latitude=40.8178049,
longitude=-121.4695413,
elevation=986,
name='HCRO',
beamwidth=3,
bandwidth=10,
frequency=135
)
.set_time_window(
begin='2024-01-18T08:00:00.0',
end='2024-01-18T08:30:00.0'
)
.set_observation_target(
declination='7d24m25.426s',
right_ascension='5h55m10.3s'
)
.set_runtime_settings(
concurrency_level=8,
time_continuity_resolution=1
)
# Alternatively set all of the above settings from a config file
#.set_from_config_file(config_file='./supplements/config.json')
.set_satellites(tle_file='./supplements/satellites.tle')
.set_satellites_filter(filterer)
.build()
)
# Display configuration
print('\nFinding satellite interference events for:\n')
print(f'Facility: {configuration.reservation.facility.name}')
print(f'Location: {configuration.reservation.facility.coordinates} at elevation '
f'{configuration.reservation.facility.elevation}')
print(f'Reservation start time: {configuration.reservation.time.begin}')
print(f'Reservation end time: {configuration.reservation.time.end}')
print(f'Observation frequency: {configuration.reservation.frequency.frequency} MHz')
# Determine Satellite Interference
event_finder = Sopp(configuration=configuration)
interference_events = event_finder.get_satellites_crossing_main_beam()
print('\n==============================================================\n')
print(f'There are {len(interference_events)} satellite interference\n'
f'events during the reservation\n')
print('==============================================================\n')
for i, window in enumerate(interference_events, start=1):
max_alt = max(window.positions, key=lambda pt: pt.position.altitude)
print(f'Satellite interference event #{i}:')
print(f'Satellite: {window.satellite.name}')
print(f'Satellite enters view: {window.overhead_time.begin} at '
f'{window.positions[0].position.azimuth:.2f}')
print(f'Satellite leaves view: {window.overhead_time.end} at '
f'{window.positions[-1].position.azimuth:.2f}')
print(f'Satellite maximum altitude: {max_alt.position.altitude:.2f}')
print('__________________________________________________\n')
if __name__ == '__main__':
main()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.