astropy / astroquery
1
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2 1
"""
3
IRSA
4
====
5

6
API from
7

8
 https://irsa.ipac.caltech.edu/applications/Gator/GatorAid/irsa/catsearch.html
9

10
The URL of the IRSA catalog query service, CatQuery, is
11

12
 https://irsa.ipac.caltech.edu/cgi-bin/Gator/nph-query
13

14
The service accepts the following keywords, which are analogous to the search
15
fields on the Gator search form:
16

17

18
spatial     Required    Type of spatial query: Cone, Box, Polygon, and NONE
19

20
polygon                 Convex polygon of ra dec pairs, separated by comma(,)
21
                        Required if spatial=polygon
22

23
radius                  Cone search radius
24
                        Optional if spatial=Cone, otherwise ignore it
25
                        (default 10 arcsec)
26

27
radunits                Units of a Cone search: arcsec, arcmin, deg.
28
                        Optional if spatial=Cone
29
                        (default='arcsec')
30

31
size                    Width of a box in arcsec
32
                        Required if spatial=Box.
33

34
objstr                  Target name or coordinate of the center of a spatial
35
                        search center. Target names must be resolved by
36
                        SIMBAD or NED.
37

38
                        Required only when spatial=Cone or spatial=Box.
39

40
                        Examples: 'M31'
41
                                  '00 42 44.3 -41 16 08'
42
                                  '00h42m44.3s -41d16m08s'
43

44
catalog     Required    Catalog name in the IRSA database management system.
45

46
selcols     Optional    Target column list with value separated by a comma(,)
47

48
                        The input list always overwrites default selections
49
                        defined by a data dictionary. Full lists of columns
50
                        can be found at the IRSA catalogs website, e.g.
51
                        https://irsa.ipac.caltech.edu/cgi-bin/Gator/nph-dd?catalog=allsky_4band_p1bs_psd
52
                        To access the full list of columns, press
53
                        the "Long Form" button at the top of the Columns
54
                        table.
55

56
outfmt      Optional    Defines query's output format.
57
                        6 - returns a program interface in XML
58
                        3 - returns a VO Table (XML)
59
                        2 - returns SVC message
60
                        1 - returns an ASCII table
61
                        0 - returns Gator Status Page in HTML (default)
62

63
desc        Optional    Short description of a specific catalog, which will
64
                        appear in the result page.
65

66
order       Optional    Results ordered by this column.
67

68
constraint  Optional    User defined query constraint(s)
69
                        Note: The constraint should follow SQL syntax.
70

71
onlist      Optional    1 - catalog is visible through Gator web interface
72
                        (default)
73

74
                        0 - catalog has been ingested into IRSA but not yet
75
                        visible through web interface.
76

77
                        This parameter will generally only be set to 0 when
78
                        users are supporting testing and evaluation of new
79
                        catalogs at IRSA's request.
80

81
If onlist=0, the following parameters are required:
82

83
    server              Symbolic DataBase Management Server (DBMS) name
84

85
    database            Name of Database.
86

87
    ddfile              The data dictionary file is used to get column
88
                        information for a specific catalog.
89

90
    selcols             Target column list with value separated by a comma(,)
91

92
                        The input list always overwrites default selections
93
                        defined by a data dictionary.
94

95
    outrows             Number of rows retrieved from database.
96

97
                        The retrieved row number outrows is always less than or
98
                        equal to available to be retrieved rows under the same
99
                        constraints.
100
"""
101 1
from __future__ import print_function, division
102

103 1
import warnings
104 1
import xml.etree.ElementTree as tree
105

106 1
import six
107 1
import astropy.units as u
108 1
import astropy.coordinates as coord
109 1
import astropy.io.votable as votable
110

111 1
from ..query import BaseQuery
112 1
from ..utils import commons
113 1
from . import conf
114 1
from ..exceptions import TableParseError, NoResultsWarning
115

116 1
__all__ = ['Irsa', 'IrsaClass']
117

118

119 1
class IrsaClass(BaseQuery):
120 1
    IRSA_URL = conf.server
