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

6
API from
7

8
 http://irsa.ipac.caltech.edu/ibe/
9
"""
10 1
from __future__ import print_function, division
11

12 1
import os
13 1
import webbrowser
14 1
from bs4 import BeautifulSoup
15

16 1
import astropy.coordinates as coord
17 1
from astropy.table import Table
18 1
import six
19

20 1
from ..exceptions import InvalidQueryError
21 1
from ..query import BaseQuery
22 1
from ..utils import commons
23 1
from . import conf
24

25 1
__all__ = ['Ibe', 'IbeClass']
26

27

28 1
class IbeClass(BaseQuery):
29 1
    URL = conf.server
30 1
    MISSION = conf.mission
31 1
    DATASET = conf.dataset
32 1
    TABLE = conf.table
33 1
    TIMEOUT = conf.timeout
34

35 1
    def query_region(
36
            self, coordinate=None, where=None, mission=None, dataset=None,
37
            table=None, columns=None, width=None, height=None,
38
            intersect='OVERLAPS', most_centered=False):
39
        """
40
        For certain missions, this function can be used to search for image and
41
        catalog files based on a point, a box (bounded by great circles) and/or
42
        an SQL-like ``where`` clause.
43

44
        If ``coordinates`` is specified, then the optional ``width`` and
45
        ``height`` arguments control the width and height of the search
46
        box. If neither ``width`` nor ``height`` are provided, then the
47
        search area is a point. If only one of ``width`` or ``height`` are
48
        specified, then the search area is a square with that side length
49
        centered at the coordinate.
50

51
        Parameters
52
        ----------
53
        coordinate : str, `astropy.coordinates` object
54
            Gives the position of the center of the box if performing a box
55
            search. If it is a string, then it must be a valid argument to
56
            `~astropy.coordinates.SkyCoord`. Required if ``where`` is absent.
57
        where : str
58
            SQL-like query string. Required if ``coordinates`` is absent.
59
        mission : str
60
            The mission to be used (if not the default mission).
61
        dataset : str
62
            The dataset to be used (if not the default dataset).
63
        table : str
64
            The table to be queried (if not the default table).
65
        columns : str, list
66
            A space-separated string or a list of strings of the names of the
67
            columns to return.
68
        width : str or `~astropy.units.Quantity` object
69
            Width of the search box if ``coordinates`` is present.
70

71
            The string must be parsable by `~astropy.coordinates.Angle`. The
72
            appropriate `~astropy.units.Quantity` object from `astropy.units`
73
            may also be used.
74
        height : str, `~astropy.units.Quantity` object
75
            Height of the search box if ``coordinates`` is present.
76

77
            The string must be parsable by `~astropy.coordinates.Angle`. The
78
            appropriate `~astropy.units.Quantity` object from `astropy.units`
79
            may also be used.
80
        intersect : ``'COVERS'``, ``'ENCLOSED'``, ``'CENTER'``, ``'OVERLAPS'``
81
            Spatial relationship between search box and image footprint.
82

83
            ``'COVERS'``: X must completely contain S. Equivalent to
84
            ``'CENTER'`` and ``'OVERLAPS'`` if S is a point.
85

86
            ``'ENCLOSED'``: S must completely contain X. If S is a point, the
87
            query will always return an empty image table.
88

89
            ``'CENTER'``: X must contain the center of S. If S is a point, this
90
            is equivalent to ``'COVERS'`` and ``'OVERLAPS'``.
91

92
            ``'OVERLAPS'``: The intersection of S and X is non-empty. If S is a
93
            point, this is equivalent to ``'CENTER'`` and ``'COVERS'``.
94
        most_centered : bool
95
            If True, then only the most centered image is returned.
96

97
        Returns
98
        -------
99
        table : `~astropy.table.Table`
100
            A table containing the results of the query
