Exemplo n.º 1
0
def test_many_identifiers(identifier):
    """Common-case lookups of many SSO ids, passed as list, array, Series"""
    if isinstance(identifier, tuple):
        with pytest.raises(ValueError):
            rocks.identify(identifier)
    else:
        rocks.identify(identifier)
Exemplo n.º 2
0
def rocks_(identifier, datacloud=[], progress=False):
    """Create multiple Rock instances.

    Parameters
    ==========
    identifier : list of str, list of int, list of float, np.array, pd.Series
        An iterable containing minor body identifiers.
    datacloud : list of str
        List of additional catalogues to retrieve from datacloud.
        Default is no additional catalogues.
    progress : bool
        Show progress of instantiation. Default is False.

    Returns
    =======
    list of rocks.core.Rock
        A list of Rock instances
    """
    if isinstance(identifier, pd.Series):
        identifier = identifier.values

    ids = [
        id_ for _, _, id_ in rocks.identify(
            identifier, return_id=True, progress=progress)
    ]  # type: ignore

    rocks_ = [
        Rock(id_, skip_id_check=True, datacloud=datacloud) for id_ in tqdm(
            ids, desc="Building rocks : ", total=len(ids), disable=~progress)
    ]

    return rocks_
Exemplo n.º 3
0
def id(id_):
    """Resolve the asteroid name and number from string input."""
    name, number = rocks.identify(id_)  # type: ignore

    if isinstance(name, (str)):
        click.echo(f"({number}) {name}")
    else:
        rocks.utils.list_candidate_ssos(id_)
Exemplo n.º 4
0
def aliases(id_):
    """Echo the aliases of an asteroid."""

    name, number, ssodnetid = rocks.identify(id_,
                                             return_id=True)  # type: ignore

    aliases = rocks.index.get_aliases(ssodnetid)

    rich.print(f"({number}) {name}, aka \n {aliases}")
Exemplo n.º 5
0
def info(id_):
    """Print the ssoCard of an asteroid."""
    _, _, id_ = rocks.identify(id_, return_id=True)  # type: ignore

    if not isinstance(id_, str):
        sys.exit()

    ssoCard = rocks.ssodnet.get_ssocard(id_)
    rich.print(ssoCard)
Exemplo n.º 6
0
def confirm_identity(ids):
    """Confirm the SsODNet ID of the passed identifier. Retrieve the current
    ssoCard and remove the former one if the ID has changed.

    Parameters
    ----------
    ids : list
        The list of SsODNet IDs to confirm.
    """

    # Drop the named asteroids - their identity won't change
    ids = set(id_ for id_ in ids if not re.match(r"^[A-Za-z \(\)\_]*$", id_))

    if not ids:
        return  # nothing to do here
    elif len(ids) == 1:
        _, _, current_ids = rocks.identify(ids, return_id=True, local=False)
        current_ids = [current_ids]
    else:
        _, _, current_ids = zip(
            *rocks.identify(ids, return_id=True, local=False, progress=True))

    # Swap the renamed ones
    updated = []

    for old_id, current_id in zip(ids, current_ids):

        if old_id == current_id:
            continue

        rich.print(
            f"{old_id} has been renamed to {current_id}. Swapping the ssoCards."
        )

        # Get new card and remove the old one
        rocks.ssodnet.get_ssocard(current_id, local=False)
        (rocks.PATH_CACHE / f"{old_id}.json").unlink()

        # This is now up-to-date
        updated.append(old_id)

    for id_ in updated:
        ids.remove(id_)
Exemplo n.º 7
0
def identify(this):
    """Get asteroid name and number from string input.

    Parameters
    ==========
    this : str
        String to identify asteroid.
    """
    name, number = rocks.identify(this)  # type: ignore

    if isinstance(name, (str)):
        click.echo(f"({number}) {name}")