121 1
    GATOR_LIST_URL = conf.gator_list_catalogs
122 1
    TIMEOUT = conf.timeout
123 1
    ROW_LIMIT = conf.row_limit
124

125 1
    def query_region(self, coordinates=None, catalog=None, spatial='Cone',
126
                     radius=10 * u.arcsec, width=None, polygon=None,
127
                     get_query_payload=False, verbose=False, selcols=None):
128
        """
129
        This function can be used to perform either cone, box, polygon or
130
        all-sky search in the catalogs hosted by the NASA/IPAC Infrared
131
        Science Archive (IRSA).
132

133
        Parameters
134
        ----------
135
        coordinates : str, `astropy.coordinates` object
136
            Gives the position of the center of the cone or box if
137
            performing a cone or box search. The string can give coordinates
138
            in various coordinate systems, or the name of a source that will
139
            be resolved on the server (see `here
140
            <https://irsa.ipac.caltech.edu/search_help.html>`_ for more
141
            details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional
142
            if spatial is ``'Polygon'``.
143
        catalog : str
144
            The catalog to be used. To list the available catalogs, use
145
            :meth:`~astroquery.irsa.IrsaClass.print_catalogs`.
146
        spatial : str
147
            Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and
148
            ``'All-Sky'``. If missing then defaults to ``'Cone'``.
149
        radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``]
150
            The string must be parsable by `~astropy.coordinates.Angle`. The
151
            appropriate `~astropy.units.Quantity` object from
152
            `astropy.units` may also be used. Defaults to 10 arcsec.
153
        width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.]
154

155
            The string must be parsable by `~astropy.coordinates.Angle`. The
156
            appropriate `~astropy.units.Quantity` object from `astropy.units`
157
            may also be used.
158
        polygon : list, [Required for spatial is ``'Polygon'``]
159
            A list of ``(ra, dec)`` pairs (as tuples), in decimal degrees,
160
            outlining the polygon to search in. It can also be a list of
161
            `astropy.coordinates` object or strings that can be parsed by
162
            `astropy.coordinates.ICRS`.
163
        get_query_payload : bool, optional
164
            If `True` then returns the dictionary sent as the HTTP request.
165
            Defaults to `False`.
166
        verbose : bool, optional.
167
            If `True` then displays warnings when the returned VOTable does not
168
            conform to the standard. Defaults to `False`.
169
        selcols : str, optional
170
            Target column list with value separated by a comma(,)
171

172

173
        Returns
174
        -------
175
        table : `~astropy.table.Table`
176
            A table containing the results of the query
177
        """
178 1
        response = self.query_region_async(coordinates, catalog=catalog,
179
                                           spatial=spatial, radius=radius,
180
                                           width=width, polygon=polygon,
181
                                           get_query_payload=get_query_payload,
182
                                           selcols=selcols)
183 1
        if get_query_payload:
184 0
            return response
185 1
        return self._parse_result(response, verbose=verbose)
186

187 1
    def query_region_async(self, coordinates=None, catalog=None,
188
                           spatial='Cone', radius=10 * u.arcsec, width=None,
189
                           polygon=None, get_query_payload=False,
190
                           selcols=None):
