EVN Observation Planner. Helps you to plan a VLBI observation. Given a date, source coordinates, and a VLBI array, it will tell you when the source can be observed by each antenna, the reached rms noise level and resolution, among other details.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

345 lines
12 KiB

import configparser
from importlib import resources
import numpy as np
from astropy import units as u
from astropy import coordinates as coord
from astropy.io import ascii
from astropy.time import Time
from astroplan import Observer
class Station(object):
def __init__(self, name, codename, network, location, freqs_sefds, min_elevation=20*u.deg,
fullname=None, all_networks=None, country='', diameter='', real_time=False):
"""Initializes a station. The given name must be the name of the station that
observes, with the typical 2-letter format used in the EVN (with exceptions).
- name : str
Name of the observer (the station that is going to observe).
- codename : str
A code for the name of the station. It can be the same as name.
- network : str
Name of the network to which the station belongs.
- location : EarthLocation
Position of the observer on Earth.
- freqs_sefds : dict
Dictionary with all frequencies the station can observe, and as values
the SEFD at each frequency.
- min_elevation : Quantity
Minimum elevation that the station can observe a source. If no units
provided, degrees are assumed. By default it 20 degrees.
- fullname : str [OPTIONAL]
Full name of the station. If not given, `name` is assumed.
- all_networks : str [OPTIONAL]
Networks where the station can participate (free style).
- country : str [OPTIONAL]
Country where the station is placed.
- diameter : str [OPTIONAL]
Diameter of the station (free format).
- real_time : bool [OPTIONAL, False by default]
If the station can participate in real-time observations (e.g. e-EVN).
self.observer = Observer(name=name.replace('_', ' '), location=location)
self._codename = codename
self._network = network
self._freqs_sefds = freqs_sefds
if (type(min_elevation) is float) or (type(min_elevation) is int):
self._min_elev = min_elevation*u.deg
self._min_elev = min_elevation
if fullname is None:
self._fullname = name
self._fullname = fullname
if all_networks is None:
self._all_networks = network
self._all_networks = all_networks
self._country = country
self._diameter = diameter
self._real_time = real_time
def name(self):
"""Name of the station.
return self.observer.name
def codename(self):
"""Codename of the station (typically a two-letter accronym).
return self._codename
def fullname(self):
return self._fullname
def network(self):
"""Name of the network to which the station belongs.
return self._network
def all_networks(self):
"""Name of all networks to which the station belongs.
return self._all_networks
def country(self):
return self._country
def diameter(self):
return self._diameter
def real_time(self):
return self._real_time
def location(self):
"""Location of the station in EarthLocation type.
return self.observer.location
def bands(self):
"""Observing bands the station can observe.
return self._freqs_sefds.keys()
def sefds(self):
"""Returns a dictionary with the SEFDs for each of the frequencies
the station can observe (given as keys).
return self._freqs_sefds
def min_elevation(self):
"""Minimum elevation the station can observe a source.
return self._min_elev
def elevation(self, obs_times, target):
"""Returns the elevation of the source as seen by the Station during obs_times.
- obs_times : astropy.time.Time
Time to compute the elevation of the source (either single time or a list of times).
- target : astroplan.FixedTarget
Target to observe.
- elevations : ndarray
Elevation of the source at the given obs_times
# source_altaz = source_coord.transform_to(coord.AltAz(obstime=obs_times,
# location=self.location))
# return source_altaz.alt
return self.observer.altaz(obs_times, target).alt
def altaz(self, obs_times, target):
"""Returns the altaz coordinates of the target for the given observing times.
return self.observer.altaz(obs_times, target)
def is_visible(self, obs_times, target):
"""Return if the source is visible for this station at the given times.
elevations = self.elevation(obs_times, target)
return np.where(elevations >= self.min_elevation)
def has_band(self, band):
"""Returns if the Station can observed the given band `the_band`.
return band in self.bands
def sefd(self, band):
"""Returns the SEFD of the Station at the given band.
return self._freqs_sefds[band]
def __str__(self):
return f"<{self.codename}>"
def __repr__(self):
return f"<stations.Station: {self.codename}>"
class SelectedStation(Station):
def __init__(self, name, codename, network, location, freqs_sefds, min_elevation=20*u.deg,
fullname=None, all_networks=None, country='', diameter='', real_time=False, selected=True):
self._selected = selected
super().__init__(name, codename, network, location, freqs_sefds,
min_elevation, fullname, all_networks, country, diameter, real_time)
def selected(self):
return self._selected
def selected(self, isselected):
assert isinstance(isselected, bool)
self._selected = isselected
class Stations(object):
"""Class that collects groups of stations (`Station` objects) that form
an observatory/array/network.
def __init__(self, name, stations):
- name : str
Name associated to the observatory/array/network.
- stations : list of Stations
List with all stations belonging to the network.
self._name = name
self._stations = {}
for a_station in stations:
assert a_station.codename not in self._stations.keys()
self._stations[a_station.codename] = a_station
self._keys = tuple(self._stations.keys())
def name(self):
"""Name of the observatory/array/network.
return self._name
def stations(self):
"""Returns the list of all stations in the observatory/array/network.
return list(self._stations.values())
def number_of_stations(self):
return len(self.stations)
def add(self, a_station):
"""Adds a new station to the observatory/array/network.
if a_station.codename in self._stations.keys():
print(f"WARNING: {a_station.codename} already in {self.name}.")
self._stations[a_station.codename] = a_station
self._keys = tuple(self._stations.keys())
def keys(self):
"""Returns a tuple of `codenames` from the stations.
return self._keys
def __str__(self):
return f"<{self.name}: <{', '.join(self._stations.keys())}>>"
def __len__(self):
return self._stations.__len__()
def __getitem__(self, key):
if isinstance(key, int):
return self._stations[self.keys()[key]]
return self._stations[key]
def __setitem__(self, key, value):
self._stations[key] = value
self._keys = tuple(self._stations.keys())
def __delitem__(self, key):
if isinstance(key, int):
self._keys = tuple(self._stations.keys())
def __iter__(self):
return iter(self._stations.values())
def __contains__(self, item):
return self._stations.__contains__(item)
def get_stations_from_configfile(filename=None):
"""Retrieves the information concerning all stations available in the 'filename'
file. Creates a Stations object containing the stations and the information on it.
The file must have a format readable by the Python ConfigParser.
Each section will be named with the name of the station, and then it must have
the following keys:
station - full name of the station.
code - codename for the station (typically two letters).
network - main network to which it belongs to.
possible_networks - all networks the station can participate in (including 'network')
country - country where the station is located.
diameter - string with the diameter of the station.
position = x, y, z (in meters). Geoposition of the station.
min_elevation (in degrees) - minimum elevation the station can observe.
real_time = yes/no - if the station can participate in real-time observations (e.g. e-EVN).
SEFD_** - SEFD of the station at the **cm band. If a given band is not present,
it is assumed that the station cannot observe it.
img - a path to an image of the station.
link - a url linking to the station page/related information.
config = configparser.ConfigParser()
if filename is None:
with resources.path("data", "stations_catalog.inp") as stations_catalog_path:
networks = Stations('network', [])
for stationname in config.sections():
temp = [float(i.strip()) for i in config[stationname]['position'].split(',')]
a_loc = coord.EarthLocation(temp[0]*u.m, temp[1]*u.m, temp[2]*u.m)
# Getting the SEFD values for the bands
min_elev = float(config[stationname]['min_elevation'])*u.deg
does_real_time = True if config[stationname]['real_time']=='yes' else False
sefds = {}
for akey in config[stationname].keys():
if 'SEFD_' in akey.upper():
sefds[f"{akey.upper().replace('SEFD_', '').strip()}cm"] = \
new_station = SelectedStation(stationname, config[stationname]['code'],
config[stationname]['network'], a_loc, sefds, min_elev,
config[stationname]['station'], config[stationname]['possible_networks'],
config[stationname]['country'], config[stationname]['diameter'], does_real_time)
return networks
def stations_with_band(self, band, output_network_name=None):
"""For a given collection of networks or Stations, returns a Stations object
including all stations that can observe at the given band.
- band : str
if output_network_name is None:
output_network_name = f"Stations@{band}"
antennas = stations.Stations(output_network_name, [])
for station in self.stations:
if band in station.bands:
return antennas