Skip to content

Commit bcb0ff5

Browse files
jespinosaar“Javier
authored andcommitted
EHSTPCR-902: cone_search_criteria implemented
1 parent db603f7 commit bcb0ff5

File tree

4 files changed

+273
-56
lines changed

4 files changed

+273
-56
lines changed

astroquery/esa/hubble/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ class Conf(_config.ConfigNamespace):
2424
"ehst-sl-server/servlet/"
2525
"metadata-action",
2626
"Main url for retriving hst metadata")
27+
TARGET_ACTION = _config.ConfigItem("http://archives.esac.esa.int/"
28+
"ehst-sl-server/servlet/"
29+
"targetresolver-action",
30+
"Main url for solving targets")
2731
TIMEOUT = 60
2832

2933

astroquery/esa/hubble/core.py

Lines changed: 161 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from six import BytesIO
2727
import shutil
2828
import os
29+
import json
2930

3031
from . import conf
3132
from astroquery import log
@@ -40,6 +41,7 @@ class ESAHubbleClass(BaseQuery):
4041

4142
data_url = conf.DATA_ACTION
4243
metadata_url = conf.METADATA_ACTION
44+
target_url = conf.TARGET_ACTION
4345
TIMEOUT = conf.TIMEOUT
4446
calibration_levels = {0: "AUXILIARY", 1: "RAW", 2: "CALIBRATED",
4547
3: "PRODUCT"}
@@ -215,7 +217,8 @@ def get_postcard(self, observation_id, calibration_level="RAW",
215217
shutil.move(response, filename)
216218

217219
def cone_search(self, coordinates, radius=0.0, filename=None,
218-
output_format='votable', save=False, cache=True):
220+
output_format='votable', async_job=False,
221+
cache=True, verbose=False):
219222
"""
220223
To execute a cone search defined by a coordinate and a radius
221224
@@ -233,14 +236,14 @@ def cone_search(self, coordinates, radius=0.0, filename=None,
233236
results format. Options are:
234237
'votable': str, binary VOTable format
235238
'csv': str, comma-separated values format
236-
save : bool
237-
optional, default 'False'
238-
Flag to save the result in a file. If the filename
239-
is not defined, it will use a formatted name to save
240-
the file
239+
async_job : bool, optional, default 'False'
240+
executes the query (job) in asynchronous/synchronous mode (default
241+
synchronous)
241242
cache : bool
242243
optional, default 'True'
243244
Flag to save the results in the local cache
245+
verbose : bool, optional, default 'False'
246+
flag to display information about the process
244247
245248
Returns
246249
-------
@@ -251,50 +254,158 @@ def cone_search(self, coordinates, radius=0.0, filename=None,
251254

252255
ra_hours, dec = commons.coord_to_radec(coord)
253256
ra = ra_hours * 15.0 # Converts to degrees
254-
payload = {"RESOURCE_CLASS": "OBSERVATION",
255-
"ADQLQUERY": "SELECT DISTINCT OBSERVATION,OBSERVATION.TYPE,"
256-
"TARGET.MOVING_TARGET"
257-
",TARGET.TARGET_NAME,TARGET.TARGET_DESCRIPTION,PROPOSAL."
258-
"PROPOSAL_ID,PROPOSAL.PI_"
259-
"NAME,PROPOSAL.PROPOSAL_TITLE,INSTRUMENT.INSTRUMENT_NAME,"
260-
"PLANE.METADATA_PROVENANCE"
261-
",PLANE.DATA_PRODUCT_TYPE,PLANE.SOFTWARE_VERSION,POSITION"
262-
".RA,POSITION.DEC,POSITION."
263-
"GAL_LAT,POSITION.GAL_LON,POSITION.ECL_LAT,POSITION.ECL_LON"
264-
",POSITION.FOV_SIZE,ENERGY."
265-
"WAVE_CENTRAL,ENERGY.WAVE_BANDWIDTH,ENERGY.WAVE_MAX,ENERGY"
266-
".WAVE_MIN,ENERGY.FILTER FROM"
267-
" FIELD_NOT_USED WHERE OBSERVATION.COLLECTION='HST' AND "
268-
"PLANE.MAIN_SCIENCE_PLANE="
269-
"'true' AND (OBSERVATION.TYPE='HST Composite' OR "
270-
"OBSERVATION.TYPE='HST Singleton')"
271-
" AND INTERSECTS(CIRCLE('ICRS', {0}, {1}, {2}"
272-
"),POSITION)=1 AND PLANE.MAIN_SCIENCE_PLANE='true' "
273-
"ORDER BY PROPOSAL.PROPOSAL_ID "
274-
"DESC".format(str(ra), str(dec), str(radius_in_grades)),
275-
"RETURN_TYPE": str(output_format)}
276-
response = self._request('GET',
277-
self.metadata_url,
278-
params=payload,
279-
save=save or filename is not None,
280-
cache=cache,
281-
timeout=self.TIMEOUT)
282-
if response is None:
283-
table = None
257+
258+
query = "select o.observation_id, "\
259+
"o.start_time, o.end_time, o.start_time_mjd, "\
260+
"o.end_time_mjd, o.exposure_duration, o.release_date, "\
261+
"o.run_id, o.program_id, o.set_id, o.collection, "\
262+
"o.members_number, o.instrument_configuration, "\
263+
"o.instrument_name, o.obs_type, o.target_moving, "\
264+
"o.target_name, o.target_description, o.proposal_id, "\
265+
"o.pi_name, prop.title, pl.metadata_provenance, "\
266+
"pl.data_product_type, pl.software_version, pos.ra, "\
267+
"pos.dec, pos.gal_lat, pos.gal_lon, pos.ecl_lat, "\
268+
"pos.ecl_lon, pos.fov_size, en.wave_central, "\
269+
"en.wave_bandwidth, en.wave_max, en.wave_min, "\
270+
"en.filter from ehst.observation o join ehst.proposal "\
271+
"prop on o.proposal_id=prop.proposal_id join ehst.plane "\
272+
"pl on pl.observation_id=o.observation_id join "\
273+
"ehst.position pos on pos.plane_id = pl.plane_id join "\
274+
"ehst.energy en on en.plane_id=pl.plane_id where "\
275+
"pl.main_science_plane='true' and 1=CONTAINS(POINT('ICRS', "\
276+
"pos.ra, pos.dec),CIRCLE('ICRS', {0}, {1}, {2})) order "\
277+
"by prop.proposal_id desc".format(str(ra), str(dec),
278+
str(radius_in_grades))
279+
if verbose:
280+
log.info(query)
281+
table = self.query_hst_tap(query=query, async_job=async_job,
282+
output_file=filename,
283+
output_format=output_format,
284+
verbose=verbose)
285+
return table
286+
287+
def cone_search_criteria(self, target=None, coordinates=None,
288+
radius=0.0,
289+
calibration_level=None,
290+
data_product_type=None,
291+
intent=None,
292+
obs_collection=None,
293+
instrument_name=None,
294+
filters=None,
295+
async_job=True,
296+
filename=None,
297+
output_format='votable',
298+
save=False,
299+
cache=True,
300+
verbose=False):
301+
"""
302+
To execute a cone search defined by a coordinate (an
303+
astropy.coordinate element or a target name which is resolved),
304+
a radius and a set of criteria to filter the results. This function
305+
comprises the outputs of query_target, cone_search and query_criteria
306+
methods.
307+
308+
Parameters
309+
----------
310+
coordinates : astropy.coordinate, mandatory
311+
coordinates of the center in the cone search
312+
radius : float, default 0
313+
radius in arcmin of the cone_search
314+
calibration_level : str or int, optional
315+
The identifier of the data reduction/processing applied to the
316+
data. RAW (1), CALIBRATED (2), PRODUCT (3) or AUXILIARY (0)
317+
data_product_type : str, optional
318+
High level description of the product.
319+
image, spectrum or timeseries.
320+
intent : str, optional
321+
The intent of the original observer in acquiring this observation.
322+
SCIENCE or CALIBRATION
323+
collection : list of str, optional
324+
List of collections that are available in eHST catalogue.
325+
HLA, HST
326+
instrument_name : list of str, optional
327+
Name(s) of the instrument(s) used to generate the dataset
328+
filters : list of str, optional
329+
Name(s) of the filter(s) used to generate the dataset
330+
async_job : bool, optional, default 'False'
331+
executes the query (job) in asynchronous/synchronous mode (default
332+
synchronous)
333+
filename : str, default None
334+
Path and name of the file to store the results.
335+
If the filename is defined, the file will be
336+
automatically saved
337+
output_format : string
338+
results format. Options are:
339+
'votable': str, binary VOTable format
340+
'csv': str, comma-separated values format
341+
save : bool
342+
optional, default 'False'
343+
Flag to save the result in a file. If the filename
344+
is not defined, it will use a formatted name to save
345+
the file
346+
cache : bool
347+
optional, default 'True'
348+
Flag to save the results in the local cache
349+
verbose : bool, optional, default 'False'
350+
flag to display information about the process
351+
352+
Returns
353+
-------
354+
astropy.table.Table with the result of the cone_search
355+
"""
356+
crit_query = self.query_criteria(calibration_level=calibration_level,
357+
data_product_type=data_product_type,
358+
intent=intent,
359+
obs_collection=obs_collection,
360+
instrument_name=instrument_name,
361+
filters=filters,
362+
async_job=True,
363+
get_query=True)
364+
if crit_query.endswith(")"):
365+
crit_query = crit_query[:-1] + " AND "
284366
else:
285-
if save or filename is not None:
286-
if filename is None:
287-
filename = "cone." + str(output_format)
288-
shutil.move(response, filename)
289-
table = Table.read(filename, format=output_format)
290-
log.info("File has been saved in " + os.path.abspath(filename))
291-
else:
292-
fileobj = BytesIO(response.content)
293-
table = Table.read(fileobj, format=output_format)
294-
# TODO: add "correct units" material here
367+
crit_query = crit_query + " WHERE ("
368+
369+
if(target and coordinates):
370+
raise TypeError("Please use only target or coordinates as"
371+
"parameter.")
372+
if(target):
373+
try:
374+
ra, dec = self._query_tap_target(target)
375+
except Exception:
376+
raise ValueError('This target cannot be resolved')
377+
else:
378+
coord = self._getCoordInput(coordinates, "coordinate")
379+
ra_hours, dec = commons.coord_to_radec(coord)
380+
ra = ra_hours * 15.0 # Converts to degrees
381+
382+
radius_in_grades = float(radius/60) # Converts to degrees
383+
cone_query = "1=CONTAINS(POINT('ICRS', pos.ra, pos.dec),"\
384+
"CIRCLE('ICRS', {0}, {1}, {2}))".\
385+
format(str(ra), str(dec), str(radius_in_grades))
386+
query = "{}{})".format(crit_query, cone_query)
387+
if verbose:
388+
log.info(query)
295389

390+
table = self.query_hst_tap(query=query, async_job=async_job,
391+
output_file=filename,
392+
output_format=output_format,
393+
verbose=verbose)
296394
return table
297395

396+
def _query_tap_target(self, target):
397+
params = {"TARGET_NAME": target,
398+
"RESOLVER_TYPE": "SN",
399+
"FORMAT": "json"}
400+
target_response = self._request('GET',
401+
self.target_url,
402+
cache=True,
403+
params=params)
404+
target_result = target_response.json()['data'][0]
405+
ra = target_result['RA_DEGREES']
406+
dec = target_result['DEC_DEGREES']
407+
return ra, dec
408+
298409
def query_metadata(self, output_format='votable', verbose=False):
299410
return
300411

@@ -456,9 +567,10 @@ def query_criteria(self, calibration_level=None,
456567
parameters.append("(o.instrument_configuration LIKE '%{}%')"
457568
.format("%' OR o.instrument_configuration "
458569
"LIKE '%".join(filters)))
459-
query = "select o.*, p.calibration_level, p.data_product_type "\
460-
"from ehst.observation AS o LEFT JOIN ehst.plane as p "\
461-
"on o.observation_uuid=p.observation_uuid"
570+
query = "select o.*, p.calibration_level, p.data_product_type, "\
571+
"pos.ra, pos.dec from ehst.observation AS o JOIN "\
572+
"ehst.plane as p on o.observation_uuid=p.observation_uuid "\
573+
"JOIN ehst.position as pos on p.plane_id = pos.plane_id"
462574
if parameters:
463575
query += " where({})".format(" AND ".join(parameters))
464576
table = self.query_hst_tap(query=query, async_job=async_job,

astroquery/esa/hubble/tests/test_esa_hubble.py

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,12 @@ def test_query_criteria(self):
236236
'output_format': "votable",
237237
'verbose': False}
238238
parameters3 = {'query': "select o.*, p.calibration_level, "
239-
"p.data_product_type from ehst.observation "
240-
"AS o LEFT JOIN ehst.plane as p on "
241-
"o.observation_uuid=p.observation_uuid where("
239+
"p.data_product_type, pos.ra, pos.dec "
240+
"from ehst.observation "
241+
"AS o JOIN ehst.plane as p on "
242+
"o.observation_uuid=p.observation_uuid "
243+
"JOIN ehst.position as pos on "
244+
"p.plane_id = pos.plane_id where("
242245
"p.calibration_level LIKE '%PRODUCT%' AND "
243246
"p.data_product_type LIKE '%image%' AND "
244247
"o.intent LIKE '%SCIENCE%' AND (o.collection "
@@ -280,9 +283,12 @@ def test_query_criteria_numeric_calibration(self):
280283
'output_format': "votable",
281284
'verbose': False}
282285
parameters3 = {'query': "select o.*, p.calibration_level, "
283-
"p.data_product_type from ehst.observation "
284-
"AS o LEFT JOIN ehst.plane as p on "
285-
"o.observation_uuid=p.observation_uuid where("
286+
"p.data_product_type, pos.ra, pos.dec"
287+
" from ehst.observation "
288+
"AS o JOIN ehst.plane as p on "
289+
"o.observation_uuid=p.observation_uuid "
290+
"JOIN ehst.position as pos on p.plane_id "
291+
"= pos.plane_id where("
286292
"p.calibration_level LIKE '%RAW%' AND "
287293
"p.data_product_type LIKE '%image%' AND "
288294
"o.intent LIKE '%SCIENCE%' AND (o.collection "
@@ -309,6 +315,59 @@ def test_query_criteria_numeric_calibration(self):
309315
parameters1['get_query'])
310316
assert "Calibration level must be between 0 and 3" in err.value.args[0]
311317

318+
def test_cone_search_criteria(self):
319+
parameters1 = {'target': "m31",
320+
'radius': 7,
321+
'data_product_type': "image",
322+
'obs_collection': ['HST'],
323+
'instrument_name': ['ACS/WFC'],
324+
'filters': ['F435W'],
325+
'async_job': False,
326+
'filename': "output_test_query_by_criteria.vot.gz",
327+
'output_format': "votable",
328+
'verbose': True}
329+
test_query = "select o.*, p.calibration_level, p.data_product_type, "\
330+
"pos.ra, pos.dec from ehst.observation AS o JOIN "\
331+
"ehst.plane as p on o.observation_uuid=p.observation_"\
332+
"uuid JOIN ehst.position as pos on p.plane_id = "\
333+
"pos.plane_id where((o.collection LIKE '%HST%') AND "\
334+
"(o.instrument_name LIKE '%WFPC2%') AND "\
335+
"(o.instrument_configuration LIKE '%F606W%') AND "\
336+
"1=CONTAINS(POINT('ICRS', pos.ra, pos.dec),"\
337+
"CIRCLE('ICRS', 10.6847083, 41.26875, "\
338+
"0.11666666666666667)))"
339+
parameters3 = {'query': test_query,
340+
'output_file': "output_test_query_by_criteria.vot.gz",
341+
'output_format': "votable",
342+
'verbose': False}
343+
ehst = ESAHubbleClass(self.get_dummy_tap_handler())
344+
query_criteria_query = "select o.*, p.calibration_level, "\
345+
"p.data_product_type, pos.ra, pos.dec from "\
346+
"ehst.observation AS o JOIN ehst.plane as p "\
347+
"on o.observation_uuid=p.observation_uuid "\
348+
"JOIN ehst.position as pos on p.plane_id = "\
349+
"pos.plane_id where((o.collection LIKE "\
350+
"'%HST%') AND (o.instrument_name LIKE "\
351+
"'%WFPC2%') AND (o.instrument_configuration "\
352+
"LIKE '%F606W%'))"
353+
ehst.query_criteria = MagicMock(return_value=query_criteria_query)
354+
target = {'RA_DEGREES': '10.6847083', 'DEC_DEGREES': '41.26875'}
355+
ehst._query_tap_target = MagicMock(return_value=target)
356+
ehst.cone_search_criteria(target=parameters1['target'],
357+
radius=parameters1['radius'],
358+
data_product_type=parameters1
359+
['data_product_type'],
360+
obs_collection=parameters1['obs_collection'],
361+
instrument_name=parameters1
362+
['instrument_name'],
363+
filters=parameters1['filters'],
364+
async_job=parameters1['async_job'],
365+
filename=parameters1['filename'],
366+
output_format=parameters1['output_format'],
367+
verbose=parameters1['verbose'])
368+
dummy_tap_handler = DummyHubbleTapHandler("launch_job", parameters3)
369+
dummy_tap_handler.check_call("launch_job", parameters3)
370+
312371
def test_query_criteria_no_params(self):
313372
ehst = ESAHubbleClass(self.get_dummy_tap_handler())
314373
ehst.query_criteria(async_job=False,

0 commit comments

Comments
 (0)