Repository: astropy/pyvo

Issue:
sia query doesn't accept scalar coordinate
@andamian this looks like a bug in how `pyvo` is handlign the `pos` keyword (or, I don't understand what's expected, but in that case it's clearly a documentation issue)

```python
>>> from astroquery.alma import Alma
>>> from astropy import coordinates
>>> from astropy import units as u
>>> galactic_center = coordinates.SkyCoord(0*u.deg, 0*u.deg, frame='galactic')
>>> Alma.query_sia(pos=galactic_center, pol='XX')

Traceback (most recent call last):
  File "<ipython-input-3-cfb9f36d29ef>", line 1, in <module>
    Alma.query_sia(pos=galactic_center, pol='XX')
  File "/Users/adam/miniconda3/envs/python3.9/lib/python3.9/site-packages/astroquery/alma/core.py", line 406, in query_sia
    return self.sia.search(
  File "/Users/adam/miniconda3/envs/python3.9/lib/python3.9/site-packages/pyvo/dal/sia2.py", line 192, in search
    return SIAQuery(self.query_ep, pos=pos, band=band,
  File "/Users/adam/miniconda3/envs/python3.9/lib/python3.9/site-packages/pyvo/dal/sia2.py", line 265, in __init__
    self.pos.add(pp)
  File "/Users/adam/miniconda3/envs/python3.9/lib/python3.9/site-packages/pyvo/dal/params.py", line 255, in add
    if item in self:
  File "/Users/adam/miniconda3/envs/python3.9/lib/python3.9/site-packages/pyvo/dal/params.py", line 276, in __contains__
    return self.get_dal_format(item) in self.dal
  File "/Users/adam/miniconda3/envs/python3.9/lib/python3.9/site-packages/pyvo/dal/params.py", line 298, in get_dal_format
    self._validate_pos(val)
  File "/Users/adam/miniconda3/envs/python3.9/lib/python3.9/site-packages/pyvo/dal/params.py", line 319, in _validate_pos
    if len(pos) == 3:
  File "/Users/adam/repos/astropy/astropy/utils/shapes.py", line 209, in __len__
    raise TypeError("Scalar {!r} object has no len()"
TypeError: Scalar 'SkyCoord' object has no len()
```

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/astropy/pyvo/pull/459?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) Report
> Merging [#459](https://app.codecov.io/gh/astropy/pyvo/pull/459?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) (90e5a68) into [main](https://app.codecov.io/gh/astropy/pyvo/commit/9e8f6024e75efc2274a670158ccac53caded352d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) (9e8f602) will **decrease** coverage by `0.03%`.
> The diff coverage is `72.72%`.

> :exclamation: Current head 90e5a68 differs from pull request most recent head 61572e5. Consider uploading reports for the commit 61572e5 to get more accurate results

```diff
@@            Coverage Diff             @@
##             main     #459      +/-   ##
==========================================
- Coverage   80.08%   80.06%   -0.03%     
==========================================
  Files          52       52              
  Lines        6020     6029       +9     
==========================================
+ Hits         4821     4827       +6     
- Misses       1199     1202       +3     
```


| [Files Changed](https://app.codecov.io/gh/astropy/pyvo/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy) | Coverage Δ | |
|---|---|---|
| [pyvo/dal/params.py](https://app.codecov.io/gh/astropy/pyvo/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy#diff-cHl2by9kYWwvcGFyYW1zLnB5) | `85.71% <72.72%> (-0.67%)` | :arrow_down: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=astropy)

bsipocz: I suppose we could use astropy regions https://astropy-regions.readthedocs.io/en/stable/index.html? 

Cc'ing @keflavich and @andamian as astroquery.alma already uses it on the user api (but then I suppose it just wraps back to this under the hood, I'll check it when I'm back at the keyboard)
keflavich: I'm not sure I understood the question, but yes, I would advocate for using astropy regions anywhere we're talking about sky regions.
bsipocz: I'm not sure how to make the RANGE and POLYGON easily work with SkyCoord as well as to support vector SkyCoord queries. Maybe the logic could be: check whether the first element is a SkyCoord. Check whether the last one if an angle. If so, do a CIRCLE query, if not, then use the vector SkyCoord elements as points in the RANGE or POLYGON?

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index ca48efe..63c634e 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -30,6 +30,8 @@
 
 - Fix handling of nan values for Time properties in SIA2 records. [#463]
 
+- Fix SIA2 search to accept SkyCoord position inputs. [#459]
+
 
 1.4.1 (2023-03-07)
 ==================
diff --git a/pyvo/dal/params.py b/pyvo/dal/params.py
index 124af28..89c1b1e 100644
--- a/pyvo/dal/params.py
+++ b/pyvo/dal/params.py
@@ -7,6 +7,7 @@ from collections.abc import MutableSet
 import abc
 
 from astropy import units as u
+from astropy.coordinates import SkyCoord
 from astropy.units import Quantity, Unit
 from astropy.time import Time
 from astropy.io.votable.converters import (
@@ -301,7 +302,7 @@ class PosQueryParam(AbstractDalQueryParam):
         entries in values are either quantities or assumed to be degrees
         """
         self._validate_pos(val)
-        if len(val) == 3:
+        if len(val) == 2 or len(val) == 3:
             shape = 'CIRCLE'
         elif len(val) == 4:
             shape = 'RANGE'
@@ -313,6 +314,7 @@ class PosQueryParam(AbstractDalQueryParam):
                 'even 6 and above (POLYGON) accepted.'.format(val))
         return '{} {}'.format(shape, ' '.join(
             [str(val.to(u.deg).value) if isinstance(val, Quantity) else
+             val.transform_to('icrs').to_string() if isinstance(val, SkyCoord) else
              str((val * u.deg).value) for val in val]))
 
     def _validate_pos(self, pos):
@@ -321,7 +323,17 @@ class PosQueryParam(AbstractDalQueryParam):
 
         This has probably done already somewhere else
         """
-        if len(pos) == 3:
+
+        if len(pos) == 2:
+            if not isinstance(pos[0], SkyCoord):
+                raise ValueError
+            if not isinstance(pos[1], Quantity):
+                radius = pos[1] * u.deg
+            else:
+                radius = pos[1]
+            if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg:
+                raise ValueError('Invalid circle radius: {}'.format(radius))
+        elif len(pos) == 3:
             self._validate_ra(pos[0])
             self._validate_dec(pos[1])
             if not isinstance(pos[2], Quantity):


Test patch:
diff --git a/pyvo/dal/tests/test_sia.py b/pyvo/dal/tests/test_sia.py
index 87ccda2..7356f0d 100644
--- a/pyvo/dal/tests/test_sia.py
+++ b/pyvo/dal/tests/test_sia.py
@@ -11,6 +11,7 @@ import pytest
 from pyvo.dal.sia import search, SIAService
 
 from astropy.io.fits import HDUList
+from astropy.coordinates import SkyCoord
 from astropy.utils.data import get_pkg_data_contents
 
 get_pkg_data_contents = partial(
@@ -45,8 +46,9 @@ def _test_result(result):
 @pytest.mark.usefixtures('sia')
 @pytest.mark.usefixtures('register_mocks')
 @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06")
-def test_search():
-    results = search('http://example.com/sia', pos=(288, 15))
+@pytest.mark.parametrize("position", ((288, 15), SkyCoord(288, 15, unit="deg")))
+def test_search(position):
+    results = search('http://example.com/sia', pos=position)
     result = results[0]
 
     _test_result(result)
diff --git a/pyvo/dal/tests/test_sia2.py b/pyvo/dal/tests/test_sia2.py
index 95df62f..de20687 100644
--- a/pyvo/dal/tests/test_sia2.py
+++ b/pyvo/dal/tests/test_sia2.py
@@ -12,6 +12,7 @@ import pytest
 from pyvo.dal.sia2 import search, SIA2Service, SIA2Query, SIAService, SIAQuery
 
 import astropy.units as u
+from astropy.coordinates import SkyCoord
 from astropy.utils.data import get_pkg_data_contents
 from astropy.utils.exceptions import AstropyDeprecationWarning
 
@@ -101,7 +102,8 @@ class TestSIA2Service:
                  (12.0 * u.deg, 34.0 * u.deg,
                   14.0 * u.deg, 35.0 * u.deg,
                   14.0 * u.deg, 36.0 * u.deg,
-                  12.0 * u.deg, 35.0 * u.deg)]
+                  12.0 * u.deg, 35.0 * u.deg),
+                 (SkyCoord(2, 4, unit='deg'), 0.166 * u.deg)]
 
     @pytest.mark.usefixtures('sia')
     @pytest.mark.usefixtures('capabilities')