191
        """
192
        This function serves the same purpose as
193
        :meth:`~astroquery.irsa.IrsaClass.query_region`, but returns the raw
194
        HTTP response rather than the results in a `~astropy.table.Table`.
195

196
        Parameters
197
        ----------
198
        coordinates : str, `astropy.coordinates` object
199
            Gives the position of the center of the cone or box if
200
            performing a cone or box search. The string can give coordinates
201
            in various coordinate systems, or the name of a source that will
202
            be resolved on the server (see `here
203
            <https://irsa.ipac.caltech.edu/search_help.html>`_ for more
204
            details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional
205
            if spatial is ``'Polygon'``.
206
        catalog : str
207
            The catalog to be used. To list the available catalogs, use
208
            :meth:`~astroquery.irsa.IrsaClass.print_catalogs`.
209
        spatial : str
210
            Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and
211
            ``'All-Sky'``. If missing then defaults to ``'Cone'``.
212
        radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``]
213
            The string must be parsable by `~astropy.coordinates.Angle`. The
214
            appropriate `~astropy.units.Quantity` object from
215
            `astropy.units` may also be used. Defaults to 10 arcsec.
216
        width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.]
217
            The string must be parsable by `~astropy.coordinates.Angle`. The
218
            appropriate `~astropy.units.Quantity` object from `astropy.units`
219
            may also be used.
220
        polygon : list, [Required for spatial is ``'Polygon'``]
221
            A list of ``(ra, dec)`` pairs (as tuples), in decimal degrees,
222
            outlining the polygon to search in. It can also be a list of
223
            `astropy.coordinates` object or strings that can be parsed by
224
            `astropy.coordinates.ICRS`.
225
        get_query_payload : bool, optional
226
            If `True` then returns the dictionary sent as the HTTP request.
227
            Defaults to `False`.
228
        selcols : str, optional
229
            Target column list with value separated by a comma(,)
230

231
        Returns
232
        -------
233
        response : `requests.Response`
234
            The HTTP response returned from the service
235
        """
236 1
        if catalog is None:
237 0
            raise Exception("Catalog name is required!")
238

239 1
        request_payload = self._args_to_payload(catalog, selcols=selcols)
240 1
        request_payload.update(self._parse_spatial(spatial=spatial,
241
                                                   coordinates=coordinates,
242
                                                   radius=radius, width=width,
243
                                                   polygon=polygon))
244

245 1
        if get_query_payload:
246 1
            return request_payload
247 1
        response = self._request("GET", url=Irsa.IRSA_URL,
248
                                 params=request_payload, timeout=Irsa.TIMEOUT)
249 1
        return response
250

251 1
    def _parse_spatial(self, spatial, coordinates, radius=None, width=None,
252
                       polygon=None):
253
        """
254
        Parse the spatial component of a query
255

256
        Parameters
257
        ----------
258
        spatial : str
259
            The type of spatial query. Must be one of: ``'Cone'``, ``'Box'``,
260
            ``'Polygon'``, and ``'All-Sky'``.
261
        coordinates : str, `astropy.coordinates` object
262
            Gives the position of the center of the cone or box if
263
            performing a cone or box search. The string can give coordinates
264
            in various coordinate systems, or the name of a source that will
265
            be resolved on the server (see `here
266
            <https://irsa.ipac.caltech.edu/search_help.html>`_ for more
267
            details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional
268
            if spatial is ``'Polygon'``.
269
        radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``]
270
            The string must be parsable by `~astropy.coordinates.Angle`. The
271
            appropriate `~astropy.units.Quantity` object from `astropy.units`
272
            may also be used. Defaults to 10 arcsec.
273
        width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.]
274
            The string must be parsable by `~astropy.coordinates.Angle`. The
275
            appropriate `~astropy.units.Quantity` object from `astropy.units`
276
            may also be used.
277
        polygon : list, [Required for spatial is ``'Polygon'``]
278
            A list of ``(ra, dec)`` pairs as tuples of
279
            `astropy.coordinates.Angle`s outlining the polygon to search in.
280
            It can also be a list of `astropy.coordinates` object or strings
281
            that can be parsed by `astropy.coordinates.ICRS`.
282

283
        Returns
284
        -------
285
        payload_dict : dict
286
        """
287

288 1
        request_payload = {}
289

290 1
        if spatial == 'All-Sky':
291 1
            spatial = 'NONE'
292 1
        elif spatial in ['Cone', 'Box']:
293 1
            if not commons._is_coordinate(coordinates):
294 1
                request_payload['objstr'] = coordinates
295
            else:
296 1
                request_payload['objstr'] = _parse_coordinates(coordinates)
297 1
            if spatial == 'Cone':
298 1
                radius = _parse_dimension(radius)
299 1
                request_payload['radius'] = radius.value
300 1
                request_payload['radunits'] = radius.unit.to_string()
301
            else:
