16
16
17
17
18
18
"""
19
- from astroquery .utils import commons
20
19
from astropy import units
21
20
from astropy .coordinates import SkyCoord
22
21
from astropy .coordinates import Angle
23
- from astropy .units import Quantity
24
22
from astroquery .utils .tap .core import TapPlus
25
- from astroquery .utils .tap .model import modelutils
26
23
from astroquery .query import BaseQuery
27
- from astropy .table import Table
28
- from io import BytesIO
29
24
import shutil
30
- import os
31
- import json
32
25
33
26
from . import conf
34
27
from astroquery import log
@@ -119,14 +112,105 @@ def download_product(self, observation_id, *, calibration_level=None,
119
112
120
113
shutil .move (response , filename )
121
114
115
+ def get_member_observations (self , observation_id ):
116
+ """
117
+ Returns the related members of simple and composite observations
118
+
119
+ Parameters
120
+ ----------
121
+ observation_id : str
122
+ Observation identifier.
123
+
124
+ Returns
125
+ -------
126
+ A list of strings with the observation_id of the associated
127
+ observations
128
+ """
129
+ observation_type = self .get_observation_type (observation_id )
130
+
131
+ if 'Composite' in observation_type :
132
+ oids = self ._select_related_members (observation_id )
133
+ elif 'Simple' in observation_type :
134
+ oids = self ._select_related_composite (observation_id )
135
+ else :
136
+ raise ValueError ("Invalid observation id" )
137
+ return oids
138
+
139
+ def get_hap_hst_link (self , observation_id ):
140
+ """
141
+ Returns the related members of hap and hst observations
142
+
143
+ Parameters
144
+ ----------
145
+ observation_id : string
146
+ id of the observation to be downloaded, mandatory
147
+ The identifier of the observation we want to retrieve, regardless
148
+ of whether it is simple or composite.
149
+
150
+ Returns
151
+ -------
152
+ A list of strings with the observation_id of the associated
153
+ observations
154
+ """
155
+ observation_type = self .get_observation_type (observation_id )
156
+ if 'Composite' in observation_type :
157
+ raise ValueError ("HAP-HST link is only available for simple observations. Input observation is Composite." )
158
+ elif 'HAP' in observation_type :
159
+ oids = self ._select_related_members (observation_id )
160
+ elif 'HST' in observation_type :
161
+ query = f"select observation_id from ehst.observation where obs_type='HAP Simple' and members like '%{ observation_id } %'"
162
+ job = self .query_hst_tap (query = query )
163
+ oids = job ["observation_id" ].pformat (show_name = False )
164
+ else :
165
+ raise ValueError ("Invalid observation id" )
166
+ return oids
167
+
168
+ def get_observation_type (self , observation_id ):
169
+ """
170
+ Returns the type of an observation
171
+
172
+ Parameters
173
+ ----------
174
+ observation_id : string
175
+ id of the observation to be downloaded, mandatory
176
+ The identifier of the observation we want to retrieve, regardless
177
+ of whether it is simple or composite.
178
+
179
+ Returns
180
+ -------
181
+ String with the observation type
182
+ """
183
+ if observation_id is None :
184
+ raise ValueError ("Please input an observation id" )
185
+
186
+ query = f"select obs_type from ehst.observation where observation_id='{ observation_id } '"
187
+ job = self .query_hst_tap (query = query )
188
+ if any (job ["obs_type" ]):
189
+ obs_type = self ._get_decoded_string (string = job ["obs_type" ][0 ])
190
+ else :
191
+ raise ValueError ("Invalid Observation ID" )
192
+ return obs_type
193
+
194
+ def _select_related_members (self , observation_id ):
195
+ query = f"select members from ehst.observation where observation_id='{ observation_id } '"
196
+ job = self .query_hst_tap (query = query )
197
+ oids = self ._get_decoded_string (string = job ["members" ][0 ]).replace ("caom:HST/" , "" ).split (" " )
198
+ return oids
199
+
200
+ def _select_related_composite (self , observation_id ):
201
+ query = f"select observation_id from ehst.observation where members like '%{ observation_id } %'"
202
+ job = self .query_hst_tap (query = query )
203
+ oids = job ["observation_id" ].pformat (show_name = False )
204
+ return oids
205
+
122
206
def __validate_product_type (self , product_type ):
123
- if (product_type not in self .product_types ):
207
+ if (product_type not in self .product_types ):
124
208
raise ValueError ("This product_type is not allowed" )
125
209
126
210
def _get_product_filename (self , product_type , filename ):
127
- if (product_type == "PRODUCT" ):
211
+ if (product_type == "PRODUCT" ):
128
212
return filename
129
- elif (product_type == "SCIENCE_PRODUCT" ):
213
+ elif (product_type == "SCIENCE_PRODUCT" ):
130
214
log .info ("This is a SCIENCE_PRODUCT, the filename will be "
131
215
"renamed to " + filename + ".fits.gz" )
132
216
return filename + ".fits.gz"
@@ -263,27 +347,27 @@ def cone_search(self, coordinates, radius, filename=None,
263
347
radius_in_grades = radius .to (units .deg ).value
264
348
ra = coord .ra .deg
265
349
dec = coord .dec .deg
266
- query = "select o.observation_id, " \
267
- "o.start_time, o.end_time, o.start_time_mjd, " \
268
- "o.end_time_mjd, o.exposure_duration, o.release_date, " \
269
- "o.run_id, o.program_id, o.set_id, o.collection, " \
270
- "o.members_number, o.instrument_configuration, " \
271
- "o.instrument_name, o.obs_type, o.target_moving, " \
272
- "o.target_name, o.target_description, o.proposal_id, " \
273
- "o.pi_name, prop.title, pl.metadata_provenance, " \
274
- "pl.data_product_type, pl.software_version, pos.ra, " \
275
- "pos.dec, pos.gal_lat, pos.gal_lon, pos.ecl_lat, " \
276
- "pos.ecl_lon, pos.fov_size, en.wave_central, " \
277
- "en.wave_bandwidth, en.wave_max, en.wave_min, " \
278
- "en.filter from ehst.observation o join ehst.proposal " \
279
- "prop on o.proposal_id=prop.proposal_id join ehst.plane " \
280
- "pl on pl.observation_id=o.observation_id join " \
281
- "ehst.position pos on pos.plane_id = pl.plane_id join " \
282
- "ehst.energy en on en.plane_id=pl.plane_id where " \
283
- "pl.main_science_plane='true' and 1=CONTAINS(POINT('ICRS', " \
284
- "pos.ra, pos.dec),CIRCLE('ICRS', {0 }, {1 }, {2 })) order " \
285
- "by prop.proposal_id desc" . format ( str ( ra ), str ( dec ),
286
- str (radius_in_grades ))
350
+ query = ( "select o.observation_id, "
351
+ "o.start_time, o.end_time, o.start_time_mjd, "
352
+ "o.end_time_mjd, o.exposure_duration, o.release_date, "
353
+ "o.run_id, o.program_id, o.set_id, o.collection, "
354
+ "o.members_number, o.instrument_configuration, "
355
+ "o.instrument_name, o.obs_type, o.target_moving, "
356
+ "o.target_name, o.target_description, o.proposal_id, "
357
+ "o.pi_name, prop.title, pl.metadata_provenance, "
358
+ "pl.data_product_type, pl.software_version, pos.ra, "
359
+ "pos.dec, pos.gal_lat, pos.gal_lon, pos.ecl_lat, "
360
+ "pos.ecl_lon, pos.fov_size, en.wave_central, "
361
+ "en.wave_bandwidth, en.wave_max, en.wave_min, "
362
+ "en.filter from ehst.observation o join ehst.proposal "
363
+ "prop on o.proposal_id=prop.proposal_id join ehst.plane "
364
+ "pl on pl.observation_id=o.observation_id join "
365
+ "ehst.position pos on pos.plane_id = pl.plane_id join "
366
+ "ehst.energy en on en.plane_id=pl.plane_id where "
367
+ "pl.main_science_plane='true' and 1=CONTAINS(POINT('ICRS', "
368
+ f "pos.ra, pos.dec),CIRCLE('ICRS', { str ( ra ) } , { str ( dec ) } , { str ( radius_in_grades ) } )) order "
369
+ "by prop.proposal_id desc" )
370
+ print ( "type: " + str (type ( query ) ))
287
371
if verbose :
288
372
log .info (query )
289
373
table = self .query_hst_tap (query = query , async_job = async_job ,
@@ -380,19 +464,20 @@ def cone_search_criteria(self, radius, target=None,
380
464
raise TypeError ("Please use only target or coordinates as"
381
465
"parameter." )
382
466
if target :
383
- ra , dec = self ._query_tap_target (target )
467
+ coord = self ._query_tap_target (target )
384
468
else :
385
469
coord = self ._getCoordInput (coordinates )
386
- ra = coord .ra .deg
387
- dec = coord .dec .deg
470
+
471
+ ra = coord .ra .deg
472
+ dec = coord .dec .deg
388
473
389
474
if type (radius ) == int or type (radius ) == float :
390
475
radius_in_grades = Angle (radius , units .arcmin ).deg
391
476
else :
392
477
radius_in_grades = radius .to (units .deg ).value
393
- cone_query = "1=CONTAINS(POINT('ICRS', pos.ra, pos.dec)," \
394
- "CIRCLE('ICRS', {0}, {1}, {2}))" .\
395
- format (str (ra ), str (dec ), str (radius_in_grades ))
478
+ cone_query = "1=CONTAINS(POINT('ICRS', pos.ra, pos.dec)," \
479
+ "CIRCLE('ICRS', {0}, {1}, {2}))" . \
480
+ format (str (ra ), str (dec ), str (radius_in_grades ))
396
481
query = "{}{})" .format (crit_query , cone_query )
397
482
if verbose :
398
483
log .info (query )
@@ -415,15 +500,15 @@ def _query_tap_target(self, target):
415
500
target_result = target_response .json ()['data' ][0 ]
416
501
ra = target_result ['RA_DEGREES' ]
417
502
dec = target_result ['DEC_DEGREES' ]
418
- return ra , dec
503
+ return SkyCoord ( ra = ra , dec = dec , unit = "deg" )
419
504
except KeyError as e :
420
505
raise ValueError ("This target cannot be resolved" )
421
506
422
507
def query_metadata (self , output_format = 'votable' , verbose = False ):
423
508
return
424
509
425
- def query_target (self , name , filename = None , output_format = 'votable' ,
426
- verbose = False ):
510
+ def query_target (self , name , * , filename = None , output_format = 'votable' ,
511
+ verbose = False , async_job = False , radius = 7 ):
427
512
"""
428
513
It executes a query over EHST and download the xml with the results.
429
514
@@ -439,34 +524,22 @@ def query_target(self, name, filename=None, output_format='votable',
439
524
verbose : bool
440
525
optional, default 'False'
441
526
Flag to display information about the process
527
+ async_job : bool, optional, default 'False'
528
+ executes the query (job) in asynchronous/synchronous mode (default
529
+ synchronous)
530
+ radius : int
531
+ optional, default 7
532
+ radius in arcmin (int, float) or quantity of the cone_search
442
533
443
534
Returns
444
535
-------
445
536
Table with the result of the query. It downloads metadata as a file.
446
537
"""
538
+ coordinates = self ._query_tap_target (name )
539
+ table = self .cone_search (coordinates , radius , filename = filename , output_format = output_format ,
540
+ verbose = verbose , async_job = async_job )
447
541
448
- params = {"RESOURCE_CLASS" : "OBSERVATION" ,
449
- "USERNAME" : "ehst-astroquery" ,
450
- "SELECTED_FIELDS" : "OBSERVATION" ,
451
- "QUERY" : "(TARGET.TARGET_NAME=='" + name + "')" ,
452
- "RETURN_TYPE" : str (output_format )}
453
- response = self ._request ('GET' , self .metadata_url , save = True ,
454
- cache = True ,
455
- params = params )
456
-
457
- if verbose :
458
- log .info (self .metadata_url + "?RESOURCE_CLASS=OBSERVATION&"
459
- "SELECTED_FIELDS=OBSERVATION&QUERY=(TARGET.TARGET_NAME"
460
- "=='" + name + "')&USERNAME=ehst-astroquery&"
461
- "RETURN_TYPE=" + str (output_format ))
462
- log .info (self .copying_string .format (filename ))
463
- if filename is None :
464
- filename = "target.xml"
465
-
466
- shutil .move (response , filename )
467
-
468
- return modelutils .read_results_table_from_file (filename ,
469
- str (output_format ))
542
+ return table
470
543
471
544
def query_hst_tap (self , query , async_job = False , output_file = None ,
472
545
output_format = "votable" , verbose = False ):
@@ -495,13 +568,12 @@ def query_hst_tap(self, query, async_job=False, output_file=None,
495
568
job = self ._tap .launch_job_async (query = query ,
496
569
output_file = output_file ,
497
570
output_format = output_format ,
498
- verbose = False ,
499
- dump_to_file = output_file
500
- is not None )
571
+ verbose = verbose ,
572
+ dump_to_file = output_file is not None )
501
573
else :
502
574
job = self ._tap .launch_job (query = query , output_file = output_file ,
503
575
output_format = output_format ,
504
- verbose = False ,
576
+ verbose = verbose ,
505
577
dump_to_file = output_file is not None )
506
578
table = job .get_results ()
507
579
return table
@@ -582,9 +654,9 @@ def query_criteria(self, calibration_level=None,
582
654
parameters .append ("(o.instrument_configuration LIKE '%{}%')"
583
655
.format ("%' OR o.instrument_configuration "
584
656
"LIKE '%" .join (filters )))
585
- query = "select o.*, p.calibration_level, p.data_product_type, " \
586
- "pos.ra, pos.dec from ehst.observation AS o JOIN " \
587
- "ehst.plane as p on o.observation_uuid=p.observation_uuid " \
657
+ query = "select o.*, p.calibration_level, p.data_product_type, " \
658
+ "pos.ra, pos.dec from ehst.observation AS o JOIN " \
659
+ "ehst.plane as p on o.observation_uuid=p.observation_uuid " \
588
660
"JOIN ehst.position as pos on p.plane_id = pos.plane_id"
589
661
if parameters :
590
662
query += " where({})" .format (" AND " .join (parameters ))
@@ -600,7 +672,7 @@ def query_criteria(self, calibration_level=None,
600
672
601
673
def __get_calibration_level (self , calibration_level ):
602
674
condition = ""
603
- if (calibration_level is not None ):
675
+ if (calibration_level is not None ):
604
676
if isinstance (calibration_level , str ):
605
677
condition = calibration_level
606
678
elif isinstance (calibration_level , int ):
@@ -696,5 +768,11 @@ def _getCoordInput(self, value):
696
768
else :
697
769
return value
698
770
771
+ def _get_decoded_string (self , string ):
772
+ try :
773
+ return string .decode ('utf-8' )
774
+ except (UnicodeDecodeError , AttributeError ):
775
+ return string
776
+
699
777
700
778
ESAHubble = ESAHubbleClass ()
0 commit comments