Skip to content

Commit 809f309

Browse files
authored
Merge pull request #3386 from esdc-esac-esa-int/ESA_euclid_EUCLIDMNGT-1449_x-match
2 parents 822ecf0 + d4787d0 commit 809f309

File tree

5 files changed

+410
-7
lines changed

5 files changed

+410
-7
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ esa.euclid
3333

3434
- New method, get_scientific_product_list, to retrieve scientific LE3
3535
products. [#3313]
36+
- New cross-match method [#3386]
3637

3738
gaia
3839
^^^^

astroquery/esa/euclid/core.py

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from astroquery import log
2424
from astroquery.utils import commons
2525
from astroquery.utils.tap import TapPlus
26+
from astroquery.utils.tap import taputils
2627
from . import conf
2728

2829

@@ -116,6 +117,156 @@ def __init__(self, *, environment='PDR', tap_plus_conn_handler=None, datalink_ha
116117
if show_server_messages:
117118
self.get_status_messages()
118119

120+
def cross_match_basic(self, *, table_a_full_qualified_name, table_a_column_ra, table_a_column_dec,
121+
table_b_full_qualified_name=None, table_b_column_ra=None, table_b_column_dec=None,
122+
results_name=None,
123+
radius=1.0, background=False, verbose=False):
124+
"""Performs a positional cross-match between the specified tables.
125+
126+
This method carries out the following steps in one step:
127+
128+
1. updates the user table metadata to flag the positional RA/Dec columns;
129+
2. launches a positional cross-match as an asynchronous query;
130+
3. returns all the columns from both tables plus the angular distance (deg) for the cross-matched sources.
131+
132+
The result is a join table with the identifies of both tables and the distance (degrees), that is returned
133+
without metadata units. If desired, units can be added using the Units package of Astropy as follows:
134+
``results[‘separation’].unit = u.degree``. To speed up the cross-match, pass the biggest table to the
135+
``table_b_full_qualified_name`` parameter.
136+
TAP+ only
137+
138+
Parameters
139+
----------
140+
table_a_full_qualified_name : str, mandatory
141+
a full qualified table name (i.e. schema name and table name)
142+
table_a_column_ra : str, mandatory
143+
the ‘ra’ column in the table table_a_full_qualified_name
144+
table_a_column_dec : str, mandatory
145+
the ‘dec’ column in the table table_a_full_qualified_name
146+
table_b_full_qualified_name : str, optional, default the main_table associated to the selected environment
147+
a full qualified table name (i.e. schema name and table name)
148+
table_b_column_ra : str, optional, default the main_table_ra_column associated to the selected environment
149+
the ‘ra’ column in the table table_b_full_qualified_name
150+
table_b_column_dec : str, default the main_table_dec_column associated to the selected environment
151+
the ‘dec’ column in the table table_b_full_qualified_name
152+
results_name : str, optional, default None
153+
custom name defined by the user for the job that is going to be created
154+
radius : float (arc. seconds), str or astropy.coordinate, optional, default 1.0
155+
radius (valid range: 0.1-10.0). For an astropy.coordinate any angular unit is valid, but its value in arc
156+
sec must be contained within the valid range.
157+
background : bool, optional, default 'False'
158+
when the job is executed in asynchronous mode, this flag specifies
159+
whether the execution will wait until results are available
160+
verbose : bool, optional, default 'False'
161+
flag to display information about the process
162+
163+
Returns
164+
-------
165+
A Job object
166+
"""
167+
168+
radius_quantity = self.__get_radius_as_quantity_arcsec(radius)
169+
170+
radius_arc_sec = radius_quantity.value
171+
172+
if radius_arc_sec < 0.1 or radius_arc_sec > 10.0:
173+
raise ValueError(f"Invalid radius value. Found {radius_quantity}, valid range is: 0.1 to 10.0")
174+
175+
schema_a = self.__get_schema_name(table_a_full_qualified_name)
176+
if not schema_a:
177+
raise ValueError(f"Schema name is empty in full qualified table: '{table_a_full_qualified_name}'")
178+
179+
if table_b_full_qualified_name is None:
180+
table_b_full_qualified_name = self.main_table
181+
table_b_column_ra = self.main_table_ra
182+
table_b_column_dec = self.main_table_dec
183+
else:
184+
if table_b_column_ra is None or table_b_column_dec is None:
185+
raise ValueError(f"Invalid ra or dec column names: '{table_b_column_ra}' and '{table_b_column_dec}'")
186+
187+
schema_b = self.__get_schema_name(table_b_full_qualified_name)
188+
if not schema_b:
189+
raise ValueError(f"Schema name is empty in full qualified table: '{table_b_full_qualified_name}'")
190+
191+
table_metadata_a = self.__get_table_metadata(table_a_full_qualified_name, verbose)
192+
193+
table_metadata_b = self.__get_table_metadata(table_b_full_qualified_name, verbose)
194+
195+
self.__check_columns_exist(table_metadata_a, table_a_full_qualified_name, table_a_column_ra, table_a_column_dec)
196+
197+
self.__update_ra_dec_columns(table_a_full_qualified_name, table_a_column_ra, table_a_column_dec,
198+
table_metadata_a, verbose)
199+
200+
self.__check_columns_exist(table_metadata_b, table_b_full_qualified_name, table_b_column_ra, table_b_column_dec)
201+
202+
self.__update_ra_dec_columns(table_b_full_qualified_name, table_b_column_ra, table_b_column_dec,
203+
table_metadata_b, verbose)
204+
205+
query = (
206+
f"SELECT a.*, DISTANCE(a.{table_a_column_ra}, a.{table_a_column_dec}, b.{table_b_column_ra}, "
207+
f"b.{table_b_column_dec}) AS separation, b.* "
208+
f"FROM {table_a_full_qualified_name} AS a JOIN {table_b_full_qualified_name} AS b "
209+
f"ON DISTANCE(a.{table_a_column_ra}, a.{table_a_column_dec}, b.{table_b_column_ra}, b.{table_b_column_dec})"
210+
f" < {radius_quantity.to(u.deg).value}")
211+
212+
return self.launch_job_async(query=query, name=results_name, output_file=None, output_format="votable_gzip",
213+
verbose=verbose, dump_to_file=False, background=background, upload_resource=None,
214+
upload_table_name=None)
215+
216+
def __get_radius_as_quantity_arcsec(self, radius):
217+
"""
218+
transform the input radius into an astropy.Quantity in arc seconds
219+
"""
220+
if not isinstance(radius, units.Quantity):
221+
radius_quantity = Quantity(value=radius, unit=u.arcsec)
222+
else:
223+
radius_quantity = radius.to(u.arcsec)
224+
return radius_quantity
225+
226+
def __update_ra_dec_columns(self, full_qualified_table_name, column_ra, column_dec, table_metadata, verbose):
227+
"""
228+
Update table metadata for the ‘ra’ and the ‘dec’ columns in the input table
229+
"""
230+
if full_qualified_table_name.startswith("user_"):
231+
list_of_changes = list()
232+
for column in table_metadata.columns:
233+
if column.name == column_ra and column.flags != '1':
234+
list_of_changes.append([column_ra, "flags", "Ra"])
235+
list_of_changes.append([column_ra, "indexed", True])
236+
if column.name == column_dec and column.flags != '2':
237+
list_of_changes.append([column_dec, "flags", "Dec"])
238+
list_of_changes.append([column_dec, "indexed", True])
239+
240+
if list_of_changes:
241+
TapPlus.update_user_table(self, table_name=full_qualified_table_name, list_of_changes=list_of_changes,
242+
verbose=verbose)
243+
244+
def __check_columns_exist(self, table_metadata_a, full_qualified_table_name, column_ra, column_dec):
245+
"""
246+
Check whether the ‘ra’ and the ‘dec’ columns exists the input table
247+
"""
248+
column_names = [column.name for column in table_metadata_a.columns]
249+
if column_ra not in column_names or column_dec not in column_names:
250+
raise ValueError(
251+
f"Please check: columns {column_ra} or {column_dec} not available in the table '"
252+
f"{full_qualified_table_name}'")
253+
254+
def __get_table_metadata(self, full_qualified_table_name, verbose):
255+
"""
256+
Get the table metadata for the input table
257+
"""
258+
259+
return self.load_table(table=full_qualified_table_name, verbose=verbose)
260+
261+
def __get_schema_name(self, full_qualified_table_name):
262+
"""
263+
Get the schema name from the full qualified table
264+
"""
265+
schema = taputils.get_schema_name(full_qualified_table_name)
266+
if schema is None:
267+
raise ValueError(f"Not found schema name in full qualified table: '{full_qualified_table_name}'")
268+
return schema
269+
119270
def launch_job(self, query, *, name=None, dump_to_file=False, output_file=None, output_format="csv", verbose=False,
120271
upload_resource=None, upload_table_name=None):
121272
"""
@@ -282,10 +433,10 @@ def __cone_search(self, coordinate, radius, *, table_name=None, ra_column_name=N
282433
coordinates center point
283434
radius : astropy.units, mandatory
284435
radius
285-
table_name : str, optional, default main gaia table name doing the cone search against
286-
ra_column_name : str, optional, default ra column in main gaia table
436+
table_name : str, optional, default main table name doing the cone search against
437+
ra_column_name : str, optional, default ra column in main table
287438
ra column doing the cone search against
288-
dec_column_name : str, optional, default dec column in main gaia table
439+
dec_column_name : str, optional, default dec column in main table
289440
dec column doing the cone search against
290441
async_job : bool, optional, default 'False'
291442
executes the job in asynchronous/synchronous mode (default

0 commit comments

Comments
 (0)