Skip to content

Commit a7f6306

Browse files
committed
changelog, check for catalog matches
1 parent 79ba695 commit a7f6306

File tree

6 files changed

+146
-43
lines changed

6 files changed

+146
-43
lines changed

CHANGES.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ utils.tap
4343
- The method ``upload_table`` accepts file formats accepted by astropy's
4444
``Table.read()``. [#3295]
4545

46+
mast
47+
^^^^
48+
49+
- Added ``resolver`` parameter to query methods to specify the resolver to use when resolving object names to coordinates. [#3292]
50+
51+
- Added ``resolve_all`` parameter to ``MastClass.resolve_object`` to resolve object names and return
52+
coordinates for all available resolvers. [#3292]
53+
4654

4755
Infrastructure, Utility and Other Changes and Additions
4856
-------------------------------------------------------

astroquery/mast/tests/data/README.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,16 @@ To generate `~astroquery.mast.tests.data.mast_relative_path.json`, use the follo
4848
... {'uri': ['mast:HST/product/u9o40504m_c3m.fits', 'mast:HST/product/does_not_exist.fits']})
4949
>>> with open('mast_relative_path.json', 'w') as file:
5050
... json.dump(resp.json(), file, indent=4) # doctest: +SKIP
51+
52+
53+
To generate `~astroquery.mast.tests.data.resolver.json`, use the following:
54+
55+
.. doctest-remote-data::
56+
57+
>>> import json
58+
>>> from astroquery.mast import utils
59+
...
60+
>>> resp = utils._simple_request('http://mastresolver.stsci.edu/Santa-war/query',
61+
... {'name': 'TIC 307210830', 'outputFormat': 'json', 'resolveAll': 'true'})
62+
>>> with open('resolver.json', 'w') as file:
63+
... json.dump(resp.json(), file, indent=4) # doctest: +SKIP
Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,39 @@
1-
{"resolvedCoordinate":[{"searchString":"m103","resolver":"NED","cached":true,"resolverTime":114,"cacheDate":"Apr 19, 2017 2:32:38 PM","searchRadius":-1.0,"canonicalName":"MESSIER 103","ra":23.34086,"decl":60.658,"objectType":"*Cl"}],"status":""}
1+
{
2+
"resolvedCoordinate": [
3+
{
4+
"searchString": "tic 307210830",
5+
"resolver": "TIC",
6+
"cached": false,
7+
"resolverTime": 3,
8+
"searchRadius": 0.000333,
9+
"canonicalName": "TIC 307210830",
10+
"ra": 124.531756290083,
11+
"decl": -68.3129998725044
12+
},
13+
{
14+
"searchString": "tic 307210830",
15+
"resolver": "SIMBADCFA",
16+
"cached": true,
17+
"resolverTime": 289,
18+
"cacheDate": "Mar 19, 2025, 4:36:27 PM",
19+
"searchRadius": -1.0,
20+
"canonicalName": "L 98-59",
21+
"ra": 124.5317560026638,
22+
"decl": -68.3130014904408,
23+
"objectType": "HighPM*"
24+
},
25+
{
26+
"searchString": "tic 307210830",
27+
"resolver": "SIMBAD",
28+
"cached": true,
29+
"resolverTime": 299,
30+
"cacheDate": "Apr 17, 2025, 3:47:59 PM",
31+
"searchRadius": -1.0,
32+
"canonicalName": "L 98-59",
33+
"ra": 124.5317560026638,
34+
"decl": -68.3130014904408,
35+
"objectType": "HighPM*"
36+
}
37+
],
38+
"status": ""
39+
}

astroquery/mast/tests/test_mast.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -492,26 +492,32 @@ def test_mast_query(patch_post):
492492

493493

494494
def test_resolve_object(patch_post):
495-
coord = SkyCoord(23.34086, 60.658, unit='deg')
496-
m103_loc = mast.Mast.resolve_object("M103")
497-
assert round(m103_loc.separation(coord).value, 10) == 0
495+
obj = "TIC 307210830"
496+
tic_coord = SkyCoord(124.531756290083, -68.3129998725044, unit="deg")
497+
simbad_coord = SkyCoord(124.5317560026638, -68.3130014904408, unit="deg")
498+
obj_loc = mast.Mast.resolve_object(obj)
499+
assert round(obj_loc.separation(tic_coord).value, 10) == 0
498500

499-
# resolve using a specific resolver
500-
m103_loc_ned = mast.Mast.resolve_object("M103", resolver="NED")
501-
assert round(m103_loc_ned.separation(coord).value, 10) == 0
501+
# resolve using a specific resolver and an object that belongs to a MAST catalog
502+
obj_loc_simbad = mast.Mast.resolve_object(obj, resolver="SIMBAD")
503+
assert round(obj_loc_simbad.separation(simbad_coord).value, 10) == 0
504+
505+
# resolve using a specific resolver and an object that does not belong to a MAST catalog
506+
obj_loc_simbad = mast.Mast.resolve_object("M101", resolver="SIMBAD")
507+
assert round(obj_loc_simbad.separation(simbad_coord).value, 10) == 0
502508

503509
# resolve using all resolvers
504-
m103_loc_dict = mast.Mast.resolve_object("M103", resolve_all=True)
505-
assert isinstance(m103_loc_dict, dict)
506-
assert round(m103_loc_dict['NED'].separation(coord).value, 10) == 0
510+
obj_loc_dict = mast.Mast.resolve_object(obj, resolve_all=True)
511+
assert isinstance(obj_loc_dict, dict)
512+
assert round(obj_loc_dict["SIMBAD"].separation(simbad_coord).value, 10) == 0
507513

508514
# error with invalid resolver
509515
with pytest.raises(ResolverError, match="Invalid resolver"):
510-
mast.Mast.resolve_object("M103", resolver="invalid")
516+
mast.Mast.resolve_object(obj, resolver="invalid")
511517

512518
# warn if specifying both resolver and resolve_all
513519
with pytest.warns(InputWarning, match="The resolver parameter is ignored when resolve_all is True"):
514-
loc = mast.Mast.resolve_object("m103", resolver="NED", resolve_all=True)
520+
loc = mast.Mast.resolve_object(obj, resolver="NED", resolve_all=True)
515521
assert isinstance(loc, dict)
516522

517523

astroquery/mast/tests/test_mast_remote.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ def test_resolve_object(self):
6060
simbad_loc = utils.resolve_object("jw100", resolver="simbad")
6161
assert round(simbad_loc.separation(SkyCoord("83.70341477 -5.55918309", unit="deg")).value, 4) == 0
6262

63+
# Try an object from a MAST catalog with a resolver
64+
catalog_loc = utils.resolve_object("TIC 307210830", resolver="SIMBAD")
65+
assert round(catalog_loc.separation(SkyCoord("124.5317560 -68.31300149", unit="deg")).value, 4) == 0
66+
6367
# Use resolve_all to get all resolvers
6468
loc_dict = utils.resolve_object("jw100", resolve_all=True)
6569
assert isinstance(loc_dict, dict)

astroquery/mast/utils.py

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
import requests
1313
import platform
1414

15-
import astropy.coordinates as coord
15+
from astropy.coordinates import SkyCoord
1616
from astropy.table import unique, Table
17+
from astropy import units as u
1718

1819
from .. import log
1920
from ..version import version
@@ -111,49 +112,82 @@ def resolve_object(objectname, resolver=None, resolve_all=False):
111112
response : `~astropy.coordinates.SkyCoord`
112113
The sky position of the given object.
113114
"""
115+
is_catalog = False # Flag to check if object name belongs to a MAST catalog
116+
catalog = None # Variable to store the catalog name
117+
objectname = objectname.strip()
118+
catalog_prefixes = {
119+
'TIC ': 'TIC',
120+
'KIC ': 'KEPLER',
121+
'EPIC ': 'K2'
122+
}
114123

115-
# Check that resolver is valid
116-
valid_resolvers = ['ned', 'simbad']
117124
if resolver:
118-
if resolver.lower() not in valid_resolvers:
125+
# Check that resolver is valid
126+
resolver = resolver.upper()
127+
if resolver not in ('NED', 'SIMBAD'):
119128
raise ResolverError('Invalid resolver. Must be "NED" or "SIMBAD".')
120129

121130
if resolve_all:
131+
# Warn if user is trying to use a resolver with resolve_all
122132
warnings.warn('The resolver parameter is ignored when resolve_all is True. '
123133
'Coordinates will be resolved using all available resolvers.', InputWarning)
124-
resolver = None
134+
135+
# Check if object belongs to a MAST catalog
136+
for prefix, name in catalog_prefixes.items():
137+
if objectname.startswith(prefix):
138+
is_catalog = True
139+
catalog = name
140+
break
141+
142+
# Whether to set resolveAll to True when making the HTTP request
143+
# Should be True when resolve_all = True or when object name belongs to a MAST catalog (TIC, KIC, EPIC, K2)
144+
use_resolve_all = resolve_all or is_catalog
125145

126146
# Send request to STScI Archive Name Translation Application (SANTA)
127-
params = {'name': objectname, 'outputFormat': 'json', 'resolveAll': str(resolve_all).lower()}
128-
if resolver:
147+
params = {'name': objectname,
148+
'outputFormat': 'json',
149+
'resolveAll': use_resolve_all}
150+
if resolver and not use_resolve_all:
129151
params['resolver'] = resolver
130152
response = _simple_request('http://mastresolver.stsci.edu/Santa-war/query', params)
131153
response.raise_for_status() # Raise any errors
132-
result = response.json()
133-
134-
if len(result['resolvedCoordinate']) == 0:
135-
if resolver:
136-
raise ResolverError("Could not resolve {} to a sky position using {}. "
137-
"Please try another resolver or set ``resolver=None`` to use the first "
138-
"compatible resolver.".format(objectname, resolver))
139-
else:
140-
raise ResolverError("Could not resolve {} to a sky position.".format(objectname))
141-
154+
result = response.json().get('resolvedCoordinate', [])
155+
156+
# If a resolver is specified and resolve_all is False, find and return the result for that resolver
157+
if resolver and not resolve_all:
158+
resolver_result = next((res for res in result if res.get('resolver') == resolver), None)
159+
if not resolver_result:
160+
raise ResolverError(f'Could not resolve {objectname} to a sky position using {resolver}. '
161+
'Please try another resolver or set ``resolver=None`` to use the first '
162+
'compatible resolver.')
163+
resolver_coord = SkyCoord(resolver_result['ra'], resolver_result['decl'], unit='deg')
164+
165+
# If object belongs to a MAST catalog, check the separation between the coordinates from the
166+
# resolver and the catalog
167+
if is_catalog:
168+
catalog_result = next((res for res in result if res.get('resolver') == catalog), None)
169+
if catalog_result:
170+
catalog_coord = SkyCoord(catalog_result['ra'], catalog_result['decl'], unit='deg')
171+
if resolver_coord.separation(catalog_coord) > 1 * u.arcsec:
172+
# Warn user if the coordinates differ by more than 1 arcsec
173+
warnings.warn(f'Resolver {resolver} returned coordinates that differ from MAST {catalog} catalog '
174+
'by more than 0.1 arcsec. ', InputWarning)
175+
176+
return resolver_coord
177+
178+
if not result:
179+
raise ResolverError('Could not resolve {} to a sky position.'.format(objectname))
180+
181+
# Return results for all compatible resolvers
142182
if resolve_all:
143-
# Return results for all compatible resolvers
144-
coordinates = {}
145-
for res in result['resolvedCoordinate']:
146-
ra = res['ra']
147-
dec = res['decl']
148-
coordinates[res['resolver']] = coord.SkyCoord(ra, dec, unit="deg")
149-
return coordinates
150-
151-
# Return coordinates for a single resolver
152-
ra = result['resolvedCoordinate'][0]['ra']
153-
dec = result['resolvedCoordinate'][0]['decl']
154-
coordinates = coord.SkyCoord(ra, dec, unit="deg")
155-
156-
return coordinates
183+
return {
184+
res['resolver']: SkyCoord(res['ra'], res['decl'], unit='deg')
185+
for res in result
186+
}
187+
188+
# Case when resolve_all is False and no resolver is specified
189+
# SANTA returns result from first compatible resolver
190+
return SkyCoord(result[0]['ra'], result[0]['decl'], unit='deg')
157191

158192

159193
def parse_input_location(coordinates=None, objectname=None, resolver=None):

0 commit comments

Comments
 (0)