Source code for azely.azel

"""Azely's azel module (high-level API).

This module mainly provides ``compute`` function as a high-level API for users
which computes azimuth/elevation of an astronomical object under given conditions.

The ``compute`` function (1) gets object, location, and time information, (2) computes
az/el and LST (local sidereal time), and (3) returns them as a pandas DataFrame.

Object information can be obtained either online (CDS) or offline (an user-defined
TOML file) by query (e.g., ``'NGC1068'`` or ``'Sun'``). Location information can be
obtained either online (IP address or OpenStreetMap) or offline (an user-defined
TOML file) by query (e.g., ``'Tokyo'`` or ``'ALMA AOS'``). Time information can be
computed from either formatted (e.g., ``'2020-01-01'``) and natural language-like
query (e.g., ``'Jan 1st 2020'``). See docstrings of ``get_[object|location|time]``
functions for more detailed query options.

There are two different locations to be used for a computation:
(1) ``site``: location where az/el of an object is computed.
(2) ``view``: location where time information (timezone) is considered.

Examples:
    To compute daily az/el of NGC1068 at ALMA AOS::

        >>> df = azely.compute('NGC1068', 'ALMA AOS', '2020-02-01')

    To compute the same object and location but view from Japan::

        >>> df = azely.compute('NGC1068', 'ALMA AOS', '2020-02-01', view='Tokyo')

    To compute az/el of Sun at noon during an year at Tokyo::

        >>> df = azely.compute('Sun', 'Tokyo', '1/1 12:00 to 12/31 12:00', freq='1D')

As DataFrame has ``plot`` method for matplotlib, plotting the result is so easy::

    >>> df.el.plot() # plot elevation

If users want to use LST instead of time information, use ``in_lst`` property::

    >>> df.in_lst.el.plot()

In order to use LST values as an index of DataFrame, LST has pseudo dates which
start from ``1970-01-01``. Please ignore them or hide them by using matplotlib's
DateFormatter when you plot the result.

"""
__all__ = ["AzEl", "compute"]


# dependent packages
from pandas import DataFrame, DatetimeIndex, Timestamp, to_timedelta
from .utils import set_defaults
from .location import Location, get_location
from .object import Object, get_object
from .time import Time, get_time


# constants
from .consts import (
    AZELY_CONFIG,
    DAYFIRST,
    HERE,
    FRAME,
    FREQ,
    TIMEOUT,
    TODAY,
    YEARFIRST,
)

SOLAR_TO_SIDEREAL = 1.002_737_909


# data class
[docs]class AzEl(DataFrame): """Subclass of pandas DataFrame with special properties for Azely.""" #: allowed custom attributes _metadata = ["object", "site"] @property def alt(self): """Alias of ``dataframe.el``.""" return self.el @property def in_lst(self): """Convert time index to LST.""" td = self.index - self.index[0] td_lst = td * SOLAR_TO_SIDEREAL + self.lst[0] td_lst = td_lst.floor("1D") + self.lst lst = Timestamp(0) + td_lst return self.set_index(DatetimeIndex(lst, name="LST")) @property def in_utc(self): """Convert time index to UTC.""" utc = self.index.tz_convert("UTC") return self.set_index(DatetimeIndex(utc, name="UTC")) @property def _constructor(self): """Constructor of class.""" return AzEl
# main functions
[docs]@set_defaults(AZELY_CONFIG, "compute") def compute( object: str, site: str = HERE, time: str = TODAY, view: str = "", frame: str = FRAME, freq: str = FREQ, dayfirst: bool = DAYFIRST, yearfirst: bool = YEARFIRST, timeout: int = TIMEOUT, ) -> AzEl: """Compute az/el and local sidereal time (LST) of an astronomical object. The ``compute`` function (1) gets object, location, and time information, (2) computes az/el and LST (local sidereal time), and (3) returns them as a pandas DataFrame. Object information can be obtained either online (CDS) or offline (an user-defined TOML file) by query (e.g., ``'NGC1068'`` or ``'Sun'``). Location information can be obtained either online (IP address or OpenStreetMap) or offline (an user-defined TOML file) by query (e.g., ``'Tokyo'`` or ``'ALMA AOS'``). Time information can be computed from either formatted (e.g., ``'2020-01-01'``) and natural language-like query (e.g., ``'Jan 1st 2020'``). See docstrings of ``get_[object|location|time]`` functions for more detailed query options. There are two different locations to be used for a computation: (1) ``site``: location where az/el of an object is computed. (2) ``view``: location where time information (timezone) is considered. Args: object: Query string for object information (e.g., ``'Sun'`` or ``'NGC1068'``). Specify ``'user:NGC1068'`` if users want to get information from ``user.toml``. site: Query string for location information at a site (e.g., ``'Tokyo'``). Specify ``'user:Tokyo'`` if users want to get information from ``user.toml``. time: Query string for time information at a view (e.g., ``'2020-01-01'``). view: Query string for timezone information at the view. (e.g., ``'Asia/Tokyo'``, ``'UTC'``, or ``Tokyo``). By default (``''``), timezone at the site is used. frame: (object option) Name of equatorial coordinates used in astropy's SkyCoord. freq: (time option) Frequency of time samples as the same format of pandas offset aliases (e.g., ``'1D'`` -> 1 day, ``'3H'`` -> 3 hours, ``'10T'`` -> 10 minutes). dayfirst: (time option) Whether to interpret the first value in an ambiguous 3-integer date (e.g., ``'01-02-03'``) as the day. If True, for example, ``'01-02-03'`` is treated as Feb. 1st 2003. yearfirst: (time option) Whether to interpret the first value in an ambiguous 3-integer date (e.g., ``'01-02-03'``) as the year. If True, for example, ``'01-02-03'`` is treated as Feb. 3rd 2001. If ``dayfirst`` is also ``True``, then it will be Mar. 2nd 2001. timeout: (common option) Query timeout expressed in units of seconds. Returns: Computed DataFrame of object's az/el and LST at given site and view. Raises: AzelyError: Raised if one of mid-level APIs fails to get any information. Examples: To compute daily az/el of NGC1068 at ALMA AOS:: >>> df = azely.compute('NGC1068', 'ALMA AOS', '2020-02-01') To compute the same object and location but view from Japan:: >>> df = azely.compute('NGC1068', 'ALMA AOS', '2020-02-01', view='Tokyo') To compute az/el of Sun at noon during an year at Tokyo:: >>> df = azely.compute('Sun', 'Tokyo', '1/1 12:00 to 12/31 12:00', freq='1D') """ # noqa: E501 object_ = get_object(object, frame, timeout) site_ = get_location(site, timeout) time_ = get_time(time, view or site, freq, dayfirst, yearfirst, timeout) return _compute(object_, site_, time_)
# helper functions def _compute(object: Object, site: Location, time: Time) -> AzEl: """Compute az/el and local sidereal time (LST) of an astronomical object. Similar to ``compute`` function, but this function receives instances of ``Object``, ``Location``, and ``Time`` classes as arguments. Args: object: Object information. site: Site location information. time: Time information. Returns: Computed DataFrame of object's az/el and LST at given site and view. Raises: AzelyError: Raised if one of mid-level APIs fails to get any information. """ obstime = time.to_obstime(site.to_earthloc()) skycoord = object.to_skycoord(obstime) az = skycoord.altaz.az el = skycoord.altaz.alt lst = to_timedelta(obstime.sidereal_time("mean").value, unit="hr") azel = AzEl(dict(az=az, el=el, lst=lst), index=time.to_index()) azel.object = object azel.site = site return azel