101
        """
102 1
        response = self.query_region_async(
103
            coordinate=coordinate, where=where, mission=mission,
104
            dataset=dataset, table=table, columns=columns, width=width,
105
            height=height, intersect=intersect, most_centered=most_centered)
106

107
        # Raise exception, if request failed
108 1
        response.raise_for_status()
109

110 1
        return Table.read(response.text, format='ipac', guess=False)
111

112 1
    def query_region_sia(self, coordinate=None, mission=None,
113
                         dataset=None, table=None, width=None,
114
                         height=None, intersect='OVERLAPS',
115
                         most_centered=False):
116
        """
117
        Query using simple image access protocol.  See ``query_region`` for
118
        details.  The returned table will include a list of URLs.
119
        """
120 0
        response = self.query_region_async(
121
            coordinate=coordinate, mission=mission,
122
            dataset=dataset, table=table, width=width,
123
            height=height, intersect=intersect, most_centered=most_centered,
124
            action='sia')
125

126
        # Raise exception, if request failed
127 0
        response.raise_for_status()
128

129 0
        return commons.parse_votable(
130
            response.text).get_first_table().to_table()
131

132 1
    def query_region_async(
133
            self, coordinate=None, where=None, mission=None, dataset=None,
134
            table=None, columns=None, width=None, height=None,
135
            action='search',
136
            intersect='OVERLAPS', most_centered=False):
137
        """
138
        For certain missions, this function can be used to search for image and
139
        catalog files based on a point, a box (bounded by great circles) and/or
140
        an SQL-like ``where`` clause.
141

142
        If ``coordinates`` is specified, then the optional ``width`` and
143
        ``height`` arguments control the width and height of the search
144
        box. If neither ``width`` nor ``height`` are provided, then the
145
        search area is a point. If only one of ``width`` or ``height`` are
146
        specified, then the search area is a square with that side length
147
        centered at the coordinate.
148

149
        Parameters
150
        ----------
151
        coordinate : str, `astropy.coordinates` object
152
            Gives the position of the center of the box if performing a box
153
            search. If it is a string, then it must be a valid argument to
154
            `~astropy.coordinates.SkyCoord`. Required if ``where`` is absent.
155
        where : str
156
            SQL-like query string. Required if ``coordinates`` is absent.
157
        mission : str
158
            The mission to be used (if not the default mission).
159
        dataset : str
160
            The dataset to be used (if not the default dataset).
161
        table : str
162
            The table to be queried (if not the default table).
163
        columns : str, list
164
            A space-separated string or a list of strings of the names of the
165
            columns to return.
166
        width : str or `~astropy.units.Quantity` object
167
            Width of the search box if ``coordinates`` is present.
168

169
            The string must be parsable by `~astropy.coordinates.Angle`. The
170
            appropriate `~astropy.units.Quantity` object from `astropy.units`
171
            may also be used.
172
        height : str, `~astropy.units.Quantity` object
173
            Height of the search box if ``coordinates`` is present.
174

175
            The string must be parsable by `~astropy.coordinates.Angle`. The
176
            appropriate `~astropy.units.Quantity` object from `astropy.units`
177
            may also be used.
178
        intersect : ``'COVERS'``, ``'ENCLOSED'``, ``'CENTER'``, ``'OVERLAPS'``
179
            Spatial relationship between search box and image footprint.
180

181
            ``'COVERS'``: X must completely contain S. Equivalent to
182
            ``'CENTER'`` and ``'OVERLAPS'`` if S is a point.
183

184
            ``'ENCLOSED'``: S must completely contain X. If S is a point, the
185
            query will always return an empty image table.
186

187
            ``'CENTER'``: X must contain the center of S. If S is a point, this
188
            is equivalent to ``'COVERS'`` and ``'OVERLAPS'``.
189

190
            ``'OVERLAPS'``: The intersection of S and X is non-empty. If S is a
191
            point, this is equivalent to ``'CENTER'`` and ``'COVERS'``.
192
        most_centered : bool
193
            If True, then only the most centered image is returned.
194
        action : ``'search'``, ``'data'``, or ``'sia'``
195
            The action to perform at the server.  The default is ``'search'``,
196
            which returns a table of the available data.  ``'data'`` requires
197
            advanced path construction that is not yet supported. ``'sia'``
198
            provides access to the 'simple image access' IVOA protocol
199

200
        Returns
201
        -------
202
        response : `~requests.Response`
