# Licensed under a 3-clause BSD style license - see LICENSE.rst
from collections import OrderedDict
import warnings
from io import BytesIO
from astropy.table import QTable, MaskedColumn
from astropy.io import ascii
from astropy.time import Time
from astropy.io.votable import parse
import astropy.units as u
from astropy.coordinates import SkyCoord, Angle
from ..query import BaseQuery
from ..utils import async_to_sync, commons
from . import conf
__all__ = ['Miriade', 'MiriadeClass', 'Skybot', 'SkybotClass']
[docs]@async_to_sync
class MiriadeClass(BaseQuery):
"""
A class for querying the
`IMCCE/Miriade <http://vo.imcce.fr/webservices/miriade/>`_ service.
"""
_query_uri = None # uri used in query
_get_raw_response = False
@property
def uri(self):
"""
URI used in query to service.
"""
return self._query_uri
[docs] def get_ephemerides_async(self, targetname, objtype='asteroid',
epoch=None, epoch_step='1d', epoch_nsteps=1,
location=500, coordtype=1,
timescale='UTC',
planetary_theory='INPOP',
ephtype=1,
refplane='equator',
elements='ASTORB',
radial_velocity=False,
get_query_payload=False,
get_raw_response=False, cache=True):
"""
Query the
`IMCCE Miriade <http://vo.imcce.fr/webservices/miriade/>`_
`ephemcc <http://vo.imcce.fr/webservices/miriade/?ephemcc>`_
service.
Parameters
----------
targetname : str
Name of the target to be queried.
objtype : str, optional
Type of the object to be queried. Available are: ``'asteroid'``,
``'comet'``, ``'dwarf planet'``, ``'planet'``, ``'satellite'``.
Default: ``'asteroid'``
epoch : `~astropy.time.Time` object, float, str,``None``, optional
Start epoch of the query. If a float is provided, it is
expected to be a Julian Date; if a str is provided, it is
expected to be an iso date of the form
``'YYYY-MM-DD HH-MM-SS'``. If ``None`` is provided, the
current date and time are used as epoch. Default: ``None``
epoch_step : str, optional
Step size for ephemerides calculation. Must consist of a decimal
number followed by a single character: (d)ays, (h)ours,
(m)inutes or (s)econds. Default: ``'1d'``
epoch_nsteps : int, optional
Number of increments of ``epoch_step`` starting from ``epoch``
for which ephemerides are calculated. Maximum number of steps
is 5000. Default: 1
location : str, optional
Location of the observer on Earth as a code or a set of
coordinates. See the
`Miriade manual <http://vo.imcce.fr/webservices/miriade/?documentation#field_7>`_
for details. Default: geocentric location (``'500'``)
coordtype : int, optional
Type of coordinates to be calculated: ``1``: spherical, ``2``:
rectangular, ``3``: local coordinates (azimuth and elevation),
``4``: hour angle coordinates, ``5``: dedicated to observation,
``6``: dedicated to AO observation. Default: ``1``
timescale : str, optional
The time scale used in the computation of the ephemerides:
``'UTC'`` or ``'TT'``. Default: ``'UTC'``
planetary_theory : str, optional
Planetary ephemerides set to be utilized in the calculations:
``'INPOP'``, ``'DE405'``, ``'DE406'``. Default: ``'INPOP'``
ephtype : int, optional
Type of ephemerides to be calculated: ``1``: astrometric J2000,
``2``: apparent of the date, ``3``: mean of the date,
``4``: mean J2000, Default: ``1``
refplane : str, optional
Reference plane: ``'equator'`` or ``'ecliptic'``. Default:
``'equator'``
elements : str, optional
Set of osculating elements to be used in the calculations:
``'ASTORB'`` or ``'MPCORB'``. Default: ``'ASTORB'``
radial_velocity : bool, optional
Calculate additional information on the target's radial velocity.
Default: ``False``
get_query_payload : bool, optional
When set to ``True`` the method returns the HTTP request
parameters as a dict, default: ``False``
get_raw_response : bool, optional
Return raw data as obtained by Miriade without parsing the
data into a table, default: ``False``
cache : bool, optional
If ``True`` the query will be cached. Default: ``True``
Notes
-----
The following parameters can be queried using this function. Note
that different ``coordtype`` setting provide different sets of
parameters; number in parentheses denote which ``coordtype``
settings include the parameters.
+------------------+-----------------------------------------------+
| Column Name | Definition |
+==================+===============================================+
| ``target`` | Target name (str, 1, 2, 3, 4, 5, 6 ) |
+------------------+-----------------------------------------------+
| ``epoch`` | Ephemerides epoch (JD, float, 1, 2, 3, 4, 5, |
| | 6) |
+------------------+-----------------------------------------------+
| ``RA`` | Target RA at ``ephtype`` (deg, float, 1) |
+------------------+-----------------------------------------------+
| ``DEC`` | Target declination at ``ephtype`` (deg, |
| | float, 1, 4, 5) |
+------------------+-----------------------------------------------+
| ``RAJ2000`` | Target RA at J2000 (deg, float, 5, 6) |
+------------------+-----------------------------------------------+
| ``DECJ2000`` | Target declination at J2000 (deg, float, 5, 6)|
+------------------+-----------------------------------------------+
| ``AZ`` | Target azimuth (deg, float, 3, 5) |
+------------------+-----------------------------------------------+
| ``EL`` | Target elevation (deg, float, 3, 5) |
+------------------+-----------------------------------------------+
| ``delta`` | Distance from observer (au, float, 1, 2, 3, |
| | 4, 5, 6) |
+------------------+-----------------------------------------------+
| ``delta_rate`` | Rate in observer distance (km/s, float, |
| | 1, 5, 6) |
+------------------+-----------------------------------------------+
| ``V`` | Apparent visual magnitude (mag, float, 1, 2, |
| | 3, 4, 5, 6) |
+------------------+-----------------------------------------------+
| ``alpha`` | Solar phase angle (deg, 1, 2, 3, 4, 5, 6) |
+------------------+-----------------------------------------------+
| ``elong`` | Solar elongation angle (deg, 1, 2, 3, 4, 5, 6)|
+------------------+-----------------------------------------------+
| ``RAcosD_rate`` | Rate of motion in RA * cos(DEC) (arcsec/min, |
| | float, 1, 5, 6) |
+------------------+-----------------------------------------------+
| ``DEC_rate`` | Rate of motion in DEC (arcsec/min, float, 1, |
| | 5, 6) |
+------------------+-----------------------------------------------+
| ``x`` | X position state vector (au, float, 2) |
+------------------+-----------------------------------------------+
| ``y`` | Y position state vector (au, float, 2) |
+------------------+-----------------------------------------------+
| ``z`` | Z position state vector (au, float, 2) |
+------------------+-----------------------------------------------+
| ``vx`` | X velocity state vector (au/d, float, 2) |
+------------------+-----------------------------------------------+
| ``vy`` | Y velocity state vector (au/d, float, 2) |
+------------------+-----------------------------------------------+
| ``vz`` | Z velocity state vector (au/d, float, 2) |
+------------------+-----------------------------------------------+
| ``rv`` | Radial velocity (km/s, float, 2) |
+------------------+-----------------------------------------------+
| ``heldist`` | Target heliocentric distance (au, float, 2, |
| | 5, 6) |
+------------------+-----------------------------------------------+
| ``x_h`` | X heliocentric position vector (au, float, 2) |
+------------------+-----------------------------------------------+
| ``y_h`` | Y heliocentric position vector (au, float, 2) |
+------------------+-----------------------------------------------+
| ``z_h`` | Z heliocentric position vector (au, float, 2) |
+------------------+-----------------------------------------------+
| ``vx_h`` | X heliocentric vel. vector (au/d, float, 2) |
+------------------+-----------------------------------------------+
| ``vy_h`` | Y heliocentric vel. vector (au/d, float, 2) |
+------------------+-----------------------------------------------+
| ``vz_h`` | Z heliocentric vel. vector (au/d, float, 2) |
+------------------+-----------------------------------------------+
| ``hourangle`` | Target hour angle (deg, float, 4, 5) |
+------------------+-----------------------------------------------+
| ``siderealtime`` | Local sidereal time (hr, float, 5, 6) |
+------------------+-----------------------------------------------+
| ``refraction`` | Atmospheric refraction (arcsec, float, 5, 6) |
+------------------+-----------------------------------------------+
| ``airmass`` | Target airmass (float, 5, 6) |
+------------------+-----------------------------------------------+
| ``posunc`` | Positional uncertainty (arcsec, float, 5, 6) |
+------------------+-----------------------------------------------+
Examples
--------
>>> from astroquery.imcce import Miriade
>>> from astropy.time import Time
>>> epoch = Time('2019-01-01', format='iso')
>>> Miriade.get_ephemerides('3552', epoch=epoch) # doctest: +SKIP
<Table masked=True length=1>
target epoch RA ... DEC_rate delta_rate
d deg ... arcs / min km / s
bytes20 float64 float64 ... float64 float64
----------- -------------------- ------------------ ... ---------- ------------
Don Quixote 2458484.5 16.105294999999998 ... -0.25244 31.4752734
"""
URL = conf.ephemcc_server
TIMEOUT = conf.timeout
if isinstance(epoch, (int, float)):
epoch = Time(epoch, format='jd')
elif isinstance(epoch, str):
epoch = Time(epoch, format='iso')
elif epoch is None:
epoch = Time.now()
request_payload = OrderedDict([
('-name', targetname),
('-type', objtype[0].upper()+objtype[1:]),
('-ep', str(epoch.jd)),
('-step', epoch_step),
('-nbd', epoch_nsteps),
('-observer', location),
('-output', '--jul'),
('-tscale', timescale),
('-theory', planetary_theory),
('-teph', ephtype),
('-tcoor', coordtype),
('-rplane', {'equator': 1, 'ecliptic': 2}[refplane]),
('-oscelem', elements),
('-mime', 'votable')])
if radial_velocity:
request_payload['-output'] += ',--rv'
if get_query_payload:
return request_payload
# query and parse
response = self._request('GET', URL, params=request_payload,
timeout=TIMEOUT, cache=cache)
self._query_uri = response.url
self._get_raw_response = get_raw_response
return response
def _parse_result(self, response, verbose=None):
"""
Parser for Miriade request results
"""
response_txt = response.text
if self._get_raw_response:
return response_txt
# intercept error messages
for line in response_txt.split('\n'):
if 'name="QUERY_STATUS" value="ERROR"' in line:
errmsg = line[line.find('ERROR:>')+9:
line.find('</vot:INFO>')]
raise RuntimeError(errmsg)
# convert votable to table
commons.suppress_vo_warnings()
voraw = BytesIO(response.content)
votable = parse(voraw)
data = votable.get_first_table().to_table()
# modify table columns
data['epoch'].unit = u.d
if 'ra' in data.columns:
data['ra'] = Angle(data['ra'], unit=u.hourangle).deg*u.deg
data.rename_column('ra', 'RA')
if 'dec' in data.columns:
data['dec'] = Angle(data['dec'], unit=u.deg).deg*u.deg
data.rename_column('dec', 'DEC')
if 'raJ2000' in data.columns and 'decJ2000' in data.columns:
data['raJ2000'] = Angle(
data['raJ2000'], unit=u.hourangle).deg*u.deg
data['decJ2000'] = Angle(data['decJ2000'], unit=u.deg).deg*u.deg
data.rename_column('raJ2000', 'RAJ2000')
data.rename_column('decJ2000', 'DECJ2000')
if all([p in data.columns for p in ['xp', 'yp', 'zp']]):
data.rename_column('xp', 'vx')
data.rename_column('yp', 'vy')
data.rename_column('zp', 'vz')
if all([str(data[p].unit) == 'au/day'
for p in ['vx', 'vy', 'vz']]):
data['vx'].unit = u.au/u.day
data['vy'].unit = u.au/u.day
data['vz'].unit = u.au/u.day
if all([p in data.columns for p in ['xh', 'yh', 'zh']]):
data.rename_column('xh', 'x_h')
data.rename_column('yh', 'y_h')
data.rename_column('zh', 'z_h')
if all([p in data.columns for p in ['xhp', 'yhp', 'zhp']]):
data.rename_column('xhp', 'vx_h')
data.rename_column('yhp', 'vy_h')
data.rename_column('zhp', 'vz_h')
if all([str(data[p].unit) == 'au/day'
for p in ['vx_h', 'vy_h', 'vz_h']]):
data['vx_h'].unit = u.au/u.day
data['vy_h'].unit = u.au/u.day
data['vz_h'].unit = u.au/u.day
if 'distance' in data.columns:
data.rename_column('distance', 'delta')
if 'obsdistance' in data.columns:
data.rename_column('obsdistance', 'delta')
if 'heliodistance' in data.columns:
data.rename_column('heliodistance', 'heldist')
if 'azimut' in data.columns and 'elevation' in data.columns:
data['azimut'] = Angle(data['azimut'], unit=u.deg).deg * u.deg
data['elevation'] = Angle(
data['elevation'], unit=u.deg).deg * u.deg
data.rename_column('azimut', 'AZ')
data.rename_column('elevation', 'EL')
if 'mv' in data.columns:
data.rename_column('mv', 'V')
data['V'].unit = u.mag
if 'phase' in data.columns:
data.rename_column('phase', 'alpha')
if 'elongation' in data.columns:
data.rename_column('elongation', 'elong')
if 'dracosdec' in data.columns:
data.rename_column('dracosdec', 'RAcosD_rate')
if 'ddec' in data.columns:
data.rename_column('ddec', 'DEC_rate')
if 'dist_dot' in data.columns:
data.rename_column('dist_dot', 'delta_rate')
if 'lst' in data.columns:
data.rename_column('lst', 'siderealtime')
if 'hourangle' in data.columns:
data['hourangle'] = Angle(data['hourangle'],
unit=u.hourangle).deg * u.deg
if 'aeu' in data.columns:
data.rename_column('aeu', 'posunc')
return data
Miriade = MiriadeClass()
[docs]@async_to_sync
class SkybotClass(BaseQuery):
"""A class for querying the `IMCCE SkyBoT
<http://vo.imcce.fr/webservices/skybot>`_ service.
"""
_uri = None # query uri
_get_raw_response = False
@property
def uri(self):
"""
URI used in query to service.
Examples
--------
>>> from astroquery.imcce import Skybot
>>> from astropy.coordinates import SkyCoord
>>> import astropy.units as u
>>> from astropy.time import Time
>>> field = SkyCoord(1*u.deg, 1*u.deg)
>>> epoch = Time('2019-05-29 21:42', format='iso')
>>> skybot = Skybot()
>>> obj = skybot.cone_search(field, 0.1*u.deg, epoch) # doctest: +SKIP
>>> skybot.uri # doctest: +SKIP
'http://vo.imcce.fr/webservices/skybot/skybotconesearch_query.php?-ra=1.0&-dec=1.0&-rd=0.1&-ep=2458633.404166667&-loc=500&-filter=120.0&-objFilter=111&-refsys=EQJ2000&-output=all&-mime=text'
"""
return self._uri
[docs] def cone_search_async(self,
coo,
rad,
epoch,
location='500',
position_error=120,
find_planets=True,
find_asteroids=True,
find_comets=True,
get_query_payload=False,
get_raw_response=False,
cache=True):
"""
This method queries the IMCCE
`SkyBoT <http://vo.imcce.fr/webservices/skybot/?conesearch>`_
cone search service and produces a `~astropy.table.QTable` object
containing all Solar System bodies that might be in the cone
defined by the cone center coordinates and epoch provided.
Parameters
----------
coo : `~astropy.coordinates.SkyCoord` object or tuple
Center coordinates of the search cone in ICRS coordinates. If
provided as tuple, the input is excepted as (right ascension
in degrees, declination in degrees).
rad : `~astropy.units.Quantity` object or float
Radius of the search cone. If no units are provided (input as
float), degrees are assumed. The maximum search radius is 10
degrees; if this
maximum radius is exceeded, it will be clipped and a warning
will be provided to the user.
epoch : `~astropy.time.Time` object, float, or string
Epoch of search process in UT. If provided as float, it is
interpreted as Julian Date, if provided as string, it is
interpreted as date in the form ``'YYYY-MM-DD HH-MM-SS'``.
location : int or str, optional
Location of the observer on Earth as defined in the official
`list of IAU codes
<https://www.minorplanetcenter.net/iau/lists/ObsCodes.html>`_.
Default: geocentric location (``'500'``)
position_error : `~astropy.units.Quantity` or float, optional
Maximum positional error for targets to be queried. If no
unit is provided, arcseconds are assumed. Maximum positional
error is 120 arcseconds, larger values are clipped and warning
will be provided to the user. Default: 120 arcseconds
find_planets : boolean, optional
If ``True``, planets will be included in the search. Default:
``True``
find_asteroids : boolean, optional
If ``True``, asteroids will be included in the search. Default:
``True``
find_comets : boolean, optional
If ``True``, comets will be included in the search. Default:
``True``
get_query_payload : boolean, optional
Returns the query payload only and performs no query.
Default: ``False``
get_raw_response : boolean, optional
Returns the raw response as provided by the IMCCE server instead
of the parsed output. Default: ``False``
cache : boolean, optional
Cache this specfific query so it might be retrieved faster in
the future. Default: ``True``
Notes
-----
The following parameters are queried from the SkyBoT service:
+------------------+-----------------------------------------------+
| Column Name | Definition |
+==================+===============================================+
| ``'Number'`` | Target Number (``-1`` if none provided, int) |
+------------------+-----------------------------------------------+
| ``'Name'`` | Target Name (str) |
+------------------+-----------------------------------------------+
| ``'RA'`` | Target RA (J2000, deg, float) |
+------------------+-----------------------------------------------+
| ``'DEC'`` | Target declination (J2000, deg, float) |
+------------------+-----------------------------------------------+
| ``'Type'`` | Target dynamical/physical type (str) |
+------------------+-----------------------------------------------+
| ``'V'`` | Target apparent brightness (V-band, mag, |
| | float) |
+------------------+-----------------------------------------------+
| ``'posunc'`` | Positional uncertainty (arcsec, float) |
+------------------+-----------------------------------------------+
| ``'centerdist'`` | Angular distance of target from cone center |
| | (arcsec, float) |
+------------------+-----------------------------------------------+
| ``'RA_rate'`` | RA rate of motion (arcsec/hr, float) |
+------------------+-----------------------------------------------+
| ``'DEC_rate'`` | Declination rate of motion (arcsec/hr, float) |
+------------------+-----------------------------------------------+
| ``'geodist'`` | Geocentric distance of target (au, float) |
+------------------+-----------------------------------------------+
| ``'heliodist'`` | Heliocentric distance of target (au, float) |
+------------------+-----------------------------------------------+
| ``'alpha'`` | Solar phase angle (deg, float) |
+------------------+-----------------------------------------------+
| ``'elong'`` | Solar elongation angle (deg, float) |
+------------------+-----------------------------------------------+
| ``'x'`` | Target equatorial vector x (au, float) |
+------------------+-----------------------------------------------+
| ``'y'`` | Target equatorial vector y (au, float) |
+------------------+-----------------------------------------------+
| ``'z'`` | Target equatorial vector z (au, float) |
+------------------+-----------------------------------------------+
| ``'vx'`` | Target velocity vector x (au/d, float) |
+------------------+-----------------------------------------------+
| ``'vy'`` | Target velocity vector y (au/d, float) |
+------------------+-----------------------------------------------+
| ``'vz'`` | Target velocity vector z (au/d, float) |
+------------------+-----------------------------------------------+
| ``'epoch'`` | Ephemerides epoch (JD, float) |
+------------------+-----------------------------------------------+
Examples
--------
>>> from astroquery.imcce import Skybot
>>> from astropy.coordinates import SkyCoord
>>> from astropy.time import Time
>>> import astropy.units as u
>>> field = SkyCoord(1*u.deg, 1*u.deg)
>>> epoch = Time('2019-05-29 21:42', format='iso')
>>> Skybot.cone_search(field, 0.1*u.deg, epoch) # doctest: +SKIP
<QTable length=2>
Number Name RA ... vy vz epoch
deg ... AU / d AU / d d
int64 str9 float64 ... float64 float64 float64
------ --------- ------------------ ... ----------- ----------- ---------
180969 2005 MM39 1.0019566666666666 ... 0.00977568 0.003022634 2458630.0
107804 2001 FV58 1.0765258333333332 ... 0.006551369 0.003846177 2458630.0
"""
URL = conf.skybot_server
TIMEOUT = conf.timeout
# check for types and units
if not isinstance(coo, SkyCoord):
coo = SkyCoord(ra=coo[0]*u.degree,
dec=coo[1]*u.degree, frame='icrs')
if isinstance(rad, u.Quantity):
rad = Angle(rad.value, unit=rad.unit)
if not isinstance(rad, u.Quantity):
rad = Angle(rad, unit=u.degree)
if rad > Angle(10, unit=u.degree):
rad = Angle(10, unit=u.degree)
warnings.warn('search cone radius set to maximum: 10 deg',
UserWarning)
if isinstance(epoch, (int, float)):
epoch = Time(epoch, format='jd')
elif isinstance(epoch, str):
epoch = Time(epoch, format='iso')
if isinstance(position_error, u.Quantity):
position_error = Angle(position_error.value,
unit=position_error.unit)
if not isinstance(position_error, u.Quantity):
position_error = Angle(position_error, unit=u.arcsec)
if position_error > Angle(120, unit=u.arcsec):
position_error = Angle(120, unit=u.arcsec)
warnings.warn('positional error set to maximum: 120 arcsec',
UserWarning)
# assemble payload
request_payload = {'-ra': coo.ra.deg,
'-dec': coo.dec.deg,
'-rd': rad.deg,
'-ep': str(epoch.jd),
'-loc': str(location),
'-filter': position_error.arcsec,
'-objFilter':
str(int(find_asteroids)) +
str(int(find_planets)) +
str(int(find_comets)),
'-refsys': 'EQJ2000',
'-output': 'all',
'-mime': 'text'}
# check for diagnostic flags
if get_query_payload:
return request_payload
self._get_raw_response = get_raw_response
response = self._request(method='GET', url=URL,
params=request_payload,
timeout=TIMEOUT, cache=cache)
self._uri = response.url
return response
def _parse_result(self, response, verbose=False):
"""
internal wrapper to parse queries
"""
if self._get_raw_response:
return response.text
# intercept error messages
response_txt = response.text.split('\n')[2:-1]
if len(response_txt) < 3 and len(response_txt[-1].split('|')) < 21:
raise RuntimeError(response_txt[-1])
names = response_txt[0].replace('# ', '').strip().split(' | ')
results = ascii.read(response_txt[1:], delimiter='|',
names=names, fast_reader=False)
results = QTable(results)
# convert coordinates to degrees
coo = SkyCoord(ra=results['RA(h)'], dec=results['DE(deg)'],
unit=(u.hourangle, u.deg), frame='icrs')
results['RA(h)'] = coo.ra.deg
results['DE(deg)'] = coo.dec.deg
colnames = results.columns[:]
for fieldname in colnames:
# apply field name change
results.rename_column(fieldname, conf.field_names[fieldname])
# apply unit, if available
if conf.field_names[fieldname] in conf.field_units:
results[conf.field_names[fieldname]].unit = conf.field_units[
conf.field_names[fieldname]]
# convert object numbers to int
# unnumbered asteroids return as non numeric values ('-')
# this is treated as defaulting to 0, and masking the entry
unnumbered_mask = [not str(x).isdigit() for x in results['Number']]
numbers = [int(x) if str(x).isdigit()
else 0
for x in results['Number']]
asteroid_number_col = MaskedColumn(numbers, name='Number',
mask=unnumbered_mask)
results.replace_column('Number', asteroid_number_col)
return results
Skybot = SkybotClass()