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
:
from sopp.builder.configuration_builder import ConfigurationBuilder
configuration = (
ConfigurationBuilder()
.set_facility(
latitude=40.8178049,
longitude=-121.4695413,
elevation=986,
name='HCRO',
beamwidth=3,
)
.set_frequency_range(
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,
min_altitude=5.0,
)
# 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_frequency_range()
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
)
The set_frequency_range()
method specifies bandwidth, and frequency of the observation:
configuration.set_frequency_range(
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
. Alternatively, begin
and end
can be provided as datetimes. 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,
altitude=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' # optional
)
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
)
add_filter()
Alternatively to constructing a Filterer
object you can simply call add_filter()
configuration.add_filter(filter_frequency(FrequencyRange(10, 10)))
set_runtime_settings()
The set_runtime_settings()
method:
- Specifies the time resolution for calculating satellite positions in seconds via the
time_continuity_resolution
parameter. - Specifies 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. - The
min_altitude
specifies the minimum altitude a satellite must be to be considered above the horizon. Useful for locations with obstructed horizons. - Runtime settings are optional, the defaults are: concurrency_level = 1, time_continuity_resolution = 1 and min_altitude = 0.0.
configuration.set_runtime_settings(
concurrency_level=8,
time_continuity_resolution=1,
min_altitude=0.0,
)
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,
"min_altitude": 0.0
}
}
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
.
from sopp.sopp import Sopp
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, azimuth and distance in km 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.
If None
is passed to any of the filters, no filtering for that specific filter will be applied.
Alternatively to passing a Filterer
object to the ConfigurationBuilder
via set_satellites_filter
, filters can simply be added with add_filter(filter_name_contains('STARLINK'))
.
The provided filters accessible from sopp.satellites_filter.filters
include:
filter_frequency()
:
Parameters: - observation_frequency (FrequencyRange): The observation frequency range.
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. Requires a FrequencyRange
object.
filter_name_regex()
:
Parameters: - regex (str): The regex to match for in the satellite names.
returns True
if a given regex matches in the name of a Satellite.
Example: filter_name_regex('YAM|ZARYA') will return a list of satellites that contain YAM or ZARYA within their name.
filter_name_contains()
:
Parameters: - substring (str): The substring to check for in the satellite names.
returns True
if a given substring is present in the name of a Satellite.
filter_name_does_not_contain
:
Parameters: - substring (str): The substring to check for absence for in the satellite names.
returns True
if a given substring is not present in the name of a Satellite.
filter_name_is()
:
Parameters: - substring (str): The substring to match for in the satellite names.
returns True
if a given substring matches exactly the name of a Satellite.
filter_orbit_is(orbit_type)
:
Parameters: - orbit_type (str): The type of orbit ('leo', 'meo', or 'geo').
if orbit_type='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
if orbit_type='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
if orbit_type='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
from sopp.satellites_filter.filters import (
filter_name_does_not_contain,
filter_orbit_is,
filter_frequency,
filter_name_is,
)
filterer = (
Filterer()
.add_filter(filter_name_does_not_contain('STARLINK'))
.add_filter(filter_orbit_is(orbit_type='leo'))
.add_filter(filter_frequency())
.add_filter(filter_name_is(None)) # this filter will do nothing
)
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.filters import (
filter_name_does_not_contain,
filter_orbit_is,
)
def main():
configuration = (
ConfigurationBuilder()
.set_facility(
latitude=40.8178049,
longitude=-121.4695413,
elevation=986,
name='HCRO',
beamwidth=3,
)
.set_frequency_range(
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')
.add_filter(filter_name_does_not_contain('STARLINK'))
.add_filter(filter_orbit_is(orbit_type='leo'))
.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
sopp = Sopp(configuration=configuration)
interference_events = sopp.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} '
f'Distance: {window.positions[0].position.distance_km:.2f} km')
print(f'Satellite leaves view: {window.overhead_time.end} at '
f'{window.positions[-1].position.azimuth:.2f} '
f'Distance: {window.positions[-1].position.distance_km:.2f} km')
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.
Source Distribution
Built Distribution
File details
Details for the file sopp-0.8.2.tar.gz
.
File metadata
- Download URL: sopp-0.8.2.tar.gz
- Upload date:
- Size: 36.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/5.0.0 CPython/3.12.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 88493dbe5be2a48d217c6be739e40949bee8c40ce93df6628ef584647ce12fc1 |
|
MD5 | 212089515bfa0d292231c974f0c967af |
|
BLAKE2b-256 | 820031d01051938ea630a465c81602fd16802912de67f0435dd6c6bf2e7b6d83 |
File details
Details for the file sopp-0.8.2-py3-none-any.whl
.
File metadata
- Download URL: sopp-0.8.2-py3-none-any.whl
- Upload date:
- Size: 57.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/5.0.0 CPython/3.12.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 937500cc91432cca781963a87e799179e4bf64b633d1b1b966b494b47cdc4236 |
|
MD5 | 7a278baf605d3efd9bfe576ef7347358 |
|
BLAKE2b-256 | c14a86735b391cda2022e332cc410552810df0656f4984c6a72342780ebfc924 |