From 210c903d676938e3d27bbc250db86201d4ebaf80 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Fri, 30 Aug 2024 11:13:23 +0200 Subject: [PATCH 1/8] maint: remove unnecessary coding comment --- astroquery/mocserver/core.py | 1 - astroquery/mocserver/tests/test_mocserver.py | 1 - astroquery/mocserver/tests/test_mocserver_remote.py | 1 - 3 files changed, 3 deletions(-) diff --git a/astroquery/mocserver/core.py b/astroquery/mocserver/core.py index 806c9290ed..0c64a4e3d3 100644 --- a/astroquery/mocserver/core.py +++ b/astroquery/mocserver/core.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -* # Licensed under a 3-clause BSD style license - see LICENSE.rst diff --git a/astroquery/mocserver/tests/test_mocserver.py b/astroquery/mocserver/tests/test_mocserver.py index 272026f44f..d724bd563f 100644 --- a/astroquery/mocserver/tests/test_mocserver.py +++ b/astroquery/mocserver/tests/test_mocserver.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -* # Licensed under a 3-clause BSD style license - see LICENSE.rst import pytest diff --git a/astroquery/mocserver/tests/test_mocserver_remote.py b/astroquery/mocserver/tests/test_mocserver_remote.py index 12d6e4e6a9..9fc6901e34 100644 --- a/astroquery/mocserver/tests/test_mocserver_remote.py +++ b/astroquery/mocserver/tests/test_mocserver_remote.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -* # Licensed under a 3-clause BSD style license - see LICENSE.rst import pytest From 298f23c07f58d39109d191ccba4ae61f6c37c334 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Fri, 30 Aug 2024 16:49:59 +0200 Subject: [PATCH 2/8] tests: make tests shorter mostly achieved by looking at a small MOC in test_moc_order_param rather than the whole sky --- .../mocserver/tests/test_mocserver_remote.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/astroquery/mocserver/tests/test_mocserver_remote.py b/astroquery/mocserver/tests/test_mocserver_remote.py index 9fc6901e34..82996a39ce 100644 --- a/astroquery/mocserver/tests/test_mocserver_remote.py +++ b/astroquery/mocserver/tests/test_mocserver_remote.py @@ -31,11 +31,10 @@ class TestMOCServerRemote: # test of MAXREC payload @pytest.mark.skipif(not HAS_REGIONS, reason="regions is required") - @pytest.mark.parametrize("max_rec", [3, 10, 25, 100]) + @pytest.mark.parametrize("max_rec", [3, 10]) def test_max_rec_param(self, max_rec): center = coordinates.SkyCoord(ra=10.8, dec=32.2, unit="deg") radius = coordinates.Angle(1.5, unit="deg") - cone_region = CircleSkyRegion(center, radius) result = MOCServer.query_region( region=cone_region, max_rec=max_rec, get_query_payload=False @@ -49,9 +48,7 @@ def test_max_rec_param(self, max_rec): "field_l", [ ["ID"], - ["ID", "moc_sky_fraction"], ["data_ucd", "vizier_popularity", "ID"], - ["publisher_id", "ID"], ], ) def test_field_l_param(self, field_l): @@ -72,21 +69,15 @@ def test_field_l_param(self, field_l): # test of moc_order payload @pytest.mark.parametrize("moc_order", [5, 10]) - def test_moc_order_param(self, moc_order, tmp_cwd): - # We need a long timeout for this - MOCServer.TIMEOUT = 300 - - moc_region = MOC.from_json({"0": [1]}) - + def test_moc_order_param(self, moc_order): + moc_region = MOC.from_str("10/0-9") result = MOCServer.query_region( region=moc_region, - # return a mocpy obj return_moc=True, max_norder=moc_order, - get_query_payload=False, ) - assert isinstance(result, MOC) + assert result.max_order == moc_order @pytest.mark.parametrize( "meta_data_expr", @@ -96,7 +87,5 @@ def test_find_data_sets(self, meta_data_expr): result = MOCServer.find_datasets( meta_data=meta_data_expr, fields=["ID", "moc_sky_fraction"], - get_query_payload=False, ) - assert isinstance(result, Table) From 1766ca4e26d8e205af0a6dbba087d9267f1f5561 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Fri, 30 Aug 2024 16:53:21 +0200 Subject: [PATCH 3/8] maint: make parse_result simpler for tables, we use the fact that Table accepts dictionnaries. For MOCs, the dictionnary parsing has been improved in MOCpy > 0.12 and does not require to remove empty orders anymore --- astroquery/mocserver/core.py | 124 ++++++++++------------------------- setup.cfg | 2 +- 2 files changed, 35 insertions(+), 91 deletions(-) diff --git a/astroquery/mocserver/core.py b/astroquery/mocserver/core.py index 0c64a4e3d3..8d45a8d4d8 100644 --- a/astroquery/mocserver/core.py +++ b/astroquery/mocserver/core.py @@ -8,10 +8,11 @@ from . import conf import os +from ast import literal_eval +from copy import copy + from astropy import units as u from astropy.table import Table -from astropy.table import MaskedColumn -from copy import copy try: from mocpy import MOC @@ -34,15 +35,15 @@ class MOCServerClass(BaseQuery): Query the `CDS MOCServer `_ The `CDS MOCServer `_ allows the user to retrieve all the data sets (with - their meta-datas) having sources in a specific region. This region can be a `regions.CircleSkyRegion`, a + their meta-data) having sources in a specific region. This region can be a `regions.CircleSkyRegion`, a `regions.PolygonSkyRegion` or a `mocpy.MOC` object. This package implements two methods: * :meth:`~astroquery.mocserver.MOCServerClass.query_region` retrieving data-sets - (their associated MOCs and meta-datas) having sources in a given region. + (their associated MOCs and meta-data) having sources in a given region. * :meth:`~astroquery.mocserver.MOCServerClass.find_datasets` retrieving data-sets - (their associated MOCs and meta-datas) based on the values of their meta-datas. + (their associated MOCs and meta-data) based on the values of their meta-data. """ URL = conf.server @@ -86,18 +87,18 @@ def query_region(self, *, region=None, get_query_payload=False, verbose=False, * max_norder : int, optional Has sense only if ``return_moc`` is set to True. Specifies the maximum precision order of the returned MOC. fields : [str], optional - Has sense only if ``return_moc`` is set to False. Specifies which meta datas to retrieve. The returned + Has sense only if ``return_moc`` is set to False. Specifies which meta data to retrieve. The returned `astropy.table.Table` table will only contain the column names given in ``fields``. Specifying the fields we want to retrieve allows the request to be faster because of the reduced chunk of data moving from the MOCServer to the client. - Some meta-datas as ``obs_collection`` or ``data_ucd`` do not keep a constant type throughout all the + Some meta-data as ``obs_collection`` or ``data_ucd`` do not keep a constant type throughout all the MOCServer's data-sets and this lead to problems because `astropy.table.Table` supposes values in a column to have an unique type. When we encounter this problem for a specific meta-data, we remove its corresponding column from the returned astropy table. meta_data : str, optional - Algebraic expression on meta-datas for filtering the data-sets at the server side. + Algebraic expression on meta-data for filtering the data-sets at the server side. Examples of meta data expressions: * Retrieve all the Hubble surveys: "ID=*HST*" @@ -132,7 +133,7 @@ def find_datasets(self, meta_data, *, get_query_payload=False, verbose=False, ** Parameters ---------- meta_data : str - Algebraic expression on meta-datas for filtering the data-sets at the server side. + Algebraic expression on meta-data for filtering the data-sets at the server side. Examples of meta data expressions: * Retrieve all the Hubble surveys: "ID=*HST*" @@ -142,16 +143,16 @@ def find_datasets(self, meta_data, *, get_query_payload=False, verbose=False, ** More example of expressions can be found following this `link `_ (especially see the urls). fields : [str], optional - Has sense only if ``return_moc`` is set to False. Specifies which meta datas to retrieve. The returned + Has sense only if ``return_moc`` is set to False. Specifies which meta data to retrieve. The returned `astropy.table.Table` table will only contain the column names given in ``fields``. Specifying the fields we want to retrieve allows the request to be faster because of the reduced chunk of data moving from the MOCServer to the client. - Some meta-datas such as ``obs_collection`` or ``data_ucd`` do not keep a constant type throughout all the + Some meta-data such as ``obs_collection`` or ``data_ucd`` do not keep a constant type throughout all the MOCServer's data-sets and this lead to problems because `astropy.table.Table` supposes values in a column to have an unique type. This case is not common: it is mainly linked to a typing error in the text files - describing the meta-datas of the data-sets. When we encounter this for a specific meta-data, we link the + describing the meta-data of the data-sets. When we encounter this for a specific meta-data, we link the generic type ``object`` to the column. Therefore, keep in mind that ``object`` typed columns can contain values of different types (e.g. lists and singletons or string and floats). max_rec : int, optional @@ -329,87 +330,30 @@ def _parse_result(self, response, *, verbose=False): result = response.json() if not self.return_moc: - """ - The user will get `astropy.table.Table` object whose columns refer to the returned data-set meta-datas. - """ - # cast the data-sets meta-datas values to their correct Python type. - typed_result = [] - for d in result: - typed_d = {k: self._cast_to_float(v) for k, v in d.items()} - typed_result.append(typed_d) - - # looping over all the record's keys to find all the existing keys - column_names_l = [] - for d in typed_result: - column_names_l.extend(d.keys()) - - # remove all the doubles - column_names_l = list(set(column_names_l)) - # init a dict mapping all the meta-data's name to an empty list - table_d = {key: [] for key in column_names_l} - type_d = {key: None for key in column_names_l} - - masked_array_d = {key: [] for key in column_names_l} - # fill the dict with the value of each returned data-set one by one. - for d in typed_result: - row_table_d = {key: None for key in column_names_l} - row_table_d.update(d) - - for k, mask_l in masked_array_d.items(): - entry_masked = False if k in d.keys() else True - mask_l.append(entry_masked) - - for k, v in row_table_d.items(): - if v: - type_d[k] = type(v) - - table_d[k].append(v) - - # define all the columns using astropy.table.MaskedColumn objects - columns_l = [] - for k, v in table_d.items(): - try: - if k != '#': - columns_l.append(MaskedColumn(v, name=k, mask=masked_array_d[k], dtype=type_d[k])) - except ValueError: - # some metadata can be of multiple types when looking on all the datasets. - # this can be due to internal typing errors of the metadatas. - columns_l.append(MaskedColumn(v, name=k, mask=masked_array_d[k], dtype=object)) - - # return an `astropy.table.Table` object created from columns_l - return Table(columns_l) - - """ - The user will get `mocpy.MOC` object. - """ - # remove - empty_order_removed_d = {} - for order, ipix_l in result.items(): - if len(ipix_l) > 0: - empty_order_removed_d.update({order: ipix_l}) + # return a table with the meta-data, we cast the string values for convenience + result = [{key: _cast_to_float(value) for key, value in row.items()} for row in result] + return Table(rows=result) # return a `mocpy.MOC` object. See https://github.com/cds-astro/mocpy and the MOCPy's doc - return MOC.from_json(empty_order_removed_d) - - @staticmethod - def _cast_to_float(value): - """ - Cast ``value`` to a float if possible. - - Parameters - ---------- - value : str - string to cast + return MOC.from_json(result) + +def _cast_to_float(value): + """ + Cast ``value`` to a float if possible. - Returns - ------- - value : float or str - A float if it can be casted so otherwise the initial string. - """ - try: - return float(value) - except (ValueError, TypeError): - return value + Parameters + ---------- + value : str + string to cast + Returns + ------- + value : float or str + A float if it can be casted so otherwise the initial string. + """ + try: + return float(value) + except (ValueError, TypeError): + return value MOCServer = MOCServerClass() diff --git a/setup.cfg b/setup.cfg index e84a41b727..ff75cd2f7a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -157,7 +157,7 @@ docs= scipy fsspec[http] all= - mocpy>=0.9 + mocpy>=0.12 astropy-healpix boto3 regions>=0.5 From b780441a13cd1bbcd0ec1e9f20577d0450a70506 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Wed, 27 Nov 2024 15:52:01 +0100 Subject: [PATCH 4/8] refactor: rewrite the mocserver module this is mainly motivated by the new support for time mocs and space-time mocs upstream --- CHANGES.rst | 28 + astroquery/mocserver/__init__.py | 25 +- astroquery/mocserver/core.py | 839 ++++++++++++------ .../mocserver/tests/data/hips_frames.vot | 314 +++++++ .../tests/data/hips_from_saada_alasky.json | 6 - .../mocserver/tests/data/list_fields.json | 1 + .../mocserver/tests/data/properties.json | 9 - astroquery/mocserver/tests/setup_package.py | 8 +- astroquery/mocserver/tests/test_mocserver.py | 345 ++++--- .../mocserver/tests/test_mocserver_remote.py | 90 +- docs/mocserver/MOC_GALEXGR6_AIS_FUV.png | Bin 131787 -> 0 bytes docs/mocserver/mocserver.rst | 713 +++++++-------- 12 files changed, 1560 insertions(+), 818 deletions(-) create mode 100644 astroquery/mocserver/tests/data/hips_frames.vot delete mode 100644 astroquery/mocserver/tests/data/hips_from_saada_alasky.json create mode 100644 astroquery/mocserver/tests/data/list_fields.json delete mode 100644 astroquery/mocserver/tests/data/properties.json delete mode 100644 docs/mocserver/MOC_GALEXGR6_AIS_FUV.png diff --git a/CHANGES.rst b/CHANGES.rst index 0a95d9945c..256a2c78d5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -54,6 +54,34 @@ ipac.irsa - Add more robust handling of errors returned in Most.query_object() responses. [#3140] +mocserver +^^^^^^^^^ + +- Switch to https instead of http for the default url (allows pyodide to use the module) [#3139] + +- Add ``TimeMOC`` and ``STMOC`` as possible entries in ``MOCServer.query_region`` to + allow temporal and space-time searches [#3139] + +- ``return_moc`` now allows to ask for a Time-MOC or a Space-Time MOC rather than only + Space-MOCs [#3139] + +- Fix query by MOC that would write a file ``moc.fits`` where the method was executed + in overwriting mode (potentially deleting data if there was a conflicting file) [#3139] + +- Returned tables now have a default list of fields instead of the > 130 columns returned + previously. The full list of fields can be displayed with the new method + ``MOCServer.list_fields`` [#3139] + +- Add ``casesensitive`` parameter in the queries (previously, this was hardcoded + to ``True``) [#3139] + +- Add ``spacesys`` parameter to the queries to allow to filter on the different bodies + or frames. The list of available space systems can be printed with the new method + ``MOCServer.list_spacesys`` [#3139] + +- Add ``query_hips`` method, which is convenient to filter only Hierarchical progressive + surveys [#3139] + mpc ^^^ diff --git a/astroquery/mocserver/__init__.py b/astroquery/mocserver/__init__.py index 2030fd2821..95efa11a0c 100644 --- a/astroquery/mocserver/__init__.py +++ b/astroquery/mocserver/__init__.py @@ -1,7 +1,6 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -""" -CDS MOCServer Query Tool ------------------------- +"""CDS MOCServer Query Tool. +---------------------------- :Author: Matthieu Baumann (matthieu.baumann@astro.unistra.fr) @@ -13,8 +12,9 @@ Note: If the access to MOCs with the MOCServer tool was helpful for your research, the following acknowledgment would be appreciated:: - This research has made use of the MOCServer, a tool developed at CDS, Strasbourg, France aiming at retrieving - MOCs/meta-data from known data-sets. MOC is an IVOA standard described in the following paper : + This research has made use of the MOCServer, a tool developed at CDS, Strasbourg, + France aiming at retrieving MOCs/meta-data from known data-sets. MOC is an IVOA + standard described in : http://www.ivoa.net/documents/MOC/20140602/REC-MOC-1.0-20140602.pdf """ @@ -24,12 +24,11 @@ class Conf(_config.ConfigNamespace): - """ - Configuration parameters for ``astroquery.template_module``. - """ + """Configuration parameters for ``astroquery.template_module``.""" server = _config.ConfigItem( [ + "https://alasky.unistra.fr/MocServer/query", "http://alasky.unistra.fr/MocServer/query", "http://alaskybis.unistra.fr/MocServer/query", ], @@ -40,6 +39,16 @@ class Conf(_config.ConfigNamespace): 30, "Time limit for connecting to template_module server." ) + default_fields = [ + "ID", + "obs_title", + "obs_description", + "nb_rows", + "obs_regime", + "bib_reference", + "dataproduct_type", + ] + conf = Conf() diff --git a/astroquery/mocserver/core.py b/astroquery/mocserver/core.py index 8d45a8d4d8..ccab2a98ca 100644 --- a/astroquery/mocserver/core.py +++ b/astroquery/mocserver/core.py @@ -1,345 +1,688 @@ - # Licensed under a 3-clause BSD style license - see LICENSE.rst from ..query import BaseQuery from ..utils import commons -from ..utils import async_to_sync from . import conf -import os -from ast import literal_eval from copy import copy +from tempfile import NamedTemporaryFile from astropy import units as u from astropy.table import Table try: - from mocpy import MOC -except ImportError: - print("Could not import mocpy, which is a requirement for the MOC server service." - "Please refer to https://cds-astro.github.io/mocpy/install.html for how to install it.") - -try: - from regions import CircleSkyRegion, PolygonSkyRegion + from mocpy import MOC, TimeMOC, STMOC except ImportError: - print("Could not import astropy-regions, which is a requirement for the CDS service." - "Please refer to http://astropy-regions.readthedocs.io/en/latest/installation.html for how to install it.") + print("'mocpy' is a mandatory dependency for MOCServer. You can install " + "it with 'pip install mocpy'.") -__all__ = ['MOCServerClass', 'MOCServer'] +__all__ = ["MOCServerClass", "MOCServer"] -@async_to_sync class MOCServerClass(BaseQuery): - """ - Query the `CDS MOCServer `_ - - The `CDS MOCServer `_ allows the user to retrieve all the data sets (with - their meta-data) having sources in a specific region. This region can be a `regions.CircleSkyRegion`, a - `regions.PolygonSkyRegion` or a `mocpy.MOC` object. - - This package implements two methods: - - * :meth:`~astroquery.mocserver.MOCServerClass.query_region` retrieving data-sets - (their associated MOCs and meta-data) having sources in a given region. - * :meth:`~astroquery.mocserver.MOCServerClass.find_datasets` retrieving data-sets - (their associated MOCs and meta-data) based on the values of their meta-data. + """Query the `CDS MOCServer `_. + The `CDS MOCServer `_ allows the user + to retrieve all the datasets (with their meta-data) having sources in a specific + space-time region. This region can be a `regions.CircleSkyRegion`, a + `regions.PolygonSkyRegion`, a `mocpy.MOC`, a `mocpy.TimeMOC`, or a `mocpy.STMOC` + object. """ + URL = conf.server TIMEOUT = conf.timeout + DEFAULT_FIELDS = conf.default_fields def __init__(self): super().__init__() - self.path_moc_file = None self.return_moc = False - def query_region(self, *, region=None, get_query_payload=False, verbose=False, **kwargs): - """ - Query the `CDS MOCServer `_ with a region. - - Can be a `regions.CircleSkyRegion`, `regions.PolygonSkyRegion` or `mocpy.MOC` object. Returns the data-sets - having at least one source in the region. + def query_region( + self, region=None, + *, + meta_data=None, + intersect="overlaps", + return_moc=None, + max_norder=None, + fields=None, + max_rec=None, + casesensitive=False, + spacesys=None, + get_query_payload=False, + verbose=False, + cache=True, + ): + """Query the MOC Server. Parameters ---------- - region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion` or `mocpy.MOC` - The region to query the MOCServer with. - Can be one of the following types: - - * ``regions.CircleSkyRegion`` : defines an astropy cone region. - * ``regions.PolygonSkyRegion`` : defines an astropy polygon region. - * ``mocpy.moc.MOC`` : defines a MOC from the MOCPy library. See the `MOCPy's documentation - `__ for how to instantiate a MOC object. - + region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion`, `mocpy.MOC`, + `mocpy.TimeMOC`, or `mocpy.STMOC` + The region to query the MOCServer with. Note that this can also be a + space-time region with the Time-MOCs and Space-Time-MOCs. + Defaults to None, which means that the search will be on the whole sky. + meta_data : str + Algebraic expression to select the datasets. + Examples of expressions can be found `on the mocserver's examples page + `_. + Example: "ID=*HST*" will return datasets with HST in their ID column. The + star means any character. + casesensitive : Bool, optional + Whether the search should take the case into account. By default, False. + fields : [str], optional + Specifies which columns to retrieve. Defaults to a pre-defined subset of + fields. The complete list of fields can be obtained with `list_fields`. + spacesys: str, optional + This is the space system on which the coordinates are expressed. Can take + the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + be printed with `list_spacesys`. Default is None, meaning that the results + will have mixed frames. intersect : str, optional - This parameter can take only three different values: + This parameter can take three different values: + + - ``overlaps`` (default) select datasets overlapping the region + - ``covers`` returned datasets are covering the region. + - ``encloses`` returned datasets are enclosing the region. - - ``overlaps`` (default). Returned data-sets are those overlapping the MOC region. - - ``covers``. Returned data-sets are those covering the MOC region. - - ``encloses``. Returned data-sets are those enclosing the MOC region. max_rec : int, optional Maximum number of data-sets to return. By default, there is no upper limit. return_moc : bool, optional - Specifies if we want a `mocpy.MOC` object in return. This MOC corresponds to the union - of the MOCs of all the matching data-sets. By default it is set to False and - :meth:`~astroquery.mocserver.MOCServerClass.query_region` returns an `astropy.table.Table` object. + Specifies whether the output should be a `mocpy.MOC`. The returned MOC is the + union of the MOCs of all the matching datasets. By default it is False and + this method returns a `astropy.table.Table` object. max_norder : int, optional - Has sense only if ``return_moc`` is set to True. Specifies the maximum precision order of the returned MOC. - fields : [str], optional - Has sense only if ``return_moc`` is set to False. Specifies which meta data to retrieve. The returned - `astropy.table.Table` table will only contain the column names given in ``fields``. - - Specifying the fields we want to retrieve allows the request to be faster because of the reduced chunk of - data moving from the MOCServer to the client. - - Some meta-data as ``obs_collection`` or ``data_ucd`` do not keep a constant type throughout all the - MOCServer's data-sets and this lead to problems because `astropy.table.Table` supposes values in a column - to have an unique type. When we encounter this problem for a specific meta-data, we remove its corresponding - column from the returned astropy table. - meta_data : str, optional - Algebraic expression on meta-data for filtering the data-sets at the server side. - Examples of meta data expressions: - - * Retrieve all the Hubble surveys: "ID=*HST*" - * Provides the records of HiPS distributed simultaneously by saada and alasky http server: - "(hips_service_url*=http://saada*)&&(hips_service_url*=http://alasky.*)" - - More example of expressions can be found following this `link - `_ (especially see the urls). + If ``return_moc`` is set to True, this fixes the maximum precision order + of the returned MOC. For one dimensional MOCs (Space-MOCs and Time-MOCs), + the order is given as an integer. For Space-Time MOCs, the order should be a + string with the space and time orders. + Example: 's3 t45' get_query_payload : bool, optional - If True, returns a dictionary of the query payload instead of the parsed response. + If True, returns a dictionary of the query payload instead of the response. verbose : bool, optional + Whether to show warnings. Defaults to False. + cache: bool, optional + Whether the response should be cached. Returns ------- - response : `astropy.table.Table` or `mocpy.MOC` - By default an astropy table of the data-sets matching the query. If ``return_moc`` is set to True, it gives - a MOC object corresponding to the union of the MOCs from all the retrieved data-sets. + response : `astropy.table.Table`, `mocpy.MOC`, `mocpy.TimeMOC`, or `mocpy.STMOC` + By default, returns a table with the datasets matching the query. + If ``return_moc`` is set to True, it gives a MOC object corresponding to the + union of the MOCs from all the retrieved data-sets. + """ - response = self.query_region_async(get_query_payload=get_query_payload, region=region, **kwargs) + response = self.query_async( + meta_data=meta_data, + region=region, + intersect=intersect, + return_moc=return_moc, + max_norder=max_norder, + fields=fields, + max_rec=max_rec, + casesensitive=casesensitive, + spacesys=spacesys, + get_query_payload=get_query_payload, + cache=cache, + ) if get_query_payload: return response + return _parse_result(response, verbose=verbose, return_moc=return_moc) + + def query_hips( + self, + *, + meta_data=None, + region=None, + intersect="overlaps", + return_moc=None, + max_norder=None, + fields=None, + max_rec=None, + casesensitive=False, + spacesys=None, + get_query_payload=False, + verbose=False, + cache=True, + ): + """Query the MOCServer for HiPS surveys. + + Parameters + ---------- + meta_data : str + Algebraic expression to select the datasets. + Examples of expressions can be found `on the mocserver's examples page + `_. + Example: "ID=*HST*" will return datasets with HST in their ID column. The + star means any character. + casesensitive : Bool, optional + Wheter the search should take the case into account. By default, False. + fields : [str], optional + Specifies which columns to retrieve. Defaults to a pre-defined subset of + fields. The complete list of fields can be obtained with `list_fields`. + spacesys: str, optional + This is the space system on which the coordinates are expressed. Can take + the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + be printed with `list_spacesys`. Default is None, meaning that the results + will have mixed frames. + region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion`, `mocpy.MOC`, + `mocpy.TimeMOC`, or `mocpy.STMOC` + The region to query the MOCServer with. Note that this can also be a + space-time region with the Time-MOCs and Space-Time-MOCs. + intersect : str, optional + This parameter can take three different values: + + - ``overlaps`` (default) select datasets overlapping the region + - ``covers`` returned datasets are covering the region. + - ``encloses`` returned datasets are enclosing the region. - result = self._parse_result(response, verbose=verbose) + max_rec : int, optional + Maximum number of data-sets to return. By default, there is no upper limit. + return_moc : bool, optional + Specifies whether the output should be a `mocpy.MOC`. The returned MOC is the + union of the MOCs of all the matching datasets. By default it is False and + this method returns a `astropy.table.Table` object. + max_norder : int, optional + If ``return_moc`` is set to True, this fixes the maximum precision order + of the returned MOC. For one dimensional MOCs (Space-MOCs and Time-MOCs), + the order is given as an integer. For Space-Time MOCs, the order should be a + string with the space and time orders. + Example: 's3 t45' + get_query_payload : bool, optional + If True, returns a dictionary of the query payload instead of the response. + verbose : bool, optional + Whether to show warnings. Defaults to False. + cache: bool, optional + Whether the response should be cached. - return result + Returns + ------- + response : `astropy.table.Table`, `mocpy.MOC`, `mocpy.TimeMOC`, or `mocpy.STMOC` + By default, returns a table with the datasets matching the query. + If ``return_moc`` is set to True, it gives a MOC object corresponding to the + union of the MOCs from all the retrieved data-sets. - def find_datasets(self, meta_data, *, get_query_payload=False, verbose=False, **kwargs): """ - Query the `CDS MOCServer `_ to retrieve the data-sets based on their - meta data values. This method does not need any region argument but it requires an expression on the meta datas. + if meta_data: + meta_data = f"({meta_data})&&hips_frame=*" + else: + meta_data = "hips_frame=*" + return self.query_region( + meta_data=meta_data, + region=region, + intersect=intersect, + return_moc=return_moc, + max_norder=max_norder, + fields=fields, + max_rec=max_rec, + casesensitive=casesensitive, + spacesys=spacesys, + get_query_payload=get_query_payload, + verbose=verbose, + cache=cache, + ) + + def find_datasets( + self, meta_data, + *, + region=None, + intersect="overlaps", + return_moc=None, + max_norder=None, + fields=None, + max_rec=None, + casesensitive=False, + spacesys=None, + get_query_payload=False, + verbose=False, + cache=True, + ): + """Query the MOC Server. Parameters ---------- meta_data : str - Algebraic expression on meta-data for filtering the data-sets at the server side. - Examples of meta data expressions: + Algebraic expression to select the datasets. + Examples of expressions can be found `on the mocserver's examples page + `_. + Example: "ID=*HST*" will return datasets with HST in their ID column. The + star means any character. + casesensitive : Bool, optional + Whether the search should take the case into account. By default, False. + fields : [str], optional + Specifies which columns to retrieve. Defaults to a pre-defined subset of + fields. The complete list of fields can be obtained with `list_fields`. + spacesys: str, optional + This is the space system on which the coordinates are expressed. Can take + the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + be printed with `list_spacesys`. Default is None, meaning that the results + will have mixed frames. + region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion`, `mocpy.MOC`, + `mocpy.TimeMOC`, or `mocpy.STMOC` + The region to query the MOCServer with. Note that this can also be a + space-time region with the Time-MOCs and Space-Time-MOCs. + intersect : str, optional + This parameter can take three different values: - * Retrieve all the Hubble surveys: "ID=*HST*" - * Provides the records of HiPS distributed simultaneously by saada and alasky http server: - "(hips_service_url*=http://saada*)&&(hips_service_url*=http://alasky.*)" + - ``overlaps`` (default) select datasets overlapping the region + - ``covers`` returned datasets are covering the region. + - ``encloses`` returned datasets are enclosing the region. - More example of expressions can be found following this `link - `_ (especially see the urls). - fields : [str], optional - Has sense only if ``return_moc`` is set to False. Specifies which meta data to retrieve. The returned - `astropy.table.Table` table will only contain the column names given in ``fields``. - - Specifying the fields we want to retrieve allows the request to be faster because of the reduced chunk of - data moving from the MOCServer to the client. - - Some meta-data such as ``obs_collection`` or ``data_ucd`` do not keep a constant type throughout all the - MOCServer's data-sets and this lead to problems because `astropy.table.Table` supposes values in a column - to have an unique type. This case is not common: it is mainly linked to a typing error in the text files - describing the meta-data of the data-sets. When we encounter this for a specific meta-data, we link the - generic type ``object`` to the column. Therefore, keep in mind that ``object`` typed columns can contain - values of different types (e.g. lists and singletons or string and floats). max_rec : int, optional Maximum number of data-sets to return. By default, there is no upper limit. return_moc : bool, optional - Specifies if we want a `mocpy.MOC` object in return. This MOC corresponds to the union of the MOCs of all - the matching data-sets. By default it is set to False and - :meth:`~astroquery.mocserver.MOCServerClass.query_region` returns an `astropy.table.Table` object. + Specifies whether the output should be a `mocpy.MOC`. The returned MOC is the + union of the MOCs of all the matching datasets. By default it is False and + this method returns a `astropy.table.Table` object. max_norder : int, optional - Has sense only if ``return_moc`` is set to True. Specifies the maximum precision order of the returned MOC. + If ``return_moc`` is set to True, this fixes the maximum precision order + of the returned MOC. For one dimensional MOCs (Space-MOCs and Time-MOCs), + the order is given as an integer. For Space-Time MOCs, the order should be a + string with the space and time orders. + Example: 's3 t45' get_query_payload : bool, optional - If True, returns a dictionary of the query payload instead of the parsed response. + If True, returns a dictionary of the query payload instead of the response. verbose : bool, optional + Whether to show warnings. Defaults to False. + cache: bool, optional + Whether the response should be cached. Returns ------- - response : `astropy.table.Table` or `mocpy.MOC` - By default an astropy table of the data-sets matching the query. If ``return_moc`` is set to True, it gives - a MOC object corresponding to the union of the MOCs from all the retrieved data-sets. - """ - response = self.query_region_async(get_query_payload=get_query_payload, meta_data=meta_data, **kwargs) - if get_query_payload: - return response - - result = self._parse_result(response, verbose=verbose) + response : `astropy.table.Table`, `mocpy.MOC`, `mocpy.TimeMOC`, or `mocpy.STMOC` + By default, returns a table with the datasets matching the query. + If ``return_moc`` is set to True, it gives a MOC object corresponding to the + union of the MOCs from all the retrieved data-sets. - return result - - def query_region_async(self, *, get_query_payload=False, **kwargs): """ - Serves the same purpose as :meth:`~astroquery.mocserver.MOCServerClass.query_region` - but only returns the HTTP response rather than the parsed result. + return self.query_region( + meta_data=meta_data, + region=region, + intersect=intersect, + return_moc=return_moc, + max_norder=max_norder, + fields=fields, + max_rec=max_rec, + casesensitive=casesensitive, + spacesys=spacesys, + get_query_payload=get_query_payload, + verbose=verbose, + cache=cache, + ) + + def query_async( + self, + *, + region=None, + meta_data=None, + return_moc=None, + max_norder=None, + fields=None, + max_rec=None, + intersect="overlaps", + spacesys=None, + casesensitive=False, + get_query_payload=False, + cache=True, + ): + """Return the HTTP response rather than the parsed result. Parameters ---------- - get_query_payload : bool - If True, returns a dictionary of the query payload instead of the parsed response. - **kwargs - Arbitrary keyword arguments. + meta_data : str + Algebraic expression to select the datasets. + Examples of expressions can be found `on the mocserver's examples page + `_. + Example: "ID=*HST*" will return datasets with HST in their ID column. The + star means any character. + casesensitive : Bool, optional + Whether the search should take the case into account. By default, False. + fields : [str], optional + Specifies which columns to retrieve. Defaults to a pre-defined subset of + fields. The complete list of fields can be obtained with `list_fields`. + spacesys: str, optional + This is the space system on which the coordinates are expressed. Can take + the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + be printed with `list_spacesys`. Default is None, meaning that the results + will have mixed frames. + region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion`, `mocpy.MOC`, + `mocpy.TimeMOC`, or `mocpy.STMOC` + The region to query the MOCServer with. Note that this can also be a + space-time region with the Time-MOCs and Space-Time-MOCs. + intersect : str, optional + This parameter can take three different values: + + - ``overlaps`` (default) select datasets overlapping the region + - ``covers`` returned datasets are covering the region. + - ``encloses`` returned datasets are enclosing the region. + + max_rec : int, optional + Maximum number of data-sets to return. By default, there is no upper limit. + return_moc : bool, optional + Specifies whether the output should be a `mocpy.MOC`. The returned MOC is the + union of the MOCs of all the matching datasets. By default it is False and + this method returns a `astropy.table.Table` object. + max_norder : int, optional + If ``return_moc`` is set to True, this fixes the maximum precision order + of the returned MOC. For one dimensional MOCs (Space-MOCs and Time-MOCs), + the order is given as an integer. For Space-Time MOCs, the order should be a + string with the space and time orders. + Example: 's3 t45' + get_query_payload : bool, optional + If True, returns a dictionary of the query payload instead of the response. + verbose : bool, optional + cache: bool, optional + Whether the response should be cached. Returns ------- response : `~requests.Response`: - The HTTP response from the `CDS MOCServer `_. + The HTTP response from the + `CDS MOCServer `_. + """ - request_payload = self._args_to_payload(**kwargs) - if get_query_payload: - return request_payload + request_payload = _args_to_payload( + meta_data=meta_data, + return_moc=return_moc, + max_norder=max_norder, + fields=fields, + max_rec=max_rec, + region=region, + intersect=intersect, + spacesys=spacesys, + casesensitive=casesensitive, + default_fields=self.DEFAULT_FIELDS, + ) params_d = { - 'method': 'GET', - 'url': self.URL, - 'timeout': self.TIMEOUT, - 'data': kwargs.get('data', None), - 'cache': False, - 'params': request_payload, + "method": "GET", + "url": self.URL, + "timeout": self.TIMEOUT, + "cache": cache, + "params": request_payload, } - if not self.path_moc_file: - response = self._request(**params_d) - else: - # The user ask for querying on a MOC region. - with open(self.path_moc_file, 'rb') as f: - params_d.update({'files': {'moc': f.read()}}) - response = self._request(**params_d) + if region: + if get_query_payload: + return request_payload + if not isinstance(region, (MOC, TimeMOC, STMOC)): + return self._request(**params_d) + # MOCs have to be sent in a multipart request + with NamedTemporaryFile() as tmp_file: + region.save(tmp_file.name, overwrite=True, format="fits") - return response + params_d.update({"files": {"moc": tmp_file.read()}}) + return self._request(**params_d) - def _args_to_payload(self, **kwargs): - """ - Convert the keyword arguments to a payload. + if get_query_payload: + return request_payload + return self._request(**params_d) + + def list_fields(self, keyword=None, cache=True): + """List MOC Server fields. + + In the MOC Server, the fields are free. This means that anyone publishing there + can set a new field. This results in a long set of possible fields, that are + more or less frequent in the database. + This method allows to retrieve all fields currently existing, with their + occurrences and an example. Parameters ---------- - kwargs - Arbitrary keyword arguments. The same as those defined in the docstring of - :meth:`~astroquery.mocserver.MOCServerClass.query_object`. + keyword : str, optional + A keyword to filter the output for fields that have the keyword in their + name. This is not case-sensitive. + If you don't give a keyword, you will retrieve all existing fields. + cache: bool, optional + Whether the response should be cached. Returns ------- - request_payload : dict - The payload submitted to the MOC server. - """ - request_payload = dict() - intersect = kwargs.get('intersect', 'overlaps') - if intersect == 'encloses': - intersect = 'enclosed' - - request_payload.update({'intersect': intersect, - 'casesensitive': 'true', - 'fmt': 'json', - 'get': 'record', - }) - - # Region Type - if 'region' in kwargs: - region = kwargs['region'] - if isinstance(region, MOC): - self.path_moc_file = os.path.join(os.getcwd(), 'moc.fits') - if os.path.isfile(self.path_moc_file): # Silent overwrite - os.remove(self.path_moc_file) - region.save(self.path_moc_file, format="fits") - # add the moc region payload to the request payload - elif isinstance(region, CircleSkyRegion): - # add the cone region payload to the request payload - request_payload.update({ - 'DEC': str(region.center.dec.to(u.deg).value), - 'RA': str(region.center.ra.to(u.deg).value), - 'SR': str(region.radius.to(u.deg).value), - }) - elif isinstance(region, PolygonSkyRegion): - # add the polygon region payload to the request payload - polygon_payload = "Polygon" - vertices = region.vertices - for i in range(len(vertices.ra)): - polygon_payload += ' ' + str(vertices.ra[i].to(u.deg).value) + \ - ' ' + str(vertices.dec[i].to(u.deg).value) - request_payload.update({'stc': polygon_payload}) - else: - if region is not None: - raise ValueError('`region` belongs to none of the following types: `regions.CircleSkyRegion`,' - '`regions.PolygonSkyRegion` or `mocpy.MOC`') - - if 'meta_data' in kwargs: - request_payload.update({'expr': kwargs['meta_data']}) - - if 'fields' in kwargs: - fields = kwargs['fields'] - field_l = list(fields) if not isinstance(fields, list) else copy(fields) - # The CDS MOC service responds badly to record queries which do not ask - # for the ID field. To prevent that, we add it to the list of requested fields - field_l.append('ID') - field_l = list(set(field_l)) - fields_str = str(field_l[0]) - for field in field_l[1:]: - fields_str += ', ' - fields_str += field - - request_payload.update({"fields": fields_str}) - - if 'max_rec' in kwargs: - max_rec = kwargs['max_rec'] - request_payload.update({'MAXREC': str(max_rec)}) - - self.return_moc = kwargs.get('return_moc', False) - if self.return_moc: - request_payload.update({'get': 'moc'}) - if 'max_norder' in kwargs: - request_payload.update({'order': kwargs['max_norder']}) - else: - request_payload.update({'order': 'max'}) - - return request_payload - - def _parse_result(self, response, *, verbose=False): + `~astropy.table.Table` + A table containing the field names, heir occurrence (expressed in number + of records), and an example for each field. + + Examples + -------- + >>> from astroquery.mocserver import MOCServer + >>> MOCServer.list_fields("publisher") # doctest: +REMOTE_DATA +IGNORE_OUTPUT + + field_name occurrence example + str27 int64 str70 + -------------- ---------- ------------------------- + publisher_id 32987 ivo://CDS + publisher_did 2871 ivo://nasa.heasarc/warps2 + hips_publisher 14 CDS + """ - Parsing of the response returned by the MOCServer. + fields_descriptions = dict(self.list_fields_async(cache=cache).json()[0]) + occurrences = [ + int(value.split("x")[0][1:]) for value in fields_descriptions.values() + ] + field_names = list(fields_descriptions.keys()) + examples = [value.split("ex: ")[-1] for value in fields_descriptions.values()] + list_fields = Table( + [field_names, occurrences, examples], + names=["field_name", "occurrence", "example"], + ) + list_fields.sort("occurrence", reverse=True) + if keyword: + return list_fields[ + [ + keyword.casefold() in name.casefold() + for name in list_fields["field_name"] + ] + ] + return list_fields + + def list_fields_async(self, cache=True): + """List MOC Server fields asynchronously. + + In the MOC Server, the fields are free. This means that anyone publishing there + can set a new field. This results in a long set of possible fields, that are + more or less frequent in the database. + This method allows to retrieve all fields currently existing, with their + occurrences and an example. Parameters ---------- - response : `~requests.Response` - The HTTP response returned by the MOCServer. - verbose : bool, optional - False by default. + keyword : str, optional + A keyword to filter the output for fields that have the keyword in their + name. This is not case-sensitive. + If you don't give a keyword, you will retrieve all existing fields. + cache: bool, optional + Whether the response should be cached. Returns ------- - result : `astropy.table.Table` or `mocpy.MOC` - By default an astropy table of the data-sets matching the query. If ``return_moc`` is set to True, it gives - a MOC object corresponding to the union of the MOCs from all the matched data-sets. + `~requests.models.Response` + """ - if not verbose: - commons.suppress_vo_warnings() + return self._request( + method="GET", + url=self.URL, + timeout=self.TIMEOUT, + cache=cache, + params={"get": "example", "fmt": "json"}, + ) - result = response.json() + def list_spacesys(self): + """Return the list of "spacesys" currently available in the MOC Server. - if not self.return_moc: - # return a table with the meta-data, we cast the string values for convenience - result = [{key: _cast_to_float(value) for key, value in row.items()} for row in result] - return Table(rows=result) + This list may be enriched later, as new datasets are added into the MOC Server. + + Returns + ------- + list + The list of spacesys currently available in the MOC Server + + """ + frames = list(set(self.query_region(meta_data="hips_frame=*", + fields=["ID", "hips_frame"], + spacesys=None)["hips_frame"])) + # `C` is a special case that corresponds to both equatorial and galactic frames + frames.append("C") + frames.sort() + return frames + + +def _args_to_payload( + *, + meta_data=None, + return_moc=None, + max_norder=None, + fields=None, + max_rec=None, + region=None, + intersect="overlaps", + spacesys=None, + casesensitive=False, + default_fields=None, +): + """Convert the parameters of `query_region` and `find_datasets` into a payload. + + Returns + ------- + dict + The payload that will be submitted to the MOC server. - # return a `mocpy.MOC` object. See https://github.com/cds-astro/mocpy and the MOCPy's doc - return MOC.from_json(result) - -def _cast_to_float(value): """ - Cast ``value`` to a float if possible. + request_payload = { + "casesensitive": str(casesensitive).casefold(), + "fmt": "json", + "get": "record", + "fields": _get_fields(fields, default_fields), + "intersect": intersect.replace("encloses", "enclosed"), + "spacesys": spacesys, + } + + if region and not isinstance(region, (MOC, STMOC, TimeMOC)): + try: + from regions import CircleSkyRegion, PolygonSkyRegion + except ImportError as err: + raise ImportError( + "The module 'regions' is mandatory to use " + "'query_region' without a MOC. Install it with " + "'pip install regions'" + ) from err + if isinstance(region, CircleSkyRegion): + # add the cone region payload to the request payload + request_payload.update( + { + "DEC": str(region.center.dec.to(u.deg).value), + "RA": str(region.center.ra.to(u.deg).value), + "SR": str(region.radius.to(u.deg).value), + } + ) + elif isinstance(region, PolygonSkyRegion): + # add the polygon region payload to the request payload + polygon_payload = "Polygon" + for point in region.vertices: + polygon_payload += ( + f" {point.ra.to(u.deg).value} {point.dec.to(u.deg).value}" + ) + print(polygon_payload) + request_payload.update({"stc": polygon_payload}) + # the MOCs have to be sent through the multipart and not through the payload + else: + # not of any accepted type + raise ValueError( + "'region' should be of type: 'regions.CircleSkyRegion'," + " 'regions.PolygonSkyRegion', 'mocpy.MOC', 'mocpy.TimeMOC'" + f" or 'mocpy.STMOC', but is of type '{type(region)}'." + ) + + if meta_data: + request_payload.update({"expr": meta_data}) + + if max_rec: + request_payload.update({"MAXREC": str(max_rec)}) + + if return_moc: + if return_moc is True: + # this is for legacy reasons return_moc used to be a boolean when the MOC + # server could only return space MOCs. + return_moc = "moc" + request_payload.update({"get": return_moc}) + request_payload.update({"fmt": "ascii"}) + if max_norder: + request_payload.update({"order": max_norder}) + else: + request_payload.update({"order": "max"}) + return request_payload + + +def _get_fields(fields, default_fields): + """Get the list of fields to be queried. + + Defaults to the list defined in conf if fields is None. + + Parameters + ---------- + fields : list[str] or str + The list of columns to be returned. + default_fields : list[str] + The default list of fields. + + """ + if fields: + if isinstance(fields, str): + fields = [fields] + if "ID" not in fields: + # ID has to be included for the server to work correctly + fields.append("ID") + fields = list(fields) if not isinstance(fields, list) else copy(fields) + else: + fields = default_fields + return ", ".join(fields) + + +def _parse_result(response, *, verbose=False, return_moc): + """Parse the response returned by the MOCServer. + + Parameters + ---------- + response : `~requests.Response` + The HTTP response returned by the MOCServer. + verbose : bool, optional + False by default. + return_moc : str or Bool + Whether the result is a MOC or a Table. + + Returns + ------- + `astropy.table.Table`, `~mocpy.MOC`, `~mocpy.STMOC`, `~mocpy.TimeMOC` + This returns a table if ``return_moc`` is not specified. Otherwise, + a MOC of the requested kind. + + """ + if not verbose: + commons.suppress_vo_warnings() + + if return_moc: + result = response.text + # return_moc==True is there to support the version when there was no choice in + # in the MOC in output and the MOC server would only be able to return SMOCs + if return_moc == "moc" or return_moc == "smoc" or return_moc is True: + return MOC.from_str(result) + if return_moc == "tmoc": + return TimeMOC.from_str(result) + if return_moc == "stmoc": + return STMOC.from_str(result) + raise ValueError( + "'return_moc' can only take the values 'moc', 'tmoc', 'smoc'," + f"or 'stmoc'. Got '{return_moc}'." + ) + # return a table with the meta-data, we cast the string values for convenience + result = response.json() + result = [ + {key: _cast_to_float(value) for key, value in row.items()} for row in result + ] + return Table(rows=result) + + +def _cast_to_float(value): + """Cast ``value`` to a float if possible. Parameters ---------- @@ -348,12 +691,14 @@ def _cast_to_float(value): Returns ------- - value : float or str - A float if it can be casted so otherwise the initial string. + float or str + A float if it can be converted to a float. Otherwise the initial string. + """ try: return float(value) except (ValueError, TypeError): return value + MOCServer = MOCServerClass() diff --git a/astroquery/mocserver/tests/data/hips_frames.vot b/astroquery/mocserver/tests/data/hips_frames.vot new file mode 100644 index 0000000000..a30f1945f7 --- /dev/null +++ b/astroquery/mocserver/tests/data/hips_frames.vot @@ -0,0 +1,314 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
galactic
galactic
equatorial
equatorial
galactic
equatorial
sun
equatorial
equatorial
venus
galactic
triton
galactic
galactic
galactic
equatorial
equatorial
equatorial
galactic
galactic
galactic
galactic
equatorial
galactic
galactic
galactic
galactic
galactic
galactic
galactic
equatorial
equatorial
equatorial
galactic
galactic
equatorial
equatorial
equatorial
equatorial
equatorial
equatorial
equatorial
galactic
galactic
equatorial
galactic
equatorial
galactic
galactic
equatorial
galactic
galactic
galactic
galactic
equatorial
galactic
galactic
galactic
galactic
galactic
galactic
galactic
galactic
galactic
equatorial
equatorial
equatorial
equatorial
equatorial
equatorial
equatorial
equatorial
galactic
galactic
galactic
equatorial
equatorial
equatorial
galactic
galactic
galactic
galactic
galactic
galactic
equatorial
galactic
equatorial
galactic
galactic
galactic
equatorial
neptune
equatorial
equatorial
equatorial
equatorial
galactic
equatorial
galactic
moon
+ + diff --git a/astroquery/mocserver/tests/data/hips_from_saada_alasky.json b/astroquery/mocserver/tests/data/hips_from_saada_alasky.json deleted file mode 100644 index 0c19110752..0000000000 --- a/astroquery/mocserver/tests/data/hips_from_saada_alasky.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - {"ID": "xcatdb/P/XMM/PN/color"}, - {"ID": "xcatdb/P/XMM/PN/eb2"}, - {"ID": "xcatdb/P/XMM/PN/eb3"}, - {"ID": "xcatdb/P/XMM/PN/eb4"} -] diff --git a/astroquery/mocserver/tests/data/list_fields.json b/astroquery/mocserver/tests/data/list_fields.json new file mode 100644 index 0000000000..6b3b178aee --- /dev/null +++ b/astroquery/mocserver/tests/data/list_fields.json @@ -0,0 +1 @@ +{"ID": "(37291x) ex: CDS/J/AJ/165/248/table3", "publisher_id": "(33146x) ex: ivo://CDS", "obs_id": "(33121x) ex: J/AJ/165/248/table3", "obs_title": "(37274x) ex: COSMOS2020; 230 quiescent galaxies at 3=10.040 AladinDesktop>=11.125", "hips_rgb_red": "(56x) ex: XMM EPIC (RED) [1.0E-6 NaN 0.003 Log]", "hips_rgb_green": "(46x) ex: XMM EPIC (GREEN) [1.0E-6 NaN 0.003 Log]", "hips_rgb_blue": "(56x) ex: XMM EPIC (BLUE) [1.0E-6 NaN 0.003 Log]", "obs_superseded_by": "(262x) ex: V/145", "bib_reference_url": "(448x) ex: http://cdsads.u-strasbg.fr/abs/1996ASPC..101...88L", "client_sort_key": "(193x) ex: 03-01-03a", "hips_master_url": "(61x) ex: https://hips.astron.nl/ASTRON/P/lotss_dr1_high/", "hips_cat_nrows": "(29x) ex: 862", "data_bunit": "(10x) ex: sr-1", "prov_did": "(41x) ex: ivo://CDS/I/355/gaiadr3", "glutag": "(12x) ex: VizieRGaiaCustom", "hips_publisher": "(14x) ex: CDS", "hips_cube_depth": "(24x) ex: 2177", "hips_cube_firstframe": "(24x) ex: 1088", "addendum_id": "(14x) ex: APPEND/P/1678101097", "o_unit": "(2x) ex: m", "o_ucd": "(2x) ex: pos.bodyrc.alt", "creator": "(43x) ex: ESA (ESDC & INTEGRAL SOC)", "data_cube_crpix3": "(18x) ex: 0", "data_cube_crval3": "(18x) ex: 1.79934", "data_cube_cdelt3": "(18x) ex: 0.0252101", "data_cube_cunit3": "(1x) ex: 'pc-LOG'", "moc_release_date": "(4x) ex: 2015-02-25T11:51Z", "obs_acknowledgement": "(1x) ex: Centre de Donnees astronomique de Strasbourg", "client_link": "(4x) ex: CDS/P/DM/simbad-biblio/otypes/Galaxy maps by object-type", "client_tap_mainlist": "(6x) ex: true", "hips_skyval_method": "(34x) ex: SKYVAL", "hips_skyval_value": "(34x) ex: -0.5 100.0 -357.66149139404297 1421.6846084594727", "hread": "(1x) ex: 8 partitioning=4096", "data_cube_bunit3": "(16x) ex: null", "hips_skyval": "(25x) ex: none", "obs_ack_url": "(9x) ex: https://www.nasa.gov/webbfirstimages", "obs_progenitor": "(1x) ex: CADC", "order": "(3x) ex: 5", "obs_provenance": "(2x) ex: NOAO", "hhips_builder": "(2x) ex: Aladin/HipsGen v12.060", "hips_data_minmax": "(4x) ex: -57430 90620", "#client_category": "(1x) ex: Problematic", "obs_descrition_url": "(1x) ex: http://l2db.selene.darts.isas.jaxa.jp/index.html.en", "cs_glutag": "(1x) ex: SkyBoT.IMCCE", "ohips_frame": "(1x) ex: equatorial", "hips_bunit": "(2x) ex: (1/2.04142)MJy/sr", "hips_glu_tag": "(1x) ex: P-GLIMPSE360.hpx", "hips_lon_asc": "(1x) ex: true", "obs_copyrigh_url": "(1x) ex: http://amundsen.astro.swarthmore.edu/SHASSA/index.html", "HiPSBuilder": "(1x) ex: cds.hips.cat.standalone.v0.1", "processingDate": "(1x) ex: 2016-04-01T13:18Z", "isCatalog": "(1x) ex: true", "nbSources": "(1x) ex: 623604", "hips_pixel_function": "(1x) ex: asinh"} \ No newline at end of file diff --git a/astroquery/mocserver/tests/data/properties.json b/astroquery/mocserver/tests/data/properties.json deleted file mode 100644 index 7670c3e990..0000000000 --- a/astroquery/mocserver/tests/data/properties.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - {"ID": "CDS/J/ApJ/749/10/SDSS-obs"}, - {"ID": "CDS/P/HLA/SDSSg"}, - {"ID": "CDS/P/HLA/SDSSr"}, - {"ID": "CDS/P/HLA/SDSSz"}, - {"ID": "CDS/P/HST/SDSSg"}, - {"ID": "CDS/P/HST/SDSSr"}, - {"ID": "CDS/P/HST/SDSSz"} -] diff --git a/astroquery/mocserver/tests/setup_package.py b/astroquery/mocserver/tests/setup_package.py index 89f6c4bf6a..b5b7e838a4 100644 --- a/astroquery/mocserver/tests/setup_package.py +++ b/astroquery/mocserver/tests/setup_package.py @@ -1,7 +1,11 @@ -import os +from pathlib import Path def get_package_data(): - paths_test = [os.path.join("data", "*.json"), os.path.join("data", "*.fits")] + paths_test = [ + str(Path("data") / "hips_frames.vot"), + str(Path("data") / "list_fields.json"), + str(Path("data") / "moc.fits"), + ] return {"astroquery.mocserver.tests": paths_test} diff --git a/astroquery/mocserver/tests/test_mocserver.py b/astroquery/mocserver/tests/test_mocserver.py index d724bd563f..fda74f989c 100644 --- a/astroquery/mocserver/tests/test_mocserver.py +++ b/astroquery/mocserver/tests/test_mocserver.py @@ -1,154 +1,172 @@ - # Licensed under a 3-clause BSD style license - see LICENSE.rst +import json +from pathlib import Path + import pytest -import os -import requests from astropy import coordinates +from astropy.io.votable import parse_single_table from astropy.table import Table -from ..core import MOCServer -from astroquery.utils.mocks import MockResponse +try: + from mocpy import MOC, STMOC, TimeMOC, FrequencyMOC + HAS_MOCPY = True +except ImportError: + HAS_MOCPY = False +try: + from regions import CircleSkyRegion, PolygonSkyRegion # noqa: E402 + HAS_REGIONS = True +except ImportError: + HAS_REGIONS = False + +from ... import mocserver +from ..core import MOCServer, _parse_result, _cast_to_float + + +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +@pytest.mark.skipif(not HAS_REGIONS, reason="regions is required") +def test_polygon_spatial_request(): + polygon_region = PolygonSkyRegion( + coordinates.SkyCoord( + [57.376, 56.391, 56.025, 56.616], + [24.053, 24.622, 24.049, 24.291], + frame="icrs", + unit="deg", + ) + ) + request_payload = MOCServer.query_region( + region=polygon_region, intersect="overlaps", get_query_payload=True + ) + print(request_payload) + assert request_payload["stc"] == ( + "Polygon 57.376 24.053 56.391 24.622 56.025 24.049 56.616 24.291" + ) -pytest.importorskip("mocpy") -pytest.importorskip("regions") +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +@pytest.mark.skipif(not HAS_REGIONS, reason="regions is required") +def test_cone_search_spatial_request(): + ra = 10.8 + dec = 6.5 + radius = 0.5 + center = coordinates.SkyCoord(ra, dec, unit="deg") + cone_region = CircleSkyRegion( + center=center, radius=coordinates.Angle(radius, unit="deg") + ) + request_payload = MOCServer.query_region( + region=cone_region, get_query_payload=True, intersect="overlaps" + ) + assert request_payload["DEC"] == str(dec) + assert request_payload["RA"] == str(ra) + assert request_payload["SR"] == str(radius) -from regions import CircleSkyRegion, PolygonSkyRegion # noqa: E402 +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +def test_regions_provided_as_mocs(monkeypatch): + # FrequencyMOCs are not supported yet, they should raise an error, along with + # any other unsupported type + with pytest.raises(ValueError, match="'region' should be of type: *"): + MOCServer.query_region(region=FrequencyMOC.from_str("5/0"), + get_query_payload=True) + # patching _request -DATA_FILES = { - "PROPERTIES_SEARCH": "properties.json", - "HIPS_FROM_SAADA_AND_ALASKY": "hips_from_saada_alasky.json", -} + def mockreturn(**kwargs): + return kwargs + monkeypatch.setattr(MOCServer, "_request", mockreturn) + # looping on accepted MOC flavors + for moc in [ + MOC.from_str("0/0-11"), + TimeMOC.from_str("0/0-1"), + STMOC.from_str("t0/0-1 s0/0-11"), + ]: + tempfile_moc = MOCServer.query_async(region=moc)["files"]["moc"] + assert tempfile_moc.startswith(b"SIMPLE = T") -@pytest.fixture -def patch_get(request): - mp = request.getfixturevalue("monkeypatch") - - mp.setattr(requests.Session, "request", get_mockreturn) - return mp - - -def get_mockreturn( - self, - method, - url, - data=None, - timeout=10, - files=None, - params=None, - headers=None, - **kwargs -): - filename = data_path(DATA_FILES[data]) - with open(filename, "rb") as infile: - content = infile.read() - return MockResponse(content) - - -def data_path(filename): - data_dir = os.path.join(os.path.dirname(__file__), "data") - return os.path.join(data_dir, filename) - - -"""List of all the constrain we want to test""" - - -# SPATIAL CONSTRAINTS DEFINITIONS -polygon1 = coordinates.SkyCoord( - [57.376, 56.391, 56.025, 56.616], - [24.053, 24.622, 24.049, 24.291], - frame="icrs", - unit="deg", -) -polygon2 = coordinates.SkyCoord( - [58.376, 53.391, 56.025, 54.616], - [24.053, 25.622, 22.049, 27.291], - frame="icrs", - unit="deg", -) - -# PROPERTY CONSTRAINTS DEFINITIONS -meta_data_ex = "ID = *SDSS* && moc_sky_fraction<=0.01" -meta_data_hips_from_saada_alasky = ( - "(hips_service_url*=http://saada*) && (hips_service_url*=http://alasky.*)" -) -""" -Combination of one spatial with a property constrain - -Each tuple(spatial, property) characterizes a specific query and is tested -with regards to the true results stored in a file located in the data directory - -""" - - -@pytest.mark.parametrize( - "datafile", ["PROPERTIES_SEARCH", "HIPS_FROM_SAADA_AND_ALASKY"] -) -def test_request_results(patch_get, datafile): - """ - Compare the request result obtained with the astroquery.Mocserver API - - with the one obtained on the http://alasky.unistra.fr/MocServer/query - """ - results = MOCServer.query_region( - get_query_payload=False, verbose=True, data=datafile - ) - assert isinstance(results, Table) +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +def test_query_hips(): + # no meta + payload = MOCServer.query_hips(get_query_payload=True) + assert payload["expr"] == "hips_frame=*" + # with meta + payload = MOCServer.query_hips(meta_data="TEST", get_query_payload=True) + assert payload["expr"] == "(TEST)&&hips_frame=*" -""" -Spatial Constrains requests -We test a polygon/cone/moc search and ensure the -request param 'intersect' is correct +# ----------- +# List fields +# ----------- -""" +@pytest.fixture +def _mock_list_fields(monkeypatch): + """Avoid a request to get the list of fields in the mocserver.""" + + # This response changes with time. To regenerate it, do: + # >>> from astroquery.mocserver import MOCServer + # >>> import json + # >>> response = MOCServer.list_fields_async() + # >>> with open("list_fields.json", "w") as f: + # ... json.dump(dict(response.json()[0])) + class MockedListFields: + def json(self): + with open(Path(__file__).parent / "data" / "list_fields.json", "r") as f: + return [json.load(f)] + + monkeypatch.setattr( + mocserver.MOCServerClass, + "list_fields_async", + lambda self, cache: MockedListFields(), + ) -@pytest.mark.parametrize( - "RA, DEC, RADIUS", [(10.8, 6.5, 0.5), (25.6, -23.2, 1.1), (150.6, 45.1, 1.5)] -) -def test_cone_search_spatial_request(RA, DEC, RADIUS): - center = coordinates.SkyCoord(ra=RA, dec=DEC, unit="deg") - radius = coordinates.Angle(RADIUS, unit="deg") - cone_region = CircleSkyRegion(center=center, radius=radius) - request_payload = MOCServer.query_region( - region=cone_region, get_query_payload=True, intersect="overlaps" - ) +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +@pytest.mark.usefixtures("_mock_list_fields") +def test_list_fields(): + fields = MOCServer.list_fields("id") + assert "ID" in fields["field_name"] - assert ( - (request_payload["DEC"] == str(DEC)) - and (request_payload["RA"] == str(RA)) - and (request_payload["SR"] == str(RADIUS)) - ) +# ------------- +# List spacesys +# ------------- -@pytest.mark.parametrize( - "poly, poly_payload", - [ - (polygon1, "Polygon 57.376 24.053 56.391 24.622 56.025 24.049 56.616 24.291"), - (polygon2, "Polygon 58.376 24.053 53.391 25.622 56.025 22.049 54.616 27.291"), - ], -) -def test_polygon_spatial_request(poly, poly_payload): - polygon_region = PolygonSkyRegion(vertices=poly) - request_payload = MOCServer.query_region( - region=polygon_region, intersect="overlaps", get_query_payload=True +@pytest.fixture +def _mock_list_spacesys(monkeypatch): + # This list changes upstream. To regenerate it, do: + # >>> from astroquery.mocserver import MOCServer + # >>> hips_frames = MOCServer.query_region(meta_data="hips_frame=*", + # ... fields=["hips_frame"], + # ... spacesys=None, max_rec=100) + # >>> hips_frames.remove_column("ID") + # >>> hips_frames.write("hips_frames.vot", format="votable", overwrite=True) + with open(Path(__file__).parent / "data" / "hips_frames.vot", "rb") as f: + table = parse_single_table(f).to_table() + monkeypatch.setattr( + mocserver.MOCServerClass, "query_region", lambda self, **kwargs: table ) - assert request_payload["stc"] == poly_payload + +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +@pytest.mark.usefixtures("_mock_list_spacesys") +def test_list_spacesys(): + list_spacesys = MOCServer.list_spacesys() + assert "C" in list_spacesys and "equatorial" in list_spacesys +# --------------------- +# Special case keywords +# --------------------- + + +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +@pytest.mark.skipif(not HAS_REGIONS, reason="regions is required") @pytest.mark.parametrize("intersect", ["encloses", "overlaps", "covers"]) def test_intersect_param(intersect): center = coordinates.SkyCoord(ra=10.8, dec=32.2, unit="deg") radius = coordinates.Angle(1.5, unit="deg") - cone_region = CircleSkyRegion(center, radius) request_payload = MOCServer.query_region( region=cone_region, intersect=intersect, get_query_payload=True @@ -157,3 +175,92 @@ def test_intersect_param(intersect): assert request_payload["intersect"] == "enclosed" else: assert request_payload["intersect"] == intersect + + +def test_fields(): + # check that it works for a single field + payload = MOCServer.query_region(meta_data="", fields="ID", get_query_payload=True) + assert payload["fields"] == "ID" + # as well as more fields + payload = MOCServer.query_region( + meta_data="", fields=["ID", "hips_properties"], get_query_payload=True + ) + # cannot test the order, due to the use of set + assert "hips_properties" in payload["fields"] and "ID" in payload["fields"] + # ID has to be in fields + payload = MOCServer.query_region( + meta_data="", fields="hips_body", get_query_payload=True + ) + assert "ID" in payload["fields"] + + +def test_caseinsensitive(): + # casesensitive was hardcoded to true until astroquery 0.4.8. It is now an option + payload = MOCServer.query_region(meta_data="", fields="ID", get_query_payload=True) + assert payload["casesensitive"] == "false" + payload = MOCServer.query_region( + meta_data="", fields="ID", get_query_payload=True, casesensitive=True + ) + assert payload["casesensitive"] == "true" + + +def test_maxrec(): + payload = MOCServer.query_region(meta_data="", max_rec=100, get_query_payload=True) + assert payload["MAXREC"] == "100" + + +def test_return_moc(): + # legacy compatibility, return_moc=True means a space-MOC + payload = MOCServer.query_region( + meta_data="", return_moc=True, max_norder=5, get_query_payload=True + ) + assert payload["get"] == "moc" + assert payload["fmt"] == "ascii" + assert payload["order"] == 5 + # no max_norder means maximum order available + payload = MOCServer.query_region( + meta_data="", return_moc=True, get_query_payload=True + ) + assert payload["order"] == "max" + + +# ---------------- +# Helper functions +# ---------------- + + +@pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") +def test_parse_result(): + class MockResult: + def __init__(self, text): + self.text = text + + def json(self): + return self.text + + # all MOC types + assert isinstance(_parse_result(MockResult("0/0-11"), return_moc="moc"), MOC) + assert isinstance(_parse_result(MockResult("0/0-1"), return_moc="tmoc"), TimeMOC) + assert isinstance(_parse_result(MockResult("t3/ s5/"), return_moc="stmoc"), STMOC) + # fmoc not yet accepted + with pytest.raises(ValueError, match="'return_moc' can only take the values*"): + _parse_result(MockResult("test"), return_moc="fmoc") + # non-MOC response + assert isinstance( + _parse_result(MockResult([{"a": 0, "b": 1}]), return_moc=None), Table + ) + + +def test_cast_to_float(): + assert _cast_to_float("3") == 3 + assert _cast_to_float("test") == "test" + +# ------------ +# Deprecations +# ------------ + + +def test_find_datasets(): + # find datasets is useless as it does the same than query region + old = MOCServer.find_datasets(meta_data="ID=*Euclid*", get_query_payload=True) + assert old == MOCServer.query_region(meta_data="ID=*Euclid*", get_query_payload=True) diff --git a/astroquery/mocserver/tests/test_mocserver_remote.py b/astroquery/mocserver/tests/test_mocserver_remote.py index 82996a39ce..0bc7473115 100644 --- a/astroquery/mocserver/tests/test_mocserver_remote.py +++ b/astroquery/mocserver/tests/test_mocserver_remote.py @@ -1,49 +1,34 @@ - # Licensed under a 3-clause BSD style license - see LICENSE.rst import pytest -from astropy import coordinates from astropy.table import Table +from astropy.time import Time try: - from mocpy import MOC + from mocpy import MOC, STMOC, TimeMOC HAS_MOCPY = True except ImportError: HAS_MOCPY = False -try: - from regions import CircleSkyRegion - - HAS_REGIONS = True -except ImportError: - HAS_REGIONS = False - from ..core import MOCServer @pytest.mark.remote_data @pytest.mark.skipif(not HAS_MOCPY, reason="mocpy is required") class TestMOCServerRemote: - """ - Tests requiring regions - """ - # test of MAXREC payload - @pytest.mark.skipif(not HAS_REGIONS, reason="regions is required") @pytest.mark.parametrize("max_rec", [3, 10]) def test_max_rec_param(self, max_rec): - center = coordinates.SkyCoord(ra=10.8, dec=32.2, unit="deg") - radius = coordinates.Angle(1.5, unit="deg") - cone_region = CircleSkyRegion(center, radius) result = MOCServer.query_region( - region=cone_region, max_rec=max_rec, get_query_payload=False + region=MOC.from_str("0/0-11"), + max_rec=max_rec, + get_query_payload=False, + fields=["ID"], ) - assert max_rec == len(result) # test of field_l when retrieving dataset records - @pytest.mark.skipif(not HAS_REGIONS, reason="regions is required") @pytest.mark.parametrize( "field_l", [ @@ -52,40 +37,65 @@ def test_max_rec_param(self, max_rec): ], ) def test_field_l_param(self, field_l): - center = coordinates.SkyCoord(ra=10.8, dec=32.2, unit="deg") - radius = coordinates.Angle(1.5, unit="deg") - - cone_region = CircleSkyRegion(center, radius) - table = MOCServer.query_region( - region=cone_region, fields=field_l, get_query_payload=False + region=MOC.from_str("20/0"), fields=field_l, get_query_payload=False ) assert isinstance(table, Table) assert set(table.colnames).issubset(set(field_l)) - """ - Tests requiring mocpy - """ - # test of moc_order payload - @pytest.mark.parametrize("moc_order", [5, 10]) + @pytest.mark.parametrize("moc_order", [1, 3]) def test_moc_order_param(self, moc_order): moc_region = MOC.from_str("10/0-9") result = MOCServer.query_region( region=moc_region, return_moc=True, max_norder=moc_order, + intersect="enclosed", ) assert isinstance(result, MOC) assert result.max_order == moc_order - @pytest.mark.parametrize( - "meta_data_expr", - ["ID=*HST*", "moc_sky_fraction>0.5", "(ID=*DSS*)&&(moc_sky_fraction>0.1)"], - ) - def test_find_data_sets(self, meta_data_expr): - result = MOCServer.find_datasets( - meta_data=meta_data_expr, + def test_stmoc_as_outputs(self): + # chose a dataset with a MOC with few cells + stmoc = MOCServer.query_region( + meta_data="ID=CDS/J/AJ/157/109/table1", return_moc="stmoc" + ) + assert isinstance(stmoc, STMOC) + + def test_temporal_mocs_as_inputs(self): + tmoc = TimeMOC.from_str("11/1") + result = MOCServer.query_region( + region=tmoc, + fields=["t_min"], + max_rec=100, + meta_data="dataproduct_type='image'&&t_min=*", + ) + min_time_result = Time(result["t_min"].value, format="mjd") + # the resulting datasets should only have starting times after the + # beginning of the T-MOC + assert all(tmoc.min_time < min_time_result) + + def test_no_region(self): + result = MOCServer.query_region( + meta_data="moc_sky_fraction>0.5&&moc_sky_fraction=*", fields=["ID", "moc_sky_fraction"], + max_rec=100, ) - assert isinstance(result, Table) + assert all(result["moc_sky_fraction"] > 0.5) + + def test_query_hips(self): + result = MOCServer.query_hips(spacesys="venus", fields="hips_frame") + assert all(result["hips_frame"] == "venus") + + def test_list_fields(self): + # with keyword + moc_fields = MOCServer.list_fields("moc") + assert all("moc" in field for field in moc_fields["field_name"]) + type_moc = moc_fields[moc_fields["field_name"] == "moc_type"] + # this was the occurrence number in 2024, can only be higher in the future + assert type_moc["occurrence"][0] >= 34000 + # without keyword + all_fields = MOCServer.list_fields() + assert len(all_fields) > 100 + assert "ID" in list(all_fields["field_name"]) diff --git a/docs/mocserver/MOC_GALEXGR6_AIS_FUV.png b/docs/mocserver/MOC_GALEXGR6_AIS_FUV.png deleted file mode 100644 index 98212592f0abfc438ad1dc57c91cd76502335b70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131787 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 V-rrX(#;>$`#^Uvpb(^4Q9yIltJ~~*MZjhg zx3rvpv0~Pq;Q_Pv(=kn51REZZ$Dh{RlB+H*!UPEy)fQndXQ!8cS49QgA0POtCo89w zJRC4iBdu>~`2w_BrcZctR_k`p!-)BiW8vr%L{YG3_VP|t4)6cjMQ`10q(0i(&3viKVB|PIGh&@ z$AbBYH$r4P+1V}rr6`4MrxT7@ljnDOczF>k&D4|?eM+A0p0>wCCw;D2UbcJQ0ZWdk zfuQn)tqiFfn08@+&!*7(+(70KRR{rjOWs(}Is-oPu zGtcA#1$v&VLSt_SXXz@&0OS|L>zfs1OL76*}=g_ zcer2PZbI-CiB^{Om#3FkPVC*0l$@Mz-nRwaOEt(ztrZujAt6bm3N`8Wh34$bSOCLW!YHL>nDMdv^yv)%n9D0+pJGJju%3T!W<^9UZ6kct0zh-3LHf=(7N^ zd!3Z@6bymAd7Juy6$1SHA8jU-G#rM4FR!$;wJpGfCI)-r5L~`X*1BiF*G{{CC6RHb zTqv7nB7Smw+^9(p#C*Jk#Dj{;%3clkD-tyC*^x$?J-ErYG1WYiQ>&;Lr*S~zaGQ); z!sn@=nSx3QK|w(v<%7_ZpfG5xufJE&W9Ou<-W~f(7%XVw>gVS_L%@ZD3m1tRkwg7Q zIL{s`Dk_(I|HbVKE~I~b&PD%%@6SnVS2RoC-i)FX5IBJ!L8n5%{1*x2)BgF^V~2>b z_Ap8)+C6_EplD zn6!y1f=C2ysfCRVS{g)|iX#admkePB%3+a!xMc!Q53Ej4H4=BT9To2;xL#NP9f z(4c#NjhCaaBhQ~$v@xSKow9O6@8{nHrL3(j6r;=W^w1go1Qu?VX92z+Ev8$<413vi;A;|9`_ and the `MOCPy's documentation -`_ developed by the CDS. +MOC means Multi-Order Coverage. It's an `IVOA standard`_ that allows to describe +Space-Time coverages of arbitrary sky regions --with or without +an associated time of observation. -CDS has set up a server known as the `MOCServer `_ storing data-set names -each associated with a MOC spatial coverage and some meta-datas giving a more detailed explanation of the data-set. +The space component maps the sky with the HEALPix sky +tessellation to represent regions on the sky by hierarchically grouped HEALPix cells. +It other words, a Spatial MOC is a set of HEALPix cells at different orders. -The MOCServer aims at returning the data-sets having at least one source lying in a specific sky region defined by the -user. Internally the MOCServer performs the intersection between the given sky region and the MOCs associated with each -data-sets. Because the MOC associated to a data-set describes its sky coverage, if the above intersection is not null -then the MOCServer knows that some sources of this data-set are in the user defined sky region. +For those wanting to know more about MOCs, please refer to `the MOC 2.0 specification +document `_. -To be aware of what the MOCServer returns, please refers to this `link -`_. -We have queried the MOCServer with a cone region of center ra, dec = (10.8, 32.2) deg and radius = 1.5 deg. In return, -the MOCServer gives a list of data-sets each tagged with an unique ID along with some other meta-datas too e.g. -``obs_title``, ``obs_description``, ``moc_access_url`` (url for accessing the MOC associated with the data-set. Usually -a FITS file storing a list of HEALPix cells). +MOCPy is a Python library allowing to manipulate these Space-Time Coverage objects. +You're welcome to have a look at `MOCPy's documentation `_. -It is also possible to ask the MOCServer for retrieving data-sets based on their meta-data values. `Here -`_ -we have queried the MOCServer for only the image data-sets being in the cone defined above (``dataproduct_type`` -meta-data equals to ``"image"``). +What's the MOC Server? +---------------------- -This package implements two methods: +The MOC Server is a service of astronomical resources organized by spatial and/or +temporal coverages following the Space and Time MOC specification. +In the MOC Server, there a few tens of thousands of astronomical collections. +They each have and identifier ``ID`` and a set of properties that describe their content. +This is a practical way of finding datasets with criteria on time and space. -* :meth:`~astroquery.mocserver.MOCServerClass.query_region` retrieving data-sets (their associated MOCs and meta-datas) having sources in a given region. -* :meth:`~astroquery.mocserver.MOCServerClass.find_datasets` retrieving data-sets (their associated MOCs and meta-datas) based on the - values of their meta-datas. +The meta-data properties are freely assigned by each publisher. You can get the list of +properties with their frequency of usage and examples example with +`astroquery.mocserver.MOCServerClass.list_fields`. +This method also accepts a string to limit the number of responses. Let's try with ``MOC``: -Requirements ----------------------------------------------------- -The following packages are required for the use of this module: +.. + We ignore output here, as occurrence is changing often +.. doctest-remote-data:: + + >>> from astroquery.mocserver import MOCServer + >>> MOCServer.list_fields("MOC") # doctest: +IGNORE_OUTPUT + + field_name occurrence example + str27 int64 str70 + ---------------- ---------- ---------------------------------------------------------------------- + moc_type 34226 smoc + moc_access_url 33084 https://alasky.unistra.fr/footprints/tables/vizier/J_AJ_165_248_tab... + moc_sky_fraction 30175 4.271E-6 + moc_order 30161 11 + moc_time_range 5319 5491 + moc_time_order 5319 35 + moc_release_date 4 2015-02-25T11:51Z + +Here, we learn that there are very few MOC-related field names. The most frequent is +``moc_type`` that will tell if the MOC is a spatial moc (``smoc``), a temporal moc +(``tmoc``)... -* `astropy-healpix`_ -* `mocpy`_ -* `regions`_ +Querying with a region +====================== -Examples -======== +The MOCServer is optimized to return the datasets having at least one source lying in a +specific sky region (or time interval). +The regions can be provided either as astropy-regions from the ``region`` python library, +or as an accepted MOC type (`mocpy.TimeMOC`, `mocpy.MOC`, `~mocpy.STMOC`). +The frequency MOCs are not yet available. -Performing a CDS MOC query on a cone region -------------------------------------------- +Performing a query on a cone region +----------------------------------- -The first thing to do is to import the `regions`_ package and the ``mocserver`` module. +Let's get the datasets for which all the data is comprised in a cone (this is +what the ``enclosed`` option means for intersect). -.. code-block:: python +.. doctest-remote-data:: >>> from astropy import coordinates >>> from regions import CircleSkyRegion >>> from astroquery.mocserver import MOCServer + >>> center = coordinates.SkyCoord(10.8, 32.2, unit='deg') + >>> radius = coordinates.Angle(0.5, unit='deg') + >>> cone = CircleSkyRegion(center, radius) + >>> MOCServer.query_region(region=cone, intersect="enclosed", spacesys="C") # doctest: +IGNORE_OUTPUT +
+ ID ... + str49 ... + ------------------------------ ... + CDS/C/GALFAHI/Narrow ... + CDS/C/GALFAHI/Narrow/DR2 ... + CDS/C/GALFAHI/Wide/DR2 ... + CDS/C/HI4PI/HI ... + CDS/I/220/out ... + CDS/I/243/out ... + CDS/I/252/out ... + CDS/I/254/out ... + CDS/I/255/out ... + ... ... + ov-gso/P/WHAM ... + simg.de/P/NSNS/DR0_1/halpha ... + simg.de/P/NSNS/DR0_1/halpha8 ... + simg.de/P/NSNS/DR0_1/hbr8 ... + simg.de/P/NSNS/DR0_1/sn-halpha ... + simg.de/P/NSNS/DR0_1/sn-vc ... + simg.de/P/NSNS/DR0_1/tc8 ... + simg.de/P/NSNS/DR0_1/vc ... + wfau.roe.ac.uk/P/UHSDR1/J ... + wfau.roe.ac.uk/UHSDR1/J ... + +You can also use this method with `regions.PolygonSkyRegion`, `mocpy.MOC`, `mocpy.TimeMOC`, +and `mocpy.STMOC`. + +Querying by meta-data +===================== + +Retrieving datasets based on their meta-data values +---------------------------------------------------- -``mocserver`` implements the method :meth:`~astroquery.mocserver.MOCServerClass.query_region` and this is what we will use. -First, we need to define a cone region. For that purpose we will instantiate a `regions.CircleSkyRegion` object: +The ``meta_data`` parameter of :meth:`~astroquery.mocserver.MOCServerClass.query_region` +allows to write an algebraic expression on the metadata. +Let's add a criteria to get only images from the previous query: -.. code-block:: python +.. doctest-remote-data:: + >>> from astropy import coordinates + >>> from regions import CircleSkyRegion + >>> from astroquery.mocserver import MOCServer >>> center = coordinates.SkyCoord(10.8, 32.2, unit='deg') - >>> radius = coordinates.Angle(1.5, unit='deg') - + >>> radius = coordinates.Angle(0.5, unit='deg') >>> cone = CircleSkyRegion(center, radius) + >>> MOCServer.query_region(region=cone, intersect="enclosed", + ... fields=['ID', 'dataproduct_type', 'moc_sky_fraction'], + ... meta_data="dataproduct_type=image") # doctest: +IGNORE_OUTPUT +
+ ID dataproduct_type moc_sky_fraction + str49 str5 float64 + ------------------------------ ---------------- ---------------- + CDS/P/2MASS/H image 1.0 + CDS/P/2MASS/J image 1.0 + CDS/P/2MASS/K image 1.0 + CDS/P/2MASS/color image 1.0 + CDS/P/AKARI/FIS/Color image 1.0 + CDS/P/AKARI/FIS/N160 image 1.0 + CDS/P/AKARI/FIS/N60 image 1.0 + CDS/P/AKARI/FIS/WideL image 1.0 + CDS/P/AKARI/FIS/WideS image 1.0 + ... ... ... + ov-gso/P/RASS/SoftBand image 1.0 + ov-gso/P/WHAM image 1.0 + simg.de/P/NSNS/DR0_1/halpha image 0.6464 + simg.de/P/NSNS/DR0_1/halpha8 image 0.6464 + simg.de/P/NSNS/DR0_1/hbr8 image 0.651 + simg.de/P/NSNS/DR0_1/sn-halpha image 0.6466 + simg.de/P/NSNS/DR0_1/sn-vc image 0.6466 + simg.de/P/NSNS/DR0_1/tc8 image 0.651 + simg.de/P/NSNS/DR0_1/vc image 0.6464 + wfau.roe.ac.uk/P/UHSDR1/J image 0.3083 + + +Looking at the ``dataproduct_type`` column, all the datasets are indeed images. + +`This page `_ on the web interface of the +MOCServer gives examples of some filtering expressions. + +Alternatively, you can search on the whole sky by ommitting the region parameter. +The next example retrieves all the ``moc_access_url`` of the Hubble surveys: + +.. doctest-remote-data:: + + >>> MOCServer.query_region(meta_data="ID=*HST*", + ... fields=['ID', 'moc_access_url'], + ... casesensitive=False) # doctest: +IGNORE_OUTPUT +
+ ID moc_access_url + str26 str51 + -------------------------- --------------------------------------------------- + CDS/P/HST-programs/3D-DASH -- + CDS/P/HST/B -- + CDS/P/HST/CO -- + CDS/P/HST/EPO -- + CDS/P/HST/GOODS/b http://alasky.unistra.fr/GOODS/GOODSb/Moc.fits + CDS/P/HST/GOODS/color http://alasky.unistra.fr/GOODS/GOODS-color/Moc.fits + CDS/P/HST/GOODS/i http://alasky.unistra.fr/GOODS/GOODSi/Moc.fits + CDS/P/HST/GOODS/v http://alasky.unistra.fr/GOODS/GOODSv/Moc.fits + CDS/P/HST/GOODS/z http://alasky.unistra.fr/GOODS/GOODSz/Moc.fits + ... ... + CDS/P/HST/Y -- + CDS/P/HST/other -- + CDS/P/HST/wideUV -- + CDS/P/HST/wideV -- + ESAVO/P/HST/ACS-blue -- + ESAVO/P/HST/FOC -- + ESAVO/P/HST/NICMOS -- + ESAVO/P/HST/WFC3 -- + ESAVO/P/HST/WFPC -- + ESAVO/P/HST/WFPC2 -- + +Query for HiPS surveys +====================== + +The MOCServer contains an extensive list of HiPS, for images and catalogs. These +progressive surveys can be displayed in applications such as Aladin or ESASky. +The `astroquery.mocserver.MOCServerClass.query_hips` method allows to find these HiPS. +It accepts the same parameters (``region`` and ``meta_data`` for example as the other) +methods. The only difference is that the output will only contain HiPS data. + +.. doctest-remote-data:: + + >>> from astroquery.mocserver import MOCServer + >>> MOCServer.query_hips(spacesys="mars") # doctest: +IGNORE_OUTPUT +
+ ID ... + str35 ... + ----------------------------------- ... + CDS/P/Mars/Express286545 ... + CDS/P/Mars/MGS-MOLA-DEM ... + CDS/P/Mars/MGS-TES-Dust ... + CDS/P/Mars/MOLA-color ... + CDS/P/Mars/MRO-CTX ... + CDS/P/Mars/TES-Albedo ... + CDS/P/Mars/TES-Thermal-Inertia ... + CDS/P/Mars/THEMIS-Day-100m-v12 ... + CDS/P/Mars/THEMIS-IR-Night-100m-v14 ... + ... ... + idoc/P/omega/emissivite_5-03mic ... + idoc/P/omega/emissivite_5-05mic ... + idoc/P/omega/emissivite_5-07mic ... + idoc/P/omega/emissivite_5-09mic ... + idoc/P/omega/ferric_bd530 ... + idoc/P/omega/ferric_nnphs ... + idoc/P/omega/olivine_osp1 ... + idoc/P/omega/olivine_osp2 ... + idoc/P/omega/olivine_osp3 ... + idoc/P/omega/pyroxene_bd2000 ... doi:10.1029/2012JE004117 ... omega pyroxene_bd2000 + +Here, we see that there are currently 25 HiPS surveys for the planet Mars available in +the MOCServer. + +Personalization of the queries +============================== + +Changing the default fields +--------------------------- + +By default, :meth:`~astroquery.mocserver.MOCServerClass.query_region` returns an +`astropy.table.Table` with information about the matching datasets. The default fields +are ``ID``, ``obs_title``, ``obs_description``, ``nb_rows``, ``obs_regime``, +``bib_reference``, and ``dataproduct_type``. +To change this default behavior, use the ``fields`` parameter. +Let's say we would like only the ``ID``, and the ``moc_sky_fraction`` for this query: + +.. doctest-remote-data:: -And basically call the :meth:`~astroquery.mocserver.MOCServerClass.query_region` method with the cone and that's all. - -.. code-block:: python - - >>> MOCServer.query_region(region=cone) -
- hips_service_url_8 hips_status hips_status_7 ... hipsgen_date_5 hips_master_url moc_sky_fraction - object object object ... object object float64 - ------------------ -------------------------- ------------- ... ----------------- -------------------------------------------------------------------------------- ---------------- - -- -- -- ... -- -- 0.0588 - -- -- -- ... -- -- 2.066e-06 - -- -- -- ... -- -- 0.002134 - -- -- -- ... -- -- 0.003107 - -- -- -- ... -- -- 0.0001764 - -- -- -- ... -- -- 0.008365 - -- -- -- ... -- -- 0.0009891 - -- -- -- ... -- -- 0.0004252 - -- -- -- ... -- -- 0.0006163 - -- -- -- ... -- -- 0.0008544 - -- -- -- ... -- -- 0.0009243 - -- -- -- ... -- -- 0.00016 - -- -- -- ... -- -- 0.000729 - -- -- -- ... -- -- 2.998e-05 - -- -- -- ... -- -- 0.01136 - -- -- -- ... -- -- 0.0006112 - -- -- -- ... -- -- 6.632e-05 - -- -- -- ... -- -- 0.0001141 - -- -- -- ... -- -- 0.0008666 - -- -- -- ... -- -- 0.001025 - -- -- -- ... -- -- 0.008088 - -- -- -- ... -- -- 0.000282 - -- -- -- ... -- -- 0.002413 - -- -- -- ... -- -- 0.0001468 - -- public master clonableOnce -- ... -- -- 0.3164 - -- public master clonableOnce -- ... -- -- 1.0 - -- -- -- ... -- -- 4.444e-05 - -- -- -- ... -- -- 4.641e-05 - -- -- -- ... -- -- 0.00044 - ... ... ... ... ... ... ... - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- http://cade.irap.omp.eu/documents/Ancillary/4Aladin/DRAO-VillaElisa_21cm_POLQ 1.0 - -- public master unclonable -- ... -- http://cade.irap.omp.eu/documents/Ancillary/4Aladin/DRAO-VillaElisa_21cm_POLU 1.0 - -- public master unclonable -- ... -- http://cade.irap.omp.eu/documents/Ancillary/4Aladin/DRAO_22MHz 0.7283 - -- public master unclonable -- ... 2017-02-09T13:48Z -- 0.5723 - -- public master unclonable -- ... -- -- 0.5468 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- http://cade.irap.omp.eu/documents/Ancillary/4Aladin/GAURIBIDANUR/ 0.8623 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 0.9635 - -- public master unclonable -- ... -- -- 0.4284 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- -- 1.0 - -- public master unclonable -- ... -- http://cade.irap.omp.eu/documents/Ancillary/4Aladin/STOCKERT+VILLAELISA_1420MHz/ 1.0 - -- public master unclonable -- ... -- -- 0.181 - -- public master unclonable -- ... -- -- 0.1918 - -- public master unclonable -- ... 2017-05-15T12:44Z -- 1.0 - -- -- -- ... -- -- 0.1553 - -- -- -- ... -- -- 0.2373 - -- public master clonableOnce -- ... -- -- 0.08287 - -- public master clonableOnce -- ... -- -- 0.02227 - -- public master clonableOnce -- ... -- -- 0.02227 - -- public master clonableOnce -- ... -- -- 0.02227 - -You can also query the MOCServer on a `regions.PolygonSkyRegion` or even an `mocpy.MOC` following the same pattern i.e. just -by replacing ``cone`` with a polygon or a MOC object. - - -By default, :meth:`~astroquery.mocserver.MOCServerClass.query_region` returns an `astropy.table.Table` object storing the data-sets -as rows and their meta-datas as columns. Data-sets might have no information for a specific meta-data. If so, the value -associated with this meta-data for this data-set is set to "-". The above astropy table looks like : - - -Retrieve only a subset of meta-datas ------------------------------------- - -This table refers to a lot of meta-datas whereas we could only use a few of them. In fact, it is possible to ask the -MOCServer to give us a reduced set of meta-datas for the resulting data-sets. The table returned by the MOCServer -will be lighter and thus faster to retrieve. - -The parameter ``fields`` of :meth:`~astroquery.mocserver.MOCServerClass.query_region` allows us to provide the list of meta-datas we -want to get. Let's say we would like only the ``ID``, the ``moc_sky_fraction`` and the ``moc_access_url`` of the -resulting data-sets: - -.. code-block:: python - - >>> MOCServer.query_region(region=cone, fields=['ID', 'moc_sky_fraction', 'moc_access_url']) -
- moc_access_url ID moc_sky_fraction - object str48 float64 - ------------------------------------------------------------------------------------ ------------------------------------ ---------------- - http://alasky.unistra.fr/footprints/tables/vizier/B_assocdata_obscore/MOC?nside=2048 CDS/B/assocdata/obscore 0.0588 - http://alasky.unistra.fr/footprints/tables/vizier/B_cb_lmxbdata/MOC?nside=2048 CDS/B/cb/lmxbdata 2.066e-06 - http://alasky.unistra.fr/footprints/tables/vizier/B_cfht_cfht/MOC?nside=2048 CDS/B/cfht/cfht 0.002134 - http://alasky.unistra.fr/footprints/tables/vizier/B_cfht_obscore/MOC?nside=2048 CDS/B/cfht/obscore 0.003107 - http://alasky.unistra.fr/footprints/tables/vizier/B_chandra_chandra/MOC?nside=2048 CDS/B/chandra/chandra 0.0001764 - http://alasky.unistra.fr/footprints/tables/vizier/B_eso_eso_arc/MOC?nside=2048 CDS/B/eso/eso_arc 0.008365 - http://alasky.unistra.fr/footprints/tables/vizier/B_gcvs_gcvs_cat/MOC?nside=2048 CDS/B/gcvs/gcvs_cat 0.0009891 - http://alasky.unistra.fr/footprints/tables/vizier/B_gcvs_nsv_cat/MOC?nside=2048 CDS/B/gcvs/nsv_cat 0.0004252 - http://alasky.unistra.fr/footprints/tables/vizier/B_gemini_obscore/MOC?nside=2048 CDS/B/gemini/obscore 0.0006163 - http://alasky.unistra.fr/footprints/tables/vizier/B_hst_hstlog/MOC?nside=2048 CDS/B/hst/hstlog 0.0008544 - http://alasky.unistra.fr/footprints/tables/vizier/B_hst_obscore/MOC?nside=2048 CDS/B/hst/obscore 0.0009243 - http://alasky.unistra.fr/footprints/tables/vizier/B_hst_wfpc2/MOC?nside=2048 CDS/B/hst/wfpc2 0.00016 - http://alasky.unistra.fr/footprints/tables/vizier/B_jcmt_obscore/MOC?nside=2048 CDS/B/jcmt/obscore 0.000729 - http://alasky.unistra.fr/footprints/tables/vizier/B_merlin_merlin/MOC?nside=2048 CDS/B/merlin/merlin 2.998e-05 - http://alasky.unistra.fr/footprints/tables/vizier/B_mk_mktypes/MOC?nside=2048 CDS/B/mk/mktypes 0.01136 - http://alasky.unistra.fr/footprints/tables/vizier/B_pastel_pastel/MOC?nside=2048 CDS/B/pastel/pastel 0.0006112 - http://alasky.unistra.fr/footprints/tables/vizier/B_sb9_main/MOC?nside=2048 CDS/B/sb9/main 6.632e-05 - http://alasky.unistra.fr/footprints/tables/vizier/B_sn_sncat/MOC?nside=2048 CDS/B/sn/sncat 0.0001141 - http://alasky.unistra.fr/footprints/tables/vizier/B_subaru_suprimc/MOC?nside=2048 CDS/B/subaru/suprimc 0.0008666 - http://alasky.unistra.fr/footprints/tables/vizier/B_swift_swiftlog/MOC?nside=2048 CDS/B/swift/swiftlog 0.001025 - http://alasky.unistra.fr/footprints/tables/vizier/B_vsx_vsx/MOC?nside=2048 CDS/B/vsx/vsx 0.008088 - http://alasky.unistra.fr/footprints/tables/vizier/B_wd_catalog/MOC?nside=2048 CDS/B/wd/catalog 0.000282 - http://alasky.unistra.fr/footprints/tables/vizier/B_wds_wds/MOC?nside=2048 CDS/B/wds/wds 0.002413 - http://alasky.unistra.fr/footprints/tables/vizier/B_xmm_xmmlog/MOC?nside=2048 CDS/B/xmm/xmmlog 0.0001468 - -- CDS/C/GALFAHI/Narrow 0.3164 - -- CDS/C/HI4PI/HI 1.0 - http://alasky.unistra.fr/footprints/tables/vizier/I_100A_w10/MOC?nside=2048 CDS/I/100A/w10 4.444e-05 - http://alasky.unistra.fr/footprints/tables/vizier/I_100A_w25/MOC?nside=2048 CDS/I/100A/w25 4.641e-05 - http://alasky.unistra.fr/footprints/tables/vizier/I_100A_w50/MOC?nside=2048 CDS/I/100A/w50 0.00044 - ... ... ... - -- ov-gso/P/DIRBE/ZSMA9 1.0 - -- ov-gso/P/DRAO-VillaElisa/21cm/POLQ 1.0 - -- ov-gso/P/DRAO-VillaElisa/21cm/POLU 1.0 - -- ov-gso/P/DRAO/22MHz 0.7283 - -- ov-gso/P/DWINGELOO/820MHz 0.5723 - -- ov-gso/P/EBHIS 0.5468 - -- ov-gso/P/GASS+EBHIS 1.0 - -- ov-gso/P/GAURIBIDANUR 0.8623 - -- ov-gso/P/IRIS/1 1.0 - -- ov-gso/P/IRIS/2 1.0 - -- ov-gso/P/IRIS/3 1.0 - -- ov-gso/P/IRIS/4 1.0 - -- ov-gso/P/LAB 1.0 - -- ov-gso/P/MAIPU-MU 0.9635 - -- ov-gso/P/MITEoR 0.4284 - -- ov-gso/P/RASS 1.0 - -- ov-gso/P/RASS/EXP 1.0 - -- ov-gso/P/RASS/HardBand 1.0 - -- ov-gso/P/RASS/SoftBand 1.0 - -- ov-gso/P/STOCKERT+VILLAELISA/1420MHz 1.0 - -- ov-gso/P/VTSS/CONT 0.181 - -- ov-gso/P/VTSS/Ha 0.1918 - -- ov-gso/P/WHAM 1.0 - -- svo.cab/cat/catlib 0.1553 - -- svo.cab/cat/miles 0.2373 - -- xcatdb/P/XMM/PN/color 0.08287 - -- xcatdb/P/XMM/PN/eb2 0.02227 - -- xcatdb/P/XMM/PN/eb3 0.02227 - -- xcatdb/P/XMM/PN/eb4 0.02227 - -This astropy table now have only 3 columns and can be manipulated much faster. - -Retrieving data-sets based on their meta-data values ----------------------------------------------------- - -As expressed in the last paragraph of the Getting Started section, we can ask the MOCServer to do some filtering tasks for us -at the server side. The ``meta_data`` parameter of :meth:`~astroquery.mocserver.MOCServerClass.query_region` allows the user to -write an algebraic expression on the meta-datas. Let's query the MOCServer for retrieving what we have done using the -web interface in the Getting Started section i.e. retrieving only the image data-sets that lie in the previously defined cone. - -.. code-block:: python - - >>> MOCServer.query_region(region=cone, - ... fields=['ID', 'dataproduct_type', 'moc_sky_fraction', 'moc_access_url'], - ... meta_data="dataproduct_type=image") -
- moc_access_url ID dataproduct_type moc_sky_fraction - object str48 str5 float64 - ------------------------------------------------------------------------ --------------------------------------- ---------------- ---------------- - http://alasky.u-strasbg.fr/2MASS/H/Moc.fits CDS/P/2MASS/H image 1.0 - http://alasky.u-strasbg.fr/2MASS/J/Moc.fits CDS/P/2MASS/J image 1.0 - http://alasky.u-strasbg.fr/2MASS/K/Moc.fits CDS/P/2MASS/K image 1.0 - http://alasky.u-strasbg.fr/2MASS/Color/Moc.fits CDS/P/2MASS/color image 1.0 - http://alasky.u-strasbg.fr/AKARI-FIS/ColorLSN60/Moc.fits CDS/P/AKARI/FIS/Color image 1.0 - http://alasky.u-strasbg.fr/AKARI-FIS/N160/Moc.fits CDS/P/AKARI/FIS/N160 image 0.9988 - http://alasky.u-strasbg.fr/AKARI-FIS/N60/Moc.fits CDS/P/AKARI/FIS/N60 image 0.9976 - http://alasky.u-strasbg.fr/AKARI-FIS/WideL/Moc.fits CDS/P/AKARI/FIS/WideL image 0.9989 - http://alasky.u-strasbg.fr/AKARI-FIS/WideS/Moc.fits CDS/P/AKARI/FIS/WideS image 0.9976 - -- CDS/P/Ariel/Voyager image 1.0 - http://alasky.u-strasbg.fr/CO/Moc.fits CDS/P/CO image 1.0 - -- CDS/P/Callisto/Voyager-Galileo-simp-1km image 1.0 - -- CDS/P/Charon/NewHorizon-PIA19866 image 1.0 - -- CDS/P/DM/flux-Bp/I/345/gaia2 image 1.0 - -- CDS/P/DM/flux-G/I/345/gaia2 image 1.0 - -- CDS/P/DM/flux-Rp/I/345/gaia2 image 1.0 - -- CDS/P/DM/flux-color-Rp-G-Bp/I/345/gaia2 image 1.0 - -- CDS/P/DSS2/NIR image 0.9943 - http://alasky.u-strasbg.fr/DSS/DSS2-blue-XJ-S/Moc.fits CDS/P/DSS2/blue image 0.9956 - http://alasky.u-strasbg.fr/DSS/DSSColor/Moc.fits CDS/P/DSS2/color image 1.0 - http://alasky.u-strasbg.fr/DSS/DSS2Merged/Moc.fits CDS/P/DSS2/red image 1.0 - -- CDS/P/Dione/Cassini-PIA12577 image 1.0 - http://alasky.u-strasbg.fr/EGRET/EGRET-dif/EGRET_dif_100-150/Moc.fits CDS/P/EGRET/Dif/100-150 image 1.0 - http://alasky.u-strasbg.fr/EGRET/EGRET-dif/EGRET_dif_1000-2000/Moc.fits CDS/P/EGRET/Dif/1000-2000 image 1.0 - http://alasky.u-strasbg.fr/EGRET/EGRET-dif/EGRET_dif_150-300/Moc.fits CDS/P/EGRET/Dif/150-300 image 1.0 - http://alasky.u-strasbg.fr/EGRET/EGRET-dif/EGRET_dif_2000-4000/Moc.fits CDS/P/EGRET/Dif/2000-4000 image 1.0 - http://alasky.u-strasbg.fr/EGRET/EGRET-dif/EGRET_dif_30-50/Moc.fits CDS/P/EGRET/Dif/30-50 image 1.0 - http://alasky.u-strasbg.fr/EGRET/EGRET-dif/EGRET_dif_300-500/Moc.fits CDS/P/EGRET/Dif/300-500 image 1.0 - http://alasky.u-strasbg.fr/EGRET/EGRET-dif/EGRET_dif_4000-10000/Moc.fits CDS/P/EGRET/Dif/4000-10000 image 1.0 - ... ... ... ... - -- ov-gso/P/DIRBE/ZSMA7 image 1.0 - -- ov-gso/P/DIRBE/ZSMA8 image 1.0 - -- ov-gso/P/DIRBE/ZSMA9 image 1.0 - -- ov-gso/P/DRAO-VillaElisa/21cm/POLQ image 1.0 - -- ov-gso/P/DRAO-VillaElisa/21cm/POLU image 1.0 - -- ov-gso/P/DRAO/22MHz image 0.7283 - -- ov-gso/P/DWINGELOO/820MHz image 0.5723 - -- ov-gso/P/EBHIS image 0.5468 - -- ov-gso/P/GASS+EBHIS image 1.0 - -- ov-gso/P/GAURIBIDANUR image 0.8623 - -- ov-gso/P/IRIS/1 image 1.0 - -- ov-gso/P/IRIS/2 image 1.0 - -- ov-gso/P/IRIS/3 image 1.0 - -- ov-gso/P/IRIS/4 image 1.0 - -- ov-gso/P/LAB image 1.0 - -- ov-gso/P/MAIPU-MU image 0.9635 - -- ov-gso/P/MITEoR image 0.4284 - -- ov-gso/P/RASS image 1.0 - -- ov-gso/P/RASS/EXP image 1.0 - -- ov-gso/P/RASS/HardBand image 1.0 - -- ov-gso/P/RASS/SoftBand image 1.0 - -- ov-gso/P/STOCKERT+VILLAELISA/1420MHz image 1.0 - -- ov-gso/P/VTSS/CONT image 0.181 - -- ov-gso/P/VTSS/Ha image 0.1918 - -- ov-gso/P/WHAM image 1.0 - -- xcatdb/P/XMM/PN/color image 0.08287 - -- xcatdb/P/XMM/PN/eb2 image 0.02227 - -- xcatdb/P/XMM/PN/eb3 image 0.02227 - -- xcatdb/P/XMM/PN/eb4 image 0.02227 - - -Looking at the ``dataproduct_type`` column, all the data-sets seem to be images. We could have been done that using -numpy operations on `astropy.table.Table` objects but here the MOCServer made it for us. - -`This page `_ on the web interface of the MOCServer gives examples of some filtering expressions. - -Alternatively, the method :meth:`~astroquery.mocserver.MOCServerClass.find_datasets` searches data-sets on the whole sky. If you want -to get the MOCs or meta-datas from some specific data-sets this is the method to use. The next example retrieves all the -``moc_access_url`` of the Hubble surveys: - -.. code-block:: python - - >>> MOCServer.find_datasets(meta_data="ID=*HST*", - ... fields=['ID', 'moc_access_url']) -
- moc_access_url ID - object str21 - --------------------------------------------------- --------------------- - -- CDS/P/HST/B - -- CDS/P/HST/CO - http://alasky.unistra.fr/GOODS/GOODSb/Moc.fits CDS/P/HST/GOODS/b - http://alasky.unistra.fr/GOODS/GOODS-color/Moc.fits CDS/P/HST/GOODS/color - http://alasky.unistra.fr/GOODS/GOODSi/Moc.fits CDS/P/HST/GOODS/i - http://alasky.unistra.fr/GOODS/GOODSv/Moc.fits CDS/P/HST/GOODS/v - http://alasky.unistra.fr/GOODS/GOODSz/Moc.fits CDS/P/HST/GOODS/z - -- CDS/P/HST/H - -- CDS/P/HST/H2O - -- CDS/P/HST/Halpha - -- CDS/P/HST/Hbeta - -- CDS/P/HST/I - -- CDS/P/HST/J - -- CDS/P/HST/NII - -- CDS/P/HST/OII - -- CDS/P/HST/OIII - http://alasky.u-strasbg.fr/PHAT/F110W/Moc.fits CDS/P/HST/PHAT/F110W - http://alasky.u-strasbg.fr/PHAT/F160W/Moc.fits CDS/P/HST/PHAT/F160W - http://alasky.u-strasbg.fr/PHAT/F275W/Moc.fits CDS/P/HST/PHAT/F275W - http://alasky.u-strasbg.fr/PHAT/F336W/Moc.fits CDS/P/HST/PHAT/F336W - http://alasky.u-strasbg.fr/PHAT/F475W/Moc.fits CDS/P/HST/PHAT/F475W - http://alasky.u-strasbg.fr/PHAT/F814W/Moc.fits CDS/P/HST/PHAT/F814W - -- CDS/P/HST/Palpha - -- CDS/P/HST/Palpha_c - -- CDS/P/HST/R - -- CDS/P/HST/SDSSg - -- CDS/P/HST/SDSSr - -- CDS/P/HST/SDSSz - -- CDS/P/HST/SIII - -- CDS/P/HST/U - -- CDS/P/HST/UV - -- CDS/P/HST/V - -- CDS/P/HST/Y - -- CDS/P/HST/other - -- CDS/P/HST/wideUV - -- CDS/P/HST/wideV - -- ESAVO/P/HST/ACS-blue - -- ESAVO/P/HST/FOC - -- ESAVO/P/HST/NICMOS - -- ESAVO/P/HST/WFC3 - -- ESAVO/P/HST/WFPC - -- ESAVO/P/HST/WFPC2 - -Misc ----- - -Limiting the number of returned data-sets -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + >>> from astropy import coordinates + >>> from regions import CircleSkyRegion + >>> from astroquery.mocserver import MOCServer + >>> center = coordinates.SkyCoord(10.8, 32.2, unit='deg') + >>> radius = coordinates.Angle(0.5, unit='deg') + >>> cone = CircleSkyRegion(center, radius) + >>> MOCServer.query_region(region=cone, + ... intersect="enclosed", + ... fields=['ID', 'moc_sky_fraction']) # doctest: +IGNORE_OUTPUT +
+ ID moc_sky_fraction + str49 float64 + CDS/I/220/out 0.9697 + CDS/I/243/out 1.0 + CDS/I/252/out 0.9993 + CDS/I/254/out 1.0 + CDS/I/255/out 0.9696 + ... ... + ov-gso/P/WHAM 1.0 + simg.de/P/NSNS/DR0_1/halpha 0.6464 + simg.de/P/NSNS/DR0_1/halpha8 0.6464 + simg.de/P/NSNS/DR0_1/hbr8 0.651 + simg.de/P/NSNS/DR0_1/sn-halpha 0.6466 + +We now see in a single glance that the dataset ``CDS/I/220/out`` covers almost all the +sky! + +Limiting the number of returned datasets +---------------------------------------- Another parameter called ``max_rec`` specifies an upper limit for the number of data-sets to be returned: -.. code-block:: python +.. doctest-remote-data:: - >>> MOCServer.query_region(region=cone, max_rec=3) -
- publisher_id obs_description_url moc_access_url ... TIMESTAMP obs_label moc_sky_fraction - str9 str52 str84 ... float64 str8 float64 - ------------ ---------------------------------------------------- ------------------------------------------------------------------------------------ ... --------------- --------- ---------------- - ivo://CDS http://cdsarc.u-strasbg.fr/viz-bin/Cat?B%2Fassocdata http://alasky.unistra.fr/footprints/tables/vizier/B_assocdata_obscore/MOC?nside=2048 ... 1531742659000.0 obscore 0.0588 - ivo://CDS http://cdsarc.u-strasbg.fr/viz-bin/Cat?B%2Fcb http://alasky.unistra.fr/footprints/tables/vizier/B_cb_lmxbdata/MOC?nside=2048 ... 1531742660000.0 lmxbdata 2.066e-06 - ivo://CDS http://cdsarc.u-strasbg.fr/viz-bin/Cat?B%2Fcfht http://alasky.unistra.fr/footprints/tables/vizier/B_cfht_cfht/MOC?nside=2048 ... 1531742660000.0 cfht 0.002134 + >>> from astroquery.mocserver import MOCServer + >>> from mocpy import MOC + >>> MOCServer.query_region(region=MOC.from_string("5/22-24"), max_rec=3) # doctest: +IGNORE_OUTPUT +
+ ID ... dataproduct_type + str24 ... str7 + ------------------------ ... ---------------- + CDS/J/AJ/156/102/table9 ... catalog + CDS/J/ApJS/257/54/table1 ... catalog + CDS/III/39A/catalog ... catalog -This astropy table has only 3 rows although we know more data-sets match the query. It's useful if you do not need -to retrieve all the data-sets matching a query but only a few. Again, the result will come faster from the MOCServer because -this operation is done at the server side. +This astropy has only 3 rows although we know that more datasets match the query. +The result will come faster than requesting all results. -Returning a `~mocpy.MOC` object as a result -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Returning a ``mocpy`` object as a result +---------------------------------------- -Some users might want the union of all the MOCs from the data-sets matching the query. You can get a `mocpy.MOC` object -instead of an `astropy.table.Table` by setting the parameter ``return_moc`` to True. An additional parameter ``max_norder`` -allows the user to set the resolution/precision of the returned MOC that he wants. +I you need want the union of all the MOCs of the datasets matching the query, you can +get the result as a `mocpy.MOC`, `mocpy.TimeMOC`, or `mocpy.STMOC` object instead of an +`astropy.table.Table` by setting the parameter ``return_moc`` to ``smoc``, ``tmoc``, or +``stmoc``. An additional parameter ``max_norder`` allows to set the resolution/precision +of the returned MOC. -As an example, we would like to obtain the union of the spatial coverage of all the Hubble surveys: +As an example, we would like to obtain the union of the space coverage of all the +Hubble surveys: -.. code-block:: python +.. doctest-remote-data:: >>> from mocpy import MOC - >>> # We want to retrieve all the HST surveys i.e. the HST surveys covering any region of the sky. - >>> allsky = CircleSkyRegion(coordinates.SkyCoord(0, 0, unit="deg"), coordinates.Angle(180, unit="deg")) - >>> moc = MOCServer.query_region(region=allsky, - ... # We want a mocpy object instead of an astropy table - ... return_moc=True, - ... # The order of the MOC - ... max_norder=7, - ... # Expression on the ID meta-data - ... meta_data="ID=*HST*") - >>> moc.plot(title="Union of the spatial coverage of all the Hubble surveys.") + >>> import matplotlib.pyplot as plt + >>> from astroquery.mocserver import MOCServer + >>> moc = MOCServer.query_region(return_moc="smoc", + ... max_norder=20, + ... meta_data="ID=*HST*") + +The resulting MOC looks like: -.. image:: ./HST_union.png +.. image:: HST_union.png -Retrieve the `~mocpy.MOC` of a specific data-set -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Retrieve the `~mocpy.STMOC` of a specific dataset +------------------------------------------------- -Finally, if you want to retrieve the MOC of a specific data-set, please consider using the `~astroquery.mocserver.MOCServerClass.find_datasets` -method with the ID of the data-set you want to retrieve the MOC along with the ``return_moc`` parameter set to True. -The last example will show you how to get the MOC (i.e. a `mocpy.MOC` object) of the ``GALEXGR6/AIS/FUV`` survey. +To retrieve the MOC of a specific dataset, we can use +`~astroquery.mocserver.MOCServerClass.query_region`. +This example will show you how to get the space-time MOC (i.e. a `mocpy.STMOC` +object) of the ``GALEXGR6/AIS/FUV`` survey. -.. code-block:: python +.. doctest-remote-data:: >>> from mocpy import MOC - >>> moc_galex= MOCServer.find_datasets(meta_data="ID=CDS/P/GALEXGR6/AIS/FUV", return_moc=True) - >>> moc_galex.plot("MOC associated to CDS/P/GALEXGR6/AIS/FUV.") + >>> from astroquery.mocserver import MOCServer + >>> moc_galex = MOCServer.query_region(meta_data="ID=CDS/P/GALEXGR6/AIS/FUV", + ... return_moc="stmoc", max_norder="s7 t26") + >>> print(f"GALEX GR6 contains data taken from {moc_galex.min_time.iso} to" + ... f" {moc_galex.max_time.iso}.") + GALEX GR6 contains data taken from 2010-03-31 18:02:05.602 to 2010-06-01 18:57:24.787. + + +The ``mocserver`` package can therefore be used in complementarity with `mocpy`_. +We can now retrieve `mocpy.MOC` objects coming from the MOCServer and manipulate them +with `mocpy`_. + +Finding data on a specific solar system body +-------------------------------------------- + +The default value for ``spacesys`` is None. It means that we're looking for data for the +sky and all other possible frames. This can take all the values listed by +`astroquery.mocserver.MOCServerClass.list_spacesys`: + +.. doctest-remote-data:: + + >>> from astroquery.mocserver import MOCServer + >>> MOCServer.list_spacesys() + ['C', 'ariel', 'callisto', 'ceres', 'charon', 'dione', 'earth', 'enceladus', 'equatorial', 'europa', 'galactic', 'ganymede', 'iapetus', 'io', 'jupiter', 'mars', 'mars-pia20284', 'mars-pia24422', 'mars-stimson', 'mercury', 'mimas', 'miranda', 'moon', 'moon-pan1', 'neptune', 'oberon', 'pluto', 'rhea', 'sun', 'tethys', 'titan', 'titania', 'triton', 'umbriel', 'venus'] + +Where the special value ``C`` means any celestial frame (mainly ``equatorial`` and +``galactic``). This can be used in any of the query methods like so: + +.. doctest-remote-data:: -.. image:: ./MOC_GALEXGR6_AIS_FUV.png + >>> from astroquery.mocserver import MOCServer + >>> MOCServer.query_hips(spacesys="ariel") # doctest: +IGNORE_OUTPUT +
+ ID obs_title obs_description dataproduct_type + str19 str13 str65 str5 + ------------------- ------------- ----------------------------------------------------------------- ---------------- + CDS/P/Ariel/Voyager Ariel Voyager Ariel Uranus satellite map mosaicked with Voyager imagery by USGS image -The ``mocserver`` package can therefore be used in complementarity with `mocpy`_. We can now retrieve `mocpy.MOC` objects -coming from the MOCServer and manipulate them in a python session with `mocpy`_. Reference/API ============= From d2e06ab1513fff320155356c6f4d26c6bc5d517d Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 17 Dec 2024 15:57:00 +0100 Subject: [PATCH 5/8] maint: use https in the servers allows to work in the browser with wasm-based python implementations --- astroquery/mocserver/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/astroquery/mocserver/__init__.py b/astroquery/mocserver/__init__.py index 95efa11a0c..9a6c98b2e0 100644 --- a/astroquery/mocserver/__init__.py +++ b/astroquery/mocserver/__init__.py @@ -29,8 +29,7 @@ class Conf(_config.ConfigNamespace): server = _config.ConfigItem( [ "https://alasky.unistra.fr/MocServer/query", - "http://alasky.unistra.fr/MocServer/query", - "http://alaskybis.unistra.fr/MocServer/query", + "https://alaskybis.unistra.fr/MocServer/query", ], "Name of the template_module server to use.", ) From 8c0a3419fcba0b0ad36811b9877c7382284a9784 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 19 Dec 2024 11:36:50 +0100 Subject: [PATCH 6/8] fix: rename C into sky this makes this parameter different from the one the server understands, but it's more consistant with the other possible values for spacesys --- astroquery/mocserver/core.py | 12 ++++++------ astroquery/mocserver/tests/test_mocserver.py | 10 +++++++++- docs/mocserver/mocserver.rst | 8 +++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/astroquery/mocserver/core.py b/astroquery/mocserver/core.py index ccab2a98ca..2eae12e856 100644 --- a/astroquery/mocserver/core.py +++ b/astroquery/mocserver/core.py @@ -75,7 +75,7 @@ def query_region( fields. The complete list of fields can be obtained with `list_fields`. spacesys: str, optional This is the space system on which the coordinates are expressed. Can take - the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + the values ``sky``, ``mars``, ``moon``... The extended list can be printed with `list_spacesys`. Default is None, meaning that the results will have mixed frames. intersect : str, optional @@ -162,7 +162,7 @@ def query_hips( fields. The complete list of fields can be obtained with `list_fields`. spacesys: str, optional This is the space system on which the coordinates are expressed. Can take - the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + the values ``sky``, ``mars``, ``moon``... The extended list can be printed with `list_spacesys`. Default is None, meaning that the results will have mixed frames. region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion`, `mocpy.MOC`, @@ -254,7 +254,7 @@ def find_datasets( fields. The complete list of fields can be obtained with `list_fields`. spacesys: str, optional This is the space system on which the coordinates are expressed. Can take - the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + the values ``sky``, ``mars``, ``moon``... The extended list can be printed with `list_spacesys`. Default is None, meaning that the results will have mixed frames. region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion`, `mocpy.MOC`, @@ -342,7 +342,7 @@ def query_async( fields. The complete list of fields can be obtained with `list_fields`. spacesys: str, optional This is the space system on which the coordinates are expressed. Can take - the values ``C`` (for the sky), ``mars``, ``moon``... The extended list can + the values ``sky``, ``mars``, ``moon``... The extended list can be printed with `list_spacesys`. Default is None, meaning that the results will have mixed frames. region : `regions.CircleSkyRegion`, `regions.PolygonSkyRegion`, `mocpy.MOC`, @@ -521,7 +521,7 @@ def list_spacesys(self): fields=["ID", "hips_frame"], spacesys=None)["hips_frame"])) # `C` is a special case that corresponds to both equatorial and galactic frames - frames.append("C") + frames.append("sky") frames.sort() return frames @@ -553,7 +553,7 @@ def _args_to_payload( "get": "record", "fields": _get_fields(fields, default_fields), "intersect": intersect.replace("encloses", "enclosed"), - "spacesys": spacesys, + "spacesys": "C" if spacesys == "sky" else spacesys, } if region and not isinstance(region, (MOC, STMOC, TimeMOC)): diff --git a/astroquery/mocserver/tests/test_mocserver.py b/astroquery/mocserver/tests/test_mocserver.py index fda74f989c..547a405b8a 100644 --- a/astroquery/mocserver/tests/test_mocserver.py +++ b/astroquery/mocserver/tests/test_mocserver.py @@ -153,7 +153,7 @@ def _mock_list_spacesys(monkeypatch): @pytest.mark.usefixtures("_mock_list_spacesys") def test_list_spacesys(): list_spacesys = MOCServer.list_spacesys() - assert "C" in list_spacesys and "equatorial" in list_spacesys + assert "sky" in list_spacesys and "equatorial" in list_spacesys # --------------------- @@ -224,6 +224,14 @@ def test_return_moc(): assert payload["order"] == "max" +def test_spacesys(): + payload = MOCServer.query_region( + spacesys="sky", meta_data="", return_moc=True, + max_norder=5, get_query_payload=True + ) + assert payload["spacesys"] == "C" + + # ---------------- # Helper functions # ---------------- diff --git a/docs/mocserver/mocserver.rst b/docs/mocserver/mocserver.rst index a95a8821f4..ff508c5385 100644 --- a/docs/mocserver/mocserver.rst +++ b/docs/mocserver/mocserver.rst @@ -362,10 +362,12 @@ sky and all other possible frames. This can take all the values listed by >>> from astroquery.mocserver import MOCServer >>> MOCServer.list_spacesys() - ['C', 'ariel', 'callisto', 'ceres', 'charon', 'dione', 'earth', 'enceladus', 'equatorial', 'europa', 'galactic', 'ganymede', 'iapetus', 'io', 'jupiter', 'mars', 'mars-pia20284', 'mars-pia24422', 'mars-stimson', 'mercury', 'mimas', 'miranda', 'moon', 'moon-pan1', 'neptune', 'oberon', 'pluto', 'rhea', 'sun', 'tethys', 'titan', 'titania', 'triton', 'umbriel', 'venus'] + ['ariel', 'callisto', 'ceres', 'charon', 'dione', 'earth', 'enceladus', 'equatorial', 'europa', 'galactic', 'ganymede', 'iapetus', 'io', 'jupiter', 'mars', 'mars-pia20284', 'mars-pia24422', 'mars-stimson', 'mercury', 'mimas', 'miranda', 'moon', 'moon-pan1', 'neptune', 'oberon', 'pluto', 'rhea', 'sky', 'sun', 'tethys', 'titan', 'titania', 'triton', 'umbriel', 'venus'] -Where the special value ``C`` means any celestial frame (mainly ``equatorial`` and -``galactic``). This can be used in any of the query methods like so: +Where the special value ``sky`` means any celestial frame (mainly ``equatorial`` and +``galactic``). + +The ``spacesys`` can be used in any of the query methods like so: .. doctest-remote-data:: From 27334cc396bacb1abf310f4325662cda0dcfbffc Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 19 Dec 2024 13:52:24 +0100 Subject: [PATCH 7/8] docs: various docs typos/precisions --- docs/mocserver/mocserver.rst | 57 ++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/docs/mocserver/mocserver.rst b/docs/mocserver/mocserver.rst index ff508c5385..04a59af375 100644 --- a/docs/mocserver/mocserver.rst +++ b/docs/mocserver/mocserver.rst @@ -20,6 +20,8 @@ The space component maps the sky with the HEALPix sky tessellation to represent regions on the sky by hierarchically grouped HEALPix cells. It other words, a Spatial MOC is a set of HEALPix cells at different orders. +The idea is the same for Time MOCs. The time axis is split into time cells. + For those wanting to know more about MOCs, please refer to `the MOC 2.0 specification document `_. @@ -32,8 +34,8 @@ What's the MOC Server? The MOC Server is a service of astronomical resources organized by spatial and/or temporal coverages following the Space and Time MOC specification. In the MOC Server, there a few tens of thousands of astronomical collections. -They each have and identifier ``ID`` and a set of properties that describe their content. -This is a practical way of finding datasets with criteria on time and space. +They each have an identifier ``ID`` and a set of properties that describe their content. +This is a practical way of finding datasets with criteria on time and/or space. The meta-data properties are freely assigned by each publisher. You can get the list of properties with their frequency of usage and examples example with @@ -67,7 +69,7 @@ Querying with a region The MOCServer is optimized to return the datasets having at least one source lying in a specific sky region (or time interval). -The regions can be provided either as astropy-regions from the ``region`` python library, +The regions can be provided either as astropy-regions from the ``regions`` python library, or as an accepted MOC type (`mocpy.TimeMOC`, `mocpy.MOC`, `~mocpy.STMOC`). The frequency MOCs are not yet available. @@ -75,7 +77,8 @@ Performing a query on a cone region ----------------------------------- Let's get the datasets for which all the data is comprised in a cone (this is -what the ``enclosed`` option means for intersect). +what the ``enclosed`` option means for intersect). We also restrict our search to +datasets describing the sky (with ``spacesys=sky``). .. doctest-remote-data:: @@ -85,7 +88,7 @@ what the ``enclosed`` option means for intersect). >>> center = coordinates.SkyCoord(10.8, 32.2, unit='deg') >>> radius = coordinates.Angle(0.5, unit='deg') >>> cone = CircleSkyRegion(center, radius) - >>> MOCServer.query_region(region=cone, intersect="enclosed", spacesys="C") # doctest: +IGNORE_OUTPUT + >>> MOCServer.query_region(region=cone, intersect="enclosed", spacesys="sky") # doctest: +IGNORE_OUTPUT
ID ... str49 ... @@ -112,7 +115,8 @@ what the ``enclosed`` option means for intersect). wfau.roe.ac.uk/UHSDR1/J ... You can also use this method with `regions.PolygonSkyRegion`, `mocpy.MOC`, `mocpy.TimeMOC`, -and `mocpy.STMOC`. +and `mocpy.STMOC`. Not providing the region parameter means that the search is done on +the whole sky (or the whole planet, if we chose a different ``spacesys``). Querying by meta-data ===================== @@ -161,13 +165,15 @@ Let's add a criteria to get only images from the previous query: wfau.roe.ac.uk/P/UHSDR1/J image 0.3083 -Looking at the ``dataproduct_type`` column, all the datasets are indeed images. +Looking at the ``dataproduct_type`` column, all the datasets are indeed images. There +are a few less results than when we did not apply this additional criteria. -`This page `_ on the web interface of the -MOCServer gives examples of some filtering expressions. +Other examples for filtering expressions can be found on the `help page of the MOC Server +web interface `_. -Alternatively, you can search on the whole sky by ommitting the region parameter. -The next example retrieves all the ``moc_access_url`` of the Hubble surveys: +Let's do a search on the whole sky by omitting the region parameter. +The next example retrieves all the ``moc_access_url`` of datasets having ``HST`` in +their identifier. These correspond to the Hubble surveys: .. doctest-remote-data:: @@ -205,7 +211,7 @@ Query for HiPS surveys The MOCServer contains an extensive list of HiPS, for images and catalogs. These progressive surveys can be displayed in applications such as Aladin or ESASky. The `astroquery.mocserver.MOCServerClass.query_hips` method allows to find these HiPS. -It accepts the same parameters (``region`` and ``meta_data`` for example as the other) +It accepts the same parameters (``region`` and ``meta_data`` for example) as the other methods. The only difference is that the output will only contain HiPS data. .. doctest-remote-data:: @@ -300,13 +306,13 @@ Another parameter called ``max_rec`` specifies an upper limit for the number of CDS/J/ApJS/257/54/table1 ... catalog CDS/III/39A/catalog ... catalog -This astropy has only 3 rows although we know that more datasets match the query. +This result has only 3 rows although we know that more datasets match the query. The result will come faster than requesting all results. Returning a ``mocpy`` object as a result ---------------------------------------- -I you need want the union of all the MOCs of the datasets matching the query, you can +If you want the union of all the MOCs of the datasets matching the query, you can get the result as a `mocpy.MOC`, `mocpy.TimeMOC`, or `mocpy.STMOC` object instead of an `astropy.table.Table` by setting the parameter ``return_moc`` to ``smoc``, ``tmoc``, or ``stmoc``. An additional parameter ``max_norder`` allows to set the resolution/precision @@ -346,16 +352,17 @@ object) of the ``GALEXGR6/AIS/FUV`` survey. ... f" {moc_galex.max_time.iso}.") GALEX GR6 contains data taken from 2010-03-31 18:02:05.602 to 2010-06-01 18:57:24.787. - -The ``mocserver`` package can therefore be used in complementarity with `mocpy`_. -We can now retrieve `mocpy.MOC` objects coming from the MOCServer and manipulate them -with `mocpy`_. +.. note:: + Note that for Space-Time MOCs the ``max_norder`` parameter is not an integer but a + string that contains the information about both the spatial order and the time order. + Here, we requested a spatial order of 7 (roughly 27') and a time order of 26 (roughly + 9 hours). Finding data on a specific solar system body -------------------------------------------- The default value for ``spacesys`` is None. It means that we're looking for data for the -sky and all other possible frames. This can take all the values listed by +sky and all other possible frames. This parameter can take all the values listed by `astroquery.mocserver.MOCServerClass.list_spacesys`: .. doctest-remote-data:: @@ -367,17 +374,17 @@ sky and all other possible frames. This can take all the values listed by Where the special value ``sky`` means any celestial frame (mainly ``equatorial`` and ``galactic``). -The ``spacesys`` can be used in any of the query methods like so: +The ``spacesys`` parameter can be used in any of the query methods like so: .. doctest-remote-data:: >>> from astroquery.mocserver import MOCServer - >>> MOCServer.query_hips(spacesys="ariel") # doctest: +IGNORE_OUTPUT + >>> MOCServer.query_hips(spacesys="ariel")
- ID obs_title obs_description dataproduct_type - str19 str13 str65 str5 - ------------------- ------------- ----------------------------------------------------------------- ---------------- - CDS/P/Ariel/Voyager Ariel Voyager Ariel Uranus satellite map mosaicked with Voyager imagery by USGS image + ID obs_title ... dataproduct_type + str19 str13 ... str5 + ------------------- ------------- ... ---------------- + CDS/P/Ariel/Voyager Ariel Voyager ... image Reference/API From 804c94a38b823b08c0dec32780b90e8b0183903b Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 19 Dec 2024 14:03:21 +0100 Subject: [PATCH 8/8] maint: add deprecation warning on find_datasets that can be replaced by query_region --- astroquery/mocserver/core.py | 3 +++ astroquery/mocserver/tests/test_mocserver.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/astroquery/mocserver/core.py b/astroquery/mocserver/core.py index 2eae12e856..a9bd2a7608 100644 --- a/astroquery/mocserver/core.py +++ b/astroquery/mocserver/core.py @@ -10,6 +10,7 @@ from astropy import units as u from astropy.table import Table +from astropy.utils import deprecated try: from mocpy import MOC, TimeMOC, STMOC @@ -222,6 +223,8 @@ def query_hips( cache=cache, ) + @deprecated(since="v0.4.8", + alternative="query_region") def find_datasets( self, meta_data, *, diff --git a/astroquery/mocserver/tests/test_mocserver.py b/astroquery/mocserver/tests/test_mocserver.py index 547a405b8a..5318a21917 100644 --- a/astroquery/mocserver/tests/test_mocserver.py +++ b/astroquery/mocserver/tests/test_mocserver.py @@ -7,6 +7,7 @@ from astropy import coordinates from astropy.io.votable import parse_single_table from astropy.table import Table +from astropy.utils.exceptions import AstropyDeprecationWarning try: from mocpy import MOC, STMOC, TimeMOC, FrequencyMOC @@ -270,5 +271,6 @@ def test_cast_to_float(): def test_find_datasets(): # find datasets is useless as it does the same than query region - old = MOCServer.find_datasets(meta_data="ID=*Euclid*", get_query_payload=True) + with pytest.warns(AstropyDeprecationWarning, match="The find_datasets function *"): + old = MOCServer.find_datasets(meta_data="ID=*Euclid*", get_query_payload=True) assert old == MOCServer.query_region(meta_data="ID=*Euclid*", get_query_payload=True)