|
23 | 23 | from astroquery import log
|
24 | 24 | from astroquery.utils import commons
|
25 | 25 | from astroquery.utils.tap import TapPlus
|
| 26 | +from astroquery.utils.tap import taputils |
26 | 27 | from . import conf
|
27 | 28 |
|
28 | 29 |
|
@@ -116,6 +117,156 @@ def __init__(self, *, environment='PDR', tap_plus_conn_handler=None, datalink_ha
|
116 | 117 | if show_server_messages:
|
117 | 118 | self.get_status_messages()
|
118 | 119 |
|
| 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 | + |
119 | 270 | def launch_job(self, query, *, name=None, dump_to_file=False, output_file=None, output_format="csv", verbose=False,
|
120 | 271 | upload_resource=None, upload_table_name=None):
|
121 | 272 | """
|
@@ -282,10 +433,10 @@ def __cone_search(self, coordinate, radius, *, table_name=None, ra_column_name=N
|
282 | 433 | coordinates center point
|
283 | 434 | radius : astropy.units, mandatory
|
284 | 435 | 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 |
287 | 438 | 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 |
289 | 440 | dec column doing the cone search against
|
290 | 441 | async_job : bool, optional, default 'False'
|
291 | 442 | executes the job in asynchronous/synchronous mode (default
|
|
0 commit comments