diff --git a/astroquery/esasky/__init__.py b/astroquery/esasky/__init__.py
new file mode 100644
index 0000000000..9cd769f256
--- /dev/null
+++ b/astroquery/esasky/__init__.py
@@ -0,0 +1,25 @@
+# Licensed under a 3-clause BSD style license - see LICENSE.rst
+from astropy import config as _config
+
+
+class Conf(_config.ConfigNamespace):
+ """
+ Configuration parameters for `astroquery.esasky`.
+ """
+ urlBase = _config.ConfigItem(
+ 'http://sky.esa.int/esasky-tap',
+ 'ESASky base URL')
+
+ timeout = _config.ConfigItem(
+ 1000,
+ 'Time limit for connecting to template_module server.')
+
+
+conf = Conf()
+
+from .core import ESASky, ESASkyClass
+
+__all__ = ['ESASky', 'ESASkyClass',
+ 'Conf', 'conf',
+ ]
+
diff --git a/astroquery/esasky/core.py b/astroquery/esasky/core.py
new file mode 100644
index 0000000000..1e85db2236
--- /dev/null
+++ b/astroquery/esasky/core.py
@@ -0,0 +1,762 @@
+# Licensed under a 3-clause BSD style license - see LICENSE.rst
+from __future__ import print_function
+import json
+import os
+import tempfile
+import tarfile
+import sys
+
+from astropy.extern import six
+from astropy.io import fits
+from astropy import log
+import astropy.units
+import astropy.io.votable as votable
+
+from ..query import BaseQuery
+from ..utils import commons
+from ..utils import async_to_sync
+from . import conf
+from ..exceptions import TableParseError
+
+@async_to_sync
+class ESASkyClass(BaseQuery):
+
+ URLbase = conf.urlBase
+ TIMEOUT = conf.timeout
+
+ __FITS_STRING = ".fits"
+ __FTZ_STRING = ".FTZ"
+ __TAR_STRING = ".tar"
+ __ALL_STRING = "all"
+ __CATALOGS_STRING = "catalogs"
+ __OBSERVATIONS_STRING = "observations"
+ __MISSION_STRING = "mission"
+ __TAP_TABLE_STRING = "tapTable"
+ __TAP_NAME_STRING = "tapName"
+ __FILTER_STRING = "filter"
+ __LABEL_STRING = "label"
+ __OBSERVATION_ID_STRING = "observation_id"
+ __METADATA_STRING = "metadata"
+ __MAPS_STRING = "Maps"
+ __PRODUCT_URL_STRING = "product_url"
+ __SOURCE_LIMIT_STRING = "sourceLimit"
+ __POLYGON_NAME_STRING = "polygonNameTapColumn"
+ __POLYGON_RA_STRING = "polygonRaTapColumn"
+ __POLYGON_DEC_STRING = "polygonDecTapColumn"
+ __POS_TAP_STRING = "posTapColumn"
+ __ORDER_BY_STRING = "orderBy"
+ __IS_SURVEY_MISSION_STRING = "isSurveyMission"
+ __ZERO_ARCMIN_STRING = "0 arcmin"
+ __MIN_RADIUS_CATALOG_STRING = "5 arcsec"
+
+ __HERSCHEL_STRING = 'herschel'
+ __HST_STRING = 'hst'
+ __INTEGRAL_STRING = 'integral'
+
+
+ def list_maps(self):
+ """
+ Get a list of the mission names of the available observations in ESASky
+ """
+ return self._json_object_field_to_list(
+ self._get_observation_json(cache = True), self.__MISSION_STRING)
+
+ def list_catalogs(self):
+ """
+ Get a list of the mission names of the available catalogs in ESASky
+ """
+ return self._json_object_field_to_list(
+ self._get_catalogs_json(cache = True), self.__MISSION_STRING)
+
+ def query_object_maps(self, position, missions=__ALL_STRING,
+ get_query_payload=False, cache=True):
+ """
+ This method queries a chosen object or coordinate for all available maps
+ which have observation data on the chosen position. It returns a
+ TableList with all the found maps metadata for the chosen missions
+ and object.
+
+ Parameters
+ ----------
+ position : str or `astropy.coordinates` object
+ Can either be a string of the location, eg 'M51', or the coordinates
+ of the object.
+ missions : string or list, optional
+ Can be either a specific mission or a list of missions (all mission
+ names are found in list_missions()) or 'all' to search in all
+ missions. Defaults to 'all'.
+ get_query_payload : bool, optional
+ When set to True the method returns the HTTP request parameters.
+ Defaults to False.
+ cache : bool, optional
+ When set to True the method will use a cache located at
+ .astropy/astroquery/cache. Defaults to True.
+
+ Returns
+ -------
+ table_list : `astroquery.utils.TableList`
+ Each mission returns a `astroquery.table.Table` with the metadata
+ and observations available for the chosen missions and object.
+ It is structured in a TableList like this:
+ TableList with 8 tables:
+ '0:HERSCHEL' with 8 column(s) and 25 row(s)
+ '1:HST' with 8 column(s) and 735 row(s)
+
+ Examples
+ --------
+ query_object_maps("m101", "all")
+
+ query_object_maps("265.05, 69.0", "Herschel")
+ query_object_maps("265.05, 69.0", ["Herschel", "HST"])
+ """
+ return self.query_region_maps(position, self.__ZERO_ARCMIN_STRING, missions,
+ get_query_payload, cache)
+
+ def query_object_catalogs(self, position, catalogs=__ALL_STRING,
+ get_query_payload=False, cache=True):
+ """
+ This method queries a chosen object or coordinate for all available
+ catalogs and returns a TableList with all the found catalogs metadata
+ for the chosen missions and object. To account for errors in telescope
+ position, the method will look for any sources within a radius of
+ 5 arcsec of the chosen position.
+
+ Parameters
+ ----------
+ position : str or `astropy.coordinates` object
+ Can either be a string of the location, eg 'M51', or the coordinates
+ of the object.
+ catalogs : string or list, optional
+ Can be either a specific catalog or a list of catalogs (all catalog
+ names are found in list_catalogs()) or 'all' to search in all
+ catalogs. Defaults to 'all'.
+ get_query_payload : bool, optional
+ When set to True the method returns the HTTP request parameters.
+ Defaults to False.
+ cache : bool, optional
+ When set to True the method will use a cache located at
+ .astropy/astroquery/cache. Defaults to True.
+ Returns
+ -------
+ table_list : `astroquery.utils.TableList`
+ Each mission returns a `astroquery.table.Table` with the metadata
+ of the catalogs available for the chosen mission and object.
+ It is structured in a TableList like this:
+ TableList with 8 tables:
+ '0:Gaia DR1 TGA' with 8 column(s) and 25 row(s)
+ '1:HSC' with 8 column(s) and 75 row(s)
+
+ Examples
+ --------
+ query_object_catalogs("m101", "all")
+
+ query_object_catalogs("265.05, 69.0", "Gaia DR1 TGA")
+ query_object_catalogs("265.05, 69.0", ["Gaia DR1 TGA", "HSC"])
+ """
+ return self.query_region_catalogs(position, self.__ZERO_ARCMIN_STRING,
+ catalogs, get_query_payload, cache)
+
+ def query_region_maps(self, position, radius, missions=__ALL_STRING,
+ get_query_payload=False, cache=True):
+ """
+ This method queries a chosen region for all available maps and returns a
+ TableList with all the found maps metadata for the chosen missions and
+ region.
+
+ Parameters
+ ----------
+ position : str or `astropy.coordinates` object
+ Can either be a string of the location, eg 'M51', or the coordinates
+ of the object.
+ radius : str or `~astropy.units.Quantity`
+ The radius of a region.
+ missions : string or list, optional
+ Can be either a specific mission or a list of missions (all mission
+ names are found in list_missions()) or 'all' to search in all
+ missions. Defaults to 'all'.
+ get_query_payload : bool, optional
+ When set to True the method returns the HTTP request parameters.
+ Defaults to False.
+ cache : bool, optional
+ When set to True the method will use a cache located at
+ .astropy/astroquery/cache. Defaults to True.
+
+ Returns
+ -------
+ table_list : `astroquery.utils.TableList`
+ Each mission returns a `astroquery.table.Table` with the metadata
+ and observations available for the chosen missions and region.
+ It is structured in a TableList like this:
+ TableList with 8 tables:
+ '0:HERSCHEL' with 8 column(s) and 25 row(s)
+ '1:HST' with 8 column(s) and 735 row(s)
+
+ Examples
+ --------
+ query_region_maps("m101", "14'", "all")
+
+ import astropy.units as u
+ query_region_maps("265.05, 69.0", 14*u.arcmin, "Herschel")
+ query_region_maps("265.05, 69.0", ["Herschel", "HST"])
+ """
+ sanitized_position = self._sanitize_input_position(position)
+ sanitized_radius = self._sanitize_input_radius(radius)
+ sanitized_missions = self._sanitize_input_mission(missions)
+
+ query_result = {}
+
+ coordinates = commons.parse_coordinates(sanitized_position)
+
+ self._store_query_result_maps(query_result, sanitized_missions,
+ coordinates, sanitized_radius,
+ get_query_payload, cache)
+
+ if (get_query_payload):
+ return query_result
+
+ return commons.TableList(query_result)
+
+ def query_region_catalogs(self, position, radius, catalogs=__ALL_STRING,
+ get_query_payload=False, cache=True):
+ """
+ This method queries a chosen region for all available catalogs and
+ returns a TableList with all the found catalogs metadata for the chosen
+ missions and region.
+
+ Parameters
+ ----------
+ position : str or `astropy.coordinates` object
+ Can either be a string of the location, eg 'M51', or the coordinates
+ of the object.
+ radius : str or `~astropy.units.Quantity`
+ The radius of a region.
+ catalogs : string or list, optional
+ Can be either a specific catalog or a list of catalogs (all catalog
+ names are found in list_catalogs()) or 'all' to search in all
+ catalogs. Defaults to 'all'.
+ get_query_payload : bool, optional
+ When set to True the method returns the HTTP request parameters.
+ Defaults to False.
+ cache : bool, optional
+ When set to True the method will use a cache located at
+ .astropy/astroquery/cache. Defaults to True.
+
+ Returns
+ -------
+ table_list : `astroquery.utils.TableList`
+ Each mission returns a `astroquery.table.Table` with the metadata of
+ the catalogs available
+ for the chosen mission and region.
+ It is structured in a TableList like this:
+ TableList with 8 tables:
+ '0:Gaia DR1 TGA' with 8 column(s) and 25 row(s)
+ '1:HSC' with 8 column(s) and 75 row(s)
+
+ Examples
+ --------
+ query_region_catalogs("m101", "14'", "all")
+
+ import astropy.units as u
+ query_region_catalogs("265.05, 69.0", 14*u.arcmin, "Gaia DR1 TGA")
+ query_region_catalogs("265.05, 69.0", 14*u.arcmin, ["Gaia DR1 TGA", "HSC"])
+ """
+ sanitized_position = self._sanitize_input_position(position)
+ sanitized_radius = self._sanitize_input_radius(radius)
+ sanitized_catalogs = self._sanitize_input_catalogs(catalogs)
+
+ coordinates = commons.parse_coordinates(sanitized_position)
+
+ query_result = {}
+
+ self._store_query_result_catalogs(query_result, sanitized_catalogs,
+ coordinates, sanitized_radius,
+ get_query_payload, cache)
+
+ if (get_query_payload):
+ return query_result
+
+ return commons.TableList(query_result)
+
+ def get_maps(self, query_table_list, missions=__ALL_STRING,
+ download_directory=__MAPS_STRING, cache=True):
+ """
+ This method takes the dictionary of missions and metadata as returned by
+ query_region_maps and downloads all maps to the selected folder.
+ The method returns a dictionary which is divided by mission.
+ All mission except Herschel returns a list of HDULists.
+ For Herschel each item in the list is a dictionary where the used
+ filter is the key and the HDUList is the value.
+
+ Parameters
+ ----------
+ query_table_list : `astroquery.utils.TableList`
+ A TableList with all the missions wanted and their respective
+ metadata. Usually the return value of query_region_maps.
+ missions : string or list, optional
+ Can be either a specific mission or a list of missions (all mission
+ names are found in list_missions()) or 'all' to search in all
+ missions. Defaults to 'all'.
+ download_directory : string, optional
+ The folder where all downloaded maps should be stored.
+ Defaults to a folder called 'Maps' in the current working directory.
+ cache : bool, optional
+ When set to True the method will use a cache located at
+ .astropy/astroquery/cache. Defaults to True.
+
+ Returns
+ -------
+ maps : `dict`
+ All mission except Herschel returns a list of HDULists.
+ For Herschel each item in the list is a dictionary where the used
+ filter is the key and the HDUList is the value.
+ It is structured in a dictionary like this:
+ dict: {
+ 'HERSCHEL': [{'70': [HDUList], ' 160': [HDUList]}, {'70': [HDUList], ' 160': [HDUList]}, ...],
+ 'HST':[[HDUList], HDUList], HDUList], HDUList], HDUList], ...],
+ 'XMM-EPIC' : [HDUList], HDUList], HDUList], HDUList], ...]
+ ...
+ }
+
+ Examples
+ --------
+ get_maps(query_region_catalogs("m101", "14'", "all"))
+
+ """
+ sanitized_query_table_list = self._sanitize_input_table_list(query_table_list)
+ sanitized_missions = self._sanitize_input_mission(missions)
+
+ maps = dict()
+
+ for query_mission in sanitized_query_table_list.keys():
+ for mission in sanitized_missions:
+ #INTEGRAL does not have a product url yet.
+ if (query_mission.lower() == self.__INTEGRAL_STRING):
+ print("INTEGRAL does not yet support downloading of "
+ "fits files")
+ break
+ if (query_mission.lower() == mission.lower()):
+ maps[query_mission] = (
+ self._get_maps_for_mission(
+ sanitized_query_table_list[query_mission],
+ query_mission,
+ download_directory,
+ cache))
+ break
+ if (len(sanitized_query_table_list) > 0):
+ log.info("Maps available at %s" %os.path.abspath(download_directory))
+ else:
+ print("No maps found")
+ return maps
+
+ def get_images(self, position, radius=__ZERO_ARCMIN_STRING, missions=__ALL_STRING,
+ download_directory=__MAPS_STRING, cache=True):
+ """
+ This method gets the fits files available for the selected position and
+ mission and downloads all maps to the the selected folder.
+ The method returns a dictionary which is divided by mission.
+ All mission except Herschel returns a list of HDULists.
+ For Herschel each item in the list is a dictionary where the used
+ filter is the key and the HDUList is the value.
+
+ Parameters
+ ----------
+ position : str or `astropy.coordinates` object
+ Can either be a string of the location, eg 'M51', or the coordinates
+ of the object.
+ radius : str or `~astropy.units.Quantity`, optional
+ The radius of a region. Defaults to 0.
+ missions : string or list, optional
+ Can be either a specific mission or a list of missions (all mission
+ names are found in list_missions()) or 'all' to search in all
+ missions. Defaults to 'all'.
+ download_directory : string, optional
+ The folder where all downloaded maps should be stored.
+ Defaults to a folder called 'Maps' in the current working directory.
+ cache : bool, optional
+ When set to True the method will use a cache located at
+ .astropy/astroquery/cache. Defaults to True.
+
+ Returns
+ -------
+ maps : `dict`
+ All mission except Herschel returns a list of HDULists.
+ For Herschel each item in the list is a dictionary where the used
+ filter is the key and the HDUList is the value.
+ It is structured in a dictionary like this:
+ dict: {
+ 'HERSCHEL': [{'70': [HDUList], ' 160': [HDUList]}, {'70': [HDUList], ' 160': [HDUList]}, ...],
+ 'HST':[[HDUList], HDUList], HDUList], HDUList], HDUList], ...],
+ 'XMM-EPIC' : [HDUList], HDUList], HDUList], HDUList], ...]
+ ...
+ }
+
+ Examples
+ --------
+ get_images("m101", "14'", "all")
+
+ """
+ sanitized_position = self._sanitize_input_position(position)
+ sanitized_radius = self._sanitize_input_radius(radius)
+ sanitized_missions = self._sanitize_input_mission(missions)
+
+ maps = dict()
+
+ map_query_result = self.query_region_maps(sanitized_position,
+ sanitized_radius,
+ sanitized_missions,
+ get_query_payload=False,
+ cache=cache)
+
+ for query_mission in map_query_result.keys():
+ #INTEGRAL does not have a product url yet.
+ if (query_mission.lower() == self.__INTEGRAL_STRING):
+ print("INTEGRAL does not yet support downloading of "
+ "fits files")
+ continue
+ maps[query_mission] = (
+ self._get_maps_for_mission(
+ map_query_result[query_mission],
+ query_mission,
+ download_directory,
+ cache))
+
+ print("Maps available at %s" %os.path.abspath(download_directory))
+ return maps
+
+ def _sanitize_input_position(self, position):
+ if (isinstance(position, str) or isinstance(position,
+ commons.CoordClasses)):
+ return position
+ else:
+ raise ValueError("Position must be either a string or "
+ "astropy.coordinates")
+
+ def _sanitize_input_radius(self, radius):
+ if (isinstance(radius, str) or isinstance(radius,
+ astropy.units.Quantity)):
+ return radius
+ else:
+ raise ValueError("Radius must be either a string or "
+ "astropy.units.Quantity")
+
+ def _sanitize_input_mission(self, missions):
+ if (isinstance(missions, list)):
+ return missions
+ if (isinstance(missions, str)):
+ if (missions.lower() == self.__ALL_STRING):
+ return self.list_maps()
+ else:
+ return [missions]
+ raise ValueError("Mission must be either a string or a list of "
+ "missions")
+
+ def _sanitize_input_catalogs(self, catalogs):
+ if (isinstance(catalogs, list)):
+ return catalogs
+ if (isinstance(catalogs, str)):
+ if (catalogs.lower() == self.__ALL_STRING):
+ return self.list_catalogs()
+ else:
+ return [catalogs]
+ raise ValueError("Catalog must be either a string or a list of "
+ "catalogs")
+
+ def _sanitize_input_table_list(self, table_list):
+ if (isinstance(table_list, commons.TableList)):
+ return table_list
+ raise ValueError("Query_table_list must be an astropy.utils.TableList")
+
+ def _get_maps_for_mission(self, maps_table, mission, download_directory, cache):
+ maps = []
+
+ if (len(maps_table[self.__PRODUCT_URL_STRING]) > 0):
+ mission_directory = self._create_mission_directory(mission,
+ download_directory)
+ print("Starting download of %s data. (%d files)"
+ %(mission, len(maps_table[self.__PRODUCT_URL_STRING])))
+ for index in range(len(maps_table)):
+ product_url = (maps_table[self.__PRODUCT_URL_STRING][index]
+ .decode('utf-8'))
+ observation_id = (maps_table[self.__OBSERVATION_ID_STRING][index]
+ .decode('utf-8'))
+ print("Downloading Observation ID: %s from %s"
+ %(observation_id, product_url), end=" ")
+ sys.stdout.flush()
+ directory_path = mission_directory + "/"
+ if (mission.lower() == self.__HERSCHEL_STRING):
+ herschel_filter = (maps_table[self.__FILTER_STRING][index]
+ .decode('utf-8').split(","))
+ maps.append(self._get_herschel_observation(product_url,
+ directory_path,
+ herschel_filter,
+ cache))
+
+ else:
+ response = self._request('GET', product_url, cache = cache)
+ file_name = ""
+ if (product_url.endswith(self.__FITS_STRING)):
+ file_name = (directory_path +
+ self._extract_file_name_from_url(product_url))
+ else:
+ file_name = (directory_path +
+ self._extract_file_name_from_response_header(response.headers))
+
+ fits_data = response.content
+ with open(file_name, 'wb') as fits_file:
+ fits_file.write(fits_data)
+ fits_file.close()
+ maps.append(fits.open(file_name))
+
+ print("[Done]")
+ print("Downloading of %s data complete." %mission)
+
+ return maps
+
+ def _get_herschel_observation(self, product_url, directory_path, filters,
+ cache):
+ observation = dict()
+ tar_file = tempfile.NamedTemporaryFile()
+ response = self._request('GET', product_url, cache = cache)
+ tar_file.write(response.content)
+ with tarfile.open(tar_file.name,'r') as tar:
+ i = 0
+ for member in tar.getmembers():
+ member_name = member.name.lower()
+ if ('hspire' in member_name or 'hpacs' in member_name):
+ tar.extract(member, directory_path)
+ member.name = (
+ self._remove_extra_herschel_directory(member.name,
+ directory_path))
+ observation[filters[i]] = fits.open(directory_path +
+ member.name)
+ i += 1
+ return observation
+
+ def _remove_extra_herschel_directory(self, file_and_directory_name,
+ directory_path):
+ full_directory_path = os.path.abspath(directory_path)
+ file_name = file_and_directory_name[file_and_directory_name.index("/") + 1:]
+ os.renames(os.path.join(full_directory_path, file_and_directory_name),
+ os.path.join(full_directory_path, file_name))
+ return file_name
+
+ def _create_mission_directory(self, mission, download_directory):
+ if (download_directory == self.__MAPS_STRING):
+ mission_directory = self.__MAPS_STRING + "/" + mission
+ else:
+ mission_directory = (download_directory + "/" + self.__MAPS_STRING +
+ "/" + mission)
+ if not os.path.exists(mission_directory):
+ os.makedirs(mission_directory)
+ return mission_directory
+
+ def _extract_file_name_from_response_header(self, headers):
+ content_disposition = headers.get('Content-Disposition')
+ filename_string = "filename="
+ start_index = (content_disposition.index(filename_string) +
+ len(filename_string))
+ if (content_disposition[start_index] == '\"'):
+ start_index += 1
+
+ if (self.__FITS_STRING in content_disposition[start_index : ]):
+ end_index = (
+ content_disposition.index(self.__FITS_STRING, start_index + 1) +
+ len(self.__FITS_STRING))
+ return content_disposition[start_index : end_index]
+ elif (self.__FTZ_STRING in content_disposition[start_index : ]):
+ end_index = (
+ content_disposition.index(self.__FTZ_STRING, start_index + 1) +
+ len(self.__FTZ_STRING))
+ return content_disposition[start_index : end_index]
+ elif (self.__TAR_STRING in content_disposition[start_index : ]):
+ end_index = (
+ content_disposition.index(self.__TAR_STRING, start_index + 1) +
+ len(self.__TAR_STRING))
+ return content_disposition[start_index : end_index]
+ else:
+ raise ValueError("Could not find file name in header. "
+ "Content disposition: %s." %content_disposition)
+
+ def _extract_file_name_from_url(self, product_url):
+ start_index = product_url.rindex("/") + 1
+ return product_url[start_index:]
+
+ def _query_region_maps(self, coordinates, radius, observation_name,
+ get_query_payload, cache):
+ observation_tap_name = (
+ self._find_observation_tap_table_name(observation_name, cache))
+ query = (
+ self._build_observation_query(coordinates, radius,
+ self._find_observation_parameters(observation_tap_name,
+ cache)))
+ request_payload = self._create_request_payload(query)
+ if (get_query_payload):
+ return request_payload
+ return self._get_and_parse_from_tap(request_payload, cache)
+
+ def _query_region_catalog(self, coordinates, radius, catalog_name,
+ get_query_payload, cache):
+ catalog_tap_name = self._find_catalog_tap_table_name(catalog_name, cache)
+ query = self._build_catalog_query(coordinates, radius,
+ self._find_catalog_parameters(catalog_tap_name,
+ cache))
+ request_payload = self._create_request_payload(query)
+ if (get_query_payload):
+ return request_payload
+ return self._get_and_parse_from_tap(request_payload, cache)
+
+ def _build_observation_query(self, coordinates, radius, json):
+ raHours, dec = commons.coord_to_radec(coordinates)
+ ra = raHours * 15.0 # Converts to degrees
+ radiusDeg = commons.radius_to_unit(radius, unit='deg')
+
+ select_query = "SELECT DISTINCT "
+
+ metadata = json[self.__METADATA_STRING]
+ metadata_tap_names = ", ".join(["%s" % entry[self.__TAP_NAME_STRING]
+ for entry in metadata])
+
+ from_query = " FROM %s" %json[self.__TAP_TABLE_STRING]
+ if (radiusDeg != 0 or json[self.__IS_SURVEY_MISSION_STRING]):
+ if (json[self.__IS_SURVEY_MISSION_STRING]):
+ area_or_point_string = "pos"
+ else:
+ area_or_point_string = "fov"
+ where_query = (" WHERE 1=CONTAINS(%s, CIRCLE('ICRS', %f, %f, %f));"
+ %(area_or_point_string, ra, dec, radiusDeg))
+ else:
+ area_or_point_string = "fov"
+ where_query = (" WHERE 1=CONTAINS(POINT('ICRS', %f, %f), %s);"
+ %(ra, dec, area_or_point_string))
+
+ query = "".join([select_query, metadata_tap_names, from_query,
+ where_query])
+ return query
+
+ def _build_catalog_query(self, coordinates, radius, json):
+ raHours, dec = commons.coord_to_radec(coordinates)
+ ra = raHours * 15.0 # Converts to degrees
+ radiusDeg = commons.radius_to_unit(radius, unit='deg')
+
+ select_query = "SELECT TOP %s " %json[self.__SOURCE_LIMIT_STRING]
+
+ metadata = json[self.__METADATA_STRING]
+ metadata_tap_names = ", ".join(["%s" % entry[self.__TAP_NAME_STRING]
+ for entry in metadata])
+
+ from_query = " FROM %s" %json[self.__TAP_TABLE_STRING]
+ if (radiusDeg == 0):
+ where_query = (" WHERE 1=CONTAINS(%s, CIRCLE('ICRS', %f, %f, %f))"
+ %(json[self.__POS_TAP_STRING], ra, dec,
+ commons.radius_to_unit(
+ self.__MIN_RADIUS_CATALOG_STRING, unit='deg')))
+ else:
+ where_query = (" WHERE 1=CONTAINS(%s, CIRCLE('ICRS', %f, %f, %f))"
+ %(json[self.__POS_TAP_STRING], ra, dec, radiusDeg))
+ order_by_query = " ORDER BY %s;" %json[self.__ORDER_BY_STRING]
+
+ query = "".join([select_query, metadata_tap_names, from_query,
+ where_query, order_by_query])
+
+ return query
+
+ def _store_query_result_maps(self, query_result, missions, coordinates,
+ radius, get_query_payload, cache):
+ for mission in missions:
+ mission_table = self._query_region_maps(coordinates, radius,
+ mission, get_query_payload,
+ cache)
+ if (len(mission_table) > 0):
+ query_result[mission.upper()] = mission_table
+
+ def _store_query_result_catalogs(self, query_result, catalogs, coordinates,
+ radius, get_query_payload, cache):
+ for catalog in catalogs:
+ catalog_table = self._query_region_catalog(coordinates, radius,
+ catalog, get_query_payload,
+ cache)
+ if (len(catalog_table) > 0):
+ query_result[catalog.upper()] = catalog_table
+
+ def _find_observation_parameters(self, mission_name, cache):
+ return self._find_mission_parameters_in_json(mission_name,
+ self._get_observation_json(cache))
+
+ def _find_catalog_parameters(self, catalog_name, cache):
+ return self._find_mission_parameters_in_json(catalog_name,
+ self._get_catalogs_json(cache))
+
+ def _find_mission_parameters_in_json(self, mission_tap_name, json):
+ for mission in json:
+ if (mission[self.__TAP_TABLE_STRING] == mission_tap_name):
+ return mission
+ raise ValueError("Input tap name %s not available." %mission_tap_name)
+
+ def _find_observation_tap_table_name(self, mission_name, cache):
+ return self._find_mission_tap_table_name(
+ self._fetch_and_parse_json(self.__OBSERVATIONS_STRING, cache),
+ mission_name)
+
+ def _find_catalog_tap_table_name(self, mission_name, cache):
+ return self._find_mission_tap_table_name(
+ self._fetch_and_parse_json(self.__CATALOGS_STRING, cache),
+ mission_name)
+
+ def _find_mission_tap_table_name(self, json, mission_name):
+ for index in range(len(json)):
+ if (json[index][self.__MISSION_STRING].lower() == mission_name.lower()):
+ return json[index][self.__TAP_TABLE_STRING]
+
+ raise ValueError("Input %s not available." %mission_name)
+ return None
+
+ def _get_observation_json(self, cache):
+ return self._fetch_and_parse_json(self.__OBSERVATIONS_STRING, cache)
+
+ def _get_catalogs_json(self, cache):
+ return self._fetch_and_parse_json(self.__CATALOGS_STRING, cache)
+
+ def _fetch_and_parse_json(self, object_name, cache):
+ url = self.URLbase + "/" + object_name
+ response = self._request('GET', url, cache = cache)
+ string_response = response.content.decode('utf-8')
+ json_response = json.loads(string_response)
+ return json_response[object_name]
+
+ def _json_object_field_to_list(self, json, field_name):
+ response_list = []
+ for index in range(len(json)):
+ response_list.append(json[index][field_name])
+ return response_list
+
+ def _create_request_payload(self, query):
+ return {'REQUEST':'doQuery', 'LANG':'ADQL', 'FORMAT': 'VOTABLE',
+ 'QUERY': query}
+
+ def _get_and_parse_from_tap(self, request_payload, cache):
+ response = self._send_get_request("/tap/sync", request_payload, cache)
+ return self._parse_xml_table(response)
+
+ def _send_get_request(self, url_extension, request_payload, cache):
+ url = self.URLbase + url_extension
+ return self._request('GET', url, params=request_payload,
+ timeout=self.TIMEOUT, cache=cache)
+
+ def _parse_xml_table(self, response):
+ # try to parse the result into an astropy.Table, else
+ # return the raw result with an informative error message.
+ try:
+ tf = six.BytesIO(response.content)
+ vo_table = votable.parse(tf, pedantic = False)
+ first_table = vo_table.get_first_table()
+ table = first_table.to_table(use_names_over_ids = True)
+ return table
+ except Exception as ex:
+ self.response = response
+ self.table_parse_error = ex
+ raise TableParseError(
+ "Failed to parse ESASky VOTABLE result! The raw response can be "
+ "found in self.response, and the error in "
+ "self.table_parse_error.")
+
+ESASky = ESASkyClass()
diff --git a/astroquery/esasky/tests/__init__.py b/astroquery/esasky/tests/__init__.py
new file mode 100755
index 0000000000..e69de29bb2
diff --git a/astroquery/esasky/tests/data/catalogs.txt b/astroquery/esasky/tests/data/catalogs.txt
new file mode 100644
index 0000000000..1a48bb1e3d
--- /dev/null
+++ b/astroquery/esasky/tests/data/catalogs.txt
@@ -0,0 +1,845 @@
+{
+ "catalogs" : [ {
+ "mission" : "INTEGRAL",
+ "wavelength" : "HARD_X_RAY",
+ "tapTable" : "integral_40_fits",
+ "countColumn" : "cat_integral40",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "INTEGRAL",
+ "histoColor" : "#87CEFA",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Flux at 20-60 keV",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "isgr_flux_1 DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 1
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 2
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(DEC) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 3
+ }, {
+ "tapName" : "isgr_flux_1",
+ "label" : "Flux20-60 keV
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "isgr_flux_2",
+ "label" : "Flux60-200 keV
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "isgri_flag",
+ "label" : "isgri flag1",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ }, {
+ "tapName" : "isgri_flag2",
+ "label" : "isgri flag2",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 7
+ } ]
+ }, {
+ "mission" : "XMM-EPIC",
+ "wavelength" : "SOFT_X_RAY",
+ "tapTable" : "mv_xsa_epic_source_cat",
+ "countColumn" : "cat_xmm_epic",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "3XMM EPIC",
+ "histoColor" : "#0000FF",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Flux",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "http://nxsa.esac.esa.int/nxsa-web/#",
+ "archiveProductURI" : "iauname=@@@Name@@@",
+ "fovLimit" : 90.0,
+ "orderBy" : "ep_8_flux DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 2
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 3
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(DEC) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 4
+ }, {
+ "tapName" : "ep_8_flux",
+ "label" : "Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ } ]
+ }, {
+ "mission" : "XMM-OM",
+ "wavelength" : "UV",
+ "tapTable" : " mv_xsa_om_source_cat",
+ "countColumn" : "cat_xmm_om",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "XMM OM",
+ "histoColor" : "#800080",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by flux starting with the UVW1 filter",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "uvw1_ab_flux,uvw2_ab_flux,uvm2_ab_flux,u_ab_flux,v_ab_flux,b_ab_flux DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 2
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 3
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(DEC) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 4
+ }, {
+ "tapName" : "poserr",
+ "label" : "ΔPos",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "date_obs",
+ "label" : "Date obs",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "uvw1_ab_flux",
+ "label" : "UVW1 Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "uvw2_ab_flux",
+ "label" : "UVW2 Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "uvm2_ab_flux",
+ "label" : "UVM2 Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "u_ab_flux",
+ "label" : "U Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "v_ab_flux",
+ "label" : "V Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "b_ab_flux",
+ "label" : "B Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ } ]
+ }, {
+ "mission" : "XMM-SLEW",
+ "wavelength" : "SOFT_X_RAY",
+ "tapTable" : "xmm_slew_source_cat",
+ "countColumn" : "cat_xmm_slew",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "XMM Slew",
+ "histoColor" : "#3F96C3",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Flux",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "flux DESC",
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 2
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 3
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 4
+ }, {
+ "tapName" : "flux",
+ "label" : "Flux
(erg cm-2s-1)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ } ]
+ }, {
+ "mission" : "Tycho-2",
+ "wavelength" : "VISIBLE",
+ "tapTable" : "mv_tycho2_fdw",
+ "countColumn" : "cat_tycho2",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "Tycho-2",
+ "histoColor" : "#DAA520",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by BT Magnitude",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "bt_mag DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 2
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 3
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 4
+ }, {
+ "tapName" : "pm_ra",
+ "label" : "Proper Motion in RA
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 3
+ }, {
+ "tapName" : "pm_de",
+ "label" : "Proper Motion in Dec
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "bt_mag",
+ "label" : "BT Magnitude
(mag)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "vt_mag",
+ "label" : "VT Magnitude
(mag)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ } ]
+ }, {
+ "mission" : "Gaia DR1 TGAS",
+ "wavelength" : "VISIBLE",
+ "tapTable" : "mv_tgas_source_fdw",
+ "countColumn" : "cat_tgas",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "Gaia DR1 TGAS",
+ "histoColor" : "#ffb74d",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by G magnitude",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "phot_g_mean_mag ASC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 2
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2015)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 3
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2015)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 4
+ }, {
+ "tapName" : "parallax",
+ "label" : "Parallax
(mas)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "parallax_error",
+ "label" : "Parallax error
(mas)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "pmra",
+ "label" : "Proper Motion in RA
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 3
+ }, {
+ "tapName" : "pmra_error",
+ "label" : "PM in RA error
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 3
+ }, {
+ "tapName" : "pmdec",
+ "label" : "Proper Motion in Dec
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "pmdec_error",
+ "label" : "PM in Dec error
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "phot_g_mean_mag",
+ "label" : "G mean Mag
(mag)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ } ]
+ }, {
+ "mission" : "Hipparcos-2",
+ "wavelength" : "VISIBLE",
+ "tapTable" : "mv_hipparcos_fdw",
+ "countColumn" : "cat_hip2_mv",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "Hipparcos-2",
+ "histoColor" : "#ffb74d",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Hp magnitude",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "hp_mag DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 2
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 3
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 4
+ }, {
+ "tapName" : "pm_ra",
+ "label" : "Proper Motion in RA
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 3
+ }, {
+ "tapName" : "pm_de",
+ "label" : "Proper Motion in Dec
(mas/yr)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "plx",
+ "label" : "Parallax
(mas)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "hp_mag",
+ "label" : "Magnitude
(mag)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ } ]
+ }, {
+ "mission" : "HSC",
+ "wavelength" : "VISIBLE",
+ "tapTable" : "mv_hubble_sc_fdw",
+ "countColumn" : "cat_hubble",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "HSC",
+ "histoColor" : "#FFD700",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the 2000 most observed sources (NumImages)",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "numimages DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 1
+ }, {
+ "tapName" : "numimages",
+ "label" : "NumImages",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 2
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 3
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 4
+ }, {
+ "tapName" : "w2_f450w",
+ "label" : "F450W
(mag)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "w2_f606w",
+ "label" : "F606W
(mag)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ }, {
+ "tapName" : "w2_f702w",
+ "label" : "F702W
(mag)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 7
+ }, {
+ "tapName" : "w2_f814w",
+ "label" : "F814W
(mag)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 8
+ } ]
+ }, {
+ "mission" : "Planck-PGCC2",
+ "wavelength" : "IR_RADIO",
+ "tapTable" : "mv_v_gcc_catalog_fdw",
+ "countColumn" : "cat_plank_pgss",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "PGCC2",
+ "histoColor" : "#FF4500",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Signal to Noise",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "snr DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 1
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 2
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 3
+ }, {
+ "tapName" : "snr",
+ "label" : "Signal to Noise",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "gau_major_axis",
+ "label" : "Major
FWHM
(arcmin)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "gau_major_axis_sig",
+ "label" : "Major FWHM error
(arcmin)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ }, {
+ "tapName" : "gau_minor_axis",
+ "label" : "Minor FWHM
(arcmin)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 7
+ }, {
+ "tapName" : "gau_minor_axis_sig",
+ "label" : "Minor FWHM error
(arcmin)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 8
+ } ]
+ }, {
+ "mission" : "Planck-PCCS2E",
+ "wavelength" : "IR_RADIO",
+ "tapTable" : "mv_pcss_catalog_excluded_fdw",
+ "countColumn" : "cat_planck_pccse",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "PCCS2E",
+ "histoColor" : "#ff4000",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Flux density",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "detflux DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 1
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA)(J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 2
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 3
+ }, {
+ "tapName" : "frequency",
+ "label" : "Frequency
(GHz)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "detflux",
+ "label" : "Flux density
(mJy)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "detflux_err",
+ "label" : "Flux density error
(mJy)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ }, {
+ "tapName" : "extended",
+ "label" : "Extended
source flag",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 7
+ }, {
+ "tapName" : "highest_reliability_cat",
+ "label" : "Cat. reliability",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 8
+ } ]
+ }, {
+ "mission" : "Planck-PCCS2-HFI",
+ "wavelength" : "IR_RADIO",
+ "tapTable" : "mv_pcss_catalog_hfi_fdw",
+ "countColumn" : "cat_planck_pccs_hfi",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "PCCS2-HFI",
+ "histoColor" : "#ff0000",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Flux density",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "detflux DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 1
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA)(J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 2
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 3
+ }, {
+ "tapName" : "frequency",
+ "label" : "Frequency
(GHz)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "detflux",
+ "label" : "Flux density
(mJy)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "detflux_err",
+ "label" : "Flux density error
(mJy)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ }, {
+ "tapName" : "extended",
+ "label" : "Extended
source flag",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 7
+ }, {
+ "tapName" : "highest_reliability_cat",
+ "label" : "Cat. reliability",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 8
+ } ]
+ }, {
+ "mission" : "Planck-PCCS2-LFI",
+ "wavelength" : "IR_RADIO",
+ "tapTable" : "mv_pcss_catalog_lfi_fdw",
+ "countColumn" : "cat_planck_pccs_lfi",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "PCCS2-LFI",
+ "histoColor" : "#ff8566",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Flux density",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "detflux DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 1
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA)(J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 2
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 3
+ }, {
+ "tapName" : "frequency",
+ "label" : "Frequency
(GHz)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "detflux",
+ "label" : "Flux density
(mJy)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "detflux_err",
+ "label" : "Flux density error
(mJy)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ }, {
+ "tapName" : "extended",
+ "label" : "Extended
source flag",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 7
+ }, {
+ "tapName" : "highest_reliability_cat",
+ "label" : "Cat. reliability",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 8
+ } ]
+ }, {
+ "mission" : "Planck-PSZ",
+ "wavelength" : "IR_RADIO",
+ "tapTable" : "mv_v_sz_catalog_fdw",
+ "countColumn" : "cat_plank_psz",
+ "countFovLimit" : 3.0,
+ "guiLabel" : "PSZ",
+ "histoColor" : "#C71585",
+ "sourceLimit" : 2000,
+ "sourceLimitDescription" : "Showing the first 2000 sources ordered by Signal to Noise",
+ "posTapColumn" : "pos",
+ "polygonRaTapColumn" : "ra",
+ "polygonDecTapColumn" : "dec",
+ "polygonNameTapColumn" : "name",
+ "archiveURL" : "",
+ "archiveProductURI" : "",
+ "fovLimit" : 90.0,
+ "orderBy" : "snr DESC",
+ "metadata" : [ {
+ "tapName" : "name",
+ "label" : "Name",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 1
+ }, {
+ "tapName" : "ra",
+ "label" : "Right Ascension
(RA) (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 2
+ }, {
+ "tapName" : "dec",
+ "label" : "Declination
(Dec) (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 3
+ }, {
+ "tapName" : "snr",
+ "label" : "Signal to Noise",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 4
+ }, {
+ "tapName" : "redshift",
+ "label" : "Redshift",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 5
+ }, {
+ "tapName" : "msz",
+ "label" : "Sunyaev-Zeldovich Mass(1014 Msolar)",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 6
+ }, {
+ "tapName" : "validation",
+ "label" : "Validation",
+ "visible" : true,
+ "type" : "DOUBLE",
+ "index" : 7
+ } ]
+ } ],
+ "total" : 13
+}
diff --git a/astroquery/esasky/tests/data/observations.txt b/astroquery/esasky/tests/data/observations.txt
new file mode 100644
index 0000000000..395e9a4762
--- /dev/null
+++ b/astroquery/esasky/tests/data/observations.txt
@@ -0,0 +1,626 @@
+{
+ "observations" : [ {
+ "mission" : "INTEGRAL",
+ "wavelength" : "HARD_X_RAY",
+ "tapTable" : "integral_data",
+ "countColumn" : "sur_integral",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "",
+ "tapObservationId" : "scw_id",
+ "guiLabel" : "INTEGRAL",
+ "histoColor" : "#87CEFA",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "integral_moc",
+ "mocSTCSColumn" : "aladin_lite_polygon",
+ "mocLimit" : 3000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "observation_id",
+ "label" : "Science Window Id",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 3
+ }, {
+ "tapName" : "instrument",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 5
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 6
+ }, {
+ "tapName" : "tstart_iso",
+ "label" : "Start time",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 7
+ }, {
+ "tapName" : "telapse",
+ "label" : "Duration (s)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 7
+ } ],
+ "archiveURL" : "http://www.isdc.unige.ch/browse/isdcvo2Browse.pl?instrument=integral_jemx&goodTime=%3E200&radius=180&resultMax=1000&",
+ "archiveProductURI" : "ra=@@@RA (J2000)@@@&dec=@@@DEC (J2000)@@@",
+ "ddProductURI" : "",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "",
+ "ddProductIDColumn" : null,
+ "sampEnabled" : true,
+ "isSurveyMission" : true
+ }, {
+ "mission" : "XMM-EPIC",
+ "wavelength" : "SOFT_X_RAY",
+ "tapTable" : "xmm_data",
+ "countColumn" : "obs_xmm_epic",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "stc_s",
+ "tapObservationId" : "observation_id",
+ "guiLabel" : "XMM-Newton",
+ "histoColor" : "#0000cc",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "xmm_moc",
+ "mocSTCSColumn" : "aladin_lite_polygon",
+ "mocLimit" : 2000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 2
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 3
+ }, {
+ "tapName" : "instrument",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 5
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 6
+ }, {
+ "tapName" : "start_time",
+ "label" : "Start Time",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 7
+ }, {
+ "tapName" : "duration",
+ "label" : "Duration (s)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 8
+ }, {
+ "tapName" : "stc_s",
+ "label" : "",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 9
+ } ],
+ "archiveURL" : "http://nxsa.esac.esa.int/nxsa-web/#",
+ "archiveProductURI" : "obsid=@@@ObservationId@@@",
+ "ddProductURI" : "obsno=@@@ObservationId@@@",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "obsno",
+ "ddProductIDColumn" : "ObservationId",
+ "sampEnabled" : true,
+ "isSurveyMission" : false
+ }, {
+ "mission" : "SUZAKU",
+ "wavelength" : "SOFT_X_RAY",
+ "tapTable" : "suzaku_data",
+ "countColumn" : "obs_suzaku",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "stc_s",
+ "tapObservationId" : "observation_id",
+ "guiLabel" : "SUZAKU",
+ "histoColor" : "#0099ff",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "suzaku_moc",
+ "mocSTCSColumn" : "aladin_lite_polygon",
+ "mocLimit" : 1000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 2
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 3
+ }, {
+ "tapName" : "instrument",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 5
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 6
+ }, {
+ "tapName" : "stc_s",
+ "label" : "",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 0
+ } ],
+ "archiveURL" : "http://darts.isas.jaxa.jp/astro/judo2/meta_info_page/html/SUZAKU/",
+ "archiveProductURI" : "@@@ObservationId@@@.html",
+ "ddProductURI" : "",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "",
+ "ddProductIDColumn" : "",
+ "sampEnabled" : true,
+ "isSurveyMission" : false
+ }, {
+ "mission" : "XMM-OM-OPTICAL",
+ "wavelength" : "VISIBLE",
+ "tapTable" : "xmm_om_optical_data",
+ "countColumn" : "obs_xmm_om_optical",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "stc_s",
+ "tapObservationId" : "observation_id",
+ "guiLabel" : "XMM-OM(Optical)",
+ "histoColor" : "#DAA520",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "xmm_om_vub_moc",
+ "mocSTCSColumn" : "icrs_polygon",
+ "mocLimit" : 2000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 3
+ }, {
+ "tapName" : "instrument",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "filter",
+ "label" : "Filter",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 5
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 6
+ }, {
+ "tapName" : "start_utc",
+ "label" : "Start Time",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ }, {
+ "tapName" : "duration",
+ "label" : "Duration (s)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ }, {
+ "tapName" : "stc_s",
+ "label" : "",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 0
+ } ],
+ "archiveURL" : "http://nxsa.esac.esa.int/nxsa-web/#",
+ "archiveProductURI" : "obsid=@@@ObservationId@@@",
+ "ddProductURI" : "",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "",
+ "ddProductIDColumn" : "",
+ "sampEnabled" : true,
+ "isSurveyMission" : false
+ }, {
+ "mission" : "XMM-OM-UV",
+ "wavelength" : "UV",
+ "tapTable" : "xmm_om_uv_data",
+ "countColumn" : "obs_xmm_om_uv",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "stc_s",
+ "tapObservationId" : "observation_id",
+ "guiLabel" : "XMM-OM(UV)",
+ "histoColor" : "#800080",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "xmm_om_uv_moc",
+ "mocSTCSColumn" : "icrs_polygon",
+ "mocLimit" : 2000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 3
+ }, {
+ "tapName" : "instrument",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "filter",
+ "label" : "Filter",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 5
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 6
+ }, {
+ "tapName" : "start_utc",
+ "label" : "Start Time",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ }, {
+ "tapName" : "duration",
+ "label" : "Duration (s)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ }, {
+ "tapName" : "stc_s",
+ "label" : "",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 0
+ } ],
+ "archiveURL" : "http://nxsa.esac.esa.int/nxsa-web/#",
+ "archiveProductURI" : "obsid=@@@ObservationId@@@",
+ "ddProductURI" : "",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "",
+ "ddProductIDColumn" : "",
+ "sampEnabled" : true,
+ "isSurveyMission" : false
+ }, {
+ "mission" : "HST",
+ "wavelength" : "VISIBLE",
+ "tapTable" : "mv_hst_observation_fdw",
+ "countColumn" : "obs_hst",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "stc_s",
+ "tapObservationId" : "observation_id",
+ "guiLabel" : "HST",
+ "histoColor" : "#FFD700",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "hst_moc7",
+ "mocSTCSColumn" : "aladin_lite_polygon",
+ "mocLimit" : 5000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 2
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 3
+ }, {
+ "tapName" : "collection",
+ "label" : "Collection",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "instrument_name",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "filter",
+ "label" : "Filter",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 7
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 8
+ }, {
+ "tapName" : "start_time",
+ "label" : "Start Time",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 9
+ }, {
+ "tapName" : "exposure_duration",
+ "label" : "Duration (s)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 10
+ }, {
+ "tapName" : "stc_s",
+ "label" : "",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 11
+ } ],
+ "archiveURL" : "http://archives.esac.esa.int/ehst/#",
+ "archiveProductURI" : "observationid=@@@ObservationId@@@",
+ "ddProductURI" : "ARTIFACT_ID=@@@ObservationId@@@_DRZ",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "ARTIFACT_ID",
+ "ddProductIDColumn" : "ObservationId",
+ "sampEnabled" : true,
+ "isSurveyMission" : false
+ }, {
+ "mission" : "Herschel",
+ "wavelength" : "IR_RADIO",
+ "tapTable" : "mv_hsa_esasky_photo_table_fdw",
+ "countColumn" : "obs_hsa_photo",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "stc_s",
+ "tapObservationId" : "observation_id",
+ "guiLabel" : "Herschel",
+ "histoColor" : "#20B2AA",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "hsa_moc",
+ "mocSTCSColumn" : "aladin_lite_polygon",
+ "mocLimit" : 2000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 2
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 3
+ }, {
+ "tapName" : "observation_oid",
+ "label" : "ObservationOid",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "instrument",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 5
+ }, {
+ "tapName" : "filter",
+ "label" : "Filter (microns)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 6
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 7
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 8
+ }, {
+ "tapName" : "start_time",
+ "label" : "Start Time",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 9
+ }, {
+ "tapName" : "duration",
+ "label" : "Duration (s)",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 10
+ }, {
+ "tapName" : "stc_s",
+ "label" : "",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 11
+ } ],
+ "archiveURL" : "http://archives.esac.esa.int/hsa/aio/jsp/postcardPage.jsp?",
+ "archiveProductURI" : "OBSERVATION_ID=@@@ObservationId@@@&INSTRUMENT=@@@Instrument@@@",
+ "ddProductURI" : "OBSERVATION.OBSERVATION_OID=@@@ObservationOid@@@",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "OBSERVATION.OBSERVATION_OID",
+ "ddProductIDColumn" : "ObservationOid",
+ "sampEnabled" : false,
+ "isSurveyMission" : false
+ }, {
+ "mission" : "ISO",
+ "wavelength" : "IR_RADIO",
+ "tapTable" : "ida_data",
+ "countColumn" : "obs_iso",
+ "countFovLimit" : 3.0,
+ "tapSTCSColumn" : "stc_s",
+ "tapObservationId" : "observation_id",
+ "guiLabel" : "ISO",
+ "histoColor" : "#BF3030",
+ "fovLimit" : 90.0,
+ "mocTapTable" : "ida_moc",
+ "mocSTCSColumn" : "aladin_lite_polygon",
+ "mocLimit" : 2000,
+ "metadata" : [ {
+ "tapName" : "postcard_url",
+ "label" : "",
+ "visible" : true,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "product_url",
+ "label" : "ProductURL",
+ "visible" : false,
+ "type" : "LINK",
+ "index" : 1
+ }, {
+ "tapName" : "observation_id",
+ "label" : "ObservationId",
+ "visible" : true,
+ "type" : "LINK2ARCHIVE",
+ "index" : 3
+ }, {
+ "tapName" : "instrument",
+ "label" : "Instrument",
+ "visible" : true,
+ "type" : "STRING",
+ "index" : 4
+ }, {
+ "tapName" : "ra_deg",
+ "label" : "RA (J2000)",
+ "visible" : true,
+ "type" : "RA",
+ "index" : 5
+ }, {
+ "tapName" : "dec_deg",
+ "label" : "DEC (J2000)",
+ "visible" : true,
+ "type" : "DEC",
+ "index" : 6
+ }, {
+ "tapName" : "stc_s",
+ "label" : "",
+ "visible" : false,
+ "type" : "STRING",
+ "index" : 0
+ } ],
+ "archiveURL" : "http://archives.esac.esa.int/ida/aio/jsp/createPostcards.jsp?",
+ "archiveProductURI" : "obsno=@@@ObservationId@@@",
+ "ddProductURI" : "",
+ "ddBaseURL" : "",
+ "ddProductIDParameter" : "",
+ "ddProductIDColumn" : null,
+ "sampEnabled" : true,
+ "isSurveyMission" : false
+ } ],
+ "total" : 8
+}
\ No newline at end of file
diff --git a/astroquery/esasky/tests/setup_package.py b/astroquery/esasky/tests/setup_package.py
new file mode 100644
index 0000000000..302c602298
--- /dev/null
+++ b/astroquery/esasky/tests/setup_package.py
@@ -0,0 +1,16 @@
+# Licensed under a 3-clause BSD style license - see LICENSE.rst
+from __future__ import absolute_import
+
+import os
+
+
+# setup paths to the test data
+# can specify a single file or a list of files
+def get_package_data():
+ paths = [os.path.join('data', '*.dat'),
+ os.path.join('data', '*.xml'),
+ os.path.join('data', '*.txt'),
+ ] # etc, add other extensions
+ # you can also enlist files individually by names
+ # finally construct and return a dict for the sub module
+ return {'astroquery.esasky.tests': paths}
diff --git a/astroquery/esasky/tests/test_esasky.py b/astroquery/esasky/tests/test_esasky.py
new file mode 100755
index 0000000000..e7e1c142aa
--- /dev/null
+++ b/astroquery/esasky/tests/test_esasky.py
@@ -0,0 +1,52 @@
+# Licensed under a 3-clause BSD style license - see LICENSE.rst
+from __future__ import print_function
+
+from astropy.tests.helper import pytest
+
+import os
+import unittest
+
+from ...utils.testing_tools import MockResponse
+from ...esasky import ESASky
+
+
+DATA_FILES = {'GET':
+ {'http://ammidev.n1data.lan:8080/esasky-tap/observations':
+ 'observations.txt',
+ 'http://sky.esa.int/esasky-tap/catalogs':
+ 'catalogs.txt'
+ },
+ }
+
+def data_path(filename):
+ data_dir = os.path.join(os.path.dirname(__file__), 'data')
+ return os.path.join(data_dir, filename)
+
+def nonremote_request(request_type, url, **kwargs):
+ with open(data_path(DATA_FILES[request_type][url]), 'rb') as f:
+ response = MockResponse(content=f.read(), url=url)
+ return response
+
+@pytest.fixture
+def esasky_request(request):
+ mp = request.getfuncargvalue("monkeypatch")
+ mp.setattr(ESASky, '_request', nonremote_request)
+ return mp
+
+@pytest.mark.usefixtures("esasky_request")
+class TestEsaSkyLocal(unittest.TestCase):
+ def test_esasky_query_region_maps_invalid_position(self):
+ with self.assertRaises(ValueError):
+ ESASky.query_region_maps(51, "5 arcmin")
+
+ def test_esasky_query_region_maps_invalid_radius(self):
+ with self.assertRaises(ValueError):
+ ESASky.query_region_maps("M51", 5)
+
+ def test_esasky_query_region_maps_invalid_mission(self):
+ with self.assertRaises(ValueError):
+ ESASky.query_region_maps("M51", "5 arcmin", missions=True)
+
+ def test_list_catalogs(self):
+ result = ESASky.list_catalogs()
+ assert (len(result) == 13)
diff --git a/astroquery/esasky/tests/test_esasky_remote.py b/astroquery/esasky/tests/test_esasky_remote.py
new file mode 100755
index 0000000000..edacf3ae53
--- /dev/null
+++ b/astroquery/esasky/tests/test_esasky_remote.py
@@ -0,0 +1,26 @@
+# Licensed under a 3-clause BSD style license - see LICENSE.rst
+from __future__ import print_function
+
+from astroquery.utils.commons import TableList
+from astropy.tests.helper import remote_data
+
+from ... import esasky
+
+@remote_data
+class TestESASky:
+
+ def test_esasky_query_region_maps(self):
+ result = esasky.core.ESASkyClass().query_region_maps("M51", "5 arcmin")
+ assert isinstance(result, TableList)
+
+ def test_esasky_query_object_maps(self):
+ result = esasky.core.ESASkyClass().query_object_maps("M51")
+ assert isinstance(result, TableList)
+
+ def test_esasky_query_region_catalogs(self):
+ result = esasky.core.ESASkyClass().query_region_catalogs("M51", "5 arcmin")
+ assert isinstance(result, TableList)
+
+ def test_esasky_query_object_catalogs(self):
+ result = esasky.core.ESASkyClass().query_object_maps("M51")
+ assert isinstance(result, TableList)
diff --git a/docs/esasky/esasky.rst b/docs/esasky/esasky.rst
new file mode 100644
index 0000000000..36cfd208a5
--- /dev/null
+++ b/docs/esasky/esasky.rst
@@ -0,0 +1,248 @@
+.. doctest-skip-all
+
+.. _astroquery.esasky:
+
+************************************
+ESASky Queries (`astroquery.esasky`)
+************************************
+
+Getting started
+===============
+
+This is a python interface for querying the ESASky web service. This supports
+querying an object as well as querying a region around the target. For region
+queries, the region dimensions may be specified as a
+radius. The queries may be further constrained by specifying
+a choice of catalogs or missions.
+
+Get the available catalog names
+-------------------------------
+
+If you know the names of all the available catalogs you can use
+:meth:`~astroquery.esasky.ESASkyClass.list_catalogs`:
+
+.. code-block:: python
+
+ >>> catalog_list = ESASky.list_catalogs()
+ >>> print(catalog_list)
+ ['INTEGRAL', 'XMM-EPIC', 'XMM-OM', 'XMM-SLEW', 'Tycho-2',
+ 'Gaia DR1 TGAS', 'Hipparcos-2', 'HSC', 'Planck-PGCC2', 'Planck-PCCS2E',
+ 'Planck-PCCS2-HFI', 'Planck-PCCS2-LFI', 'Planck-PSZ']
+
+Get the available maps mission names
+------------------------------------
+
+If you know the names of all the available maps missions you can use
+:meth:`~astroquery.esasky.ESASkyClass.list_maps`:
+
+.. code-block:: python
+
+ >>> maps_list = ESASky.list_maps()
+ >>> print(maps_list)
+ ['INTEGRAL', 'XMM-EPIC', 'SUZAKU', 'XMM-OM-OPTICAL', 'XMM-OM-UV',
+ 'HST', 'Herschel', 'ISO']
+
+Query an object
+---------------
+
+There are two query objects methods in this module
+:meth:`~astroquery.esasky.ESASkyClass.query_object_catalogs` and
+:meth:`~astroquery.esasky.ESASkyClass.query_object_maps`. They both work in
+almost the same way except that one has catalogs as input and output and the
+other one has mission names and observations as input and output.
+
+For catalogs, the query returns a maximum of 2000 sources per mission.
+To account for observation errors, this method will search for any sources
+within 5 arcsec from the object.
+
+For instance to query an object around M51 in the integral catalog:
+
+.. code-block:: python
+
+ >>> from astroquery.esasky import ESASky
+ >>> result = ESASky.query_object_catalogs("M51", "integral")
+
+Note that the catalog may also be specified as a list.
+So the above query may also be written as:
+
+.. code-block:: python
+
+ >>> result = ESASky.query_object_catalogs("M51", ["integral", "XMM-OM"])
+
+To search in all available catalogs you can write "all" instead of a catalog
+name. The same thing will happen if you don't write any catalog name.
+
+.. code-block:: python
+
+ >>> result = ESASky.query_object_catalogs("M51", "all")
+ >>> result = ESASky.query_object_catalogs("M51")
+
+To see the result:
+
+.. code-block:: python
+
+ >>> print(result)
+ TableList with 4 tables:
+ '0:XMM-EPIC' with 4 column(s) and 3 row(s)
+ '1:HSC' with 8 column(s) and 2000 row(s)
+ '2:XMM-OM' with 12 column(s) and 220 row(s)
+ '3:PLANCK-PCCS2-HFI' with 8 column(s) and 1 row(s)
+
+All the results are returned as a `astroquery.utils.TableList` object. This is a
+container for `~astropy.table.Table` objects. It is basically an extension to
+`collections.OrderedDict` for storing a `~astropy.table.Table` against its name.
+
+To access an individual table from the `astroquery.utils.TableList` object
+
+.. code-block:: python
+
+ >>> interesting_table = result['PLANCK-PCCS2-HFI']
+ >>> print(interesting_table)
+ name ra [1] dec [1]
+ ----------------------- ------------- -------------
+ PCCS2 217 G104.83+68.55 202.485459453 47.2001843799
+
+To do some common processing to all the tables in the returned
+`astroquery.utils.TableList` object, do just what you would do for a python
+dictionary:
+
+.. code-block:: python
+
+ >>> for table_name in result:
+ ... table = result[table_name]
+ ... # table is now an `astropy.table.Table` object
+ ... # some code to apply on table
+
+As mentioned earlier, :meth:`astroquery.esasky.ESASkyClass.query_object_maps`
+works extremely similar. It will return all maps that contain the chosen object
+or coordinate. To execute the same command as above you write this:
+
+.. code-block:: python
+
+ >>> result = ESASky.query_object_maps("M51", "all")
+
+The parameters are interchangeable in the same way as in query_object_catalogs
+
+Query a region
+--------------
+The region queries work in a similar way as query_object, except that you must
+choose a radius as well. There are two query region methods in this module
+:meth:`astroquery.esasky.ESASkyClass.query_region_catalogs` and
+:meth:`astroquery.esasky.ESASkyClass.query_region_maps`.
+The query returns a maximum of 2000 sources per mission.
+
+To query a region either the coordinates or the object name around which to
+query should be specified along with the value for the radius of the region.
+For instance to query region around M51 in the integral catalog:
+
+.. code-block:: python
+
+ >>> from astroquery.esasky import ESASky
+ >>> import astropy.units as u
+ >>> result = ESASky.query_region_catalogs("M51", 10 * u.arcmin, "integral")
+
+Note that the catalog may also be specified as a list.
+So the above query may also be written as:
+
+.. code-block:: python
+
+ >>> result = ESASky.query_region_catalogs("M51", 10 * u.arcmin, ["integral", "XMM-OM"])
+
+To search in all available catalogs you can write "all" instead of a catalog
+name. The same thing will happen if you don't write any catalog name.
+
+.. code-block:: python
+
+ >>> result = ESASky.query_region_catalogs("M51", 10 * u.arcmin, "all")
+ >>> result = ESASky.query_region_catalogs("M51", 10 * u.arcmin)
+
+In the same manner, the radius can be specified with either
+a string or any `~astropy.units.Quantity`
+
+.. code-block:: python
+
+ >>> result = ESASKY.query_region_catalogs("M51", "10 arcmin")
+
+To see the result:
+
+.. code-block:: python
+
+ >>> print(result)
+ TableList with 4 tables:
+ '0:XMM-EPIC' with 4 column(s) and 3 row(s)
+ '1:HSC' with 8 column(s) and 2000 row(s)
+ '2:XMM-OM' with 12 column(s) and 220 row(s)
+ '3:PLANCK-PCCS2-HFI' with 8 column(s) and 1 row(s)
+
+As mentioned earlier, query_region_maps works extremely similar.
+To execute the same command as above you write this:
+
+.. code-block:: python
+
+ >>> result = ESASky.query_region_maps("M51", 10 * u.arcmin, "all")
+
+The parameters are interchangeable in the same way as in query_region_catalogs
+
+Get images
+----------
+
+You can fetch images around the specified target or coordinates. When a target
+name is used rather than the coordinates, this will be resolved to coordinates
+using astropy name resolving methods that utilize online services like
+SESAME. Coordinates may be entered using the suitable object from
+`astropy.coordinates`.
+
+The method returns a `dict` to separate the different
+missions. All mission except Herschel returns a list of
+`~astropy.io.fits.HDUList`. For Herschel each item in the list is a
+dictionary where the used filter is the key and the HDUList is the value.
+
+.. code-block:: python
+
+ >>> from astroquery.esasky import ESASky
+ >>> images = ESASky.get_images("m51", radius="20 arcmin", missions=['Herschel', 'XMM-EPIC'])
+
+ Starting download of HERSCHEL data. (12 files)
+ Downloading Observation ID: 1342183910 from http://archives.esac.esa.int/hsa/aio/jsp/standaloneproduct.jsp?RETRIEVAL_TYPE=STANDALONE&OBSERVATION.OBSERVATION_ID=1342183910&OBSERVING_MODE.OBSERVING_MODE_NAME=PacsPhoto&INSTRUMENT.INSTRUMENT_NAME=PACS [Done]
+ Downloading Observation ID: 1342183907 from http://archives.esac.esa.int/hsa/aio/jsp/standaloneproduct.jsp?RETRIEVAL_TYPE=STANDALONE&OBSERVATION.OBSERVATION_ID=1342183907&OBSERVING_MODE.OBSERVING_MODE_NAME=PacsPhoto&INSTRUMENT.INSTRUMENT_NAME=PACS [Done]
+ ...
+
+ >>> print(images)
+ {
+ 'HERSCHEL': [{'70': [HDUList], ' 160': [HDUList]}, {'70': [HDUList], ' 160': [HDUList]}, ...],
+ 'XMM-EPIC' : [HDUList], HDUList], HDUList], HDUList], ...]
+ ...
+ }
+
+Note that the fits files also are stored to disk. By default they are saved to
+the working directory but the location can be chosen by the download_directory
+parameter:
+
+.. code-block:: python
+
+ >>> images = ESASky.get_images("m51", radius="20 arcmin", missions=['Herschel', 'XMM-EPIC'], download_directory="/home/user/esasky")
+
+Get maps
+--------
+
+You can also fetch images using :meth:`astroquery.esasky.ESASkyClass.get_maps`.
+It works exactly as :meth:`astroquery.esasky.ESASkyClass.get_images` except that
+it takes a `~astropy.utils.TableList` instead of position, radius and missions.
+
+.. code-block:: python
+
+ >>> table_list = ESASky.query_region_maps("m51", radius="20 arcmin", missions=['Herschel', 'XMM-EPIC'])
+ >>> images = ESASky.get_maps(table_list, download_directory="/home/user/esasky")
+
+This example is equivalent to:
+
+.. code-block:: python
+
+ >>> images = ESASky.get_images("m51", radius="20 arcmin", missions=['Herschel', 'XMM-EPIC'], download_directory="/home/user/esasky")
+
+
+Reference/API
+=============
+
+.. automodapi:: astroquery.esasky
+ :no-inheritance-diagram:
diff --git a/docs/index.rst b/docs/index.rst
index a50463ac03..9fa796f25a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -134,6 +134,7 @@ The following modules have been completed using a common API:
simbad/simbad.rst
vizier/vizier.rst
+ esasky/esasky.rst
irsa/irsa_dust.rst
ned/ned.rst
splatalogue/splatalogue.rst