102.6. Cross-match to uploaded table with TAP#
102.6. Cross-match to uploaded table with TAP¶
For the Rubin Science Platform at data.lsst.cloud.
Data Release: Data Preview 1
Container Size: Large
LSST Science Pipelines version: r29.2.0
Last verified to run: 2025-09-22
Repository: github.com/lsst/tutorial-notebooks
Learning objective: How to use the TAP service to cross-match to a user-uploaded table.
LSST data products: Object
table, MPCORB
table
Packages: lsst.rsp.get_tap_service
Credit: Originally developed by the Rubin Community Science team. Please consider acknowledging them if this notebook is used for the preparation of journal articles, software releases, or other notebooks.
Get Support: Everyone is encouraged to ask questions or raise issues in the Support Category of the Rubin Community Forum. Rubin staff will respond to all questions posted there.
1. Introduction¶
TAP provides standardized access to catalog data for discovery, search, and retrieval. Full documentation for TAP is provided by the International Virtual Observatory Alliance (IVOA).
Related tutorials: The other 100-level tutorials in this series demonstrate how to use the TAP service.
1.1. Import packages¶
Import general python package astropy
. Import the pyvo
module for accessing remote data. From the lsst
package, import the module for accessing the Table Access Protocol (TAP) service.
from astropy.table import Table
import pyvo
from lsst.rsp import get_tap_service
1.2. Define parameters¶
Instantiate the TAP service.
service = get_tap_service("tap")
assert service is not None
2. Cross-match with a table of coordinates¶
Load a user-defined table to upload to the TAP service and spatially cross-match to the Object
table, which includes detections in the coadded images.
This example uses a table of objects from the SDSS SkyServer within 0.5 degrees of the center of the northernmost DP1 field, the Low Ecliptic Latitude Field (RA = 37.86, Dec = 6.89).
2.1. Load a table with coordinates¶
Read the table as an astropy
table.
path = '/rubin/cst_repos/tutorial-notebooks-data/data/'
ut1 = Table.read(path + 'dp1_102_6_user_table_SDSS.csv', format='ascii.csv')
Option to display the table.
# ut1
2.2. Cross-match to the Object
table¶
Query the Object
table for objects matching the coordinates from the user-defined table, ut1. Use a spatial cross-match radius of 1 arcsec and retrieve the objectId, coordinates, and cModel magnitudes.
query = "SELECT ut1.objid AS ut1_objid, "\
"ut1.ra AS ut1_ra, ut1.dec AS ut1_dec, "\
"objectId, coord_ra, coord_dec, "\
"u_cModelMag, g_cModelMag, r_cModelMag, "\
"i_cModelMag, z_cModelMag, y_cModelMag "\
"FROM dp1.Object "\
"JOIN TAP_UPLOAD.ut1 AS ut1 "\
"WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec), "\
"CIRCLE('ICRS', ut1.ra, ut1.dec, 0.00027))=1 "
Run the query and upload the user-defined table. Retrieve the results.
job = service.submit_job(query, uploads={"ut1": ut1})
job.run()
job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', job.phase)
if job.phase == 'ERROR':
job.raise_if_error()
assert job.phase == 'COMPLETED'
results = job.fetch_result().to_table()
Job phase is COMPLETED
Display the results of the query.
results
ut1_objid | ut1_ra | ut1_dec | objectId | coord_ra | coord_dec | u_cModelMag | g_cModelMag | r_cModelMag | i_cModelMag | z_cModelMag | y_cModelMag |
---|---|---|---|---|---|---|---|---|---|---|---|
deg | deg | mag | mag | mag | mag | mag | mag | ||||
int64 | float64 | float64 | int64 | float64 | float64 | float32 | float32 | float32 | float32 | float32 | float32 |
1237670017269367163 | 38.0578934775496 | 7.4277059696483 | 648368125565216675 | 38.0578955158831 | 7.427678283659483 | -- | 22.3473 | 20.8504 | 19.1487 | 18.4172 | -- |
1237670017269367135 | 38.0380097296434 | 7.4208625344998 | 648368125565216779 | 38.03799154665733 | 7.420815523624187 | -- | 20.4955 | 19.9307 | 19.5524 | 19.5198 | -- |
1237670017269367138 | 38.0405014279851 | 7.42558199894606 | 648368125565216865 | 38.04049812733134 | 7.425580160378057 | -- | 20.8585 | 20.2295 | 19.7944 | 19.9894 | -- |
1237670017269367194 | 38.0858491374902 | 7.42454384847825 | 648368125565216816 | 38.085855234143565 | 7.424561461984984 | -- | 21.0139 | 20.002 | 19.3324 | 19.034 | -- |
1237670017269367161 | 38.0561088297459 | 7.42484433760601 | 648368125565216820 | 38.05609481494671 | 7.4248269172911785 | -- | 22.4634 | 22.0107 | 21.7796 | 21.7385 | -- |
1237670017269366997 | 38.0345148104381 | 7.4302562915662 | 648368125565216616 | 38.03446533924158 | 7.430339999182441 | -- | 21.4401 | 19.9154 | 18.5918 | 18.0448 | -- |
1237670017269367024 | 38.0358699730184 | 7.42752170257929 | 648368125565216617 | 38.03588820804813 | 7.427517859611573 | -- | 20.8136 | 20.288 | 20.0384 | 19.9923 | -- |
1237670017269367361 | 38.0465434395526 | 7.41538105849588 | 648368125565215782 | 38.04654266667325 | 7.415426976515737 | -- | 23.1355 | 22.2494 | 21.8968 | 21.6267 | -- |
1237670017269367109 | 38.0264696213815 | 7.42906608949243 | 648368125565216618 | 38.02646930429519 | 7.429094194827333 | -- | 22.3579 | 21.7662 | 21.4726 | 21.4218 | -- |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1237680003065774789 | 38.2605421721046 | 6.7126589402092 | 648372798489635074 | 38.26052213261512 | 6.71269535424419 | -- | 20.7357 | 19.5352 | 19.0385 | 18.777 | -- |
1237680003065774815 | 38.2778318667821 | 6.71484705182021 | 648372798489635052 | 38.27785651825313 | 6.714768978994745 | -- | 22.6205 | 21.862 | 21.3553 | 21.525 | -- |
1237680003065774308 | 38.2822367883301 | 6.71349131691253 | 648372798489635128 | 38.28228840862636 | 6.713495137258602 | -- | 19.2671 | 17.9494 | 16.9499 | 16.5073 | -- |
1237680003065774733 | 38.2202222347535 | 6.72175054206644 | 648372798489635317 | 38.220227889044 | 6.721716384618791 | -- | 22.1347 | 21.4248 | 21.0575 | 20.7602 | -- |
1237680003065774691 | 38.1982135852548 | 6.72249714692861 | 648372798489635342 | 38.19821159150923 | 6.7225412408182175 | -- | 21.545 | 20.7752 | 20.4199 | 20.2384 | -- |
1237680003065774776 | 38.2424839276331 | 6.72272407702287 | 648372798489635366 | 38.24246884996766 | 6.722750396985676 | -- | 22.5934 | 21.7246 | 21.0439 | 20.8355 | -- |
1237680003065774724 | 38.215926516263 | 6.72260225163418 | 648372798489635322 | 38.21591213802974 | 6.7226326470092586 | -- | 21.1112 | 20.4534 | 20.1532 | 20.0256 | -- |
1237680003065774303 | 38.2567832724226 | 6.72286318889888 | 648372798489635285 | 38.25676650279843 | 6.72288514836295 | -- | 19.8377 | 19.0242 | 18.6041 | 18.4021 | -- |
1237680003065774948 | 38.198431224028 | 6.71973362889175 | 648372798489635267 | 38.198448369105975 | 6.719743410461669 | -- | 23.5211 | 22.2351 | 21.5494 | 21.0067 | -- |
del results, query, ut1
3. Cross-match with table of identifiers¶
Query an external database for solar system objects and cross-match it to the MPCORB
table using the Minor Planet Catalog (MPC) designation.
This example uses a table of moving objects retrieved from the Gaia database using PyVO.
3.1. Query the Gaia database using TAP¶
Instantiate the Gaia TAP service using PyVO
.
gaia_tap_url = 'https://gea.esac.esa.int/tap-server/tap'
gaia_tap = pyvo.dal.TAPService(gaia_tap_url)
assert gaia_tap is not None
assert gaia_tap.baseurl == gaia_tap_url
Query the Gaia sso_orbits
table for main-belt asteroids (MBAs). Constrain the query to 1000 objects, with semimajor axis a
and q
following the definition of MBA used by the
JPL Horizons small body database query tool (
2.0 < a
< 3.25 au and q
> 1.666 au).
gaia_query = "SELECT TOP 1000 denomination, inclination, "\
"eccentricity, semi_major_axis "\
"FROM gaiadr3.sso_orbits "\
"WHERE num_observations > 200 "\
"AND semi_major_axis > 2.0 "\
"AND semi_major_axis < 3.25 "\
"AND semi_major_axis*(1-eccentricity) > 1.666 "
Run the query and retrieve the results as a pandas DataFrame.
gaia_job = gaia_tap.submit_job(gaia_query)
gaia_job.run()
gaia_job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', gaia_job.phase)
assert gaia_job.phase == 'COMPLETED'
gaia_results = gaia_job.fetch_result().to_table().to_pandas()
Job phase is COMPLETED
Display the results of the query.
gaia_results
denomination | inclination | eccentricity | semi_major_axis | |
---|---|---|---|---|
0 | 2000_so179 | 0.058380 | 0.120836 | 2.701355 |
1 | 1999_gu8 | 0.113790 | 0.208611 | 2.203320 |
2 | 2000_ge143 | 0.185379 | 0.074487 | 3.091090 |
3 | 5082_t-3 | 0.097229 | 0.149435 | 2.313023 |
4 | 2000_su299 | 0.227229 | 0.075321 | 3.169976 |
... | ... | ... | ... | ... |
995 | 1999_jx102 | 0.107432 | 0.114787 | 3.137027 |
996 | 1993_fv82 | 0.049898 | 0.202413 | 2.189258 |
997 | 1999_jv24 | 0.267638 | 0.046316 | 2.570573 |
998 | andrewchiang | 0.147260 | 0.037843 | 3.062983 |
999 | 2001_qs256 | 0.079710 | 0.130844 | 2.671761 |
1000 rows × 4 columns
3.2. Reformat the Gaia results table¶
Format the denomination
column in the Gaia results table to match the standard format for MPC designation used in the MPCORB
table. Create a new column from the Gaia denomination
column with underscores removed and letters capitalized.
gaia_results['mpc_designation'] = gaia_results['denomination'].str.replace('_', ' ').str.upper()
Option to display the table.
# gaia_results
Convert the pandas
DataFrame to an astropy
table and check the data type of the columns.
ut2 = Table.from_pandas(gaia_results)
ut2.dtype
dtype([('denomination', '<U15'), ('inclination', '<f8'), ('eccentricity', '<f8'), ('semi_major_axis', '<f8'), ('mpc_designation', '<U15')])
Columns with data type Unicode are not supported for table uploads. Convert the data type from Unicode to bytestring.
ut2.convert_unicode_to_bytestring()
3.3. Cross-match to the MPCORB
table¶
Query the MPCORB
table for objects matching the MPC designation from the Gaia results table, ut2. Retrieve the SSObjectId for the matches.
query = "SELECT ut2.mpc_designation AS ut2_mpcDesignation, "\
"mpc.mpcDesignation, mpc.ssObjectId "\
"FROM dp1.MPCORB AS mpc "\
"JOIN TAP_UPLOAD.ut2 as ut2 "\
"ON ut2.mpc_designation = mpc.mpcDesignation "
Run the query and upload the user-defined table. Retrieve the results.
job = service.submit_job(query, uploads={"ut2": ut2})
job.run()
job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', job.phase)
if job.phase == 'ERROR':
job.raise_if_error()
assert job.phase == 'COMPLETED'
results = job.fetch_result().to_table()
Job phase is COMPLETED
Display the results of the query.
results
ut2_mpcDesignation | mpcDesignation | ssObjectId |
---|---|---|
str15 | str13 | int64 |
2000 SO179 | 2000 SO179 | 21163607367104847 |
1999 GU8 | 1999 GU8 | 20892066446850133 |
2000 GE143 | 2000 GE143 | 21163607165580101 |
5082 T-3 | 5082 T-3 | 23700330511349810 |
2000 SU299 | 2000 SU299 | 21163607367891285 |
2000 ST343 | 2000 ST343 | 21163607368217428 |
1999 JC31 | 1999 JC31 | 20892066497376579 |
1999 RN133 | 1999 RN133 | 20892066632708942 |
1999 XO23 | 1999 XO23 | 20892066732192591 |
... | ... | ... |
2001 DT2 | 2001 DT2 | 21163611408839252 |
2001 SK183 | 2001 SK183 | 21163611662136139 |
1999 XR215 | 1999 XR215 | 20892066733897042 |
1979 OH1 | 1979 OH1 | 20889867557810504 |
1999 GF35 | 1999 GF35 | 20892066447045958 |
1996 LZ | 1996 LZ | 20892053645832282 |
1999 JX102 | 1999 JX102 | 20892066498294360 |
1993 FV82 | 1993 FV82 | 20892040660791894 |
1999 JV24 | 1999 JV24 | 20892066497311830 |
2001 QS256 | 2001 QS256 | 21163611629041235 |
del results, query, ut2