Exemplo n.º 8
0
def info(this, minimal):
    """Print ssoCard of minor body.

    Parameters
    ==========
    this : str, optional
        Minor body name, designation, or number.
        If empty, a selection is prompted.
    """
    if not this:
        _, _, this = rocks.utils.select_sso_from_index()
    else:  # passed identified string, ensure that we know it
        _, _, this = rocks.identify(this, return_id=True)  # type: ignore

    if not isinstance(this, str):
        sys.exit()

    ssoCard = rocks.ssodnet.get_ssocard(this)

    if ssoCard is not None:
        rocks.utils.pretty_print_card(ssoCard, minimal)
Exemplo n.º 9
0
def create_index():
    """Update index of numbered SSOs."""

    # Get currently indexed objects
    index = read_index()

    # Get list of numbered asteroids from MPC
    url = "https://www.minorplanetcenter.net/iau/lists/NumberedMPs.txt"
    numbered = pd.read_fwf(url, colspecs=[(0, 7)], names=["number"])
    numbered = set(int(n.strip(" (")) for n in numbered.number)  # type: ignore

    # Compare list to index
    missing = set(index.number) ^ set(numbered)

    if not missing:
        return

    # Get ids of missing entries, append to index
    names, numbers, ids = zip(*rocks.identify(missing, return_id=True))

    index = index.append(
        pd.DataFrame({
            "name": names,
            "number": numbers,
            "id_": ids,
        }))

    # Save index to file
    index = (index.dropna(how="any").drop_duplicates("number").sort_values(
        "number", ascending=True).astype({
            "number": np.int64,
            "name": str,
            "id_": str
        }))

    index = index.reset_index()

    with open(rocks.PATH_INDEX, "wb") as ind:
        pickle.dump(index, ind, protocol=4)
Exemplo n.º 10
0
def test_query_and_resolve(id_, expected):
    """Test response of quaero queries. These can be local or remote queries."""

    name, number = rocks.identify(id_)
    np.testing.assert_equal((name, number), expected)
Exemplo n.º 11
0
# Download SDSS MOC1 (6.2MB)
data = pd.read_fwf(
    "https://faculty.washington.edu/ivezic/sdssmoc/ADR1.dat.gz",
    colspecs=[(244, 250), (250, 270)],
    names=["numeration", "designation"],
)

print(f"Number of observations in SDSS MOC1: {len(data)}")

# Remove the unknown objects
data = data[data.designation.str.strip(" ") != "-"]
print(f"Observations of known objects: {len(set(data.designation))}")

# ------
# Get current designations and numbers for objects

# Unnumbered objects should be NaN
data.loc[data.numeration == 0, "numeration"] = np.nan

# Create list of identifiers by merging 'numeration' and 'designation' columns
ids = data.numeration.fillna(data.designation)
print("Identifying known objects in catalogue..")
names_numbers = rocks.identify(ids)

# Add numbers and names to data
data["name"] = [name_number[0] for name_number in names_numbers]
data["number"] = [name_number[1] for name_number in names_numbers]

data.number = data.number.astype("Int64")  # Int64 supports integers and NaN
print(data.head())
Exemplo n.º 12
0
def rocks_(ids, datacloud=None, progress=False, suppress_errors=False):
    """Create multiple Rock instances.

    Parameters
    ----------
    ids : list of str, list of int, list of float, np.array, pd.Series
        An iterable containing minor body identifiers.
    datacloud : list of str
        List of additional catalogues to retrieve from datacloud.
        Default is no additional catalogues.
    progress : bool
        Show progress of instantiation. Default is False.
    suppress_errors: bool
        Do not print errors in the ssoCard. Default is False.

    Returns
    -------
    list of rocks.core.Rock
        A list of Rock instances
    """

    # Get IDs
    if len(ids) == 1 or isinstance(ids, str):
        ids = [rocks.identify(ids, return_id=True, progress=progress)[-1]]

    else:
        _, _, ids = zip(*rocks.identify(ids, return_id=True, progress=progress))

    # Load ssoCards asynchronously
    rocks.ssodnet.get_ssocard(
        [id_ for id_ in ids if not id_ is None], progress=progress
    )

    if datacloud is not None:

        if isinstance(datacloud, str):
            datacloud = [datacloud]

        # Load datacloud catalogues asynchronously
        for cat in datacloud:

            if cat not in rocks.datacloud.CATALOGUES.keys():
                raise ValueError(
                    f"Unknown datacloud catalogue name: '{cat}'"
                    f"\nChoose from {rocks.datacloud.CATALOGUES.keys()}"
                )

            rocks.ssodnet.get_datacloud_catalogue(
                [id_ for id_ in ids if not id_ is None], cat, progress=progress
            )

    rocks_ = [
        Rock(
            id_,
            skip_id_check=True,
            datacloud=datacloud,
            suppress_errors=suppress_errors,
        )
        if not id_ is None
        else None
        for id_ in ids
    ]

    return rocks_