203
            The HTTP response returned from the service
204
        """
205

206 1
        if coordinate is None and where is None:
207 0
            raise InvalidQueryError(
208
                'At least one of `coordinate` or `where` is required')
209

210 1
        intersect = intersect.upper()
211 1
        if intersect not in ('COVERS', 'ENCLOSED', 'CENTER', 'OVERLAPS'):
212 0
            raise InvalidQueryError(
213
                "Invalid value for `intersects` " +
214
                "(must be 'COVERS', 'ENCLOSED', 'CENTER', or 'OVERLAPS')")
215

216 1
        if action not in ('sia', 'data', 'search'):
217 0
            raise InvalidQueryError("Valid actions are: sia, data, search.")
218 1
        if action == 'data':
219 0
            raise NotImplementedError(
220
                "The action='data' option is a placeholder for future " +
221
                "functionality.")
222

223 1
        args = {
224
            'INTERSECT': intersect
225
        }
226

227
        # Note: in IBE, if 'mcen' argument is present, it is true.
228
        # If absent, it is false.
229 1
        if most_centered:
230 0
            args['mcen'] = '1'
231

232 1
        if coordinate is not None:
233 1
            c = commons.parse_coordinates(coordinate).transform_to(coord.ICRS)
234 1
            args['POS'] = '{0},{1}'.format(c.ra.deg, c.dec.deg)
235 1
            if width and height:
236 0
                args['SIZE'] = '{0},{1}'.format(
237
                    coord.Angle(width).value,
238
                    coord.Angle(height).value)
239 1
            elif width or height:
240 0
                args['SIZE'] = str(coord.Angle(width or height).value)
241

242 1
        if where:
243 1
            args['where'] = where
244

245 1
        if columns:
246 0
            if isinstance(columns, six.string_types):
247 0
                columns = columns.split()
248 0
            args['columns'] = ','.join(columns)
249

250 1
        url = "{URL}{action}/{mission}/{dataset}/{table}".format(
251
                URL=self.URL,
252
                action=action,
253
                mission=mission or self.MISSION,
254
                dataset=dataset or self.DATASET,
255
                table=table or self.TABLE)
256

257 1
        return self._request('GET', url, args, timeout=self.TIMEOUT)
258

259 1
    def list_missions(self, cache=True):
260
        """
261
        Return a list of the available missions
262

263
        Parameters
264
        ----------
265
        cache : bool
266
            Cache the query result
267
        """
268 1
        if hasattr(self, '_missions') and cache:
269
            # extra level caching to avoid redoing the BeautifulSoup parsing
270
            # unnecessarily
271 1
            missions = self._missions
272
        else:
273 1
            url = self.URL + "search/"
274 1
            response = self._request('GET', url, timeout=self.TIMEOUT,
275
                                     cache=cache)
276

277 1
            root = BeautifulSoup(response.text)
278 1
            links = root.findAll('a')
279 1
            missions = [os.path.basename(a.attrs['href']) for a in links]
280 1
            self._missions = missions
281

282 1
        return missions
283

284 1
    def list_datasets(self, mission=None, cache=True):
285
        """
286
        For a given mission, list the available datasets
287

288
        Parameters
289
        ----------
290
        mission : str
291
            A mission name.  Must be one of the valid missions from
292
            `~astroquery.ibe.IbeClass.list_missions`.  Defaults to the
293
            configured Mission
294
        cache : bool
295
            Cache the query result
296

297
        Returns
298
        -------
299
        datasets : list
300
            A list of dataset names
301
        """
302 1
        if mission is None:
303 0
            mission = self.MISSION
304 1
        if mission not in self.list_missions():
305 0
            raise ValueError("Invalid mission specified: {0}."
306
                             "Must be one of: {1}"
307
                             .format(mission, self.list_missions()))
308

309 1
        url = "{URL}search/{mission}/".format(URL=self.URL, mission=mission)
310 1
        response = self._request('GET', url, timeout=self.TIMEOUT,
311
                                 cache=cache)
312

313 1
        root = BeautifulSoup(response.text)
314 1
        links = root.findAll('a')
315 1
        datasets = [a.text for a in links
316
                    if a.attrs['href'].count('/') >= 4  # shown as '..'; ignore
317
                    ]
318

319 1
        return datasets
320

321 1
    def list_tables(self, mission=None, dataset=None, cache=True):
322
        """
