Source code for alerce.crossmatch

import requests

from astropy.table import Table, Column

from .utils import Client
from .exceptions import handle_error


class AlerceXmatch(Client):
    CATALOG_TRANSLATE = {
        "2MASS": "TMASS",
        "2MASSxsc": "TMASSxsc",
        "GAIA/DR1": "GAIADR1",
        "GAIA/DR2": "GAIADR2",
        "SDSS/DR10": "SDSSDR10",
    }

    def __init__(self, **kwargs):
        self.session = requests.Session()
        default_config = {
            "CATSHTM_API_URL": "https://catshtm.alerce.online",
            "CATSHTM_ROUTES": {
                "conesearch": "/conesearch",
                "conesearch_all": "/conesearch_all",
                "crossmatch": "/crossmatch",
                "crossmatch_all": "/crossmatch_all",
            },
        }
        default_config.update(kwargs)
        super().__init__(**default_config)

    def _request_catshtm(self, method, url, params=None, result_format="json"):
        result_format = self._validate_format(result_format)
        resp = self.session.request(method, url, params=params)
        if resp.status_code >= 400:
            handle_error(resp)

        return resp.json()

    def _format_all(self, catalog_list, result_format="json"):
        votables = {}
        for idx, r in enumerate(catalog_list):
            key = list(r.keys())[0]
            if r[key] == {}:
                continue
            t = Table()
            for field in r[key].keys():
                data = (
                    r[key][field]["values"]
                    if "values" in r[key][field]
                    else [r[key][field]["value"]]
                )
                t.add_column(Column(data, name=field))
                t[field].unit = (
                    r[key][field]["units"]
                    if "units" in r[key][field]
                    else r[key][field]["unit"]
                )
            t["cat_name"] = Column(["catsHTM_%s" % key], name="cat_name")
            if result_format == "pandas":
                t = t.to_pandas()
                if len(t) == 1:
                    t = t.iloc[0]
                    t.name = "catsHTM_%s" % key
            votables[key] = t
        return votables

    def _format_one(self, catalog, result_format="json"):
        catalog_name = list(catalog.keys())[0]
        catalog_data = catalog[catalog_name]
        t = Table()
        for field in catalog_data.keys():
            data = (
                catalog_data[field]["values"]
                if "values" in catalog_data[field]
                else [catalog_data[field]["value"]]
            )
            t.add_column(Column(data, name=field))
            t[field].unit = (
                catalog_data[field]["units"]
                if "units" in catalog_data[field]
                else catalog_data[field]["unit"]
            )
        t["cat_name"] = Column(["catsHTM_%s" % catalog_name], name="cat_name")
        if result_format == "pandas":
            t = t.to_pandas()
            if len(t) == 1:
                t = t.iloc[0]
                t.name = "catsHTM_%s" % catalog_name
        return t

    def catshtm_catalog_translator(self, catalog):
        return self.CATALOG_TRANSLATE.get(catalog, catalog)

    def catshtm_conesearch(self, ra, dec, radius, catalog_name="all", format="pandas"):
        """
        catsHTM conesearch given an object and catalog_name.

        Parameters
        ----------
        ra : :py:class:`float`
            Right ascension in Degrees.
        def : :py:class:`float`
            Declination in Degrees.
        catalog_name : :py:class:`str`
            catsHTM Catalog name, `"all"` can be used to query all available catalogs. List of available catalogs can be found in `here <https://alerceapi.readthedocs.io/en/latest/catshtm.html#id1>`_.
        radius : :py:class:`float`
            Conesearch radius in arcsec.
        format : :py:class:`str`
            Output format [votable|pandas]

        Returns
        -------
        :py:class:`dict`
            Dictionary with the following structure:
            {
                <catalog_name>: :class:`astropy.table.Table` or :class:`pandas.DataFrame` or :py:class:`dict`
            }
        """
        params = {
            "catalog": "%s" % self.catshtm_catalog_translator(catalog_name),
            "ra": ra,
            "dec": dec,
            "radius": "%f" % radius,
        }
        if params["catalog"] == "all":
            q = self._request_catshtm(
                "GET",
                url=self.config["CATSHTM_API_URL"]
                + self.config["CATSHTM_ROUTES"]["conesearch_all"],
                result_format=format,
                params=params,
            )
            q = self._format_all(q["catalogs"], result_format=format)
        else:
            q = self._request_catshtm(
                "GET",
                url=self.config["CATSHTM_API_URL"]
                + self.config["CATSHTM_ROUTES"]["conesearch"],
                result_format=format,
                params=params,
            )
            if isinstance(q, dict) and len(q) == 0:
                return None
            q = self._format_one(q, result_format=format)
        return q

    def catshtm_crossmatch(self, ra, dec, radius, catalog_name="all", format="pandas"):
        """
        catsHTM crossmatch given an object and catalog_name.

        Parameters
        ----------
        ra : :py:class:`float`
            Right ascension in Degrees.
        def: :py:class:`float`
            Declination in Degrees.
        catalog_name : :py:class:`str`
            catsHTM Catalog name, `"all"` can be used to query all available catalogs. List of available catalogs can be found in `here <https://alerceapi.readthedocs.io/en/latest/catshtm.html#id1>`_.
        radius : :py:class:`float`
            Crossmatch radius in arcsec. (Default 100 arcsec)
        format : :py:class:`str`
            Output format [votable|pandas]

        Returns
        -------
        :py:class:`dict`
            Dictionary with the following structure:
            {
                <catalog_name>: :class:`astropy.table.Table` or :class:`pandas.Series`
            }
        """
        params = {
            "catalog": "%s" % self.catshtm_catalog_translator(catalog_name),
            "ra": "%f" % ra,
            "dec": "%f" % dec,
            "radius": "%f" % radius,
        }
        if catalog_name == "all":
            q = self._request_catshtm(
                "GET",
                url=self.config["CATSHTM_API_URL"]
                + self.config["CATSHTM_ROUTES"]["crossmatch_all"],
                result_format=format,
                params=params,
            )
            q = self._format_all(q, result_format=format)
        else:
            q = self._request_catshtm(
                "GET",
                url=self.config["CATSHTM_API_URL"]
                + self.config["CATSHTM_ROUTES"]["crossmatch"],
                result_format=format,
                params=params,
            )
            q = self._format_one({catalog_name: q}, result_format=format)

        return q

    def catshtm_redshift(self, ra, dec, radius, format="votable", verbose=False):
        """
        Get redshift given an object.

        Parameters
        ----------
        oid : :py:class:`str`
            object ID in ALeRCE DBs.
        radius : :py:class:`float`
            catsHTM conesearch radius in arcsec.
        format : :py:class:`str`
            Output format [votable|pandas]

        Returns
        -------
        :py:class:`float`
            Check if redshift is in a catsHTM xmatch response.
        """
        params = {"ra": "%f" % ra, "dec": "%f" % dec, "radius": "%f" % radius}

        xmatches = self.catshtm_crossmatch(
            ra=ra, dec=dec, radius=radius, catalog_name="all"
        )
        for catname in xmatches:
            for col in xmatches[catname].keys():
                if col == "z":
                    if verbose:
                        print(f"Redshift found: {catname}[{col}]")
                    return float(xmatches[catname][col])

        # check whether photometric redshift exists
        for catname in xmatches:
            for col in xmatches[catname].keys():
                if col in ["zphot", "z_phot"]:
                    return float(xmatches[catname][col])

        if verbose:
            print("No redshift found.")
        return None