satellite.py
The template contains two classes, the Satellite (Entity) object class and the one StatusPublisher (WallclockTimeIntervalPublisher) class. Both Entity and WallclockTimeIntervalPublisher come from the NOS-T Tools Library. They are found, respectively in the Simulator Objects and Application Objects sections.
The template also adds several methods within these classes. The classes have built-in methods that are unique to each object. The Satellite class tracks state transitions for the satellite, while the StatusPublisher standardizes the message structure and frequency of published messages updating these states.
Satellite
- Satellite.initialize(init_time)
Initializes the entity at a designated initial scenario time.
- Parameters:
init_time (
datetime) – initial scenario time
- Satellite.tick(time_step)
Computes the next state transition following an elapsed scenario duration (time step). If the entity hasn’t been initialized yet, the time_step will be stored but no time advancement will occur until the entity is initialized.
- Parameters:
time_step (
timedelta) – elapsed scenario duration
- Satellite.tock()
Commits the state transition pre-computed in tick and notifies observers of changes.
Status Publisher
- class StatusPublisher(app, ES, time_status_step=None, time_status_init=None)
Instrument View and Interfacing with the Ground Station Template
This satellite template is very basic. The example codes contain satellite scripts that have extended functionality. In particular, some useful functions are to see where on the Earth’s surface is in view of an instrument, and when the spacecraft is able to communicate with a ground station. The following code snippet from the FireSat+ test suite contains methods that can perform these functions. Combining the check_in_view and check_in_range methods allows for simulating ground communciations with the Ground Station Template. Further discussion on all of these methods is found in the Hand-on Tutorial Satellites section.
NOTE: If you wish to add functions to the satellite template, remember to add the relevant data to the message that main_sat.py publishes, and the structure for that data to the schemas.py.
altitude (float): Altitude (meters) above surface of the observation
field_of_regard (float): Angular width (degrees) of observation
Returns:
float : min_elevation
The minimum elevation angle (degrees) for observation
"""
earth_equatorial_radius = 6378137.000000000
earth_polar_radius = 6356752.314245179
earth_mean_radius = (2 * earth_equatorial_radius + earth_polar_radius) / 3
# eta is the angular radius of the region viewable by the satellite
sin_eta = np.sin(np.radians(field_of_regard / 2))
# rho is the angular radius of the earth viewed by the satellite
sin_rho = earth_mean_radius / (earth_mean_radius + altitude)
# epsilon is the min satellite elevation for obs (grazing angle)
cos_epsilon = sin_eta / sin_rho
if cos_epsilon > 1:
return 0.0
return np.degrees(np.arccos(cos_epsilon))
def compute_sensor_radius(altitude, min_elevation):
"""
Computes the sensor radius for a satellite at current altitude given minimum elevation constraints.
Args:
altitude (float): Altitude (meters) above surface of the observation
min_elevation (float): Minimum angle (degrees) with horizon for visibility
Returns:
float : sensor_radius
The radius (meters) of the nadir pointing sensors circular view of observation
"""
earth_equatorial_radius = 6378137.0
earth_polar_radius = 6356752.314245179
earth_mean_radius = (2 * earth_equatorial_radius + earth_polar_radius) / 3
# rho is the angular radius of the earth viewed by the satellite
sin_rho = earth_mean_radius / (earth_mean_radius + altitude)
# eta is the nadir angle between the sub-satellite direction and the target location on the surface
eta = np.degrees(np.arcsin(np.cos(np.radians(min_elevation)) * sin_rho))
# calculate swath width half angle from trigonometry
sw_HalfAngle = 90 - eta - min_elevation
if sw_HalfAngle < 0.0:
return 0.0
return earth_mean_radius * np.radians(sw_HalfAngle)
def get_elevation_angle(t, sat, loc):
"""
Returns the elevation angle (degrees) of satellite with respect to the topocentric horizon.
Args:
t (:obj:`Time`): Time object of skyfield.timelib module
sat (:obj:`EarthSatellite`): Skyview EarthSatellite object from skyfield.sgp4lib module
loc (:obj:`GeographicPosition`): Geographic location on surface specified by latitude-longitude from skyfield.toposlib module
Returns:
float : alt.degrees
Elevation angle (degrees) of satellite with respect to the topocentric horizon
"""
difference = sat - loc
topocentric = difference.at(t)
# NOTE: Topos uses term altitude for what we are referring to as elevation
alt, az, distance = topocentric.altaz()
return alt.degrees
def check_in_view(t, satellite, topos, min_elevation):
"""
Checks if the elevation angle of the satellite with respect to the ground location is greater than the minimum elevation angle constraint.
Args:
t (:obj:`Time`): Time object of skyfield.timelib module
satellite (:obj:`EarthSatellite`): Skyview EarthSatellite object from skyfield.sgp4lib module
topos (:obj:`GeographicPosition`): Geographic location on surface specified by latitude-longitude from skyfield.toposlib module
min_elevation (float): Minimum elevation angle (degrees) for ground to be in view of satellite, as calculated by compute_min_elevation
Returns:
bool : isInView
True/False indicating visibility of ground location to satellite
"""
isInView = False
elevationFromFire = get_elevation_angle(t, satellite, topos)
if elevationFromFire >= min_elevation:
isInView = True
return isInView
def check_in_range(t, satellite, grounds):
"""
Checks if the satellite is in range of any of the operational ground stations.
Args:
t (:obj:`Time`): Time object of skyfield.timelib module
satellite (:obj:`EarthSatellite`): Skyview EarthSatellite object from skyfield.sgp4lib module
grounds (:obj:`DataFrame`): Dataframe of ground station locations, minimum elevation angles for communication, and operational status (T/F)
Returns:
bool, int :
isInRange
True/False indicating visibility of satellite to any operational ground station
groundId
groundId of the ground station currently in comm range (NOTE: If in range of two ground stations simultaneously, will return first groundId)
"""
isInRange = False
groundId = None
for k, ground in grounds.iterrows():
if ground.operational:
groundLatLon = wgs84.latlon(ground.latitude, ground.longitude)
satelliteElevation = get_elevation_angle(t, satellite, groundLatLon)
if satelliteElevation >= ground.elevAngle:
isInRange = True
groundId = k
break
return isInRange, groundId
# define an entity to manage satellite updates
class Constellation(Entity):
"""