302 1
                width = _parse_dimension(width)
303 1
                request_payload['size'] = width.to(u.arcsec).value
304 1
        elif spatial == 'Polygon':
305 1
            if coordinates is not None:
306 1
                if commons._is_coordinate(coordinates):
307 0
                    request_payload['objstr'] = _parse_coordinates(coordinates)
308
                else:
309 1
                    request_payload['objstr'] = coordinates
310 1
            try:
311 1
                coordinates_list = [_parse_coordinates(c) for c in polygon]
312 1
            except (ValueError, TypeError):
313 1
                coordinates_list = [_format_decimal_coords(*_pair_to_deg(pair))
314
                                    for pair in polygon]
315 1
            request_payload['polygon'] = ','.join(coordinates_list)
316
        else:
317 1
            raise ValueError("Unrecognized spatial query type. Must be one of "
318
                             "'Cone', 'Box', 'Polygon', or 'All-Sky'.")
319

320 1
        request_payload['spatial'] = spatial
321

322 1
        return request_payload
323

324 1
    def _args_to_payload(self, catalog, selcols=None):
325
        """
326
        Sets the common parameters for all cgi -queries
327

328
        Parameters
329
        ----------
330
        catalog : str
331
            The name of the catalog to query.
332
        selcols : str, optional
333
            Target column list with value separated by a comma(,)
334

335
        Returns
336
        -------
337
        request_payload : dict
338
        """
339 1
        if selcols is None:
340 1
            selcols = ''
341 1
        request_payload = dict(catalog=catalog,
342
                               outfmt=3,
343
                               outrows=Irsa.ROW_LIMIT,
344
                               selcols=selcols)
345 1
        return request_payload
346

347 1
    def _parse_result(self, response, verbose=False):
348
        """
349
        Parses the results form the HTTP response to `~astropy.table.Table`.
350

351
        Parameters
352
        ----------
353
        response : `requests.Response`
354
            The HTTP response object
355
        verbose : bool, optional
356
            Defaults to `False`. When true it will display warnings whenever
357
            the VOtable returned from the Service doesn't conform to the
358
            standard.
359

360
        Returns
361
        -------
362
        table : `~astropy.table.Table`
363
        """
364 1
        if not verbose:
365 1
            commons.suppress_vo_warnings()
366

367 1
        content = response.text
368

369
        # Check if results were returned
370 1
        if 'The catalog is not on the list' in content:
371 0
            raise Exception("Catalog not found")
372

373
        # Check that object name was not malformed
374 1
        if 'Either wrong or missing coordinate/object name' in content:
375 0
            raise Exception("Malformed coordinate/object name")
376

377
        # Check to see that output table size limit hasn't been exceeded
378 1
        if 'Exceeding output table size limit' in content:
379 0
            raise Exception("Exceeded output table size - reduce number "
380
                            "of output columns and/or limit search area")
381

382
        # Check to see that the query engine is working
383 1
        if 'SQLConnect failed' in content:
384 0
            raise Exception("The IRSA server is currently down")
385

386
        # Check that the results are not of length zero
387 1
        if len(content) == 0:
388 0
            raise Exception("The IRSA server sent back an empty reply")
389

390
        # Read it in using the astropy VO table reader
391 1
        try:
392 1
            first_table = votable.parse(six.BytesIO(response.content),
393
                                        pedantic=False).get_first_table()
394 0
        except Exception as ex:
395 0
            self.response = response
396 0
            self.table_parse_error = ex
397 0
            raise TableParseError("Failed to parse IRSA votable! The raw "
398
                                  "response can be found in self.response, "
399
                                  "and the error in self.table_parse_error.")
400

401
        # Convert to astropy.table.Table instance
402 1
        table = first_table.to_table()
403

404
        # Check if table is empty
405 1
        if len(table) == 0:
406 0
            warnings.warn("Query returned no results, so the table will "
407
                          "be empty", NoResultsWarning)
408

409 1
        return table
410

411 1
    def list_catalogs(self):
