Source code for rubin_scheduler.utils.site
__all__ = ("Site", "declination_dependent_fwhm")
import warnings
import astropy.units as u
import healpy as hp
import numpy as np
import numpy.typing as npt
from astropy.coordinates import EarthLocation
from .constants import DEFAULT_NSIDE
from .healpy_utils import hpid2_ra_dec
class LsstSiteParameters:
"""
This is a struct containing the LSST site parameters as defined in
https://docushare.lsstcorp.org/docushare/dsweb/ImageStoreViewer/LSE-30
(accessed on 4 January 2016)
Updated from:
https://rubinobs.atlassian.net/wiki/spaces/LTS/
pages/50084908/Rubin+Telescopes+
Latitude+and+Longitude+Coordinates?
force_transition=c54e56dd-7d7b-42c2-b5e5-79aa1c1bc777
This class only exists for initializing Site with LSST parametervalues.
Users should not be accessing this class directly.
"""
def __init__(self):
self.longitude = -70.74941 # in degrees
self.latitude = -30.244628 # in degrees
self.height = 2650.0 # in meters
self.temperature = 11.5 # in centigrade
self.pressure = 750.0 # in millibars
self.humidity = 0.4 # scale 0-1
self.lapse_rate = 0.0065 # in Kelvin per meter
# the lapse rate was not specified by LSE-30;
# 0.0065 K/m appears to be the "standard" value
# see, for example
# http://mnras.oxfordjournals.org/content/365/4/1235.full
class AuxTelParameters:
"""
Struct with AuxTel default parameters
Values from:
https://rubinobs.atlassian.net/wiki/spaces/LTS/
pages/50084908/Rubin+Telescopes+
Latitude+and+Longitude+Coordinates?
force_transition=c54e56dd-7d7b-42c2-b5e5-79aa1c1bc777
"""
def __init__(self):
self.longitude = -70.747686 # in degrees
self.latitude = -30.244789 # in degrees
# Setting these the same as LSSTSiteParameters
self.height = 2650.0 # in meters
self.temperature = 11.5 # in centigrade
self.pressure = 750.0 # in millibars
self.humidity = 0.4 # scale 0-1
self.lapse_rate = 0.0065 # in Kelvin per meter
[docs]
class Site:
"""
This class will store site information for use in Catalog objects.
Defaults values are LSST site values taken from the Observatory
System Specification document
https://docushare.lsstcorp.org/docushare/dsweb/ImageStoreViewer/LSE-30
on 4 January 2016
Parameters
----------
name : `str`, opt
The name of the observatory. Set to 'LSST' for other parameters
to default to LSST values. Set to 'AuxTel' for
auxtel values.
longitude : `float`, opt
Longitude of the site in degrees.
latitude : `float`, opt
Latitude of the site in degrees.
height : `float`, opt
Height of the site in meters.
temperature : `float`, opt
Mean temperature in Centigrade
pressure : `float`, opt
Pressure for the site in millibars.
humidity : `float`, opt
Relative humidity (range 0-1).
lapse_rate : `float`, opt
Change in temperature in Kelvins per meter
"""
def __init__(
self,
name=None,
longitude=None,
latitude=None,
height=None,
temperature=None,
pressure=None,
humidity=None,
lapse_rate=None,
):
default_params = None
self._name = name
if self._name == "LSST":
default_params = LsstSiteParameters()
if self.name == "AuxTel":
default_params = AuxTelParameters()
if default_params is not None:
if longitude is None:
longitude = default_params.longitude
if latitude is None:
latitude = default_params.latitude
if height is None:
height = default_params.height
if temperature is None:
temperature = default_params.temperature
if pressure is None:
pressure = default_params.pressure
if humidity is None:
humidity = default_params.humidity
if lapse_rate is None:
lapse_rate = default_params.lapse_rate
if longitude is not None:
self._longitude_rad = np.radians(longitude)
else:
self._longitude_rad = None
if latitude is not None:
self._latitude_rad = np.radians(latitude)
else:
self._latitude_rad = None
self._longitude_deg = longitude
self._latitude_deg = latitude
self._height = height
self._pressure = pressure
if temperature is not None:
self._temperature_kelvin = temperature + 273.15 # in Kelvin
else:
self._temperature_kelvin = None
self._temperature_centigrade = temperature
self._humidity = humidity
self._lapse_rate = lapse_rate
# Go through all the attributes of this Site.
# Raise a warning if any are None so that the user
# is not surprised when some use of this Site fails
# because something that should have beena a float
# is NoneType
list_of_nones = []
if self.longitude is None or self.longitude_rad is None:
if self.longitude_rad is not None:
raise RuntimeError("in Site: longitude is None but longitude_rad is not")
if self.longitude is not None:
raise RuntimeError("in Site: longitude_rad is None but longitude is not")
list_of_nones.append("longitude")
if self.latitude is None or self.latitude_rad is None:
if self.latitude_rad is not None:
raise RuntimeError("in Site: latitude is None but latitude_rad is not")
if self.latitude is not None:
raise RuntimeError("in Site: latitude_rad is None but latitude is not")
list_of_nones.append("latitude")
if self.temperature is None or self.temperature_kelvin is None:
if self.temperature is not None:
raise RuntimeError("in Site: temperature_kelvin is None but temperature is not")
if self.temperature_kelvin is not None:
raise RuntimeError("in Site: temperature is None but temperature_kelvin is not")
list_of_nones.append("temperature")
if self.height is None:
list_of_nones.append("height")
if self.pressure is None:
list_of_nones.append("pressure")
if self.humidity is None:
list_of_nones.append("humidity")
if self.lapse_rate is None:
list_of_nones.append("lapse_rate")
if len(list_of_nones) != 0:
msg = "The following attributes of your Site were None:\n"
for name in list_of_nones:
msg += "%s\n" % name
msg += "If you want these to just default to LSST values,\n"
msg += "instantiate your Site with name='LSST'"
warnings.warn(msg)
def to_earth_location(self):
return EarthLocation.from_geodetic(
lon=self.longitude * u.deg, lat=self.latitude * u.deg, height=self.height * u.meter
)
def __eq__(self, other):
for param in self.__dict__:
if param not in other.__dict__:
return False
if self.__dict__[param] != other.__dict__[param]:
return False
for param in other.__dict__:
if param not in self.__dict__:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
@property
def name(self):
"""
observatory name
"""
return self._name
@property
def longitude_rad(self):
"""
observatory longitude in radians
"""
return self._longitude_rad
@property
def longitude(self):
"""
observatory longitude in degrees
"""
return self._longitude_deg
@property
def latitude_rad(self):
"""
observatory latitude in radians
"""
return self._latitude_rad
@property
def latitude(self):
"""
observatory latitude in degrees
"""
return self._latitude_deg
@property
def temperature(self):
"""
mean temperature in centigrade
"""
return self._temperature_centigrade
@property
def temperature_kelvin(self):
"""
mean temperature in Kelvin
"""
return self._temperature_kelvin
@property
def height(self):
"""
height in meters
"""
return self._height
@property
def pressure(self):
"""
mean pressure in millibars
"""
return self._pressure
@property
def humidity(self):
"""
mean humidity in the range 0-1
"""
return self._humidity
@property
def lapse_rate(self):
"""
temperature lapse rate (in Kelvin per meter)
"""
return self._lapse_rate
[docs]
def declination_dependent_fwhm(
nside: int = DEFAULT_NSIDE, zenith_fwhm_limit: float = 1.0
) -> npt.NDArray[np.float64]:
"""
Convert a zenith FWHM value into a declination-dependent healpix map.
Parameters
----------
nside
The nside for the returned healpix map of declination-dependent
fwhm values.
zenith_fwhm_limit
The zenith FWHM value to convert.
Returns
-------
min_fwhm : `np.ndarray`
The declination-dependent scaled FWHM values.
Notes
-----
For every point in the healpix map, the declination is converted to
the minimum airmass possible (e.g. the airmass when at the meridian).
The FWHM is then scaled by this minimum airmass according to X^0.6.
"""
site = Site("LSST")
hpid = np.arange(hp.nside2npix(nside))
ra, dec = hpid2_ra_dec(nside, hpid)
min_z = np.radians(np.abs(dec - site.latitude))
min_airmass = 1 / np.cos(min_z)
min_airmass = np.where(min_airmass < 1, np.nan, min_airmass)
min_fwhm = zenith_fwhm_limit * (np.power(min_airmass, 0.6))
# Get rid of NaNs or Infs that might be there
min_fwhm[~np.isfinite(min_fwhm)] = np.nanmax(min_fwhm)
return min_fwhm