Exemplo n.º 13
0
    def __init__(
        self,
        id_,
        ssocard=None,
        datacloud=None,
        skip_id_check=False,
        suppress_errors=False,
    ):
        """Identify a minor body  and retrieve its properties from SsODNet.

        Parameters
        ----------
        id_ : str, int, float
            Identifying asteroid name, designation, or number
        ssocard : dict
            Optional argument providing a dictionary to use as ssoCard.
            Default is empty dictionary, triggering the query of an ssoCard.
        datacloud : list of str
            Optional list of additional catalogues to retrieve from datacloud.
            Default is no additional catalogues.
        skip_id_check : bool
            Optional argument to prevent resolution of ID before getting ssoCard.
            Default is False.
        suppress_errors: bool
            Do not print errors in the ssoCard. Default is False.

        Returns
        -------
        rocks.core.Rock
            An asteroid class instance, with its properties as attributes.

        Notes
        -----
        If the asteroid could not be identified or the data contains invalid
        types, the number is None and no further attributes but the name are set.

        Example
        -------
        >>> from rocks import Rock
        >>> ceres = Rock('ceres')
        >>> ceres.taxonomy.class_
        'C'
        >>> ceres.taxonomy.shortbib
        'DeMeo+2009'
        >>> ceres.diameter.value
        848.4
        >>> ceres.diameter.unit
        'km'
        """
        if isinstance(datacloud, str):
            datacloud = [datacloud]

        id_provided = id_

        if not skip_id_check:
            _, _, id_ = rocks.identify(id_, return_id=True)  # type: ignore

        # Get ssoCard and datcloud catalogues
        if not pd.isnull(id_):
            if ssocard is None:
                ssocard = rocks.ssodnet.get_ssocard(id_)

            if ssocard is None:
                # Asteroid does not have an ssoCard
                # Instantiate minimal ssoCard for meaningful error output.
                ssocard = {"name": id_provided}

                rich.print(
                    f"Error 404: missing ssoCard for [green]{id_provided}[/green]."
                )
                # This only gets printed once
                warnings.warn(
                    "See https://rocks.readthedocs.io/en/latest/tutorials.html#error-404 for help."
                )

            else:

                # rename 'spins' to 'spin' so it does not shadow the datacloud property
                # TODO this should be done at the parameter-name level
                if "spins" in ssocard["parameters"]["physical"]:
                    ssocard["parameters"]["physical"]["spin"] = ssocard["parameters"][
                        "physical"
                    ]["spins"]

                if datacloud is not None:
                    for catalogue in datacloud:
                        ssocard = self.__add_datacloud_catalogue(
                            id_, catalogue, ssocard
                        )
        else:
            # Something failed. Instantiate minimal ssoCard for meaningful error output.
            ssocard = {"name": id_provided}

        # Deserialize the asteroid data
        try:
            super().__init__(**ssocard)  # type: ignore
        except pydantic.ValidationError as message:

            if not suppress_errors:
                self.__parse_error_message(message, id_, ssocard)

            # Set the offending properties to None to allow for instantiation anyway
            for error in message.errors():

                # Dynamically remove offending parts of the ssoCard
                offending_part = ssocard

                location_list = error["loc"][:-1]

                if any(
                    property_ in location_list for property_ in ["taxonomy", "spin"]
                ):
                    for property_ in ["taxonomy", "spin"]:
                        if property_ in location_list:
                            # these are lists instead of dicts and the indices are flipped
                            # eg taxonomy bibref 0 becomes taxonomy 0 bibref
                            idx = location_list.index(property_)
                            entry, idx_list = location_list[idx + 1 : idx + 3]

                            try:
                                del ssocard["parameters"]["physical"][property_]
                            except KeyError:
                                pass
                else:

                    for location in error["loc"][:-1]:
                        offending_part = offending_part[location]

                    del offending_part[error["loc"][-1]]

            super().__init__(**ssocard)  # type: ignore

        # Convert the retrieve datacloud catalogues into DataCloudDataFrame objects
        if datacloud is not None:
            for catalogue in datacloud:

                if catalogue in ["diameters", "albedos"]:
                    catalogue = "diamalbedo"

                try:
                    catalogue_instance = rocks.datacloud.DataCloudDataFrame(
                        data=getattr(self, catalogue).dict()
                    )

                # Common occurence of
                # ValueError: All arrays must be of the same length
                # due to malformed datacloud catalogue
                except ValueError:
                    # Drop catalogue attributes with a single entry
                    to_drop = []

                    for attribute, entries in getattr(self, catalogue).dict().items():
                        if len(entries) == 1:
                            to_drop.append(attribute)

                    for attribute in to_drop:
                        delattr(getattr(self, catalogue), attribute)

                    # Let's try this again
                    catalogue_instance = rocks.datacloud.DataCloudDataFrame(
                        data=getattr(self, catalogue).dict()
                    )

                    # warnings.warn(
                    #     f"Removed malformed attributes {to_drop} from datacloud catalogue {catalogue}"
                    # )

                setattr(self, catalogue, catalogue_instance)
