from dataclasses import dataclass
from typing import List, Optional
from warnings import warn
from astropy import units
from astropy.coordinates import EarthLocation
from katpoint import Antenna as KatPointAntenna
from katpoint import DelayModel as KatPointDelayModel
from katpoint.conversion import ecef_to_enu
[docs]
@dataclass
class Antenna:
"""
A fixed antenna.
"""
interface: str
diameter: float
location: dict
fixed_delays: List[dict]
niao: float
station_name: Optional[str] = None
station_label: Optional[str] = None
station_id: Optional[int] = None
def __post_init__(self):
"""Used to ensure the Antenna has been instantiated correctly
Raises:
ValueError: if a value is missing
"""
if self.station_name is None and self.station_label is None:
raise ValueError("Antenna must have station_name or station_label")
if self.station_label is None:
warn("station_name is deprecated, use station_label instead")
self.station_label = self.station_name
if self.station_name is None:
self.station_name = self.station_label
if self.station_id is None:
raise ValueError("Antenna must have station_id")
@property
def pos(self):
"""A tuple representing the geocentric coordinates of this antenna"""
return (
self.location["geocentric"]["x"],
self.location["geocentric"]["y"],
self.location["geocentric"]["z"],
)
@property
def x(self):
"""X coordinate of geocentric location"""
return self.location["geocentric"]["x"]
@property
def y(self):
"""Y coordinate of geocentric location"""
return self.location["geocentric"]["y"]
@property
def z(self):
"""Z coordinate of geocentric location"""
return self.location["geocentric"]["z"]
@property
def lat(self):
"""Latitude of geocentric location"""
return self.location["geodetic"]["lat"]
@property
def lon(self):
"""Longitude of geocentric location"""
return self.location["geodetic"]["lon"]
@property
def h(self):
"""Height of geocentric location"""
return self.location["geodetic"]["h"]
@property
def name(self):
"""The name of this antenna"""
warn("station name is deprecated, use label instead")
return self.station_name
@property
def label(self):
"""The label of this antenna"""
return self.station_label
@property
def id(self):
"""The ID of this antenna"""
if self.station_id is None:
raise ValueError("station_id not included in this model")
return self.station_id
@property
def dish_diameter(self):
"""The diameter of this antenna"""
return self.diameter
@property
def fixed_delay_h(self):
"""Fixed delay for H-POL"""
return self.fixed_delays[0]["delay"]
@property
def fixed_delay_v(self):
"""Fixed delay for V-POL"""
return self.fixed_delays[1]["delay"]
def _calc_KatPointDesc(self, ant_location: EarthLocation = None):
"""Return a katpoint description string
for this antenna (or a reference location).
This is used to initialise the KatPoint Antenna object.
Essentially the katpoint antennas are initialised by
string. And this formulates the string
"""
if ant_location is None:
ant_location = EarthLocation.from_geocentric(
self.x * units.m, self.y * units.m, self.z * units.m
)
longitude, latitude, height = ant_location.to_geodetic(ellipsoid="WGS84")
label = self.station_label
lat = latitude.deg
long = longitude.deg
ht = height.value
diam = self.dish_diameter
desc = f"{label},{lat},{long},{ht},{diam},0,0,0,0"
return desc
def _calc_delay_model(self, ref_location: EarthLocation):
"""Initialise the delay models for the antennas.
The KatPoint model object is initialised ENU
and the fixed delays. There is a different
delay model for each antenna (and reference
location)
However a wrinkle here is that the Katpoint Antenna object should
now have a location that is the reference location. This is required
by the instatiation of the Antenna object.
"""
enu = ecef_to_enu(
ref_location.lat.rad,
ref_location.lon.rad,
ref_location.height.to_value(units.m),
self.x,
self.y,
self.z,
)
POS_E = enu[0]
POS_N = enu[1]
POS_U = enu[2]
FIX_H = self.fixed_delay_h
FIX_V = self.fixed_delay_v
NIAO = self.niao
desc = f"{POS_E},{POS_N},{POS_U},{FIX_H},{FIX_V},{NIAO}"
return KatPointDelayModel(desc)
[docs]
def as_KatPointAntenna(self, ref_location: EarthLocation | None = None):
"""Return a katpoint.Antenna object
for this antenna. There are two types of KatPoint antenna.
One is initialised with a delay model and a reference location
and this corresponds to an antenna in an array. A single antenna is not
initialised with a delay model, and it is initialised at the location of
the antenna.
Parameters:
ref_location: EarthLocation object for the reference location. This implicitly
creates an antenna in an array. The delay model will be
calculated for this antenna and the reference postion will be
used to initialise the antenna.
"""
if ref_location is not None:
return KatPointAntenna(
self._calc_KatPointDesc(ref_location),
delay_model=self._calc_delay_model(ref_location),
)
else:
return KatPointAntenna(self._calc_KatPointDesc())