@@ -66,6 +66,8 @@ def query_crossid_async(self, coordinates, *, radius=5. * u.arcsec, timeout=TIME
66
66
67
67
This query returns the nearest `primary object`_.
68
68
69
+ Note that there is a server-side limit of 3 arcmin on ``radius``.
70
+
69
71
.. _`primary object`: https://www.sdss.org/dr17/help/glossary/#surveyprimary
70
72
71
73
Parameters
@@ -124,6 +126,14 @@ def query_crossid_async(self, coordinates, *, radius=5. * u.arcsec, timeout=TIME
124
126
cache : bool, optional
125
127
If ``True`` use the request caching mechanism.
126
128
129
+ Raises
130
+ ------
131
+ TypeError
132
+ If the ``radius`` keyword could not be parsed as an angle.
133
+ ValueError
134
+ If the ``radius`` exceeds 3 arcmin, or if the sizes of
135
+ ``coordinates`` and ``obj_names`` do not match.
136
+
127
137
Returns
128
138
-------
129
139
result : `~astropy.table.Table`
@@ -193,16 +203,31 @@ def query_crossid_async(self, coordinates, *, radius=5. * u.arcsec, timeout=TIME
193
203
timeout = timeout , cache = cache )
194
204
return response
195
205
196
- def query_region_async (self , coordinates , * , radius = 2. * u .arcsec , timeout = TIMEOUT ,
206
+ def query_region_async (self , coordinates , * , radius = None ,
207
+ width = None , height = None , timeout = TIMEOUT ,
197
208
fields = None , photoobj_fields = None , specobj_fields = None , obj_names = None ,
198
209
spectro = False , field_help = False , get_query_payload = False ,
199
210
data_release = conf .default_release , cache = True ):
200
- """
201
- Used to query a circular region (a "cone search") around given coordinates.
202
-
203
- This function is equivalent to the object cross-ID (`query_crossid`),
204
- with slightly different parameters. It returns all objects within the
205
- search radius; this could potentially include duplicate observations
211
+ r"""
212
+ Used to query a region around given coordinates. Either ``radius`` or
213
+ ``width`` must be specified.
214
+
215
+ When called with keyword ``radius``, a radial or "cone" search is
216
+ performed, centered on each of the given coordinates. In this mode, internally,
217
+ this function is equivalent to the object cross-ID (`query_crossid`),
218
+ with slightly different parameters. Note that in this mode there is a server-side
219
+ limit of 3 arcmin on ``radius``.
220
+
221
+ When called with keyword ``width``, and optionally a different ``height``,
222
+ a rectangular search is performed, centered on each of the given
223
+ coordinates. In this mode, internally, this function is equivalent to
224
+ a general SQL query (`query_sql`). The shape of the rectangle is
225
+ not corrected for declination (*i.e.* no :math:`\cos \delta` correction);
226
+ conceptually, this means that the rectangle will become increasingly
227
+ trapezoidal-shaped at high declination.
228
+
229
+ In both radial and rectangular modes, this function returns all objects
230
+ within the search area; this could potentially include duplicate observations
206
231
of the same object.
207
232
208
233
Parameters
@@ -221,8 +246,17 @@ def query_region_async(self, coordinates, *, radius=2. * u.arcsec, timeout=TIMEO
221
246
radius : str or `~astropy.units.Quantity` object, optional
222
247
The string must be parsable by `~astropy.coordinates.Angle`. The
223
248
appropriate `~astropy.units.Quantity` object from
224
- `astropy.units` may also be used. Defaults to 2 arcsec.
249
+ `astropy.units` may also be used.
225
250
The maximum allowed value is 3 arcmin.
251
+ width : str or `~astropy.units.Quantity` object, optional
252
+ The string must be parsable by `~astropy.coordinates.Angle`. The
253
+ appropriate `~astropy.units.Quantity` object from
254
+ `astropy.units` may also be used.
255
+ height : str or `~astropy.units.Quantity` object, optional
256
+ The string must be parsable by `~astropy.coordinates.Angle`. The
257
+ appropriate `~astropy.units.Quantity` object from
258
+ `astropy.units` may also be used. If not specified, it will be
259
+ set to the same value as ``width``.
226
260
timeout : float, optional
227
261
Time limit (in seconds) for establishing successful connection with
228
262
remote server. Defaults to `SDSSClass.TIMEOUT`.
@@ -256,6 +290,16 @@ def query_region_async(self, coordinates, *, radius=2. * u.arcsec, timeout=TIMEO
256
290
cache : bool, optional
257
291
If ``True`` use the request caching mechanism.
258
292
293
+ Raises
294
+ ------
295
+ TypeError
296
+ If the ``radius``, ``width`` or ``height`` keywords could not be parsed as an angle.
297
+ ValueError
298
+ If both ``radius`` and ``width`` are set (or neither),
299
+ or if the ``radius`` exceeds 3 arcmin,
300
+ or if the sizes of ``coordinates`` and ``obj_names`` do not match,
301
+ or if the units of ``width`` or ``height`` could not be parsed.
302
+
259
303
Examples
260
304
--------
261
305
>>> from astroquery.sdss import SDSS
@@ -277,16 +321,69 @@ def query_region_async(self, coordinates, *, radius=2. * u.arcsec, timeout=TIMEO
277
321
The result of the query as a `~astropy.table.Table` object.
278
322
279
323
"""
280
- request_payload , files = self .query_crossid_async (coordinates = coordinates ,
281
- radius = radius , fields = fields ,
282
- photoobj_fields = photoobj_fields ,
283
- specobj_fields = specobj_fields ,
284
- obj_names = obj_names ,
285
- spectro = spectro ,
286
- region = True ,
287
- field_help = field_help ,
288
- get_query_payload = True ,
289
- data_release = data_release )
324
+ # Allow field_help requests to pass without requiring a radius or width.
325
+ if field_help and radius is None and width is None :
326
+ radius = 2.0 * u .arcsec
327
+
328
+ if radius is None and width is None :
329
+ raise ValueError ("Either radius or width must be specified!" )
330
+ if radius is not None and width is not None :
331
+ raise ValueError ("Either radius or width must be specified, not both!" )
332
+
333
+ if radius is not None :
334
+ request_payload , files = self .query_crossid_async (coordinates = coordinates ,
335
+ radius = radius , fields = fields ,
336
+ photoobj_fields = photoobj_fields ,
337
+ specobj_fields = specobj_fields ,
338
+ obj_names = obj_names ,
339
+ spectro = spectro ,
340
+ region = True ,
341
+ field_help = field_help ,
342
+ get_query_payload = True ,
343
+ data_release = data_release )
344
+
345
+ if width is not None :
346
+ width = u .Quantity (width , u .degree ).value
347
+ if height is None :
348
+ height = width
349
+ else :
350
+ height = u .Quantity (height , u .degree ).value
351
+
352
+ dummy_payload = self ._args_to_payload (coordinates = coordinates ,
353
+ fields = fields ,
354
+ spectro = spectro , region = True ,
355
+ photoobj_fields = photoobj_fields ,
356
+ specobj_fields = specobj_fields , field_help = field_help ,
357
+ data_release = data_release )
358
+
359
+ sql_query = dummy_payload ['uquery' ].replace ('#upload u JOIN #x x ON x.up_id = u.up_id JOIN ' , '' )
360
+
361
+ if 'SpecObjAll' in dummy_payload ['uquery' ]:
362
+ sql_query = sql_query .replace ('ON p.objID = x.objID ' , '' ).replace (' ORDER BY x.up_id' , '' )
363
+ else :
364
+ sql_query = sql_query .replace (' ON p.objID = x.objID ORDER BY x.up_id' , '' )
365
+
366
+ if (not isinstance (coordinates , list ) and not isinstance (coordinates , Column )
367
+ and not (isinstance (coordinates , commons .CoordClasses ) and not coordinates .isscalar )):
368
+ coordinates = [coordinates ]
369
+
370
+ rectangles = list ()
371
+ for target in coordinates :
372
+ # Query for a rectangle
373
+ target = commons .parse_coordinates (target ).transform_to ('fk5' )
374
+ rectangles .append (self ._rectangle_sql (target .ra .degree , target .dec .degree , width , height = height ))
375
+
376
+ rect = ' OR ' .join (rectangles )
377
+
378
+ # self._args_to_payload only returns a WHERE if e.g. plate, mjd, fiber
379
+ # are set, which will not happen in this function.
380
+ sql_query += f' WHERE ({ rect } )'
381
+
382
+ return self .query_sql_async (sql_query , timeout = timeout ,
383
+ data_release = data_release ,
384
+ cache = cache ,
385
+ field_help = field_help ,
386
+ get_query_payload = get_query_payload )
290
387
291
388
if get_query_payload or field_help :
292
389
return request_payload
@@ -510,7 +607,7 @@ class = 'galaxy' \
510
607
if data_release > 11 :
511
608
request_payload ['searchtool' ] = 'SQL'
512
609
513
- if kwargs .get ('get_query_payload' ):
610
+ if kwargs .get ('get_query_payload' ) or kwargs . get ( 'field_help' ) :
514
611
return request_payload
515
612
516
613
url = self ._get_query_url (data_release )
@@ -1163,5 +1260,58 @@ def _get_crossid_url(self, data_release):
1163
1260
self ._last_url = url
1164
1261
return url
1165
1262
1263
+ def _rectangle_sql (self , ra , dec , width , height = None , cosdec = False ):
1264
+ """
1265
+ Generate SQL for a rectangular query centered on ``ra``, ``dec``.
1266
+
1267
+ This assumes that RA is defined on the range ``[0, 360)``, and Dec on
1268
+ ``[-90, 90]``.
1269
+
1270
+ Parameters
1271
+ ----------
1272
+ ra : float
1273
+ Right Ascension in degrees.
1274
+ dec : float
1275
+ Declination in degrees.
1276
+ width : float
1277
+ Width of rectangle in degrees.
1278
+ height : float, optional
1279
+ Height of rectangle in degrees. If not specified, ``width`` is used.
1280
+ cosdec : bool, optional
1281
+ If ``True`` apply ``cos(dec)`` correction to the rectangle.
1282
+ Otherwise, rectangles become increasingly triangle-like
1283
+ near the poles.
1284
+
1285
+ Returns
1286
+ -------
1287
+ :class:`str`
1288
+ A string defining the rectangle in SQL notation.
1289
+ """
1290
+ if height is None :
1291
+ height = width
1292
+ dr = width / 2.0
1293
+ dd = height / 2.0
1294
+ d0 = dec - dd
1295
+ if d0 < - 90 :
1296
+ d0 = - 90.0
1297
+ d1 = dec + dd
1298
+ if d1 > 90.0 :
1299
+ d1 = 90.0
1300
+ ra_wrap = False
1301
+ r0 = ra - dr
1302
+ if r0 < 0 :
1303
+ ra_wrap = True
1304
+ r0 += 360.0
1305
+ r1 = ra + dr
1306
+ if r1 > 360.0 :
1307
+ ra_wrap = True
1308
+ r1 -= 360.0
1309
+ # BETWEEN is inclusive, so it is equivalent to the <=, >= operators.
1310
+ if ra_wrap :
1311
+ sql = f"(((p.ra >= { r0 :g} ) OR (p.ra <= { r1 :g} ))"
1312
+ else :
1313
+ sql = f"((p.ra BETWEEN { r0 :g} AND { r1 :g} )"
1314
+ return sql + f" AND (p.dec BETWEEN { d0 :g} AND { d1 :g} ))"
1315
+
1166
1316
1167
1317
SDSS = SDSSClass ()
0 commit comments