412
        """
413
        Return a dictionary of the catalogs in the IRSA Gator tool.
414

415
        Returns
416
        -------
417
        catalogs : dict
418
            A dictionary of catalogs where the key indicates the catalog
419
            name to be used in query functions, and the value is the verbose
420
            description of the catalog.
421
        """
422 0
        response = self._request("GET", url=Irsa.GATOR_LIST_URL,
423
                                 params=dict(mode='xml'), timeout=Irsa.TIMEOUT)
424

425 0
        root = tree.fromstring(response.content)
426 0
        catalogs = {}
427 0
        for catalog in root.findall('catalog'):
428 0
            catname = catalog.find('catname').text
429 0
            desc = catalog.find('desc').text
430 0
            catalogs[catname] = desc
431 0
        return catalogs
432

433 1
    def print_catalogs(self):
434
        """
435
        Display a table of the catalogs in the IRSA Gator tool.
436
        """
437 0
        catalogs = self.list_catalogs()
438 0
        for catname in catalogs:
439 0
            print("{:30s}  {:s}".format(catname, catalogs[catname]))
440

441

442 1
Irsa = IrsaClass()
443

444

445 1
def _parse_coordinates(coordinates):
446
    # borrowed from commons.parse_coordinates as from_name wasn't required in
447
    # this case
448 1
    if isinstance(coordinates, six.string_types):
449 1
        try:
450 1
            c = coord.SkyCoord(coordinates, frame='icrs')
451 1
            warnings.warn("Coordinate string is being interpreted as an "
452
                          "ICRS coordinate.")
453 0
        except u.UnitsError as ex:
454 0
            warnings.warn("Only ICRS coordinates can be entered as strings\n"
455
                          "For other systems please use the appropriate "
456
                          "astropy.coordinates object")
457 0
            raise ex
458 1
    elif isinstance(coordinates, commons.CoordClasses):
459 1
        c = coordinates
460
    else:
461 1
        raise TypeError("Argument cannot be parsed as a coordinate")
462 1
    c_icrs = c.transform_to(coord.ICRS)
463 1
    formatted_coords = _format_decimal_coords(c_icrs.ra.degree,
464
                                              c_icrs.dec.degree)
465 1
    return formatted_coords
466

467

468 1
def _pair_to_deg(pair):
469
    """
470
    Turn a pair of floats, Angles, or Quantities into pairs of float
471
    degrees
472
    """
473

474
    # unpack
475 1
    lon, lat = pair
476

477 1
    if hasattr(lon, 'degree') and hasattr(lat, 'degree'):
478 0
        pair = (lon.degree, lat.degree)
479 1
    elif hasattr(lon, 'to') and hasattr(lat, 'to'):
480 1
        pair = [lon, lat]
481 1
        for ii, ang in enumerate((lon, lat)):
482 1
            if ang.unit.is_equivalent(u.degree):
483 1
                pair[ii] = ang.to(u.degree).value
484
    else:
485 0
        warnings.warn("Polygon endpoints are being interpreted as "
486
                      "RA/Dec pairs specified in decimal degree units.")
487 1
    return tuple(pair)
488

489

490 1
def _format_decimal_coords(ra, dec):
491
    """
492
    Print *decimal degree* RA/Dec values in an IPAC-parseable form
493
    """
494 1
    return '{0} {1:+}'.format(ra, dec)
495

496

497 1
def _parse_dimension(dim):
498 1
    if (isinstance(dim, u.Quantity) and dim.unit in u.deg.find_equivalent_units()):
499 1
        if dim.unit not in ['arcsec', 'arcmin', 'deg']:
500 1
            dim = dim.to(u.degree)
501
    # otherwise must be an Angle or be specified in hours...
502
    else:
503 1
        try:
504 1
            new_dim = coord.Angle(dim)
505 1
            dim = u.Quantity(new_dim.degree, u.Unit('degree'))
506 0
        except (u.UnitsError, coord.errors.UnitsError, AttributeError):
507 0
            raise u.UnitsError("Dimension not in proper units")
508 1
    return dim

Read our documentation on viewing source code .

Loading