323
        For a given mission and dataset (see
324
        `~.astroquery.ibe.IbeClass.list_missions`,
325
        `~astroquery.ibe.IbeClass.list_datasets`), return the list of valid
326
        table names to query.
327

328
        Parameters
329
        ----------
330
        mission : str
331
            A mission name.  Must be one of the valid missions from
332
            `~.astroquery.ibe.IbeClass.list_missions`.  Defaults to the
333
            configured Mission
334
        dataset : str
335
            A dataset name.  Must be one of the valid dataset from
336
            ``list_datsets(mission)``.  Defaults to the configured Dataset
337
        cache : bool
338
            Cache the query result
339

340
        Returns
341
        -------
342
        tables : list
343
            A list of table names
344
        """
345 1
        if mission is None:
346 0
            mission = self.MISSION
347 1
        if dataset is None:
348 0
            dataset = self.DATASET
349

350 1
        if mission not in self.list_missions():
351 0
            raise ValueError("Invalid mission specified: {0}."
352
                             "Must be one of: {1}"
353
                             .format(mission, self.list_missions()))
354

355 1
        if dataset not in self.list_datasets(mission, cache=cache):
356 0
            raise ValueError("Invalid dataset {0} specified for mission {1}."
357
                             "Must be one of: {2}"
358
                             .format(dataset, mission,
359
                                     self.list_datasets(mission, cache=True)))
360

361 1
        url = "{URL}search/{mission}/{dataset}/".format(URL=self.URL,
362
                                                        mission=mission,
363
                                                        dataset=dataset)
364 1
        response = self._request('GET', url, timeout=self.TIMEOUT,
365
                                 cache=cache)
366

367 1
        root = BeautifulSoup(response.text)
368 1
        return [tr.find('td').string for tr in root.findAll('tr')[1:]]
369

370
    # Unfortunately, the URL construction for each data set is different, and
371
    # they're not obviously accessible via API
372
    # def get_data(self, **kwargs):
373
    #    return self.query_region_async(retrieve_data=True, **kwargs)
374

375 1
    def show_docs(self, mission=None, dataset=None, table=None):
376
        """
377
        Open the documentation for a given table in a web browser.
378

379
        Parameters
380
        ----------
381
        mission : str
382
            The mission to be used (if not the default mission).
383
        dataset : str
384
            The dataset to be used (if not the default dataset).
385
        table : str
386
            The table to be queried (if not the default table).
387
        """
388

389 0
        url = "{URL}docs/{mission}/{dataset}/{table}".format(
390
                URL=self.URL,
391
                mission=mission or self.MISSION,
392
                dataset=dataset or self.DATASET,
393
                table=table or self.TABLE)
394

395 0
        return webbrowser.open(url)
396

397 1
    def get_columns(self, mission=None, dataset=None, table=None):
398
        """
399
        Get the schema for a given table.
400

401
        Parameters
402
        ----------
403
        mission : str
404
            The mission to be used (if not the default mission).
405
        dataset : str
406
            The dataset to be used (if not the default dataset).
407
        table : str
408
            The table to be queried (if not the default table).
409

410
        Returns
411
        -------
412
        table : `~astropy.table.Table`
413
            A table containing a description of the columns
414
        """
415

416 1
        url = "{URL}search/{mission}/{dataset}/{table}".format(
417
                URL=self.URL,
418
                mission=mission or self.MISSION,
419
                dataset=dataset or self.DATASET,
420
                table=table or self.TABLE)
421

422 1
        response = self._request(
423
            'GET', url, {'FORMAT': 'METADATA'}, timeout=self.TIMEOUT)
424

425
        # Raise exception, if request failed
426 1
        response.raise_for_status()
427

428 1
        return Table.read(response.text, format='ipac', guess=False)
429

430

431 1
Ibe = IbeClass()

Read our documentation on viewing source code .

Loading