Skip to content

Commit 0d351b5

Browse files
Jorge Fernandez HernandezJorge Fernandez Hernandez
authored andcommitted
EUCLIDMNGT-1449 new x--match for the Euclid module
1 parent f553876 commit 0d351b5

File tree

5 files changed

+402
-7
lines changed

5 files changed

+402
-7
lines changed

CHANGES.rst

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

3232
- New method, get_scientific_product_list, to retrieve scientific LE3
3333
products. [#3313]
34+
- New cross-match method [#3386]
3435

3536
gaia
3637
^^^^

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='', table_b_column_ra='', table_b_column_dec='',
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 not table_b_full_qualified_name:
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+
184+
schema_b = self.__get_schema_name(table_b_full_qualified_name)
185+
if not schema_b:
186+
raise ValueError(f"Schema name is empty in full qualified table: '{table_b_full_qualified_name}'")
187+
188+
table_metadata_a = self.__get_table_metadata(table_a_full_qualified_name, verbose)
189+
190+
table_metadata_b = self.__get_table_metadata(table_b_full_qualified_name, verbose)
191+
192+
self.__check_columns_exist(table_metadata_a, table_a_full_qualified_name, table_a_column_ra, table_a_column_dec)
193+
194+
self.__update_ra_dec_columns(table_a_full_qualified_name, table_a_column_ra, table_a_column_dec,
195+
table_metadata_a, verbose)
196+
197+
self.__check_columns_exist(table_metadata_b, table_b_full_qualified_name, table_b_column_ra, table_b_column_dec)
198+
199+
self.__update_ra_dec_columns(table_b_full_qualified_name, table_b_column_ra, table_b_column_dec,
200+
table_metadata_b, verbose)
201+
202+
query = (
203+
f"SELECT a.*, DISTANCE(a.{table_a_column_ra}, a.{table_a_column_dec}, b.{table_b_column_ra}, "
204+
f"b.{table_b_column_dec}) AS separation, b.* "
205+
f"FROM {table_a_full_qualified_name} AS a JOIN {table_b_full_qualified_name} AS b "
206+
f"ON DISTANCE(a.{table_a_column_ra}, a.{table_a_column_dec}, b.{table_b_column_ra}, b.{table_b_column_dec})"
207+
f" < {radius_quantity.to(u.deg).value}")
208+
209+
return self.launch_job_async(query=query, name=results_name, output_file=None, output_format="votable_gzip",
210+
verbose=verbose, dump_to_file=False, background=background, upload_resource=None,
211+
upload_table_name=None)
212+
213+
def __get_radius_as_quantity_arcsec(self, radius):
214+
"""
215+
transform the input radius into an astropy.Quantity in arc seconds
216+
"""
217+
if not isinstance(radius, units.Quantity):
218+
radius_quantity = Quantity(value=radius, unit=u.arcsec)
219+
else:
220+
radius_quantity = radius.to(u.arcsec)
221+
return radius_quantity
222+
223+
def __update_ra_dec_columns(self, full_qualified_table_name, column_ra, column_dec, table_metadata, verbose):
224+
"""
225+
Update table metadata for the ‘ra’ and the ‘dec’ columns in the input table
226+
"""
227+
if full_qualified_table_name.startswith("user_"):
228+
list_of_changes = list()
229+
for column in table_metadata.columns:
230+
if column.name == column_ra and column.flags != '1':
231+
list_of_changes.append([column_ra, "flags", "Ra"])
232+
list_of_changes.append([column_ra, "indexed", True])
233+
if column.name == column_dec and column.flags != '2':
234+
list_of_changes.append([column_dec, "flags", "Dec"])
235+
list_of_changes.append([column_dec, "indexed", True])
236+
237+
if list_of_changes:
238+
TapPlus.update_user_table(self, table_name=full_qualified_table_name, list_of_changes=list_of_changes,
239+
verbose=verbose)
240+
241+
def __check_columns_exist(self, table_metadata_a, full_qualified_table_name, column_ra, column_dec):
242+
"""
243+
Check whether the ‘ra’ and the ‘dec’ columns exists the input table
244+
"""
245+
column_names = [column.name for column in table_metadata_a.columns]
246+
if column_ra not in column_names or column_dec not in column_names:
247+
raise ValueError(
248+
f"Please check: columns {column_ra} or {column_dec} not available in the table '"
249+
f"{full_qualified_table_name}'")
250+
251+
def __get_table_metadata(self, full_qualified_table_name, verbose):
252+
"""
253+
Get the table metadata for the input table
254+
"""
255+
try:
256+
table_metadata = self.load_table(table=full_qualified_table_name, verbose=verbose)
257+
except Exception:
258+
raise ValueError(f"Not found table '{full_qualified_table_name}' in the archive")
259+
return table_metadata
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)