Exemplo n.º 14
0
    def __init__(self, id_, ssocard={}, datacloud=[], skip_id_check=False):
        """Identify a minor body  and retrieve its properties from SsODNet.

        Parameters
        ==========
        id_ : str, int, float
            Identifying asteroid name, designation, or number
        ssocard : dict
            Optional argument providing a dictionary to use as ssoCard.
            Default is empty dictionary, triggering the query of an ssoCard.
        datacloud : list of str
            Optional list of additional catalogues to retrieve from datacloud.
            Default is no additional catalogues.
        skip_id_check : bool
            Optional argument to prevent resolution of ID before getting ssoCard.
            Default is False.

        Returns
        =======
        rocks.core.Rock
            An asteroid class instance, with its properties as attributes.

        Notes
        =====
        If the asteroid could not be identified or the data contains invalid
        types, the number is None and no further attributes but the name are set.

        Example
        =======
        >>> from rocks import Rock
        >>> ceres = Rock('ceres')
        >>> ceres.taxonomy.class_
        'C'
        >>> ceres.taxonomy.shortbib
        'DeMeo+2009'
        >>> ceres.diameter
        848.4
        >>> ceres.diameter.unit
        'km'
        """
        if isinstance(datacloud, str):
            datacloud = [datacloud]

        id_provided = id_

        if not skip_id_check:
            _, _, id_ = rocks.identify(id_, return_id=True)  # type: ignore

        # Get ssoCard and datcloud catalogues
        if not pd.isnull(id_):
            if not ssocard:
                ssocard = rocks.ssodnet.get_ssocard(id_)

            for catalogue in datacloud:
                ssocard = self.__add_datacloud_catalogue(
                    id_, catalogue, ssocard)
        else:
            # Something failed. Instantiate minimal ssoCard for meaningful error output.
            ssocard = {"name": id_provided}

        # Deserialize the asteroid data
        try:
            super().__init__(**ssocard)  # type: ignore
        except pydantic.ValidationError as message:
            self.__parse_error_message(message, id_, ssocard)
            return super().__init__(**{"name": id_provided})